Skip to content

Give every rule a Signal: line and record each rule's size - #745

Merged
derek73 merged 8 commits into
masterfrom
feat/rule-signal-and-size-budget
Oct 19, 2026
Merged

derek73 merged 8 commits into
masterfrom
feat/rule-signal-and-size-budget

Conversation

@derek73

@derek73 derek73 commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Rebased onto master after #743 (squash 90f6d2c) and #744 merged.

Lands the two remedies #743 deferred from the 2026-10-19 critique of docs/design: splitting S2 (#682) and C1 (#728) fixed the file granularity but did nothing to stop statements growing a clause at a time.

What changes

  • A Signal: line in every rule (all 64). It names what in the written name the rule reads: a count, a case contrast, a shape, a neighbor, a position.
    • It is one line, directly above the pointer line. The parser refuses a wrapped one, a misplaced one, a near-miss spelling (signal:, Signals:, **Signal:**, a fullwidth colon), a second one, or one outside a rule block.
    • The parser now also refuses any line a rule block cannot hold (six-space or tab-indented prose) and a misspelled Accepted clause (Accepted :, Accepted --): either would otherwise fall out of the quotable statement or the budget without a word.
    • It is metadata: a citation cannot quote it, and the budget does not count it.
  • Hubs. C1 writes Signal: hub of C3, …, C12. S2 writes its own two signals and then ; hub of S3, …, S10.
    • The new hub_findings check (in test_doc_citations.py) requires every rule a hub names to appear in the hub's interacts: and in one sentence of its statement, which is the map, and runs the other way too: a rule the map names that the Signal: line leaves off is a finding.
    • Deleting C8 from C1's map fails the check; a whole-prose search would have passed, because C1 names C8 elsewhere.
  • S2's map is now complete. It names S3–S10, and S9 is added to its interacts:.
  • Two conventions are written down in the rules README:
    • a condition the rule cites to the rule stating it stays off the line (S10's count is S4's);
    • a gate on the name's size ("exactly three name words", "a word to spare") is part of the signal it gates, not a second signal.
  • Per-rule size budget (mechanisms.md#RECORDED-ROSTERS):
    • tests/v2/rules_budget.json records, per rule: statement words (Rationale included, Accepted clauses not), how many times except/unless appear (a proxy for carve-out clauses), the number of Accepted clauses, and their words.
    • test_rules_budget.py fails on any move, shrinking included, so the record follows a rule down, and asserts the record holds exactly the parsed rules.
    • --record parses the rules first and refuses text the doc tests reject or an empty rule set; the record loader refuses a duplicated key or a non-count value.
    • It is not a cap. Re-record with uv run python -m tests.v2.rules_budget --record and say so in the PR.
    • The median statement is 222 words.
  • One extractor. The prose a citation may quote and the prose the budget counts now come from one function, rules_doc.prose_blocks, moved out of test_doc_citations._statements. I verified its output is identical over the tree except S2, whose statement this PR edits.
  • Statement edits beyond S2's map are citations only:
    • P2 and P6 now cite S6 (the case signal at the suffix slot) where they read it, and list S6 in interacts:.
    • C9 cites T4 (the bare letter as an initial) for the period.
  • Stale tolerated: claims corrected (predating this PR): rules_doc.py said the corpus builder skips a tolerated rule and that the corpus enforces examples at released baselines, neither true since Replace the differential ledgers with a committed snapshot of corpus readings #664. The README's tolerated paragraph now says plainly what such a rule covers, with W3 as the case.
  • Docs: the rules README preamble, docs/design/AGENTS.md (the one-rule-per-piece-of-evidence section and axis 12), the "Lives in" list of mechanisms.md#RECORDED-ROSTERS, and root AGENTS.md's module list.

Split candidates (listed, not split)

These are the rules whose Signal line has to name two signals. grep -l '^ Signal:.* + ' docs/design/rules/*.md lists them.

  • P2 (particles join forward): the particle chain + a particle that is also suffix vocabulary before a family comma
  • P3 (connectives): the connective join + a single-letter connective's case
  • P6 (the trailing orphan particle): its position after a family comma + post-nominals on both sides of a word that is both particle and suffix vocabulary
  • S10 (the trailing roman numeral): the numeral's shape + an initial in front of it + a lower-case numeral in a mixed-case name
  • C8 (the listing form's part after the comma): whether the part holds a name word + a credential opening it
  • C9 (a one-character suffix word inside a comma part): what stands before the letter + a further comma that has already named the suffix
  • T4 (the bare letter as an initial): the letter's period + its case. This one is arguably a single question, how an initial is marked; flagged for the decision rather than settled.
  • S2 (a suffix word ends a name and never opens the string): a hub that also reads two signals of its own, the string-opening position and the written periods

Review

Ran the design-docs-reviewer three times; every finding is fixed:

  • Round 1 (seven findings). The main ones:
    • the map check read the whole prose, not the map;
    • the budget missed Accepted, and …: clauses (R2, R4) and could not see growth inside an Accepted clause;
    • S2's map sent dotted acronyms to S3;
    • several Signal lines hid a second signal.
  • Round 2 (four findings), reviewing round 1's fix commit.
  • Round 3: the round-2 fixes are three Signal-line edits and one regex widening, which I checked myself; "hub" appears in no rule except the two hub lines.
  • /pr-review-toolkit:review-pr (code, tests, comments, silent failures; fixed in c0d7322):
    • silent failures: wrongly indented prose, misspelled Accepted clauses, hubs of, and --record on a bad or empty tree, all now refused;
    • tests: main() end to end, ID ordering, the README's grep recipe, the hub check's choice of map sentence, and each bad Signal: row matched to its own error branch;
    • docs: the signal kinds read as examples rather than a closed list, the hub docstrings allow a hub's own signals, tolerated: sits above Signal:, and the reviewer skip list names the new guards.
    • When narrowing the hub misspelling check I checked it against every case the first form refused (Describe each word by its readings: per-word TOML vocabulary compiled to today's sets (phase 1) #737's lesson); Signal: hub C3, C4 had slipped and is now a pinned row.

Test plan

  • uv run pytest: 21,843 passed, 123 skipped, 3 xfailed (after rebasing onto master)
  • uv run mypy, uv run ruff check
  • Negative controls:
    • removing S9 from S2's map and interacts:, or C8 from C1's map, fails hub_findings;
    • adding words to A2 fails the budget;
    • each grammar near miss has a row that raises.

🤖 Generated with Claude Code

@derek73 derek73 added docs Documentation fixes and updates tests design labels Oct 19, 2026
@derek73 derek73 self-assigned this Oct 19, 2026
Base automatically changed from claude/docs-system-critique-5dfabe to master October 19, 2026 18:46
derek73 and others added 5 commits October 19, 2026 11:48
Each of the 64 rules now states what in the written name it reads on a
one-line `Signal:` above its pointer line; the hubs C1 and S2 write
`Signal: hub of ...`, and a doc test holds each named rule to the hub's
interacts: and to its statement's map. S2's map now names S3-S10 and its
interacts: gains S9, the gap the check was written to catch. Four rules
whose line needs two signals (P2, P3, P6, S10) join them with ` + ` and
are split candidates, not split here.

tests/v2/rules_budget.json records each rule's statement words, its
except/unless count and its Accepted: clauses; test_rules_budget.py fails
when a rule moves against the record, and
`uv run python -m tests.v2.rules_budget --record` re-records. The rule
prose a citation may quote and the prose the budget counts now come from
one function, rules_doc.prose_blocks, moved out of test_doc_citations.py
with its output unchanged over the tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The hub check reads the map: every rule a hub names must sit in ONE
  sentence of its statement, Accepted clauses aside. Deleting C8 from
  C1's map passed the whole-prose check, C1 naming C8 elsewhere.
- A hub may state its own signals before `hub of`; S2 now does (the
  string-opening position and the written periods), and its map sends
  a word whose chunks the vocabulary knows, or an unlisted dotted word,
  to S3 rather than every word with interior periods.
- The budget counts `Accepted, and ...:` clauses (R2, R4 were miscounted)
  and records Accepted-clause words, so growth inside one is seen.
- Signal lines: S10 gains its case contrast, C8, C9 and T4 mark their
  two signals, P3 and C5 reworded; P6 and P2 cite S6 and C9 cites T4
  where they read those rules' signals. Conventions written down: a
  cited condition stays off the line, a size gate is part of its signal.
- A Signal: line outside a rule block raises; the second-Signal branch
  gets a test that reaches it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hub list after any separator but '; ' now raises rather than reading
as a plain signal; C5's line drops the condition it cites to C6; P3's
three-word gate moves to the join it gates; P2 and P6 list the S6 their
statements now cite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Silent failures closed (each now raises, with a test row):
- a line no rule block holds (six-space or tab-indented prose) fell
  out of both the quotable statement and the size budget;
- a misspelled Accepted clause (`Accepted :`, `Accepted --`) counted as
  statement and could carry a hub's map;
- `hubs of`, `Signals:`, `**Signal:**` and a fullwidth colon read as
  prose or as a plain signal;
- `--record` could write a record from text the parser rejects or from
  no rules at all; measure() now parses first and refuses a mismatch,
  read_record() refuses a duplicated key or a non-count value, and
  changes() reports a measure the record holds but no longer measures.

The hub check runs both ways (a rule the map names that the Signal:
line leaves off is a finding), takes the sentence naming the most, and
ends a sentence past a closing bracket or quote. Signal errors name the
rule. New tests: main() end to end against a scratch record, ID order,
the README's split-candidate grep against the parsed rules, and each
bad Signal: row matched to the branch it is named for.

Docs: signal kinds written as examples, not a closed list; the hub
field and test docstrings allow a hub's own signals; tolerated: sits
above the Signal: line; root AGENTS.md says Signal: lines are not
quotable; docs/design/AGENTS.md's skip list names the new guards and
no longer claims the check made S2's omission of S9 a failure by itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@derek73
derek73 force-pushed the feat/rule-signal-and-size-budget branch from ff8e807 to c0d7322 Compare October 19, 2026 18:54
@codecov

codecov Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.05%. Comparing base (90f6d2c) to head (a2be6f2).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #745   +/-   ##
=======================================
  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 derek73 added this to the 2.6 milestone Oct 19, 2026
derek73 and others added 3 commits October 19, 2026 12:10
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rules_doc.py's docstring, its Rule.tolerated comment, the misspelled-
marker error and the grammar test's comment all said build_rules_corpus.py
skips a tolerated rule and that the corpus enforces examples at released
baselines. Neither has been true since #664: the builder harvests a
tolerated rule's examples like any other's, and the snapshot only watches
readings move. The refusal of a near-miss marker now gives its real cost:
the rule would read as a promise, and the line would become quotable
statement prose counted by the budget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@derek73
derek73 merged commit d373055 into master Oct 19, 2026
12 checks passed
@derek73
derek73 deleted the feat/rule-signal-and-size-budget branch October 19, 2026 19:33
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 tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant