docs(agents): record §5 of the conventions spec, and the markdown line-width call - #388
Merged
Conversation
Two conventions that were live but unwritten. **Stage contract docstrings (§5).** The 2026-07-11 v2 implementation conventions spec fixed nine sections before the first line of code. Its own header attests to distilling "§1–§4, §6–§9" into this file on 2026-07-12 — a range that reads as exhaustive unless you notice §5 missing from the middle of it. §5 is the one section that never reached a committed home, and the 2026-08-16 spec-residue sweep (#381) did not catch it, most likely for the same reason. It has been followed anyway: all nine pipeline stages carry the Consumes:/Produces:/Reads: docstring, and the two _pipeline/ modules that are not stages say so in their first line instead. So the convention was load-bearing and unenforceable at once — a new stage could have omitted it and no reviewer would have had grounds. Now recorded, including that the absence of the three lines is itself a claim about the module. **Markdown line width.** Measured rather than argued: wrapped and unwrapped markdown render identically, docs/design/ is not in the Sphinx build, and unwrapping is byte-identical since newline→space is 1:1 — so no rendering, build, size or token difference exists. The cost is one-sided, at 74% of the deleted words in #386's decisions.md diff being reflow rather than change. New prose goes unwrapped; existing paragraphs stay until rewritten, because a bulk rewrap resets git blame across 31 and 43 authoring commits in rules.md and decisions.md, and that provenance is what dates a stale claim. The exception is enforced, not stylistic: rules.md's example lines and pointer lines are parsed per line, and a wrapped example is a hard error. Statement prose is free because the citation check normalizes whitespace first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #388 +/- ##
=======================================
Coverage 98.51% 98.51%
=======================================
Files 44 44
Lines 2895 2895
=======================================
Hits 2852 2852
Misses 43 43 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The note telling you not to hard-wrap prose was hard-wrapped across two paragraphs and 19 lines, having been offered as one. Now one unwrapped line, which is both the convention it states and the length it promised. Dropped in the trim: the per-medium enumeration of what makes no difference (rendering, build, size, tokens), the blame-commit counts, the .git-blame-ignore-revs escape hatch, and the exception's field list. The measurements stay in this PR's description and #386's history; a convention needs the rule and the one number that justifies it, not the workings. Note the section it sits in was already wrapped while "2.0 API modules" below is not, so the file was mixed before either note existed -- which is why the §5 bullet in the previous commit is unwrapped and correct without anyone deciding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
derek73
force-pushed
the
docs/conventions-gap
branch
from
August 17, 2026 03:23
d710e49 to
ca6cec2
Compare
Three errors in the §5 bullet, all found in review. It said stages are "named exactly as rules.md and the specs name the stage". rules.md cannot be that authority and says so in its own preamble -- "Statements are implementation-free: no stage names, no function names, no regexes" -- so the bullet asked rules.md to do the one thing it forbids itself. Nor does `implemented:` supply it: that field names the modules honoring a rule, and a rule can list several while a module honors several, so it carries no stage naming at all. The committed authority is `_pipeline/__init__.py`'s STAGES for the set and ParseState's docstring for the field-ownership map, pinned by tests/v2/pipeline/test_state.py. The original §5 pointed at the v2 core API spec, which is gitignored -- which is why the distillation needed a different anchor rather than a transcribed one. It said "all nine stages carry it". There are eight; STAGES calls itself the eight-stage fold. The ninth module is _assemble, which the same sentence describes as not a stage -- the count contradicted its own clause. Trimmed with it: the spec-header attestation story, which is why this section was missing rather than what the convention is, and belongs in #388's description. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mechanical reformat, no words added, removed or reordered. Verified by collapsing all whitespace in each file before and after and comparing digests -- identical for all five -- independently of the rewrapper's own claim, since a script asserting its own correctness is the shape this project keeps getting caught by. AGENTS.md 531 -> 375 lines docs/design/decisions.md 1457 -> 513 docs/design/mechanisms.md 575 -> 154 docs/design/rules.md 828 -> 675 docs/design/nameparser-2.0-rfc.md 305 -> 160 Left wrapped, deliberately: rules.md's rule BODIES. A rule block is a record format, not a paragraph -- 4-space statement prose above 6-space example lines that align into a scannable unit -- so it sits with tables and code fences, which the rewrapper also skips. Only rules.md's preamble and section Backgrounds moved, which is why its reduction is the smallest of the five. Its example lines and pointer lines are parsed per line and could not have been joined regardless; tests/v2/test_rules_doc.py and test_doc_citations.py pass, 233 of them. Blame cost, measured rather than assumed: 43 lines of 1717 end up attributed to this commit -- 13 in AGENTS.md, 24 in decisions.md, 3 each in mechanisms.md and rules.md. Git's diff already maps most joined lines back to whichever commit wrote their opening text, so the reformat is far cheaper for `git blame` than the wholesale reset an earlier note in this branch predicted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds .git-blame-ignore-revs and updates the line-width note now that the bulk rewrap has happened -- the note previously said "don't bulk-rewrap", which the previous commit contradicts. Both halves of the justification I gave for that advice turned out wrong, and measuring rather than re-reading is what caught them. The blame damage is 43 lines of 1717, not wholesale. Git's diff already maps most joined lines back to whichever commit wrote their opening text, so a reflow costs far less attribution than predicted. And .git-blame-ignore-revs recovers NONE of those 43 -- verified with the file well-formed and carrying the correct full SHA, identical counts with and without. --ignore-revs-file re-attributes a line by finding it in the parent, and a line produced by MERGING five lines has no counterpart there, so there is nothing to hand the blame back to. The entry is kept because it is the right home for future format-only commits that map 1:1, and the file says in its own header that it does nothing for this one -- inert machinery is fine when it is labelled inert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
derek73
added a commit
that referenced
this pull request
Aug 17, 2026
#388's §5 bullet asserted that rules.md names the pipeline stages, which its preamble forbids in as many words. Axis 4 as written would not have caught it: it pointed inward, from a rule to the decision entry it cites, while that error pointed outward, from AGENTS.md at rules.md. Same class, opposite direction, so the axis now names both rather than the list growing a tenth item -- the note's own warning is that a checklist asking for meaningless things teaches skimming. Checked while writing it, and worth recording: rules.md IS compliant with its preamble. A first detector flagged seven candidates and all seven were false positives -- "group" three times as the ordinary English word, which P2 legitimately uses for groups of words, and four regex-shaped hits that were my own pattern matching markdown bold. The one that survived inspection, matches()/comparison_key(), sits in "Not in scope", and _statements() in test_doc_citations.py defines a statement as rule-block text up to the first example -- so Backgrounds and Not-in-scope may name functions, and do. That caveat is in the axis for a reason: without it the check produces confident false positives, and someone acts on one. A constitution's terms of art are defined somewhere executable in this repo, and that definition outranks the intuitive reading of the word. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Documentation only —
AGENTS.md,docs/design/, and a new.git-blame-ignore-revs. Five commits, the last two of which correctclaims the first three made.
§5 — stage contract docstrings
The 2026-07-11 v2 implementation-conventions spec fixed nine sections
before the first line of the rewrite. Its own header attests:
That range reads as exhaustive unless you notice §5 is missing from
the middle of it. It is the one section that never reached a
committed home, and #381's spec-residue sweep didn't catch it —
plausibly for the same reason, since a sweep reading that header ticks
the spec off as distilled. A false attestation defeats an audit more
thoroughly than no attestation, because it turns the audit into a
lookup.
The convention was followed anyway — all eight pipeline stages carry
the
Consumes:/Produces:/Reads:docstring, and_pipeline/modules that are not stages say so in their first line instead. So it
was load-bearing and unenforceable at once.
Corrected in review (thanks — all three were wrong):
rules.mdas naming the stages.rules.mdforbids exactlythat in its own preamble: "Statements are implementation-free: no
stage names, no function names, no regexes."
implemented:, which names modules honoring a rule —many-to-many, and carries no stage naming.
_assemble, which the same sentence called not-a-stage.The real authority is
_pipeline/__init__.py'sSTAGESplus thefield-ownership map in
ParseState's docstring, pinned bytests/v2/pipeline/test_state.py. §5 originally pointed at the coreAPI spec, which is gitignored — so the distillation needed a new
anchor and I reached for the nearest normative-sounding document
rather than the correct one.
Markdown line width, and the rewrap
Measured: wrapped and unwrapped render identically,
docs/design/isnot in the Sphinx build, and unwrapping is byte-identical
(newline→space is 1:1). No rendering, build, size or token difference
exists. The cost is one-sided — 74% of the deleted words in #386's
decisions.mddiff were reflow, not change.All five documents are now unwrapped in one whitespace-only commit:
Content verified identical after whitespace collapse, checked against
HEADrather than trusting the rewrapper's own claim.rules.md's rule bodies stay wrapped. A rule block is a recordformat — 4-space statement prose above 6-space example lines that
align into a scannable unit — so it sits with tables and code fences,
which the rewrapper skips. Its example and pointer lines are parsed
per line and could not have been joined regardless; the 233 doc tests
pass on the result.
Two claims the rewrap falsified
Both were in the justification I wrote for advising against a bulk
rewrap, and both were assumptions:
git blame." Measured: 43 lines of 1717 (~2.5%).Git's diff maps most joined lines back to whichever commit wrote
their opening text.
.git-blame-ignore-revsfixes that." It recovers none ofthe 43. Misconfiguration ruled out — file well-formed, correct full
SHA, identical counts with and without.
--ignore-revs-filere-attributes a line by finding it in the parent, and a line made by
merging five lines has no counterpart there.
The file is kept as the right home for future format-only commits that
map 1:1, and its header says plainly that it does nothing for this one.
Verification
uv run --frozen pytest— 3459 passed, 20 skipped, 11 xfailed, greenat every commit.
🤖 Generated with Claude Code