Skip to content

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

Merged
kyle-sexton merged 21 commits into
mainfrom
fix/1283-rename-references-position-forms-v2
Jul 26, 2026
Merged

feat(docs-hygiene): add container-position pattern forms to rename-references#1386
kyle-sexton merged 21 commits into
mainfrom
fix/1283-rename-references-position-forms-v2

Conversation

@kyle-sexton

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

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.
  • 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

🤖 Generated with Claude Code

https://claude.ai/code/session_01GSXnCLnmzk8y4cKv2y1z9f

kyle-sexton and others added 3 commits July 25, 2026 15:45
…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
…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
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
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude encountered an error after 0s —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude encountered an error after 0s —— View job


I'll analyze this and get back to you.

@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: 9a618c31dc

ℹ️ 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 thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
Comment thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
Comment thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
Comment thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
Comment thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
@kyle-sexton

Copy link
Copy Markdown
Contributor Author

@codex review

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

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


Security Review

  • Gather context
  • Review PR diff for security issues
  • Analyze changed files
  • Post findings

Scope

This PR is entirely documentation and configuration changes: three new markdown instruction sections (patterns.md Forms 13–15, triage.md, audit.md), a JSON eval file, and a version bump in plugin.json. No compiled or executable code is modified. No GitHub Actions workflows are touched.


Findings

IMPORTANT — Form 13 email-address collision in apply mode

Confidence: HIGH

patterns.md line 178:

\b<old>@[\w.-]+

This standalone alternative matches <old>@anything.domain anywhere in a file. When <old> is a short common word — info, admin, support, contact, hello, dev, or any plugin name that happens to be an email local part — this pattern silently matches email addresses (info@melodicsoftware.com, admin@example.com).

apply.md lines 43–44 show that Certain matches are auto-applied by default, without per-match confirmation:

Default: auto-apply (these forms have empirically near-zero false-positive rate)

The combination is: a Certain-rated pattern that matches email addresses + an apply mode that auto-rewrites Certain hits → contact addresses in documentation get silently renamed to the new plugin identifier, with no confirmation prompt and no false-positive accounting.

The false-positive note in the form's own definition ("prose does not accidentally say /plugin configure before an English verb") is accurate for the first alternative but does not hold for the second, which has no management-verb anchor at all.

Mitigation options: Constrain the second alternative to known marketplace registries (@melodic-software, @github, etc.), require the @ token to be preceded by a known qualifier (backtick, /plugin install, enabledPlugins:), or split the two alternatives so the <old>@<registry> form gets Ambiguous triage rather than Certain.


SUGGESTION — --include-bare-token flag is unreachable

Confidence: HIGH

patterns.md line 302 documents that users can pass --include-bare-token to surface the container-mode residue as Ambiguous. However, SKILL.md lines 48–55 define exactly three accepted flags and state explicitly: "Unknown flags raise an error." --include-bare-token is not in that table, so invoking it as documented produces an error rather than widening the sweep. The only documented escape hatch for inspecting the suppressed residue is inoperable.

This is a functional gap rather than a security vulnerability, but it matters for security auditing: a user who wants to verify no naming-sense references were suppressed cannot do so through the documented interface.


No other security issues found

  • No secrets, credentials, or PII introduced
  • No GitHub Actions or workflow changes
  • No new external dependencies
  • No shell-execution surfaces added (patterns are executed via the Grep tool, not raw shell)
  • The plugin-form isolation rules (${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA}) are unchanged
  • Eval cases in evals.json are read-only test expectations with no runtime execution surface

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

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


PR Review

  • Gather context (PR diff, changed files)
  • Review patterns.md — Forms 13–15, precedence rule, and scope constraints
  • Review triage.md — blocklist-collision class documentation
  • Review audit.md — pattern-form breakdown additions
  • Review evals.json — eval cases 7–11
  • Review plugin.json and CHANGELOG.md
  • Assess Codex findings
  • Post final review

The framing and motivation here are strong — the commit-based fixture validation approach, the measured precision/recall numbers, and the explanation of why blocklist-extension is the wrong remedy are all high-quality. The additions to triage.md and audit.md are well-scoped and coherent. Four issues found; two are correctness gaps that need fixing before merge.


Finding 1 — HIGH: Precedence rule conflicts with Form 14's scope-based demotion and with eval 11

File: patterns.md:269-271

The precedence dedup rule says unconditionally:

A (file, line) matched by any of Forms 13–15 is attributed to that form and enters the Certain bucket.

But Form 14's scope rule (lines 222–228) says title matches outside container-owned files, or with a common-word token, must be demoted to Ambiguous. These two rules contradict: an agent applying the precedence rule literally sends every Form 14 hit to Certain and drops the Form 2 match that would have been Ambiguous.

Eval 11 documents the correct desired behavior explicitly: "Precedence does not discard the safer classification for an out-of-scope title match." That expectation is currently unachievable given what the precedence text says.

The bug path: testing plugin rename → Form 14 matches README.md:86 ### Testing → precedence rule routes it Certain → Form 2's Ambiguous classification is dropped → auto-apply rewrites a category heading in the repository root README. This is exactly the false positive the scope rule was added to prevent.

Fix: Replace "enters the Certain bucket" in the precedence rule with language that preserves the form-specific classification: "is attributed to that form and enters that form's own triage bucket (which may be Ambiguous per its scope rules)."

Fix this →


Finding 2 — HIGH: apply.md Phase 6 completion check breaks under container-rename mode

File: context/apply.md (unchanged in this PR) + patterns.md:292-303

Phase 0b says the bare-token residue (the 126 verb-use lines that no container-position form touches) is "not proposed" and reported as an aggregate. But apply.md Phase 6 reruns the full survey and reaches completion only when "count == 0." After the 8 Certain hits are applied, the 126 residue lines still match <old> — the count is never 0. This triggers Phase 6 Outcome B ("missed some matches, loop back to Phase 4") indefinitely.

Eval 10 covers the audit path correctly, but apply mode has no eval case for post-apply re-sweep under container-rename mode. The invariant that makes Phase 6 terminate (count is zero when no more actionable matches remain) silently breaks when the mode rule excludes residue from the actionable set.

Fix: apply.md Phase 6 needs to define "count" as the actionable count — the result of running Phase 2 and applying both the precedence dedup and the container-rename mode rule — rather than the raw match count. The simplest patch is a one-paragraph note in Phase 6: "In container-rename mode, apply the same Phase 0b rule here: bare-token residue does not count toward the completion check. Outcome A is reached when actionable-count == 0, not when raw match count is zero."

Fix this →


Finding 3 — MEDIUM: --include-bare-token flag undocumented in the flag-parsing contract

File: patterns.md:301-303

Phase 0b surfaces residue "only if the user explicitly asks to widen (--include-bare-token)", but:

  • SKILL.md's argument-hint lists exactly three flags: --include-historical|--include-memory|--include-plan-docs
  • context/audit-modes.md "Override flags" table has those same three entries and states "Unknown flags: error with usage hint, never silently ignore"

A user following Phase 0b's instruction to pass --include-bare-token gets an error. This is the only documented path to inspect suppressed residue.

Fix: Add --include-bare-token to the argument-hint in SKILL.md and to the "Override flags" table in context/audit-modes.md with a description and an APPLY-MODE ONLY or AUDIT-ONLY restriction as appropriate.

Fix this →


Finding 4 — MEDIUM: Form 13's qualified-ID alternative matches email addresses

File: patterns.md:177-178

Form 13's second alternative is \b<old>@[\w.-]+. For a plugin named info, contact, support, or any other common-word name, this pattern matches email addresses (info@melodicsoftware.com). Because Form 13 defaults to Certain, apply mode would auto-rewrite contact addresses in documentation. The first alternative is well-anchored (management verb precedes the token), but this one is not.

The false-positive is bounded — it requires the plugin name to be an email local-part — but it's a silent auto-apply failure mode on a form rated Certain.

Fix: Constrain the @ alternative by requiring at least one dot in the domain part AND anchoring it more tightly to known marketplace identifiers, or by adding a "False-positives" note that mentions the email collision and recommending a --no-email-safe override, or simply by making this sub-pattern Chain-context rather than Certain so it doesn't auto-apply.

Fix this →


Minor notes

  • PR description says "Two eval cases (7, 8)" but the diff adds five (7–11). Not a code defect, just an out-of-date description.
  • Form 14 case-sensitivity gap (low): ^#{1,6}\s+??\s*$ is case-sensitive. A plugin's own README with a title-cased heading (# Re-Anchor for re-anchor) is missed by Form 14 entirely, and if re-anchor is not in the English-verb blocklist, Form 2 would see it as Certain rather than routing it through Form 14's scope rule. The scope rule's protection only applies when Form 14 actually fires — it doesn't fire on title-cased headings. Consider documenting whether the form is intentionally case-sensitive and what the fallback is.
  • evals.json has no apply-mode eval for container-rename: eval 10 covers the audit path. An apply-mode eval confirming Phase 6 terminates cleanly (i.e. the residue does not re-trigger the loop) would be the direct test for Finding 2 above.

Verdict: Findings 1 and 2 are correctness gaps that should be addressed before merge — Finding 1 has a direct conflict with eval 11's own expectations, and Finding 2 makes apply mode non-terminating for container renames. Findings 3 and 4 are important but do not invalidate the design. The overall approach and the measured fixture validation are solid.

@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: 9a618c31dc

ℹ️ 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 thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
Comment thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
Comment thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
Comment thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
Two of these are self-contradictions this change introduced, both caught by
review and both verified against the files before fixing.

The precedence rule said a deduplicated line "enters the Certain bucket"
unconditionally, while Form 14's scope rule demotes an out-of-scope title
match to Ambiguous. Applied literally, precedence would route every Form 14
hit to Certain and drop the Form 2 match that would have been Ambiguous —
using precedence to launder a demotion, producing exactly the false positive
the scope rule was added to prevent, and making eval 11's own expectation
unachievable. A deduplicated line now enters the owning form's bucket AFTER
its scope rules apply. Precedence decides which form owns a line, never how
safely that line is rated.

