Skip to content

docs(topics): commit the skill-listing budget measurement for #1271 - #1286

Closed
kyle-sexton wants to merge 4 commits into
mainfrom
docs/1271-listing-budget-evidence
Closed

docs(topics): commit the skill-listing budget measurement for #1271#1286
kyle-sexton wants to merge 4 commits into
mainfrom
docs/1271-listing-budget-evidence

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Moves the skill-listing budget measurement out of the checkout-local .work/ memory tier into the committed contract tier at docs/topics/context-engineering-rightsizing/design/listing-budget-measurement.md, so later lanes can read it.

The measurement recovers the listing algorithm from the shipped Claude Code v2.1.219 binary rather than inferring it. Three results reframe #1271:

So this is authoring discipline and future-proofing, not a budget fix — and the PR is deliberately written that way. Overstating it would not survive contact with these numbers.

No skill file is modified. This PR is documentation only, one added file.

The re-anchor rewrite is prepared, not applied

§6 carries a complete rewritten frontmatter block for all 15 model-invocable re-anchor skills, measured at −920 characters (−9.2%). It is not applied, because PR #1276 renames the re-anchor plugin to discipline across 45 files and is open and unmerged. Racing a bulk metadata migration against a bulk rename of the same files is the conflict #1271's own "Blocked by" section names. The block ships as a ready-to-apply proposal against the renamed paths.

follow-our-standards goes up by 13 characters under the rewrite. That asymmetry is the honest shape of the change on a short description and is stated as such.

New: the incumbent gate (§8)

Read before proposing anything new. skill-quality:check already does most of this:

  • check-skill.sh:236-304 (check 3) extracts trigger phrases from description and when_to_use combined on both sides of the diff. The exact migration skill metadata: trigger phrases crammed into description; when_to_use is unused and the shared listing budget silently drops them #1271 proposes therefore passes it, while genuinely dropping a phrase fails it. This is the "no discovery was lost" gate, already built. Nothing needs building — the acceptance criterion should cite this check.
  • check-skill.sh:219-234 (check 2) is a per-skill 1,536-character cap, not the shared listing budget its skill description advertises. There is no incumbent check on the shared budget at all — which is how it overflowed 10× unnoticed.
  • Check 2 computes DESC_LEN + WTU_LEN at check-skill.sh:227, omitting the 3-character joiner. Not currently binding (longest entry is 1,161), but wrong at the root.
  • check-skill.sh:440-448 (check 12) warns unless use when appears in either field. The §6 rewrite uses bare quoted phrases with no marker, so all 15 proposed rewrites WARN. Recorded with two honest fixes; the better one edits check-skill.sh and so sequences behind feat(skill-quality): fresh-eyes delegation doctrine + conformance gate (check 21) #1096, which is already editing that file. Nothing in this PR touches check-skill.sh or docs/PLUGIN-PHILOSOPHY.md.

Test plan

Every load-bearing figure was re-derived by script against this tree before committing, rather than transcribed from the source document. §9 is the full record.

  • Reproduced exactly: 181 skills, 130 model-invocable / 51 DMI, 79,653 description chars, 82,533 name+description, 83,270 entry-cost sum, 108,460 and 109,581 for all 181, zero entries over the 1,536 cap, longest 1,161 (docs-hygiene:audit-derivability), 2 skills using when_to_use carrying 211 chars, 3,140 name-only floor, 40,000 / 8,000 budgets, and every term of the 192-entry decomposition.
  • Corrected: find plugins -name SKILL.md is 188, not 187, and 7 nested SKILL.md files are excluded, not 6 — six under vendor/ plus one under references/. Median entry is 594 (description) / 600 (entry text), not 556. Neither correction changes a conclusion.
  • Basis clarified: 83,270 is the entry-cost sum; adding the n − 1 separators the measurement's own formula specifies gives 83,399. Both now appear on their stated basis.
  • Trigger preservation on the §6 proposal: run with the repo's own extractor at plugins/skill-quality/scripts/skill-frontmatter.sh:102, not a reimplementation — 122 of 122 phrases preserved, 0 lost.
  • Check 12 against the §6 proposal: 0 of 15 pass, 15 WARN. Evaluated against check 12's own predicate.
  • Both documentation quotations in §1.1 were re-fetched from the live pages (code.claude.com/docs/en/skills.md, .../settings.md) and matched character-for-character. A third-party SchemaStore divergence on skillListingMaxDescChars (8000 vs the official and shipped 1536) is recorded so a reader consulting it is not misled.
  • Carried without independent reproduction, and labelled as such in §9: the 111,578-character total for the installed population (the entry count decomposition was re-verified term by term); the §3 live-listing figures, which come from a hand transcription of a system prompt no script can read and remain an upper bound, not a measurement; and the §5 per-source spans, which are detector-dependent — an independently written detector agrees on the totals (2.4% vs 2.5%) but moves individual rows by up to 10%.
  • markdownlint-cli2: 0 errors. typos: clean. Scanned for machine-specific absolute paths and usernames per the topic-docs redaction bar: none.

Related

No linked issue — this PR deliberately closes nothing. It lands the evidence half of #1271 only; the metadata migration that issue asks for is blocked behind #1276, so #1271 stays open until that ships.

Refs #1271 — the reconciliation, the levers, and the incumbent-gate findings are posted as a comment there: #1271 (comment)

Blocked-on for the skill edits, not for this PR: #1276 (re-anchordiscipline rename).

Sequencing: any fix to check 12 sequences behind #1096.

Moves the listing-budget measurement out of the checkout-local .work/ memory
tier into the committed contract tier so later lanes can read it.

The measurement recovers the listing algorithm from the shipped Claude Code
v2.1.219 binary rather than inferring it, and lands three results that reframe
#1271: the when_to_use split costs +3 characters per skill rather than saving
any; the named reduction sources total 2.4% of description mass; and the
130-skill listing is 83,270 characters against a 40,000-character budget at 1M
context, so no metadata edit closes the gap. The work is authoring discipline
and future-proofing, not a budget fix.

Every load-bearing figure was re-derived by script against this tree before
committing, and both documentation quotations were re-fetched from the live
pages. Section 9 records what reproduced, the two corrections (file count
187 -> 188, median entry 556 -> 594), and the three figures carried without
independent reproduction. The section 3 hand-transcription caveat is preserved:
73% dark remains an upper bound, not a measurement.

Section 8 is new and records the incumbent gate: skill-quality:check's check 3
already enforces trigger-phrase preservation across description and when_to_use
combined, which is the acceptance gate #1271 needs and does not need building.
It also records that check 2 is a per-skill cap rather than the shared listing
budget, that it omits the 3-character joiner, and that check 12 warns on all 15
proposed rewrites.

Refs #1271

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 4m 27s —— View job


