Skip to content

tooling(devx): the role-word NEW-use message names the pure-relocation case - #15933

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-14659-role-word-update-message-split-case
Sep 5, 2026
Merged

tooling(devx): the role-word NEW-use message names the pure-relocation case#15933
os-project-manager merged 1 commit into
mainfrom
claude/issue-14659-role-word-update-message-split-case

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #14659

Direction is the re-triage ruling on the card (comment 5542584859): option 2 only —
amend the message, do not teach the gate a relocation verdict.
This is a text change.
The gate's accept set, its verdicts and its ratchet strength are unchanged, and
scripts/role-word-baseline.json is neither in scope nor written.

The friction

scripts/role-word-baseline.json is keyed by file PATH, so a split or move re-addresses
occurrences without adding one. The run then reports the moved occurrences as NEW use at
their new paths and a ratchet-DOWN at the old path — and the only remedy offered for
the first half is the one the message marks MAINTAINER-ONLY, whose warning is written for
a genuine new boundary. Nothing told those two cases apart, so the argument that a
relocation is safe was re-derived by hand, by the author and again by the reviewer.

What changed

The NEW-use message gains a closing clause that names the case, its recognition signal and
its evidence. Verbatim, as the author now reads it:

One shape reads as a new boundary and is NOT one: a pure RELOCATION, where a split or
move carried already-baselined occurrences to a new path. You are in that case only if
THIS run also reports baselined file is clean/gone (or an improved count) on the path
they came from, and those rows account for every occurrence reported here. The gate will
not net those two halves against each other, and is not being taught to: the ratchet is
keyed to WHERE the word is, so a file that GAINS an occurrence while a sibling loses an
unrelated one has to stay distinguishable from a move. So the path is still this one and
still MAINTAINER-ONLY — what a relocation changes is only that the maintainer's question
is cheap to answer, so answer it in the diff: re-baselining a pure move touches nothing
but the paths involved, the old row removed and the new rows added summing back to it —
- "OLD_PATH": 2 replaced by + "NEW_PATH_A": 1 and + "NEW_PATH_B": 1. Any OTHER row
in that diff is --update re-baselining something else in the same stroke, which is the
act the marker is on, and what you are showing is then not a relocation.

Two supporting edits, both in service of pinning that text:

  • The two ratchet-DOWN messages become named pure functions, the way grewMessage already
    is. The clause quotes one of them as the author's recognition signal, so the coupling has
    to be reachable from the self-test — a re-typed fixture would prove nothing. Their
    emitted text is byte-identical
    (evidence below).
  • newUseOfferNamesRelocation() joins ratchetRemedyCarriesAuthority() as a predicate on
    the same offer detector, so the convention is "a message that offers the
    baseline-expanding path must also name the one case that path is wrong for", and the
    self-test can prove the predicate discriminates instead of approving every long message.

The one thing the ruling asked to establish first

The ruling said to check whether the gate can already tell — whether it could print the
SPECIFIC form naming the old path and its counts — and warned that adding cross-file
bookkeeping to make that possible is option 1 arriving through the back door.

Measured: it cannot, and must not be made to. newUseMessage(file, count) is pure over one
file. To name the old path and assert the total held, the gate would have to pair NEW-use
rows with vanished baseline rows and sum across files — the refused verdict arriving as
advice, computed in the same place and believed the same way. So the clause is generic:
it states the test, and the author states the answer in the diff where a reviewer can check
it. The clause says the refusal out loud, and the self-test pins that sentence, because the
risk this wording carries is not that it disappears but that a later edit softens it into
an apology for the gate.

Evidence

Premise re-verified before writing. A sandbox tree reproducing the card's split (a
baselined file at 2, both lines moved byte-identically into two new paths under a new
rules/ dir) reports exactly the three rows the card records, with the wording intact:

check-role-word: 3 problem(s)
  - skills/mypack/rules/list-views.md: NEW use of the reserved word ... (1 occurrence(s))
  - skills/mypack/rules/navigation.md: NEW use of the reserved word ... (1 occurrence(s))
  - skills/mypack/SKILL.md: baselined file is clean/gone (was 2) - ratchet DOWN

