feat(claude-config): detect cross-surface instruction conflicts - #1343
Conversation
The official memory docs mandate this review and name no mechanism that performs it: > Consistency: if two rules contradict each other, Claude may pick one > arbitrarily. Review your CLAUDE.md files, nested CLAUDE.md files in > subdirectories, and `.claude/rules/` periodically to remove outdated or > conflicting instructions. > -- https://code.claude.com/docs/en/memory (fetched 2026-07-25) Adds Phase B2 to `audit-instructions`: a pairwise pass over two surfaces that both claim authority over one behavior and disagree. Phase B fans out one lane per surface, so a lane sees only one half of a pair and is structurally blind to the contradiction. Phase A's and Phase B's contracts are unchanged; B2 consumes Phase A's inventory and re-enumerates nothing. `claude-memory:audit` already ships check C6 Consistency, which grades contradictions inside the memory layer. B2 extends it outward rather than standing up a parallel check: a pair with both halves in that layer routes to C6, a pair with at least one half outside is B2's. `~/.claude/rules/` is ruled outside C6's population -- its audit enumerates rules project-relative (`find .claude/rules`) and resolves a user-level directory only for auto-memory. Precedence is cited, never invented. Where the docs state an order the pass names the winner and its source; where they are silent -- user vs project CLAUDE.md, skill body vs any CLAUDE.md, managed policy vs lower scopes -- it reports the pair unresolved. Load order is not override semantics. False positives are the failure mode for a report-only auditor, so the criteria carry a 13-case must-not-flag set, seven of them pinned executable in the test suite. `conflict-scan.sh` is advisory and always exits 0, matching `instruction-scan.sh`. Classification and pairing run in one awk pass bucketed by entity; the earlier subprocess-per-mention shape did not finish in 10 minutes over this repo's skill bodies, where the awk pass takes 2.4s. Entities are derived by CamelCase shape rather than a hardcoded tool list -- recall over precision, not strictly better, since proper nouns match the same shape and account for 121 of the 169 rows a repo-wide run returns. That 28% precision is why no conflict class is gate-grade today and no ci.yml lane ships here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 5m 30s —— View job Code Review
OverallSolid addition. The single-awk-pass bucketing design is the right call for the scale problem described, the windowing approach correctly prevents the "distant prohibition" false positive class, the 24-case test suite pins every suppression rule with a concrete fixture, and the C6 boundary documentation is clear and accurate. The notes below are one portability gap, one asymmetry worth closing, and a few minor observations.
|
|
Claude finished @kyle-sexton's task in 2m 53s —— View job Security Review — PR #1343
Scope: No CRITICAL or IMPORTANT findings. Two low-severity observations below. SUGGESTION — Confidence: mediumUnvalidated env variable
Suggested guard (before the [[ "$WINDOW_CHARS" =~ ^[1-9][0-9]*$ ]] || WINDOW_CHARS=60SUGGESTION — Confidence: lowFilenames containing The awk print a[1] ":" a[2] "|" b[1] ":" b[2] "|" ent "|" flagsA path containing a literal Everything else checked and clean:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2b765c98f7
ℹ️ 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".
…talog The two new scripts landed without the executable bit the hygiene exec-bit check requires of every tracked `.sh`, and README's generated catalog block still carried the pre-bump `claude-config` description. README is regenerated by `scripts/generate-catalog.mjs`, not hand-edited. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 38s —— View job Code Review — PR #1343
Latest commit (
|
|
Claude finished @kyle-sexton's task in 3m 13s —— View job Security Review — PR #1343
Scope: Head SHA: No CRITICAL or IMPORTANT findings. Two low-severity observations below; both were already flagged by the prior automated review in the comments, so this review confirms them and adds analysis of any additional surfaces. SUGGESTION — Confidence: medium
Suggested guard before the [[ "$WINDOW_CHARS" =~ ^[1-9][0-9]*$ ]] || WINDOW_CHARS=60SUGGESTION — Confidence: lowFile paths containing The awk print a[1] ":" a[2] "|" b[1] ":" b[2] "|" ent "|" flagsA path containing a literal Everything else checked and clean:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 680bb57b76
ℹ️ 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".
Routing was drawn from a layer label rather than from what the incumbent enumerates, and the gap it opened was silent. C6 discovers files project-relative and its check text names only CLAUDE.md, CLAUDE.local.md and rules files, so a pair reaching ~/.claude/ or auto-memory MEMORY.md was deferred to C6 and never picked up by it — audited by neither skill. Routing is now on C6's actual population, and SKILL.md says the same thing the criteria file does. Split-brain was listed as a fourth conflict type while gate 1 requires co-residency, which it fails by construction. An implementation applying the gates would discard every instance. It is now reported separately as orphaned instruction drift, and the report's type field is A-C. The residency table covered only skill bodies and memory surfaces while Phase A also inventories skill reference files, agent definitions, prompt-type hooks and output styles. Every inventoried surface now has a row; hooks and output styles carry an explicit UNVERIFIED marker and their pairs report as residency-unknown rather than being cleared or classified on a guessed load model. The scanner missed three real shapes. A prohibition following its entity within the same sentence now counts, so "X must not be used" is a prohibition rather than a mandate matching on "must". A single capitalized word inside backticks is now an entity, which is what reaches Bash, Read and Edit; requiring the backticks keeps sentence-initial words out. An opt-in gate suppresses a pair only when it reads as a condition, so a directive whose subject happens to be opt-in prompts is still classified. The description-versus-body example was labelled Type C while both halves sit in one file and the omission prescribes no incompatible action. It is now the calibration case for a near-miss the gates correctly reject, which is what it always was. Nine tests cover the three scanner fixes; all 30 pass.
The routing correction pushed the body to 206 lines against a 200-line soft target. The detail belongs in the reference file that already carries the routing table and its evidence, so the body keeps the rule and the pointer.
|
Claude finished @kyle-sexton's task in 5m 25s —— View job Code Review — PR #1343 (head
|
|
Claude finished @kyle-sexton's task in 3m 11s —— View job Security Review — PR #1343
Scope: 7 files changed across the 4 commits on this branch, HEAD No CRITICAL or IMPORTANT findings. What was checkedInjection surfaces
Path handling
The
Test script (Case 16 PATH isolation)
GitHub Actions No Credentials / secrets / network No network calls, no external API invocations, no credential handling, no secrets referenced in any changed file. Two prior SUGGESTION-level observations confirmed still validBoth were already reported in the earlier automated security reviews on this PR; I list them here for completeness, not as new findings.
No new security findings at this HEAD. |
The routing correction pushed the body past its 200-line soft target. The routing table and the precedence table both live in the criteria file and the scope boundary already carries the rule, so the Phase B2 prose points at them instead of restating them.
|
Claude finished @kyle-sexton's task in 4m 10s —— View job Code Review — PR #1343 (latest push)
Summary
Resolved — confirmed against current source1. Postposed prohibitions ( Window offset accounting for Case 17 and 18 in the test suite pin both halves (catches / must-not-flag). ✓ 2. Single-word tool identifiers ( 3. Opt-in as subject vs. gated condition ( 4. Auto-memory routing gap ( 5. Type D / Gate 1 contradiction ( 6. Residency table incomplete for inventoried surfaces ( 7. Example 2 (same-file non-conflict) ( Still openThree items from earlier reviews were not addressed in this push. None block the advisory function of the skill, but two of the three are operator-facing and worth closing before merge. 1. mapfile -t rows < <(
if (( BASH_VERSINFO[0] < 4 )); then
echo "ERROR: bash 4+ required (macOS ships 3.2 — install bash via Homebrew)" >&2
exit 2
fi…or add a macOS note to the Gotchas section alongside the Git Bash line. Fix this → 2. Missing The script exits 2 when 3. WINDOW_CHARS="${CONFLICT_SCAN_WINDOW:-60}"A non-numeric value (empty string, [[ "$WINDOW_CHARS" =~ ^[1-9][0-9]*$ ]] || WINDOW_CHARS=60One minor observation
OverallThe seven targeted fixes are all correct and well-tested. The core logic additions — postwindow sentence truncation, backtick entity form, CONDITIONAL_ERE double-gate, C6 boundary redrawn on actual population — are mechanically sound. Items 1 and 2 above are the only gaps with operator-visible failure modes; item 3 is a silent false-negative risk worth a one-liner guard. |
|
Claude finished @kyle-sexton's task in 2m 42s —— View job Security Review — PR #1343 (HEAD
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c3f606ce7e
ℹ️ 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".
The execution record described lanes by what sat uncommitted in a worktree, which is unreachable from a clone and therefore not a citation. Every lane has since published, so each row now names the PR or issue that carries its result: L2 as #1343, L3 as #1349 with the UNBACKED-to-OPINION mapping stated so the claim set does not need to survive as a separate list, L4 as #1385 closed with recovery on #1403, L1 as #1286 closed with the fold on #1271 and gate defects on #1404. The source article is named with its URL rather than left as "a practitioner article", so a reader auditing this ADR's premises can reach the thing the digests measured. The digests themselves prune with the contract slice, which is why the pointer replaces them rather than supplementing them.
… break The trailing window stopped at a sentence boundary but the leading one did not, so an unrelated prohibition earlier on the same line won over the mandate that governed the entity. "Never delete branches. Always use AskUserQuestion before deleting a branch." classified as a prohibition, and pairing it with a real prohibition produced no candidate at all. Both halves now stop at a boundary: trailing text at its first, leading text after its last. A boundary is a sentence-ending mark followed by a space rather than a bare mark, because a bare mark also appears inside a dotted config path or a version number and cutting there would truncate the window mid-clause — which would trade this false negative for a different one. Three tests: the preceding-sentence case, the same-sentence prohibition that must still win, and a dotted path that must not truncate. All 33 checks pass.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ee6d22c49c
ℹ️ 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".
Truncating the polarity halves fixed prohibition matching but left the mandate, opt-in and exception tests reading the raw span, so the scanner's same-sentence contract held for one of four tests. Two consequences, both measured: an opt-in condition in a neighbouring sentence suppressed an unconditional directive entirely, and an unrelated mandate beside a neutral mention produced a candidate pair that inflates the review queue. The full window is now rebuilt from the bounded halves plus the mention, so a gate, a polarity token or an exception clause governs the entity only when it shares a sentence with it. Two tests pin the pair of cases; all 35 checks pass.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 27d3101599
ℹ️ 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".
#1349 landed checks I12-I14 as claude-config 0.10.0, so this branch becomes 0.11.0 and its changelog section sits above that one. Both branches edited the skill description and argument hint. The merged frontmatter keeps every trigger phrase from each: the harness-claim, @path and startup-read vocabulary from I12-I14, and the conflict vocabulary from this pass, plus the `conflicts` scope in the hint. Also states that the pre-scan is a priority ordering rather than the work list. It only reaches directives naming a tool-shaped entity, so an ordinary pair like "Always run tests before committing" against "Never run tests before committing" emits nothing at all. Widening the entity pattern is not the fix -- precision is already 28% on the rows it does emit -- so the lane reads the in-scope surfaces for pairs the scan cannot shape-match, and a pass that reports only what the scanner emitted has not run this check.
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
|
Warning Automated security review did not complete — this is an infrastructure failure, not a review verdict. Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."
Re-running the job, or pushing a new commit, will retry the review. |
|
Warning Automated review did not complete — this is an infrastructure failure, not a review verdict. Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."
Re-running the job, or pushing a new commit, will retry the review. |
…g row Both lanes claimed I12. #1349 landed first with I12 as the stale harness-capability claim, so this pass's conflict check was pointing at a row that exists and means something else. It becomes I15. The row itself was missing, which is the substance of the review finding: conflict-criteria.md deliberately does not restate the definition, comparison set, @path and symlink resolution, AGENTS.md exclusion, remediation-by-scope rules or must-not-flag cases, on the grounds that the catalog entry owns them -- and the catalog entry did not exist. I15 now carries all of it, and conflict-criteria.md stays what it says it is: the adjudication procedure.
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f010dc8227
ℹ️ 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".
…ntence One sentence can carry two entities at opposite polarity. In "Always use `Read`, but never use `Bash` for file inspection", the sentence-only boundary let `Bash`'s prohibition reach `Read`, classifying `Read` as prohibited -- which then failed to pair with a genuine prohibition on `Read` and dropped a straightforward conflict out of the seeded queue. A boundary is now a sentence-ending mark followed by a space, or a comma introducing a contrastive conjunction. Deliberately not any comma: ordinary comma-separated prose keeps its polarity throughout, and the worked example's own entity sits between two commas, so cutting on every comma would drop the mandate that governs it. Three tests: the earlier entity keeps its mandate, the entity inside the contrastive clause is still prohibited, and an ordinary comma does not truncate. All 38 checks pass.
|
Claude encountered an error after 3s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e0d5ecbed6
ℹ️ 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".
…yle row The C6 route said "project-scope", which includes nested CLAUDE.md and CLAUDE.local.md files. Phase A inventories those; C6 discovers with `find . -maxdepth 1` and never reads them, so a nested pair was handed to a check that cannot see it. The predicate is root-level project, and nested files stay with I15 alongside the ~/.claude and auto-memory cases. The output-style residency row was UNVERIFIED, which reduced every output-style pair to residency-unknown and put a real contradiction with a resident CLAUDE.md out of reach. Verified against the output-styles page: a style "directly modifies Claude Code's system prompt" and is read "once at session start", so the active style is resident for the main conversation. Two bounds come with it: only the active style loads -- selected via outputStyle or a plugin's force-for-plugin -- and a style applies to the main conversation only, a subagent running its own system prompt, with a fork the exception. Prompt-type hooks stay UNVERIFIED; that page was not fetched. The setup skill enumerated only instruction-scan.sh and said audit-instructions needs grep alone, so `check` would report the plugin ready on a shell where the conflict pass exits 2. It now registers conflict-scan.sh and probes awk and sort by name, since a minimal shell can carry one and not the other.
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
When CLAUDE_CONFIG_DIR is set, this hard-coded default path does not record the current project's auto-memory as a skipped surface. Phase B2 consumes Phase A's inventory, while I15 requires skipped surfaces to remain read-only conflict participants, so a contradiction involving the relocated MEMORY.md disappears from the audit even though the routing table retains auto-memory in I15; resolve the store beneath the configured root before recording it.
ℹ️ 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".
…airs on gates A surface scope narrowed Phase A's inventory, and B2 was told to consume it, so under `skills` the CLAUDE.md half of every cross-layer pair was simply absent — the headline conflict would report clean because the editable side was scoped. B2 now enumerates every surface `all` would collect, read-only, and applies the scope to the finding: a pair is reported when at least one anchor is in scope. The reasoning lives in conflict-criteria.md, where the rest of the pass's contract already is. Conflict findings also fell through to Phase C's removal prompt, which asks whether removing an instruction would cause mistakes. Both sides of a genuine conflict are usually load-bearing, so that prompt defends both and demotes the finding without testing its falsifier. A pair is now refuted on its own gates instead — same observable, existing arbitration, realistic trigger overlap — and a defended pair is one where a named gate fails. SKILL.md is back at its 200-line soft target by reflowing prose and pointing at the criteria file rather than restating it.
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
… runs under ADR 0005 makes extending Phase A with a plugin-source surface a precondition of this placement, and that extension has not shipped: Phase A enumerates the user and project .claude roots only, so in a marketplace repository it never reaches plugins/. Pairs involving a plugin README or a plugin-shipped agent definition therefore have no second side available today. Tracked as #1421 rather than folded in, because extending Phase A widens what every phase reads and changes the dispatch budget the skill already gates. The pass now reports the limit in its tier-transparency line so a clean result is not read as coverage it does not have.
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
The clause boundary required a comma, which left the same false negative one punctuation mark away: "Always use `Read` but never use `Bash`" classified `Read` as prohibited and dropped the pair. English does not require the comma, so it is now optional for the unambiguous contrastives. `while` keeps its comma requirement, because unpunctuated it is temporal at least as often as contrastive -- "use `WebFetch` while the offline flag is unset" is one clause, and splitting it would drop the mandate that governs the entity. Three tests: both entities in the unpunctuated form, and the temporal `while` that must not split. All 41 pass.
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
The entry still said "project-scope", which includes the nested CLAUDE.md and CLAUDE.local.md files C6 never reads. It now matches the criteria file: root-level project, with nested files named alongside the ~/.claude and auto-memory cases.
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 2s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 74f8cd7c2a
ℹ️ 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".
| - Does not grade a contradiction whose two halves both sit in project-scope `CLAUDE.md` / | ||
| `CLAUDE.local.md` / `.claude/rules/**` — that is `claude-memory:audit`'s C6. |
There was a problem hiding this comment.
Keep nested project memory pairs in I15
When either half is a nested project CLAUDE.md or CLAUDE.local.md, this exclusion still routes the pair to C6 because nested files are also project-scope. Fresh evidence after the earlier routing fix is this remaining broad clause: reference/conflict-criteria.md:51-57 now limits C6 to root-level files because its discovery uses find . -maxdepth 1, so following this skill-level instruction still causes nested conflicts to be graded by neither pass.
Useful? React with 👍 / 👎.
| # never use Y" reads the same as ", but") and REQUIRED for `while`, which is a | ||
| # temporal conjunction as often as a contrastive one ("use X while the flag is | ||
| # set" must not split). | ||
| BOUNDARY_ERE='([.;!?] |[^a-z],? *(but|whereas|though|although|yet)[^a-z]|, while[^a-z])' |
There was a problem hiding this comment.
Separate coordinated directives before classifying polarity
When opposite directives are coordinated with and rather than a listed contrastive, they still share polarity: Always use Readand never useBash for file inspection. classifies Read as prohibited because its trailing window contains never, so pairing it with Never use Read`` makes --count return 0. Fresh evidence beyond the resolved `but` case is this common `and never` form; recognize a coordinated new directive without treating every ordinary `and` as a boundary.
Useful? React with 👍 / 👎.
| - `${CLAUDE_PLUGIN_ROOT}/skills/audit-automation-gaps/scripts/inventory.sh` — jq | ||
| - `${CLAUDE_PLUGIN_ROOT}/skills/audit-permission-grants/scripts/permission-rule-check.sh` — jq | ||
| - `${CLAUDE_PLUGIN_ROOT}/skills/audit-instructions/scripts/instruction-scan.sh` — grep only (POSIX; no jq) | ||
| - `${CLAUDE_PLUGIN_ROOT}/skills/audit-instructions/scripts/conflict-scan.sh` — awk **and** sort (no jq) |
There was a problem hiding this comment.
Document awk and sort in the setup remediation source
On a minimal environment missing awk or sort, setup now correctly reports a failure, but its apply action explicitly sources install guidance from the README Requirements section, which still says the bundled scripts require only jq and additionally curl; the explicit remediation list likewise covers only those two tools. Fresh evidence after the earlier setup-check fix is that consumers are now told about these failures without the promised platform remediation, so add the new prerequisites to the public Requirements and apply guidance.
Useful? React with 👍 / 👎.
main took claude-config 0.11.0 via #1343, which landed the cross-surface conflict check as I15 -- so this branch's 0.11.0 is now behind main and re-ladders to 0.12.0. The manifest and both READMEs keep BOTH descriptions rather than either side's: audit-instructions detects cross-surface instruction conflicts (main's addition) AND audit-pass exists as the fifth skill (this branch's). Taking either side alone would have silently dropped a shipped capability from the text consumers read during skill selection. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AdF74i18gkGtHC9fjZyDrU
…instructions and setup (#2003) ## Summary Discharges nine verifier-confirmed review findings against `claude-config`'s `audit-instructions` and `setup` skills, and bumps the plugin to `0.21.10` with a changelog entry. Seven of the nine are instruction-surface defects in `audit-instructions` — a skill that audits other people's instruction surfaces and was violating its own rules on three of them. One is a scanner false negative reproduced and fixed with tests. One is a prerequisite the docs understated by two skills. ## What changed, per finding | # | Complaint | Discharge | |---|---|---| | 1 | The skill forbids hardcoding `~/.claude`, then hardcodes it | `SKILL.md:45`, `:75`, `:197`, `:219-222` now resolve against the user root Phase A establishes | | 2 | I3 rejects `@path` imports as non-deferring, then names a `skills:` preload as a valid destination | `criteria.md:196-201` strikes the preload; only conditional runtime invocation qualifies | | 3 | A subagent's own `memory` is graded real but never inventoried | Inventory bullet at `SKILL.md:216-229`; co-residency row at `conflict-criteria.md:98` | | 4 | The liveness gate resolved a closed five-input list with no hook enablement | `SKILL.md:175-185` and `conflict-criteria.md:270-292` resolve `disableAllHooks` per scope plus `allowManagedHooksOnly` | | 5 | A nested project memory pair routed to a check that cannot discover the file | `SKILL.md:413-417` narrows the boundary to **root-level** project; `.claude/rules/**` deliberately unchanged | | 6 | `BOUNDARY_ERE` omits `and`, dropping a real conflict | `COORD_ERE` / `COORD_HEAD_ERE` at `conflict-scan.sh:131-143`, three new test cases | | 7 | The requirements list names only `jq`/`curl` | `README.md:163-177` and `setup/SKILL.md:28-33,46-52,106-108` name `awk`/`sort` across all three skills that use them | | 8 | I14's startup set omits `./.claude/CLAUDE.md` | `criteria.md:612-621` covers both supported root locations | | 9 | I14's supporting-document exemption ignores startup `@path` imports | `criteria.md:636-642` resolves imports first, to four hops | ### Finding 6, reproduced `conflict-scan.sh --count` on the finding's exact strings: | Case | `origin/main` | this branch | |---|---|---| | "Always use `Read` and never use `Bash`" vs "Never use `Read`" | 0 | **1** | | "Always use `Read` but never use `Bash`" vs "Never use `Read`" | 1 | 1 | A **bare** `and` boundary would also return 1 for the first row while creating a new false negative: "Never use `Bash` and `Grep`" is one directive over two objects, and cutting at the coordinator strips the `never` governing `Grep`. The boundary therefore requires a polarity token after `and`, and is consumed asymmetrically — a leading window resumes after the coordinator alone so that token still classifies its entity. Case 35 is the must-not-flag test and fails under a bare-`and` boundary. ### Citations re-verified against the live docs Every citation this batch introduced was diffed character-for-character against the raw markdown of `hooks`, `memory`, and `sub-agents` (fetched 2026-08-08). Three did not survive and were corrected: - **`disableAllHooks` has no documented "own settings level and below" cascade.** The docs say "Disable all hooks", with exactly one carve-out: set in user, project, or local settings it cannot reach managed hooks. The earlier wording invented a directional cascade. Rewritten. - **The `skills:` citation was a splice** of the frontmatter-table sentence and the body sentence, quotable as neither. Replaced with the real one: "The full content of each listed skill is injected into the subagent's context at startup." - **Imports recurse "with a maximum depth of four hops"** — the text pointed at a "hop limit" the page never names. Now states the figure and quotes the wording. Also corrected: the `AGENTS.md` import is *recommended* (a symlink is a co-equal alternative, and the import is mandated only on Windows), not prescribed; subagent memory paths take upstream's `<name-of-agent>` placeholder; and the auto-memory gate now names what the subagent actually loses — "the memory instructions or the memory tool access". ### Also fixed in passing `conflict-scan.test.sh:378` had unescaped backticks inside a double-quoted assertion message, so the suite ran `and` as a command (`and: command not found` on stderr) and printed the label with a hole in it. Exit status stayed 0, so no gate saw it. Now single-quoted, matching the file's existing idiom. ### Deliberately not changed - `${CLAUDE_CONFIG_DIR:-~/.claude}` — that form *is* the correct resolver, not a hardcode. - `~/.claude` inside quoted upstream text (`criteria.md:651`, `conflict-criteria.md:111`) — altering a quotation is a worse defect than the one it would resolve. - `.claude/rules/**` routing to `claude-memory`'s C6 — C6's rules discovery is recursive, so nested rules do not leak. Finding 5 is narrower than filed. ## Testing - `conflict-scan.test.sh` — 46/46, no stderr noise - All 7 `claude-config` plugin tests — PASS - `check-changelog-parity.sh` `--check` / `--check-bump origin/main` / `--check-order` — PASS - `check-changed-skills.sh origin/main` — 4 skills, 0 failed (`audit-instructions` 281/500 lines) - `check-shell-portability.sh` / `check-skill-portability.sh` — PASS - `shellcheck`, `markdownlint-cli2`, `typos` — clean - `validate-plugins.sh`, `check-contract-slice-prune.sh`, `check-contract-clause-coverage.py`, `check-silent-skips.sh`, `check-cross-plugin-source-drift.sh`, `check-skill-leaf-names.sh`, `check-orphaned-fixtures.sh` — PASS ## Related Discharges review findings filed on #1316, #1343, and #1349. No linked issue --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
No linked issue
Summary
The official memory docs mandate this review and name no mechanism that performs it:
This adds Phase B2 to
claude-config'saudit-instructions: a pairwise pass over two surfacesthat both claim authority over one behavior and disagree. Phase B fans out one lane per surface, so a
lane sees only one half of a pair. Phase A's and Phase B's contracts are unchanged; B2 consumes Phase
A's inventory and re-enumerates nothing.
A prior boundary document recorded a false negative — corrected here
An earlier boundary analysis concluded this repo had no incumbent conflict detector, and a
repo-wide search claim supported that. That conclusion was wrong, and this PR does not rely on it.
claude-memory'sauditskill already ships a contradiction check. The corrected position wasre-verified first-hand against the tree before building:
plugins/claude-memory/skills/audit/reference/criteria.md— check C6 Consistency, "Do anyinstructions contradict each other across CLAUDE.md, CLAUDE.local.md, and rules files?", grading a
contradiction FAIL and redundancy WARN.
plugins/claude-memory/skills/audit/context/audit.md— "Step 3: Cross-file consistency check(C6)", whose first instruction is "Compare CLAUDE.md sections against
.claude/rules/forcontradictions". A live pairwise step, not a stray reference.
plugins/claude-memory/skills/audit/SKILL.md— the determinism contract places C6 in the judgmenttier.
Why three searches missed it: no skill advertises conflict detection.
claude-memory:audit'sdescription sells "memory health" and never mentions contradiction, so C6 is reachable only by reading
the skill's reference file. That is itself a description-versus-body divergence — the exact finding
class this pass detects — and it ships as worked example 2 in the criteria.
Boundary against C6 — extend, never re-implement
CLAUDE.md/CLAUDE.local.md/.claude/rules/**/ auto-memory~/.claude/rules/is ruled outside C6's population, settled from the files rather than assumed:the audit's discovery step enumerates rules project-relative (
find .claude/rules), and the pluginresolves a user-level directory only for auto-memory under
~/.claude/projects/<slug>/, never forrules. A user-global rule contradicting a project rule is therefore B2's.
Why a phase here, and not a sibling skill
Two reasons, neither of which is a duplicated-inventory argument — Phase A enumerates surface paths,
not contents, so a sibling would duplicate a cheap path walk, and that argument does not carry the
decision:
skill listing. That listing is already measured at ~83,270 characters against a 40,000-character
budget at 1M context (8,000 at 200k), with 95 of 130 model-invocable skills already dropped to
name-only. A new phase inside an existing skill adds zero listing entries; widening that
skill's description costs a few words. Adding an entry to a listing already 2–10× over budget, in
the effort convened to reduce instruction surface, would be self-defeating.
audit-instructionsalready enumerates every surface a conflict can span — userand project
CLAUDE.md,.claude/rules, skill bodies, agent definitions, prompt-type hooks,output styles. "These two instruction surfaces contradict each other" is an instruction-audit
finding by construction.
claude-memorywas considered and rejected as the home: its own scope table routes settings, hooks,MCP, agents, and skills out to
claude-config. Widening it into surfaces it explicitly hands offwould adopt a boundary the owning plugin already declined.
Precedence is cited, never invented
Where the docs state an order, the pass names the winner and its source. Where they are silent — user
vs project
CLAUDE.md, skill body vs anyCLAUDE.md, managed policy vs lower scopes — it reports thepair
unresolved. "Loads before" is load ordering, not override semantics; the same page says alldiscovered files are "concatenated into context rather than overriding each other."
False positives are the failure mode
A report-only auditor that cries wolf is worse than none, so the criteria carry a 13-case
must-not-flag set, seven of them pinned executable. The sharpest live case is a mandate and a
prohibition on the same tool in one file, where the prohibition's own exception clause satisfies
the mandate — gates 3 and 4 both fail, and the pre-scan drops it.
Why no CI lane ships here
conflict-scan.shderives entities by CamelCase shape rather than a hardcoded tool list. That isrecall over precision, not strictly better: proper nouns (
GitHub,PowerShell,EventStorming)match the same shape and account for 121 of the 169 rows a repo-wide run returns. 28% precision is
a good review queue and a bad gate — gates 2 and 5 carry the discrimination and both need a model.
This plugin is report-only regardless, so nothing landing here can block a merge. One conflict class
is gate-grade and is routed rather than built: the Type D split-brain check (an
AGENTS.mdthatis neither a symlink to
CLAUDE.mdnor@-imported by it is never loaded, per the memory page). Thisrepo currently fails it —
AGENTS.mdis a 1,261-byte regular file andCLAUDE.mdcarries zero@imports — so shipping the gate here would red-wall
mainand its remediation is a separate operatorcall. Recorded for #445's
scripts/check-*.sh+.test.sh+ci.ymllane shape.The inherited draft of the script spawned 3–4 subprocesses per entity mention and did not finish in
10 minutes over this repo's skill bodies. Classification and pairing now run in one
awkpassbucketed by entity: 2.4 seconds on the same corpus, same output contract.
Test plan
scripts/conflict-scan.test.sh— 24/24 pass, including every pre-scan-decidable must-not-flag case.skill-quality:check audit-instructions— PASS, 0 errors, 0 warnings. The one line-count warningwas fixed by trimming duplicated prose, not waived.
shellcheck— clean on both scripts.markdownlint-cli2— 0 errors across the changed markdown.hygiene,plugin-gate, andci-status. Two failures were fixed at theroot: the new scripts needed the exec bit (
100644→100755, matching their siblings), and thegenerated README catalog block needed regenerating after the
plugin.jsondescription change.Related
check-skill.shanddocs/PLUGIN-PHILOSOPHY.mddeliberately untouched (PR feat(skill-quality): fresh-eyes delegation doctrine + conformance gate (check 21) #1096 owns them).