PR Review: docs(topics): commit the skill-listing budget measurement for #1271

Tasks

  • Load tools
  • Read changed file (docs/topics/context-engineering-rightsizing/design/listing-budget-measurement.md, 670 lines)
  • Review document for accuracy, internal consistency, and project alignment
  • Post findings

Summary

Documentation-only PR: one file added, no skill files touched. The document is structured well and the arithmetic is internally consistent throughout — I verified the key figures. The re-verification record (§9) is exemplary transparency practice. That said, there are three substantive precision issues, one of which understates how much the two detectors diverge on a specific row.


Findings

1. totalChars formula notation in §2 conflates the conditional WTU construction — minor but technically incorrect

Line: listing-budget-measurement.md:186

The formula reads:

totalChars = Σ … [ len(pluginName:skillName) + 4 + min(len(desc + " - " + wtu), 1536) ]

This unconditionally includes " - " + wtu in the entry text, but the binary's entryText is conditional: e.whenToUse ? "${e.description} - ${e.whenToUse}" : e.description. For the 128 of 130 skills without when_to_use, this would overcount entry costs by 3 chars each if taken literally.

The verified totals confirm the author computed this correctly — the formula is just imprecisely written. Suggested fix: use entryText(e) as a shorthand (referencing the definition in §1.2), or add a conditional inline comment.

Fix this →


2. "Up to 10%" detector divergence claim understates the name-restating opener variance

Line: listing-budget-measurement.md:329

The note says: "the individual spans move by up to 10%" between the primary and independent detectors. But the name-restating opener row is 236 (primary) vs what appears to be 169 in the independent detector's numbers — a ~28% difference. The independent detector sequence 169 / 42,224 / 2,222 / 1,820 doesn't uniformly stay within 10% of the primary row values.

The conclusion is still sound — both detectors agree the available reduction is 2.4–2.5% of description mass — but "up to 10%" mischaracterizes the per-row variance for the smallest row. A reader who checks the numbers will see the discrepancy. Suggest replacing "up to 10%" with "up to ~28% on individual rows" (or "the ordering and totals are stable; individual rows vary more widely") to give an accurate picture.

Fix this →


3. "Zeroing the three named sources" framing creates minor tension with the +3 cost note

Lines: listing-budget-measurement.md:334–337

The paragraph reads: "Zeroing every character the three named sources can offer still leaves the listing above 81,000 characters." Three lines earlier, the table marks trigger blocks as "no — relocation, +3 chars each." A reader who arrives at "zeroing" having just read "this source costs +3 chars per skill" will wonder how "zeroing" applies to something that can't be removed. The conclusion is true (it's a valid upper-bound construction), but it works against the table's framing.

One clarifying option: replace "zeroing every character the three named sources can offer" with "even hypothetically removing all characters from the three named sources without adding the +3 joiner cost" — or note explicitly that the 81,000 threshold is a lower-bound on the gap, computed under the most favorable assumption.


4. return that in binary excerpt is an unexplained decompiled variable name

Line: listing-budget-measurement.md:77

if (process.env.SLASH_COMMAND_TOOL_CHAR_BUDGET) return that;

that as the returned value is a minifier/transpiler artifact — likely the parsed integer of the env var. A reader unfamiliar with minified JS may read it as a JavaScript this-alias pattern (a common but different idiom) and be confused. A one-line comment like // 'that' = parsed env var value; decompiled name would surface this.


What's working well

  • §9 verification record is excellent practice — distinguishing "reproduced," "corrected," and "carried without independent reproduction" with honest verdicts is exactly the right standard for a measurement document.
  • The +13 delta for follow-our-standards (§6 table) is honestly shown rather than suppressed — this is a meaningful signal that the structural split has a cost.
  • §7 must-not-flag list is the most durable part of this PR. Items 1, 2, 5, and 6 (sibling routing, Actions enumerations, domain vocabulary) would all be silently harmed by an automated compression pass, and having them enumerated in the contract tier is the right call.
  • §8 check 2 / check 12 findings are correctly sequenced against feat(skill-quality): fresh-eyes delegation doctrine + conformance gate (check 21) #1096 and clearly distinguish "wrong at the root" (check 2's missing joiner) from "blocking but fixable" (check 12's use when predicate).
  • "Upper bound" framing for §3 is correctly reasoned: a missed transcription inflates the drop count, so 73% is an upper bound. That's transparent and accurate.
  • skillOverrides caveat (§7 item 8) saves a future reader from chasing a setting that does not apply to plugin skills.

Verdict

Approve with nits. The conclusions are correct and the document is far more carefully verified than most design docs. The three substantive findings are precision issues — the arithmetic is right, the wording isn't. Issues 1 and 2 are worth fixing before this merges since readers will be doing math against these numbers. Issues 3 and 4 are light clarifications.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 55d0ecd461

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +118 to +121
no usage history is dropped **first**. This is per-machine state, so *which* descriptions survive
differs between machines and between users of the same plugin. A newly published skill is
invisible-by-description on every machine until someone invokes it — which is hard to do if they
cannot see what it does.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Qualify the claim that every new skill is hidden

A zero usage score only gives a new skill the lowest priority; it does not guarantee that its description is dropped. When the enabled listing is under budget, when residual space remains, or when the new entry is cheaper than other zero-priority entries, its description can survive—the skip-and-continue behavior described immediately below explicitly permits the last case. The current absolute claim is therefore misleading for users who install only part of the marketplace or configure a larger budget.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# Finding Classification Evidence Reacted
1 Qualify the claim that every new skill is hidden VALID — fixed in d0af6701 Confirmed against the document's own facts. Fact 7 (L123-126) explicitly permits survival: "It walks the droppable set in descending priority and skips any entry whose description does not fit, continuing to consider cheaper entries. So a small low-priority description can survive while a large higher-priority one is dropped." Fact 6 only establishes drop ordering ("A skill with no usage history is dropped first"), not a guaranteed drop. The budget also scales with context (L92-94) and skillListingBudgetFraction is consumer-settable (L44), so a partial install or larger fraction can overflow nothing at all. 👍