Container-rename mode also broke apply mode's termination invariant.
apply.md Phase 6 completes at count == 0, but the residue the mode rule
deliberately leaves unrenamed still matches the token forever, so the count
never reaches zero and Outcome B loops indefinitely. Phase 6 now evaluates
the ACTIONABLE count — the survey after precedence and mode — and reports
residue in the hand-off summary rather than looping on it.

The other two are gaps rather than contradictions:

Phase 0b named --include-bare-token as the only documented way to inspect
suppressed residue, but the flag was registered in neither SKILL.md's
argument-hint nor audit-modes.md's override table, whose contract errors on
unknown flags. Registered in both, audit-mode only, always Ambiguous.

Form 13's qualified-id alternative had no management verb anchoring it, so
for a container named info/admin/support it matched contact addresses on a
Certain-rated form — a silent auto-rewrite. The discriminator is structural:
a marketplace slug is kebab-case with no dots, an email domain carries a TLD
dot. The form now accepts [\w-] with a (?![\w.-]) lookahead. Verified
info@melodic-software matches while info@melodicsoftware.com and
info@example.co.uk do not.

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 28s —— View job


PR Review

  • Gather context
  • Read changed files
  • Analyze findings from prior reviews
  • Review patterns.md — Forms 13–15, precedence, and scope rules
  • Review apply.md — Phase 6 termination under container-rename mode
  • Review audit-modes.md and SKILL.md — flag registration
  • Review evals.json and remaining changed files
  • Post final review

The four findings from the two prior review rounds are all resolved in this HEAD (7038a0d). One new issue found below; two minor inconsistencies worth cleaning up.


Prior findings — confirmed resolved

P1 — Form 2 was never suppressed. Fixed. Phase 0b container-rename mode excludes bare-token residue from Certain and reports it as an aggregate rather than per-match prompts.

P1 (second round) — precedence left the residue. Fixed. Phase 0b is correctly documented as a second, independent rule that must run after precedence.

P2 — Form 14 over-reached on ordinary-word names. Fixed. The scope rule demotes to Ambiguous for non-container-owned files or common-word tokens, and the precedence rule now preserves that demotion ("Precedence changes WHICH form owns the line, never the safety of its rating.").

