Skip to content

Retire the block rule format (#761) - #786

Merged
derek73 merged 6 commits into
masterfrom
docs/retire-block-rule-format
Oct 21, 2026
Merged

derek73 merged 6 commits into
masterfrom
docs/retire-block-rule-format

Conversation

@derek73

@derek73 derek73 commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Every rule in docs/design/rules/ is now in the skeleton format (PRs #768, #770, #772, #777, #779, #781, #782 and #783). This PR removes the old indentation-based block format: its parser, its measures and checks, and its tests and docs. It also fixes the four parser minors that PR #768's review left for this PR.

Closes #761

No rule text, no example and no reading moves. I dumped parse_rules_doc and prose_blocks over the real rules on master and on this branch and compared them. The parsed rules (every field, example and pointer) are byte-identical. The only difference in the quotable text is the dropped Accepted: prefix described below. The budget record is unchanged and holds skeleton rows only; nothing touches the corpus or the snapshot.

Removed

  • tests/v2/rules_doc.py: the block branch of parse_rules_doc and prose_blocks, the interacts: handling and Rule.interacts, the near-miss checks for the block's tolerated:, Signal: and Accepted lines, the per-file dispatch, and the docstring's account of the old grammar. Every # ID — name heading now goes straight to parse_skeleton, so rules_skeleton.is_skeleton_block and its near-miss dispatch are gone too. The hub and no-boundary: regexes moved into rules_skeleton.py, the only module that uses them.
  • split_accepted, and the Accepted: prefix skeleton_prose added. The brief listed split_accepted as something to keep, but once the block paths were gone its only callers were tests. The prefix existed only so it could split the Accepted bullets back out. I checked by removing the prefix: no citation in the tree quotes it.
  • tests/v2/rules_budget.py: BLOCK_MEASURES and the format-change path. A leftover block row now reads as measures missing and measures gone, never as a match (tested).
  • tests/v2/test_doc_citations.py: the block branch of hub_findings (along with its unused prose argument) and the interacts half of named_id_findings. test_interacts_ids_exist is now test_the_rule_ids_a_rule_names_exist.
  • Docs: the block-format text in rules/README.md (including the budget's block measures, the block tolerated:/Signal: placement and the block hub map), the block half of the split-candidate grep, axis 13 of docs/design/AGENTS.md and its other both-formats wording, and in root AGENTS.md the line-width exception for block bodies, the "moving to the skeleton" sentence, the block wording of the excerpt-discipline rule and the old "rule shape" list.

Kept, as a refusal

Without the block parser, a rule written in the old format would be skipped silently and its examples would never run. A column-0 X1. line is now refused wherever it stands, with a message naming the retired format and the skeleton. That covers a line outside any rule, a line opening a rule's body, and a line inside or glued to a field, where it would otherwise have joined the prose. Tested in all five placements.

Tests, sorted by the code they exercise

test_rules_doc_grammar.py is rewritten around what rules_doc.py still owns: the example-line grammar (now tested directly through parse_example_line, with rows added for a D-section subject, an unassertable field and a mismatched deviates: field), the annotation registry, the split into rules and Backgrounds, duplicate IDs, the retired-format refusal and a signal line outside any rule. Tests that guarded something the skeleton parser does were converted into test_rules_skeleton.py:

Deleted, because no skeleton code is left for them to reach: the block pointer-line test, the indentation and Accepted-spelling batteries, and the block tolerated: marker tests. The skeleton's **Tolerated:** field has its own tests. The hub-findings rows that still had something to test (a hub naming itself, X21 not counting as X2) became skeleton rows. The budget tests now use skeleton docs and rows.

The four deferred minors, each with a test that fails on the pre-fix code

  1. An assertion inside a backtick span was refused as an unfenced example. It is prose about the grammar; backtick spans are now removed before the arrow-and-assertion search. A bare assertion outside backticks is still refused (a row in both directions).
  2. A #761 … line in a bullet's continuation raised "nested list". It is prose now. A real heading there raises its own error, and a nested list still raises "nested list".
  3. When and Tolerated silently accepted a second paragraph, where Then refused one. All three now refuse it. Because stays bounded by its sentence count, as before.
  4. The S10 comment in _vocab.py named an Except bullet by description ("its one-letter Except"). It now quotes the bold lead, "A numeral of one letter needs words to spare". The comment has to name rules/S10 for the citation check to reach the quote. A new test, test_a_citation_naming_a_bullet_quotes_its_bold_lead, holds every comment that names an Except or Precedence bullet to a bold lead of that rule's bullets. The verbatim-excerpt check could not do this: another sentence of the same rule passes it, which I confirmed with a mutant.

Also: the README's Signal tag list was missing character, separator and edge full stop (W3's second signal). It now names every tag, plus the two rules (A1/C11, C7) that write no tag. test_the_readme_lists_every_signal_tag_the_rules_use holds the list to the footers both ways, and fails when a tag is dropped (checked).

Review

The design-docs-reviewer agent ran on the first commit and found seven things, all fixed in the second commit:

  • The docs said a block-format line is refused "wherever it stands", but one inside a field was read as prose. It is refused now.
  • A nested list was refused only with - . *, + and numbered markers are refused too.
  • The README's refusal list was missing "outside a backtick span".
  • The "skip what the suite enforces" guard list in docs/design/AGENTS.md lacked the Standing, D2-copy, split-grep, NFD and spelling-denylist guards.
  • Three descriptions of deleted behavior were stale: the parse_example_line docstring, axis 12, and the comment on test_corpora's floor.

Each new refusal row fails on the first commit's parser, and every row the narrower checks already refused still passes. A second review of the fix commit found no defects: no real rule holds a column-0 ID. line, the new refusal never sees fence or footer lines, and every guard the list names exists.

PR review (/pr-review-toolkit:review-pr)

Four reviewers ran on the PR (code, tests, comments, silent failures). Their findings are fixed in b11faa3b. Two rounds of review on that fix commit found gaps in the new check, fixed in 77f21dfd and 67e4c293; the last round was clean, apart from a wording note fixed in 161b88f7. None of these was a regression: master behaved the same way.

  • Text outside any rule is refused. Before, a subheading after a rule's footer hid everything below it, and a fence or field in a section file was ignored; the rule count still matched the file count. Outside a rule the text may now hold only a ## Name (X) section heading, each letter once, and its one Background: paragraph. The following are refused:

    • a section heading with no Background, or a section letter headed twice;
    • structure glued to a Background (a fence, a heading, a field label, a list item; column 0 only, by decision);
    • an example in a Background, checked with the rule side's own predicate, holds_assertion, newly split out of _looks_like_example.

    The two walkers now agree on a section heading glued to a Background.

  • A numbered list at the start of a line in a prose field is refused, as * and + already were. Inside a Background only an item numbered 1 interrupts the paragraph, as in CommonMark, so a wrapped line like 2026. Then stays prose.

  • Rule.tolerated is deleted. Nothing read it once the block parser was gone; Skeleton.tolerated holds the reason.

  • Two tests were fixed whose assertions had never been shown to fail: the bullet-lead and signal-tag checks are now helpers with synthetic rows covering both directions. A rule heading with no fields gets its own rows: it used to produce no rule and drop its examples silently.

  • test_corpora's rule floor is ratcheted from 38 to 70, with the comment now stating what it guards.

  • A convention change to note: rules/README.md said a retired rule "keeps its ID with a one-line tombstone". The parser can no longer hold one, and no rule has been retired, so the sentence now says a retired rule's decisions entry records the retirement. IDs are still never reused.

  • Comment and docstring fixes: about ten stale block-format phrasings.

Each new refusal row fails on the commit before its fix, and every row the narrower checks already refused still passes.

Where axis 13's lessons went

  • The paragraph-scope trap (Migrate the P rules to the skeleton format (#761, part 4) #777) went to axis 7, General clause vs. adjudicated scope. A sentence argued for one site that reads rule-wide is the site version of vd sweeping in do and mc.
  • Accounting for every fact, every example still running, and a restructuring changing no meaning went to the moving-text paragraph, which already covers splits; the move to a new format is the same kind of restructuring.
  • The interacts: accounting is gone, with the field.

Follow-ups noticed, not done here

Other comments name a skeleton bullet by description rather than by a quoted lead: _pieces.py:808 ("C14's Except"), cases.py:280 ("W4's interpunct Except"), test_script_segment.py:987 ("W1's interpunct Except") and _comma.py:490. The new test reaches only a comment that names the bullet and quotes it. Those four quote nothing, so neither check reads them.

🤖 Generated with Claude Code

derek73 and others added 2 commits October 20, 2026 15:02
Every rule in docs/design/rules/ is in the skeleton format, so the
block format's parser, measures, checks, tests and docs go. A rule
written in the old format is now refused rather than skipped. Also
fixes the four parser minors #768's review deferred: an assertion in a
backtick span, a #-led bullet continuation, a second When or Tolerated
paragraph, and the S10 comment naming an Except bullet by description.
The README's Signal tag list now names every tag, held by a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refuse a block-format rule line inside a field too, and a nested list
in any marker under a bullet, so the docs' refusal lists hold; qualify
the README's refusal list for backtick spans; complete the guard list
in docs/design/AGENTS.md; and fix three descriptions of deleted
behavior (parse_example_line, axis 12, test_corpora's floor).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@derek73 derek73 added docs Documentation fixes and updates design labels Oct 20, 2026
@derek73 derek73 self-assigned this Oct 20, 2026
@codecov

codecov Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.05%. Comparing base (c7831d9) to head (161b88f).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #786   +/-   ##
=======================================
  Coverage   99.05%   99.05%           
=======================================
  Files          47       47           
  Lines        4642     4642           
=======================================
  Hits         4598     4598           
  Misses         44       44           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

derek73 and others added 4 commits October 20, 2026 18:11
Refuse text outside any rule other than a section heading and its one
Background paragraph, and a numbered list at the start of a line, both
of which were read as nothing or as prose; delete Rule.tolerated, which
nothing read; give the bullet-lead and signal-tag checks synthetic rows
that can fail; ratchet test_corpora's rule floor to 70; and correct the
comments and docs the review found stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refuse structure glued to a Background paragraph (a fence, heading,
field label, list item or example; a quote-led wrapped line stays
prose), a section heading with no Background and a section letter
headed twice; make prose_blocks end a Background at a section heading
as parse_rules_doc does; refuse a bare numbered item ending a line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Background check's own arrow pattern ran only on wrapped lines and
ignored backtick spans; ask rules_skeleton.holds_assertion, the rule
side's predicate less its quote-lead half, of every Background line,
the first included. A list item interrupts a Background only numbered
1, as in CommonMark. The docs state the glued check's column-0 scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@derek73
derek73 merged commit 9932592 into master Oct 21, 2026
12 checks passed
@derek73
derek73 deleted the docs/retire-block-rule-format branch October 21, 2026 01:38
derek73 added a commit that referenced this pull request Oct 21, 2026
…retired-ID guard (#761 follow-up) (#788)

* Quote the bullet a citation comment names (#761 follow-up)

Fourteen comments named a rule's Except or Accepted bullet by
description and quoted nothing, so test_doc_citations could not check
them. Each now quotes the bullet (an Except bullet by its whole bold
lead, which bullet_lead_findings holds it to) beside its rule ID, says
which clause the code implements or pins, and points at the decisions
entry. Two named the wrong bullet: test_parser.py's 'M2 Accepted row'
is M2's Except bullet on post-comma text, and test_pieces.py's
'no-comma peel's Accepted limit' is S7's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add the Except-fence check to the design review's axis 3 (#761 follow-up)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refuse a rule file that reuses a retired rule's ID (#761 follow-up)

PR #786 replaced the tombstone with 'A retired rule's decisions entry
records the retirement', which left 'never reused' unenforced. A
retired rule's decisions file now carries a line opening 'Retired:',
and test_no_rule_reuses_a_retired_id refuses a rule file with that ID.
The reader lives in tests/v2/design_docs.py. A misspelled Retired line
and one in a file keyed by no rule ID are findings too, since either
would switch the refusal off silently.

No rule is retired yet, so the real-tree test refuses nothing;
test_a_rule_file_reusing_a_retired_id_is_refused builds a synthetic
decisions file and a rule file reusing its ID and is the negative
control (it fails with the ID check disabled, measured).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address the design-docs review (#761 follow-up)

- test_properties: drop the two maiden-clause pairs said to differ;
  measured, both spellings of each read alike since #601, and M2's
  bullet the new citation quoted accepts no such split.
- _extract: the M3 contrast is a suffix word ('née PhD'), not V, which
  M2 also reads as the maiden name.
- Retired: a numbered '1. Retired:' line is a misspelling too (the new
  row failed against the old pattern first).
- docs/design/AGENTS.md axis 3: two incidents on the record (H1 in
  #779, S3's Msc.Ed. row in #783), not three; a row Then decides may
  belong under Accepted. The skip list names the new guard.
- cases.py: shape 7's U+00B7 half is what the W4 Except decides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

design docs Documentation fixes and updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Give rule blocks a skeleton: Rule, Except, Precedence

1 participant