Skip to content

feat(docs-hygiene): add container-position pattern forms to rename-references - #1335

Closed
kyle-sexton wants to merge 3 commits into
mainfrom
fix/1283-rename-references-position-forms
Closed

feat(docs-hygiene): add container-position pattern forms to rename-references#1335
kyle-sexton wants to merge 3 commits into
mainfrom
fix/1283-rename-references-position-forms

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Closes #1283

Summary

Six stale references survived three sweep passes during the re-anchordiscipline plugin rename (#1276). All six were two syntactic shapes rename-references' pattern library did not cover, and the gap is structural rather than incidental: Forms 1–12 all assume the renamed token is a skill or mode identifier.

When a CONTAINER renames — a plugin, a marketplace entry — the token occupies positions none of them reach:

  • Form 1 anchors on /<old>, so it cannot fire on /plugin configure <old>: the slash belongs to plugin, and the token sits downstream in argument position.
  • Form 3 needs a path; none of these are paths.
  • Form 2 (bare token) matches, but cannot separate the container sense from the verb sense at any triage setting when the token is also a verb in the consuming codebase.

Forms 13–15 anchor on syntax that admits only the naming sense:

Form Position Why it is Certain
13 Command argument — /plugin install <old>@mkt, /plugin configure <old> A management verb immediately precedes the token; prose does not say "/plugin configure" before an English verb
14 Document title — # <old>, frontmatter name: The $ anchor: a heading that contains the token may be verb usage, but one that IS the token can only be naming it
15 Possessive / appositive — <old>'s, the <old> plugin English verbs do not take the possessive clitic; the noun-class appositive forces the naming reading

Command-argument hits are flagged as functional breaks, not cosmetic: a reader following /plugin install <old>@marketplace gets plugin-not-found. Four of the six missed references were this shape, including the README's own install block.

Fix

  • context/patterns.md — Forms 13–15, each with the five documented fields the existing forms carry, under a short section explaining why container position is its own class.
  • context/triage.md — records the collision class the English-verb blocklist cannot serve. The blocklist holds tokens that are verbs in general; a token that is a verb in the consuming codebase fails both ways: omitted → every bare-token hit is rated Certain and the sweep proposes rewriting the verb uses; added → every hit lands ambiguous, where the per-match confirmation rule turns a handful of real defects into hundreds of prompts. Extending the blocklist swaps one unusable bucket for another. The remedy is position.
  • context/patterns.md Phase 6 — now requires validating any new form on both axes. Recall alone is not evidence: Form 2 already has perfect recall on every form in the library and is still unusable.
  • context/audit.md — pattern-form breakdown lists 13–15, so an audit report accounts for every form the sweep runs.
  • Two eval cases (7, 8): the container-rename sweep, and the blocklist-extension trap.

Verification

Validated on both axes against the real fixture rather than asserted. Recall came from the removed lines of 930c97a4 (the commit that fixed the references — its deletions are the defect set); precision from the whole pre-fix tree at 930c97a4^.

Pattern Hits on 930c97a4^ under plugins/discipline Real defects among them
Form 2 (bare token) 134 8
Forms 13–15 combined 9 8

The 9th hit is a frozen CHANGELOG-history line, which the existing "Frozen historical records" auto-exclusion already handles. The one defect line Forms 13–15 do not match is the frontmatter description trigger-phrase block — deliberately kept in that rename, so matching it would have been a false positive.

Gates run locally:

  • claude plugin validate . — passes
  • markdownlint-cli2 on all four changed markdown files — 0 errors
  • scripts/check-changelog-parity.sh --check-bump origin/main — passes (docs-hygiene 0.8.6 → 0.9.0 with a matching entry)
  • scripts/check-changelog-parity.sh --check — passes
  • evals.json parses; diff is additive only (25 insertions, 0 deletions to existing cases)

Related

🤖 Generated with Claude Code

https://claude.ai/code/session_01GSXnCLnmzk8y4cKv2y1z9f

…ferences

Six stale references survived three sweep passes during the re-anchor ->
discipline plugin rename. All six were two syntactic shapes the pattern
library did not cover, and the gap is structural rather than incidental:
Forms 1-12 all assume the renamed token is a skill or mode identifier.

When a CONTAINER renames, the token also occupies positions none of them
reach. Form 1 anchors on `/<old>`, so it cannot fire on
`/plugin configure <old>` where the slash belongs to `plugin` and the token
sits downstream in argument position. Form 3 needs a path. Form 2 matches,
but cannot separate the container sense from the verb sense at any triage
setting when the token is also a verb in the consuming codebase.

Forms 13-15 anchor on syntax that admits only the naming sense: a management
verb immediately before the token, a `$`-anchored heading whose entire
content is the token, and the possessive clitic or a noun-class appositive.
Each stays Certain regardless of blocklist membership.

Validated on both axes against the real fixture rather than asserted. Recall
came from the removed lines of the commit that fixed the references;
precision from the whole pre-fix tree. Over that tree, bare-token Form 2
matched 134 lines for 8 real defects; Forms 13-15 matched 9 -- the 8 defects
plus one frozen CHANGELOG-history line the existing rule already excludes.

Phase 6 now requires that both-axis validation for any future form, because
recall alone is not evidence: Form 2 already has perfect recall on every
form in the library and is still unusable.

triage.md records why extending the English-verb blocklist is the wrong
remedy for this collision class. The blocklist holds tokens that are verbs
in general; a token that is a verb in the consuming codebase fails both
ways -- omitted, every hit is rated Certain; added, every hit lands
ambiguous and per-match confirmation turns a handful of defects into
hundreds of prompts.

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

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

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


PR Review — feat(docs-hygiene): container-position pattern forms 13–15

  • Gather PR context
  • Read changed files
  • Evaluate pattern correctness and completeness
  • Check CLAUDE.md / plugin-form compliance
  • Review eval cases
  • Post review findings

Summary

Well-motivated, well-documented, and the measurement story (134 Form-2 hits vs 9 Forms-13–15 hits for 8 real defects) is stated consistently across patterns.md, triage.md, and the CHANGELOG. The blocking items from REVIEW.md (injection, secrets, authorization, atomicity) are not applicable to this documentation-only change. Four minor issues noted below.


Findings

🟡 Nit 1 — Form 14 misses single-quoted YAML frontmatter

patterns.md lines 204–205

^(name|title):\s*"?<old>"?\s*$

"? handles bare values and double-quoted values, but not name: 're-anchor' (single-quoted). Form 7 already carries both quote variants ("[^"]*...[^"]*" and '[^']*...[^']*'). The gap here is small — plugin manifests in this repo use JSON (double-quote), and most SKILL.md frontmatter uses bare values — but a matching fix would be "?'?<old>'?"?\s*$ or two separate patterns, consistent with Form 7's approach.


🟡 Nit 2 — Form 15 possessive misses backtick-wrapped token

patterns.md lines 223–224

\b<old>'s\b

In markdown documentation, the token frequently appears in inline code before the possessive clitic: `re-anchor`'s effective configuration. The backtick preceding re-anchor is a non-word character, so \b before the token still fires, but after re-anchor comes a backtick (non-word) before 's — meaning re-anchor's does not appear as a literal sequence and the pattern fails to match. Form 13 explicitly notes the backtick context and adds `? for that reason; Form 15 is silent on it. Adding `?\b<old>`?'s\b would close the gap.


🟡 Nit 3 — Eval 7 prompt has a stray colon after /re-anchor

evals.json line 80

"prompt": "... every /re-anchor: invocation is updated ..."

The colon after /re-anchor reads ambiguously — it could be parsed as a SKILL.md-style /skill: description invocation format or as a prose artefact. The other eval prompts (e.g. eval 1: "I renamed the skill /verify to /verify-changes") don't carry the colon. Dropping it to every /re-anchor invocation is updated removes the ambiguity.


🟡 Nit 4 — Form 14 uses a capturing group where a non-capturing group suffices

patterns.md line 204

^(name|title):\s*"?<old>"?\s*$

(name|title) captures unnecessarily — the match-group result is unused and could inadvertently trip a consumer that iterates capture-group arrays. (?:name|title) is the correct form for a pure-alternation group in a detection-only pattern. This is consistent with how Form 13 writes its leading group: (^|[^\w/]) is functionally the same concern but there the consumed character matters for the alternation, whereas name|title is only a label.


What looks right

  • Form 13's leading alternation (^|[^\w/]) correctly excludes path contexts (scripts/plugin install x) while allowing line-start, space, and backtick. The note explaining the [^\w/] motivation is clear.
  • Form 14's $ anchor is the right load-bearing mechanism — a heading that IS the token vs. one that merely contains it is a meaningful distinction, and the prose makes this explicit.
  • Form 15's Certain classification is correctly justified: English verbs do not take the possessive clitic and the noun-class appositive forces a naming reading. The "demote to ambiguous for common nouns" caveat is the right safety valve.
  • triage.md placement of the new blocklist-limitation section right after the blocklist definition is correct; it is the natural landing spot for a reader hitting the limitation while deciding whether to extend it.
  • Phase 6 bilateral-validation requirement ("beat Form 2 on precision") is the right lesson from this incident.
  • Version bump 0.8.6 → 0.9.0 is correct for a feature addition.
  • CHANGELOG is accurate and consistently cites the 134-vs-9 fixture measurement.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 15s —— View job


Security Review — PR #1335

  • Gather context and read PR diff
  • Review plugin.json changes
  • Review patterns.md changes (Forms 13–15, Phase 6 evolution rule)
  • Review triage.md changes (verb-collision section)
  • Review audit.md changes (pattern-form breakdown row additions)
  • Review evals.json changes (eval cases 7 and 8)
  • Post findings

No security vulnerabilities found.

All six changed files are documentation and configuration — markdown context files and a JSON eval set. There is no executable code, no GitHub Actions workflow, no credential handling, and no network call introduced by this PR. Detailed findings by surface:

plugin.json — Version bump 0.8.60.9.0. Manifest-only; no code path, no secrets surface.

patterns.md (Forms 13–15 and Phase 6 rule) — Introduces three new ripgrep-compatible regex patterns used by the skill when an AI agent runs /rename-references. The only substitution surface (<old>) is the same one already present in Forms 1–12. audit.md Phase 2 already mandates that <old> be regex-metacharacter-escaped before substitution — the new forms inherit that requirement and do not weaken it. The patterns themselves ((^|[^\w/])/plugins?\s+..., \b<old>@[\w.-]+, ^#{1,6}\s+..., \b<old>'s\b) are structurally safe and carry no backtracking risk in ripgrep's PCRE2 engine.

triage.md — Prose-only addition (the "Verb-sense collision the blocklist cannot serve" section). No executable logic.

audit.md — Three additional rows in the pattern-form breakdown table (Forms 13–15). Read-only documentation of audit output format.

evals.json — Two new eval test cases (ids 7 and 8). JSON with prompt/expectation fields; no code execution surface.

@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: ceeb0b9634

ℹ️ 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 +73 to +76
Extending the blocklist is therefore the WRONG remedy here; it swaps one unusable bucket for
another. The remedy is position: `patterns.md` Forms 13–15 anchor on syntax that only the
container sense can occupy (argument-to-a-management-command, a heading that IS the token, the
possessive clitic), so they stay Certain regardless of blocklist membership.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Suppress Form 2 when using container-position matches

Forms 13–15 cannot provide the claimed remedy while every sweep still runs Form 2. For the exact codebase-specific verb scenario described here, leaving the token off the blocklist makes hundreds of ordinary verb uses Form-2 Certain matches, while adding it still produces hundreds of mandatory ambiguous prompts; the new position matches neither suppress nor replace those results. The top-level skill also says blocklisted tokens are ambiguous regardless of regex position. Define container-sweep scoping or explicit match precedence/deduplication so only the position-anchored occurrences take the Certain path.

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.

CONFIRMED — P1 was correct, and it invalidated this change's own claim. Fixed in 595fff52.

The critique is exact: Forms 13–15 were sold as the remedy for a codebase-specific verb, but nothing suppressed Form 2. Every line the new forms caught was ALSO still a Form 2 hit, so the prompt flood they exist to avoid remained fully intact. They only added a lens. You also correctly caught the internal contradiction — patterns.md Phase 0 says blocklisted tokens go ambiguous regardless of regex position, which contradicts the Certain rating I gave the new forms.

Added container-position precedence in patterns.md Phase 0:

  • Forms 13–15 are strictly more specific than Form 2 — every line they match, Form 2 matches too. That containment is what licenses precedence rather than mere addition.
  • Deduplicate by (file, line) after the sweep, before triage. A line matched by 13–15 is attributed to that form and enters Certain; its Form 2 / chain-form duplicate for the same line is dropped as the same reference seen through a weaker lens, not a second finding.
  • Only lines the container forms did NOT match fall through to the blocklist rule — so Phase 0 is now scoped to what actually reaches Form 2, resolving the contradiction rather than papering over it.
  • audit.md Survey phase applies the dedup before Phase 3, and the report carries a "Form-2 hits superseded by container-position matches" count so the suppression is visible rather than inferred.

Measured effect on the same fixture: the sweep still RUNS Form 2 and still collects its 134 lines. Precedence turns those into 8 Certain container-position findings plus 126 ordinary verb uses that were never candidates, instead of 134 confirmation prompts.

Eval 9 was added to hold the behavior: dedup happens before triage, the bare-token duplicate is dropped, the superseded count is reported, and verb uses are not surfaced as candidates.

Comment on lines +203 to +204
^#{1,6}\s+`?<old>`?\s*$
^(name|title):\s*"?<old>"?\s*$

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 Match single-quoted declared names

When valid YAML frontmatter uses a single-quoted declaration such as name: 're-anchor', Form 14 does not match because this expression only permits optional double quotes. Form 2 will still see the token, but for the verb-collision case motivating this change it takes the unsafe or high-friction bare-token path, so the declared-name position is not actually covered. Accept paired single quotes as well.

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.

CONFIRMED — fixed in 595fff52. Verified against real input rather than taken on assertion:

name: 're-anchor'    old regex: MISS    new: MATCH
name: "re-anchor"    old: MATCH         new: MATCH
name: re-anchor      old: MATCH         new: MATCH

One refinement on the suggested shape. ["']?<old>["']? also matches the mismatched form name: "re-anchor', which is not valid YAML — a pattern that accepts invalid input to catch valid input is a false-positive source. Used a paired alternation instead:

^(name|title):\s*("<old>"|'<old>'|<old>)\s*$

Confirmed name: "re-anchor' is correctly rejected. The quote-pairing requirement is now documented on the form.

Your reasoning also applies to Form 15, which the second reviewer caught independently and you implicitly cover — I fixed that in the same commit. In markdown the token is usually a code span, so the literal <old>'s sequence never appears; it is `<old>` followed by 's. Form 15 therefore missed its own motivating example (Report `re-anchor`'s effective configuration). Now `?\b<old>\b`?'s\b, verified to match both the backticked and bare forms while still rejecting the verb use each skill re-anchors ONE discipline.

…rm 2 flood

The P1 review finding is correct and it invalidated this change's own claim.
Forms 13-15 were added as the remedy for a token that is a verb in the
consuming codebase, but nothing suppressed Form 2 — so every line the new
forms caught was ALSO still a Form 2 hit, and the prompt flood the forms
exist to avoid remained fully intact. The forms only added a lens.

Precedence is what makes the remedy real. Forms 13-15 are strictly more
specific than Form 2: every line they match, Form 2 matches too. The sweep
now deduplicates by (file, line) after collecting and before triage — a
container-position match takes the Certain path and its bare-token duplicate
for that line is dropped as the same reference seen through a weaker lens,
not a second finding. Only lines the container forms did not match fall
through to the blocklist rule.

On the measured fixture the sweep still runs Form 2 and still collects its
134 lines; precedence turns those into 8 Certain container-position findings
plus 126 ordinary verb uses that were never candidates, rather than 134
confirmation prompts. The audit report carries the superseded count so the
suppression is visible rather than inferred.

Two regex gaps from the same review, both verified against real input before
and after:

- Form 14 missed single-quoted YAML (`name: '<old>'`). The fix uses a paired
  alternation rather than `["']?`, which would have matched the invalid
  mismatched form `"<old>'`.
- Form 15 missed the token as inline code before the clitic. In markdown that
  is the common case, not the exception — when the token is a code span the
  literal `<old>'s` sequence never appears, so the form missed its own
  motivating example.

Eval 7's prompt said "every /re-anchor: invocation", where the bare trailing
colon reads as a malformed invocation; it now names the qualified
`/re-anchor:<skill>` form.

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

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 18s —— View job


PR Review — feat(docs-hygiene): container-position pattern forms 13–15

  • Gather PR context
  • Read diff since last review (commit 595fff5)
  • Evaluate Form 2 suppression / precedence mechanism
  • Check remaining nits from prior review
  • Post findings

The fix commit (595fff5) closes all four nits from my first pass and both Codex findings. One small item still open.


What the fix commit correctly addresses

Codex P1 — Form 2 suppression

The Phase 0 rule added to patterns.md lines 252–270 is the right mechanism: run all patterns, deduplicate by (file, line) after the sweep and before triage, attribute each shared line to its 13–15 form, and drop the Form 2 duplicate. audit.md line 37 explicitly gates the dedup before Phase 3 and explains the consequence of reversing the order ("triage will bucket the same reference twice"). The superseded count row in the report template and eval 9 together close the loop — an agent following this doc is forced to surface the suppression rather than letting it be inferred from a mysteriously small Form-2 count.

Codex P2 / Nit 1 — Form 14 single-quoted YAML

patterns.md line 204 now reads:

^(name|title):\s*("<old>"|'<old>'|<old>)\s*$

The paired-quote alternation is the correct approach. The added prose note ("A naive ["']?<old>["']? would match the mismatched form") is the right justification — it preempts the obvious simplification that would reintroduce the bug.

Nit 2 — Form 15 backtick before possessive

patterns.md line 226:

`?\b<old>\b`?'s\b
\bthe `?<old>`? (plugin|skill|marketplace entry|package|module)\b

Tracing `re-anchor`'s: ` (optional backtick) → \b (between ` and r) → re-anchor\b (between r and `) → ` (optional backtick) → 's\b. Fires correctly. The "inline-code wrapping is the common case, not the exception" note makes the motivation self-evident to a future reader who might be tempted to simplify the pattern.

Nit 3 — Eval 7 prompt stale colon

evals.json line 79 now reads every qualified '/re-anchor:<skill>' invocation is already updated — the colon is now explicitly scoped to the SKILL.md invocation format, not ambiguous punctuation.


One remaining item

🟡 Nit 4 — Form 14 still uses a capturing group (unaddressed from first review)

patterns.md line 204:

^(name|title):\s*("<old>"|'<old>'|<old>)\s*$

Both groups are capturing ((name|title) and the quote alternation). The match-group results are unused in a detection-only pattern, and a consumer iterating capture-group arrays would see spurious groups. (?:name|title) and (?:"<old>"|'<old>'|<old>) are the idiomatic form. Form 13's leading (^|[^\w/]) is in the same position but there the consumed character matters for the match — here neither group value is acted on.

This is the only open item. It is low-severity (ripgrep itself ignores unused capture groups and the patterns work correctly as written), but fixing it now keeps the library internally consistent with the non-capturing convention.

Fix this →


What looks right (fix commit)

  • Precedence prose is grounded. "134 lines; precedence is what turns those into 8 Certain container-position findings plus 126 ordinary verb uses that were never candidates" is exactly the level of specificity that prevents the guidance from drifting into vague advice. Future maintainers can re-run the fixture and verify the claim.
  • Eval 9 tests the right invariant. The expectation "Ordinary verb uses are not surfaced as rename candidates or as mandatory confirmation prompts" is the behavioral guarantee the whole deduplication mechanism exists to provide. Testing it directly is correct.
  • CHANGELOG entries are accurate and consistently reference the measured fixture numbers rather than making unverifiable precision claims.
  • The ordering constraint ("Deduplicate BEFORE Phase 3, or triage will bucket the same reference twice") is the most important operational detail in the whole Phase 0 addition, and it is stated explicitly in audit.md where the phase sequence is defined — not buried in patterns.md prose.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 33s —— View job


Security Review — PR #1335

  • Gather PR context
  • Read changed files
  • Evaluate regex patterns for injection or backtracking risk
  • Review eval JSON for unsafe content
  • Check plugin.json for supply-chain or permissions concerns
  • Post findings

No security vulnerabilities found.

All six changed files are documentation and configuration — markdown context files, a JSON eval set, and a manifest version bump. Detailed findings by surface:

plugin.json — Version bump 0.9.0 only. No new capabilities, permissions, remote MCP servers, code execution paths, or dependency changes introduced.

patterns.md (Forms 13–15 and Phase 6 rule) — Introduces three new ripgrep-compatible regex patterns. The sole substitution surface is <old>, and audit.md Phase 2 already mandates that <old> be regex-metacharacter-escaped before substitution; the new forms inherit that requirement and do not weaken it. Backtracking risk is not present: ripgrep uses Rust's linear-time regex engine by default, and the new patterns carry no nested quantifiers or ambiguous repeated alternation that could cause catastrophic behavior even under PCRE2.

triage.md — Prose-only addition. No executable logic.

audit.md — Adds a deduplication rule for container-position precedence. Read-only documentation of agent workflow; no code execution surface.

evals.json — Three new eval test cases (ids 7, 8, 9) with string-valued prompt and expectation fields. No code execution surface; the prompt strings contain only already-public plugin names (re-anchor, discipline).

CHANGELOG.md — Documentation only.

@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: 595fff52de

ℹ️ 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".

1. A `(file, line)` matched by any of Forms 13–15 is attributed to that form and enters the
**Certain** bucket. Drop the Form 2 (and any chain-form) match for that same line — it is
the same reference seen through a weaker lens, not a second finding.
2. Only lines Forms 13–15 did NOT match fall through to Form 2's blocklist rule above.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Scope Form 2 out of container-only sweeps

When <old> is a codebase-specific verb absent from the static blocklist, this still sends every line not matched by Forms 13–15 through Form 2, whose default is Certain. Fresh evidence in the newly added precedence rule is that it suppresses Form 2 only on the same (file, line) as a container match; in the documented re-anchor fixture, the other 126 ordinary verb lines therefore remain Certain auto-apply candidates rather than becoming “never candidates” as claimed. Explicitly filter unmatched Form-2 hits for container-only sweeps instead of merely deduplicating container-hit lines.

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.

CONFIRMED — you are right, and my previous reply overclaimed. Fixed in a5686a1b.

I wrote that precedence turned the 126 lines into "ordinary verb uses that were never candidates." Nothing in the precedence rule made that true. Deduplicating by (file, line) only helps lines a container form ALSO matched — 8 of 134 on the fixture. The remaining 126 fell straight through to Form 2 at its Certain default, exactly as you describe.

Added container-rename mode (patterns.md Phase 0b) rather than a wider dedup, because the residue is not an overlap problem:

  • The sweep declares a MODE at Phase 0, decided by WHAT is being renamed.
  • Container rename (plugin, marketplace entry, package): the renamed thing is a proper name, so a bare-token occurrence is evidence of nothing — as likely the ordinary word as the container. The residue is excluded from Certain regardless of blocklist membership, reported as one aggregate count ("126 bare-token occurrences not in container position, not proposed"), and surfaced only behind an explicit --include-bare-token, then as Ambiguous.
  • Identifier rename (skill, mode, dotted ID): unchanged, every form applies as before.

Mode works where the static blocklist cannot because it is a property of the rename, not of whether anyone remembered to list the token in advance — which was the original complaint in triage.md that this PR set out to answer, and which my first attempt only half-answered.

Fixture with mode + precedence: 8 Certain findings, 126 reported-not-proposed, 0 confirmation prompts, against Form 2 unaided at 134. audit.md runs both rules before Phase 3 and carries both counts. Eval 10 holds the behavior, including that dedup alone is not sufficient.

## Form 14: Document title / declared name

```regex
^#{1,6}\s+`?<old>`?\s*$

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 Restrict title matches to container-owned documents

When the renamed container has a generic name, this pattern promotes every exact heading with that word to Certain even when the heading names a concept or catalog category rather than the container. For example, renaming the testing plugin in this repository also matches README.md:86 (### Testing), the marketplace category heading, while renaming architecture matches the unrelated Architecture section in plugins/miro/README.md; precedence then discards the safer Form-2 classification and these become auto-apply candidates. Constrain this form to known container-owned title files or demote headings without container context.

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.

CONFIRMED — verified both examples against this repository before fixing. Fixed in a5686a1b.

README.md:86                 ### Testing        <- marketplace category heading
plugins/miro/README.md:39    ## Architecture    <- unrelated design section

And both testing and architecture are real plugin names in marketplace.json, so the collision is live, not hypothetical.

Form 14's stated premise — "a heading that IS the token can only be naming it" — holds for a coined or hyphenated name like re-anchor and fails for an ordinary-word one. I generalized from the fixture, which had only the former.

Your point about precedence making it worse is the sharp end of it, and I want to name it explicitly: without precedence a bad title match is one Certain hit competing with a Form 2 hit on the same line. With precedence it DISCARDS the safer Form 2 classification, so the false positive is laundered into an auto-apply candidate. The mechanism I added in the prior commit is what raised the stakes on this form's over-reach.

Scope rule added to Form 14:

  • Certain only when the file is plausibly container-owned — the container's own README/SKILL/manifest, or a path under its directory.
  • A heading match in a file the container does not own is Ambiguous, whatever the token looks like.
  • When the token is a common English word, every title match is demoted to Ambiguous regardless of path.

The false-positives field now carries both verified examples with their line numbers rather than the "effectively none" I originally wrote, which was wrong. Eval 11 uses your testing case directly.

The second P1 is correct: precedence resolved the overlap but left the
residue. Deduplicating by (file, line) only helps lines a container form
ALSO matched — 8 of Form 2's 134 on the measured fixture. The other 126 are
ordinary verb uses no container form touches, so they still fell through to
Form 2's Certain default. My claim that they "were never candidates" was
wrong; nothing in the precedence rule made that true.

The sweep now declares a MODE at Phase 0, decided by what is being renamed.
For a container — a plugin, a marketplace entry, a package — the renamed
thing is a proper name, so a bare-token occurrence is evidence of nothing:
as likely the ordinary word as the container. In that mode the residue is
excluded from Certain regardless of blocklist membership, reported as one
aggregate count rather than per-match prompts, and surfaced only behind an
explicit widen and then as Ambiguous.

Mode succeeds where the blocklist cannot because it is a property of the
rename, not of whether someone remembered to list the token in advance.

The second finding is also real, and worse under precedence. Form 14's
premise — "a heading that IS the token can only be naming it" — holds for a
coined or hyphenated name and fails for an ordinary-word one. Verified
against this repository: renaming a `testing` plugin matches README.md:86
(`### Testing`, a marketplace category heading), and renaming `architecture`
matches plugins/miro/README.md:39 (`## Architecture`, an unrelated design
section). A false Certain there is worse than a plain Form 2 hit, because
precedence discards the safer classification. Title matches are now Certain
only in plausibly container-owned files, and always Ambiguous when the token
is a common English word.

Fixture result with mode and precedence together: 8 Certain findings, 126
reported-not-proposed, 0 confirmation prompts, against Form 2's unaided 134.

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

@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: a5686a1b2d

ℹ️ 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 +269 to +271
1. A `(file, line)` matched by any of Forms 13–15 is attributed to that form and enters the
**Certain** bucket. Drop the Form 2 (and any chain-form) match for that same line — it is
the same reference seen through a weaker lens, not a second finding.

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 Preserve Form 14's demoted classification during precedence

When Form 14 matches a title outside a container-owned document or matches a common-word name, its new scope rule says to demote it to Ambiguous, but this precedence step still unconditionally sends every Form 13–15 match to Certain and drops the safer Form 2/chain classification. Fresh evidence after the earlier title-scope finding is this unconditional instruction, which conflicts directly with the added remedy and can still auto-apply unrelated headings; preserve Form 14's computed bucket while deduplicating.

Useful? React with 👍 / 👎.


```regex
^#{1,6}\s+`?<old>`?\s*$
^(name|title):\s*("<old>"|'<old>'|<old>)\s*$

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 Match JSON manifest name declarations

For a plugin/package container rename where plugin.json or package.json still contains "name": "<old>", this declared-name expression does not match because it only accepts YAML-style name: or title: keys. Container mode then removes the remaining Form 2 occurrence from the candidate set, so the default audit misses the canonical manifest identity even though the scope rule explicitly treats manifests as container-owned; add the JSON declaration shape as a position-anchored match.

Useful? React with 👍 / 👎.

## Form 14: Document title / declared name

```regex
^#{1,6}\s+`?<old>`?\s*$

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 title matching tolerate display capitalization

When a lowercase container name is capitalized in a heading, such as the documented testing### Testing case, this case-sensitive regex does not match, and container mode suppresses the bare-token fallback. Consequently the new Form 14 scope/demotion behavior and eval cannot run for a common Markdown title convention; perform case-insensitive matching for headings while retaining exact matching where identifier declarations require it.

Useful? React with 👍 / 👎.

Comment on lines +301 to +302
3. Surface the residue only if the user explicitly asks to widen (`--include-bare-token`),
and then as Ambiguous, never Certain.

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 Register the bare-token widening flag

When a user follows this instruction and passes --include-bare-token, the top-level router rejects it because SKILL.md and audit-modes.md enumerate only three override flags and explicitly error on unknown flags. That leaves no usable way to inspect the residue that container mode intentionally hides; add this flag to the argument hint, override parser, and mode documentation or describe a widening mechanism the router already accepts.

Useful? React with 👍 / 👎.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Superseded by #1386. Closing rather than updating: main moved to docs-hygiene 0.8.7 while this PR bumps 0.8.6 → 0.9.0, so the branch had to be rebased, which rewrote every SHA. Force-push is blocked by policy in the authoring environment, so the rebased work went to a fresh branch.

#1386 carries this PR's content unchanged plus the rebase resolution — 0.9.0 stacked above main's 0.8.7 entry, both retained. All four review findings from this PR (both P1s and both P2s) are already applied in the commits it carries; the review history here is the record of how they were verified and fixed.

kyle-sexton added a commit that referenced this pull request Jul 26, 2026
…ferences (#1386)

Closes #1283

## Summary

Six stale references survived three sweep passes during the `re-anchor`
→ `discipline` plugin rename (#1276). All six were two syntactic shapes
`rename-references`' pattern library did not cover, and the gap is
structural rather than incidental: **Forms 1–12 all assume the renamed
token is a skill or mode identifier.**

When a CONTAINER renames — a plugin, a marketplace entry — the token
occupies positions none of them reach:

- **Form 1** anchors on `/<old>`, so it cannot fire on `/plugin
configure <old>`: the slash belongs to `plugin`, and the token sits
downstream in argument position.
- **Form 3** needs a path; none of these are paths.
- **Form 2** (bare token) matches, but cannot separate the container
sense from the verb sense at any triage setting when the token is also a
verb in the consuming codebase.

Forms 13–15 anchor on syntax that admits **only** the naming sense:

| Form | Position | Why it is Certain |
|---|---|---|
| 13 | Command argument — `/plugin install <old>@mkt`, `/plugin
configure <old>` | A management verb immediately precedes the token;
prose does not say "/plugin configure" before an English verb |
| 14 | Document title — `# <old>`, frontmatter `name:` | The `$` anchor:
a heading that *contains* the token may be verb usage, but one that IS
the token can only be naming it |
| 15 | Possessive / appositive — `<old>'s`, `the <old> plugin` | English
verbs do not take the possessive clitic; the noun-class appositive
forces the naming reading |

Command-argument hits are flagged as **functional breaks**, not
cosmetic: a reader following `/plugin install <old>@marketplace` gets
`plugin-not-found`. Four of the six missed references were this shape,
including the README's own install block.

## Fix

- `context/patterns.md` — Forms 13–15, each with the five documented
fields the existing forms carry, under a short section explaining why
container position is its own class.
- `context/triage.md` — records the collision class the English-verb
blocklist **cannot** serve. The blocklist holds tokens that are verbs
*in general*; a token that is a verb *in the consuming codebase* fails
both ways: omitted → every bare-token hit is rated Certain and the sweep
proposes rewriting the verb uses; added → every hit lands ambiguous,
where the per-match confirmation rule turns a handful of real defects
into hundreds of prompts. Extending the blocklist swaps one unusable
bucket for another. The remedy is position.
- `context/patterns.md` Phase 6 — now requires validating any new form
on **both** axes. Recall alone is not evidence: Form 2 already has
perfect recall on every form in the library and is still unusable.
- `context/audit.md` — pattern-form breakdown lists 13–15, so an audit
report accounts for every form the sweep runs.
- Seven eval cases (7-13): the container-rename sweep, and the
blocklist-extension trap.

## Verification

Validated on **both** axes against the real fixture rather than
asserted. Recall came from the removed lines of `930c97a4` (the commit
that fixed the references — its deletions *are* the defect set);
precision from the whole pre-fix tree at `930c97a4^`.

| Pattern | Hits on `930c97a4^` under `plugins/discipline` | Real
defects among them |
|---|--:|--:|
| Form 2 (bare token) | **134** | 8 |
| Forms 13–15 combined | **9** | 8 |

The 9th hit is a frozen CHANGELOG-history line, which the existing
"Frozen historical records" auto-exclusion already handles. The one
defect line Forms 13–15 do *not* match is the frontmatter `description`
trigger-phrase block — deliberately kept in that rename, so matching it
would have been a false positive.

Gates run locally:

- `claude plugin validate .` — passes
- `markdownlint-cli2` on all four changed markdown files — 0 errors
- `scripts/check-changelog-parity.sh --check-bump origin/main` — passes
(`docs-hygiene` 0.8.6 → 0.9.0 with a matching entry)
- `scripts/check-changelog-parity.sh --check` — passes
- `evals.json` parses; diff is additive only (25 insertions, 0 deletions
to existing cases)

## Review rounds (this PR supersedes #1335 for branch reasons only —
same work, rebased)

Four findings across two reviewers, all verified against real input
before fixing; two were correct about defects in **this PR's own
claims**:

- **P1 — Form 2 was never suppressed.** The first version added Forms
13–15 as the remedy for a codebase-specific verb, but nothing suppressed
Form 2, so every prompt they were meant to avoid still fired. Fixed by
container-position precedence: dedup by `(file, line)` after the sweep,
before triage.
- **P1 (second round) — precedence left the residue.** Dedup only
resolves lines a container form ALSO matched (8 of 134 on the fixture).
The other 126 still fell through to Form 2's Certain default, so my
claim that they "were never candidates" was false. Fixed by
container-rename mode: mode is decided by WHAT is renamed, so it works
where the static blocklist cannot.
- **P2 — Form 14 over-reached on ordinary-word names.** Verified in this
repo: renaming a `testing` plugin matches `README.md:86` (`###
Testing`), renaming `architecture` matches `plugins/miro/README.md:39`.
Under precedence a false Certain there is worse than a Form 2 hit — it
discards the safer classification. Form 14 is now scoped to
container-owned files and always Ambiguous for common-word tokens.
- **P2 — regex gaps.** Form 14 missed single-quoted YAML; Form 15 missed
the token as inline code before the clitic (its own motivating example).
Both fixed with paired-quote alternation and optional backticks,
verified against real input including the mismatched-quote rejection
case.

Final fixture result: **8 Certain findings, 126 reported-not-proposed, 0
confirmation prompts**, against Form 2 unaided at 134.

## Related

- Refs #1394 — filed from this PR's review: Form 14 is case-sensitive,
so a title-cased heading falls through to Form 2 and gets a LESS safe
classification. Deliberately not fixed here — the safe version needs the
scope rule load-bearing first.
- Refs #1335 — the original PR for this work; closed unmerged because a
force-push was needed after main moved and force-push is blocked in this
environment. Identical content, rebased.
- Refs #1276 — the rename whose six missed references are this PR's
fixture; `930c97a4` is the reference commit

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01GSXnCLnmzk8y4cKv2y1z9f

---------

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.

docs-hygiene(rename-references): pattern library misses command-argument and document-title forms (6 stale refs survived 3 sweeps in #1276)

1 participant