P2 — regex gaps. Fixed. Form 14 now includes single-quoted YAML ('<old>'), and Form 15 now wraps the possessive alternative with optional backticks (`?\b<old>\b`?'s).

Security: email-address collision. Fixed. Form 13's @-form regex uses [\w](?:[\w-]*[\w])?(?![\w.-]) — the negative lookahead on a following dot excludes email domains; verified: info@melodic-software matches, info@melodicsoftware.com does not.

Finding 3 — --include-bare-token not registered. Fixed. Added to SKILL.md argument-hint, audit-modes.md override-flags table (with apply-mode block), and the header line.

Finding 2 — Phase 6 non-termination under container-rename. Fixed. apply.md Phase 6 now defines "count" as the actionable count after precedence and Phase 0b, with explicit prose explaining why raw count can never reach zero.


Finding 1 — HIGH: Form 13 hyphenated-plugin-ID false positive

File: patterns.md:176–178

Two separate false positives, both Certain-rated (auto-apply):

@-form: \b<old>@[\w](?:[\w-]*[\w])?(?![\w.-])

\b fires at the boundary between a hyphen and the first character of <old>. For <old> = guard, the pattern matches guard@melodic-software inside the string context-guard@melodic-software — the match begins at the word boundary before guard. This silently rewrites another plugin's qualified ID.

Command-argument form: ...\s+\?\b`

The trailing \b fires when <old> is followed by a hyphen. For <old> = context, the pattern matches /plugin configure context at the word boundary between t and - inside /plugin configure context-guard. This rewrites an argument referring to a different hyphenated plugin.

The fixture (re-anchordiscipline) did not surface this: re-anchor is hyphenated and not a substring of any other plugin name in the same qualified-ID position, so neither case appeared in the measured defect set. Eval 13 covers the email case but not the hyphenated-ID case.

The Cross-platform note prohibits lookbehinds. A no-lookbehind fix for both alternatives is to require a non-word, non-hyphen character before <old> — e.g., in the @-form: (?:^|[^\w-])<old>@.... For the command-argument form, replacing the trailing \b with (?=[@\s\"']|$)` excludes a following hyphen without a lookbehind.

An eval case covering context-guard@marketplace with <old> = context, and /plugin configure context-guard with <old> = context, would lock in the correct non-match behavior.

Fix this →


Finding 2 — LOW: SKILL.md Phase 6 summary still says "count == 0"

File: SKILL.md:102 and SKILL.md:149

apply.md Phase 6 now correctly says "actionable count", but the inline workflow summary at line 102 (exit when count == 0) and the Gotchas note at line 149 (Re-sweep until count == 0) were not updated. A reader using only SKILL.md would implement the wrong termination condition — the same infinite-loop bug the apply.md fix was meant to prevent.

Fix this →


Minor notes

  • Form 14 case sensitivity (low): ^#{1,6}\s+\?`?\s*$is case-sensitive. A plugin namedre-anchorwhose own README uses# Re-Anchor` (title case) is missed by Form 14 and falls into Form 2 without the scope rule's protection. Whether this is intentional isn't documented; a "Case handling" note clarifying the decision would prevent future contributors from patching it unnecessarily.
  • Eval count in PR description: description says "Two eval cases (7, 8)" but the diff adds seven (7–13). Harmless, but worth correcting so reviewers don't assume coverage is narrower than it is.
  • Eval 12 (apply-mode termination) is a good addition — this directly covers Finding 2 from the prior review; it's the right place for this invariant.

Verdict: Finding 1 is a correctness gap that needs fixing before merge — a Certain auto-apply hit on a different plugin's qualified ID is exactly what the form's management-verb anchor was supposed to prevent, and the fixture didn't catch it. Finding 2 creates a direct inconsistency between SKILL.md's inline summary and apply.md's authoritative definition. Everything from the prior review rounds is cleanly addressed.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

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


Security Review

  • Gather context
  • Review PR diff and changed files
  • Analyze for security vulnerabilities
  • Post findings

Scope

All nine changed files are documentation and configuration: Markdown instruction files (patterns.md, apply.md, audit-modes.md, audit.md, triage.md, SKILL.md, CHANGELOG.md), a JSON eval fixture, and plugin.json. No compiled code, no GitHub Actions workflows, no shell execution surfaces introduced.

Previously reported findings that are resolved in this PR: The prior review identified Form 13's email-address collision (\b<old>@[\w.-]+ could rewrite info@melodicsoftware.com) and the unregistered --include-bare-token flag. Both are confirmed fixed at HEAD (7038a0d):

  • Form 13's @ alternative now uses [\w](?:[\w-]*[\w])?(?![\w.-]) with a TLD-dot lookahead; info@melodicsoftware.com and info@example.co.uk are excluded, verified per commit message.
  • --include-bare-token is registered in SKILL.md's argument-hint and in audit-modes.md's override table, marked AUDIT-MODE ONLY.

Findings

SUGGESTION — --include-bare-token apply-mode rejection is not enforced where apply mode runs

Confidence: HIGH (that the gap exists); LOW (that it would be exploited)

audit-modes.md:11 states --include-bare-token is "AUDIT-MODE ONLY — apply mode rejects both with an explicit error." apply.md is the document an AI reads when executing apply mode (the SKILL.md action router links only apply.md for the <old> to <new> path). apply.md contains a hardcoded block for --include-plan-docs at line 141, but has no equivalent block for --include-bare-token. An AI executing apply mode while reading only apply.md has no instruction to reject the flag.

The blast radius is limited: --include-bare-token is always Ambiguous per its definition, so any residue it surfaces still requires per-match confirmation before an edit fires. This is a defense-in-depth gap (an extra footgun guard is missing), not a direct path to silent auto-rewrites. The fix mirrors the --include-plan-docs block in apply.md.

Fix this →


SUGGESTION — Form 13's @ alternative matches single-label (dotless) hostnames

Confidence: MEDIUM

The email-address fix correctly blocks TLD-bearing addresses by requiring (?![\w.-]) — any domain containing a dot is rejected. However, dotless hostnames such as info@localhost, info@intranet, or info@server-name contain no dot and therefore match. For a container named info, admin, support, or another common word, a documentation line like mailto:info@intranet or an SSH config User info@server-name would match as a Certain-rated qualified ID and be auto-rewritten without confirmation.

The scenario requires the container name to be a common word that also appears as an email local-part, AND the codebase to reference dotless hostnames in documentation — a narrow intersection. The commit message notes the TLD-dot discriminator as intentional, so this is a residual edge case rather than an oversight, but it deserves documentation under "False-positives" in Form 13 so operators with internal hostnames know to demote the @ sub-pattern to Chain-context.

Fix this →


No other security issues found

  • No secrets, credentials, or PII introduced
  • No GitHub Actions or workflow changes
  • No shell execution surfaces added — new patterns are executed via the Grep tool per the explicit cross-platform note in patterns.md
  • No new external dependencies or supply-chain risk
  • Plugin-form isolation rules (${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA}) are unchanged
  • Pre-computed context shell commands in SKILL.md (lines 12–14) are unchanged and use no user input
  • Eval cases in evals.json are read-only test expectations with no runtime execution surface

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

All four findings CONFIRMED and fixed in 7038a0d4

Each verified against the actual files before fixing. Findings 1 and 2 are self-contradictions this PR introduced — the reviews caught defects in my own reasoning, not just gaps.

# Finding Verdict Fix
1 Precedence says Certain unconditionally, contradicting Form 14's demotion CONFIRMED — HIGH Deduplicated line enters the owning form's bucket after its scope rules
2 Apply-mode Phase 6 never terminates under container-rename mode CONFIRMED — HIGH Phase 6 evaluates the actionable count
3 --include-bare-token unregistered; flag contract errors on unknown flags CONFIRMED Registered in SKILL.md + audit-modes.md, audit-only
4 Form 13's @-form matches email addresses CONFIRMED Dot-excluding slug pattern with a lookahead

Finding 1 — the sharpest of the four

You identified it exactly: precedence would be used to launder a demotion. Form 14 demotes an out-of-scope title match to Ambiguous precisely because it might be a category heading rather than the container; precedence then re-promoted it to Certain and dropped the Form 2 match that would have been the safer classification. Net effect: the scope rule I added in the previous commit to prevent auto-applying ### Testing was silently defeated by the precedence rule I added in the commit before it. Eval 11 asserted the correct behavior against a document that made it unreachable.

The rule now reads: a deduplicated line "enters that form's own triage bucket after its scope rules are applied", with an explicit statement that precedence decides WHICH form owns a line, never how safely it is rated.

Finding 2 — the invariant I broke without noticing

Container-rename mode leaves the residue unrenamed by design, but those lines still match <old> forever. apply.md Phase 6 completes at count == 0, so the count could never reach zero and Outcome B would loop indefinitely. I added the mode rule to patterns.md and never checked what it did to apply mode's termination condition.

Phase 6 now defines "count" as the actionable count — the survey result after both precedence and mode — with the reasoning stated inline so the invariant is not re-broken. Residue is reported in the Phase 7 hand-off summary as the same aggregate the audit reports. Eval 12 holds it.

Finding 3

Verified: grep -c 'include-bare-token' returned 0 in both SKILL.md and audit-modes.md. A user following Phase 0b's own instruction would hit the unknown-flag error. Registered in the argument-hint, added to the override table as a fourth flag, marked AUDIT-MODE ONLY and always Ambiguous — widening the report does not change that bare-token position carries no signal for a container rename.

Finding 4

Confirmed against real input, and the fix is structural rather than a caveat. A marketplace slug is kebab-case with no dots; an email domain always carries a TLD dot. So the @-form accepts [\w-] with a negative lookahead:

\b<old>@[\w](?:[\w-]*[\w])?(?![\w.-])

Verified on real strings:

/plugin install info@melodic-software      MATCH   (real qualified id)
"info@melodic-software": true              MATCH   (enabledPlugins key)
Contact info@melodicsoftware.com           no match
mail to info@example.co.uk                 no match

Eval 13 holds it. I did not take the "add a False-positives note" option alone — a note does not stop an auto-apply on a Certain-rated form.

Minor notes

  • PR description said "two eval cases" — stale, carried over from the first revision. There are now 13 (7–13). Corrected in the body.
  • Form 14 case-sensitivity — real gap, deliberately not fixed here. Case-insensitive matching would make # Testing match a testing rename in every document, which is the over-reach Finding 1's scope rule exists to bound; the safe version needs the scope rule to be load-bearing first. Left as-is rather than half-fixed, and worth its own issue if you want it.
  • Apply-mode eval for container mode — added as eval 12, per your suggestion. It is the direct test for Finding 2.

Gates after the fix: claude plugin validate . passes, markdownlint-cli2 0 errors across 7 files, changelog parity passes, evals.json parses with 13 cases.

@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: 7038a0d4bf

ℹ️ 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 thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
Comment thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
…xamples

plugin-gate failed on the previous commit:

  plugins/docs-hygiene/skills/rename-references/context/patterns.md:
  reusable skill content must not require publisher-specific runtime
  identifiers

The email-collision fix illustrated the dot-exclusion rule with this
marketplace's own name. `rename-references` ships to consumers with their
own marketplaces, so a portable skill must not encode the publisher's — the
gate is correct, and the same slip reached the two eval cases and the
CHANGELOG entry, which the failure did not name.

Every example now uses a neutral `acme-tools` / `acmetools.com` pair. The
distinction being demonstrated is unchanged and re-verified against real
input: `info@acme-tools` matches, `info@acmetools.com` and
`info@example.co.uk` do not.

`node scripts/validate-plugin-contracts.mjs` — the gate that failed — now
passes locally: 43 setup skills and 2097 plugin files checked, exit 0.

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 6m 39s —— View job


PR Review

  • Gather context (diff, changed files, prior review rounds)
  • Verify all prior findings against HEAD (d35e5b9)
  • Review latest commit — publisher-specific name fix
  • Check Form 13 hyphenated-plugin-ID issue from second reviewer
  • Check SKILL.md "count == 0" inconsistency
  • Check apply.md --include-bare-token rejection gap
  • New issue — PCRE2 lookahead dependency
  • Post findings

What the latest commit (d35e5b9) fixed

The plugin-gate failure from the prior commit was correctly diagnosed: rename-references ships to consumers with their own marketplace names, so any example referencing melodic-software is a portability violation. Every instance (patterns.md line 199, eval 7 expected-output, eval 13 prompt + expected-output, CHANGELOG) is now updated to the neutral acme-tools/acmetools.com pair. The semantic distinction being demonstrated is unchanged and the new examples are correct — info@acme-tools (slug, no dot) matches; info@acmetools.com (TLD dot) does not.


Prior-round findings confirmed resolved

Four findings from the first review round and all security findings marked resolved in the author's 7038a0d response are confirmed fixed at HEAD:

  • Precedence rule now says "enters that form's own triage bucket after its scope rules are applied" — Form 14 demotion is preserved ✓
  • Phase 6 defines "count" as the actionable count; Outcome A fires at actionable-count == 0
  • --include-bare-token registered in SKILL.md argument-hint and audit-modes.md override table, AUDIT-MODE ONLY ✓
  • Form 13 @-form uses (?![\w.-]) lookahead to exclude TLD-bearing addresses ✓

Finding 1 — HIGH: Form 13 hyphenated-plugin-ID false positives (from second review round, still open)

File: patterns.md:177–178

These were identified in the second reviewer's Finding 1 (HIGH) and were not addressed in either 7038a0d or d35e5b9.

@-form: \b<old>@[\w](?:[\w-]*[\w])?(?![\w.-])

\b fires at a hyphen-to-word-char boundary. For <old> = guard, the pattern matches guard@acme-tools inside context-guard@acme-tools — the word boundary before guard exists because the hyphen is a non-word character. This auto-rewrites another plugin's qualified ID. Fix: replace \b<old>@ with (?:^|[^\w-])<old>@. No lookahead needed; [^\w-] excludes both word chars and hyphens as the preceding character.

Command-argument form: (^|[^\w/])/plugins?\s+(...)\s+\?\b`

The trailing \b fires before a hyphen (non-word character). For <old> = context, /plugin configure context\b matches inside /plugin configure context-guard at the word boundary between t and -. The auto-applied edit rewrites an argument that refers to a different plugin. Fix: replace the trailing \b with a more precise terminator. Because the plugin-name argument in these commands is always followed by @, whitespace, a closing backtick/quote, or end of line — never a hyphen that continues the name — the simplest no-lookahead option is <old>[^-\w] (consuming one post-token character), with the understanding that detection patterns only need to identify the line, not the exact span for replacement.

An eval case covering context-guard@acme-tools with <old> = context (expected: no-match), and /plugin configure context-guard with <old> = context (expected: no-match), would lock in the non-match behavior.

Fix this →


Finding 2 — MEDIUM: Form 13's (?![\w.-]) lookahead may not execute without PCRE2

File: patterns.md:178, patterns.md:346

Form 13's @-form uses a negative lookahead (?![\w.-]) — the only lookahead in the entire pattern library. ripgrep's default regex engine (Rust regex crate) does not support lookaheads; PCRE2 (rg --pcre2) is required. If the Grep tool does not enable PCRE2, the lookahead silently fails: either the pattern errors out, or the lookahead is treated as an empty match and info@acmetools.com is accepted as a qualified ID — exactly the silent auto-rewrite the fix was intended to prevent.

The cross-platform note at line 346 says "ripgrep-compatible (PCRE2 subset)" and prohibits lookbehinds, but does not explicitly state that lookaheads are supported or that the Grep tool enables PCRE2. The shell fallback path (git grep -nE, rg without --pcre2) does not support lookaheads in either case.

If the Grep tool already guarantees PCRE2 (which would explain the "PCRE2 subset" language), the note should say so explicitly — both to document the constraint and to ensure the shell fallback is updated to add --pcre2. If it does not, the pattern should be reformulated without a lookahead. For this specific case, an alternative that avoids a lookahead is to limit the @-form to contexts where the qualified ID appears in a known syntactic frame (quoted string, code span, or preceded by a known key like enabledPlugins:), accepting slightly reduced recall in exchange for correctness on all regex engines.

Fix this →


Finding 3 — LOW: SKILL.md Phase 6 summary still says "count == 0" (from second review round, still open)

File: SKILL.md:102, SKILL.md:149

apply.md Phase 6 was correctly updated to define "count" as the actionable count after precedence and Phase 0b. But two references in SKILL.md were not updated:

  • Line 102: exit when count == 0
  • Line 149: Re-sweep until count == 0

A reader or AI using only SKILL.md (without reading apply.md fully) would implement the same infinite-loop behavior the apply.md fix was meant to prevent. The SKILL.md PR diff shows only +1/-1 (the --include-bare-token argument-hint addition), confirming neither of these lines was touched.

Fix this →


Finding 4 — LOW: apply.md has no --include-bare-token apply-mode rejection block (from security review, still open)

File: context/apply.md:141, context/audit-modes.md:11

audit-modes.md line 11 states both --include-plan-docs and --include-bare-token are apply-mode rejected. But apply.md's Special cases section only has the hardcoded rejection block for --include-plan-docs. An AI executing apply mode while reading apply.md has no instruction to reject --include-bare-token. Blast radius is limited (the flag forces Ambiguous, requiring per-match confirmation for every residue hit), but the gap leaves the enforcement asymmetric with what audit-modes.md promises. The fix mirrors the existing --include-plan-docs block.

Fix this →


Minor notes


Verdict: Finding 1 (HIGH) is the same hyphenated-plugin-ID gap the second reviewer identified — it was correctly diagnosed, a fix was described, but neither 7038a0d nor d35e5b9 applied it. It is a Certain-rated auto-apply path to rewriting an unrelated plugin's qualified ID, which is the class of error the management-verb anchor was specifically added to prevent. Finding 2 (MEDIUM) is a new structural issue with the email-collision fix itself — the lookahead that implements it may not execute in the environments the cross-platform note describes. Findings 3 and 4 are low-severity documentation inconsistencies that create discrepancies between what SKILL.md and apply.md say.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

plugin-gate failure — fixed in d35e5b9a

Not a flake. The exact error:

plugins/docs-hygiene/skills/rename-references/context/patterns.md:
reusable skill content must not require publisher-specific runtime identifiers

My email-collision fix in 7038a0d4 illustrated the dot-exclusion rule using this marketplace's own name. rename-references ships to consumers with their own marketplaces, so a portable skill must not encode the publisher's — the gate is right.

The failure named only patterns.md, but the same slip had reached two eval cases and the CHANGELOG entry. Swept every file this PR touches rather than fixing only the named one; all examples now use a neutral acme-tools / acmetools.com pair. The only remaining melodic-software in the diff is plugin.json's publisher field, which is pre-existing on main and legitimate.

The distinction being demonstrated is unchanged, re-verified against real input:

/plugin install info@acme-tools     MATCH
Contact info@acmetools.com          no match
mail info@example.co.uk             no match

Verified by running the failing gate locally rather than inferring from a green re-run: node scripts/validate-plugin-contracts.mjs"Plugin contracts validated: 43 setup skills and 2097 plugin files checked", exit 0.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

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


Security Review

  • Gather context
  • Read changed files (patterns.md, apply.md, audit-modes.md, SKILL.md, triage.md, evals.json, audit.md)
  • Analyze for security vulnerabilities
  • Post findings

Scope

All nine changed files are documentation and configuration: Markdown instruction files, a JSON eval fixture, and plugin.json. No compiled code, no GitHub Actions workflows, no shell execution surfaces introduced. The review focuses on the semantic attack surface of patterns that drive auto-edit decisions.


Previously reported findings confirmed resolved at this HEAD (d35e5b9):

The prior round's email-collision fix (\b<old>@[\w.-]+[\w](?:[\w-]*[\w])?(?![\w.-])) and the --include-bare-token flag registration are present in this tree. The plugin-gate failure (publisher-specific example text) was the only thing addressed in d35e5b9.


Finding 1 — IMPORTANT | Confidence: HIGH

Form 13's \b boundary fires at hyphen positions in hyphenated plugin IDs, enabling auto-rewrite of a different plugin's qualified ID

File: patterns.md:177–178

(^|[^\w/])/plugins?\s+(install|uninstall|configure|enable|disable|update|add|remove)\s+`?<old>\b
\b<old>@[\w](?:[\w-]*[\w])?(?![\w.-])

\b in the Rust regex and PCRE2 engines treats a hyphen as a non-word character, so a word boundary exists between a hyphen and the adjacent letter. This creates two false-positive paths, both Certain-rated and therefore eligible for auto-apply without per-match confirmation:

Command-argument form — trailing \b: For <old> = context, /plugin configure context-guard matches because \b fires at the boundary between t and -. Phase 5 applies the edit, renaming an argument that was referring to a different hyphenated plugin.

Qualified-ID form — leading \b: For <old> = guard, context-guard@acme-tools matches because \b fires at the boundary between - and g. The entire qualified ID is rated Certain and proposed for auto-rewrite.

Neither case appeared in the re-anchordiscipline fixture because re-anchor is itself hyphenated and was not a substring of any other plugin name at the same qualified-ID position. The fixture therefore gave this no coverage, and the measured precision numbers do not bound this failure mode.

This finding was raised in the 20:06 PR review (Finding 1, HIGH) and was not addressed by d35e5b9, which only changed example text.

Fix options (no lookbehinds — per the cross-platform constraint):

  • Command-argument form: replace trailing \b with (?=[@\s\"']|$)` — a hyphenated continuation cannot immediately follow with one of those characters.
  • Qualified-ID form: replace \b<old>@ with (?:^|[^\w-])<old>@ — requires the character before <old> to be neither a word character nor a hyphen.

Fix this →


Finding 2 — SUGGESTION | Confidence: MEDIUM

Form 13's @-form lookahead (?![\w.-]) may not execute under the Grep tool's default ripgrep invocation

File: patterns.md:178

The email-collision fix depends on the negative lookahead (?![\w.-]). The cross-platform note (line 346) describes patterns as "ripgrep-compatible (PCRE2 subset)" and instructs using the Grep tool, but it does not specify that PCRE2 mode must be enabled. Ripgrep's default Rust regex engine does not support lookaheads; they require --pcre2. The same note explicitly avoids lookbehinds for this reason, but Form 13 now introduces a lookahead — which the default engine rejects with the same class of error.

Two failure modes:

  • Regex error (most likely): the Grep tool reports a syntax error and Form 13's @-alternative returns zero hits. old@acme-tools qualified IDs are missed entirely — an undercount, not an overcount.
  • Silent drop of the lookahead (less likely, behavior of some engines): the lookahead is treated as matching everything; info@acmetools.com matches as Certain and is auto-applied.

The email-collision protection introduced in the prior round only works if the lookahead actually executes.

Fix: Either rewrite the boundary without a lookahead, or add an explicit instruction that the @-form must be invoked with --pcre2 (if the Grep tool exposes this). A lookahead-free alternative: require the character after the slug to be a specific closing character class (e.g., end-of-line, whitespace, quote, or backtick) using a character class that does not match ., which achieves the same exclusion without look-around.

Example without lookahead:

\b<old>@[\w](?:[\w-]*[\w])?(?=$|[\s"'`,;)\]])

This requires the match to terminate at a whitespace, punctuation, or end-of-line — positions where an email domain's TLD dot never appears — rather than relying on a negative lookahead to exclude following dots.

Fix this →%20so%20the%20match%20must%20terminate%20at%20whitespace%2C%20punctuation%2C%20or%20end-of-line%20--%20positions%20where%20a%20TLD%20dot%20cannot%20appear%20without%20a%20space%20or%20quote%20first.&repo=melodic-software/claude-code-plugins)


Finding 3 — SUGGESTION | Confidence: HIGH (gap exists); LOW (exploitable impact)

--include-bare-token apply-mode rejection documented in audit-modes.md but not enforced in apply.md

File: context/apply.md:141

audit-modes.md line 11 declares --include-bare-token AUDIT-MODE ONLY and states apply mode rejects both it and --include-plan-docs with an explicit error. apply.md has a hardcoded block for --include-plan-docs (line 141) but no equivalent block for --include-bare-token. An AI reading only apply.md when executing apply mode has no instruction to reject the flag.

Blast radius is limited: --include-bare-token always lands Ambiguous (per-match confirmation required, never Certain), so even if passed in apply mode, it cannot enable silent auto-rewrites. It would, however, generate hundreds of confirmation prompts on the bare-token residue the mode rule deliberately excluded — the behavior the mode rule exists to prevent.

This was flagged in the 20:06 security review and is unaddressed in d35e5b9.

Fix this →


No other security issues found

  • No secrets, credentials, or PII introduced
  • No GitHub Actions or workflow changes
  • No new external dependencies or supply-chain risk
  • Plugin-form isolation rules (${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA}) are unchanged
  • Pre-computed context shell commands in SKILL.md (lines 12–14) are read-only git introspections and take no user input
  • Eval cases in evals.json are read-only test expectations with no runtime execution surface
  • The SKILL.md "count == 0" vs apply.md "actionable count" inconsistency (Finding 2 of the 20:06 PR review) is a correctness gap, not a security issue

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

ℹ️ 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 thread plugins/docs-hygiene/skills/rename-references/context/audit-modes.md Outdated
Comment thread plugins/docs-hygiene/skills/rename-references/context/apply.md
…mmits

Each verified against the files or against real input before fixing. Two of
these were introduced BY the fixes for the earlier round.

Deduplication keyed on (file, line), so a line carrying two independent
references lost one of them: `Use <old> via /plugin install <old>@mkt` has a
bare occurrence and a command-argument occurrence. Because Phase 5 replaces a
single span at a time, the survivor was then reclassified as residue,
excluded by container mode, and the re-sweep declared completion with a live
stale reference in the file — a false completion, not merely a missed
report. A weaker match is now suppressed only when its span is COVERED BY a
more-specific match's span.

The email-collision fix used a negative lookahead. ripgrep's default engine —
the engine this skill instructs — rejects look-around without -P/--pcre2, and
patterns.md's own cross-platform note already bans lookbehinds for the same
class of reason, so the form I added would have been rejected rather than
evaluated. It now consumes a terminator, ([^\w.@-]|$), the same shape Forms 4
and 5 use. Verified equivalent across six cases including bare end-of-line.

The other two are over-promises rather than breakage:

--include-bare-token was advertised for all audit sub-modes, but Orphans
sweeps only Forms 1 and 3 and therefore has no bare-token residue to
surface — the flag silently returned the default result there. Now scoped
explicitly and reported as not-applicable.

The actionable-count rule promised users a residue aggregate, but neither
Phase 7 success template had a field for it and the default hand-off still
said `0 stragglers`. The fix prevented the loop while hiding the number it
committed to. Both templates now carry the count, emitted only under
container-rename mode and only when non-zero.

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 5m 3s —— View job


PR Review

  • Gather context (diff, changed files, prior review rounds)
  • Read patterns.md — Form 13 regex and cross-platform note
  • Read SKILL.md — "count == 0" vs "actionable count"
  • Read apply.md — Phase 6 and special cases
  • Read audit-modes.md — --include-bare-token enforcement
  • Read evals.json — check eval 9 vs eval 14 consistency
  • Post findings

What a3138a5 fixed

Four defects from the prior two commits, all confirmed resolved:

  • Span-based dedup — deduplication now keys on (file, line, start, end) rather than by whole line; a line with two independent references (Use <old> via /plugin install <old>@mkt) preserves both. Eval 14 covers this. ✓
  • PCRE2 lookahead removed(?![\w.-]) replaced with ([^\w.@-]|$) (consume-the-delimiter); the cross-platform note now explicitly documents the no-lookaround constraint and the rationale. Eval 15 covers this. ✓
  • --include-bare-token Orphans scope — flag is now documented as not applicable to Orphans (which sweeps only Forms 1 and 3) and returns a not-applicable message rather than silently returning the default. ✓
  • Phase 7 residue count — both hand-off summary templates now carry the <R> bare-token residue field, emitted only under container-rename mode and only when non-zero. ✓

Finding 1 — HIGH: Form 13 hyphenated-plugin-ID false positives (third consecutive round — still unaddressed)

File: patterns.md:177–178

Neither commit since this was first raised (7038a0d, d35e5b9, a3138a5) addressed it.

@-form — leading \b:

\b<old>@[\w]([\w-]*[\w])?([^\w.@-]|$)

\b fires at the boundary between a hyphen and the first character of <old>. For <old> = guard, context-guard@acme-tools contains a word boundary between - and g, so guard@acme-tools matches. The ([^\w.@-]|$) terminator then accepts a trailing space or end-of-line, completing a Certain-rated match on a different plugin's qualified ID.

Command-argument form — trailing \b:

(^|[^\w/])/plugins?\s+(install|...)\s+`?<old>\b

For <old> = context, /plugin configure context-guard matches because \b fires at the boundary between t and -. The auto-apply edit rewrites an argument that names a different hyphenated plugin.

Both paths are Certain-rated → auto-apply without per-match confirmation. The re-anchor → discipline fixture gave these no coverage because re-anchor is itself hyphenated, so it was never a substring of another plugin name in the same qualified-ID position.

No lookahead needed to fix either. The cross-platform constraint already prohibits lookbehinds; the same engine constraint makes (?<![\\w-]) unavailable. But [^\w-] as a required preceding character is not a lookbehind — it consumes one character:

  • @-form: replace \b<old>@ with (?:^|[^\w-])<old>@ — requires the character before <old> to be neither a word char nor a hyphen, exactly the constraint needed.
  • Command-argument form: replace trailing \b with (?=[@\s\"']|$)` — a hyphenated continuation cannot immediately follow with one of those characters.

An eval case covering context-guard@acme-tools with <old> = context (expected: no match) and /plugin configure context-guard with <old> = context (expected: no match) would lock in the correct non-match behavior and prevent a fourth round.

Fix this →


Finding 2 — MEDIUM: Eval 9 still describes line-based dedup after the span-based fix

File: evals/evals.json:105–112

a3138a5 added eval 14 to cover the new span-based dedup and updated patterns.md to mandate it. Eval 9 was not updated and still says:

"deduplicates by (file, line) before triage: each line matched by Forms 13–15 is attributed to that form and enters Certain, and its Form 2 duplicate is dropped"

and its first expectation is:

"Deduplication by (file, line) happens after the sweep and BEFORE triage"

Eval 14 says the opposite — "Suppression is by span coverage, never by line identity". A model running both evals encounters a direct contradiction: eval 9 prescribes line-based dedup; eval 14 prescribes span-based dedup. Line-based dedup is the bug a3138a5 fixed — a model following eval 9's description re-introduces it.

Fix: Update eval 9's expected_output and first expectation to use span language — replace "by (file, line)" with "by occurrence span (file, line, start, end)" — so it describes the correct behavior rather than the old one. The core behavioral point (container-position match supersedes its own bare-token duplicate) is still what eval 9 is testing; only the implementation description needs updating.

Fix this →


Finding 3 — LOW: SKILL.md Phase 6 summary still says "count == 0" (carried from 20:17 round)

File: SKILL.md:102, SKILL.md:149

  • Line 102: exit when count == 0
  • Line 149: Re-sweep until count == 0

apply.md Phase 6 correctly defines "count" as the actionable count with explicit prose explaining why raw count can never reach zero under container-rename mode. The SKILL.md inline summary and Gotchas note were not updated. A reader or model using SKILL.md as the primary reference — without reading apply.md in full — would implement the infinite-loop behavior that the Phase 6 fix was meant to prevent.

Fix this →


Finding 4 — LOW: apply.md has no explicit --include-bare-token rejection instruction (carried from security review)

File: context/apply.md:144–146

audit-modes.md line 11 states both --include-plan-docs and --include-bare-token are AUDIT-MODE ONLY — apply mode rejects both with an explicit error. The hardcoded rejection block at lines 163–172 covers only --include-plan-docs. An AI routing through apply.md for <old> to <new> invocations has no instruction to reject --include-bare-token. Blast radius is bounded (the flag always produces Ambiguous, so every match still requires per-match confirmation), but the enforcement is asymmetric with what audit-modes.md promises.

Fix this →


Summary

Finding 1 is the same hyphenated-plugin-ID gap the second reviewer raised as HIGH at 20:06, re-confirmed as HIGH at 20:17, and re-confirmed as IMPORTANT in the 20:17 security review — it now enters its fourth round without being addressed. It is a Certain-rated auto-apply false positive on an unrelated plugin's qualified ID, which is precisely what the management-verb anchor was added to prevent; the re-anchor fixture did not surface it, and the precision numbers claimed for Form 13 do not bound it.

Finding 2 is new and was introduced by a3138a5 itself — the commit that fixed span-based dedup added eval 14 but left eval 9 contradicting it.

Findings 3 and 4 are low-severity documentation gaps carried from prior rounds.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

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


Security Review

  • Read PR diff and changed files at HEAD (a3138a5)
  • Check status of open findings from prior reviews
  • Analyze new commit for new issues
  • Report security findings

Scope

All nine changed files are documentation and configuration: Markdown instruction files (patterns.md, apply.md, audit-modes.md, audit.md, triage.md, SKILL.md, CHANGELOG.md), a JSON eval fixture, and plugin.json. No compiled code, no GitHub Actions workflows, no new shell execution surfaces.

What a3138a5 addressed

The latest commit correctly resolved two issues from the prior review round:

  • Lookahead engine dependency — Form 13's (?![\w.-]) negative lookahead was replaced with the consuming terminator ([^\w.@-]|$). ripgrep's default Rust engine rejects look-around entirely, and this file's own cross-platform note already bans lookbehinds for the same reason. The new form uses the same consume-the-delimiter shape as Forms 4 and 5, and requires no engine flags. Verified.
  • Line-level dedup causing false completion(file, line) keying collapsed two independent references on the same line, leaving one unreplaced. Phase 5 replaces one span at a time, so the surviving bare occurrence was then reclassified as residue, excluded by container mode, and the re-sweep declared completion with a live stale reference still in the file. Span-level keying (file, line, start, end) with covered-by suppression corrects this. Verified.

IMPORTANT — Form 13 \b boundary fires inside hyphenated plugin IDs (auto-apply path)

Confidence: HIGH

Form 13 at patterns.md:177–178:

(^|[^\w/])/plugins?\s+(install|uninstall|configure|enable|disable|update|add|remove)\s+`?<old>\b
\b<old>@[\w]([\w-]*[\w])?([^\w.@-]|$)

\b treats a hyphen as a non-word character, so a word boundary exists between any hyphen and the adjacent letter. This produces two Certain-rated false-positive paths — both auto-apply without per-match confirmation:

Command-argument form — trailing \b: For <old> = context, the input /plugin configure context-guard matches because:

  • (^|[^\w/])/plugin matches the space + /plugin
  • \s+configure\s+ matches
  • context matches <old>
  • \b fires at the t- boundary (hyphen is non-word character)

Phase 5 applies the edit, renaming the argument in a command that was referring to context-guard, not context.

Qualified-ID form — leading \b: For <old> = guard, the input context-guard@acme-tools matches because:

  • \b fires at the -g boundary
  • guard matches <old>
  • @acme-tools satisfies @[\w]([\w-]*[\w])? and ([^\w.@-]|$) matches end-of-line

This was raised as a HIGH finding by both reviewers in the two rounds prior to this HEAD and was not addressed in either 7038a0d, d35e5b9, or a3138a5.

The fixture (re-anchordiscipline) gave this no coverage because re-anchor is itself hyphenated and was not a substring of any other plugin's qualified ID in the measured tree. The precision numbers do not bound this failure mode.

Fix (no lookbehinds — per the cross-platform constraint):

  • Command-argument form: replace trailing \b with (?=[@\s\"']|$)` — a hyphenated continuation cannot immediately follow with any of those characters.
  • Qualified-ID form: replace \b<old>@ with (?:^|[^\w-])<old>@ — requires the character before <old> to be neither a word character nor a hyphen; no lookbehind needed.

An eval case for context-guard@acme-tools with <old> = context (expected: no-match) and /plugin configure context-guard with <old> = context (expected: no-match) would lock in the correct non-match behavior.

Fix this →


SUGGESTION — --include-bare-token apply-mode rejection not enforced in apply.md

Confidence: HIGH (gap exists); LOW (direct security impact)

audit-modes.md:11 now correctly states that apply mode rejects both --include-plan-docs and --include-bare-token with an explicit error. apply.md has the hardcoded rejection block for --include-plan-docs but has no equivalent for --include-bare-token. An AI executing apply mode while reading only apply.md has no instruction to reject the flag.

Blast radius: --include-bare-token always lands Ambiguous (per-match confirmation required), so this cannot cause silent auto-rewrites. It would, however, surface hundreds of confirmation prompts on verb-use residue the mode rule deliberately excluded — the behavior the mode rule was designed to prevent.

Fix this →


SUGGESTION — SKILL.md Override flags table says "Three flags" and omits --include-bare-token

Confidence: HIGH (inconsistency is plain)

SKILL.md:46 still reads "Three flags toggle auto-exclusions ON" and the table lists only --include-historical, --include-memory, and --include-plan-docs. --include-bare-token appears in the frontmatter argument-hint on line 4, but not in the table body. Line 55 says "Unknown flags raise an error." An AI reading the table but not the argument-hint would treat --include-bare-token as unknown and error. The audit-modes.md table is correct.

Fix this →


No other security issues found

  • No secrets, credentials, or PII introduced
  • No GitHub Actions or workflow changes
  • No new external dependencies or supply-chain risk
  • Plugin-form isolation rules (${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA}) unchanged
  • Pre-computed context shell commands in SKILL.md (lines 12–14) are read-only git introspections taking no user input
  • Eval cases in evals.json are read-only test expectations with no runtime execution surface
  • The ([^\w.@-]|$) terminator correctly resolves the PCRE2/lookahead engine issue from the prior round

Verdict: The Form 13 \b boundary issue (IMPORTANT) has been correctly diagnosed through two prior review rounds and remains unfixed at HEAD. It is the only unresolved finding that creates a Certain-rated auto-apply path to rewriting an unrelated plugin's identifiers. The two SUGGESTION findings are documentation inconsistencies with limited blast radius.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Round 3 — four new findings CONFIRMED and fixed in a3138a5d

Thirteen threads are open on this PR, but they are not thirteen live findings. Sorting by the commit each is anchored to:

Anchored to Count Status
9a618c31 9 Already fixed in 7038a0d4 / d35e5b9a — re-reports of the same four findings against a superseded revision
7038a0d4 / d35e5b9a 4 Genuinely new, and fixed in a3138a5d

Two of the four new ones were introduced BY the earlier fixes. Fixing a review round created its own defects; that is worth stating plainly rather than presenting this as steady convergence.

1. Dedup by line dropped a second reference — a FALSE COMPLETION, not a missed report

The sharpest of the four. Keying on (file, line) meant a line carrying two independent references lost one:

Use <old> via /plugin install <old>@marketplace
    ^bare                     ^command-argument

Because Phase 5 replaces a single span at a time (replace_all: false), the surviving bare reference was then reclassified as residue, excluded by container mode, and the re-sweep declared completion with a live stale reference still in the file. The failure mode is a false green, not an omission from a report.

Dedup now keys on the occurrence span (file, line, start, end), and a weaker match is suppressed only when its span is covered by a more-specific match's span. Eval 14 holds it, and names the false-completion path specifically.

2. The lookahead would have been rejected, not evaluated

My email-collision fix in 7038a0d4 used (?![\w.-]). patterns.md's own Cross-platform note says "Do not use lookbehinds — Form 1 uses \B instead" — I added a lookaround 147 lines above the rule banning that class of construct, and ripgrep's default engine (the one this skill instructs) rejects look-around without -P/--pcre2.

The boundary now consumes a terminator instead — ([^\w.@-]|$) — which is the same shape Forms 4 and 5 already use. Verified equivalent across six cases:

/plugin install info@acme-tools     MATCH
`info@acme-tools`                   MATCH
"info@acme-tools": true             MATCH
info@acme-tools        (bare EOL)   MATCH
Contact info@acmetools.com          no match
mail info@example.co.uk             no match

Documented on the form with the reasoning, so the next author does not reintroduce the lookahead. Eval 15 holds it.

3. --include-bare-token over-promised on Orphans

Confirmed: Orphans sweeps only Forms 1 and 3, so it produces no bare-token residue and the flag silently returned the default result there. Scoped explicitly in both the prose and the override table, and reported as not-applicable rather than silently ignored — a flag that appears to work is worse than one that says it does not apply.

4. The residue count was promised and never emitted

Also confirmed by reading both templates: neither Phase 7 success template had a field for it, and the default hand-off still said 0 stragglers. The actionable-count fix prevented the infinite loop while hiding the aggregate it committed to — arguably worse than the loop, since it presents a raw-zero sweep. Both templates now carry the count, emitted only under container-rename mode and only when non-zero.

On the nine re-reports

Marked 👀 rather than 👍/👎. They are correct as written against 9a618c31 and were fixed two commits ago; re-reporting them against a superseded revision is a review-tooling artifact, not a disagreement. If any of them is still reproducible at a3138a5d, that is a real finding and I would rather hear it again than have it dropped.

Gates at a3138a5d: node scripts/validate-plugin-contracts.mjs (the gate that failed earlier) passes — 43 setup skills, 2097 plugin files, exit 0. claude plugin validate . passes, markdownlint-cli2 0 errors across 6 files, changelog parity passes, evals.json parses with 15 cases.

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

ℹ️ 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 thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
…tered

Three review rounds each found real defects, and six of them were one class:
a rule changed in one file while other files kept asserting its opposite.
This skill states the same contract across SKILL.md, five context/ files, and
evals/evals.json by design, so changing patterns.md alone was never enough. I
had already written that lesson into this PR after the plugin-gate failure
and then did not apply it to the rules themselves.

Swept the whole skill directory for every claim the earlier commits altered,
rather than waiting for a fourth round to name them one at a time:

- SKILL.md gated the re-sweep on `count == 0` in TWO places. That is the
  always-loaded surface, so it would have shipped the non-terminating loop
  the apply.md fix exists to close, regardless of what apply.md said.
- An eval asserted the raw count and would have FAILED against the corrected
  skill, reading as a regression rather than as the stale assertion it was.
- triage.md's bucket criteria never learned that Forms 13-15 can be demoted
  by their own scope rules — the file that owns bucketing did not know about
  the demotion that makes the title form safe.
- audit.md defined Certain as "high-precision form" without the scope
  qualifier, and flagged an empty Ambiguous bucket as unusual when under
  container-rename mode it is the designed outcome.

patterns.md Phase 6 now requires this reconciliation for any future rule
change: grep the skill directory for the claim just changed and fix every
hit, evals included.

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 53s —— View job


PR Review

  • Gather context (PR diff, changed files, review history)
  • Read current HEAD files — patterns.md, SKILL.md, apply.md, evals.json
  • Verify status of open findings from last round (a3138a5)
  • Identify what c2ba99a fixed
  • Post findings

What c2ba99a fixed

Six confirmed findings from prior rounds are resolved at this HEAD:

  • SKILL.md "count == 0" (two sites) — both now say "ACTIONABLE count is 0" with inline prose explaining why raw count never terminates under container-rename mode ✓
  • Eval 4 raw count assertion — updated from "count == 0" to "actionable count of 0" ✓
  • triage.md bucket criteria for scope demotion — Certain bucket now includes the scope qualifier; Ambiguous bucket now lists Forms 13–15 demotions explicitly; empty Ambiguous under container-rename mode is documented as expected ✓
  • audit.md Certain definition — carries the scope qualifier for container-position forms ✓
  • Eval 16 — new case locking in demotion-survives-in-bucket-criteria behavior ✓
  • Phase 6 reconciliation requirement — the explicit rule requiring every claim update to propagate across all seven files ✓

Finding 1 — HIGH: Form 13 hyphenated-plugin-ID false positives (fifth consecutive round — still unaddressed)

File: patterns.md:177–178

(^|[^\w/])/plugins?\s+(install|uninstall|configure|enable|disable|update|add|remove)\s+`?<old>\b
\b<old>@[\w]([\w-]*[\w])?([^\w.@-]|$)

Neither c2ba99a nor the three prior fix commits touched either \b boundary. Both produce Certain-rated auto-apply false positives when <old> is a component of another hyphenated plugin name:

Command-argument form — trailing \b: \b fires at the boundary between a word character and a hyphen (which is non-word). For <old> = context, /plugin configure context-guard matches — the pattern sees context followed by \b at the t- boundary. Phase 5 rewrites an argument that refers to a different plugin.

Qualified-ID form — leading \b: \b fires at the boundary between a hyphen and the first character of <old>. For <old> = guard, context-guard@acme-tools contains a word boundary at -g, so guard@acme-tools matches. A different plugin's complete qualified ID is auto-rewritten as Certain.

The re-anchor → discipline fixture gave this no coverage because re-anchor is itself hyphenated and was not a substring of another plugin name's qualified ID in the measured tree. The precision numbers do not bound this failure mode. The Phase 6 reconciliation requirement added in this very commit mandates sweeping for this exactly — but the regex the requirement exists to protect was not swept.

Fixes (no lookbehinds — cross-platform constraint):

  • Command-argument form: replace trailing `?<old>\b with `?<old>(?=[@\s`"']|$) — a hyphenated continuation cannot immediately follow with any of those characters.
  • Qualified-ID form: replace \b<old>@ with (?:^|[^\w-])<old>@ — requires the character before <old> to be neither a word character nor a hyphen. This is not a lookbehind; it consumes one character.

An eval case covering context-guard@acme-tools with <old> = context (expected: no-match), and /plugin configure context-guard with <old> = context (expected: no-match), would lock in the correct non-match behavior and prevent regression.

Fix this →


Finding 2 — MEDIUM: Eval 9 contradicts eval 14 on dedup semantics (from last round, still open)

File: evals.json:105,108

c2ba99a updated eval 4 and added eval 16 but did not update eval 9.

Eval 9 expected_output (line 105): "deduplicates by (file, line) before triage"
Eval 9 first expectation (line 108): "Deduplication by (file, line) happens after the sweep and BEFORE triage"

Eval 14 (line 167): "Suppression is by span coverage, never by line identity"

These are mutually exclusive behavioral descriptions. A model following eval 9's description re-introduces the line-based dedup that a3138a5 was specifically fixing — the false-completion bug where a second reference on the same line is silently dropped and the re-sweep declares done with a live stale reference in the file. c2ba99a itself extended the Phase 6 reconciliation rule precisely to catch this class of stale assertion; eval 9 is an instance of it.

Fix: In eval 9, replace "by (file, line)" with "by occurrence span (file, line, start, end)" in both expected_output (line 105) and the first expectation (line 108). The behavioral point eval 9 is testing — container-position match supersedes its bare-token duplicate — remains correct; only the dedup key description needs updating to match patterns.md's authority.

Fix this →


Finding 3 — LOW: apply.md has no --include-bare-token apply-mode rejection block (from security review, carried)

File: context/apply.md

audit-modes.md states both --include-plan-docs and --include-bare-token are "AUDIT-MODE ONLY — apply mode rejects both with an explicit error." apply.md's self-reference exclusion section has the hardcoded rejection block for --include-plan-docs but no equivalent for --include-bare-token. An AI routing through apply.md for a <old> to <new> invocation has no instruction to reject the flag. Blast radius is bounded (the flag produces Ambiguous, so confirmation is required per match), but the enforcement is asymmetric with what audit-modes.md promises. The c2ba99a Phase 6 sweep that found six inconsistencies missed this one.

Fix this →


Finding 4 — LOW: SKILL.md "Three flags" is now wrong (from security review, carried)

File: SKILL.md:47

Line 47 says "Three flags toggle auto-exclusions ON" and the table lists only --include-historical, --include-memory, and --include-plan-docs. --include-bare-token appears in the argument-hint frontmatter (line 4) and in audit-modes.md's override table but is absent from the SKILL.md table body. Line 55 says "Unknown flags raise an error." A reader using the SKILL.md table — the most-loaded surface — would treat --include-bare-token as unknown. c2ba99a swept SKILL.md for "count == 0" but did not sweep it for the flag count.

Fix this →


Summary

Finding 1 is the one finding that has now crossed into its fifth review round unaddressed. Every prior review — two separate review tracks — has confirmed it as HIGH or IMPORTANT and described the same fix. The c2ba99a commit explicitly adds a Phase 6 rule requiring that every affected site be reconciled when a rule changes, then does not apply that rule to the regex that needed it most. Finding 2 is the same class: an eval swept in this commit for one field was not swept for a second field where it also stated the old behavior. Findings 3 and 4 are documentation gaps carried from prior rounds.

The incremental quality of each commit has been high — the core design (Forms 13–15, precedence, scope demotion, container-rename mode) is solid and the reasoning throughout is rigorous. Finding 1 is the one blocker standing between this and a clean merge.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude encountered an error after 9s —— View job


I'll analyze this and get back to you.

@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: 5127aa0285

ℹ️ 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 thread plugins/docs-hygiene/skills/rename-references/context/patterns.md
Comment thread plugins/docs-hygiene/skills/rename-references/context/audit.md Outdated
Comment thread plugins/docs-hygiene/skills/rename-references/context/audit.md Outdated
Comment thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
…owlist

Round 11, four findings.

Form 14 missed a catalog KEYED by the container. Ladder rule 3 names both
shapes -- the name/id field of a manifest, or a key in a marketplace/registry
catalog -- but only the field shape had a pattern, so a registry written as
`plugins: { "<old>": { ... } }` selected container mode while its own key
stayed unmatched Form 2 residue. The key-position alternative requires the
value to OPEN an object or array, which distinguishes a catalog entry from an
ordinary scalar setting.

That alternative MUST carry the manifest/catalog condition rather than merely
benefit from it: `"<key>": {` is the commonest line shape in JSON and this
repository carries 569 of them, so unconditioned it would rate a large slice
of that Certain for an ordinary-word container name. Outside a manifest or
registry catalog it is Ambiguous unconditionally.

A YAML block-mapping catalog key is recorded as a deliberate gap, not
silently covered: in YAML the entry opens with nothing, so the discriminator
has nothing to bind to and the only pattern that reaches it would match every
nested YAML key -- a worse trade than the one it fixes.

Form 14's YAML declaration was anchored at column zero. A manifest or catalog
that nests its entries indents them, and the mode ladder recognizes that
field as container evidence either way, so the nested shape was excluded
residue under the mode it selected. The JSON and TOML alternatives were
indentation-agnostic from the start; the YAML one now matches them.

Container mode's Certain rule was enforced only against Form 2. Rule 1 is an
ALLOWLIST -- Forms 1, 3 and 13-15 are the whole eligible set -- so filtering
just the bare-token residue left Forms 8 and 12 on the auto-apply path, both
Certain by default. Renaming a `context` plugin would rewrite the unrelated
dotted key `context.timeout` and a `{a,context,b}` glob enumerating skills.
Both forms anchor on syntax proving the token is an IDENTIFIER, which is not
what a container rename is asking. Every non-allowlisted form now demotes to
Ambiguous, reported per match rather than folded into the aggregate, because
they are few and a container name genuinely can appear in a glob set.

The per-occurrence survey rescanned line by line, which reproduces nothing
for the two multiline forms. Form 7 and Form 14's Setext alternative match
only against a block, so the rescan emitted no record and the reference
vanished between survey and triage -- silently, on exactly the two forms
added because their references were being missed. The block is now kept
intact, the pattern re-run against it, and the captured span converted back
to (line, start, end) via the block's first-line number.

Verified verbatim through the Grep tool: the key-position alternative matches
`"<old>": {` and rejects `"<old>-extra": {`, a mid-line `"<old>":` with a
scalar value, and an indented YAML `<old>:`. The widened YAML declaration
matches `name:`, `title:` and `id:` bare, double- and single-quoted, and
still rejects `notes: <old> is used here as prose`.

Phase 6 reconciliation grep on the eligible-form list, the rescan unit and
Form 14's alternative count updated five sites: audit.md's Phase 3 Certain
definition and Phase 4 breakdown, triage.md's Form 8 entry and closing
paragraph, and two evals.

Gates: validate-plugin-contracts (43 setup skills, 2098 files), claude plugin
validate, markdownlint-cli2 0 errors over 36 files, changelog parity
--check-bump, evals parse at 36 cases with no duplicate ids.

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

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

@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: 811696be7b

ℹ️ 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 thread plugins/docs-hygiene/skills/rename-references/context/apply.md Outdated
Comment thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
…he delimiter

Round 12, two findings.

A confirmed skip was counted as actionable, so apply mode could not
terminate. The count already excluded container mode's residue but not the
matches a user declines at Phase 4, so choosing "skip this" on an unrelated
`context.timeout` re-presented the same match at Outcome B on every re-sweep;
the only exits were rewriting a known false positive or aborting with a
partial result. That is the identical non-terminating loop the residue rule
closes, reached through the other door -- and the previous commit's allowlist
change made it routine rather than rare, because demoting Forms 4-12 to
Ambiguous turns unrelated matches into per-match prompts whose correct answer
is to skip.

Phase 4 now records each decline as (file, line, start, end) -- the same
occurrence key precedence and dedup use -- and Phase 6 subtracts those spans
before testing for completion. Keyed by span rather than by file or form,
because skipping one occurrence is not consent to skip another on the same
line. Scoped to the sweep that asked: if Phase 6 finds a NEW form and the
library is extended, the question changed and the user is re-asked. Reported
on its own hand-off line, never folded into the residue aggregate -- residue
was never proposed, a skip was proposed and declined.

The catalog-key alternative failed its own motivating example. Its `^\s*`
anchor required the renamed key to begin the line, so it reached only the
pretty-printed rendering -- not the compact `plugins: { "<old>": { ... } }`
used by eval 34 and by the bullet documenting the form. A left anchor that
misses the example it was written for is the recurring failure in this file's
history. The anchor is now `(^|[{,])`: a JSON key follows a line start, an
opening brace, or a comma, and nothing else, so both renderings match with no
widening beyond them.

Verified verbatim through the Grep tool: `plugins: { "<old>": {`, an indented
`"<old>": {`, `{"a":1,"<old>":{}}` and an array-valued `"<old>": [` all
match; `"other": { "<old>": "scalar" }`, `"<old>-extra": {`, an indented YAML
`<old>:` (the documented gap) and prose all do not.

Phase 6 reconciliation grep on the actionable-count definition found it
restated as residue-only in three more places, including the always-loaded
SKILL.md in both its workflow step and its gotchas entry -- the same surface
that shipped the previous non-terminating-loop bug. Those plus eval 12 are
corrected here.

Gates: validate-plugin-contracts (43 setup skills, 2098 files), claude plugin
validate, markdownlint-cli2 0 errors over 36 files, changelog parity
--check-bump, evals parse at 37 cases with no duplicate ids.

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

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

@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: 8969f0a659

ℹ️ 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 thread plugins/docs-hygiene/skills/rename-references/context/apply.md
Comment thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
Comment thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
…ation

Round 13, three findings.

Skip spans did not survive the edits they coexist with. `<old>` and `<new>`
differ in length in the general case, so applying an accepted occurrence
shifts every LATER occurrence on that same line. On
`/plugin configure <old>; use <old>.timeout` the accepted Form 13 match moves
the skipped Form 12 match's columns, the Phase 6 rescan reports different
(start, end), the stored span fails to subtract, and the user is prompted for
the deliberate skip again -- the same non-terminating loop, defeated by the
bookkeeping added last commit to close it. Each Edit now shifts the start and
end of every stored skip span on its line whose start is greater than the
edited start; other lines and earlier spans are untouched, because a rename
replaces in place and adds no lines. The matched snippet is carried alongside
and a post-remap mismatch is reported rather than silently dropping the skip.

The JSON declaration alternative required the field to occupy a whole line.
A minified or merely compact manifest -- `{"name":"<old>","version":"1"}` --
is perfectly valid, and filesystem evidence still selects container mode for
it, so the registration went unmatched and was suppressed as residue while
apply mode reported completion. It now uses the same `(^|[{,])` opener and
`(,|}|$)` terminator as the key-position shape: in JSON a member starts after
a line start, a brace, or a comma and ends at a comma, a brace, or end of
line.

The YAML and TOML alternatives deliberately keep their `$` anchor. Both
grammars are line-oriented for the shapes manifests actually use, so
end-of-line is a real discriminator there; JSON is the one of the three with
no line structure at all. Flow-mapping and inline-table variants are recorded
as hand-resolve cases rather than a reason to widen those two into
every-nested-key patterns.

The mode-ladder lead-in still said "stop at the first rule that fires" while
the conflict rule directly below the list said to collect rules 2-4 in full
and compare. Both statements stood in one file. Only rule 1 short-circuits;
the list ordering is check order, not verdict precedence.

That third one was in scope for the previous round's reconciliation grep and
was missed because the grep filtered out the line range containing it. The
filter, not the pattern, was the defect.

Verified verbatim through the Grep tool: the JSON alternative matches
minified, compact, indented, spaced, sibling-after and `title`/`id` forms,
and rejects `-extra`, `description`, and a prose line quoting the pair
mid-sentence. Precision on this repository is unchanged at exactly
marketplace.json:192 and plugin.json:3.

Reconciliation also corrected eval 15, which still explained the YAML
alternative's miss by its column-zero anchor -- superseded two commits ago;
the real reason is that it requires an unquoted key.

Gates: validate-plugin-contracts (43 setup skills, 2098 files), claude plugin
validate, markdownlint-cli2 0 errors over 36 files, changelog parity
--check-bump, evals parse at 40 cases with no duplicate ids.

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

claude Bot commented Jul 26, 2026

Copy link
Copy Markdown

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jul 26, 2026

Copy link
Copy Markdown

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

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

ℹ️ 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 thread plugins/docs-hygiene/skills/rename-references/context/patterns.md
Round 14, one finding, and a real interaction between two earlier decisions.

Forms 3, 13, 15 and both delimiter-anchored Form 14 alternatives CONSUME a
trailing delimiter rather than asserting it, because ripgrep's default engine
rejects look-around. The consumed character is frequently the LEADING
delimiter the next occurrence needs, so the per-occurrence rescan -- which
resumed after the whole match -- silently dropped the second of two ADJACENT
references.

Reproduced exactly: on `{"name":"<old>","id":"<old>"}` a global `rg -o`
returns ONE match, `{"name":"<old>",`, because the first match ate the comma
the `id` member needed as its opener. Insert `"version":"1"` between them and
both appear, which is what makes this specifically an adjacency collision --
and a compact manifest is precisely where adjacency happens. Form 2 still
found the lost token, but under container mode that is suppressed residue, so
the sweep could report completion with the second declaration stale.

Fixed once in the survey rather than in each regex, since every
consume-the-delimiter form shares the hazard: Phase 2 now advances the rescan
cursor to the end of the CAPTURED `<old>` span. Verified that this recovers
the case -- one occurrence record becomes two on the adjacent input.

Two details recorded because the obvious variants are both wrong. Advance a
cursor, do not slice the string: re-running the pattern against a substring
lets `^` match at the cursor and would admit a member with no delimiter in
front of it. And advance by the captured token's end rather than match start
plus one, or the same occurrence is re-found through another alternative and
the record doubles.

Dropping the consumed terminator is explicitly NOT the fix, and patterns.md
says so at the site: without it the boundary is unenforced and `<old>@slug`
matches inside `<old>@slug.com` again -- the finding that put the terminator
there.

Gates: validate-plugin-contracts (43 setup skills, 2098 files), claude plugin
validate, markdownlint-cli2 0 errors over 36 files, changelog parity
--check-bump, evals parse at 41 cases with no duplicate ids.

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

claude Bot commented Jul 26, 2026

Copy link
Copy Markdown

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jul 26, 2026

Copy link
Copy Markdown

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

@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: 3bcc2bdc8d

ℹ️ 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 thread plugins/docs-hygiene/skills/rename-references/context/apply.md
Comment thread plugins/docs-hygiene/skills/rename-references/context/patterns.md Outdated
Comment thread plugins/docs-hygiene/skills/rename-references/context/audit.md Outdated
… skip in handoff

Round 15, three findings.

The survey lost occurrences inside a wide match. Form 7's pattern swallows a
whole frontmatter field, and its greedy prefix binds the captured group to a
single occurrence, so `description: "first <old> and then <old>"` produced ONE
whole-pattern match for two references. Reproduced directly. Last round's
cursor rule cannot recover the other one either: re-matching from inside the
field cannot reproduce the `description:` prefix the pattern requires, so the
fix had to be enumeration rather than a different cursor.

Phase 2 now scans each match's text for every occurrence of `<old>` and emits
one record per occurrence, all attributed to the matching form. The
whole-pattern match establishes THAT the form applies and over what extent;
the token spans inside it are the references. The cursor rule survives as the
step that finds the NEXT match, advancing past the LAST enumerated span --
past the whole match is the adjacency bug, past only the first token
re-emits what was already enumerated.

Form 14's ATX alternative rejected the closed form. `# <old> #` is valid ATX
and the trailing hash run is decoration rather than content, so the heading's
entire content is still the token and the Certain reasoning applies
unchanged; without the optional closing-hash group the title's only hit was
Form 2, which container mode excludes. The run is not part of the reference:
only the captured span is replaced. Verified that discrimination is
unchanged -- `## How <old> works`, `# <old> extra` and `# <old>-extra #` are
still rejected.

The default hand-off template omitted the skipped count. Phase 7 requires
reporting it separately from container residue, but the success template at
the bottom of apply.md carried only `<R>`, so a normal run following the
template reported "0 actionable stragglers" without disclosing that stale
occurrences were preserved by request. The zero is true and insufficient on
its own; a reader cannot otherwise distinguish it from a sweep that found
nothing.

Gates: validate-plugin-contracts (43 setup skills, 2098 files), claude plugin
validate, markdownlint-cli2 0 errors over 36 files, changelog parity
--check-bump, evals parse at 44 cases with no duplicate ids.

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

claude Bot commented Jul 26, 2026

Copy link
Copy Markdown

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jul 26, 2026

Copy link
Copy Markdown

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

@kyle-sexton
kyle-sexton merged commit 99705a7 into main Jul 26, 2026
26 checks passed
@kyle-sexton
kyle-sexton deleted the fix/1283-rename-references-position-forms-v2 branch July 26, 2026 00:43

@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: 306dac4fc4

ℹ️ 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 thread plugins/docs-hygiene/skills/rename-references/context/patterns.md
Comment thread plugins/docs-hygiene/skills/rename-references/context/patterns.md
kyle-sexton added a commit that referenced this pull request Jul 26, 2026
…nifest declarations (#1435)

Follow-up to #1386. Two **P1** findings were raised on that PR after it
had already merged, so they never reached `main`.

Closes #1439

## Form 1 rewrote unrelated slash commands

`\b` treats a hyphen as a word boundary, so `\B/<old>\b` prevented
`/confirm` matching in `/confirmation` but **not** in
`/confirm-changes`. Renaming `context` matched the unrelated
`/context-guard`.

Form 1 is rated Certain **and** sits on container mode's Certain
allowlist, so this went through the default auto-apply path and renamed
another command. Slash-command and container names are kebab-case, so it
fires constantly rather than rarely.

Now uses the consumed `([^\w-]|$)` terminator — the shape Forms 13 and
15 already use for the same reason.

Verified against this tree: the nine `/docs-hygiene:<skill>` namespaced
invocations still match, a colon being a valid terminator, while
`/context-guard`, `/contextual` and `path/context` do not.

## Manifest declarations could not carry an inline comment

`name: <old> # package name` and `name = "<old>" # package name` are
ordinary self-documenting manifests, and the end-anchored declaration
alternatives rejected the whole line — while filesystem evidence still
selected container mode, so the registration went unmatched and was
suppressed as residue while apply mode reported completion.

The whitespace rule differs between the two deliberately:

- **YAML requires whitespace before `#`.** YAML starts a comment only
after whitespace, so `name: <old>#x` is the single scalar `<old>#x` and
must not match.
- **TOML allows optional whitespace,** its value being quoted, so the
closing quote already ends the string unambiguously.
- **JSON is excluded entirely,** having no comment syntax.

All six positive shapes and both negatives verified.

## Reconciliation

The Phase 6 reconciliation grep on Form 1's boundary found it restated
in four more places — `triage.md`'s Certain criteria, `audit-modes.md`'s
Orphans sweep, `apply.md`'s word-boundary-trap gotcha, and `audit.md`'s
consume-the-delimiter form list — all corrected here.

Two eval cases added (45, 46). `docs-hygiene` 0.9.0 → 0.9.1.

## Gates

- `node scripts/validate-plugin-contracts.mjs` — 43 setup skills, 2122
files
- `claude plugin validate .`
- `markdownlint-cli2` — 0 errors over 36 files
- `scripts/check-changelog-parity.sh --check-bump origin/main`
- `evals.json` parses, 46 cases, no duplicate ids

## Related

- #1386 — the PR these findings were raised on; it merged before they
landed, so neither fix reached `main`
- #1283 — the original pattern-library gap #1386 closed
- #1394 — a separate, deliberately deferred Form 14 gap, untouched here

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

https://claude.ai/code/session_01ATCcexm8GPTaNntu2yrGMk

---------

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