Skip to content

1.0.1: the setting 1.0.0 documented but never read, the MCP seam, a recovered melody, and a demo that agrees with the compiler - #3

Merged
SuperInstance merged 12 commits into
masterfrom
claude/init-1wgqn0
Aug 17, 2026
Merged

1.0.1: the setting 1.0.0 documented but never read, the MCP seam, a recovered melody, and a demo that agrees with the compiler#3
SuperInstance merged 12 commits into
masterfrom
claude/init-1wgqn0

Conversation

@SuperInstance

@SuperInstance SuperInstance commented Aug 17, 2026

Copy link
Copy Markdown
Owner

plainsong 1.0.0 is on PyPI, cut from master. This is everything master did
not have when that happened, and it bumps the version to 1.0.1.

1.0.0 took away a rendering and shipped no way back

This is the one I would want reviewed first, because it is a fault in the
released version rather than in a branch.

1.0.0 changed how chords naming more than four notes sound. The change is right
and was arrived at by measurement — a player thinning a voicing drops the fifth
first and the root second, so taking the bottom four does exactly the wrong
thing, and D9 had been rendering as D7. But it shipped with no way out:
docs/voicing.md told readers to set render.voicing, ArrangeOptions carried
the field, and nothing in the codebase read it from configuration. The setting
existed everywhere except where it mattered.

This repository's own rule is that a change to how existing notation sounds goes
behind a setting. It is now real:

[core]
voicing = "stack"      # the pre-1.0.0 rendering, exactly
  • PLAINSONG_CORE_VOICING does it for a single run.
  • It sits in [core] beside bar_fill because it decides which notes exist,
    so it changes the MIDI and not merely the audio. render.voicing is still
    honoured — that spelling was published, and ignoring it in silence is the
    same fault being fixed.
  • A value that is not a strategy now reports. It previously fell through to
    the default without a word, which is indistinguishable from being obeyed.

Both new guards were removed and the suite confirmed red before restoring them.
The corpus fingerprint is unchanged: 6,321 files compile to exactly the music
they did
, which is the point — the default does not move.

And the changelog was worse than the code

1.0.0 shipped a section headed "Known, recorded rather than fixed" explaining
that D9 sounds like D7 — in the same release as the commit that fixed it.
Readers were told the opposite of what the software does, and were not told
the thing they needed: seventy-two files in the bundled corpus render
differently than they used to. That section now says what happened, including
the breaking-change notice it owed.

The dialect detector was deleting a melody

detect_dialect only examined lines beginning with |. The relative dialect
names its harmony first — I | 1 . 3 | 5 . . | — so in a file written that way
it saw no relative evidence at all and fell through to absolute. Every scale
degree then became a token nothing could read.

Measured on the one file here written that way: 0 lines start with a pipe, 14
have a roman numeral in front of one.
It compiled to 42 notes with 51
warnings. It is 111 notes with none.

The near-miss is the part worth keeping. An agent asked to clear those warnings
read the bare digits as generation artefacts and replaced all 51 with rests. The
note count held at 42, every test passed, the fingerprint moved by exactly the
one file it should have, and the warnings went to zero — a clean bill of health
for an edit that deleted a tune. A warning that a parser cannot read something
is evidence about the parser at least as often as about the file.

Warnings across all sources: 85 → 3. What remains is honest — two sections
whose header disagrees with their rows, and one file in examples/edge-cases/,
a directory whose job is holding malformed input.

The demo had fallen behind the compiler

The browser demo is the front door, and its chord parser was a table of 18
enumerated qualities while Python moved to an open grammar. Twelve ordinary
symbols worked in the CLI and failed silently in the browser, CM7 among them.
It also carried the same slice(0,4) truncation.

tests/test_demo.py sailed past all of it, because it pinned note counts for
four presets and said nothing about vocabulary. It now compares the two
implementations directly — core aliases, seventh-implied spellings, alt
tables, degree table, every core. Verified in real headless Chromium: presets
match, and the voicing agrees note for note (C13E Bb D A on both sides).

The MCP seam, cut where the names fall