Verdict-identical. Same sandbox, before and after: 3 problem(s) and exit 1 both times;
the ratchet-DOWN row diffs clean; the NEW-use text is byte-identical up to the splice point.
On the real tree node scripts/check-role-word.mjs exits 0 (236 files, 44 baselined).

Self-test. A new battery of seven, declared in SELF_TEST_BATTERIES with the roster
floor raised 12 to 13. Ablation, each leg committed-then-mutated with the mutation proven on
disk (deleted-text count 0, injected-text count 1) and each restore proven by an empty
git diff HEAD plus a blob hash equal to HEAD's:

leg mutation result
A refusal softened to "reports both halves separately for now, and may net" red — the refusal-wording assertion, by name
B example rows stop summing (2 becomes 1 + 2) red — the diff-shape arithmetic assertion, by name
C clause stops quoting a ratchet-DOWN row the gate prints red — the predicate and the coupling assertion
D clause dropped from the message entirely red — the reachability and predicate assertions

An earlier attempt at leg A was a no-op — its anchor spanned a source line break, so the
replacement matched nothing and the self-test stayed green. That reading is discarded as NOT
MEASURED, not as a pass; the leg above is the redo against the real source spelling.

Gates. node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack derived 31 commands on the final one-file list; results and the
--ran reconciliation are in the report comment on the card. Governed-surface predicate
re-evaluated on the final list against GOVERNED_SURFACES: 0 of 5 rows hit, so this is an
ordinary draft PR.

Changeset: none, skip-changeset. Rule applied: a changeset is required for anything
that publishes, and the label is for a diff that publishes nothing from any released
package. scripts/ is not a workspace package, and lint.yml names this very shape — "this
PR edits a CI-internal script" — as the textbook case for the label.

On the reserved word in this PR. The gate's ROOTS are content/docs and skills, so
scripts/** is outside its population and the words added to the message are not scanned —
proven by the green run above rather than assumed. Nothing was silenced.

Out of scope and not addressed here: #14296 and #14300 are referenced only as background.

🤖 Generated with Claude Code

https://claude.ai/code/session_016vtdMao3dQS9EfQfpyWixd


Generated by Claude Code

…n case

The baseline is path-keyed, so splitting or moving a baselined file
re-addresses its occurrences without adding one: the run reports them as
NEW use at the new paths and a ratchet-DOWN at the old one, and the only
remedy offered for the first half is the MAINTAINER-ONLY path whose
warning is written for a genuine new boundary. Nothing in the message
told those two cases apart, so the argument that a relocation is safe was
re-derived by hand, by the author and again by the reviewer, every time.

The message now names the case, states its recognition signal (a
ratchet-DOWN row in the same run whose counts account for the NEW-use
rows) and the evidence a reviewer checks (a baseline diff touching only
those paths, the old row removed and the new rows summing back to it).

Wording only. The verdicts, the accept set and the ratchet's strength are
unchanged, and the clause says in writing that the gate will not net the
two halves against each other and why: the ratchet is keyed to WHERE the
word is, so a file that gains an occurrence while a sibling loses an
unrelated one has to stay distinguishable from a move. The clause is
generic for the same reason - naming the actual old path and its counts
would mean pairing NEW-use rows with vanished baseline rows and summing
across files, which is that refused verdict arriving as advice.

The two ratchet-DOWN messages become named pure functions, the way
grewMessage already is, so the coupling the clause creates is reachable
from the self-test; their emitted text is byte-identical. A new self-test
battery of seven pins the wording, including the refusal itself and the
example diff's arithmetic.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016vtdMao3dQS9EfQfpyWixd
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 5, 2026
@github-actions github-actions Bot added the size/m label Sep 5, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review September 5, 2026 15:22
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit 7b20ae2 Sep 5, 2026
36 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-14659-role-word-update-message-split-case branch September 5, 2026 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants