@@ -1648,6 +1648,94 @@ to the right group rather than appending to a list.
16481648 Opaque type ` Contents[?] ` erases to ` ArrayBuffer ` at runtime,
16491649 so ` case (_: Contents[?], …) ` matches correctly.
16501650
1651+ ### Diagnostic rule ids — every message names the RULE that produced it
1652+
1653+ ** ` RuleId ` ** (` language/.../RuleId.scala ` ) is a kebab-case, subject-prefixed enum: 303 rules
1654+ covering all 307 diagnostic sites. ` Message.ruleId: Option[RuleId] ` , and ` ruleId ` is a
1655+ ** REQUIRED** parameter on the eight ` Accumulator.add* ` helpers — a new diagnostic does not
1656+ compile until it names its rule. The six calls in ` MessagesTest ` pass ` None ` explicitly.
1657+
1658+ ** It GENERALIZES ` Messages.DeprecationCode ` ; it does not sit beside it.** That object was
1659+ already a threaded kebab-case id registry for deprecations, consumed at ` RiddlLib.scala:970 `
1660+ to build ` SourceEdit ` s. Its 12 codes are reproduced EXACTLY — including ` prompt-statement ` ,
1661+ whose rule was renamed ` DoStatement ` while its code deliberately was not, because renaming a
1662+ rule is a source change and renaming its code is an API break. ** Do not introduce a second
1663+ scheme** (an early draft proposed ` REF001 ` -style ids; it was dropped for exactly this).
1664+
1665+ ** An id names a RULE, not a site.** Four rules are emitted from more than one place on
1666+ purpose — ` ref-wrong-kind ` from BOTH ` ReferenceMap.definitionOf ` and
1667+ ` ResolutionPass.wrongType ` , which is apt given those two paths once disagreed about whether
1668+ to check the kind at all.
1669+
1670+ ** Non-reuse is enforced by CODE, in three parts** (all canary-tested by breaking them):
1671+ ` values ` is generated so codes are checked unique; ` RuleId.retired ` names withdrawn codes and
1672+ no live code may appear there; and a committed ** append-only ledger**
1673+ (` language/src/test/resources/rule-ids.txt ` ) catches what the in-memory checks cannot see — a
1674+ rule DELETED without retiring its code, which is the one at risk of being reused later.
1675+ ** ` RuleId.grandfathered ` is CLOSED** : the 12 legacy codes predate the subject scheme and are
1676+ exempt from it. A new rule that fits no subject needs a SUBJECT added, never an exemption.
1677+
1678+ ** Why the enum at all** : ` DeprecationCode.all ` was a hand-maintained ` Seq ` beside the
1679+ definitions, and TWICE a code was defined but never added to it — ` entity-option-to-intention `
1680+ for months — so "exhaustive" migration reports silently omitted a whole family. ` all ` and the
1681+ mechanical-replacement map are DERIVED now; there is no second list to forget.
1682+
1683+ ** The id renders in the LOGGER, not in ` Message.format ` .** The logger already supplies the
1684+ kind prefix, so output reads ` [error] [use-unused-definition] file(...) ` , rustc's shape.
1685+ ` format ` is what ` CheckMessagesTest ` compares its 13 goldens against, so putting it there
1686+ churned every one of them for a fact those files do not exist to pin. ** ` --no-msg-ids ` **
1687+ (` CommonOptions.showMessageIds ` , default TRUE) restores the previous output exactly.
1688+
1689+ ** ` validate --json ` ** emits one object per diagnostic on stdout (rule, severity, message,
1690+ file, line, col, and context/suggestion when present); ` [] ` when clean, never empty output.
1691+ ** ` validate --fix ` / ` --fix-rule <id> ` ** applies the codemod a rule carries
1692+ (` RuleId.mechanicalFix ` ), through the SAME gate as ` find -replace ` —
1693+ ` FindEditor.applyVerified ` , lifted so there is one copy rather than two. Only PURE SPAN
1694+ replacements qualify: ` type-first-aggregate ` is a reordering and ` shape-keyword ` inserts
1695+ outside the reported span, so both are excluded rather than approximated. See BACKLOG [ 1.16]
1696+ for ` quoted-constant-literal ` , which is genuinely mechanical but needs a COMPUTED replacement
1697+ an ` Option[String] ` cannot express.
1698+
1699+ ** ` FindEditor.fileOfSource ` , never ` Path.of(loc.source.origin) ` .** ` origin ` is the SHORT name
1700+ error messages render, so treating it as a path works only when the cwd happens to be the
1701+ model's own directory — how ` find -replace ` originally shipped, and a bug ` validate --fix `
1702+ nearly reintroduced the same day.
1703+
1704+ ### A bare ` println ` is invisible to a test that redirects stdout
1705+
1706+ ** ` println ` is ` Console.println ` , and ` Console.out ` is a THREAD-LOCAL initialised at class
1707+ load.** ` System.setOut ` therefore does not redirect it, and code printing from inside a
1708+ ` Future ` — on an executor thread — writes to the real stdout regardless. In production the two
1709+ name the same object and nothing is wrong with the output; ** under capture the test reads an
1710+ empty string, which presents as exactly the "command printed nothing" defect** the whole
1711+ ` ValidateSummaryTest ` /` ProductGoesToStdoutTest ` family exists to detect. A false positive from
1712+ the instrument, not the code.
1713+
1714+ ** Emit a command's product with ` System.out.println ` .** ` ValidateCommand.emitJson ` and
1715+ ` DumpCommand.emit ` both do. ` StdStreamCapture ` also wraps ` Console.withOut ` , which closes the
1716+ same-thread half but CANNOT help across threads — the ` System.out ` form is what does.
1717+
1718+ ### Multi-line ` do ` and ` prompt ` (rc.25+)
1719+
1720+ ` do { "a" "b" "c" } ` and ` prompt({ "a" "b" }) ` , with the bare single-string form unchanged.
1721+ The braced shape is ** ` doc_block ` 's** , already RIDDL's spelling for prose, so no new syntax
1722+ idiom was invented. ** The bare form takes EXACTLY ONE string** : ` do "a" "b" ` by juxtaposition
1723+ parses unambiguously (nothing else begins with a quote) but leaves nothing except the next
1724+ keyword to mark where the statement ends.
1725+
1726+ ` DoStatement.what ` and ` PromptValue.prompt ` are ` Seq[LiteralString] ` ; ** ` .text ` ** derives the
1727+ ` \n ` -separated prose riddlg reads. Derived, not stored, so there is no second field to
1728+ disagree — and a single-line ` do ` is a Seq of one rather than a special case.
1729+
1730+ ** Additive at every layer, and that is load-bearing.** A one-line ` do ` prettifies
1731+ byte-identically to before and serializes as a bare JSON string rather than an array, so none
1732+ of the corpus's 190 models move for a feature they do not use. Several lines get ONE PER LINE
1733+ inside braces — the layout every other block uses; squashing them onto one line would be the
1734+ narrow second copy of a block dispatch ` InvariantBlock ` was already caught being.
1735+ ** BAST ` FORMAT_REVISION ` 23** : both now write a SEQUENCE where they wrote a bare string, so a
1736+ revision-22 file's string is read as a COUNT and everything after it derails. The JSON reader
1737+ accepts a string OR an array, so nothing already written stops loading.
1738+
16511739### Total Dispatch — no silent fall-through
16521740
16531741** Reid's standing rule (2026-08-09): "There must be no non-sealed matches — it
0 commit comments