plainsong/mcp/ and the sibling repo were ~2,900 duplicated lines with 109
already drifted. Not one thing forked twice — four pieces of technology
wearing one name
. features.py is 300 lines of per-bar musical analysis with
nothing protocol-shaped in it; it now sits beside notation/ and perform/.
protocol.py was byte-identical. 490 lines stopped existing in duplicate.

tools.py was assessed and deliberately left — it reached into the ensemble
module seventeen times through a relative import, so sharing it would have
silently bound the sibling's tool surface to this repo's stale ensemble.py.
That is now fixed by injection, and the fix was one line: all seventeen uses
live inside register()'s body as closures, so the implicit binding existed at
exactly one point. Proved with a test that registers against a fake ensemble
module and checks every call routes through it.

The two audiences with no front door

The README assumed a reader who already knew what this was. It now opens with
three doors — you read music but not code, you write code but not music, you are
an agent — and lists every example piece with what each one demonstrates, which
was previously discoverable only by listing a directory.

AGENTS.md is new, and its longest section is a catalogue of mistakes,
because those are the reusable part. Every one was made by an agent working on
this repository: success is not evidence (the compiler turns what it cannot read
into a rest and reports ok); a warning about the parser is not a warning about
the file; never re-record a fingerprint baseline to make CI green; do not
find-and-replace on a word ("Symphony No. 5 in C minor" became "…in Cm"
across 193 files that way); verify from somewhere neutral, because the working
directory is on sys.path.

Also

  • Chord roots rejoined to their qualities. | Gb maj7 . . . | was two
    tokens where one was meant — losing the quality and making the bar five
    tokens wide. Six files, nineteen chords, fixed by rule rather than by list.
  • N.C. reads as a rest. It already compiled to silence; it just complained
    while doing it.
  • The audio path people should use leads. The built-in synthesiser is mono
    and it is the first thing anyone hears; fluidsynth is now documented where a
    reader meets it, in docs/audio.md, with every config key read from the code
    rather than guessed.
  • Seventeen filenames the rename missed. Contents were clean, links
    resolved, tests passed — the old name only showed when you listed a directory.
  • proposals/02-the-voyage.md, a plan whose two research phases are
    answered and whose remaining unknowns are marked as unknowns.

Verification

597 tests, 7 specs, ok 6334 file(s) checked, 3 warning(s), ruff clean, and
6321 file(s) compile exactly as recorded.

Every change that could alter what notation compiles to was checked against the
fingerprint before re-recording it, never after. The two renames were proved
inert by comparing the multiset of note hashes across all 6,321 files — identical
both times, only paths moved.

What this unblocks

plainsong-mcp pins the compiler by git URL because 1.0.0 predates
plainsong.features. Merging this and releasing 1.0.1 makes that pin a version
specifier, which is the only line in that repository that has to change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc

claude added 12 commits August 17, 2026 04:42
A plan for what Plainsong becomes, written as a first draft on purpose. Two of
its six phases open with [research] because I do not know the answer yet, and a
plan that hides its unknowns is a wish.

The finding that reorders the brief: rows subdivide independently and
non-sounding rows carry no time at all. `Lyrics: | the tide came |` divides a
bar into thirds while `Chords: | Am . . . |` divides it into quarters, so
"came" -- written directly beneath "C5" -- is sung a twelfth of a bar away from
it, and lyrics have no internal position whatsoever. The alignment a reader
trusts is a fiction the compiler cannot see, warn about, or render.

That single gap is the root of three of the four stated challenges. A renderer
has nothing to lay lyrics against; a merge tool has no coordinate to detect
conflicts in; a linter cannot express "this row disagrees with its neighbours".
So the plan is built around one structure -- a time matrix giving every token in
every row a position whether or not it sounds -- and everything downstream is a
projection of it. SVG becomes a coordinate transform rather than a layout
engine. Merge becomes set intersection, which makes row-disjoint concurrent
edits provably safe rather than heuristically safe.

Worth recording, because it corrects the brief: parsing is already whitespace
invariant. Tight, loose, ragged and tab-separated layouts of the same bar
produce byte-identical output, so monospace alignment is a human convention and
not a machine dependency. The decoupling that was asked for is already there;
the problem is one layer down.

The invariants section is scars rather than style. Measure where the decision
happens -- averaging voicing strategies over the whole corpus read 99.4% against
99.9% and was useless, while scoring only the chords where a note must be
dropped read 50.1% against 94.4%. Absence of imports measures coupling, not
life. Never re-record a fingerprint baseline to make CI green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
The two scouts came back and turned Phases 2 and 3 from open questions into
decisions. Both worked around a blocked egress proxy by cloning the projects'
own source repositories and reading the documentation at HEAD, which is the
same primary-source standard reached a different way, and both flagged what
they could not verify instead of filling the gaps in.

Lyrics. Syllables bind to notes, never to raw time -- uncontested across
LilyPond, MusicXML, ABC, MEI and Humdrum, which disagree on mechanism and not
on the target. So a lyric row subdividing its bar independently of the melody
is not a variant convention, it is simply wrong.

The barline resyncs, quoted from the ABC standard: "if there are not as many
syllables as notes in a measure, typing a | automatically advances to the next
bar." A miscount costs one bar and then recovers instead of shifting every
remaining word. Plainsong already writes | in every row, so the mechanism is in
the syntax today and merely unhonoured.

And the part worth the whole errand: melisma and skip need no new characters. A
syllable held across notes is a sustain; a note with no syllable is a rest.
Plainsong's existing token classes already mean exactly those. The mapping is by
meaning rather than by borrowing a spelling, which matters because the spellings
conflict -- `_` is a skip in LilyPond and a melisma in ABC, and Plainsong's own
semantics land on the right side of both.

One genuine conflict surfaced. Every other format stacks repeated lyric rows as
parallel verses; Plainsong's rule is that a repeated row of the same kind runs
on in time. Two `Lyrics:` rows therefore mean verse-then-verse here and verse 1
over verse 2 everywhere else. One reading has to give, and the run-on rule is
load-bearing for `Melody:`. Recorded rather than resolved, because it needs
deciding before the renderer has to draw them.

SVG. Text measurement is solved and it is what decides the design: `textLength`
and `lengthAdjust` are geometry rather than CSS, so the browser fits text to a
width we declare. Compute from the Core-14 AFM tables in Python, emit
`textLength` to match, and the rendering snaps to the intended width even under
font substitution. No font library, no measurement pass.

Everything scales from one number, because SMuFL fixes 1 staff space = 0.2 em.
Horizontal space is logarithmic in duration, with LilyPond's constants giving a
directly implementable formula. Chords are left-aligned to the beat, not
centred -- the casual "centred" phrasing in circulation is imprecise.

GitHub strips raw <svg> from markdown; only <img src=...> renders. That kills
webfonts and interactivity, survives textLength, and is the strongest argument
yet that the worker should be a render service, since an <img> pointing at a URL
is the only way a .song embeds in a document on a platform we do not control.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
`plainsong/mcp/` and the sibling's `plainsong_mcp/` were ~2,900 duplicated
lines with 109 already drifted apart. The framing that unlocked it: this is not
one thing forked in two places, it is four pieces of technology wearing one
name, which is why neither copy could evolve.

`features.py` is the clearest case. Three hundred lines computing sixteen
per-bar musical features, with nothing protocol-shaped in it, living in `mcp/`
for no reason except that MCP was what needed it first. It now sits beside
`notation/` and `perform/` where it belongs, and the sibling imports it instead
of carrying a byte-identical copy.

`protocol.py` was byte-identical too -- checked raw as well as with the package
names normalised -- so the sibling now imports rather than retypes it. 490
lines of duplication gone entirely.

The measured drift went up rather than down, from 113 lines to 135, and that is
the honest number: files importing across packages necessarily read differently
from files importing locally, and each `__init__` now describes a smaller
scope. Two whole files stopped existing in duplicate, which is the result that
matters.

`tools.py` and `resources.py` were assessed and deliberately left alone, and
the reason is the most valuable thing to come out of this. They look mergeable
-- 11 and 16 diff lines, all import style -- but `tools.py` calls into the
ensemble module seventeen times through a plain relative import. Sharing them
as they stand would silently bind the sibling's tool surface to the
*compiler's* stale `ensemble.py` rather than its own, and the two copies have
already drifted 18 lines. That is a live correctness bug, not a hypothetical
one. Decoupling it wants an injected dependency and its own decision, not a
rider on a file move.