Fixed in d0af6701: the sentence is now conditioned on the enabled listing actually overflowing, and names both escape hatches (an under-budget listing drops nothing; fact 7's skip-and-continue lets a cheap zero-priority entry survive on residual space).

Comment on lines +223 to +224
Consistency check on the model of the algorithm: melodic's name-only floor is 3,140, the granted
descriptions add 20,306, for 23,446 of the 40,000 budget — leaving 16,554 for caveman, codex,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reconcile the retained-description arithmetic

The stated figures cannot produce this consistency check: 79,653 total description characters minus 59,824 withheld leaves 19,829 retained. Relative to the documented name-only floor, 35 full entries can add at most those 19,829 characters plus 70 characters for ": ", all 211 when_to_use characters, and two 3-character joiners, totaling 20,116—not 20,306. Thus at least one hand-transcribed figure is wrong, so this paragraph currently does not independently corroborate the observation as claimed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# Finding Classification Evidence Reacted
2 Reconcile the retained-description arithmetic VALID — fixed in d0af6701 Confirmed, and the exact mechanism located. 79,653 − 59,824 = 19,829 retained; 19,829 + 211 + 6 = 20,046 = the sum of entry text over the 35 kept. 20,306 − 20,046 = 260 = 2 × 130 — the per-entry +2 bullet cost was added a second time for all 130 entries, when it is already inside the 3,140 name-only floor. That floor is verifiable exactly: Σlen(name) = 82,533 − 79,653 = 2,880, and 2,880 + 2 × 130 = 3,140. The correct term is 2 × 35 = 70. 👍

Your independent figure of 20,116 is arithmetically correct — reproduced here exactly.

Fixed in d0af6701: 20,306 → 20,116, 23,446 → 23,256, 16,554 → 16,744, and the parenthetical now shows the derivation inline so the check is self-verifying. Confirmed via grep that no other figure in the repo depended on the three old numbers.

One caveat left unchanged because the fix does not touch it: the 20,046 term assumes both when_to_use carriers are among the 35 kept entries. §4 proves that for playwright (1 of 1 kept) but not for playbooks (1 of 3 kept). The exact +260 match shows the original arithmetic already made that assumption, so correcting 260 → 70 introduces no new one.

The "keep a literal Use when: prefix inside when_to_use" fix for the check 12
collision was recorded without its cost. At ~10 characters per skill on top of
the joiner's +3, a fleet-wide migration pays roughly +1,700 characters across
the 130 model-invocable skills, which exceeds most of the 1,906 the named
reduction sources can offer.

That makes the choice of fix decide whether the migration is character-neutral
at all, so the number belongs beside the option rather than only in the issue
discussion.

Refs #1271

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 51s —— View job


PR Review: docs(topics): quantify the check-12 workaround's character cost

Tasks

  • Load tools
  • Read the new commit diff
  • Review the updated document
  • Post findings

What this commit does

Commit dabb779 expands the §8 check-12 discussion to quantify the character cost of the "keep a literal Use when: prefix" workaround option. The core argument: the prefix adds ~10 chars per skill on top of the joiner's +3, so at 130 skills the migration is essentially character-neutral at best and net-negative if this option is chosen — consuming ~1,700 of the 1,906 chars the named reduction sources can offer.

This is a useful addition. The choice of check-12 fix genuinely determines whether the migration's budget headline is positive or negative, and that belongs in the evidence document.


Finding: +1,700 is the combined cost, but "that" references only the prefix cost

Line: listing-budget-measurement.md:617–619

costs ~10 characters per skill **on top of** the joiner's +3. Across 130 model-invocable skills that is
roughly **+1,700 characters**

The 1,700 figure is (10 + 3) × 130 = 1,690 ≈ 1,700 — the combined prefix-plus-joiner cost. But grammatically "that" refers to "~10 characters per skill", which gives 10 × 130 = 1,300, not 1,700. A reader doing the arithmetic inline gets 1,300 and sees a 400-char discrepancy.

The commit message has the same structure and the same ambiguity. Both the conclusion and the comparison against the 1,906-char pool are correct under the combined reading; the sentence just doesn't make its referent explicit.

Suggested fix (one option): replace the two-sentence structure with

"costs ~13 characters per skill total — ~10 for the Use when: prefix, +3 for the joiner. Across 130 model-invocable skills that is roughly +1,700 characters"

Fix this →


Prior review findings — still open, by design

The four findings from the first review pass (formula notation, "up to 10%" variance, "zeroing" framing tension, return that annotation) are unchanged in this commit. The scope is deliberately narrow. For completeness:

Finding Status
§2 totalChars formula: unconditional WTU in the sum open
§5 "up to 10%" understates per-row variance (28% for name-restating opener) open
§5 "zeroing" language tension with +3-costs-more row open
§1.2 return that decompiler artifact unexplained open

On the Codex inline comments

Line 121 (new skill visibility): The Codex note is technically correct — zero usage only guarantees lowest priority, not unconditional drop; the skip-and-continue loop at §1.2 item 7 permits a cheap low-priority entry to survive. However the §3 observation data shows entire plugin populations going name-only in a single 40k session, so "invisible-by-description" is the realistic outcome and the practical claim holds. A parenthetical hedge ("in practice, on any machine approaching the budget ceiling") would be accurate without over-engineering.

Line 224 (retained-description arithmetic): Codex's arithmetic is correct: 19,829 retained description chars + 211 WTU + 6 joiners + 70 (": " × 35) = 20,116, not 20,306. The 190-char gap means at least one hand-transcribed figure (59,824 withheld or 20,306 granted) is slightly off. The document marks this as an "order of magnitude" consistency check and explicitly flags the hand-transcription provenance, so the gap does not invalidate any conclusion — but it does undercut the value of the check. Worth correcting if the source figures can be re-verified against the system prompt record.


Verdict

The new commit's argument is correct and important. One precision fix is worth landing before merge: clarify that the 1,700 figure is the combined joiner-plus-prefix cost, not just the prefix alone. The remaining items are carry-overs from the prior pass.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dabb779830

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -0,0 +1,673 @@
# Skill-listing budget measurement for issue #1271

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Relocate this measurement out of the contract tier

If this PR merges as-is, this file becomes permanent under docs/topics/**, even though the repository's contract-slice lifecycle requires that content in this tier exist only on the task branch and that the final pre-merge commit remove the entire slice (docs/conventions/topic-docs/README.md:290-308). Since the PR is explicitly intended to land this evidence for later work, either promote the measurement to a durable location such as docs/specs/ or keep it branch-local and prune it before merge.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# Finding Classification Evidence Reacted
3 Relocate this measurement out of the contract tier VALID (defer) — escalated, not fixed here The convention text is exactly as cited. docs/conventions/topic-docs/README.md:33 — "| Contract | docs/topics/<slug>/ | Committed on the task branch only; pruned before merge |", and step 4 requires "A final commit prunes the contract slice". But this is a repo-wide policy divergence, not a defect this PR introduces: git ls-tree -r origin/main -- docs/topics already returns 59 committed files on main; step 5's "Enforcement: a required check that the net PR diff contains no path under docs/topics/**" is not implemented in any of the 8 workflows (the only ci.yml match is a path-gating comment); and the suggested promotion target does not exist — git ls-tree -r origin/main -- docs/specs is empty. 👍

Deferring rather than acting, because resolving this correctly is a decision outside a measurement-record PR and outside what this automated pass should decide unilaterally. The three available resolutions are not interchangeable:

  • prune this file before merge — but that defeats the PR's stated purpose (body: "so later lanes can read it"), and its net diff is exactly this one file;
  • promote to docs/specs/ — but that directory does not exist yet, so this PR would be establishing the durable tier as a side effect;
  • amend the convention to match observed practice — the honest option if 59 already-merged files represent the real intent, but that is a convention change, not a docs fix.

Flagging for a maintainer decision. Whichever way it goes, the same call applies to the 59 files already on main, so it wants deciding once at the convention level rather than per-PR.

Comment on lines +617 to +621
- Keep a literal `Use when:` prefix inside `when_to_use`. No code change, but it costs ~10
characters per skill **on top of** the joiner's +3. Across 130 model-invocable skills that is
roughly **+1,700 characters** — which would consume most of the 1,906 the named reduction sources
can offer (§5) and push a fleet-wide migration net-negative on characters. The "close to
character-neutral" framing therefore depends on which fix is chosen; this option is not neutral.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Count only the incremental Use when: cost

For the actual migration from the current frontmatter, Use when: is already present in the 125 detected trigger blocks, so retaining that prefix does not add roughly 10 characters per skill. The existing boundary is " Use when:", while the migrated boundary would be " - Use when:", an increase of only 2 characters per skill (about 250 characters), not roughly 1,700. The larger figure compares against the already-rewritten no-prefix proposal, then incorrectly compares that delta with the 1,906-character savings measured from the current baseline, so the conclusion that this option consumes most savings and makes the migration net-negative is unsupported.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# Finding Classification Evidence Reacted
4 Count only the incremental Use when: cost VALID — fixed in d0af6701 Confirmed. The load-bearing defect is the basis mismatch: the +1,700 is measured against the §6 rewrite (which strips the marker — L384: "No Use when: block, no name-restating opener"), while the 1,906 it was compared against is measured by §5 against the current baseline (L334-336). Measured independently at this branch's HEAD: 123 of 130 model-invocable skills already carry a trigger marker, 100 the literal " Use when:" (Use when 107, Use for 16, Triggers on 0). Your per-skill delta of +2 is correct by construction — baseline Prose. Use when: 'a' vs migrated Prose. + " - " + Use when: 'a' turns one space into three characters. 👍

Your correction is right and the conclusion it overturns was the important part. Baseline-relative the option costs 2 × 123 = 246 characters — 12.9% of the 1,906, not "most", and not net-negative.

Fixed in d0af6701: the bullet now states both bases explicitly rather than silently mixing them — ~10 chars/skill relative to the §6 rewrite (noting §6's measured −920 for re-anchor shrinks accordingly), and ~2 chars/skill (~250 fleet-wide, ~13% of the 1,906) relative to today's baseline. The unsupported "consumes most of the savings / pushes the migration net-negative" claim is removed; the bullet now says the character-neutral framing survives on the baseline basis while noting the option is still not free.

Small delta from your figures: I measure 123 skills where §5 recorded 125, because #1276 renamed re-anchor to discipline after that measurement. The per-skill deltas are constants, so this does not change the conclusion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction to my reply above. I attributed the 123-vs-125 marker-count gap to PR #1276 renaming re-anchor to discipline "after that measurement". That attribution was wrong, and I should have verified it before posting.

This branch's merge-base is 39880e3b and git diff --name-only 39880e3bb7 HEAD -- plugins is emptyplugins/discipline/ does not exist here and plugins/re-anchor/ still does. My count was therefore taken at the document's exact baseline tree, so the rename cannot explain the gap.

The real cause is a detector-definition difference: my frontmatter substring scan (Use when / Use for / Triggers on over parsed description values) is not §5's span detector, which the document already discloses as heuristic at L330-334 — §5 itself reports two independent detectors disagreeing (1,906 vs 1,989).

The verdict on finding 4 and the applied edit are unaffected: the per-skill delta is +2 either way, and 2 × 123 = 246 vs 2 × 125 = 250 does not move the "~13% of the 1,906, not net-negative" conclusion. The committed text says "123 of 130", which is what I measured directly at this branch's tree.

Addresses three Codex P2 review findings on the measurement record.

The consistency check double-counted the per-entry +2 bullet cost across
all 130 entries when it is already inside the 3,140 name-only floor
(82,533 - 79,653 = 2,880; 2,880 + 2x130 = 3,140), so it belongs only to
the 35 granted entries: 20,306 -> 20,116, 23,446 -> 23,256, 16,554 ->
16,744.

Fact 6's invisible-by-description claim was absolute; fact 7's
skip-and-continue and an under-budget listing both permit survival.

The check-12 workaround's +1,700 was measured against the section 6
rewrite but compared with the 1,906 measured from the current baseline.
Against today's baseline the marker already exists (123 of 130
model-invocable skills carry one, 100 the literal "Use when:"), so the
incremental cost is ~2 characters per skill (~250 fleet-wide), which does
not make the migration net-negative.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb58mL6JnfEos5zrhgn4GD
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 4m 36s —— View job


PR Review: docs(topics): correct listing-budget arithmetic and qualify two claims

Tasks

  • Load tools
  • Read the latest commit diff (d0af670)
  • Check which prior findings are resolved
  • Review the updated document
  • Post findings

What this commit does

Commit d0af670 addresses three findings from prior review passes — one Codex arithmetic correction, one Codex claim-qualification, and one structural error from the previous commit's check-12 workaround analysis.


What's resolved

✅ Consistency-check arithmetic fixed (§3)

listing-budget-measurement.md:225–231

The correction from 20,30620,116 is arithmetic-correct and the derivation is now fully traced: 19,829 retained description chars + 211 when_to_use chars + 6 joiner chars (2 × 3) + 70 per-granted-entry-bullet surplus (35 × 2) = 20,116. The resulting 23,256 and 16,744 are consistent. This is the right fix for the Codex P2 finding.

✅ "Invisible-by-description" qualified correctly (§1.2 fact 6)

listing-budget-measurement.md:119–123

The absolute claim is now conditional on overflow ("Wherever the enabled listing overflows the budget…") with the two carve-outs correctly identified: under-budget listings and the skip-and-continue path. The qualification is accurate and consistent with fact 7's description.

✅ Check-12 workaround analysis substantially corrected (§8)

listing-budget-measurement.md:621–629

The prior commit's claim that the Use when: prefix option "would consume most of the 1,906 savings and push a fleet-wide migration net-negative" was comparing a baseline-relative saving (1,906 chars) against a rewrite-relative cost (+1,700), which is an apples-to-oranges comparison. This commit correctly separates the two reference frames:

  • Relative to the §6 rewrite (which drops the marker): keeping the prefix costs +1,700 across 130 skills, so the −920 measured for re-anchor shrinks accordingly. ✓
  • Relative to today's baseline (where "Use when:" already lives in descriptions): moving "… Use when: 'a'" into when_to_use turns the existing space into " - ", a net +2 chars per skill (~+250 fleet-wide, 13% of the 1,906). The migration is not net-negative on this basis. ✓

The underlying reasoning — that 123 of 130 model-invocable skills already carry a trigger marker, 100 the literal "Use when:" — is the load-bearing fact that makes this work, and it's stated clearly.


Remaining open findings from prior passes

The four items below were raised in prior passes and remain unchanged in this commit.

1. §2 formula notation still unconditionally includes wtu

listing-budget-measurement.md:188

totalChars = Σ … [ len(pluginName:skillName) + 4 + min(len(desc + " - " + wtu), 1536) ]

The formula implies wtu is always present, but entryText(e) is conditional (e.whenToUse ? … : e.description). Only 2 of 130 model-invocable skills have when_to_use. A reader who uses this formula to re-derive figures from scratch will over-count entry costs for the other 128. The verified totals confirm the actual computation was conditional; the notation isn't.

Suggested fix: replace len(desc + " - " + wtu) with len(entryText(e)) referencing the §1.2 definition, or add a conditional annotation inline.

Fix this →

2. §5 "up to 10%" understates the per-row variance

listing-budget-measurement.md:332

> An independently written detector … produced 169 / 42,224 / 2,222 / 1,820 — the
> individual spans move by up to 10%, the ordering and the conclusion do not.

The name-restating opener row is 236 (primary) vs 169 (independent) — a ~28% difference. The claim "up to 10%" is inaccurate for that row regardless of how percentage is measured. The conclusion that totals and ordering are stable holds and is the right thing to emphasize; the per-row characterization does not.

Suggested fix: replace "up to 10%" with "up to ~28% on individual rows" (or: "the totals and ordering are stable; individual rows vary more widely") to give an accurate picture without undermining the main point.

Fix this →

3. §5 "zeroing" language still creates tension with the +3 trigger-block row

listing-budget-measurement.md:344

**zeroing every character the three named sources can offer still leaves the listing above 81,000 characters**

The table two paragraphs earlier marks trigger blocks as "no — relocation, +3 chars each". A reader who arrives at "zeroing" having just read "+3 chars each" will not parse "zeroing" as applying to that source. The underlying arithmetic is 83,270 − 236 − 1,670 = 81,364, i.e., it zeros name-restating openers and pure exclusions only — not trigger blocks — but "every character the three named sources can offer" reads as encompassing all three rows with a "yes" or "mostly not" in the availability column. A parenthetical clarifying that trigger blocks are excluded from the "zeroing" calculus because relocation increases cost would resolve the tension.

4. §1.2 return that decompiler artifact still unexplained

listing-budget-measurement.md:77

if (process.env.SLASH_COMMAND_TOOL_CHAR_BUDGET) return that;

that as the return value is a minifier artifact — most likely the parsed integer of the env var. A reader unfamiliar with minified JS will see that as a this-alias pattern (a common JS idiom with a different meaning) and be confused. A one-line comment (// 'that' = parsed env-var integer; decompiled name) would prevent the misread without touching the quoted excerpt.


Process note: contract-slice lifecycle

docs/conventions/topic-docs/README.md:290–308 documents a "Contract-slice lifecycle" that requires a final commit to prune docs/topics/** before merge, with a "required check" that the net PR diff contains no path under docs/topics/**. This file (docs/topics/context-engineering-rightsizing/design/listing-budget-measurement.md) would fail that gate.

No CI enforcement of the prune requirement exists today (checked ci.yml and all other workflow files — none enforce it). So this is a process convention, not a hard CI block. The PR description is explicit that this document is being committed to the "contract tier" intentionally for later lanes to read — that motivation is clear. But if the prune convention is meant to be normative, either the rationale for skipping it should be noted explicitly (e.g., "this document is graduating to permanent evidence rather than being pruned, per the vault edge in §320"), or the file should be moved to docs/specs/ before merge.


Summary

Commit d0af670 makes three correct improvements: the arithmetic in §3 is now right, the skill-visibility absolute claim is properly qualified, and the check-12 workaround analysis now correctly distinguishes baseline-relative from rewrite-relative costs. The "+250 chars fleet-wide, not net-negative" conclusion is substantively important and well-supported.

Four findings from prior passes remain open — the two most worth fixing before merge are the formula notation (#1) and the "up to 10%" variance claim (#2), as readers will be doing arithmetic against these numbers.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d0af670128

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +344 to +346
Concretely: **zeroing every character the three named sources can offer still leaves the listing
above 81,000 characters** (both detectors agree; the independent one lands at 81,410), against a
40,000 budget at 1M context and 8,000 at 200k. The 73% drop rate observed in §3 would not move.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Qualify the claim that the drop rate cannot improve

Raw listing cost remaining above the budget proves only that some descriptions will still be dropped, not that the observed 95 name-only entries remain unchanged. Under the skip-and-continue algorithm described in §1.2, removing characters from descriptions that currently survive can create enough residual capacity for previously dropped cheaper entries, so the 73% rate can move even while the uncapped total remains above 81,000; this conclusion needs the observation session's priority ordering to be recomputed or should be qualified.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# Finding Classification Evidence Reacted
5 Qualify the claim that the drop rate cannot improve VALID — fixed in 46649727 Confirmed: "would not move" asserts an invariance the algorithm does not give, and the document's own fact 7 (L123-126) contradicts it — "skips any entry whose description does not fit, continuing to consider cheaper entries". I bounded the effect rather than recomputing the priority ordering: only characters cut from the 35 granted descriptions free any budget (the 95 already-dropped entries cost zero today), and the cheapest readmittable entry is ai-briefing:generate at 310 description characters — 312 over its name-only floor (name = 20, matching the document's 330 name+description). So 1,906 / 312 = 6.1 entries is the ceiling, and §5's narrower "genuinely available" 807 / 312 = 2.6. 👍

Your correction is right on the absolute, and the quantification shows the section's conclusion survives: the ceiling is ~6 readmitted entries (95 → ~89, 73% → ~68%), realistically 1-2. So "barely move", not "would not move" — and the overflow itself is untouched either way.

Fixed in 46649727: the sentence now says the rate would barely move and states why the pool is too small to shift it, instead of asserting invariance. §3 already flagged the 73% as an upper bound (L218), so this is consistent with the rest of the document.

Comment on lines +559 to +561
5. **`Actions:` enumerations on multi-verb skills.** `work-items:track`, `skill-quality:check`,
`re-anchor:setup`. These tell the model which verb to pass, which is selection information, not
documentation. Cutting them turns a correct invocation into a body read.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove the DMI setup skill from the listing-preservation rule

re-anchor:setup has disable-model-invocation: true and, as §6 already notes, never enters the model's listing; it also exposes only one action. Its Actions: enumeration therefore cannot tell the model which verb to pass, so including it in this must-not-trim category incorrectly preserves text that has no effect on the listing-budget problem.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# Finding Classification Evidence Reacted
6 Remove the DMI setup skill from the listing-preservation rule VALID — fixed in 46649727 Confirmed empirically on both counts, and the document already contradicts itself here. plugins/re-anchor/skills/setup/SKILL.md:6 carries disable-model-invocation: true, and :4 is argument-hint: "check"; its own description says Actions: check (read-only verification, **default and only action**…). The document states the governing rule twice — L130: "skills with disable-model-invocation are filtered out of the listing", and §6 L385: "setup is DMI and never enters the listing". So §7 item 5 was preserving text that is never in the listing and enumerates no choice. 👍

Fixed in 46649727. I scoped it out with an explicit parenthetical rather than deleting the token silently — §7 is a must-not-flag list written for a future sweep, so a bare removal reads as an oversight and invites re-adding.

One correction to the finding's framing, which does not change the verdict: the live path on this branch is plugins/re-anchor/, not plugins/discipline/. PR #1276's rename is on origin/main but not here — this branch's merge-base is 39880e3b and git diff --name-only 39880e3bb7 HEAD -- plugins is empty.

Addresses two further Codex P2 findings raised against d0af670.

Section 5's "the 73% drop rate would not move" asserted an invariance the
algorithm does not give: fact 7's skip-and-continue readmits cheaper
entries when characters are freed. The conclusion survives but the
absolute does not, so the sentence now bounds the effect instead. Only
the 35 granted descriptions free budget, and the full 1,906 buys about
six entries at the cheapest entry's size (ai-briefing:generate, 310
description characters, 312 over its name-only floor).

Section 7 item 5 listed re-anchor:setup as an Actions: enumeration that
must not be trimmed, contradicting the same document's population rule
(DMI skills are filtered out of the listing) and its own section 6 note.
The skill carries disable-model-invocation: true and exposes only check,
so it is scoped out with a note rather than silently dropped.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb58mL6JnfEos5zrhgn4GD
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 5m 21s —— View job


PR Review: docs(topics): qualify the drop-rate claim and scope the Actions rule

Tasks

  • Load tools
  • Read latest commit diff (4664972)
  • Check which prior findings are resolved
  • Review new content for accuracy
  • Post findings

What this commit does

Commit 4664972 makes two targeted changes:

  1. §5 "drop rate would not move" → "would barely move" — replaces the flat claim with a mechanistic explanation: only the 35 granted descriptions can free budget (the 95 already-dropped entries are already name-only); the full 1,906-char pool buys ~6 entries at the cheapest entry size; skip-and-continue readmits that handful at most; the overflow itself is untouched.

  2. §7 item 5 — re-anchor:setup scoped out — removes it from the must-not-trim Actions: enumerations list and adds a parenthetical explaining it is disable-model-invocation and check is its only action. Verified against the skill file: re-anchor:setup has disable-model-invocation: true and carries "Actions: check (read-only verification, default and only action)" in its description frontmatter. The edit is factually correct.


What's resolved

✅ Codex P2: "Qualify the drop-rate cannot improve claim" — addressed

listing-budget-measurement.md:346–351

The new paragraph correctly identifies the two-part mechanical argument: (a) the savings pool only affects the 35 granted entries, not the 95 already-dropped ones, and (b) the pool size translates to a handful of readmissions under skip-and-continue. The overflow is unchanged because 83,270 − 1,906 = 81,364 still far exceeds the 40,000 / 8,000 budgets. The qualification from Codex's finding is now properly incorporated.

✅ Codex P2: "Remove DMI setup skill from listing-preservation rule" — addressed

listing-budget-measurement.md:564–568

re-anchor:setup is correctly excised from the must-not-trim list with a scoping note. Confirmed via the skill file.


New-content accuracy check: 1-char discrepancy worth noting

Line: listing-budget-measurement.md:349

the full 1,906 buys about six entries at the size of the cheapest one
(`ai-briefing:generate`, 310 description characters, 312 over its name-only floor)

§7 item 4 (line 560) reports ai-briefing:generate at 330 name+description chars. The fully-qualified name ai-briefing:generate is 19 characters, implying description = 311 chars, not 310. Under that count, the delta over the name-only floor is 2 + 311 = 313, not 312. The "about six" conclusion is unaffected (1906 / 312 ≈ 6.1 and 1906 / 313 ≈ 6.1), but a reader who checks both figures will see a 1-char contradiction between §5 and §7. Not blocking, but worth correcting for internal consistency.

Fix this →


Remaining open findings from prior passes

Four findings raised in prior review passes remain unchanged in this commit.

1. §2 formula notation still unconditionally includes wtu

Line: listing-budget-measurement.md:188

totalChars = Σ … [ len(pluginName:skillName) + 4 + min(len(desc + " - " + wtu), 1536) ]

The binary's entryText is conditional (e.whenToUse ? … : e.description); only 2 of 130 model-invocable skills carry when_to_use. A reader who uses this formula to re-derive figures will overcount entry costs for the other 128 by 3 chars each. The verified totals confirm the actual computation was correct — the notation is not. Suggested fix: replace len(desc + " - " + wtu) with len(entryText(e)) referencing the §1.2 definition.

Fix this →

2. §5 "up to 10%" understates the per-row variance

Line: listing-budget-measurement.md:332

> An independently written detector … produced 169 / 42,224 / 2,222 / 1,820 — the
> individual spans move by up to 10%, the ordering and the conclusion do not.

The name-restating opener row is 236 (primary) vs 169 (independent) — approximately 28% variance regardless of which is the denominator. The claim "up to 10%" is inaccurate for that row. The conclusion (totals and ordering stable) is sound and remains the right emphasis. Suggested fix: replace "up to 10%" with "up to ~28% on individual rows" or "totals and ordering are stable; individual rows vary more widely."

Fix this →

3. §5 "zeroing" language still creates tension with the +3 trigger-block row

Line: listing-budget-measurement.md:344

**zeroing every character the three named sources can offer still leaves the listing above 81,000**

The table two paragraphs earlier marks trigger blocks as "+3 chars each." A reader arriving at "zeroing" having just read that will not parse "zeroing" as applying to a source that costs more to relocate. The underlying calculation correctly zeros only name-restating (236) and pure exclusions (1,670) — trigger blocks add chars and so "offer" nothing to zero — but "every character the three named sources can offer" reads as encompassing all three non-sibling-routing rows. A brief parenthetical (e.g. "trigger blocks excluded — relocation adds chars, not removes them") would prevent the misread.

4. §1.2 return that decompiler artifact still unexplained

Line: listing-budget-measurement.md:77

if (process.env.SLASH_COMMAND_TOOL_CHAR_BUDGET) return that;

that is a minifier artifact (likely the parsed env-var integer). A reader unfamiliar with minified JS reads it as the common that = this alias pattern, which has a different meaning. A one-line comment — // 'that' = parsed env-var integer; decompiled name — would prevent the misread.


Summary

Commit 4664972 makes two correct improvements. The drop-rate qualification now correctly explains why the rate would barely move (pool size, skip-and-continue mechanics, overflow untouched), and the §7 DMI scoping is factually accurate. One new 1-char arithmetic inconsistency is introduced between the §5 paragraph and the §7 table. The four findings from prior passes remain; the two most worth fixing before merge are still finding #2 ("up to 10%") and finding #1 (formula notation), as readers will be doing arithmetic against these numbers.

Finding Status
New: §5 ai-briefing:generate 310 vs implied 311 description chars open (minor)
§2 totalChars formula: unconditional wtu in sum open
§5 "up to 10%" understates per-row variance (~28% for name-restating opener) open
§5 "zeroing" language tension with +3-cost trigger-block row open
§1.2 return that decompiler artifact unexplained open

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4664972736

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +188 to +189
totalChars = Σ over included entries [ len(pluginName:skillName) + 4 + min(len(desc + " - " + wtu), 1536) ]
+ (n − 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make the joiner conditional in the denominator formula

For every skill without when_to_use—128 of the 130 entries according to §4—this formula charges the three-character " - " joiner even though entryText at line 82 adds it only when that field is populated. Applying the printed formula therefore produces 83,783 separator-inclusive characters rather than the documented 83,399, preventing readers from reproducing the measurement; make the joiner conditional on nonempty when_to_use.

Useful? React with 👍 / 👎.

Comment on lines +605 to +608
dropping a phrase fails it. This is the "no discovery was lost" gate, already built and already
wired. Verified against the §6 proposal using the repo's own extractor
(`plugins/skill-quality/scripts/skill-frontmatter.sh:102`): **122 of 122 phrases preserved.**
Nothing new needs building; the acceptance criterion should cite this check.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not treat the quoted-trigger check as a full discovery gate

When compression removes an unquoted but matchable term from the descriptive prose, check 3 cannot detect the regression because skill-frontmatter.sh:102 extracts only single-quoted substrings. It proves preservation of the enumerated quoted phrases, not that “no discovery was lost”; using it as the sole acceptance gate can therefore approve exactly the vocabulary loss that §5 acknowledges affects matching. Narrow this claim or add a check covering the remaining discovery vocabulary.

Useful? React with 👍 / 👎.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Closing this without merging. Recorded on #1271 in full; the short form:

The document is a contract-tier measurement under docs/topics/, which docs/conventions/topic-docs/README.md:29-34 makes task-branch-only and :290-309 requires pruning before merge. A measurement is verification evidence rather than a durable architectural decision, so it does not graduate to an ADR — under D-7 it folds into #1271, and this PR becomes its citation. The diff stays readable on this closed PR, which is where the per-plugin tables, the must-not-flag list, the worked before/after, and the re-verification record live. That is exactly the pointer discipline the convention asks for: cite the PR, do not restate it.

Two things are routed rather than lost:

The §6 prepared 15-skill rewrite is not being revived. #1271 has since recorded the when_to_use migration as considered and rejected, and this measurement independently agrees: the harness joins the fields with a literal -, so the migration costs +3 characters per skill and zeroing every recoverable character still leaves the listing above 81,000. It is recorded as dead rather than parked so a later session does not rediscover it as available work.

The eight open review threads on this PR are all on the pruned document and are moot with it.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Closed — see the disposition comment above. Folded into #1271 per D-7; gate defects filed as #1404.

kyle-sexton added a commit that referenced this pull request Jul 25, 2026
The execution record described lanes by what sat uncommitted in a
worktree, which is unreachable from a clone and therefore not a
citation. Every lane has since published, so each row now names the PR
or issue that carries its result: L2 as #1343, L3 as #1349 with the
UNBACKED-to-OPINION mapping stated so the claim set does not need to
survive as a separate list, L4 as #1385 closed with recovery on #1403,
L1 as #1286 closed with the fold on #1271 and gate defects on #1404.

The source article is named with its URL rather than left as "a
practitioner article", so a reader auditing this ADR's premises can
reach the thing the digests measured. The digests themselves prune with
the contract slice, which is why the pointer replaces them rather than
supplementing them.
kyle-sexton added a commit that referenced this pull request Jul 26, 2026
…n specifies

Closes #1417

## Summary

`docs/conventions/topic-docs/README.md` specifies a required check that a
merged PR carries no path under the contract-slice dir. The check was
never built, so the convention has been unenforced for its entire life
and 17 slices reached `main` — 6 of them on a single day. The only place
`docs/topics/` reached CI at all was `scripts/docs-only-paths.txt`, as a
docs-only ALLOWLIST entry, which makes such a PR cheaper to merge rather
than blocking it.

Evidence the rule is real and was being enforced by hand: PR #1286 was
closed rather than merged, explicitly because its content was contract
tier under `docs/topics/`.

## The deletion exemption

The convention's own step 4 is a final commit that PRUNES the slice, so a
literal "no path under the contract dir appears in the diff" reading
would red-line the very commit that satisfies it. This gate keys on where
a path LANDS: removals pass, a history-preserving `git mv` out of the
contract dir (step 3's graduation) passes, and only an add, edit, or
rename-into is red-lined.

That requires knowing a path's status, which `--name-only` cannot express,
so the gate reads `--name-status`. Deliberate deviation from the letter of
the convention in service of its intent; the three-dot `base...HEAD` range
is unchanged.

## Existing debt

The 17 pre-existing slices are grandfathered by slug in
`scripts/contract-slice-baseline.txt`, using the same stale-guarded idiom
as `changelog-parity-baseline.txt` and `orphaned-fixtures-baseline.txt`:
`--check` fails on an entry whose slice no longer exists, so an exemption
cannot outlive its debt and a future slice cannot inherit a grandfathered
slug. Graduating and pruning them is tracked separately.

This is why the gate can land now instead of after a 71-file cleanup: it
stops the bleed immediately while each slice graduates on its own PR, by
whoever owns it.

## Verification

The 11-case suite covers the add, pure-deletion, untouched, grandfathered,
new-slug-despite-baseline, graduation-out, rename-into, unresolvable-base,
live-baseline, stale-baseline, and usage paths.

Measured against the four open PRs that carry `docs/topics/` paths, rather
than asserted: #1318, #1252, and #1096 pass on their baseline exemptions;
#1400 fails, correctly, because it adds two slices that are not
pre-existing debt.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 26, 2026
…n specifies (#1429)

Closes #1417

## Problem

`docs/conventions/topic-docs/README.md:305-309` specifies a required
check that the net PR diff
carries no path under the contract-slice dir:

> 5. Enforcement: a required check that the net PR diff (`git diff
--name-only base...head`)
> contains no path under the resolved `<contract_dir>/**` (default
`docs/topics/**`).

It was never built. The convention has been unenforced for its entire
life, and `main` currently
holds **19** contract slices — **8 of them landed on 2026-07-25 alone**,
two of those while this PR
was open.

The only place `docs/topics/` reaches CI today is
`scripts/docs-only-paths.txt:43`, and it is there
as a docs-only **allowlist** entry — which makes a PR confined to it
*cheaper* to merge by skipping
the heavy lanes. Nothing blocks it.

The rule is real and people have been enforcing it by hand: **PR #1286
was closed rather than
merged**, explicitly because its content was contract tier under
`docs/topics/`. That is the cost
of the missing gate — correct behaviour depending on whoever is looking
remembering an unenforced
rule, and 19 directories showing how reliably that scales.

**#1400 merged while this PR was open**, landing two more slices. It was
flagged there, and it is
the sharpest available evidence for the gate: the failure mode is live,
not historical.

## Why the gate permits deletions

The convention's own step 4 is *a final commit that prunes the slice*. A
literal reading — "no path
under the contract dir appears in the diff" — would red-line the very
commit that satisfies the
convention.

So the gate keys on where a path **lands**, not on whether it appears:

| Change | Verdict |
|---|---|
| Delete under `docs/topics/` | pass — this is the prune step |
| `git mv docs/topics/x/PLAN.md docs/adr/…` | pass — this is step 3's
history-preserving graduation |
| Add / edit under `docs/topics/` | **fail** |
| Rename *into* `docs/topics/` | **fail** |
| Diff never touches `docs/topics/` | pass |

Knowing a path's status requires `--name-status`; `--name-only` cannot
express it. That is a
deliberate deviation from the letter of the convention in service of its
intent, called out in the
script header. The three-dot `base...HEAD` range is unchanged, so a
slice `main` gained after a
branch forked stays out of scope and no stale branch is forced to
merge-from-main over someone
else's violation.

## Why this lands before the cleanup

`scripts/contract-slice-baseline.txt` grandfathers the 19 existing
slugs, using the same
stale-guarded idiom as `changelog-parity-baseline.txt` and
`orphaned-fixtures-baseline.txt`:
`--check` fails on an entry whose slice no longer exists, so an
exemption cannot outlive its debt
and a future slice cannot silently inherit a grandfathered slug.

Exemptions are resolved from the **base revision**, not the working
tree, so a PR cannot add a
slice and grandfather its own slug in the same diff. The diff is judged
against the union of the
base and head contract roots, so a PR that relocates `contract_dir`
cannot leave the root it
selected uninspected either. Both bypasses were live in earlier pushes
and were caught in review.

The alternative — prune all 19 first, then gate — is a ~1.3 MB change
requiring a graduation
judgement on each slice by whoever owns it, and it would conflict every
open PR that carries those
paths. Gating first stops the bleed immediately while each slice
graduates on its own PR at its own
pace. The burn-down is #1419; each prune PR drops its own baseline line,
and the stale guard means
the debt cannot be quietly abandoned half-done.

## Verification

`scripts/check-contract-slice-prune.test.sh` — 19 cases, all green: add,
pure deletion, untouched
tree, grandfathered slug, new slug despite a baseline, graduation out,
rename in,
unresolvable-base-ref (fail-closed, exit 2), live baseline entry, stale
baseline entry, usage,
self-grandfathering rejected, a pre-existing entry still exempting,
`contract_dir` resolved from
the concern file, a relocated root moving the gate's scope, a
root-equivalent value exiting 2, a
slug-less baseline surviving `set -u`, both base and head roots policed,
and a grandfathered slice
migrating to a relocated root.

Four review findings were raised across two rounds and all four were
reproduced before being
fixed — two bypasses (self-grandfathering; a relocation leaving its own
root uninspected) and two
fail-open / crash defects (`contract_dir` ignoring the concern file; the
gate aborting under
`set -u` once the baseline empties, which is the exact end state #1419
drives toward). See the
resolved threads; each carries its reproduction and the case that pins
it.

**Measured against the open PRs that actually carry `docs/topics/`
paths, rather than asserted.**
Because the gate reads the baseline from the base revision, these were
run against a base that
already carries it — the post-merge condition:

| PR | Slice | Result |
|---|---|---|
| #1252 | `plugin-audit-port` | passes on baseline exemption |
| #1096 | `fresh-eyes-checkpoint-audit` | passes on baseline exemption |
| #1318 | `context-engineering-claude-5` + the two slices #1400 landed |
fails until rebased onto a `main` carrying the updated baseline |

#1318's failure is an artefact of it predating #1400's merge, not a
defect: its branch adds those
two files relative to its own fork point. Once rebased, they are on
`main` and in the baseline, so
they leave its diff entirely.

The self-grandfathering bypass was verified closed by re-running the
reviewer's own reproduction
against the fix.

Also verified: `shellcheck` clean, `actionlint` clean, `shfmt` clean,
the org comment-hygiene policy
reports zero violations in the new files, and both scripts carry the
executable bit.

## Wiring

`contract-slice-prune-gate` is added to `ci-status`'s `needs:` list.
That aggregate derives its
lane list from the needs graph, and `ci-status` is already a required
status check on the ruleset,
so the new gate becomes required with **no ruleset edit**. Job naming
matches the existing
precedent (`silent-skip-gate`, `orphaned-fixture-gate`,
`changelog-parity-gate`). The self-test runs
unconditionally so a broken gate cannot mask a regression; the PR-diff
step is event-gated.

## Related

- #1419 — graduate and prune the grandfathered slices. Each prune PR
drops its own baseline line,
and this gate's stale guard fails once an entry outlives its slice. Not
closed by this PR. Its
  inventory needs updating to 19 once this lands.
- #1400 — merged while this PR was open, landing two more slices; both
added to the baseline as
debt rather than treated as incoming work. Flagged there before it
merged.
- #1252, #1096 — open PRs carrying grandfathered slice paths; verified
passing on their baseline
  exemptions. #1318 needs a rebase past #1400 (see Verification).
- #1286 — closed by hand for carrying contract-tier content, which is
the manual enforcement this
  gate replaces.
- `docs/conventions/topic-docs/README.md` — the convention specifying
this check as step 5.

---------

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant