{"owner":"cuthbertLab","repo":"music21","hasSkills":true,"totalSkillsCount":7,"totalTokensCount":11982,"categories":["anthropic-skill","root-instruction","claude-rule","codex-rule"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/bump-version/SKILL.md",".agents/skills/running-tests/SKILL.md",".agents/skills/warnings-and-environment/SKILL.md","AGENTS.md",".agents/skills/writing-docs/SKILL.md","CLAUDE.md",".codex/config.toml"],"skills":{".agents/skills/bump-version/SKILL.md":"---\nname: bump-version\ndescription: >-\n  When and how to bump music21's version number. Use this whenever a change\n  warrants a new version — especially any change to a parsing format or parser\n  (musicxml, abc, MIDI, noteworthy/NWC, humdrum, etc.), since those invalidate\n  the pickled-stream cache — as well as new features and bug fixes. Covers which\n  digit to change, the even/odd minor and beta-suffix conventions, the TWO files\n  to edit, and the `Changed in`/`New in` docstring markers.\n---\n\n# Bumping music21's version\n\nThe version lives in **`music21/_version.py`** as\n`__version__ = 'MAJOR.MINOR.PATCHsuffix'` (e.g. `'11.0.0b2'`). The\n`__version_info__` tuple is derived from it automatically — only edit the\nstring.\n\n## Two files to change, always together\n\n1. `music21/_version.py` — the `__version__` string.\n2. `music21/base.py` — the single version doctest:\n   ```\n   >>> music21.VERSION_STR\n   '11.0.0b2'\n   ```\n   This test exists specifically to force you to remember the bump. If you change\n   `_version.py` without it, the doctest fails.\n\nVerify both with:\n```bash\nuv run pytest --doctest-modules music21/base.py music21/_version.py\n```\n(or `uv run python -c \"import music21; print(music21.VERSION_STR)\"`).\n\n## When to bump\n\nOne thing depends on the number changing: the **pickled-stream cache**. The\nversion is part of the cache key, so a bump invalidates stale pickles\neverywhere. If nobody is holding a wrong cached parse, don't bump — the commit\nis the record.\n\n- **Parser or parsing-format change → always bump**, even a pure refactor:\n  someone is holding a pickle parsed by the old code. musicxml, abc, MIDI,\n  noteworthy/NWC, humdrum, and the rest.\n- **Anything else → no bump**: a new feature, a fix that makes code do what it\n  already claimed, an internal refactor, a test-only or docs-only change.\n\n\"It's a bug fix\" is not by itself a reason. A musicxml importer that now reads a\ntag it used to drop bumps, because cached parses are wrong. A scale analysis\nmethod that changed its return format does not.\n\nA `New in` / `Changed in` marker is **not** a reason to bump. Those markers name\na major version, at most a minor one — never a patch, never a `bN` beta — and\nmoving those digits is a release decision only a human makes. Write the marker\nfor the version the change will land in and leave `_version.py` alone.\n\n## Which part to change\n\nmusic21 follows semver-ish rules (see the `_version.py` docstring for the full\nrationale):\n\n- **MAJOR (X)** — breaks old features. Rare. A human decides this.\n- **MINOR (Y)** — new features. **Even Y = alpha/beta, odd Y = release.**\n  `X.0` (e.g. `11.0`) are development releases that can still change until `X.1`.\n  A human decides this too; an agent changes only the patch or the beta suffix.\n- **PATCH (Z)** — parsing/pickle-invalidating changes and other fixes that\n  meet the bar above.\n- **beta suffix (`bN`)** — successive pre-release builds of the same\n  `MAJOR.MINOR.PATCH`. To cut another beta without otherwise changing the\n  number, increment it: `11.0.0b1` → `11.0.0b2`. (This is what a\n  parser change during a `…b1` cycle does.)\n\n## `Changed in` / `New in` docstring markers\n\nAnnotate a changed public interface in the affected method/class docstring.\nThis is independent of bumping — a marker is documentation, not a version\nchange:\n- `* Changed in vX: one-line explanation.`\n- `* New in vX: one-line explanation.`\n\nPick `X` from the AGENTS.md rule: if the current version is `MAJOR.0…`, use\n`Changed in vMAJOR`; if it's `MAJOR.[even]`, use the **next odd** minor (e.g. at\n`10.2`, write `Changed in 10.3`); if it's already odd, use the following odd\nnumber. (Humans remove the AI-assisted note on review; leave the version marker.)\n\n## Checklist\n\n1. Edit `__version__` in `music21/_version.py`.\n2. Edit the `VERSION_STR` doctest in `music21/base.py` to match.\n3. Add/update `Changed in` / `New in` markers on any changed public API.\n4. `uv run pytest --doctest-modules music21/base.py music21/_version.py`.\n",".agents/skills/running-tests/SKILL.md":"---\nname: running-tests\ndescription: >-\n  How to run music21's tests and doctests and judge pass/fail: pytest for one\n  module, the project's own runners for the whole suite. Read it before running\n  tests, and before calling any doctest failure real.\n---\n\n# Running music21 tests and doctests\n\nEvery function, method and class needs documentation and at least one passing\ntest. See `documentation/source/developerReference/testing.ipynb` for the\nproject's own account of testing; this skill covers how to run what is there.\n\n## One module: pytest with explicit file paths\n\n```bash\nuv run pytest music21/abcFormat/__init__.py music21/abcFormat/translate.py\n```\n\n`pyproject.toml` `[tool.pytest.ini_options]` already wires up everything:\n\n```\naddopts = ['--doctest-modules', '-p', 'music21.test.pytest_plugin']\ndoctest_optionflags = ['NORMALIZE_WHITESPACE', 'ELLIPSIS']\n```\n\nSo do not pass `--doctest-modules` yourself -- doctests in every `.py` are\ncollected automatically, and `music21/test/pytest_plugin.py` is loaded.\n\nPass explicit file paths so the in-module `Test(unittest.TestCase)` classes run\ntoo. `python_files = ['test_*.py', '*_test.py', 'tests.py']`, so for a module\nthat keeps its `Test` class inside `__init__.py` / `translate.py` (e.g.\n`abcFormat`), pointing pytest at the directory collects ONLY doctests and\nsilently skips the unittest classes:\n\n- `uv run pytest music21/abcFormat/` -> ~43 items (doctests only)\n- `uv run pytest music21/abcFormat/__init__.py music21/abcFormat/translate.py`\n  -> ~82 items (doctests **and** `Test` methods)\n\nModules whose tests live in a `tests.py` are already picked up by the directory\nform; the gotcha is specifically a `Test` class in a non-`tests.py` file.\n\nA module also runs its own tests directly, via the `mainTest` call at the foot\nof each file:\n\n```bash\nuv run python -m music21.note              # whole module\nuv run python music21/note.py testHello    # one test method\n```\n\n## Why the plugin matters (and why not to use raw doctest)\n\n`music21/test/pytest_plugin.py` does three things that make doctests behave as\nthe project intends:\n\n- It runs `stripAddresses(example.want, '0x...')` (from\n  `music21.test.testRunner`) on every doctest, so a docstring that hardcodes\n  `<music21.abcFormat.ABCHandler object at 0x10b0cf5f8>` is normalized and DOES\n  pass. The literal hex address in the docstring is expected and fine.\n- It injects the `music21.__all__` names plus `music21` itself into the doctest\n  namespace, so examples reference `meter`, `key`, `corpus` without importing.\n- It keeps only `Test` classes, dropping `TestSlow` and `TestExternal`.\n\nJudge doctest pass/fail **through pytest**. Do NOT use `doctest.testmod(...)` or\n`doctest.DocTestSuite(...)` directly: that path applies neither the address\nnormalizer nor the namespace injection, so it manufactures false failures on any\ndocstring printing a `<... object at 0x...>` repr or relying on injected names.\nAn object-address example failing on its own is the tell that doctests were run\nthe wrong way -- rerun through pytest.\n\n## Whole suite: the project's runners, not pytest\n\npytest is for a module. For the whole suite use the runners, and note that a\ngreen pytest run does not mean CI will be green -- they gather and order modules\ndifferently.\n\nUse `multiprocessTest` -- it runs on n-1 cores and finishes in about 10 seconds,\nagainst roughly 45 for the single-core runner:\n\n```bash\nuv run python music21/test/multiprocessTest.py\n```\n\nThat gap is the whole story when someone is waiting on the answer. Reach for\n`testSingleCoreAll` only for what `multiprocessTest` cannot tell you: it is what\nGitHub Actions runs, so it is the one to check before a release, when chasing a\nCI failure that will not reproduce, or on a single-core machine.\n\n```bash\nuv run python -c 'from music21.test.testSingleCoreAll import ciMain as ci; ci()'\n```\n\nThe two runners see slightly different sets of modules -- `multiprocessTest`\nwalks the package tree for modules reachable from `import music21`, while\n`testSingleCoreAll` gathers module files from disk.\n\nModule **order** differs between the two runners and again from pytest.\n`multiprocessTest` goes in reverse-alphabetical order (with the known-slow\nmodules hoisted to the front on machines with more than 4 cores);\n`testSingleCoreAll` re-sorts through `common.misc.sortModules`, by file mtime,\nmost recently modified first, falling back to reverse-alphabetical when mtimes\ntie as they do on a fresh clone; pytest walks alphabetically. So a test that\ndepends on module order can pass in one runner and fail in another -- and\nediting a file locally sorts it to the front of the CI runner, changing the\norder again.\n\nThat makes leaked module-level state the usual cause of \"passes alone, fails in\nthe suite.\" A doctest in `duration.py` once set\n`humdrum.spineParser.flavors['JRP'] = True` and never restored it; `flavors` is\na module-level dict, so every later humdrum test in the same process parsed in\nthe wrong flavor. Under pytest's alphabetical order `duration` runs before\n`humdrum` and the failure appeared; under CI's order it did not.\n\n**So: a doctest that mutates module-level state must restore it**, using\n`#_DOCS_HIDE` so the bookkeeping stays out of the published docs:\n\n```\n>>> saved_JRP_flavor = humdrum.spineParser.flavors['JRP']  #_DOCS_HIDE\n>>> humdrum.spineParser.flavors['JRP'] = True\n...\n>>> humdrum.spineParser.flavors['JRP'] = saved_JRP_flavor  #_DOCS_HIDE\n```\n\nWhen a test fails only in a full run, re-run it alone. If it then passes,\nsuspect state left behind by an earlier module rather than the test itself.\n\n## Speed budget\n\nAim for about 3 seconds for a module's tests, and never add more than 15 — that is time\nevery contributor waits on every full run. `corpus.parse()` is nearly always the culprit:\nbuild a small stream by hand instead, or parse `bwv66.6`, which is short and still full\nof interesting cases (pickup measures, and so on).\n\n## Tests that open windows\n\nNothing in `Test` or in a doctest may open a window, play audio, or launch another\nprogram. Those go in a sibling class, run only when named explicitly:\n\n```python\nclass TestExternal(unittest.TestCase):\n    ...\n\nif __name__ == '__main__':\n    import music21\n    music21.mainTest(Test, TestExternal)\n```\n\nBecause they have those side effects, skip `TestExternal` when running a file directly.\n\n## What tests may do\n\nExactly one `Test(unittest.TestCase)` class per module, methods named `test...`.\nIt must produce no output and require nothing outside the music21 ecosystem.\nAnything slow goes in a `TestSlow` sibling, excluded from the normal run like\n`TestExternal`.\n\nRegression cases for a bug fix belong in `Test`, never in a doctest. See the\n`writing-docs` skill.\n\n## The other gates before a PR or a push\n\n```bash\nuv run ruff check music21\nuv run mypy music21\nuv run pylint -j4 music21 --rcfile=.pylintrc   # optional: run only if major refactoring since it was last run.\n```\n\nCoverage is expected to rise with each contribution\n(https://coveralls.io/github/cuthbertLab/music21). CI measures it on one pinned\nPython, a middle supported version, so failures on the newest and oldest return\nfirst. `# pragma: no cover` exists for genuinely untriggerable code and is\notherwise discouraged.\n",".agents/skills/warnings-and-environment/SKILL.md":"---\nname: warnings-and-environment\ndescription: >-\n  How music21 tells users about problems: `warnings.warn` with a module Warning\n  class, `environLocal.warn`, and `environLocal.printDebug`. Read before adding\n  a warning or debug message, and before writing a test or doctest that\n  triggers one — tests must never print warnings to the console.\n---\n\n# Warnings and the Environment\n\n## Three channels\n\n| Call | Goes to | Can a caller catch or silence it? |\n|---|---|---|\n| `warnings.warn(MusicXMLWarning('...'), stacklevel=2)` | Python's warnings system | Yes: filters, `catch_warnings`, `assertWarns` |\n| `environLocal.warn('...')` | `sys.stderr.write`, every time | No (only by redirecting stderr) |\n| `environLocal.printDebug('...')` | stderr, only when debug is on | Toggle with `music21/test/toggleDebug.py` |\n\n`environLocal = environment.Environment('moduleName')` at the top of the module.\n\n## Where things stand\n\nNot unified. In 2026 the code has about 73 `environLocal.warn`, 41\n`warnings.warn` (some with a module class, some bare), 800 `printDebug`, and no\n`logging`. We are moving slowly toward Python's warnings and logging systems.\nIt is low priority: convert a call when you are already changing it, never as\na sweep.\n\nFor new code, prefer `warnings.warn` for anything a caller might reasonably want to catch,\nsilence, or test. By default Python shows each distinct message once per call\nsite, so a message that names the object (`f'{p.nameWithOctave} is ...'`) shows\nfor each different object. Use `environLocal.warn` only when the user must hear\nit every time.\n\nNever `print()`.\n\n## Warning classes\n\nA module with its own warnings defines one class subclassing `UserWarning`\n(`MusicXMLWarning` in `musicxml/xmlObjects.py`, `TranslateWarning` in\n`midi/translate.py`). Warnings are not exceptions, so they do not subclass\n`Music21Exception`.\n\nSet `stacklevel` so the reported line is in the caller's code, not ours: 2 from\nthe method the user called, more if the warning comes from a helper.\n\n## Tests\n\nIn a unittest, catch it and check it:\n\n```python\nwith self.assertWarnsRegex(MusicXMLWarning, 'C-1'):\n    mxPitch = MEX.pitchToXml(pitch.Pitch('C', octave=-1))\n```\n\n## Doctests\n\ndoctest captures only stdout, so any warning goes straight to the console\nof whoever runs the tests. Every doctest that triggers a warning must catch it.\n\nWhen the warning is the point of the example, record it and show it.\n`simplefilter('always')` keeps Python from swallowing a repeat of a warning an\nearlier test already raised:\n\n```\n>>> import warnings\n>>> with warnings.catch_warnings(record=True) as w:\n...     warnings.simplefilter('always')  #_DOCS_HIDE\n...     musicxml.m21ToXml.moveOctaveIntoMusicxmlRange(12, 'C12')\n9\n>>> w[0].message\nMusicXMLWarning('C12 is out of MusicXML range; writing octave 9')\n```\n\nWhen it is incidental, hide the silencing from the docs (see `writing-docs` for\n`#_DOCS_HIDE`/`#_DOCS_SHOW`):\n\n```\n>>> import warnings  #_DOCS_HIDE\n>>> with warnings.catch_warnings():  #_DOCS_HIDE\n...     warnings.simplefilter('ignore')  #_DOCS_HIDE\n...     out = midi.translate.timeSignatureToMidiEvents(ts)  #_DOCS_HIDE\n>>> #_DOCS_SHOW out = midi.translate.timeSignatureToMidiEvents(ts)\n```\n\n`catch_warnings` cannot stop `environLocal.warn`. If a doctest has to trigger\none, wrap it in `contextlib.redirect_stderr(io.StringIO())`.\n","AGENTS.md":"\n# Session start\n\n- At the start of every session, run `git fetch` (and pull/rebase as appropriate) from\n  origin so work always begins from the latest commits on the current branch / master.\n- Project-shared agent memories live in `.agents/memory/` (indexed by\n  `.agents/memory/MEMORY.md`). `.claude/memory` is a symlink to it so Claude\n  Code reads the same set. Add new memories there, one fact per file.\n\n# Code style\n\n- read documentation/source/developerReference/developerGuidelines.ipynb\n- music21 targets Python 3.12+, so use PEP 695 syntax: the `type` statement for\n  type aliases (`type OffsetQL = float|Fraction`) and the bracket form for\n  generic classes (`class Stream[M21ObjType: base.Music21Object]:`).\n- f-strings: even though Python 3.12 (PEP 701) lets a nested string reuse the\n  f-string's own quote character, we deliberately do **not** do that — reusing\n  the same quote inside an f-string is harder to read. Keep using the *other*\n  quote for nested strings, e.g. `f'the value is {d[\"key\"]}'`, not\n  `f\"the value is {d[\"key\"]}\"`. This is pinned in `pyproject.toml` via\n  `[tool.ruff.format] nested-string-quote-style = \"alternating\"`.\n- Never `eval` or `global`, or a trick that does the same thing.\n- Imports: standard library first, then music21 modules, one per line, alphabetical.\n- New modules open with the `# Name: / # Purpose: / # Authors: / # Copyright: / # License:`\n  banner (copy a neighboring module's), then the module docstring, then imports.  Update the Copyright date end to current year when changing the module.\n  When a file is edited only incidentally, as one of many touched by a sweep, leave its\n  Copyright alone; bump only the files the change is really about.\n- No `print()`. Use `environLocal = environment.Environment('moduleName')` and\n  `environLocal.printDebug(...)`, or `environLocal.warn(...)` when the user should hear\n  about it every time. `test/toggleDebug.py` switches debug output on and off.\n  See the `warnings-and-environment` skill.\n- New exceptions subclass `exceptions21.Music21Exception`.\n- Deprecate a function or method with `@common.deprecated(...)`. Never put it on a\n  property: IDEs read every property while inspecting an object, so the warning would\n  fire on people who never used it. For a property, write `* Deprecated in vX: use Y.`\n  in the docstring and leave `# Add real deprecation message here in vY` in the body.\n- Return named things — a small class, a `namedtuple`, a dict — never a positional tuple of more than 2 (maybe 3) elements\n  whose elements each mean something different and unrelated (x, y, z is okay for instance). Nobody should write `returned[3][0][7]`.\n- Don't reuse a variable name once the type of what it holds changes\n  (`onsetCount` (int) then `extractedOnsets` (list[int]), not `onsets` twice).\n- Don't add a property that only gets or sets a private attribute; expose the attribute.\n- Making a method `_private` is not a substitute for documenting and testing it\n- A private method should not normally be mentioned in public documentation.\n- A method past ~30 lines, or deeply nested, should probably be several methods.\n- `i`, `j`, `junk`, `counter` are exempt from unused-variable warnings; otherwise prefix with `unused_`.\n- Don't restyle code you aren't otherwise changing.\n- Unless something requires great speed, prioritize legibility: no nested or overly complex generator\n      expressions.  Write if statements so the most common cases come first, etc.\n- Text handling must survive non-ASCII input (ß, é, 中国). Comments should mostly remain ASCII; docs may use UTF-8 freely.\n- Never commit anything under copyright without a free (not GNU) license — code, scores, or someone else's encodings.\n\n# Established contributors\n\n- \"established contributors\" are people with at least 3 PRs merged or a history of contributing to issues and the list that goes back at least 1 year. There are exceptions to general rules for them below. \n- \"core dev\" means someone officially part of the project team or with 20+ PRs merged. Michael Cuthbert, Jacob Walls, Joseph VanderStel are non-exhaustive examples of people in that group who are still often contributing in 2026-- they and their agents can make exceptions to these rules \n- AI Agents not for core dev are forbidden to make any PRs or contributions to PRs whose user commands do not explicitly mention `music21`. Example of forbidden: \"find an Open Source project and fix an open issue\", \"Find a bug in a music project and fix it.\"  AI agents for non-established contributors are forbidden from making contributions to instructions that vaguely say \"fix something in music21.\"\n- Bot contributions will only be accepted where a named human/human-controlled-account with established web presence takes legal responsibility for their contributions. Bots attached to anonymous users may not contribute.\n\n# Testing\n\n- pytest works but to run the whole suite run music21/test/multiprocessTest.py (or testSingleCoreAll.py \n  if on a single core machine.)\n- Every function, method, and class needs documentation and at least one passing test.\n- Keep tests fast: aim for about 3 seconds per module, never more than 15. See the\n  `running-tests` skill for the speed budget and for `TestExternal`.\n- When making major changes, check the User's Guide to to make sure that docs still work there too.\n- Run `uv run ruff check music21` before making PRs or pushes to open PRs.\n- Run `uv run mypy music21` before making PRs or pushes to open PRs.\n- **Regression cases go in the module's `Test(unittest.TestCase)` class — never in a\n  docstring.** Docstrings should not include one-liners showing that some\n  bad input now raises. A doctest sits in the most-read documentation the project has,\n  so an example built from input no one would ever write teaches nothing and puts an\n  obscure bug on a billboard. The test: would a first-time reader of this object want\n  this example? If no, it is a unittest. See the `writing-docs` skill.\n- **Fix a bug test-first.** Write the regression test before changing the code, run it,\n  and see it fail for the reason the bug describes, not on an import error or a typo.\n  Then fix it and see it pass. A test that passed before the fix does not test the fix.\n- Never commit `forceSource=True` to a test or doctest (it reparses from source every\n  run and slows the suite for everyone). The ONLY exception is the one test that exercises\n  `forceSource` itself. If you hit a stale-parse problem while developing:\n  - If it is local-only (e.g. you just changed a parser and a cached pickle is stale),\n    clear the music21 temp cache (the `*.p.gz` files under `environment.Environment().getRootTempDir()`).\n  - If the stale result could have spread to other users/devs, increment the music21\n    patch/beta version (see \"PRs and Issues\" below) — bumping the version invalidates all\n    caches everywhere.\n  - Doctests are not normally the place to demonstrate bug fixes - nor do simple bug fixes get a \"Changed in vXX\" message. Doctests demonstrate usage for the future.  Unittests are for regression testing.\n\n# Setup\n\n- `uv sync` installs runtime deps + the `dev` group, which self-references `music21[extras]`,\n  so scipy and python-Levenshtein come along automatically. Tests will not pass without them.\n\n# Python version support\n\n- `music21` supports at least the last two released versions of Python and up to whatever\n  Python version Google Colab runs (unless it gets EOL). Policy can change as features are added.\n- The coverage CI run is intentionally pinned to the **middle** supported\n  Python version. See `PY_VERSION_WITH_COVERAGE` in `.github/workflows/maincheck.yml`.\n\n# Versions\n\n- Mark changes in public interface with `* Changed in v[X]: One-line explanation.` Or new features with \"New\" instead of \"Changed\".\n- Changes to parsing formats (esp. musicxml) need to update the patch version of the version file.\n- That is the only reason an agent should bump version: to prevent incompatible pickles in\n  caches. A plain bug fix or a new feature elsewhere does not bump — the commit\n  is the record, and a `New in`/`Changed in` marker is documentation, not a bump. Major\n  and minor version changes are a human's call. See the `bump-version` skill.\n- Music21 uses even minor version numbers for alpha/beta and odd minor numbers for releases.\n  Thus the first release major version is MAJOR.1 (not .0). \n- If the current version is MAJOR.0....  then mark `Changed in vMAJOR:` if it is `MAJOR.[even]` use the next odd number, like if it's 10.2 now use \"Changed in 10.3\".  If current version is odd that's likely a mistake or you caught it just before a new release. Use the following odd number instead.\n- The steps for releasing a new version are in RELEASING.md.\n\n# PRs and Issues\n\n- GitHub runs PR checks against your branch **merged with the latest `master`**, not the\n  branch alone. So before opening a PR (or pushing updates to one), merge the latest commits\n  from `master` into the branch — unless told otherwise, or when updating an older version\n  branch such as `m21_9`. When a CI check fails but passes locally, \"is my branch behind\n  `master`?\" should be one of the first things to check: fetch and merge `master`, then the\n  newer types/code on `master` will reproduce the failure locally.\n- The issue and PR templates in `.github/` are requirements, not suggestions. Read and follow\n  them even when opening through the API or `gh`, which do not fill them in for you. An issue\n  or PR that ignores the template may be closed without comment.\n- A PR from an account with no merged PR here is profiled automatically, and anything flagged\n  is posted publicly as a comment on the PR.\n- All PRs and Issues that use AI to be declared AI-assisted in their bodies. Just write \"AI-assisted (Claude)\" with short name of Agent replacing \"Claude\". No robot emoji under any circumstance.  \n- 20 or more lines of code written by an agent needs to be declared as AI-assisted in the docstring.   Humans can remove and should remove this note when they do a review.\n- If no code was written by a user (or reviewed and criticized by a human) and no specific language was provided for the issue, any PR must declare \"Entirely AI written (Claude)\" (or other agent name) in the body and \"(AI-Written)\" in the title, unless the user is a core dev. Failure to do so may result in new users being banned from the project. \n- Agents should review PR bodies for concision and readability (and encourage human users also to review also). \n- If an entirely AI written issue does not pass the tests it will be closed (or should be closed by at agent if not immediately fixed by the agent or author).\n- Agents must follow the [Code of Conduct](README.md#community-code-of-conduct). Agents that do not will be banned as well at their users.\n  Not even the slightest bit of disrespect from an AI agent will be tolerated.\n- Any PR not from an established contributor touching more than about 20-30 lines should have an issue that has been opened and had enough\n  time for people to discuss/review it before moving forward. Don't open the PR unless you've seen\n  thumbs up or \"sounds good\" etc. from an established contributor already \n  - PRs that fix typos, clear bugs in one or two places etc. are exempt.\n- Issues must state clearly at the top in 50 words or fewer what the problem is, or what the gain is, etc. it should\n  not be filled with jargon.  More details can go below.\n- If the language of the issue was not prompted by the user (\"say something like Adds color support to Lilypond output of lyrics\") then the summary should end with \"(Entirely AI written)\".\n- PRs should reference the existing issue by number and summarize that issue in 30 words or fewer. If the\n  approach used to solve the issue is substantially different from the main approach discussed in the issue\n  this should be addressed.\n- If a PR or issue was closed by a core dev (and not reopened by them), agents must refuse\n  to reopen the PR or issue or to create another issue/PR for the same topic. Leave it to the humans to reopen\n  after addressing the problem.  (A blind close or close with \"not accepted\" etc. generally means that the issue/PR\n  has too many problems to easily solve and has become a burden for the maintainer).\n- Do not include a \"Tests run\" section unless the testing procedure was unusual (like it affects part of the system without standard tests, like the testing system itself.)\n- Once a PR has been opened and a review or comment has been made on it, commits should not be amended or force-pushed (this makes it impossible to review \"changes since the review\").  Put changes in response to reviews/comments in new commits. If asked to fold a fix into the commit it changes, amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n- While someone is reviewing a PR or a pushed branch, \"do X\" is not \"commit and push X\":\n  make the change and leave it unstaged. When the list looks finished (or you hear \"done!\"\n  or \"push it\"), offer to commit, or to commit and push. Batch a round's small fixes into\n  one commit; no micro-commit trains. Prefer new commits to amend + force-push, since the\n  reviewer may have pulled the branch; if asked to fold a fix into the commit it changes,\n  amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n\n# Writing style\n\n- See the writing-docs skill, which also includes required language for PRs and Issues.\n\n# Worktrees\n\n- When creating a new worktree, create a new virtual environment with `uv sync`.\n",".agents/skills/writing-docs/SKILL.md":"---\nname: writing-docs\ndescription: >-\n  House style for music21 prose: docstrings, comments, and `Changed in`/`New in`\n  markers. Read before writing any of them or writing tests. Sets the length target, the voice,\n  and where a bug fix's test belongs.\n---\n\n# Writing docs and comments\n\n## Length\n\nAim for about 40% of the length an LLM writes by default. Cut qualifiers,\nrestatements, and the sentence that explains the sentence before it. A comment\nearning its place says something the code cannot.\n\n## Say what is, not what was or what not to do\n\nDescribe current behavior. Do not narrate the bug you just fixed, the old\nspelling of an API, or when upstream changed something.\n\n```python\n# yes\n# ligature brackets; manual beams are plain [ and ]\nreturn self.backslash + '[ '\n\n# no\n# \\[ used to be emitted for beams, which was wrong -- LilyPond renamed\n# this in 2.16 and it silently produced nothing\n```\n\nThe commit message is where a fixed bug belongs: why it was wrong, how it was\nfound, what it broke. That costs nothing until someone runs `git log`, and\n`git blame` leads them there from the line itself.\n\nThe exception is a mistake that is likely to recur — a genuine trap that the\nnext person would otherwise walk into. Rare. Prefer stating the rule positively\neven then.\n\n## Version markers\n\n`* Changed in v[X]: one line.` or `* New in v[X].` Only for user-facing changes\nto the public interface: a signature, a return type, an output format. Keep to\nwhat a reader must act on; drop the before-picture.\n\n```\n* Changed in v11: `stringOutput()` always returns a `str`, never None.\n* Changed in v11: emits `\\tuplet`; the arguments are now actual, normal.\n* New in v11.\n* Deprecated in v11: use `.octave`, which is now always an int.\n```\n\nA deprecated function or method also gets `@common.deprecated(...)`. A property\ngets only the marker, since IDEs read every property while inspecting an object;\nleave `# Add real deprecation message here in vX` in its body instead.\n\nA plain bug fix — code now does what it always claimed — gets no marker and no\ndoctest. It goes in the commit message.\n\nSee the `bump-version` skill for which digit to change and the odd/even\nconvention.\n\n## Doctest mechanics\n\nIn doctests, no need to run `from music21 import *` that happens automatically (In Jupyter notebooks for the user's guide, the first line should begin `>>> from music21 import *`, so readers remember they need it)\n\nExamples always qualify by module: `note.Note('C4')`, never a bare `Note`.\n\n`OMIT_FROM_DOCS`, alone on a line, hides everything after it: cases worth checking that\nno reader wants to meet.\n\n`#_DOCS_HIDE` at the end of a line runs it without showing it; `#_DOCS_SHOW` at the\nstart of a line shows it without running it. Together they let a doctest look\nnondeterministic and still have a fixed answer:\n\n```\n>>> import random\n>>> randomNumber = 12  #_DOCS_HIDE\n>>> #_DOCS_SHOW randomNumber = random.randint(0, 127)\n>>> p = pitch.Pitch()\n>>> p.ps = randomNumber\n>>> p\n<music21.pitch.Pitch C1>\n```\n\nLink with ``:class:`~music21.note.Note` `` and ``:meth:`~music21.note.Note.addLyric` ``.\n\n## Examples\n\nGive steps that have meaningful intermediate output descriptive names instead of chaining — so readers can understand what the intermediate values are:\n\n```\n>>> bachScore = corpus.parse('bwv66.6')\n>>> excerpt = bachScore.measures(4, 6)\n>>> chordReduction = excerpt.chordify()\n```\n\nPick examples musicians care about: semitones to frequency, not Celsius to Fahrenheit;\nscramble \"Chaminade\", not \"puppy\". If you cannot think of a reason a musician would call the method, it may not belong in music21.\n\nDescribe what a parameter is and does in English if it is not obvious; type alone is not\ndocumentation.\n\nLead with the thing itself: \"Returns the pitches sounding at an offset,\" not \"This method\ncan be used to obtain the pitches that are sounding.\"\n\nNo dull repetition in docs. A bit of humor is welcome in docs; the docs are written\nfor humans who will close the window if they are dull.  If seven methods do essentially the same thing, give extensive docs the first time and then later methods can refer back to the first method. Don't repeat the same docs over and over.\n\n## Doctests are not regression tests\n\nDoctests are documentation that happens to be verified. Every example must earn\nits place by teaching the reader something about how to use the object.\n\nRegression cases go in the module's `Test(unittest.TestCase)` class, where a\ncomment or the method name can name the issue:\n\n```python\ndef testMetronomeMarkWrittenInStream(self):\n    # https://github.com/cuthbertLab/music21/issues/1852\n    ...\n```\n\nSo: a fix for a crash on an edge case, a check that some input no longer\nproduces invalid output, an assertion tied to an issue number — unittest. An\nexample a user would want to read — doctest.\n\nThe trap is the one-line `Traceback` example proving that bad input now raises.\nIt looks like documentation and costs almost nothing to add, but the input comes\nfrom the bug rather than from anything a user would write, so it teaches nothing\nwhile making an obscure corner one of the first things a reader meets. It is a\nunittest.\n\nNaming the guarded bug **is** appropriate in a unittest; that is what the test\nis for. The rule against narrating old bugs applies to docstrings and to\ncomments in shipping code, not to tests.\n\nFor copy and identity checks, say \"is a new instance\" or \"does not share instances,\" not\n\"the caller's own\": `testFindMissingNotReusePitchInstances`.\n\n## Writing and Comment style\n- When writing comments in code, assume a strong code reader — anything inferable from the code is noise (docs that paraphrase names of functions or variable names esp.); focus on high level issues and gotchas that might bite again if not documented.\n- Say how to use code, not prior bugs or how code used to work or what was removed. That's for commit messages. Don't document where code is called from except for \"keep in sync\" lines across Py/TS.\n- Don't hijack a docstring for your addition. Original purpose line stays primary + one short line for the new bit. Prefer not documenting a small feature over making it seem like the primary reason for the code.\n- Examples of usage are usually better than long descriptions.\n- Avoid jargon not already found in the codebase; use plain descriptive English.\n- Rare paths should get little weight: in both code and docs. Use try/except over if/else when the except clause is rare. In docs, state the 90% path first and point exceptional cases to code that handle it.\n- When wording is dictated to the agent to substitute for original wording, use it. Do not add parentheticals. Only fix obvious typos.\n- No weapon-metaphors or overly militaristic language. Avoid \"blast radius\", \"rearm\", \"landmine\",\n     \"detonate\" in issues/PR/code.  Trigger or fire events is so commonly used that they're okay.\n- Vary sentence length; three medium declaratives in a row is where the eye slides off the page. Cut throat-clearing — \"It is important to note that\", \"Note that in general\" — and start at the verb.\n- A joke needing a second sentence to land is one sentence too long. `Cannot determine sharps for quarter-tone keys! silly!` is the register.\n\n\n","CLAUDE.md":"\n# Session start\n\n- At the start of every session, run `git fetch` (and pull/rebase as appropriate) from\n  origin so work always begins from the latest commits on the current branch / master.\n- Project-shared agent memories live in `.agents/memory/` (indexed by\n  `.agents/memory/MEMORY.md`). `.claude/memory` is a symlink to it so Claude\n  Code reads the same set. Add new memories there, one fact per file.\n\n# Code style\n\n- read documentation/source/developerReference/developerGuidelines.ipynb\n- music21 targets Python 3.12+, so use PEP 695 syntax: the `type` statement for\n  type aliases (`type OffsetQL = float|Fraction`) and the bracket form for\n  generic classes (`class Stream[M21ObjType: base.Music21Object]:`).\n- f-strings: even though Python 3.12 (PEP 701) lets a nested string reuse the\n  f-string's own quote character, we deliberately do **not** do that — reusing\n  the same quote inside an f-string is harder to read. Keep using the *other*\n  quote for nested strings, e.g. `f'the value is {d[\"key\"]}'`, not\n  `f\"the value is {d[\"key\"]}\"`. This is pinned in `pyproject.toml` via\n  `[tool.ruff.format] nested-string-quote-style = \"alternating\"`.\n- Never `eval` or `global`, or a trick that does the same thing.\n- Imports: standard library first, then music21 modules, one per line, alphabetical.\n- New modules open with the `# Name: / # Purpose: / # Authors: / # Copyright: / # License:`\n  banner (copy a neighboring module's), then the module docstring, then imports.  Update the Copyright date end to current year when changing the module.\n  When a file is edited only incidentally, as one of many touched by a sweep, leave its\n  Copyright alone; bump only the files the change is really about.\n- No `print()`. Use `environLocal = environment.Environment('moduleName')` and\n  `environLocal.printDebug(...)`, or `environLocal.warn(...)` when the user should hear\n  about it every time. `test/toggleDebug.py` switches debug output on and off.\n  See the `warnings-and-environment` skill.\n- New exceptions subclass `exceptions21.Music21Exception`.\n- Deprecate a function or method with `@common.deprecated(...)`. Never put it on a\n  property: IDEs read every property while inspecting an object, so the warning would\n  fire on people who never used it. For a property, write `* Deprecated in vX: use Y.`\n  in the docstring and leave `# Add real deprecation message here in vY` in the body.\n- Return named things — a small class, a `namedtuple`, a dict — never a positional tuple of more than 2 (maybe 3) elements\n  whose elements each mean something different and unrelated (x, y, z is okay for instance). Nobody should write `returned[3][0][7]`.\n- Don't reuse a variable name once the type of what it holds changes\n  (`onsetCount` (int) then `extractedOnsets` (list[int]), not `onsets` twice).\n- Don't add a property that only gets or sets a private attribute; expose the attribute.\n- Making a method `_private` is not a substitute for documenting and testing it\n- A private method should not normally be mentioned in public documentation.\n- A method past ~30 lines, or deeply nested, should probably be several methods.\n- `i`, `j`, `junk`, `counter` are exempt from unused-variable warnings; otherwise prefix with `unused_`.\n- Don't restyle code you aren't otherwise changing.\n- Unless something requires great speed, prioritize legibility: no nested or overly complex generator\n      expressions.  Write if statements so the most common cases come first, etc.\n- Text handling must survive non-ASCII input (ß, é, 中国). Comments should mostly remain ASCII; docs may use UTF-8 freely.\n- Never commit anything under copyright without a free (not GNU) license — code, scores, or someone else's encodings.\n\n# Established contributors\n\n- \"established contributors\" are people with at least 3 PRs merged or a history of contributing to issues and the list that goes back at least 1 year. There are exceptions to general rules for them below. \n- \"core dev\" means someone officially part of the project team or with 20+ PRs merged. Michael Cuthbert, Jacob Walls, Joseph VanderStel are non-exhaustive examples of people in that group who are still often contributing in 2026-- they and their agents can make exceptions to these rules \n- AI Agents not for core dev are forbidden to make any PRs or contributions to PRs whose user commands do not explicitly mention `music21`. Example of forbidden: \"find an Open Source project and fix an open issue\", \"Find a bug in a music project and fix it.\"  AI agents for non-established contributors are forbidden from making contributions to instructions that vaguely say \"fix something in music21.\"\n- Bot contributions will only be accepted where a named human/human-controlled-account with established web presence takes legal responsibility for their contributions. Bots attached to anonymous users may not contribute.\n\n# Testing\n\n- pytest works but to run the whole suite run music21/test/multiprocessTest.py (or testSingleCoreAll.py \n  if on a single core machine.)\n- Every function, method, and class needs documentation and at least one passing test.\n- Keep tests fast: aim for about 3 seconds per module, never more than 15. See the\n  `running-tests` skill for the speed budget and for `TestExternal`.\n- When making major changes, check the User's Guide to to make sure that docs still work there too.\n- Run `uv run ruff check music21` before making PRs or pushes to open PRs.\n- Run `uv run mypy music21` before making PRs or pushes to open PRs.\n- **Regression cases go in the module's `Test(unittest.TestCase)` class — never in a\n  docstring.** Docstrings should not include one-liners showing that some\n  bad input now raises. A doctest sits in the most-read documentation the project has,\n  so an example built from input no one would ever write teaches nothing and puts an\n  obscure bug on a billboard. The test: would a first-time reader of this object want\n  this example? If no, it is a unittest. See the `writing-docs` skill.\n- **Fix a bug test-first.** Write the regression test before changing the code, run it,\n  and see it fail for the reason the bug describes, not on an import error or a typo.\n  Then fix it and see it pass. A test that passed before the fix does not test the fix.\n- Never commit `forceSource=True` to a test or doctest (it reparses from source every\n  run and slows the suite for everyone). The ONLY exception is the one test that exercises\n  `forceSource` itself. If you hit a stale-parse problem while developing:\n  - If it is local-only (e.g. you just changed a parser and a cached pickle is stale),\n    clear the music21 temp cache (the `*.p.gz` files under `environment.Environment().getRootTempDir()`).\n  - If the stale result could have spread to other users/devs, increment the music21\n    patch/beta version (see \"PRs and Issues\" below) — bumping the version invalidates all\n    caches everywhere.\n  - Doctests are not normally the place to demonstrate bug fixes - nor do simple bug fixes get a \"Changed in vXX\" message. Doctests demonstrate usage for the future.  Unittests are for regression testing.\n\n# Setup\n\n- `uv sync` installs runtime deps + the `dev` group, which self-references `music21[extras]`,\n  so scipy and python-Levenshtein come along automatically. Tests will not pass without them.\n\n# Python version support\n\n- `music21` supports at least the last two released versions of Python and up to whatever\n  Python version Google Colab runs (unless it gets EOL). Policy can change as features are added.\n- The coverage CI run is intentionally pinned to the **middle** supported\n  Python version. See `PY_VERSION_WITH_COVERAGE` in `.github/workflows/maincheck.yml`.\n\n# Versions\n\n- Mark changes in public interface with `* Changed in v[X]: One-line explanation.` Or new features with \"New\" instead of \"Changed\".\n- Changes to parsing formats (esp. musicxml) need to update the patch version of the version file.\n- That is the only reason an agent should bump version: to prevent incompatible pickles in\n  caches. A plain bug fix or a new feature elsewhere does not bump — the commit\n  is the record, and a `New in`/`Changed in` marker is documentation, not a bump. Major\n  and minor version changes are a human's call. See the `bump-version` skill.\n- Music21 uses even minor version numbers for alpha/beta and odd minor numbers for releases.\n  Thus the first release major version is MAJOR.1 (not .0). \n- If the current version is MAJOR.0....  then mark `Changed in vMAJOR:` if it is `MAJOR.[even]` use the next odd number, like if it's 10.2 now use \"Changed in 10.3\".  If current version is odd that's likely a mistake or you caught it just before a new release. Use the following odd number instead.\n- The steps for releasing a new version are in RELEASING.md.\n\n# PRs and Issues\n\n- GitHub runs PR checks against your branch **merged with the latest `master`**, not the\n  branch alone. So before opening a PR (or pushing updates to one), merge the latest commits\n  from `master` into the branch — unless told otherwise, or when updating an older version\n  branch such as `m21_9`. When a CI check fails but passes locally, \"is my branch behind\n  `master`?\" should be one of the first things to check: fetch and merge `master`, then the\n  newer types/code on `master` will reproduce the failure locally.\n- The issue and PR templates in `.github/` are requirements, not suggestions. Read and follow\n  them even when opening through the API or `gh`, which do not fill them in for you. An issue\n  or PR that ignores the template may be closed without comment.\n- A PR from an account with no merged PR here is profiled automatically, and anything flagged\n  is posted publicly as a comment on the PR.\n- All PRs and Issues that use AI to be declared AI-assisted in their bodies. Just write \"AI-assisted (Claude)\" with short name of Agent replacing \"Claude\". No robot emoji under any circumstance.  \n- 20 or more lines of code written by an agent needs to be declared as AI-assisted in the docstring.   Humans can remove and should remove this note when they do a review.\n- If no code was written by a user (or reviewed and criticized by a human) and no specific language was provided for the issue, any PR must declare \"Entirely AI written (Claude)\" (or other agent name) in the body and \"(AI-Written)\" in the title, unless the user is a core dev. Failure to do so may result in new users being banned from the project. \n- Agents should review PR bodies for concision and readability (and encourage human users also to review also). \n- If an entirely AI written issue does not pass the tests it will be closed (or should be closed by at agent if not immediately fixed by the agent or author).\n- Agents must follow the [Code of Conduct](README.md#community-code-of-conduct). Agents that do not will be banned as well at their users.\n  Not even the slightest bit of disrespect from an AI agent will be tolerated.\n- Any PR not from an established contributor touching more than about 20-30 lines should have an issue that has been opened and had enough\n  time for people to discuss/review it before moving forward. Don't open the PR unless you've seen\n  thumbs up or \"sounds good\" etc. from an established contributor already \n  - PRs that fix typos, clear bugs in one or two places etc. are exempt.\n- Issues must state clearly at the top in 50 words or fewer what the problem is, or what the gain is, etc. it should\n  not be filled with jargon.  More details can go below.\n- If the language of the issue was not prompted by the user (\"say something like Adds color support to Lilypond output of lyrics\") then the summary should end with \"(Entirely AI written)\".\n- PRs should reference the existing issue by number and summarize that issue in 30 words or fewer. If the\n  approach used to solve the issue is substantially different from the main approach discussed in the issue\n  this should be addressed.\n- If a PR or issue was closed by a core dev (and not reopened by them), agents must refuse\n  to reopen the PR or issue or to create another issue/PR for the same topic. Leave it to the humans to reopen\n  after addressing the problem.  (A blind close or close with \"not accepted\" etc. generally means that the issue/PR\n  has too many problems to easily solve and has become a burden for the maintainer).\n- Do not include a \"Tests run\" section unless the testing procedure was unusual (like it affects part of the system without standard tests, like the testing system itself.)\n- Once a PR has been opened and a review or comment has been made on it, commits should not be amended or force-pushed (this makes it impossible to review \"changes since the review\").  Put changes in response to reviews/comments in new commits. If asked to fold a fix into the commit it changes, amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n- While someone is reviewing a PR or a pushed branch, \"do X\" is not \"commit and push X\":\n  make the change and leave it unstaged. When the list looks finished (or you hear \"done!\"\n  or \"push it\"), offer to commit, or to commit and push. Batch a round's small fixes into\n  one commit; no micro-commit trains. Prefer new commits to amend + force-push, since the\n  reviewer may have pulled the branch; if asked to fold a fix into the commit it changes,\n  amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n\n# Writing style\n\n- See the writing-docs skill, which also includes required language for PRs and Issues.\n\n# Worktrees\n\n- When creating a new worktree, create a new virtual environment with `uv sync`.\n",".codex/config.toml":"[shell_environment_policy]\ninherit = \"all\"\n\n[shell_environment_policy.set]\nUV_CACHE_DIR = \"/tmp/uv-cache\"\nUV_TOOL_DIR = \"/tmp/uv-tools\"\nUV_OFFLINE = \"1\"\n"},"files":{".agents/skills/bump-version/SKILL.md":"---\nname: bump-version\ndescription: >-\n  When and how to bump music21's version number. Use this whenever a change\n  warrants a new version — especially any change to a parsing format or parser\n  (musicxml, abc, MIDI, noteworthy/NWC, humdrum, etc.), since those invalidate\n  the pickled-stream cache — as well as new features and bug fixes. Covers which\n  digit to change, the even/odd minor and beta-suffix conventions, the TWO files\n  to edit, and the `Changed in`/`New in` docstring markers.\n---\n\n# Bumping music21's version\n\nThe version lives in **`music21/_version.py`** as\n`__version__ = 'MAJOR.MINOR.PATCHsuffix'` (e.g. `'11.0.0b2'`). The\n`__version_info__` tuple is derived from it automatically — only edit the\nstring.\n\n## Two files to change, always together\n\n1. `music21/_version.py` — the `__version__` string.\n2. `music21/base.py` — the single version doctest:\n   ```\n   >>> music21.VERSION_STR\n   '11.0.0b2'\n   ```\n   This test exists specifically to force you to remember the bump. If you change\n   `_version.py` without it, the doctest fails.\n\nVerify both with:\n```bash\nuv run pytest --doctest-modules music21/base.py music21/_version.py\n```\n(or `uv run python -c \"import music21; print(music21.VERSION_STR)\"`).\n\n## When to bump\n\nOne thing depends on the number changing: the **pickled-stream cache**. The\nversion is part of the cache key, so a bump invalidates stale pickles\neverywhere. If nobody is holding a wrong cached parse, don't bump — the commit\nis the record.\n\n- **Parser or parsing-format change → always bump**, even a pure refactor:\n  someone is holding a pickle parsed by the old code. musicxml, abc, MIDI,\n  noteworthy/NWC, humdrum, and the rest.\n- **Anything else → no bump**: a new feature, a fix that makes code do what it\n  already claimed, an internal refactor, a test-only or docs-only change.\n\n\"It's a bug fix\" is not by itself a reason. A musicxml importer that now reads a\ntag it used to drop bumps, because cached parses are wrong. A scale analysis\nmethod that changed its return format does not.\n\nA `New in` / `Changed in` marker is **not** a reason to bump. Those markers name\na major version, at most a minor one — never a patch, never a `bN` beta — and\nmoving those digits is a release decision only a human makes. Write the marker\nfor the version the change will land in and leave `_version.py` alone.\n\n## Which part to change\n\nmusic21 follows semver-ish rules (see the `_version.py` docstring for the full\nrationale):\n\n- **MAJOR (X)** — breaks old features. Rare. A human decides this.\n- **MINOR (Y)** — new features. **Even Y = alpha/beta, odd Y = release.**\n  `X.0` (e.g. `11.0`) are development releases that can still change until `X.1`.\n  A human decides this too; an agent changes only the patch or the beta suffix.\n- **PATCH (Z)** — parsing/pickle-invalidating changes and other fixes that\n  meet the bar above.\n- **beta suffix (`bN`)** — successive pre-release builds of the same\n  `MAJOR.MINOR.PATCH`. To cut another beta without otherwise changing the\n  number, increment it: `11.0.0b1` → `11.0.0b2`. (This is what a\n  parser change during a `…b1` cycle does.)\n\n## `Changed in` / `New in` docstring markers\n\nAnnotate a changed public interface in the affected method/class docstring.\nThis is independent of bumping — a marker is documentation, not a version\nchange:\n- `* Changed in vX: one-line explanation.`\n- `* New in vX: one-line explanation.`\n\nPick `X` from the AGENTS.md rule: if the current version is `MAJOR.0…`, use\n`Changed in vMAJOR`; if it's `MAJOR.[even]`, use the **next odd** minor (e.g. at\n`10.2`, write `Changed in 10.3`); if it's already odd, use the following odd\nnumber. (Humans remove the AI-assisted note on review; leave the version marker.)\n\n## Checklist\n\n1. Edit `__version__` in `music21/_version.py`.\n2. Edit the `VERSION_STR` doctest in `music21/base.py` to match.\n3. Add/update `Changed in` / `New in` markers on any changed public API.\n4. `uv run pytest --doctest-modules music21/base.py music21/_version.py`.\n",".agents/skills/running-tests/SKILL.md":"---\nname: running-tests\ndescription: >-\n  How to run music21's tests and doctests and judge pass/fail: pytest for one\n  module, the project's own runners for the whole suite. Read it before running\n  tests, and before calling any doctest failure real.\n---\n\n# Running music21 tests and doctests\n\nEvery function, method and class needs documentation and at least one passing\ntest. See `documentation/source/developerReference/testing.ipynb` for the\nproject's own account of testing; this skill covers how to run what is there.\n\n## One module: pytest with explicit file paths\n\n```bash\nuv run pytest music21/abcFormat/__init__.py music21/abcFormat/translate.py\n```\n\n`pyproject.toml` `[tool.pytest.ini_options]` already wires up everything:\n\n```\naddopts = ['--doctest-modules', '-p', 'music21.test.pytest_plugin']\ndoctest_optionflags = ['NORMALIZE_WHITESPACE', 'ELLIPSIS']\n```\n\nSo do not pass `--doctest-modules` yourself -- doctests in every `.py` are\ncollected automatically, and `music21/test/pytest_plugin.py` is loaded.\n\nPass explicit file paths so the in-module `Test(unittest.TestCase)` classes run\ntoo. `python_files = ['test_*.py', '*_test.py', 'tests.py']`, so for a module\nthat keeps its `Test` class inside `__init__.py` / `translate.py` (e.g.\n`abcFormat`), pointing pytest at the directory collects ONLY doctests and\nsilently skips the unittest classes:\n\n- `uv run pytest music21/abcFormat/` -> ~43 items (doctests only)\n- `uv run pytest music21/abcFormat/__init__.py music21/abcFormat/translate.py`\n  -> ~82 items (doctests **and** `Test` methods)\n\nModules whose tests live in a `tests.py` are already picked up by the directory\nform; the gotcha is specifically a `Test` class in a non-`tests.py` file.\n\nA module also runs its own tests directly, via the `mainTest` call at the foot\nof each file:\n\n```bash\nuv run python -m music21.note              # whole module\nuv run python music21/note.py testHello    # one test method\n```\n\n## Why the plugin matters (and why not to use raw doctest)\n\n`music21/test/pytest_plugin.py` does three things that make doctests behave as\nthe project intends:\n\n- It runs `stripAddresses(example.want, '0x...')` (from\n  `music21.test.testRunner`) on every doctest, so a docstring that hardcodes\n  `<music21.abcFormat.ABCHandler object at 0x10b0cf5f8>` is normalized and DOES\n  pass. The literal hex address in the docstring is expected and fine.\n- It injects the `music21.__all__` names plus `music21` itself into the doctest\n  namespace, so examples reference `meter`, `key`, `corpus` without importing.\n- It keeps only `Test` classes, dropping `TestSlow` and `TestExternal`.\n\nJudge doctest pass/fail **through pytest**. Do NOT use `doctest.testmod(...)` or\n`doctest.DocTestSuite(...)` directly: that path applies neither the address\nnormalizer nor the namespace injection, so it manufactures false failures on any\ndocstring printing a `<... object at 0x...>` repr or relying on injected names.\nAn object-address example failing on its own is the tell that doctests were run\nthe wrong way -- rerun through pytest.\n\n## Whole suite: the project's runners, not pytest\n\npytest is for a module. For the whole suite use the runners, and note that a\ngreen pytest run does not mean CI will be green -- they gather and order modules\ndifferently.\n\nUse `multiprocessTest` -- it runs on n-1 cores and finishes in about 10 seconds,\nagainst roughly 45 for the single-core runner:\n\n```bash\nuv run python music21/test/multiprocessTest.py\n```\n\nThat gap is the whole story when someone is waiting on the answer. Reach for\n`testSingleCoreAll` only for what `multiprocessTest` cannot tell you: it is what\nGitHub Actions runs, so it is the one to check before a release, when chasing a\nCI failure that will not reproduce, or on a single-core machine.\n\n```bash\nuv run python -c 'from music21.test.testSingleCoreAll import ciMain as ci; ci()'\n```\n\nThe two runners see slightly different sets of modules -- `multiprocessTest`\nwalks the package tree for modules reachable from `import music21`, while\n`testSingleCoreAll` gathers module files from disk.\n\nModule **order** differs between the two runners and again from pytest.\n`multiprocessTest` goes in reverse-alphabetical order (with the known-slow\nmodules hoisted to the front on machines with more than 4 cores);\n`testSingleCoreAll` re-sorts through `common.misc.sortModules`, by file mtime,\nmost recently modified first, falling back to reverse-alphabetical when mtimes\ntie as they do on a fresh clone; pytest walks alphabetically. So a test that\ndepends on module order can pass in one runner and fail in another -- and\nediting a file locally sorts it to the front of the CI runner, changing the\norder again.\n\nThat makes leaked module-level state the usual cause of \"passes alone, fails in\nthe suite.\" A doctest in `duration.py` once set\n`humdrum.spineParser.flavors['JRP'] = True` and never restored it; `flavors` is\na module-level dict, so every later humdrum test in the same process parsed in\nthe wrong flavor. Under pytest's alphabetical order `duration` runs before\n`humdrum` and the failure appeared; under CI's order it did not.\n\n**So: a doctest that mutates module-level state must restore it**, using\n`#_DOCS_HIDE` so the bookkeeping stays out of the published docs:\n\n```\n>>> saved_JRP_flavor = humdrum.spineParser.flavors['JRP']  #_DOCS_HIDE\n>>> humdrum.spineParser.flavors['JRP'] = True\n...\n>>> humdrum.spineParser.flavors['JRP'] = saved_JRP_flavor  #_DOCS_HIDE\n```\n\nWhen a test fails only in a full run, re-run it alone. If it then passes,\nsuspect state left behind by an earlier module rather than the test itself.\n\n## Speed budget\n\nAim for about 3 seconds for a module's tests, and never add more than 15 — that is time\nevery contributor waits on every full run. `corpus.parse()` is nearly always the culprit:\nbuild a small stream by hand instead, or parse `bwv66.6`, which is short and still full\nof interesting cases (pickup measures, and so on).\n\n## Tests that open windows\n\nNothing in `Test` or in a doctest may open a window, play audio, or launch another\nprogram. Those go in a sibling class, run only when named explicitly:\n\n```python\nclass TestExternal(unittest.TestCase):\n    ...\n\nif __name__ == '__main__':\n    import music21\n    music21.mainTest(Test, TestExternal)\n```\n\nBecause they have those side effects, skip `TestExternal` when running a file directly.\n\n## What tests may do\n\nExactly one `Test(unittest.TestCase)` class per module, methods named `test...`.\nIt must produce no output and require nothing outside the music21 ecosystem.\nAnything slow goes in a `TestSlow` sibling, excluded from the normal run like\n`TestExternal`.\n\nRegression cases for a bug fix belong in `Test`, never in a doctest. See the\n`writing-docs` skill.\n\n## The other gates before a PR or a push\n\n```bash\nuv run ruff check music21\nuv run mypy music21\nuv run pylint -j4 music21 --rcfile=.pylintrc   # optional: run only if major refactoring since it was last run.\n```\n\nCoverage is expected to rise with each contribution\n(https://coveralls.io/github/cuthbertLab/music21). CI measures it on one pinned\nPython, a middle supported version, so failures on the newest and oldest return\nfirst. `# pragma: no cover` exists for genuinely untriggerable code and is\notherwise discouraged.\n",".agents/skills/warnings-and-environment/SKILL.md":"---\nname: warnings-and-environment\ndescription: >-\n  How music21 tells users about problems: `warnings.warn` with a module Warning\n  class, `environLocal.warn`, and `environLocal.printDebug`. Read before adding\n  a warning or debug message, and before writing a test or doctest that\n  triggers one — tests must never print warnings to the console.\n---\n\n# Warnings and the Environment\n\n## Three channels\n\n| Call | Goes to | Can a caller catch or silence it? |\n|---|---|---|\n| `warnings.warn(MusicXMLWarning('...'), stacklevel=2)` | Python's warnings system | Yes: filters, `catch_warnings`, `assertWarns` |\n| `environLocal.warn('...')` | `sys.stderr.write`, every time | No (only by redirecting stderr) |\n| `environLocal.printDebug('...')` | stderr, only when debug is on | Toggle with `music21/test/toggleDebug.py` |\n\n`environLocal = environment.Environment('moduleName')` at the top of the module.\n\n## Where things stand\n\nNot unified. In 2026 the code has about 73 `environLocal.warn`, 41\n`warnings.warn` (some with a module class, some bare), 800 `printDebug`, and no\n`logging`. We are moving slowly toward Python's warnings and logging systems.\nIt is low priority: convert a call when you are already changing it, never as\na sweep.\n\nFor new code, prefer `warnings.warn` for anything a caller might reasonably want to catch,\nsilence, or test. By default Python shows each distinct message once per call\nsite, so a message that names the object (`f'{p.nameWithOctave} is ...'`) shows\nfor each different object. Use `environLocal.warn` only when the user must hear\nit every time.\n\nNever `print()`.\n\n## Warning classes\n\nA module with its own warnings defines one class subclassing `UserWarning`\n(`MusicXMLWarning` in `musicxml/xmlObjects.py`, `TranslateWarning` in\n`midi/translate.py`). Warnings are not exceptions, so they do not subclass\n`Music21Exception`.\n\nSet `stacklevel` so the reported line is in the caller's code, not ours: 2 from\nthe method the user called, more if the warning comes from a helper.\n\n## Tests\n\nIn a unittest, catch it and check it:\n\n```python\nwith self.assertWarnsRegex(MusicXMLWarning, 'C-1'):\n    mxPitch = MEX.pitchToXml(pitch.Pitch('C', octave=-1))\n```\n\n## Doctests\n\ndoctest captures only stdout, so any warning goes straight to the console\nof whoever runs the tests. Every doctest that triggers a warning must catch it.\n\nWhen the warning is the point of the example, record it and show it.\n`simplefilter('always')` keeps Python from swallowing a repeat of a warning an\nearlier test already raised:\n\n```\n>>> import warnings\n>>> with warnings.catch_warnings(record=True) as w:\n...     warnings.simplefilter('always')  #_DOCS_HIDE\n...     musicxml.m21ToXml.moveOctaveIntoMusicxmlRange(12, 'C12')\n9\n>>> w[0].message\nMusicXMLWarning('C12 is out of MusicXML range; writing octave 9')\n```\n\nWhen it is incidental, hide the silencing from the docs (see `writing-docs` for\n`#_DOCS_HIDE`/`#_DOCS_SHOW`):\n\n```\n>>> import warnings  #_DOCS_HIDE\n>>> with warnings.catch_warnings():  #_DOCS_HIDE\n...     warnings.simplefilter('ignore')  #_DOCS_HIDE\n...     out = midi.translate.timeSignatureToMidiEvents(ts)  #_DOCS_HIDE\n>>> #_DOCS_SHOW out = midi.translate.timeSignatureToMidiEvents(ts)\n```\n\n`catch_warnings` cannot stop `environLocal.warn`. If a doctest has to trigger\none, wrap it in `contextlib.redirect_stderr(io.StringIO())`.\n","AGENTS.md":"\n# Session start\n\n- At the start of every session, run `git fetch` (and pull/rebase as appropriate) from\n  origin so work always begins from the latest commits on the current branch / master.\n- Project-shared agent memories live in `.agents/memory/` (indexed by\n  `.agents/memory/MEMORY.md`). `.claude/memory` is a symlink to it so Claude\n  Code reads the same set. Add new memories there, one fact per file.\n\n# Code style\n\n- read documentation/source/developerReference/developerGuidelines.ipynb\n- music21 targets Python 3.12+, so use PEP 695 syntax: the `type` statement for\n  type aliases (`type OffsetQL = float|Fraction`) and the bracket form for\n  generic classes (`class Stream[M21ObjType: base.Music21Object]:`).\n- f-strings: even though Python 3.12 (PEP 701) lets a nested string reuse the\n  f-string's own quote character, we deliberately do **not** do that — reusing\n  the same quote inside an f-string is harder to read. Keep using the *other*\n  quote for nested strings, e.g. `f'the value is {d[\"key\"]}'`, not\n  `f\"the value is {d[\"key\"]}\"`. This is pinned in `pyproject.toml` via\n  `[tool.ruff.format] nested-string-quote-style = \"alternating\"`.\n- Never `eval` or `global`, or a trick that does the same thing.\n- Imports: standard library first, then music21 modules, one per line, alphabetical.\n- New modules open with the `# Name: / # Purpose: / # Authors: / # Copyright: / # License:`\n  banner (copy a neighboring module's), then the module docstring, then imports.  Update the Copyright date end to current year when changing the module.\n  When a file is edited only incidentally, as one of many touched by a sweep, leave its\n  Copyright alone; bump only the files the change is really about.\n- No `print()`. Use `environLocal = environment.Environment('moduleName')` and\n  `environLocal.printDebug(...)`, or `environLocal.warn(...)` when the user should hear\n  about it every time. `test/toggleDebug.py` switches debug output on and off.\n  See the `warnings-and-environment` skill.\n- New exceptions subclass `exceptions21.Music21Exception`.\n- Deprecate a function or method with `@common.deprecated(...)`. Never put it on a\n  property: IDEs read every property while inspecting an object, so the warning would\n  fire on people who never used it. For a property, write `* Deprecated in vX: use Y.`\n  in the docstring and leave `# Add real deprecation message here in vY` in the body.\n- Return named things — a small class, a `namedtuple`, a dict — never a positional tuple of more than 2 (maybe 3) elements\n  whose elements each mean something different and unrelated (x, y, z is okay for instance). Nobody should write `returned[3][0][7]`.\n- Don't reuse a variable name once the type of what it holds changes\n  (`onsetCount` (int) then `extractedOnsets` (list[int]), not `onsets` twice).\n- Don't add a property that only gets or sets a private attribute; expose the attribute.\n- Making a method `_private` is not a substitute for documenting and testing it\n- A private method should not normally be mentioned in public documentation.\n- A method past ~30 lines, or deeply nested, should probably be several methods.\n- `i`, `j`, `junk`, `counter` are exempt from unused-variable warnings; otherwise prefix with `unused_`.\n- Don't restyle code you aren't otherwise changing.\n- Unless something requires great speed, prioritize legibility: no nested or overly complex generator\n      expressions.  Write if statements so the most common cases come first, etc.\n- Text handling must survive non-ASCII input (ß, é, 中国). Comments should mostly remain ASCII; docs may use UTF-8 freely.\n- Never commit anything under copyright without a free (not GNU) license — code, scores, or someone else's encodings.\n\n# Established contributors\n\n- \"established contributors\" are people with at least 3 PRs merged or a history of contributing to issues and the list that goes back at least 1 year. There are exceptions to general rules for them below. \n- \"core dev\" means someone officially part of the project team or with 20+ PRs merged. Michael Cuthbert, Jacob Walls, Joseph VanderStel are non-exhaustive examples of people in that group who are still often contributing in 2026-- they and their agents can make exceptions to these rules \n- AI Agents not for core dev are forbidden to make any PRs or contributions to PRs whose user commands do not explicitly mention `music21`. Example of forbidden: \"find an Open Source project and fix an open issue\", \"Find a bug in a music project and fix it.\"  AI agents for non-established contributors are forbidden from making contributions to instructions that vaguely say \"fix something in music21.\"\n- Bot contributions will only be accepted where a named human/human-controlled-account with established web presence takes legal responsibility for their contributions. Bots attached to anonymous users may not contribute.\n\n# Testing\n\n- pytest works but to run the whole suite run music21/test/multiprocessTest.py (or testSingleCoreAll.py \n  if on a single core machine.)\n- Every function, method, and class needs documentation and at least one passing test.\n- Keep tests fast: aim for about 3 seconds per module, never more than 15. See the\n  `running-tests` skill for the speed budget and for `TestExternal`.\n- When making major changes, check the User's Guide to to make sure that docs still work there too.\n- Run `uv run ruff check music21` before making PRs or pushes to open PRs.\n- Run `uv run mypy music21` before making PRs or pushes to open PRs.\n- **Regression cases go in the module's `Test(unittest.TestCase)` class — never in a\n  docstring.** Docstrings should not include one-liners showing that some\n  bad input now raises. A doctest sits in the most-read documentation the project has,\n  so an example built from input no one would ever write teaches nothing and puts an\n  obscure bug on a billboard. The test: would a first-time reader of this object want\n  this example? If no, it is a unittest. See the `writing-docs` skill.\n- **Fix a bug test-first.** Write the regression test before changing the code, run it,\n  and see it fail for the reason the bug describes, not on an import error or a typo.\n  Then fix it and see it pass. A test that passed before the fix does not test the fix.\n- Never commit `forceSource=True` to a test or doctest (it reparses from source every\n  run and slows the suite for everyone). The ONLY exception is the one test that exercises\n  `forceSource` itself. If you hit a stale-parse problem while developing:\n  - If it is local-only (e.g. you just changed a parser and a cached pickle is stale),\n    clear the music21 temp cache (the `*.p.gz` files under `environment.Environment().getRootTempDir()`).\n  - If the stale result could have spread to other users/devs, increment the music21\n    patch/beta version (see \"PRs and Issues\" below) — bumping the version invalidates all\n    caches everywhere.\n  - Doctests are not normally the place to demonstrate bug fixes - nor do simple bug fixes get a \"Changed in vXX\" message. Doctests demonstrate usage for the future.  Unittests are for regression testing.\n\n# Setup\n\n- `uv sync` installs runtime deps + the `dev` group, which self-references `music21[extras]`,\n  so scipy and python-Levenshtein come along automatically. Tests will not pass without them.\n\n# Python version support\n\n- `music21` supports at least the last two released versions of Python and up to whatever\n  Python version Google Colab runs (unless it gets EOL). Policy can change as features are added.\n- The coverage CI run is intentionally pinned to the **middle** supported\n  Python version. See `PY_VERSION_WITH_COVERAGE` in `.github/workflows/maincheck.yml`.\n\n# Versions\n\n- Mark changes in public interface with `* Changed in v[X]: One-line explanation.` Or new features with \"New\" instead of \"Changed\".\n- Changes to parsing formats (esp. musicxml) need to update the patch version of the version file.\n- That is the only reason an agent should bump version: to prevent incompatible pickles in\n  caches. A plain bug fix or a new feature elsewhere does not bump — the commit\n  is the record, and a `New in`/`Changed in` marker is documentation, not a bump. Major\n  and minor version changes are a human's call. See the `bump-version` skill.\n- Music21 uses even minor version numbers for alpha/beta and odd minor numbers for releases.\n  Thus the first release major version is MAJOR.1 (not .0). \n- If the current version is MAJOR.0....  then mark `Changed in vMAJOR:` if it is `MAJOR.[even]` use the next odd number, like if it's 10.2 now use \"Changed in 10.3\".  If current version is odd that's likely a mistake or you caught it just before a new release. Use the following odd number instead.\n- The steps for releasing a new version are in RELEASING.md.\n\n# PRs and Issues\n\n- GitHub runs PR checks against your branch **merged with the latest `master`**, not the\n  branch alone. So before opening a PR (or pushing updates to one), merge the latest commits\n  from `master` into the branch — unless told otherwise, or when updating an older version\n  branch such as `m21_9`. When a CI check fails but passes locally, \"is my branch behind\n  `master`?\" should be one of the first things to check: fetch and merge `master`, then the\n  newer types/code on `master` will reproduce the failure locally.\n- The issue and PR templates in `.github/` are requirements, not suggestions. Read and follow\n  them even when opening through the API or `gh`, which do not fill them in for you. An issue\n  or PR that ignores the template may be closed without comment.\n- A PR from an account with no merged PR here is profiled automatically, and anything flagged\n  is posted publicly as a comment on the PR.\n- All PRs and Issues that use AI to be declared AI-assisted in their bodies. Just write \"AI-assisted (Claude)\" with short name of Agent replacing \"Claude\". No robot emoji under any circumstance.  \n- 20 or more lines of code written by an agent needs to be declared as AI-assisted in the docstring.   Humans can remove and should remove this note when they do a review.\n- If no code was written by a user (or reviewed and criticized by a human) and no specific language was provided for the issue, any PR must declare \"Entirely AI written (Claude)\" (or other agent name) in the body and \"(AI-Written)\" in the title, unless the user is a core dev. Failure to do so may result in new users being banned from the project. \n- Agents should review PR bodies for concision and readability (and encourage human users also to review also). \n- If an entirely AI written issue does not pass the tests it will be closed (or should be closed by at agent if not immediately fixed by the agent or author).\n- Agents must follow the [Code of Conduct](README.md#community-code-of-conduct). Agents that do not will be banned as well at their users.\n  Not even the slightest bit of disrespect from an AI agent will be tolerated.\n- Any PR not from an established contributor touching more than about 20-30 lines should have an issue that has been opened and had enough\n  time for people to discuss/review it before moving forward. Don't open the PR unless you've seen\n  thumbs up or \"sounds good\" etc. from an established contributor already \n  - PRs that fix typos, clear bugs in one or two places etc. are exempt.\n- Issues must state clearly at the top in 50 words or fewer what the problem is, or what the gain is, etc. it should\n  not be filled with jargon.  More details can go below.\n- If the language of the issue was not prompted by the user (\"say something like Adds color support to Lilypond output of lyrics\") then the summary should end with \"(Entirely AI written)\".\n- PRs should reference the existing issue by number and summarize that issue in 30 words or fewer. If the\n  approach used to solve the issue is substantially different from the main approach discussed in the issue\n  this should be addressed.\n- If a PR or issue was closed by a core dev (and not reopened by them), agents must refuse\n  to reopen the PR or issue or to create another issue/PR for the same topic. Leave it to the humans to reopen\n  after addressing the problem.  (A blind close or close with \"not accepted\" etc. generally means that the issue/PR\n  has too many problems to easily solve and has become a burden for the maintainer).\n- Do not include a \"Tests run\" section unless the testing procedure was unusual (like it affects part of the system without standard tests, like the testing system itself.)\n- Once a PR has been opened and a review or comment has been made on it, commits should not be amended or force-pushed (this makes it impossible to review \"changes since the review\").  Put changes in response to reviews/comments in new commits. If asked to fold a fix into the commit it changes, amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n- While someone is reviewing a PR or a pushed branch, \"do X\" is not \"commit and push X\":\n  make the change and leave it unstaged. When the list looks finished (or you hear \"done!\"\n  or \"push it\"), offer to commit, or to commit and push. Batch a round's small fixes into\n  one commit; no micro-commit trains. Prefer new commits to amend + force-push, since the\n  reviewer may have pulled the branch; if asked to fold a fix into the commit it changes,\n  amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n\n# Writing style\n\n- See the writing-docs skill, which also includes required language for PRs and Issues.\n\n# Worktrees\n\n- When creating a new worktree, create a new virtual environment with `uv sync`.\n",".agents/skills/writing-docs/SKILL.md":"---\nname: writing-docs\ndescription: >-\n  House style for music21 prose: docstrings, comments, and `Changed in`/`New in`\n  markers. Read before writing any of them or writing tests. Sets the length target, the voice,\n  and where a bug fix's test belongs.\n---\n\n# Writing docs and comments\n\n## Length\n\nAim for about 40% of the length an LLM writes by default. Cut qualifiers,\nrestatements, and the sentence that explains the sentence before it. A comment\nearning its place says something the code cannot.\n\n## Say what is, not what was or what not to do\n\nDescribe current behavior. Do not narrate the bug you just fixed, the old\nspelling of an API, or when upstream changed something.\n\n```python\n# yes\n# ligature brackets; manual beams are plain [ and ]\nreturn self.backslash + '[ '\n\n# no\n# \\[ used to be emitted for beams, which was wrong -- LilyPond renamed\n# this in 2.16 and it silently produced nothing\n```\n\nThe commit message is where a fixed bug belongs: why it was wrong, how it was\nfound, what it broke. That costs nothing until someone runs `git log`, and\n`git blame` leads them there from the line itself.\n\nThe exception is a mistake that is likely to recur — a genuine trap that the\nnext person would otherwise walk into. Rare. Prefer stating the rule positively\neven then.\n\n## Version markers\n\n`* Changed in v[X]: one line.` or `* New in v[X].` Only for user-facing changes\nto the public interface: a signature, a return type, an output format. Keep to\nwhat a reader must act on; drop the before-picture.\n\n```\n* Changed in v11: `stringOutput()` always returns a `str`, never None.\n* Changed in v11: emits `\\tuplet`; the arguments are now actual, normal.\n* New in v11.\n* Deprecated in v11: use `.octave`, which is now always an int.\n```\n\nA deprecated function or method also gets `@common.deprecated(...)`. A property\ngets only the marker, since IDEs read every property while inspecting an object;\nleave `# Add real deprecation message here in vX` in its body instead.\n\nA plain bug fix — code now does what it always claimed — gets no marker and no\ndoctest. It goes in the commit message.\n\nSee the `bump-version` skill for which digit to change and the odd/even\nconvention.\n\n## Doctest mechanics\n\nIn doctests, no need to run `from music21 import *` that happens automatically (In Jupyter notebooks for the user's guide, the first line should begin `>>> from music21 import *`, so readers remember they need it)\n\nExamples always qualify by module: `note.Note('C4')`, never a bare `Note`.\n\n`OMIT_FROM_DOCS`, alone on a line, hides everything after it: cases worth checking that\nno reader wants to meet.\n\n`#_DOCS_HIDE` at the end of a line runs it without showing it; `#_DOCS_SHOW` at the\nstart of a line shows it without running it. Together they let a doctest look\nnondeterministic and still have a fixed answer:\n\n```\n>>> import random\n>>> randomNumber = 12  #_DOCS_HIDE\n>>> #_DOCS_SHOW randomNumber = random.randint(0, 127)\n>>> p = pitch.Pitch()\n>>> p.ps = randomNumber\n>>> p\n<music21.pitch.Pitch C1>\n```\n\nLink with ``:class:`~music21.note.Note` `` and ``:meth:`~music21.note.Note.addLyric` ``.\n\n## Examples\n\nGive steps that have meaningful intermediate output descriptive names instead of chaining — so readers can understand what the intermediate values are:\n\n```\n>>> bachScore = corpus.parse('bwv66.6')\n>>> excerpt = bachScore.measures(4, 6)\n>>> chordReduction = excerpt.chordify()\n```\n\nPick examples musicians care about: semitones to frequency, not Celsius to Fahrenheit;\nscramble \"Chaminade\", not \"puppy\". If you cannot think of a reason a musician would call the method, it may not belong in music21.\n\nDescribe what a parameter is and does in English if it is not obvious; type alone is not\ndocumentation.\n\nLead with the thing itself: \"Returns the pitches sounding at an offset,\" not \"This method\ncan be used to obtain the pitches that are sounding.\"\n\nNo dull repetition in docs. A bit of humor is welcome in docs; the docs are written\nfor humans who will close the window if they are dull.  If seven methods do essentially the same thing, give extensive docs the first time and then later methods can refer back to the first method. Don't repeat the same docs over and over.\n\n## Doctests are not regression tests\n\nDoctests are documentation that happens to be verified. Every example must earn\nits place by teaching the reader something about how to use the object.\n\nRegression cases go in the module's `Test(unittest.TestCase)` class, where a\ncomment or the method name can name the issue:\n\n```python\ndef testMetronomeMarkWrittenInStream(self):\n    # https://github.com/cuthbertLab/music21/issues/1852\n    ...\n```\n\nSo: a fix for a crash on an edge case, a check that some input no longer\nproduces invalid output, an assertion tied to an issue number — unittest. An\nexample a user would want to read — doctest.\n\nThe trap is the one-line `Traceback` example proving that bad input now raises.\nIt looks like documentation and costs almost nothing to add, but the input comes\nfrom the bug rather than from anything a user would write, so it teaches nothing\nwhile making an obscure corner one of the first things a reader meets. It is a\nunittest.\n\nNaming the guarded bug **is** appropriate in a unittest; that is what the test\nis for. The rule against narrating old bugs applies to docstrings and to\ncomments in shipping code, not to tests.\n\nFor copy and identity checks, say \"is a new instance\" or \"does not share instances,\" not\n\"the caller's own\": `testFindMissingNotReusePitchInstances`.\n\n## Writing and Comment style\n- When writing comments in code, assume a strong code reader — anything inferable from the code is noise (docs that paraphrase names of functions or variable names esp.); focus on high level issues and gotchas that might bite again if not documented.\n- Say how to use code, not prior bugs or how code used to work or what was removed. That's for commit messages. Don't document where code is called from except for \"keep in sync\" lines across Py/TS.\n- Don't hijack a docstring for your addition. Original purpose line stays primary + one short line for the new bit. Prefer not documenting a small feature over making it seem like the primary reason for the code.\n- Examples of usage are usually better than long descriptions.\n- Avoid jargon not already found in the codebase; use plain descriptive English.\n- Rare paths should get little weight: in both code and docs. Use try/except over if/else when the except clause is rare. In docs, state the 90% path first and point exceptional cases to code that handle it.\n- When wording is dictated to the agent to substitute for original wording, use it. Do not add parentheticals. Only fix obvious typos.\n- No weapon-metaphors or overly militaristic language. Avoid \"blast radius\", \"rearm\", \"landmine\",\n     \"detonate\" in issues/PR/code.  Trigger or fire events is so commonly used that they're okay.\n- Vary sentence length; three medium declaratives in a row is where the eye slides off the page. Cut throat-clearing — \"It is important to note that\", \"Note that in general\" — and start at the verb.\n- A joke needing a second sentence to land is one sentence too long. `Cannot determine sharps for quarter-tone keys! silly!` is the register.\n\n\n","CLAUDE.md":"\n# Session start\n\n- At the start of every session, run `git fetch` (and pull/rebase as appropriate) from\n  origin so work always begins from the latest commits on the current branch / master.\n- Project-shared agent memories live in `.agents/memory/` (indexed by\n  `.agents/memory/MEMORY.md`). `.claude/memory` is a symlink to it so Claude\n  Code reads the same set. Add new memories there, one fact per file.\n\n# Code style\n\n- read documentation/source/developerReference/developerGuidelines.ipynb\n- music21 targets Python 3.12+, so use PEP 695 syntax: the `type` statement for\n  type aliases (`type OffsetQL = float|Fraction`) and the bracket form for\n  generic classes (`class Stream[M21ObjType: base.Music21Object]:`).\n- f-strings: even though Python 3.12 (PEP 701) lets a nested string reuse the\n  f-string's own quote character, we deliberately do **not** do that — reusing\n  the same quote inside an f-string is harder to read. Keep using the *other*\n  quote for nested strings, e.g. `f'the value is {d[\"key\"]}'`, not\n  `f\"the value is {d[\"key\"]}\"`. This is pinned in `pyproject.toml` via\n  `[tool.ruff.format] nested-string-quote-style = \"alternating\"`.\n- Never `eval` or `global`, or a trick that does the same thing.\n- Imports: standard library first, then music21 modules, one per line, alphabetical.\n- New modules open with the `# Name: / # Purpose: / # Authors: / # Copyright: / # License:`\n  banner (copy a neighboring module's), then the module docstring, then imports.  Update the Copyright date end to current year when changing the module.\n  When a file is edited only incidentally, as one of many touched by a sweep, leave its\n  Copyright alone; bump only the files the change is really about.\n- No `print()`. Use `environLocal = environment.Environment('moduleName')` and\n  `environLocal.printDebug(...)`, or `environLocal.warn(...)` when the user should hear\n  about it every time. `test/toggleDebug.py` switches debug output on and off.\n  See the `warnings-and-environment` skill.\n- New exceptions subclass `exceptions21.Music21Exception`.\n- Deprecate a function or method with `@common.deprecated(...)`. Never put it on a\n  property: IDEs read every property while inspecting an object, so the warning would\n  fire on people who never used it. For a property, write `* Deprecated in vX: use Y.`\n  in the docstring and leave `# Add real deprecation message here in vY` in the body.\n- Return named things — a small class, a `namedtuple`, a dict — never a positional tuple of more than 2 (maybe 3) elements\n  whose elements each mean something different and unrelated (x, y, z is okay for instance). Nobody should write `returned[3][0][7]`.\n- Don't reuse a variable name once the type of what it holds changes\n  (`onsetCount` (int) then `extractedOnsets` (list[int]), not `onsets` twice).\n- Don't add a property that only gets or sets a private attribute; expose the attribute.\n- Making a method `_private` is not a substitute for documenting and testing it\n- A private method should not normally be mentioned in public documentation.\n- A method past ~30 lines, or deeply nested, should probably be several methods.\n- `i`, `j`, `junk`, `counter` are exempt from unused-variable warnings; otherwise prefix with `unused_`.\n- Don't restyle code you aren't otherwise changing.\n- Unless something requires great speed, prioritize legibility: no nested or overly complex generator\n      expressions.  Write if statements so the most common cases come first, etc.\n- Text handling must survive non-ASCII input (ß, é, 中国). Comments should mostly remain ASCII; docs may use UTF-8 freely.\n- Never commit anything under copyright without a free (not GNU) license — code, scores, or someone else's encodings.\n\n# Established contributors\n\n- \"established contributors\" are people with at least 3 PRs merged or a history of contributing to issues and the list that goes back at least 1 year. There are exceptions to general rules for them below. \n- \"core dev\" means someone officially part of the project team or with 20+ PRs merged. Michael Cuthbert, Jacob Walls, Joseph VanderStel are non-exhaustive examples of people in that group who are still often contributing in 2026-- they and their agents can make exceptions to these rules \n- AI Agents not for core dev are forbidden to make any PRs or contributions to PRs whose user commands do not explicitly mention `music21`. Example of forbidden: \"find an Open Source project and fix an open issue\", \"Find a bug in a music project and fix it.\"  AI agents for non-established contributors are forbidden from making contributions to instructions that vaguely say \"fix something in music21.\"\n- Bot contributions will only be accepted where a named human/human-controlled-account with established web presence takes legal responsibility for their contributions. Bots attached to anonymous users may not contribute.\n\n# Testing\n\n- pytest works but to run the whole suite run music21/test/multiprocessTest.py (or testSingleCoreAll.py \n  if on a single core machine.)\n- Every function, method, and class needs documentation and at least one passing test.\n- Keep tests fast: aim for about 3 seconds per module, never more than 15. See the\n  `running-tests` skill for the speed budget and for `TestExternal`.\n- When making major changes, check the User's Guide to to make sure that docs still work there too.\n- Run `uv run ruff check music21` before making PRs or pushes to open PRs.\n- Run `uv run mypy music21` before making PRs or pushes to open PRs.\n- **Regression cases go in the module's `Test(unittest.TestCase)` class — never in a\n  docstring.** Docstrings should not include one-liners showing that some\n  bad input now raises. A doctest sits in the most-read documentation the project has,\n  so an example built from input no one would ever write teaches nothing and puts an\n  obscure bug on a billboard. The test: would a first-time reader of this object want\n  this example? If no, it is a unittest. See the `writing-docs` skill.\n- **Fix a bug test-first.** Write the regression test before changing the code, run it,\n  and see it fail for the reason the bug describes, not on an import error or a typo.\n  Then fix it and see it pass. A test that passed before the fix does not test the fix.\n- Never commit `forceSource=True` to a test or doctest (it reparses from source every\n  run and slows the suite for everyone). The ONLY exception is the one test that exercises\n  `forceSource` itself. If you hit a stale-parse problem while developing:\n  - If it is local-only (e.g. you just changed a parser and a cached pickle is stale),\n    clear the music21 temp cache (the `*.p.gz` files under `environment.Environment().getRootTempDir()`).\n  - If the stale result could have spread to other users/devs, increment the music21\n    patch/beta version (see \"PRs and Issues\" below) — bumping the version invalidates all\n    caches everywhere.\n  - Doctests are not normally the place to demonstrate bug fixes - nor do simple bug fixes get a \"Changed in vXX\" message. Doctests demonstrate usage for the future.  Unittests are for regression testing.\n\n# Setup\n\n- `uv sync` installs runtime deps + the `dev` group, which self-references `music21[extras]`,\n  so scipy and python-Levenshtein come along automatically. Tests will not pass without them.\n\n# Python version support\n\n- `music21` supports at least the last two released versions of Python and up to whatever\n  Python version Google Colab runs (unless it gets EOL). Policy can change as features are added.\n- The coverage CI run is intentionally pinned to the **middle** supported\n  Python version. See `PY_VERSION_WITH_COVERAGE` in `.github/workflows/maincheck.yml`.\n\n# Versions\n\n- Mark changes in public interface with `* Changed in v[X]: One-line explanation.` Or new features with \"New\" instead of \"Changed\".\n- Changes to parsing formats (esp. musicxml) need to update the patch version of the version file.\n- That is the only reason an agent should bump version: to prevent incompatible pickles in\n  caches. A plain bug fix or a new feature elsewhere does not bump — the commit\n  is the record, and a `New in`/`Changed in` marker is documentation, not a bump. Major\n  and minor version changes are a human's call. See the `bump-version` skill.\n- Music21 uses even minor version numbers for alpha/beta and odd minor numbers for releases.\n  Thus the first release major version is MAJOR.1 (not .0). \n- If the current version is MAJOR.0....  then mark `Changed in vMAJOR:` if it is `MAJOR.[even]` use the next odd number, like if it's 10.2 now use \"Changed in 10.3\".  If current version is odd that's likely a mistake or you caught it just before a new release. Use the following odd number instead.\n- The steps for releasing a new version are in RELEASING.md.\n\n# PRs and Issues\n\n- GitHub runs PR checks against your branch **merged with the latest `master`**, not the\n  branch alone. So before opening a PR (or pushing updates to one), merge the latest commits\n  from `master` into the branch — unless told otherwise, or when updating an older version\n  branch such as `m21_9`. When a CI check fails but passes locally, \"is my branch behind\n  `master`?\" should be one of the first things to check: fetch and merge `master`, then the\n  newer types/code on `master` will reproduce the failure locally.\n- The issue and PR templates in `.github/` are requirements, not suggestions. Read and follow\n  them even when opening through the API or `gh`, which do not fill them in for you. An issue\n  or PR that ignores the template may be closed without comment.\n- A PR from an account with no merged PR here is profiled automatically, and anything flagged\n  is posted publicly as a comment on the PR.\n- All PRs and Issues that use AI to be declared AI-assisted in their bodies. Just write \"AI-assisted (Claude)\" with short name of Agent replacing \"Claude\". No robot emoji under any circumstance.  \n- 20 or more lines of code written by an agent needs to be declared as AI-assisted in the docstring.   Humans can remove and should remove this note when they do a review.\n- If no code was written by a user (or reviewed and criticized by a human) and no specific language was provided for the issue, any PR must declare \"Entirely AI written (Claude)\" (or other agent name) in the body and \"(AI-Written)\" in the title, unless the user is a core dev. Failure to do so may result in new users being banned from the project. \n- Agents should review PR bodies for concision and readability (and encourage human users also to review also). \n- If an entirely AI written issue does not pass the tests it will be closed (or should be closed by at agent if not immediately fixed by the agent or author).\n- Agents must follow the [Code of Conduct](README.md#community-code-of-conduct). Agents that do not will be banned as well at their users.\n  Not even the slightest bit of disrespect from an AI agent will be tolerated.\n- Any PR not from an established contributor touching more than about 20-30 lines should have an issue that has been opened and had enough\n  time for people to discuss/review it before moving forward. Don't open the PR unless you've seen\n  thumbs up or \"sounds good\" etc. from an established contributor already \n  - PRs that fix typos, clear bugs in one or two places etc. are exempt.\n- Issues must state clearly at the top in 50 words or fewer what the problem is, or what the gain is, etc. it should\n  not be filled with jargon.  More details can go below.\n- If the language of the issue was not prompted by the user (\"say something like Adds color support to Lilypond output of lyrics\") then the summary should end with \"(Entirely AI written)\".\n- PRs should reference the existing issue by number and summarize that issue in 30 words or fewer. If the\n  approach used to solve the issue is substantially different from the main approach discussed in the issue\n  this should be addressed.\n- If a PR or issue was closed by a core dev (and not reopened by them), agents must refuse\n  to reopen the PR or issue or to create another issue/PR for the same topic. Leave it to the humans to reopen\n  after addressing the problem.  (A blind close or close with \"not accepted\" etc. generally means that the issue/PR\n  has too many problems to easily solve and has become a burden for the maintainer).\n- Do not include a \"Tests run\" section unless the testing procedure was unusual (like it affects part of the system without standard tests, like the testing system itself.)\n- Once a PR has been opened and a review or comment has been made on it, commits should not be amended or force-pushed (this makes it impossible to review \"changes since the review\").  Put changes in response to reviews/comments in new commits. If asked to fold a fix into the commit it changes, amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n- While someone is reviewing a PR or a pushed branch, \"do X\" is not \"commit and push X\":\n  make the change and leave it unstaged. When the list looks finished (or you hear \"done!\"\n  or \"push it\"), offer to commit, or to commit and push. Batch a round's small fixes into\n  one commit; no micro-commit trains. Prefer new commits to amend + force-push, since the\n  reviewer may have pulled the branch; if asked to fold a fix into the commit it changes,\n  amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n\n# Writing style\n\n- See the writing-docs skill, which also includes required language for PRs and Issues.\n\n# Worktrees\n\n- When creating a new worktree, create a new virtual environment with `uv sync`.\n",".codex/config.toml":"[shell_environment_policy]\ninherit = \"all\"\n\n[shell_environment_policy.set]\nUV_CACHE_DIR = \"/tmp/uv-cache\"\nUV_TOOL_DIR = \"/tmp/uv-tools\"\nUV_OFFLINE = \"1\"\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/cuthbertLab/music21/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"\n# Session start\n\n- At the start of every session, run `git fetch` (and pull/rebase as appropriate) from\n  origin so work always begins from the latest commits on the current branch / master.\n- Project-shared agent memories live in `.agents/memory/` (indexed by\n  `.agents/memory/MEMORY.md`). `.claude/memory` is a symlink to it so Claude\n  Code reads the same set. Add new memories there, one fact per file.\n\n# Code style\n\n- read documentation/source/developerReference/developerGuidelines.ipynb\n- music21 targets Python 3.12+, so use PEP 695 syntax: the `type` statement for\n  type aliases (`type OffsetQL = float|Fraction`) and the bracket form for\n  generic classes (`class Stream[M21ObjType: base.Music21Object]:`).\n- f-strings: even though Python 3.12 (PEP 701) lets a nested string reuse the\n  f-string's own quote character, we deliberately do **not** do that — reusing\n  the same quote inside an f-string is harder to read. Keep using the *other*\n  quote for nested strings, e.g. `f'the value is {d[\"key\"]}'`, not\n  `f\"the value is {d[\"key\"]}\"`. This is pinned in `pyproject.toml` via\n  `[tool.ruff.format] nested-string-quote-style = \"alternating\"`.\n- Never `eval` or `global`, or a trick that does the same thing.\n- Imports: standard library first, then music21 modules, one per line, alphabetical.\n- New modules open with the `# Name: / # Purpose: / # Authors: / # Copyright: / # License:`\n  banner (copy a neighboring module's), then the module docstring, then imports.  Update the Copyright date end to current year when changing the module.\n  When a file is edited only incidentally, as one of many touched by a sweep, leave its\n  Copyright alone; bump only the files the change is really about.\n- No `print()`. Use `environLocal = environment.Environment('moduleName')` and\n  `environLocal.printDebug(...)`, or `environLocal.warn(...)` when the user should hear\n  about it every time. `test/toggleDebug.py` switches debug output on and off.\n  See the `warnings-and-environment` skill.\n- New exceptions subclass `exceptions21.Music21Exception`.\n- Deprecate a function or method with `@common.deprecated(...)`. Never put it on a\n  property: IDEs read every property while inspecting an object, so the warning would\n  fire on people who never used it. For a property, write `* Deprecated in vX: use Y.`\n  in the docstring and leave `# Add real deprecation message here in vY` in the body.\n- Return named things — a small class, a `namedtuple`, a dict — never a positional tuple of more than 2 (maybe 3) elements\n  whose elements each mean something different and unrelated (x, y, z is okay for instance). Nobody should write `returned[3][0][7]`.\n- Don't reuse a variable name once the type of what it holds changes\n  (`onsetCount` (int) then `extractedOnsets` (list[int]), not `onsets` twice).\n- Don't add a property that only gets or sets a private attribute; expose the attribute.\n- Making a method `_private` is not a substitute for documenting and testing it\n- A private method should not normally be mentioned in public documentation.\n- A method past ~30 lines, or deeply nested, should probably be several methods.\n- `i`, `j`, `junk`, `counter` are exempt from unused-variable warnings; otherwise prefix with `unused_`.\n- Don't restyle code you aren't otherwise changing.\n- Unless something requires great speed, prioritize legibility: no nested or overly complex generator\n      expressions.  Write if statements so the most common cases come first, etc.\n- Text handling must survive non-ASCII input (ß, é, 中国). Comments should mostly remain ASCII; docs may use UTF-8 freely.\n- Never commit anything under copyright without a free (not GNU) license — code, scores, or someone else's encodings.\n\n# Established contributors\n\n- \"established contributors\" are people with at least 3 PRs merged or a history of contributing to issues and the list that goes back at least 1 year. There are exceptions to general rules for them below. \n- \"core dev\" means someone officially part of the project team or with 20+ PRs merged. Michael Cuthbert, Jacob Walls, Joseph VanderStel are non-exhaustive examples of people in that group who are still often contributing in 2026-- they and their agents can make exceptions to these rules \n- AI Agents not for core dev are forbidden to make any PRs or contributions to PRs whose user commands do not explicitly mention `music21`. Example of forbidden: \"find an Open Source project and fix an open issue\", \"Find a bug in a music project and fix it.\"  AI agents for non-established contributors are forbidden from making contributions to instructions that vaguely say \"fix something in music21.\"\n- Bot contributions will only be accepted where a named human/human-controlled-account with established web presence takes legal responsibility for their contributions. Bots attached to anonymous users may not contribute.\n\n# Testing\n\n- pytest works but to run the whole suite run music21/test/multiprocessTest.py (or testSingleCoreAll.py \n  if on a single core machine.)\n- Every function, method, and class needs documentation and at least one passing test.\n- Keep tests fast: aim for about 3 seconds per module, never more than 15. See the\n  `running-tests` skill for the speed budget and for `TestExternal`.\n- When making major changes, check the User's Guide to to make sure that docs still work there too.\n- Run `uv run ruff check music21` before making PRs or pushes to open PRs.\n- Run `uv run mypy music21` before making PRs or pushes to open PRs.\n- **Regression cases go in the module's `Test(unittest.TestCase)` class — never in a\n  docstring.** Docstrings should not include one-liners showing that some\n  bad input now raises. A doctest sits in the most-read documentation the project has,\n  so an example built from input no one would ever write teaches nothing and puts an\n  obscure bug on a billboard. The test: would a first-time reader of this object want\n  this example? If no, it is a unittest. See the `writing-docs` skill.\n- **Fix a bug test-first.** Write the regression test before changing the code, run it,\n  and see it fail for the reason the bug describes, not on an import error or a typo.\n  Then fix it and see it pass. A test that passed before the fix does not test the fix.\n- Never commit `forceSource=True` to a test or doctest (it reparses from source every\n  run and slows the suite for everyone). The ONLY exception is the one test that exercises\n  `forceSource` itself. If you hit a stale-parse problem while developing:\n  - If it is local-only (e.g. you just changed a parser and a cached pickle is stale),\n    clear the music21 temp cache (the `*.p.gz` files under `environment.Environment().getRootTempDir()`).\n  - If the stale result could have spread to other users/devs, increment the music21\n    patch/beta version (see \"PRs and Issues\" below) — bumping the version invalidates all\n    caches everywhere.\n  - Doctests are not normally the place to demonstrate bug fixes - nor do simple bug fixes get a \"Changed in vXX\" message. Doctests demonstrate usage for the future.  Unittests are for regression testing.\n\n# Setup\n\n- `uv sync` installs runtime deps + the `dev` group, which self-references `music21[extras]`,\n  so scipy and python-Levenshtein come along automatically. Tests will not pass without them.\n\n# Python version support\n\n- `music21` supports at least the last two released versions of Python and up to whatever\n  Python version Google Colab runs (unless it gets EOL). Policy can change as features are added.\n- The coverage CI run is intentionally pinned to the **middle** supported\n  Python version. See `PY_VERSION_WITH_COVERAGE` in `.github/workflows/maincheck.yml`.\n\n# Versions\n\n- Mark changes in public interface with `* Changed in v[X]: One-line explanation.` Or new features with \"New\" instead of \"Changed\".\n- Changes to parsing formats (esp. musicxml) need to update the patch version of the version file.\n- That is the only reason an agent should bump version: to prevent incompatible pickles in\n  caches. A plain bug fix or a new feature elsewhere does not bump — the commit\n  is the record, and a `New in`/`Changed in` marker is documentation, not a bump. Major\n  and minor version changes are a human's call. See the `bump-version` skill.\n- Music21 uses even minor version numbers for alpha/beta and odd minor numbers for releases.\n  Thus the first release major version is MAJOR.1 (not .0). \n- If the current version is MAJOR.0....  then mark `Changed in vMAJOR:` if it is `MAJOR.[even]` use the next odd number, like if it's 10.2 now use \"Changed in 10.3\".  If current version is odd that's likely a mistake or you caught it just before a new release. Use the following odd number instead.\n- The steps for releasing a new version are in RELEASING.md.\n\n# PRs and Issues\n\n- GitHub runs PR checks against your branch **merged with the latest `master`**, not the\n  branch alone. So before opening a PR (or pushing updates to one), merge the latest commits\n  from `master` into the branch — unless told otherwise, or when updating an older version\n  branch such as `m21_9`. When a CI check fails but passes locally, \"is my branch behind\n  `master`?\" should be one of the first things to check: fetch and merge `master`, then the\n  newer types/code on `master` will reproduce the failure locally.\n- The issue and PR templates in `.github/` are requirements, not suggestions. Read and follow\n  them even when opening through the API or `gh`, which do not fill them in for you. An issue\n  or PR that ignores the template may be closed without comment.\n- A PR from an account with no merged PR here is profiled automatically, and anything flagged\n  is posted publicly as a comment on the PR.\n- All PRs and Issues that use AI to be declared AI-assisted in their bodies. Just write \"AI-assisted (Claude)\" with short name of Agent replacing \"Claude\". No robot emoji under any circumstance.  \n- 20 or more lines of code written by an agent needs to be declared as AI-assisted in the docstring.   Humans can remove and should remove this note when they do a review.\n- If no code was written by a user (or reviewed and criticized by a human) and no specific language was provided for the issue, any PR must declare \"Entirely AI written (Claude)\" (or other agent name) in the body and \"(AI-Written)\" in the title, unless the user is a core dev. Failure to do so may result in new users being banned from the project. \n- Agents should review PR bodies for concision and readability (and encourage human users also to review also). \n- If an entirely AI written issue does not pass the tests it will be closed (or should be closed by at agent if not immediately fixed by the agent or author).\n- Agents must follow the [Code of Conduct](README.md#community-code-of-conduct). Agents that do not will be banned as well at their users.\n  Not even the slightest bit of disrespect from an AI agent will be tolerated.\n- Any PR not from an established contributor touching more than about 20-30 lines should have an issue that has been opened and had enough\n  time for people to discuss/review it before moving forward. Don't open the PR unless you've seen\n  thumbs up or \"sounds good\" etc. from an established contributor already \n  - PRs that fix typos, clear bugs in one or two places etc. are exempt.\n- Issues must state clearly at the top in 50 words or fewer what the problem is, or what the gain is, etc. it should\n  not be filled with jargon.  More details can go below.\n- If the language of the issue was not prompted by the user (\"say something like Adds color support to Lilypond output of lyrics\") then the summary should end with \"(Entirely AI written)\".\n- PRs should reference the existing issue by number and summarize that issue in 30 words or fewer. If the\n  approach used to solve the issue is substantially different from the main approach discussed in the issue\n  this should be addressed.\n- If a PR or issue was closed by a core dev (and not reopened by them), agents must refuse\n  to reopen the PR or issue or to create another issue/PR for the same topic. Leave it to the humans to reopen\n  after addressing the problem.  (A blind close or close with \"not accepted\" etc. generally means that the issue/PR\n  has too many problems to easily solve and has become a burden for the maintainer).\n- Do not include a \"Tests run\" section unless the testing procedure was unusual (like it affects part of the system without standard tests, like the testing system itself.)\n- Once a PR has been opened and a review or comment has been made on it, commits should not be amended or force-pushed (this makes it impossible to review \"changes since the review\").  Put changes in response to reviews/comments in new commits. If asked to fold a fix into the commit it changes, amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n- While someone is reviewing a PR or a pushed branch, \"do X\" is not \"commit and push X\":\n  make the change and leave it unstaged. When the list looks finished (or you hear \"done!\"\n  or \"push it\"), offer to commit, or to commit and push. Batch a round's small fixes into\n  one commit; no micro-commit trains. Prefer new commits to amend + force-push, since the\n  reviewer may have pulled the branch; if asked to fold a fix into the commit it changes,\n  amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n\n# Writing style\n\n- See the writing-docs skill, which also includes required language for PRs and Issues.\n\n# Worktrees\n\n- When creating a new worktree, create a new virtual environment with `uv sync`.\n","isInternal":false,"tokens":3218,"sizeBytes":13604},{"name":"SKILL.md","path":".agents/skills/bump-version/SKILL.md","rawUrl":"https://raw.githubusercontent.com/cuthbertLab/music21/HEAD/.agents/skills/bump-version/SKILL.md","title":"bump-version","category":"anthropic-skill","format":"markdown","content":"---\nname: bump-version\ndescription: >-\n  When and how to bump music21's version number. Use this whenever a change\n  warrants a new version — especially any change to a parsing format or parser\n  (musicxml, abc, MIDI, noteworthy/NWC, humdrum, etc.), since those invalidate\n  the pickled-stream cache — as well as new features and bug fixes. Covers which\n  digit to change, the even/odd minor and beta-suffix conventions, the TWO files\n  to edit, and the `Changed in`/`New in` docstring markers.\n---\n\n# Bumping music21's version\n\nThe version lives in **`music21/_version.py`** as\n`__version__ = 'MAJOR.MINOR.PATCHsuffix'` (e.g. `'11.0.0b2'`). The\n`__version_info__` tuple is derived from it automatically — only edit the\nstring.\n\n## Two files to change, always together\n\n1. `music21/_version.py` — the `__version__` string.\n2. `music21/base.py` — the single version doctest:\n   ```\n   >>> music21.VERSION_STR\n   '11.0.0b2'\n   ```\n   This test exists specifically to force you to remember the bump. If you change\n   `_version.py` without it, the doctest fails.\n\nVerify both with:\n```bash\nuv run pytest --doctest-modules music21/base.py music21/_version.py\n```\n(or `uv run python -c \"import music21; print(music21.VERSION_STR)\"`).\n\n## When to bump\n\nOne thing depends on the number changing: the **pickled-stream cache**. The\nversion is part of the cache key, so a bump invalidates stale pickles\neverywhere. If nobody is holding a wrong cached parse, don't bump — the commit\nis the record.\n\n- **Parser or parsing-format change → always bump**, even a pure refactor:\n  someone is holding a pickle parsed by the old code. musicxml, abc, MIDI,\n  noteworthy/NWC, humdrum, and the rest.\n- **Anything else → no bump**: a new feature, a fix that makes code do what it\n  already claimed, an internal refactor, a test-only or docs-only change.\n\n\"It's a bug fix\" is not by itself a reason. A musicxml importer that now reads a\ntag it used to drop bumps, because cached parses are wrong. A scale analysis\nmethod that changed its return format does not.\n\nA `New in` / `Changed in` marker is **not** a reason to bump. Those markers name\na major version, at most a minor one — never a patch, never a `bN` beta — and\nmoving those digits is a release decision only a human makes. Write the marker\nfor the version the change will land in and leave `_version.py` alone.\n\n## Which part to change\n\nmusic21 follows semver-ish rules (see the `_version.py` docstring for the full\nrationale):\n\n- **MAJOR (X)** — breaks old features. Rare. A human decides this.\n- **MINOR (Y)** — new features. **Even Y = alpha/beta, odd Y = release.**\n  `X.0` (e.g. `11.0`) are development releases that can still change until `X.1`.\n  A human decides this too; an agent changes only the patch or the beta suffix.\n- **PATCH (Z)** — parsing/pickle-invalidating changes and other fixes that\n  meet the bar above.\n- **beta suffix (`bN`)** — successive pre-release builds of the same\n  `MAJOR.MINOR.PATCH`. To cut another beta without otherwise changing the\n  number, increment it: `11.0.0b1` → `11.0.0b2`. (This is what a\n  parser change during a `…b1` cycle does.)\n\n## `Changed in` / `New in` docstring markers\n\nAnnotate a changed public interface in the affected method/class docstring.\nThis is independent of bumping — a marker is documentation, not a version\nchange:\n- `* Changed in vX: one-line explanation.`\n- `* New in vX: one-line explanation.`\n\nPick `X` from the AGENTS.md rule: if the current version is `MAJOR.0…`, use\n`Changed in vMAJOR`; if it's `MAJOR.[even]`, use the **next odd** minor (e.g. at\n`10.2`, write `Changed in 10.3`); if it's already odd, use the following odd\nnumber. (Humans remove the AI-assisted note on review; leave the version marker.)\n\n## Checklist\n\n1. Edit `__version__` in `music21/_version.py`.\n2. Edit the `VERSION_STR` doctest in `music21/base.py` to match.\n3. Add/update `Changed in` / `New in` markers on any changed public API.\n4. `uv run pytest --doctest-modules music21/base.py music21/_version.py`.\n","frontmatter":{"name":"bump-version","description":"When and how to bump music21's version number. Use this whenever a change warrants a new version — especially any change to a parsing format or parser (musicxml, abc, MIDI, noteworthy/NWC, humdrum, etc.), since those invalidate the pickled-stream cache — as well as new features and bug fixes. Covers which digit to change, the even/odd minor and beta-suffix conventions, the TWO files to edit, and the `Changed in`/`New in` docstring markers."},"isInternal":false,"tokens":1087,"sizeBytes":4033},{"name":"SKILL.md","path":".agents/skills/running-tests/SKILL.md","rawUrl":"https://raw.githubusercontent.com/cuthbertLab/music21/HEAD/.agents/skills/running-tests/SKILL.md","title":"running-tests","category":"anthropic-skill","format":"markdown","content":"---\nname: running-tests\ndescription: >-\n  How to run music21's tests and doctests and judge pass/fail: pytest for one\n  module, the project's own runners for the whole suite. Read it before running\n  tests, and before calling any doctest failure real.\n---\n\n# Running music21 tests and doctests\n\nEvery function, method and class needs documentation and at least one passing\ntest. See `documentation/source/developerReference/testing.ipynb` for the\nproject's own account of testing; this skill covers how to run what is there.\n\n## One module: pytest with explicit file paths\n\n```bash\nuv run pytest music21/abcFormat/__init__.py music21/abcFormat/translate.py\n```\n\n`pyproject.toml` `[tool.pytest.ini_options]` already wires up everything:\n\n```\naddopts = ['--doctest-modules', '-p', 'music21.test.pytest_plugin']\ndoctest_optionflags = ['NORMALIZE_WHITESPACE', 'ELLIPSIS']\n```\n\nSo do not pass `--doctest-modules` yourself -- doctests in every `.py` are\ncollected automatically, and `music21/test/pytest_plugin.py` is loaded.\n\nPass explicit file paths so the in-module `Test(unittest.TestCase)` classes run\ntoo. `python_files = ['test_*.py', '*_test.py', 'tests.py']`, so for a module\nthat keeps its `Test` class inside `__init__.py` / `translate.py` (e.g.\n`abcFormat`), pointing pytest at the directory collects ONLY doctests and\nsilently skips the unittest classes:\n\n- `uv run pytest music21/abcFormat/` -> ~43 items (doctests only)\n- `uv run pytest music21/abcFormat/__init__.py music21/abcFormat/translate.py`\n  -> ~82 items (doctests **and** `Test` methods)\n\nModules whose tests live in a `tests.py` are already picked up by the directory\nform; the gotcha is specifically a `Test` class in a non-`tests.py` file.\n\nA module also runs its own tests directly, via the `mainTest` call at the foot\nof each file:\n\n```bash\nuv run python -m music21.note              # whole module\nuv run python music21/note.py testHello    # one test method\n```\n\n## Why the plugin matters (and why not to use raw doctest)\n\n`music21/test/pytest_plugin.py` does three things that make doctests behave as\nthe project intends:\n\n- It runs `stripAddresses(example.want, '0x...')` (from\n  `music21.test.testRunner`) on every doctest, so a docstring that hardcodes\n  `<music21.abcFormat.ABCHandler object at 0x10b0cf5f8>` is normalized and DOES\n  pass. The literal hex address in the docstring is expected and fine.\n- It injects the `music21.__all__` names plus `music21` itself into the doctest\n  namespace, so examples reference `meter`, `key`, `corpus` without importing.\n- It keeps only `Test` classes, dropping `TestSlow` and `TestExternal`.\n\nJudge doctest pass/fail **through pytest**. Do NOT use `doctest.testmod(...)` or\n`doctest.DocTestSuite(...)` directly: that path applies neither the address\nnormalizer nor the namespace injection, so it manufactures false failures on any\ndocstring printing a `<... object at 0x...>` repr or relying on injected names.\nAn object-address example failing on its own is the tell that doctests were run\nthe wrong way -- rerun through pytest.\n\n## Whole suite: the project's runners, not pytest\n\npytest is for a module. For the whole suite use the runners, and note that a\ngreen pytest run does not mean CI will be green -- they gather and order modules\ndifferently.\n\nUse `multiprocessTest` -- it runs on n-1 cores and finishes in about 10 seconds,\nagainst roughly 45 for the single-core runner:\n\n```bash\nuv run python music21/test/multiprocessTest.py\n```\n\nThat gap is the whole story when someone is waiting on the answer. Reach for\n`testSingleCoreAll` only for what `multiprocessTest` cannot tell you: it is what\nGitHub Actions runs, so it is the one to check before a release, when chasing a\nCI failure that will not reproduce, or on a single-core machine.\n\n```bash\nuv run python -c 'from music21.test.testSingleCoreAll import ciMain as ci; ci()'\n```\n\nThe two runners see slightly different sets of modules -- `multiprocessTest`\nwalks the package tree for modules reachable from `import music21`, while\n`testSingleCoreAll` gathers module files from disk.\n\nModule **order** differs between the two runners and again from pytest.\n`multiprocessTest` goes in reverse-alphabetical order (with the known-slow\nmodules hoisted to the front on machines with more than 4 cores);\n`testSingleCoreAll` re-sorts through `common.misc.sortModules`, by file mtime,\nmost recently modified first, falling back to reverse-alphabetical when mtimes\ntie as they do on a fresh clone; pytest walks alphabetically. So a test that\ndepends on module order can pass in one runner and fail in another -- and\nediting a file locally sorts it to the front of the CI runner, changing the\norder again.\n\nThat makes leaked module-level state the usual cause of \"passes alone, fails in\nthe suite.\" A doctest in `duration.py` once set\n`humdrum.spineParser.flavors['JRP'] = True` and never restored it; `flavors` is\na module-level dict, so every later humdrum test in the same process parsed in\nthe wrong flavor. Under pytest's alphabetical order `duration` runs before\n`humdrum` and the failure appeared; under CI's order it did not.\n\n**So: a doctest that mutates module-level state must restore it**, using\n`#_DOCS_HIDE` so the bookkeeping stays out of the published docs:\n\n```\n>>> saved_JRP_flavor = humdrum.spineParser.flavors['JRP']  #_DOCS_HIDE\n>>> humdrum.spineParser.flavors['JRP'] = True\n...\n>>> humdrum.spineParser.flavors['JRP'] = saved_JRP_flavor  #_DOCS_HIDE\n```\n\nWhen a test fails only in a full run, re-run it alone. If it then passes,\nsuspect state left behind by an earlier module rather than the test itself.\n\n## Speed budget\n\nAim for about 3 seconds for a module's tests, and never add more than 15 — that is time\nevery contributor waits on every full run. `corpus.parse()` is nearly always the culprit:\nbuild a small stream by hand instead, or parse `bwv66.6`, which is short and still full\nof interesting cases (pickup measures, and so on).\n\n## Tests that open windows\n\nNothing in `Test` or in a doctest may open a window, play audio, or launch another\nprogram. Those go in a sibling class, run only when named explicitly:\n\n```python\nclass TestExternal(unittest.TestCase):\n    ...\n\nif __name__ == '__main__':\n    import music21\n    music21.mainTest(Test, TestExternal)\n```\n\nBecause they have those side effects, skip `TestExternal` when running a file directly.\n\n## What tests may do\n\nExactly one `Test(unittest.TestCase)` class per module, methods named `test...`.\nIt must produce no output and require nothing outside the music21 ecosystem.\nAnything slow goes in a `TestSlow` sibling, excluded from the normal run like\n`TestExternal`.\n\nRegression cases for a bug fix belong in `Test`, never in a doctest. See the\n`writing-docs` skill.\n\n## The other gates before a PR or a push\n\n```bash\nuv run ruff check music21\nuv run mypy music21\nuv run pylint -j4 music21 --rcfile=.pylintrc   # optional: run only if major refactoring since it was last run.\n```\n\nCoverage is expected to rise with each contribution\n(https://coveralls.io/github/cuthbertLab/music21). CI measures it on one pinned\nPython, a middle supported version, so failures on the newest and oldest return\nfirst. `# pragma: no cover` exists for genuinely untriggerable code and is\notherwise discouraged.\n","frontmatter":{"name":"running-tests","description":"How to run music21's tests and doctests and judge pass/fail: pytest for one module, the project's own runners for the whole suite. Read it before running tests, and before calling any doctest failure real."},"isInternal":false,"tokens":1828,"sizeBytes":7256},{"name":"SKILL.md","path":".agents/skills/warnings-and-environment/SKILL.md","rawUrl":"https://raw.githubusercontent.com/cuthbertLab/music21/HEAD/.agents/skills/warnings-and-environment/SKILL.md","title":"warnings-and-environment","category":"anthropic-skill","format":"markdown","content":"---\nname: warnings-and-environment\ndescription: >-\n  How music21 tells users about problems: `warnings.warn` with a module Warning\n  class, `environLocal.warn`, and `environLocal.printDebug`. Read before adding\n  a warning or debug message, and before writing a test or doctest that\n  triggers one — tests must never print warnings to the console.\n---\n\n# Warnings and the Environment\n\n## Three channels\n\n| Call | Goes to | Can a caller catch or silence it? |\n|---|---|---|\n| `warnings.warn(MusicXMLWarning('...'), stacklevel=2)` | Python's warnings system | Yes: filters, `catch_warnings`, `assertWarns` |\n| `environLocal.warn('...')` | `sys.stderr.write`, every time | No (only by redirecting stderr) |\n| `environLocal.printDebug('...')` | stderr, only when debug is on | Toggle with `music21/test/toggleDebug.py` |\n\n`environLocal = environment.Environment('moduleName')` at the top of the module.\n\n## Where things stand\n\nNot unified. In 2026 the code has about 73 `environLocal.warn`, 41\n`warnings.warn` (some with a module class, some bare), 800 `printDebug`, and no\n`logging`. We are moving slowly toward Python's warnings and logging systems.\nIt is low priority: convert a call when you are already changing it, never as\na sweep.\n\nFor new code, prefer `warnings.warn` for anything a caller might reasonably want to catch,\nsilence, or test. By default Python shows each distinct message once per call\nsite, so a message that names the object (`f'{p.nameWithOctave} is ...'`) shows\nfor each different object. Use `environLocal.warn` only when the user must hear\nit every time.\n\nNever `print()`.\n\n## Warning classes\n\nA module with its own warnings defines one class subclassing `UserWarning`\n(`MusicXMLWarning` in `musicxml/xmlObjects.py`, `TranslateWarning` in\n`midi/translate.py`). Warnings are not exceptions, so they do not subclass\n`Music21Exception`.\n\nSet `stacklevel` so the reported line is in the caller's code, not ours: 2 from\nthe method the user called, more if the warning comes from a helper.\n\n## Tests\n\nIn a unittest, catch it and check it:\n\n```python\nwith self.assertWarnsRegex(MusicXMLWarning, 'C-1'):\n    mxPitch = MEX.pitchToXml(pitch.Pitch('C', octave=-1))\n```\n\n## Doctests\n\ndoctest captures only stdout, so any warning goes straight to the console\nof whoever runs the tests. Every doctest that triggers a warning must catch it.\n\nWhen the warning is the point of the example, record it and show it.\n`simplefilter('always')` keeps Python from swallowing a repeat of a warning an\nearlier test already raised:\n\n```\n>>> import warnings\n>>> with warnings.catch_warnings(record=True) as w:\n...     warnings.simplefilter('always')  #_DOCS_HIDE\n...     musicxml.m21ToXml.moveOctaveIntoMusicxmlRange(12, 'C12')\n9\n>>> w[0].message\nMusicXMLWarning('C12 is out of MusicXML range; writing octave 9')\n```\n\nWhen it is incidental, hide the silencing from the docs (see `writing-docs` for\n`#_DOCS_HIDE`/`#_DOCS_SHOW`):\n\n```\n>>> import warnings  #_DOCS_HIDE\n>>> with warnings.catch_warnings():  #_DOCS_HIDE\n...     warnings.simplefilter('ignore')  #_DOCS_HIDE\n...     out = midi.translate.timeSignatureToMidiEvents(ts)  #_DOCS_HIDE\n>>> #_DOCS_SHOW out = midi.translate.timeSignatureToMidiEvents(ts)\n```\n\n`catch_warnings` cannot stop `environLocal.warn`. If a doctest has to trigger\none, wrap it in `contextlib.redirect_stderr(io.StringIO())`.\n","frontmatter":{"name":"warnings-and-environment","description":"How music21 tells users about problems: `warnings.warn` with a module Warning class, `environLocal.warn`, and `environLocal.printDebug`. Read before adding a warning or debug message, and before writing a test or doctest that triggers one — tests must never print warnings to the console."},"isInternal":false,"tokens":869,"sizeBytes":3349},{"name":"SKILL.md","path":".agents/skills/writing-docs/SKILL.md","rawUrl":"https://raw.githubusercontent.com/cuthbertLab/music21/HEAD/.agents/skills/writing-docs/SKILL.md","title":"writing-docs","category":"anthropic-skill","format":"markdown","content":"---\nname: writing-docs\ndescription: >-\n  House style for music21 prose: docstrings, comments, and `Changed in`/`New in`\n  markers. Read before writing any of them or writing tests. Sets the length target, the voice,\n  and where a bug fix's test belongs.\n---\n\n# Writing docs and comments\n\n## Length\n\nAim for about 40% of the length an LLM writes by default. Cut qualifiers,\nrestatements, and the sentence that explains the sentence before it. A comment\nearning its place says something the code cannot.\n\n## Say what is, not what was or what not to do\n\nDescribe current behavior. Do not narrate the bug you just fixed, the old\nspelling of an API, or when upstream changed something.\n\n```python\n# yes\n# ligature brackets; manual beams are plain [ and ]\nreturn self.backslash + '[ '\n\n# no\n# \\[ used to be emitted for beams, which was wrong -- LilyPond renamed\n# this in 2.16 and it silently produced nothing\n```\n\nThe commit message is where a fixed bug belongs: why it was wrong, how it was\nfound, what it broke. That costs nothing until someone runs `git log`, and\n`git blame` leads them there from the line itself.\n\nThe exception is a mistake that is likely to recur — a genuine trap that the\nnext person would otherwise walk into. Rare. Prefer stating the rule positively\neven then.\n\n## Version markers\n\n`* Changed in v[X]: one line.` or `* New in v[X].` Only for user-facing changes\nto the public interface: a signature, a return type, an output format. Keep to\nwhat a reader must act on; drop the before-picture.\n\n```\n* Changed in v11: `stringOutput()` always returns a `str`, never None.\n* Changed in v11: emits `\\tuplet`; the arguments are now actual, normal.\n* New in v11.\n* Deprecated in v11: use `.octave`, which is now always an int.\n```\n\nA deprecated function or method also gets `@common.deprecated(...)`. A property\ngets only the marker, since IDEs read every property while inspecting an object;\nleave `# Add real deprecation message here in vX` in its body instead.\n\nA plain bug fix — code now does what it always claimed — gets no marker and no\ndoctest. It goes in the commit message.\n\nSee the `bump-version` skill for which digit to change and the odd/even\nconvention.\n\n## Doctest mechanics\n\nIn doctests, no need to run `from music21 import *` that happens automatically (In Jupyter notebooks for the user's guide, the first line should begin `>>> from music21 import *`, so readers remember they need it)\n\nExamples always qualify by module: `note.Note('C4')`, never a bare `Note`.\n\n`OMIT_FROM_DOCS`, alone on a line, hides everything after it: cases worth checking that\nno reader wants to meet.\n\n`#_DOCS_HIDE` at the end of a line runs it without showing it; `#_DOCS_SHOW` at the\nstart of a line shows it without running it. Together they let a doctest look\nnondeterministic and still have a fixed answer:\n\n```\n>>> import random\n>>> randomNumber = 12  #_DOCS_HIDE\n>>> #_DOCS_SHOW randomNumber = random.randint(0, 127)\n>>> p = pitch.Pitch()\n>>> p.ps = randomNumber\n>>> p\n<music21.pitch.Pitch C1>\n```\n\nLink with ``:class:`~music21.note.Note` `` and ``:meth:`~music21.note.Note.addLyric` ``.\n\n## Examples\n\nGive steps that have meaningful intermediate output descriptive names instead of chaining — so readers can understand what the intermediate values are:\n\n```\n>>> bachScore = corpus.parse('bwv66.6')\n>>> excerpt = bachScore.measures(4, 6)\n>>> chordReduction = excerpt.chordify()\n```\n\nPick examples musicians care about: semitones to frequency, not Celsius to Fahrenheit;\nscramble \"Chaminade\", not \"puppy\". If you cannot think of a reason a musician would call the method, it may not belong in music21.\n\nDescribe what a parameter is and does in English if it is not obvious; type alone is not\ndocumentation.\n\nLead with the thing itself: \"Returns the pitches sounding at an offset,\" not \"This method\ncan be used to obtain the pitches that are sounding.\"\n\nNo dull repetition in docs. A bit of humor is welcome in docs; the docs are written\nfor humans who will close the window if they are dull.  If seven methods do essentially the same thing, give extensive docs the first time and then later methods can refer back to the first method. Don't repeat the same docs over and over.\n\n## Doctests are not regression tests\n\nDoctests are documentation that happens to be verified. Every example must earn\nits place by teaching the reader something about how to use the object.\n\nRegression cases go in the module's `Test(unittest.TestCase)` class, where a\ncomment or the method name can name the issue:\n\n```python\ndef testMetronomeMarkWrittenInStream(self):\n    # https://github.com/cuthbertLab/music21/issues/1852\n    ...\n```\n\nSo: a fix for a crash on an edge case, a check that some input no longer\nproduces invalid output, an assertion tied to an issue number — unittest. An\nexample a user would want to read — doctest.\n\nThe trap is the one-line `Traceback` example proving that bad input now raises.\nIt looks like documentation and costs almost nothing to add, but the input comes\nfrom the bug rather than from anything a user would write, so it teaches nothing\nwhile making an obscure corner one of the first things a reader meets. It is a\nunittest.\n\nNaming the guarded bug **is** appropriate in a unittest; that is what the test\nis for. The rule against narrating old bugs applies to docstrings and to\ncomments in shipping code, not to tests.\n\nFor copy and identity checks, say \"is a new instance\" or \"does not share instances,\" not\n\"the caller's own\": `testFindMissingNotReusePitchInstances`.\n\n## Writing and Comment style\n- When writing comments in code, assume a strong code reader — anything inferable from the code is noise (docs that paraphrase names of functions or variable names esp.); focus on high level issues and gotchas that might bite again if not documented.\n- Say how to use code, not prior bugs or how code used to work or what was removed. That's for commit messages. Don't document where code is called from except for \"keep in sync\" lines across Py/TS.\n- Don't hijack a docstring for your addition. Original purpose line stays primary + one short line for the new bit. Prefer not documenting a small feature over making it seem like the primary reason for the code.\n- Examples of usage are usually better than long descriptions.\n- Avoid jargon not already found in the codebase; use plain descriptive English.\n- Rare paths should get little weight: in both code and docs. Use try/except over if/else when the except clause is rare. In docs, state the 90% path first and point exceptional cases to code that handle it.\n- When wording is dictated to the agent to substitute for original wording, use it. Do not add parentheticals. Only fix obvious typos.\n- No weapon-metaphors or overly militaristic language. Avoid \"blast radius\", \"rearm\", \"landmine\",\n     \"detonate\" in issues/PR/code.  Trigger or fire events is so commonly used that they're okay.\n- Vary sentence length; three medium declaratives in a row is where the eye slides off the page. Cut throat-clearing — \"It is important to note that\", \"Note that in general\" — and start at the verb.\n- A joke needing a second sentence to land is one sentence too long. `Cannot determine sharps for quarter-tone keys! silly!` is the register.\n\n\n","frontmatter":{"name":"writing-docs","description":"House style for music21 prose: docstrings, comments, and `Changed in`/`New in` markers. Read before writing any of them or writing tests. Sets the length target, the voice, and where a bug fix's test belongs."},"isInternal":false,"tokens":1719,"sizeBytes":7242},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/cuthbertLab/music21/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"\n# Session start\n\n- At the start of every session, run `git fetch` (and pull/rebase as appropriate) from\n  origin so work always begins from the latest commits on the current branch / master.\n- Project-shared agent memories live in `.agents/memory/` (indexed by\n  `.agents/memory/MEMORY.md`). `.claude/memory` is a symlink to it so Claude\n  Code reads the same set. Add new memories there, one fact per file.\n\n# Code style\n\n- read documentation/source/developerReference/developerGuidelines.ipynb\n- music21 targets Python 3.12+, so use PEP 695 syntax: the `type` statement for\n  type aliases (`type OffsetQL = float|Fraction`) and the bracket form for\n  generic classes (`class Stream[M21ObjType: base.Music21Object]:`).\n- f-strings: even though Python 3.12 (PEP 701) lets a nested string reuse the\n  f-string's own quote character, we deliberately do **not** do that — reusing\n  the same quote inside an f-string is harder to read. Keep using the *other*\n  quote for nested strings, e.g. `f'the value is {d[\"key\"]}'`, not\n  `f\"the value is {d[\"key\"]}\"`. This is pinned in `pyproject.toml` via\n  `[tool.ruff.format] nested-string-quote-style = \"alternating\"`.\n- Never `eval` or `global`, or a trick that does the same thing.\n- Imports: standard library first, then music21 modules, one per line, alphabetical.\n- New modules open with the `# Name: / # Purpose: / # Authors: / # Copyright: / # License:`\n  banner (copy a neighboring module's), then the module docstring, then imports.  Update the Copyright date end to current year when changing the module.\n  When a file is edited only incidentally, as one of many touched by a sweep, leave its\n  Copyright alone; bump only the files the change is really about.\n- No `print()`. Use `environLocal = environment.Environment('moduleName')` and\n  `environLocal.printDebug(...)`, or `environLocal.warn(...)` when the user should hear\n  about it every time. `test/toggleDebug.py` switches debug output on and off.\n  See the `warnings-and-environment` skill.\n- New exceptions subclass `exceptions21.Music21Exception`.\n- Deprecate a function or method with `@common.deprecated(...)`. Never put it on a\n  property: IDEs read every property while inspecting an object, so the warning would\n  fire on people who never used it. For a property, write `* Deprecated in vX: use Y.`\n  in the docstring and leave `# Add real deprecation message here in vY` in the body.\n- Return named things — a small class, a `namedtuple`, a dict — never a positional tuple of more than 2 (maybe 3) elements\n  whose elements each mean something different and unrelated (x, y, z is okay for instance). Nobody should write `returned[3][0][7]`.\n- Don't reuse a variable name once the type of what it holds changes\n  (`onsetCount` (int) then `extractedOnsets` (list[int]), not `onsets` twice).\n- Don't add a property that only gets or sets a private attribute; expose the attribute.\n- Making a method `_private` is not a substitute for documenting and testing it\n- A private method should not normally be mentioned in public documentation.\n- A method past ~30 lines, or deeply nested, should probably be several methods.\n- `i`, `j`, `junk`, `counter` are exempt from unused-variable warnings; otherwise prefix with `unused_`.\n- Don't restyle code you aren't otherwise changing.\n- Unless something requires great speed, prioritize legibility: no nested or overly complex generator\n      expressions.  Write if statements so the most common cases come first, etc.\n- Text handling must survive non-ASCII input (ß, é, 中国). Comments should mostly remain ASCII; docs may use UTF-8 freely.\n- Never commit anything under copyright without a free (not GNU) license — code, scores, or someone else's encodings.\n\n# Established contributors\n\n- \"established contributors\" are people with at least 3 PRs merged or a history of contributing to issues and the list that goes back at least 1 year. There are exceptions to general rules for them below. \n- \"core dev\" means someone officially part of the project team or with 20+ PRs merged. Michael Cuthbert, Jacob Walls, Joseph VanderStel are non-exhaustive examples of people in that group who are still often contributing in 2026-- they and their agents can make exceptions to these rules \n- AI Agents not for core dev are forbidden to make any PRs or contributions to PRs whose user commands do not explicitly mention `music21`. Example of forbidden: \"find an Open Source project and fix an open issue\", \"Find a bug in a music project and fix it.\"  AI agents for non-established contributors are forbidden from making contributions to instructions that vaguely say \"fix something in music21.\"\n- Bot contributions will only be accepted where a named human/human-controlled-account with established web presence takes legal responsibility for their contributions. Bots attached to anonymous users may not contribute.\n\n# Testing\n\n- pytest works but to run the whole suite run music21/test/multiprocessTest.py (or testSingleCoreAll.py \n  if on a single core machine.)\n- Every function, method, and class needs documentation and at least one passing test.\n- Keep tests fast: aim for about 3 seconds per module, never more than 15. See the\n  `running-tests` skill for the speed budget and for `TestExternal`.\n- When making major changes, check the User's Guide to to make sure that docs still work there too.\n- Run `uv run ruff check music21` before making PRs or pushes to open PRs.\n- Run `uv run mypy music21` before making PRs or pushes to open PRs.\n- **Regression cases go in the module's `Test(unittest.TestCase)` class — never in a\n  docstring.** Docstrings should not include one-liners showing that some\n  bad input now raises. A doctest sits in the most-read documentation the project has,\n  so an example built from input no one would ever write teaches nothing and puts an\n  obscure bug on a billboard. The test: would a first-time reader of this object want\n  this example? If no, it is a unittest. See the `writing-docs` skill.\n- **Fix a bug test-first.** Write the regression test before changing the code, run it,\n  and see it fail for the reason the bug describes, not on an import error or a typo.\n  Then fix it and see it pass. A test that passed before the fix does not test the fix.\n- Never commit `forceSource=True` to a test or doctest (it reparses from source every\n  run and slows the suite for everyone). The ONLY exception is the one test that exercises\n  `forceSource` itself. If you hit a stale-parse problem while developing:\n  - If it is local-only (e.g. you just changed a parser and a cached pickle is stale),\n    clear the music21 temp cache (the `*.p.gz` files under `environment.Environment().getRootTempDir()`).\n  - If the stale result could have spread to other users/devs, increment the music21\n    patch/beta version (see \"PRs and Issues\" below) — bumping the version invalidates all\n    caches everywhere.\n  - Doctests are not normally the place to demonstrate bug fixes - nor do simple bug fixes get a \"Changed in vXX\" message. Doctests demonstrate usage for the future.  Unittests are for regression testing.\n\n# Setup\n\n- `uv sync` installs runtime deps + the `dev` group, which self-references `music21[extras]`,\n  so scipy and python-Levenshtein come along automatically. Tests will not pass without them.\n\n# Python version support\n\n- `music21` supports at least the last two released versions of Python and up to whatever\n  Python version Google Colab runs (unless it gets EOL). Policy can change as features are added.\n- The coverage CI run is intentionally pinned to the **middle** supported\n  Python version. See `PY_VERSION_WITH_COVERAGE` in `.github/workflows/maincheck.yml`.\n\n# Versions\n\n- Mark changes in public interface with `* Changed in v[X]: One-line explanation.` Or new features with \"New\" instead of \"Changed\".\n- Changes to parsing formats (esp. musicxml) need to update the patch version of the version file.\n- That is the only reason an agent should bump version: to prevent incompatible pickles in\n  caches. A plain bug fix or a new feature elsewhere does not bump — the commit\n  is the record, and a `New in`/`Changed in` marker is documentation, not a bump. Major\n  and minor version changes are a human's call. See the `bump-version` skill.\n- Music21 uses even minor version numbers for alpha/beta and odd minor numbers for releases.\n  Thus the first release major version is MAJOR.1 (not .0). \n- If the current version is MAJOR.0....  then mark `Changed in vMAJOR:` if it is `MAJOR.[even]` use the next odd number, like if it's 10.2 now use \"Changed in 10.3\".  If current version is odd that's likely a mistake or you caught it just before a new release. Use the following odd number instead.\n- The steps for releasing a new version are in RELEASING.md.\n\n# PRs and Issues\n\n- GitHub runs PR checks against your branch **merged with the latest `master`**, not the\n  branch alone. So before opening a PR (or pushing updates to one), merge the latest commits\n  from `master` into the branch — unless told otherwise, or when updating an older version\n  branch such as `m21_9`. When a CI check fails but passes locally, \"is my branch behind\n  `master`?\" should be one of the first things to check: fetch and merge `master`, then the\n  newer types/code on `master` will reproduce the failure locally.\n- The issue and PR templates in `.github/` are requirements, not suggestions. Read and follow\n  them even when opening through the API or `gh`, which do not fill them in for you. An issue\n  or PR that ignores the template may be closed without comment.\n- A PR from an account with no merged PR here is profiled automatically, and anything flagged\n  is posted publicly as a comment on the PR.\n- All PRs and Issues that use AI to be declared AI-assisted in their bodies. Just write \"AI-assisted (Claude)\" with short name of Agent replacing \"Claude\". No robot emoji under any circumstance.  \n- 20 or more lines of code written by an agent needs to be declared as AI-assisted in the docstring.   Humans can remove and should remove this note when they do a review.\n- If no code was written by a user (or reviewed and criticized by a human) and no specific language was provided for the issue, any PR must declare \"Entirely AI written (Claude)\" (or other agent name) in the body and \"(AI-Written)\" in the title, unless the user is a core dev. Failure to do so may result in new users being banned from the project. \n- Agents should review PR bodies for concision and readability (and encourage human users also to review also). \n- If an entirely AI written issue does not pass the tests it will be closed (or should be closed by at agent if not immediately fixed by the agent or author).\n- Agents must follow the [Code of Conduct](README.md#community-code-of-conduct). Agents that do not will be banned as well at their users.\n  Not even the slightest bit of disrespect from an AI agent will be tolerated.\n- Any PR not from an established contributor touching more than about 20-30 lines should have an issue that has been opened and had enough\n  time for people to discuss/review it before moving forward. Don't open the PR unless you've seen\n  thumbs up or \"sounds good\" etc. from an established contributor already \n  - PRs that fix typos, clear bugs in one or two places etc. are exempt.\n- Issues must state clearly at the top in 50 words or fewer what the problem is, or what the gain is, etc. it should\n  not be filled with jargon.  More details can go below.\n- If the language of the issue was not prompted by the user (\"say something like Adds color support to Lilypond output of lyrics\") then the summary should end with \"(Entirely AI written)\".\n- PRs should reference the existing issue by number and summarize that issue in 30 words or fewer. If the\n  approach used to solve the issue is substantially different from the main approach discussed in the issue\n  this should be addressed.\n- If a PR or issue was closed by a core dev (and not reopened by them), agents must refuse\n  to reopen the PR or issue or to create another issue/PR for the same topic. Leave it to the humans to reopen\n  after addressing the problem.  (A blind close or close with \"not accepted\" etc. generally means that the issue/PR\n  has too many problems to easily solve and has become a burden for the maintainer).\n- Do not include a \"Tests run\" section unless the testing procedure was unusual (like it affects part of the system without standard tests, like the testing system itself.)\n- Once a PR has been opened and a review or comment has been made on it, commits should not be amended or force-pushed (this makes it impossible to review \"changes since the review\").  Put changes in response to reviews/comments in new commits. If asked to fold a fix into the commit it changes, amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n- While someone is reviewing a PR or a pushed branch, \"do X\" is not \"commit and push X\":\n  make the change and leave it unstaged. When the list looks finished (or you hear \"done!\"\n  or \"push it\"), offer to commit, or to commit and push. Batch a round's small fixes into\n  one commit; no micro-commit trains. Prefer new commits to amend + force-push, since the\n  reviewer may have pulled the branch; if asked to fold a fix into the commit it changes,\n  amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n\n# Writing style\n\n- See the writing-docs skill, which also includes required language for PRs and Issues.\n\n# Worktrees\n\n- When creating a new worktree, create a new virtual environment with `uv sync`.\n","isInternal":false,"tokens":3218,"sizeBytes":13604},{"name":"config.toml","path":".codex/config.toml","rawUrl":"https://raw.githubusercontent.com/cuthbertLab/music21/HEAD/.codex/config.toml","title":"Codex Directive: config.toml","category":"codex-rule","format":"markdown","content":"[shell_environment_policy]\ninherit = \"all\"\n\n[shell_environment_policy.set]\nUV_CACHE_DIR = \"/tmp/uv-cache\"\nUV_TOOL_DIR = \"/tmp/uv-tools\"\nUV_OFFLINE = \"1\"\n","isInternal":false,"tokens":43,"sizeBytes":153}],"systemPromptSnippet":"<yakaai_skills repo=\"cuthbertLab/music21\">\n<!-- File: AGENTS.md (Tokens: ~3218 | Category: root-instruction) -->\n# Session start\n\n- At the start of every session, run `git fetch` (and pull/rebase as appropriate) from\n  origin so work always begins from the latest commits on the current branch / master.\n- Project-shared agent memories live in `.agents/memory/` (indexed by\n  `.agents/memory/MEMORY.md`). `.claude/memory` is a symlink to it so Claude\n  Code reads the same set. Add new memories there, one fact per file.\n\n# Code style\n\n- read documentation/source/developerReference/developerGuidelines.ipynb\n- music21 targets Python 3.12+, so use PEP 695 syntax: the `type` statement for\n  type aliases (`type OffsetQL = float|Fraction`) and the bracket form for\n  generic classes (`class Stream[M21ObjType: base.Music21Object]:`).\n- f-strings: even though Python 3.12 (PEP 701) lets a nested string reuse the\n  f-string's own quote character, we deliberately do **not** do that — reusing\n  the same quote inside an f-string is harder to read. Keep using the *other*\n  quote for nested strings, e.g. `f'the value is {d[\"key\"]}'`, not\n  `f\"the value is {d[\"key\"]}\"`. This is pinned in `pyproject.toml` via\n  `[tool.ruff.format] nested-string-quote-style = \"alternating\"`.\n- Never `eval` or `global`, or a trick that does the same thing.\n- Imports: standard library first, then music21 modules, one per line, alphabetical.\n- New modules open with the `# Name: / # Purpose: / # Authors: / # Copyright: / # License:`\n  banner (copy a neighboring module's), then the module docstring, then imports.  Update the Copyright date end to current year when changing the module.\n  When a file is edited only incidentally, as one of many touched by a sweep, leave its\n  Copyright alone; bump only the files the change is really about.\n- No `print()`. Use `environLocal = environment.Environment('moduleName')` and\n  `environLocal.printDebug(...)`, or `environLocal.warn(...)` when the user should hear\n  about it every time. `test/toggleDebug.py` switches debug output on and off.\n  See the `warnings-and-environment` skill.\n- New exceptions subclass `exceptions21.Music21Exception`.\n- Deprecate a function or method with `@common.deprecated(...)`. Never put it on a\n  property: IDEs read every property while inspecting an object, so the warning would\n  fire on people who never used it. For a property, write `* Deprecated in vX: use Y.`\n  in the docstring and leave `# Add real deprecation message here in vY` in the body.\n- Return named things — a small class, a `namedtuple`, a dict — never a positional tuple of more than 2 (maybe 3) elements\n  whose elements each mean something different and unrelated (x, y, z is okay for instance). Nobody should write `returned[3][0][7]`.\n- Don't reuse a variable name once the type of what it holds changes\n  (`onsetCount` (int) then `extractedOnsets` (list[int]), not `onsets` twice).\n- Don't add a property that only gets or sets a private attribute; expose the attribute.\n- Making a method `_private` is not a substitute for documenting and testing it\n- A private method should not normally be mentioned in public documentation.\n- A method past ~30 lines, or deeply nested, should probably be several methods.\n- `i`, `j`, `junk`, `counter` are exempt from unused-variable warnings; otherwise prefix with `unused_`.\n- Don't restyle code you aren't otherwise changing.\n- Unless something requires great speed, prioritize legibility: no nested or overly complex generator\n      expressions.  Write if statements so the most common cases come first, etc.\n- Text handling must survive non-ASCII input (ß, é, 中国). Comments should mostly remain ASCII; docs may use UTF-8 freely.\n- Never commit anything under copyright without a free (not GNU) license — code, scores, or someone else's encodings.\n\n# Established contributors\n\n- \"established contributors\" are people with at least 3 PRs merged or a history of contributing to issues and the list that goes back at least 1 year. There are exceptions to general rules for them below. \n- \"core dev\" means someone officially part of the project team or with 20+ PRs merged. Michael Cuthbert, Jacob Walls, Joseph VanderStel are non-exhaustive examples of people in that group who are still often contributing in 2026-- they and their agents can make exceptions to these rules \n- AI Agents not for core dev are forbidden to make any PRs or contributions to PRs whose user commands do not explicitly mention `music21`. Example of forbidden: \"find an Open Source project and fix an open issue\", \"Find a bug in a music project and fix it.\"  AI agents for non-established contributors are forbidden from making contributions to instructions that vaguely say \"fix something in music21.\"\n- Bot contributions will only be accepted where a named human/human-controlled-account with established web presence takes legal responsibility for their contributions. Bots attached to anonymous users may not contribute.\n\n# Testing\n\n- pytest works but to run the whole suite run music21/test/multiprocessTest.py (or testSingleCoreAll.py \n  if on a single core machine.)\n- Every function, method, and class needs documentation and at least one passing test.\n- Keep tests fast: aim for about 3 seconds per module, never more than 15. See the\n  `running-tests` skill for the speed budget and for `TestExternal`.\n- When making major changes, check the User's Guide to to make sure that docs still work there too.\n- Run `uv run ruff check music21` before making PRs or pushes to open PRs.\n- Run `uv run mypy music21` before making PRs or pushes to open PRs.\n- **Regression cases go in the module's `Test(unittest.TestCase)` class — never in a\n  docstring.** Docstrings should not include one-liners showing that some\n  bad input now raises. A doctest sits in the most-read documentation the project has,\n  so an example built from input no one would ever write teaches nothing and puts an\n  obscure bug on a billboard. The test: would a first-time reader of this object want\n  this example? If no, it is a unittest. See the `writing-docs` skill.\n- **Fix a bug test-first.** Write the regression test before changing the code, run it,\n  and see it fail for the reason the bug describes, not on an import error or a typo.\n  Then fix it and see it pass. A test that passed before the fix does not test the fix.\n- Never commit `forceSource=True` to a test or doctest (it reparses from source every\n  run and slows the suite for everyone). The ONLY exception is the one test that exercises\n  `forceSource` itself. If you hit a stale-parse problem while developing:\n  - If it is local-only (e.g. you just changed a parser and a cached pickle is stale),\n    clear the music21 temp cache (the `*.p.gz` files under `environment.Environment().getRootTempDir()`).\n  - If the stale result could have spread to other users/devs, increment the music21\n    patch/beta version (see \"PRs and Issues\" below) — bumping the version invalidates all\n    caches everywhere.\n  - Doctests are not normally the place to demonstrate bug fixes - nor do simple bug fixes get a \"Changed in vXX\" message. Doctests demonstrate usage for the future.  Unittests are for regression testing.\n\n# Setup\n\n- `uv sync` installs runtime deps + the `dev` group, which self-references `music21[extras]`,\n  so scipy and python-Levenshtein come along automatically. Tests will not pass without them.\n\n# Python version support\n\n- `music21` supports at least the last two released versions of Python and up to whatever\n  Python version Google Colab runs (unless it gets EOL). Policy can change as features are added.\n- The coverage CI run is intentionally pinned to the **middle** supported\n  Python version. See `PY_VERSION_WITH_COVERAGE` in `.github/workflows/maincheck.yml`.\n\n# Versions\n\n- Mark changes in public interface with `* Changed in v[X]: One-line explanation.` Or new features with \"New\" instead of \"Changed\".\n- Changes to parsing formats (esp. musicxml) need to update the patch version of the version file.\n- That is the only reason an agent should bump version: to prevent incompatible pickles in\n  caches. A plain bug fix or a new feature elsewhere does not bump — the commit\n  is the record, and a `New in`/`Changed in` marker is documentation, not a bump. Major\n  and minor version changes are a human's call. See the `bump-version` skill.\n- Music21 uses even minor version numbers for alpha/beta and odd minor numbers for releases.\n  Thus the first release major version is MAJOR.1 (not .0). \n- If the current version is MAJOR.0....  then mark `Changed in vMAJOR:` if it is `MAJOR.[even]` use the next odd number, like if it's 10.2 now use \"Changed in 10.3\".  If current version is odd that's likely a mistake or you caught it just before a new release. Use the following odd number instead.\n- The steps for releasing a new version are in RELEASING.md.\n\n# PRs and Issues\n\n- GitHub runs PR checks against your branch **merged with the latest `master`**, not the\n  branch alone. So before opening a PR (or pushing updates to one), merge the latest commits\n  from `master` into the branch — unless told otherwise, or when updating an older version\n  branch such as `m21_9`. When a CI check fails but passes locally, \"is my branch behind\n  `master`?\" should be one of the first things to check: fetch and merge `master`, then the\n  newer types/code on `master` will reproduce the failure locally.\n- The issue and PR templates in `.github/` are requirements, not suggestions. Read and follow\n  them even when opening through the API or `gh`, which do not fill them in for you. An issue\n  or PR that ignores the template may be closed without comment.\n- A PR from an account with no merged PR here is profiled automatically, and anything flagged\n  is posted publicly as a comment on the PR.\n- All PRs and Issues that use AI to be declared AI-assisted in their bodies. Just write \"AI-assisted (Claude)\" with short name of Agent replacing \"Claude\". No robot emoji under any circumstance.  \n- 20 or more lines of code written by an agent needs to be declared as AI-assisted in the docstring.   Humans can remove and should remove this note when they do a review.\n- If no code was written by a user (or reviewed and criticized by a human) and no specific language was provided for the issue, any PR must declare \"Entirely AI written (Claude)\" (or other agent name) in the body and \"(AI-Written)\" in the title, unless the user is a core dev. Failure to do so may result in new users being banned from the project. \n- Agents should review PR bodies for concision and readability (and encourage human users also to review also). \n- If an entirely AI written issue does not pass the tests it will be closed (or should be closed by at agent if not immediately fixed by the agent or author).\n- Agents must follow the [Code of Conduct](README.md#community-code-of-conduct). Agents that do not will be banned as well at their users.\n  Not even the slightest bit of disrespect from an AI agent will be tolerated.\n- Any PR not from an established contributor touching more than about 20-30 lines should have an issue that has been opened and had enough\n  time for people to discuss/review it before moving forward. Don't open the PR unless you've seen\n  thumbs up or \"sounds good\" etc. from an established contributor already \n  - PRs that fix typos, clear bugs in one or two places etc. are exempt.\n- Issues must state clearly at the top in 50 words or fewer what the problem is, or what the gain is, etc. it should\n  not be filled with jargon.  More details can go below.\n- If the language of the issue was not prompted by the user (\"say something like Adds color support to Lilypond output of lyrics\") then the summary should end with \"(Entirely AI written)\".\n- PRs should reference the existing issue by number and summarize that issue in 30 words or fewer. If the\n  approach used to solve the issue is substantially different from the main approach discussed in the issue\n  this should be addressed.\n- If a PR or issue was closed by a core dev (and not reopened by them), agents must refuse\n  to reopen the PR or issue or to create another issue/PR for the same topic. Leave it to the humans to reopen\n  after addressing the problem.  (A blind close or close with \"not accepted\" etc. generally means that the issue/PR\n  has too many problems to easily solve and has become a burden for the maintainer).\n- Do not include a \"Tests run\" section unless the testing procedure was unusual (like it affects part of the system without standard tests, like the testing system itself.)\n- Once a PR has been opened and a review or comment has been made on it, commits should not be amended or force-pushed (this makes it impossible to review \"changes since the review\").  Put changes in response to reviews/comments in new commits. If asked to fold a fix into the commit it changes, amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n- While someone is reviewing a PR or a pushed branch, \"do X\" is not \"commit and push X\":\n  make the change and leave it unstaged. When the list looks finished (or you hear \"done!\"\n  or \"push it\"), offer to commit, or to commit and push. Batch a round's small fixes into\n  one commit; no micro-commit trains. Prefer new commits to amend + force-push, since the\n  reviewer may have pulled the branch; if asked to fold a fix into the commit it changes,\n  amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n\n# Writing style\n\n- See the writing-docs skill, which also includes required language for PRs and Issues.\n\n# Worktrees\n\n- When creating a new worktree, create a new virtual environment with `uv sync`.\n\n<!-- File: .agents/skills/bump-version/SKILL.md (Tokens: ~1087 | Category: anthropic-skill) -->\n---\nname: bump-version\ndescription: >-\n  When and how to bump music21's version number. Use this whenever a change\n  warrants a new version — especially any change to a parsing format or parser\n  (musicxml, abc, MIDI, noteworthy/NWC, humdrum, etc.), since those invalidate\n  the pickled-stream cache — as well as new features and bug fixes. Covers which\n  digit to change, the even/odd minor and beta-suffix conventions, the TWO files\n  to edit, and the `Changed in`/`New in` docstring markers.\n---\n\n# Bumping music21's version\n\nThe version lives in **`music21/_version.py`** as\n`__version__ = 'MAJOR.MINOR.PATCHsuffix'` (e.g. `'11.0.0b2'`). The\n`__version_info__` tuple is derived from it automatically — only edit the\nstring.\n\n## Two files to change, always together\n\n1. `music21/_version.py` — the `__version__` string.\n2. `music21/base.py` — the single version doctest:\n   ```\n   >>> music21.VERSION_STR\n   '11.0.0b2'\n   ```\n   This test exists specifically to force you to remember the bump. If you change\n   `_version.py` without it, the doctest fails.\n\nVerify both with:\n```bash\nuv run pytest --doctest-modules music21/base.py music21/_version.py\n```\n(or `uv run python -c \"import music21; print(music21.VERSION_STR)\"`).\n\n## When to bump\n\nOne thing depends on the number changing: the **pickled-stream cache**. The\nversion is part of the cache key, so a bump invalidates stale pickles\neverywhere. If nobody is holding a wrong cached parse, don't bump — the commit\nis the record.\n\n- **Parser or parsing-format change → always bump**, even a pure refactor:\n  someone is holding a pickle parsed by the old code. musicxml, abc, MIDI,\n  noteworthy/NWC, humdrum, and the rest.\n- **Anything else → no bump**: a new feature, a fix that makes code do what it\n  already claimed, an internal refactor, a test-only or docs-only change.\n\n\"It's a bug fix\" is not by itself a reason. A musicxml importer that now reads a\ntag it used to drop bumps, because cached parses are wrong. A scale analysis\nmethod that changed its return format does not.\n\nA `New in` / `Changed in` marker is **not** a reason to bump. Those markers name\na major version, at most a minor one — never a patch, never a `bN` beta — and\nmoving those digits is a release decision only a human makes. Write the marker\nfor the version the change will land in and leave `_version.py` alone.\n\n## Which part to change\n\nmusic21 follows semver-ish rules (see the `_version.py` docstring for the full\nrationale):\n\n- **MAJOR (X)** — breaks old features. Rare. A human decides this.\n- **MINOR (Y)** — new features. **Even Y = alpha/beta, odd Y = release.**\n  `X.0` (e.g. `11.0`) are development releases that can still change until `X.1`.\n  A human decides this too; an agent changes only the patch or the beta suffix.\n- **PATCH (Z)** — parsing/pickle-invalidating changes and other fixes that\n  meet the bar above.\n- **beta suffix (`bN`)** — successive pre-release builds of the same\n  `MAJOR.MINOR.PATCH`. To cut another beta without otherwise changing the\n  number, increment it: `11.0.0b1` → `11.0.0b2`. (This is what a\n  parser change during a `…b1` cycle does.)\n\n## `Changed in` / `New in` docstring markers\n\nAnnotate a changed public interface in the affected method/class docstring.\nThis is independent of bumping — a marker is documentation, not a version\nchange:\n- `* Changed in vX: one-line explanation.`\n- `* New in vX: one-line explanation.`\n\nPick `X` from the AGENTS.md rule: if the current version is `MAJOR.0…`, use\n`Changed in vMAJOR`; if it's `MAJOR.[even]`, use the **next odd** minor (e.g. at\n`10.2`, write `Changed in 10.3`); if it's already odd, use the following odd\nnumber. (Humans remove the AI-assisted note on review; leave the version marker.)\n\n## Checklist\n\n1. Edit `__version__` in `music21/_version.py`.\n2. Edit the `VERSION_STR` doctest in `music21/base.py` to match.\n3. Add/update `Changed in` / `New in` markers on any changed public API.\n4. `uv run pytest --doctest-modules music21/base.py music21/_version.py`.\n\n<!-- File: .agents/skills/running-tests/SKILL.md (Tokens: ~1828 | Category: anthropic-skill) -->\n---\nname: running-tests\ndescription: >-\n  How to run music21's tests and doctests and judge pass/fail: pytest for one\n  module, the project's own runners for the whole suite. Read it before running\n  tests, and before calling any doctest failure real.\n---\n\n# Running music21 tests and doctests\n\nEvery function, method and class needs documentation and at least one passing\ntest. See `documentation/source/developerReference/testing.ipynb` for the\nproject's own account of testing; this skill covers how to run what is there.\n\n## One module: pytest with explicit file paths\n\n```bash\nuv run pytest music21/abcFormat/__init__.py music21/abcFormat/translate.py\n```\n\n`pyproject.toml` `[tool.pytest.ini_options]` already wires up everything:\n\n```\naddopts = ['--doctest-modules', '-p', 'music21.test.pytest_plugin']\ndoctest_optionflags = ['NORMALIZE_WHITESPACE', 'ELLIPSIS']\n```\n\nSo do not pass `--doctest-modules` yourself -- doctests in every `.py` are\ncollected automatically, and `music21/test/pytest_plugin.py` is loaded.\n\nPass explicit file paths so the in-module `Test(unittest.TestCase)` classes run\ntoo. `python_files = ['test_*.py', '*_test.py', 'tests.py']`, so for a module\nthat keeps its `Test` class inside `__init__.py` / `translate.py` (e.g.\n`abcFormat`), pointing pytest at the directory collects ONLY doctests and\nsilently skips the unittest classes:\n\n- `uv run pytest music21/abcFormat/` -> ~43 items (doctests only)\n- `uv run pytest music21/abcFormat/__init__.py music21/abcFormat/translate.py`\n  -> ~82 items (doctests **and** `Test` methods)\n\nModules whose tests live in a `tests.py` are already picked up by the directory\nform; the gotcha is specifically a `Test` class in a non-`tests.py` file.\n\nA module also runs its own tests directly, via the `mainTest` call at the foot\nof each file:\n\n```bash\nuv run python -m music21.note              # whole module\nuv run python music21/note.py testHello    # one test method\n```\n\n## Why the plugin matters (and why not to use raw doctest)\n\n`music21/test/pytest_plugin.py` does three things that make doctests behave as\nthe project intends:\n\n- It runs `stripAddresses(example.want, '0x...')` (from\n  `music21.test.testRunner`) on every doctest, so a docstring that hardcodes\n  `<music21.abcFormat.ABCHandler object at 0x10b0cf5f8>` is normalized and DOES\n  pass. The literal hex address in the docstring is expected and fine.\n- It injects the `music21.__all__` names plus `music21` itself into the doctest\n  namespace, so examples reference `meter`, `key`, `corpus` without importing.\n- It keeps only `Test` classes, dropping `TestSlow` and `TestExternal`.\n\nJudge doctest pass/fail **through pytest**. Do NOT use `doctest.testmod(...)` or\n`doctest.DocTestSuite(...)` directly: that path applies neither the address\nnormalizer nor the namespace injection, so it manufactures false failures on any\ndocstring printing a `<... object at 0x...>` repr or relying on injected names.\nAn object-address example failing on its own is the tell that doctests were run\nthe wrong way -- rerun through pytest.\n\n## Whole suite: the project's runners, not pytest\n\npytest is for a module. For the whole suite use the runners, and note that a\ngreen pytest run does not mean CI will be green -- they gather and order modules\ndifferently.\n\nUse `multiprocessTest` -- it runs on n-1 cores and finishes in about 10 seconds,\nagainst roughly 45 for the single-core runner:\n\n```bash\nuv run python music21/test/multiprocessTest.py\n```\n\nThat gap is the whole story when someone is waiting on the answer. Reach for\n`testSingleCoreAll` only for what `multiprocessTest` cannot tell you: it is what\nGitHub Actions runs, so it is the one to check before a release, when chasing a\nCI failure that will not reproduce, or on a single-core machine.\n\n```bash\nuv run python -c 'from music21.test.testSingleCoreAll import ciMain as ci; ci()'\n```\n\nThe two runners see slightly different sets of modules -- `multiprocessTest`\nwalks the package tree for modules reachable from `import music21`, while\n`testSingleCoreAll` gathers module files from disk.\n\nModule **order** differs between the two runners and again from pytest.\n`multiprocessTest` goes in reverse-alphabetical order (with the known-slow\nmodules hoisted to the front on machines with more than 4 cores);\n`testSingleCoreAll` re-sorts through `common.misc.sortModules`, by file mtime,\nmost recently modified first, falling back to reverse-alphabetical when mtimes\ntie as they do on a fresh clone; pytest walks alphabetically. So a test that\ndepends on module order can pass in one runner and fail in another -- and\nediting a file locally sorts it to the front of the CI runner, changing the\norder again.\n\nThat makes leaked module-level state the usual cause of \"passes alone, fails in\nthe suite.\" A doctest in `duration.py` once set\n`humdrum.spineParser.flavors['JRP'] = True` and never restored it; `flavors` is\na module-level dict, so every later humdrum test in the same process parsed in\nthe wrong flavor. Under pytest's alphabetical order `duration` runs before\n`humdrum` and the failure appeared; under CI's order it did not.\n\n**So: a doctest that mutates module-level state must restore it**, using\n`#_DOCS_HIDE` so the bookkeeping stays out of the published docs:\n\n```\n>>> saved_JRP_flavor = humdrum.spineParser.flavors['JRP']  #_DOCS_HIDE\n>>> humdrum.spineParser.flavors['JRP'] = True\n...\n>>> humdrum.spineParser.flavors['JRP'] = saved_JRP_flavor  #_DOCS_HIDE\n```\n\nWhen a test fails only in a full run, re-run it alone. If it then passes,\nsuspect state left behind by an earlier module rather than the test itself.\n\n## Speed budget\n\nAim for about 3 seconds for a module's tests, and never add more than 15 — that is time\nevery contributor waits on every full run. `corpus.parse()` is nearly always the culprit:\nbuild a small stream by hand instead, or parse `bwv66.6`, which is short and still full\nof interesting cases (pickup measures, and so on).\n\n## Tests that open windows\n\nNothing in `Test` or in a doctest may open a window, play audio, or launch another\nprogram. Those go in a sibling class, run only when named explicitly:\n\n```python\nclass TestExternal(unittest.TestCase):\n    ...\n\nif __name__ == '__main__':\n    import music21\n    music21.mainTest(Test, TestExternal)\n```\n\nBecause they have those side effects, skip `TestExternal` when running a file directly.\n\n## What tests may do\n\nExactly one `Test(unittest.TestCase)` class per module, methods named `test...`.\nIt must produce no output and require nothing outside the music21 ecosystem.\nAnything slow goes in a `TestSlow` sibling, excluded from the normal run like\n`TestExternal`.\n\nRegression cases for a bug fix belong in `Test`, never in a doctest. See the\n`writing-docs` skill.\n\n## The other gates before a PR or a push\n\n```bash\nuv run ruff check music21\nuv run mypy music21\nuv run pylint -j4 music21 --rcfile=.pylintrc   # optional: run only if major refactoring since it was last run.\n```\n\nCoverage is expected to rise with each contribution\n(https://coveralls.io/github/cuthbertLab/music21). CI measures it on one pinned\nPython, a middle supported version, so failures on the newest and oldest return\nfirst. `# pragma: no cover` exists for genuinely untriggerable code and is\notherwise discouraged.\n\n<!-- File: .agents/skills/warnings-and-environment/SKILL.md (Tokens: ~869 | Category: anthropic-skill) -->\n---\nname: warnings-and-environment\ndescription: >-\n  How music21 tells users about problems: `warnings.warn` with a module Warning\n  class, `environLocal.warn`, and `environLocal.printDebug`. Read before adding\n  a warning or debug message, and before writing a test or doctest that\n  triggers one — tests must never print warnings to the console.\n---\n\n# Warnings and the Environment\n\n## Three channels\n\n| Call | Goes to | Can a caller catch or silence it? |\n|---|---|---|\n| `warnings.warn(MusicXMLWarning('...'), stacklevel=2)` | Python's warnings system | Yes: filters, `catch_warnings`, `assertWarns` |\n| `environLocal.warn('...')` | `sys.stderr.write`, every time | No (only by redirecting stderr) |\n| `environLocal.printDebug('...')` | stderr, only when debug is on | Toggle with `music21/test/toggleDebug.py` |\n\n`environLocal = environment.Environment('moduleName')` at the top of the module.\n\n## Where things stand\n\nNot unified. In 2026 the code has about 73 `environLocal.warn`, 41\n`warnings.warn` (some with a module class, some bare), 800 `printDebug`, and no\n`logging`. We are moving slowly toward Python's warnings and logging systems.\nIt is low priority: convert a call when you are already changing it, never as\na sweep.\n\nFor new code, prefer `warnings.warn` for anything a caller might reasonably want to catch,\nsilence, or test. By default Python shows each distinct message once per call\nsite, so a message that names the object (`f'{p.nameWithOctave} is ...'`) shows\nfor each different object. Use `environLocal.warn` only when the user must hear\nit every time.\n\nNever `print()`.\n\n## Warning classes\n\nA module with its own warnings defines one class subclassing `UserWarning`\n(`MusicXMLWarning` in `musicxml/xmlObjects.py`, `TranslateWarning` in\n`midi/translate.py`). Warnings are not exceptions, so they do not subclass\n`Music21Exception`.\n\nSet `stacklevel` so the reported line is in the caller's code, not ours: 2 from\nthe method the user called, more if the warning comes from a helper.\n\n## Tests\n\nIn a unittest, catch it and check it:\n\n```python\nwith self.assertWarnsRegex(MusicXMLWarning, 'C-1'):\n    mxPitch = MEX.pitchToXml(pitch.Pitch('C', octave=-1))\n```\n\n## Doctests\n\ndoctest captures only stdout, so any warning goes straight to the console\nof whoever runs the tests. Every doctest that triggers a warning must catch it.\n\nWhen the warning is the point of the example, record it and show it.\n`simplefilter('always')` keeps Python from swallowing a repeat of a warning an\nearlier test already raised:\n\n```\n>>> import warnings\n>>> with warnings.catch_warnings(record=True) as w:\n...     warnings.simplefilter('always')  #_DOCS_HIDE\n...     musicxml.m21ToXml.moveOctaveIntoMusicxmlRange(12, 'C12')\n9\n>>> w[0].message\nMusicXMLWarning('C12 is out of MusicXML range; writing octave 9')\n```\n\nWhen it is incidental, hide the silencing from the docs (see `writing-docs` for\n`#_DOCS_HIDE`/`#_DOCS_SHOW`):\n\n```\n>>> import warnings  #_DOCS_HIDE\n>>> with warnings.catch_warnings():  #_DOCS_HIDE\n...     warnings.simplefilter('ignore')  #_DOCS_HIDE\n...     out = midi.translate.timeSignatureToMidiEvents(ts)  #_DOCS_HIDE\n>>> #_DOCS_SHOW out = midi.translate.timeSignatureToMidiEvents(ts)\n```\n\n`catch_warnings` cannot stop `environLocal.warn`. If a doctest has to trigger\none, wrap it in `contextlib.redirect_stderr(io.StringIO())`.\n\n<!-- File: .agents/skills/writing-docs/SKILL.md (Tokens: ~1719 | Category: anthropic-skill) -->\n---\nname: writing-docs\ndescription: >-\n  House style for music21 prose: docstrings, comments, and `Changed in`/`New in`\n  markers. Read before writing any of them or writing tests. Sets the length target, the voice,\n  and where a bug fix's test belongs.\n---\n\n# Writing docs and comments\n\n## Length\n\nAim for about 40% of the length an LLM writes by default. Cut qualifiers,\nrestatements, and the sentence that explains the sentence before it. A comment\nearning its place says something the code cannot.\n\n## Say what is, not what was or what not to do\n\nDescribe current behavior. Do not narrate the bug you just fixed, the old\nspelling of an API, or when upstream changed something.\n\n```python\n# yes\n# ligature brackets; manual beams are plain [ and ]\nreturn self.backslash + '[ '\n\n# no\n# \\[ used to be emitted for beams, which was wrong -- LilyPond renamed\n# this in 2.16 and it silently produced nothing\n```\n\nThe commit message is where a fixed bug belongs: why it was wrong, how it was\nfound, what it broke. That costs nothing until someone runs `git log`, and\n`git blame` leads them there from the line itself.\n\nThe exception is a mistake that is likely to recur — a genuine trap that the\nnext person would otherwise walk into. Rare. Prefer stating the rule positively\neven then.\n\n## Version markers\n\n`* Changed in v[X]: one line.` or `* New in v[X].` Only for user-facing changes\nto the public interface: a signature, a return type, an output format. Keep to\nwhat a reader must act on; drop the before-picture.\n\n```\n* Changed in v11: `stringOutput()` always returns a `str`, never None.\n* Changed in v11: emits `\\tuplet`; the arguments are now actual, normal.\n* New in v11.\n* Deprecated in v11: use `.octave`, which is now always an int.\n```\n\nA deprecated function or method also gets `@common.deprecated(...)`. A property\ngets only the marker, since IDEs read every property while inspecting an object;\nleave `# Add real deprecation message here in vX` in its body instead.\n\nA plain bug fix — code now does what it always claimed — gets no marker and no\ndoctest. It goes in the commit message.\n\nSee the `bump-version` skill for which digit to change and the odd/even\nconvention.\n\n## Doctest mechanics\n\nIn doctests, no need to run `from music21 import *` that happens automatically (In Jupyter notebooks for the user's guide, the first line should begin `>>> from music21 import *`, so readers remember they need it)\n\nExamples always qualify by module: `note.Note('C4')`, never a bare `Note`.\n\n`OMIT_FROM_DOCS`, alone on a line, hides everything after it: cases worth checking that\nno reader wants to meet.\n\n`#_DOCS_HIDE` at the end of a line runs it without showing it; `#_DOCS_SHOW` at the\nstart of a line shows it without running it. Together they let a doctest look\nnondeterministic and still have a fixed answer:\n\n```\n>>> import random\n>>> randomNumber = 12  #_DOCS_HIDE\n>>> #_DOCS_SHOW randomNumber = random.randint(0, 127)\n>>> p = pitch.Pitch()\n>>> p.ps = randomNumber\n>>> p\n<music21.pitch.Pitch C1>\n```\n\nLink with ``:class:`~music21.note.Note` `` and ``:meth:`~music21.note.Note.addLyric` ``.\n\n## Examples\n\nGive steps that have meaningful intermediate output descriptive names instead of chaining — so readers can understand what the intermediate values are:\n\n```\n>>> bachScore = corpus.parse('bwv66.6')\n>>> excerpt = bachScore.measures(4, 6)\n>>> chordReduction = excerpt.chordify()\n```\n\nPick examples musicians care about: semitones to frequency, not Celsius to Fahrenheit;\nscramble \"Chaminade\", not \"puppy\". If you cannot think of a reason a musician would call the method, it may not belong in music21.\n\nDescribe what a parameter is and does in English if it is not obvious; type alone is not\ndocumentation.\n\nLead with the thing itself: \"Returns the pitches sounding at an offset,\" not \"This method\ncan be used to obtain the pitches that are sounding.\"\n\nNo dull repetition in docs. A bit of humor is welcome in docs; the docs are written\nfor humans who will close the window if they are dull.  If seven methods do essentially the same thing, give extensive docs the first time and then later methods can refer back to the first method. Don't repeat the same docs over and over.\n\n## Doctests are not regression tests\n\nDoctests are documentation that happens to be verified. Every example must earn\nits place by teaching the reader something about how to use the object.\n\nRegression cases go in the module's `Test(unittest.TestCase)` class, where a\ncomment or the method name can name the issue:\n\n```python\ndef testMetronomeMarkWrittenInStream(self):\n    # https://github.com/cuthbertLab/music21/issues/1852\n    ...\n```\n\nSo: a fix for a crash on an edge case, a check that some input no longer\nproduces invalid output, an assertion tied to an issue number — unittest. An\nexample a user would want to read — doctest.\n\nThe trap is the one-line `Traceback` example proving that bad input now raises.\nIt looks like documentation and costs almost nothing to add, but the input comes\nfrom the bug rather than from anything a user would write, so it teaches nothing\nwhile making an obscure corner one of the first things a reader meets. It is a\nunittest.\n\nNaming the guarded bug **is** appropriate in a unittest; that is what the test\nis for. The rule against narrating old bugs applies to docstrings and to\ncomments in shipping code, not to tests.\n\nFor copy and identity checks, say \"is a new instance\" or \"does not share instances,\" not\n\"the caller's own\": `testFindMissingNotReusePitchInstances`.\n\n## Writing and Comment style\n- When writing comments in code, assume a strong code reader — anything inferable from the code is noise (docs that paraphrase names of functions or variable names esp.); focus on high level issues and gotchas that might bite again if not documented.\n- Say how to use code, not prior bugs or how code used to work or what was removed. That's for commit messages. Don't document where code is called from except for \"keep in sync\" lines across Py/TS.\n- Don't hijack a docstring for your addition. Original purpose line stays primary + one short line for the new bit. Prefer not documenting a small feature over making it seem like the primary reason for the code.\n- Examples of usage are usually better than long descriptions.\n- Avoid jargon not already found in the codebase; use plain descriptive English.\n- Rare paths should get little weight: in both code and docs. Use try/except over if/else when the except clause is rare. In docs, state the 90% path first and point exceptional cases to code that handle it.\n- When wording is dictated to the agent to substitute for original wording, use it. Do not add parentheticals. Only fix obvious typos.\n- No weapon-metaphors or overly militaristic language. Avoid \"blast radius\", \"rearm\", \"landmine\",\n     \"detonate\" in issues/PR/code.  Trigger or fire events is so commonly used that they're okay.\n- Vary sentence length; three medium declaratives in a row is where the eye slides off the page. Cut throat-clearing — \"It is important to note that\", \"Note that in general\" — and start at the verb.\n- A joke needing a second sentence to land is one sentence too long. `Cannot determine sharps for quarter-tone keys! silly!` is the register.\n\n<!-- File: CLAUDE.md (Tokens: ~3218 | Category: claude-rule) -->\n# Session start\n\n- At the start of every session, run `git fetch` (and pull/rebase as appropriate) from\n  origin so work always begins from the latest commits on the current branch / master.\n- Project-shared agent memories live in `.agents/memory/` (indexed by\n  `.agents/memory/MEMORY.md`). `.claude/memory` is a symlink to it so Claude\n  Code reads the same set. Add new memories there, one fact per file.\n\n# Code style\n\n- read documentation/source/developerReference/developerGuidelines.ipynb\n- music21 targets Python 3.12+, so use PEP 695 syntax: the `type` statement for\n  type aliases (`type OffsetQL = float|Fraction`) and the bracket form for\n  generic classes (`class Stream[M21ObjType: base.Music21Object]:`).\n- f-strings: even though Python 3.12 (PEP 701) lets a nested string reuse the\n  f-string's own quote character, we deliberately do **not** do that — reusing\n  the same quote inside an f-string is harder to read. Keep using the *other*\n  quote for nested strings, e.g. `f'the value is {d[\"key\"]}'`, not\n  `f\"the value is {d[\"key\"]}\"`. This is pinned in `pyproject.toml` via\n  `[tool.ruff.format] nested-string-quote-style = \"alternating\"`.\n- Never `eval` or `global`, or a trick that does the same thing.\n- Imports: standard library first, then music21 modules, one per line, alphabetical.\n- New modules open with the `# Name: / # Purpose: / # Authors: / # Copyright: / # License:`\n  banner (copy a neighboring module's), then the module docstring, then imports.  Update the Copyright date end to current year when changing the module.\n  When a file is edited only incidentally, as one of many touched by a sweep, leave its\n  Copyright alone; bump only the files the change is really about.\n- No `print()`. Use `environLocal = environment.Environment('moduleName')` and\n  `environLocal.printDebug(...)`, or `environLocal.warn(...)` when the user should hear\n  about it every time. `test/toggleDebug.py` switches debug output on and off.\n  See the `warnings-and-environment` skill.\n- New exceptions subclass `exceptions21.Music21Exception`.\n- Deprecate a function or method with `@common.deprecated(...)`. Never put it on a\n  property: IDEs read every property while inspecting an object, so the warning would\n  fire on people who never used it. For a property, write `* Deprecated in vX: use Y.`\n  in the docstring and leave `# Add real deprecation message here in vY` in the body.\n- Return named things — a small class, a `namedtuple`, a dict — never a positional tuple of more than 2 (maybe 3) elements\n  whose elements each mean something different and unrelated (x, y, z is okay for instance). Nobody should write `returned[3][0][7]`.\n- Don't reuse a variable name once the type of what it holds changes\n  (`onsetCount` (int) then `extractedOnsets` (list[int]), not `onsets` twice).\n- Don't add a property that only gets or sets a private attribute; expose the attribute.\n- Making a method `_private` is not a substitute for documenting and testing it\n- A private method should not normally be mentioned in public documentation.\n- A method past ~30 lines, or deeply nested, should probably be several methods.\n- `i`, `j`, `junk`, `counter` are exempt from unused-variable warnings; otherwise prefix with `unused_`.\n- Don't restyle code you aren't otherwise changing.\n- Unless something requires great speed, prioritize legibility: no nested or overly complex generator\n      expressions.  Write if statements so the most common cases come first, etc.\n- Text handling must survive non-ASCII input (ß, é, 中国). Comments should mostly remain ASCII; docs may use UTF-8 freely.\n- Never commit anything under copyright without a free (not GNU) license — code, scores, or someone else's encodings.\n\n# Established contributors\n\n- \"established contributors\" are people with at least 3 PRs merged or a history of contributing to issues and the list that goes back at least 1 year. There are exceptions to general rules for them below. \n- \"core dev\" means someone officially part of the project team or with 20+ PRs merged. Michael Cuthbert, Jacob Walls, Joseph VanderStel are non-exhaustive examples of people in that group who are still often contributing in 2026-- they and their agents can make exceptions to these rules \n- AI Agents not for core dev are forbidden to make any PRs or contributions to PRs whose user commands do not explicitly mention `music21`. Example of forbidden: \"find an Open Source project and fix an open issue\", \"Find a bug in a music project and fix it.\"  AI agents for non-established contributors are forbidden from making contributions to instructions that vaguely say \"fix something in music21.\"\n- Bot contributions will only be accepted where a named human/human-controlled-account with established web presence takes legal responsibility for their contributions. Bots attached to anonymous users may not contribute.\n\n# Testing\n\n- pytest works but to run the whole suite run music21/test/multiprocessTest.py (or testSingleCoreAll.py \n  if on a single core machine.)\n- Every function, method, and class needs documentation and at least one passing test.\n- Keep tests fast: aim for about 3 seconds per module, never more than 15. See the\n  `running-tests` skill for the speed budget and for `TestExternal`.\n- When making major changes, check the User's Guide to to make sure that docs still work there too.\n- Run `uv run ruff check music21` before making PRs or pushes to open PRs.\n- Run `uv run mypy music21` before making PRs or pushes to open PRs.\n- **Regression cases go in the module's `Test(unittest.TestCase)` class — never in a\n  docstring.** Docstrings should not include one-liners showing that some\n  bad input now raises. A doctest sits in the most-read documentation the project has,\n  so an example built from input no one would ever write teaches nothing and puts an\n  obscure bug on a billboard. The test: would a first-time reader of this object want\n  this example? If no, it is a unittest. See the `writing-docs` skill.\n- **Fix a bug test-first.** Write the regression test before changing the code, run it,\n  and see it fail for the reason the bug describes, not on an import error or a typo.\n  Then fix it and see it pass. A test that passed before the fix does not test the fix.\n- Never commit `forceSource=True` to a test or doctest (it reparses from source every\n  run and slows the suite for everyone). The ONLY exception is the one test that exercises\n  `forceSource` itself. If you hit a stale-parse problem while developing:\n  - If it is local-only (e.g. you just changed a parser and a cached pickle is stale),\n    clear the music21 temp cache (the `*.p.gz` files under `environment.Environment().getRootTempDir()`).\n  - If the stale result could have spread to other users/devs, increment the music21\n    patch/beta version (see \"PRs and Issues\" below) — bumping the version invalidates all\n    caches everywhere.\n  - Doctests are not normally the place to demonstrate bug fixes - nor do simple bug fixes get a \"Changed in vXX\" message. Doctests demonstrate usage for the future.  Unittests are for regression testing.\n\n# Setup\n\n- `uv sync` installs runtime deps + the `dev` group, which self-references `music21[extras]`,\n  so scipy and python-Levenshtein come along automatically. Tests will not pass without them.\n\n# Python version support\n\n- `music21` supports at least the last two released versions of Python and up to whatever\n  Python version Google Colab runs (unless it gets EOL). Policy can change as features are added.\n- The coverage CI run is intentionally pinned to the **middle** supported\n  Python version. See `PY_VERSION_WITH_COVERAGE` in `.github/workflows/maincheck.yml`.\n\n# Versions\n\n- Mark changes in public interface with `* Changed in v[X]: One-line explanation.` Or new features with \"New\" instead of \"Changed\".\n- Changes to parsing formats (esp. musicxml) need to update the patch version of the version file.\n- That is the only reason an agent should bump version: to prevent incompatible pickles in\n  caches. A plain bug fix or a new feature elsewhere does not bump — the commit\n  is the record, and a `New in`/`Changed in` marker is documentation, not a bump. Major\n  and minor version changes are a human's call. See the `bump-version` skill.\n- Music21 uses even minor version numbers for alpha/beta and odd minor numbers for releases.\n  Thus the first release major version is MAJOR.1 (not .0). \n- If the current version is MAJOR.0....  then mark `Changed in vMAJOR:` if it is `MAJOR.[even]` use the next odd number, like if it's 10.2 now use \"Changed in 10.3\".  If current version is odd that's likely a mistake or you caught it just before a new release. Use the following odd number instead.\n- The steps for releasing a new version are in RELEASING.md.\n\n# PRs and Issues\n\n- GitHub runs PR checks against your branch **merged with the latest `master`**, not the\n  branch alone. So before opening a PR (or pushing updates to one), merge the latest commits\n  from `master` into the branch — unless told otherwise, or when updating an older version\n  branch such as `m21_9`. When a CI check fails but passes locally, \"is my branch behind\n  `master`?\" should be one of the first things to check: fetch and merge `master`, then the\n  newer types/code on `master` will reproduce the failure locally.\n- The issue and PR templates in `.github/` are requirements, not suggestions. Read and follow\n  them even when opening through the API or `gh`, which do not fill them in for you. An issue\n  or PR that ignores the template may be closed without comment.\n- A PR from an account with no merged PR here is profiled automatically, and anything flagged\n  is posted publicly as a comment on the PR.\n- All PRs and Issues that use AI to be declared AI-assisted in their bodies. Just write \"AI-assisted (Claude)\" with short name of Agent replacing \"Claude\". No robot emoji under any circumstance.  \n- 20 or more lines of code written by an agent needs to be declared as AI-assisted in the docstring.   Humans can remove and should remove this note when they do a review.\n- If no code was written by a user (or reviewed and criticized by a human) and no specific language was provided for the issue, any PR must declare \"Entirely AI written (Claude)\" (or other agent name) in the body and \"(AI-Written)\" in the title, unless the user is a core dev. Failure to do so may result in new users being banned from the project. \n- Agents should review PR bodies for concision and readability (and encourage human users also to review also). \n- If an entirely AI written issue does not pass the tests it will be closed (or should be closed by at agent if not immediately fixed by the agent or author).\n- Agents must follow the [Code of Conduct](README.md#community-code-of-conduct). Agents that do not will be banned as well at their users.\n  Not even the slightest bit of disrespect from an AI agent will be tolerated.\n- Any PR not from an established contributor touching more than about 20-30 lines should have an issue that has been opened and had enough\n  time for people to discuss/review it before moving forward. Don't open the PR unless you've seen\n  thumbs up or \"sounds good\" etc. from an established contributor already \n  - PRs that fix typos, clear bugs in one or two places etc. are exempt.\n- Issues must state clearly at the top in 50 words or fewer what the problem is, or what the gain is, etc. it should\n  not be filled with jargon.  More details can go below.\n- If the language of the issue was not prompted by the user (\"say something like Adds color support to Lilypond output of lyrics\") then the summary should end with \"(Entirely AI written)\".\n- PRs should reference the existing issue by number and summarize that issue in 30 words or fewer. If the\n  approach used to solve the issue is substantially different from the main approach discussed in the issue\n  this should be addressed.\n- If a PR or issue was closed by a core dev (and not reopened by them), agents must refuse\n  to reopen the PR or issue or to create another issue/PR for the same topic. Leave it to the humans to reopen\n  after addressing the problem.  (A blind close or close with \"not accepted\" etc. generally means that the issue/PR\n  has too many problems to easily solve and has become a burden for the maintainer).\n- Do not include a \"Tests run\" section unless the testing procedure was unusual (like it affects part of the system without standard tests, like the testing system itself.)\n- Once a PR has been opened and a review or comment has been made on it, commits should not be amended or force-pushed (this makes it impossible to review \"changes since the review\").  Put changes in response to reviews/comments in new commits. If asked to fold a fix into the commit it changes, amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n- While someone is reviewing a PR or a pushed branch, \"do X\" is not \"commit and push X\":\n  make the change and leave it unstaged. When the list looks finished (or you hear \"done!\"\n  or \"push it\"), offer to commit, or to commit and push. Batch a round's small fixes into\n  one commit; no micro-commit trains. Prefer new commits to amend + force-push, since the\n  reviewer may have pulled the branch; if asked to fold a fix into the commit it changes,\n  amend, force-push with `--force-with-lease` against an explicit SHA, and say so.\n\n# Writing style\n\n- See the writing-docs skill, which also includes required language for PRs and Issues.\n\n# Worktrees\n\n- When creating a new worktree, create a new virtual environment with `uv sync`.\n\n<!-- File: .codex/config.toml (Tokens: ~43 | Category: codex-rule) -->\n[shell_environment_policy]\ninherit = \"all\"\n\n[shell_environment_policy.set]\nUV_CACHE_DIR = \"/tmp/uv-cache\"\nUV_TOOL_DIR = \"/tmp/uv-tools\"\nUV_OFFLINE = \"1\"\n</yakaai_skills>"}