The fingerprint is unchanged at 6321 files, which is the point: moving a Python
module cannot alter what notation compiles to, and if it had, the move would
have been wrong.

578 tests, 7 specs, ruff clean; the sibling's 91 tests pass and its server
still answers with 27 tools.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
`| Gb maj7 . . . |` is two tokens where one was meant: `Gb`, a chord, and
`maj7`, which nothing can read. It loses the quality *and* makes the bar five
tokens wide instead of four, so the bar subdivides wrongly as well. Six files,
nineteen chords, warnings from 85 to 54.

Fixed by rule rather than by a list: only chord rows, only inside pipes, and
only where joining the two tokens produces something that parses as a chord --
so it cannot invent a chord that was not already implied. Every edited bar had
its other chords already joined, which is what makes the intent unambiguous.

Also recorded, and worth more than the warning count: **dialect auto-detection
fails on a relative-dialect file and it looks exactly like corrupt source.**
`examples/tapscript-4-tap-closing-time.song` writes `I`, `IV`, `vi` as roman
numerals and `1 . 3 | 5 . . |` as scale degrees. `dialect: auto` reads it as
absolute, so every degree becomes unreadable: 42 notes, 51 warnings. Told
`--dialect relative`, the same file gives 111 notes. The melody is real; the
detector is wrong. That single misdetection is 51 of the 54 warnings left.

How close that came is the part worth keeping. An agent clearing warnings read
the bare digits as generation artefacts and replaced all 51 with rests. Note
count held at 42, every test passed, the fingerprint moved by exactly the one
file it should have, warnings went to zero -- a clean bill of health for an edit
that deleted a tune. A warning that a parser cannot read something is evidence
about the parser at least as often as about the file.

The four bar-count warnings stay: a section header may legitimately disagree
with its rows. So does the one in `examples/edge-cases/`, a directory whose job
is holding malformed input.

Fingerprint moved for exactly the six files edited and no others -- checked
before re-recording, which is the only circumstance in which re-recording is
honest.

578 tests, 7 specs, ruff clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
The demo is the front door -- a compiler in one file that runs from file://
with nothing installed -- and its chord parser was a table of 18 enumerated
qualities while Python moved to an open grammar. Twelve ordinary symbols worked
in the CLI and failed silently in the browser, `CM7` among them. A visitor
types a normal chord, gets nothing, and concludes the project is broken.

`tests/test_demo.py` sailed straight past it, because it pinned note counts for
four presets and said nothing about vocabulary. The guard that let the drift
happen was as much the defect as the drift.

Now it compares the two implementations directly: the core aliases, the
seventh-implied spellings, the `alt` tables, the degree table and every core,
each asserted against the Python grammar it was ported from. Six tests where
there was one, and a divergence fails CI instead of waiting for a visitor.

Verified in real headless Chromium rather than inferred: all four presets still
compile to the counts the page claims, all twelve previously-failing symbols
read, and the voicing agrees note for note -- D9 gives D Gb C E on both sides,
G7alt gives B F Ab Db, C13 gives the rootless E Bb D A. The demo carried the
same `intervals.slice(0,4)` truncation Python had, so it had the D9-sounds-like-D7
bug too; the guide strategy is ported with it.

One thing to correct in the record. The JavaScript port itself is not in this
commit -- it landed in 7ca17ad, a commit about chord spacing in `.song` files,
because I ran `git add -A` while a subagent was still writing to the same
working tree. 183 lines of unreviewed JavaScript went in under a message that
does not mention them, and I pushed it. The code turns out to be right, which
is luck rather than process: I have reviewed and tested it only now, after it
was already on the remote. Four write-agents in one checkout is what caused
this, and the same mistake earlier had one agent revert another's work.

579 tests, 7 specs, fingerprint unchanged, ruff clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
`detect_dialect` only examined lines that begin with a pipe. The relative
dialect names its harmony first -- `I | 1 . 3 | 5 . . |` -- so in a file
written that way the detector saw no relative evidence at all and fell through
to absolute. Every scale degree then became a token nothing could read.

The measurements, on the one file in this repository written that way: 0 lines
start with a pipe, 14 have a roman numeral in front of one. It compiled to 42
notes with 51 warnings. It should be 111 notes with none.

A roman numeral standing where a row label goes is sufficient evidence by
itself, because it means nothing in the absolute dialect, so the fix is to
count that line and scan the tokens after its first bar rather than the whole
line.

Warnings across every source fall from 54 to 3, and what remains is honest: two
sections whose header disagrees with their rows, which is allowed, and one file
in `examples/edge-cases/`, a directory whose job is holding malformed input.

Blast radius is exactly one file -- checked against the fingerprint before
re-recording, not after. That it is one file is the point: this is a detector
that was wrong about a single piece, not a change in how the dialect works.

This is the bug that nearly cost the tune. An agent asked to clear warnings
read those bare digits as generation artefacts and replaced all 51 with rests;
the note count held at 42, every test passed, and the warnings went to zero. A
warning that a parser cannot read something is evidence about the parser at
least as often as about the file, and here it was entirely about the parser.

Three tests, and the guard was removed and confirmed to fail two of them.

584 tests, 7 specs, ruff clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
Nothing has ever shipped, so a split between Unreleased and a dated 1.0.0 was
fiction: it was all one unreleased release. Folded, and dated the day it is
actually being cut, because a version that has not shipped has no date worth
trusting.

`docs/releasing.md` asks for exactly this before a tag, and a tag is now the
only step left. Pre-flight run from a clean venv outside the source tree:
version agreement between pyproject and version.py, wheel and sdist build,
install, then --version, new, compile, spec (7 passed, so the packaged data
files are intact), library (the songbook ships), and chord (the grammar ships).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
`tools.py` reached into the ensemble module seventeen times through
`from . import ensemble as ens` -- a plain relative import, which resolves to
whichever copy of `ensemble.py` the file physically sits next to. Sharing this
module with the sibling repository would therefore have wired its tool surface
to *this* repository's `ensemble.py` rather than its own, and the two copies had
already drifted eighteen lines. A live correctness bug, and the reason the
previous consolidation pass stopped here rather than forcing it.

The fix turned out to be one line, because all seventeen uses live inside
`register()`'s own body as closures over nested handlers. There was no generic
core to split out; the implicit binding existed at exactly one point, the import
statement. So `register()` and `Resources.__init__()` take an optional
`ensemble`, defaulting to a lazily-resolved import of this package's own copy.
Not one of the seventeen call sites changed -- they still read
`ens.find_session(...)` verbatim; only what `ens` is bound to moved, from
"whatever lives next to this file" to "what the caller passed, or that same
default".

Behaviour is unchanged by construction: with no argument the default path runs
byte-for-byte the same import that used to run at module scope, merely deferred.
`server.py` calls both without the new parameter and needed no edit.

Proved rather than asserted. `tests/test_mcp_ensemble_injection.py` registers
the tools against a fake stand-in module carrying the same seven names, and
checks every `ensemble_*` call and every session resource routes through the
injected module instead of this repository's -- while the no-argument path still
reaches the real one and the counts stay at 27 tools and 9 resources either way.

592 tests, 7 specs, fingerprint unchanged, ruff clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
The first thing anyone hears is the judgement they make of the project, and the
built-in synthesiser is a preview renderer -- mono, approximate timbres, pure
Python. That it needs nothing installed is a real achievement and the reason it
exists, but it is not what the work should be judged on, and the quality path
was a footnote in a limitations list at the bottom of the README.

Now the README says both things where a reader meets them: it works with
nothing installed, and fluidsynth with a General MIDI soundfont is one command
away. `docs/audio.md` carries the detail -- what the built-in renderer is and
honestly is not, installation on macOS, Linux and Windows, where to find a
soundfont, what ffmpeg adds, what mido and a hardware port add.

The configuration in it was read rather than guessed, which is the failure mode
for a document like this: `PLAINSONG_SOUNDFONT` is real at
`runtime/capabilities.py:131`, and the documented search paths are the actual
contents of `SOUNDFONT_HINTS`. No config key appears in the document that does
not appear in the code.

What it deliberately does not say is how much better fluidsynth sounds. Nobody
here can listen to it. It describes what each backend *is* -- a sampled
soundfont against synthesised waveforms -- and leaves the judgement to the ear
that has one.

6333 sources check with 3 warnings, unchanged; every relative link resolves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
The big rename replaced `tapscript` everywhere inside files and moved every
`.tap` to `.song`, but it never touched filenames that carried the old name.
Seventeen of them survived: nine example pieces, the VS Code grammar and
snippets, four proposals, a knowledge-base note.

Nobody would have noticed from the tests -- everything compiled, every link
resolved, and the word appeared nowhere in any file's contents. It only shows
up when you list a directory, which is exactly where a new reader looks first.

Found by inventorying what the README could honestly link to, which is a better
audit than grepping for a word.

Verified inert the same way the original rename was: the multiset of note
hashes is identical across all 6,321 files, only the paths moved. A rename that
changed music would not be a rename.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
The README assumed a reader who already knew what this was. It now opens
with three doors -- you read music but not code, you write code but not
music, you are an agent -- and points each at the shortest path to a sound
coming out. It also lists every example piece in the repository with what
each one demonstrates, which was previously only discoverable by listing a
directory.

AGENTS.md is new, and is the document I wish had existed before an agent
replaced fifty-one scale degrees with rests to clear a warning. Its longest
section is the mistakes, because those are the reusable part:

- Success is not evidence. The compiler turns what it cannot read into a
  rest and reports ok. Read the note count, not the exit code.
- A warning about the parser is not a warning about the file. The melody
  that got deleted was fine; detect_dialect was not.
- Never re-record a fingerprint baseline to make CI green. That check is
  the only thing standing between a refactor and a silently transposed
  corpus, and --write disarms it in one command.
- Do not find-and-replace on a word. "Symphony No. 5 in C minor" became
  "Symphony No. 5 in Cm" across 193 files that way.
- Verify from somewhere neutral. The working directory is on sys.path, so
  importing the package from inside the checkout tells you nothing about
  the wheel.

Every command quoted in either file was run, both compile clean under
plainsong check, and all relative links resolve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
1.0.0 changed how chords with more than four notes sound. That change was
right and was arrived at by measurement, but it shipped with no way out:
docs/voicing.md told readers to set `render.voicing`, `ArrangeOptions`
carried the field, and nothing in the codebase read it from configuration.
The setting existed in every place except the one that mattered.

This repository's own rule is that a change to how existing notation sounds
goes behind a setting. The setting is now real:

    [core]
    voicing = "stack"      # the pre-1.0.0 rendering, exactly

`PLAINSONG_CORE_VOICING` does it for one run. It sits in [core] beside
bar_fill because it decides which notes exist, so it changes the MIDI and
not merely the audio. `render.voicing` is still honoured -- that spelling
was published, and ignoring it in silence is the same fault as the one
being fixed.

A value that is not a strategy now reports. It previously fell through to
the default without a word, which looks exactly like having been obeyed.
Both guards were removed and the suite confirmed red before being restored.

The changelog was worse than the code. 1.0.0 shipped a section headed
"Known, recorded rather than fixed" explaining that D9 sounds like D7 --
in the same release as the commit that fixed it. Readers were told the
opposite of what the software does, and were not told the thing they
actually needed to know: seventy-two files in the bundled corpus render
differently than they used to. That section now says what happened.

597 tests, 7 specs, ruff clean, and 6,321 files compile exactly as
recorded -- the default is untouched, which is the point.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PBAjxy7cD6DzJ72NX8TJEc
@SuperInstance SuperInstance changed the title Everything 1.0.0 shipped without: the MCP seam, a recovered melody, and a demo that agrees with the compiler 1.0.1: the setting 1.0.0 documented but never read, the MCP seam, a recovered melody, and a demo that agrees with the compiler Aug 17, 2026
@SuperInstance
SuperInstance merged commit 13bcdd2 into master Aug 17, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants