Skip to content

feat(claude-config): detect cross-surface instruction conflicts - #1343

Merged
kyle-sexton merged 17 commits into
mainfrom
feat/cross-surface-conflict-detector
Jul 25, 2026
Merged

feat(claude-config): detect cross-surface instruction conflicts#1343
kyle-sexton merged 17 commits into
mainfrom
feat/cross-surface-conflict-detector

Conversation

@kyle-sexton

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

Copy link
Copy Markdown
Contributor

No linked issue

Summary

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)

This adds Phase B2 to claude-config's 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. 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's audit skill already ships a contradiction check. The corrected position was
re-verified first-hand against the tree before building:

  • plugins/claude-memory/skills/audit/reference/criteria.md — check C6 Consistency, "Do any
    instructions 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/ for
    contradictions". A live pairwise step, not a stray reference.
  • plugins/claude-memory/skills/audit/SKILL.md — the determinism contract places C6 in the judgment
    tier.

Why three searches missed it: no skill advertises conflict detection. claude-memory:audit's
description 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

Pair Owner
Both halves in project CLAUDE.md / CLAUDE.local.md / .claude/rules/** / auto-memory C6
At least one half outside that set Phase B2

~/.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 plugin
resolves a user-level directory only for auto-memory under ~/.claude/projects/<slug>/, never for
rules. 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:

  1. Listing budget. A sibling skill adds a whole name-plus-description entry to the model-invocable
    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.
  2. Semantic fit. audit-instructions already enumerates every surface a conflict can span — user
    and 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-memory was 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 off
would 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 any CLAUDE.md, managed policy vs lower scopes — it reports the
pair unresolved. "Loads before" is load ordering, not override semantics; the same page says all
discovered 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.sh derives entities by CamelCase shape rather than a hardcoded tool list. That is
recall 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.md that
is neither a symlink to CLAUDE.md nor @-imported by it is never loaded, per the memory page). This
repo currently fails it — AGENTS.md is a 1,261-byte regular file and CLAUDE.md carries zero @
imports — so shipping the gate here would red-wall main and its remediation is a separate operator
call. Recorded for #445's scripts/check-*.sh + .test.sh + ci.yml lane 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 awk pass
bucketed by entity: 2.4 seconds on the same corpus, same output contract.

Test plan

  • scripts/conflict-scan.test.sh24/24 pass, including every pre-scan-decidable must-not-flag case.
  • skill-quality:check audit-instructionsPASS, 0 errors, 0 warnings. The one line-count warning
    was fixed by trimming duplicated prose, not waived.
  • shellcheck — clean on both scripts.
  • markdownlint-cli2 — 0 errors across the changed markdown.
  • Full CI green, including hygiene, plugin-gate, and ci-status. Two failures were fixed at the
    root: the new scripts needed the exec bit (100644100755, matching their siblings), and the
    generated README catalog block needed regenerating after the plugin.json description change.
  • All official-docs quotes re-fetched and confirmed verbatim this session.
  • Nothing suppressed anywhere.

Related

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

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

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


Code Review

  • Read CLAUDE.md / AGENTS.md guidelines
  • Read changed files
  • Review conflict-scan.sh
  • Review conflict-scan.test.sh
  • Review conflict-criteria.md
  • Review SKILL.md
  • Review plugin.json and CHANGELOG.md
  • Synthesize findings

Overall

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


conflict-scan.sh

mapfile requires bash 4+ — the sharpest portability issue in the PR.

Line 115:

mapfile -t rows < <(

mapfile is a bash 4.0 builtin. macOS ships bash 3.2 as the default, and #!/usr/bin/env bash resolves to it. On 3.2, the user gets mapfile: command not found — the kind of opaque failure that wastes time. The Gotchas section in SKILL.md names Git Bash for Windows but says nothing about macOS. Either add a version gate to the script:

if (( BASH_VERSINFO[0] < 4 )); then
  echo "ERROR: bash 4+ required" >&2
  exit 2
fi

…or add a macOS note to the Gotchas section (alongside the Git Bash line) so operators know to brew install bash and invoke with the absolute path. Fix this →

awk check asymmetry.

Lines 73–76 guard on grep and exit 2. awk is equally required and equally not guaranteed (e.g. minimal container images), but there is no corresponding guard. The test suite already skips on missing awk (lines 48–51), so the harness covers it — but the script itself would fail with a less clear error. The instruction-scan.sh sibling may set a precedent here; a consistent guard would close the gap.

MANDATE_ERE includes ask and use — high recall, known tradeoff.

Line 98:

MANDATE_ERE='[^a-z](must|always|mandator(y|ily)|require[ds]?|shall|use|present|ask)[^a-z]'

use and ask are high-frequency words in instruction prose and will contribute to the proper-noun noise the PR already quantifies (121/169 rows). The CHANGELOG and criteria doc both acknowledge the recall-over-precision tradeoff, so this is noted rather than flagged — but the lane's first refinement step (entity triage) will need to be the primary noise filter.

Window arithmetic is correct. The prewindow/window split in classify (lines 152–156), the pad-offset accounting (ws + 1 because pad = " " tolower(line) " " shifts positions by one), and the end-of-string truncation behavior of awk's substr all check out.

lineno reset is safe. lineno = 0 is set after close(file) in the main awk rule (line 160). If getline returns -1 (unreadable file that passed the -f check in bash), the while loop exits early and lineno resets correctly for the next file. No bug.


conflict-scan.test.sh

Test count matches PR claim. Tallied 24 assertions across all 16 cases, consistent with the "24/24 pass" claim.

Must-not-flag coverage is the right priority. Cases 3–9 each pin a distinct suppression class with a concrete fixture. This is the correct test strategy for an advisory pre-scan — false positives are more damaging than false negatives.

Case 5 (trailing) checks absence but not completeness. Lines 101–102 assert that the output does not contain |AskUserQuestion|. Since the fixture file has only one entity, any unexpected match would be on that entity, so the assertion is effectively complete — but a companion assert_eq "..." 0 "$(bash "$SCRIPT" --count ...)" would make the intent explicit and catch an unexpected match on a differently-named entity if the fixture ever gains more content.

Case 16 (missing grep) is well-constructed. Using "$real_bash" with a stripped PATH cleanly isolates the test without interfering with the test harness itself.


conflict-criteria.md

C6 cross-reference resolves correctly. The relative path ../../../../claude-memory/skills/audit/reference/criteria.md navigates from plugins/claude-config/skills/audit-instructions/reference/ to plugins/claude-memory/skills/audit/reference/criteria.md, which exists. ✓

Precedence table is accurate and conservatively scoped. The "unresolved" entries for user CLAUDE.md vs project CLAUDE.md and skill body vs any CLAUDE.md are correct — the docs state concatenation, not override, and "loads before" is load ordering, not priority. Citing the lack of a winner rather than inferring one is the right posture.

Case 8 / deference clause is the right escape hatch. The handling (treat as resolved, use as remediation template for Type C) is consistent with the arbitration logic elsewhere.


SKILL.md

Phase B2 contract is clean. The placement (between Phase B and Phase C), the "consumes Phase A's inventory" note, the advisory pre-scan invocation pattern, and the "detect; do not adjudicate" instruction all fit the existing skill structure without breaking Phase A/B/C contracts.

conflicts scope is a useful composition primitive. Allows a scheduled hygiene routine to skip the full per-surface fan-out and pay only for Phase A + B2. The argument table in the Arguments section is updated correctly.

Windows Gotcha is the only mention of shell requirements. The macOS/bash 4+ gap noted above applies here as well.


plugin.json / CHANGELOG.md

  • Version bump 0.9.2 → 0.10.0 is appropriate for a new phase and new scope argument.
  • Description update correctly names "detects cross-surface instruction conflicts".
  • CHANGELOG entry covers all shipped artifacts (B2, criteria doc, script, scope argument) and the claude-memory C6 boundary. The CI/precision rationale paragraph is a useful record for future maintainers.

Summary

One actionable gap (bash 4+ not enforced or documented for macOS), one asymmetry worth closing (missing awk guard to match the grep guard), and two observations (mandate token recall, trailing-prohibition test completeness). The core design — windowed polarity, single awk pass bucketed by entity, must-not-flag pinning — is sound, and the advisory contract is correctly honored throughout.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

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


Security Review — PR #1343

  • Load tools
  • Fetch PR diff
  • Read changed files
  • Analyze for security vulnerabilities
  • Post findings

Scope: conflict-scan.sh, conflict-scan.test.sh, SKILL.md, conflict-criteria.md, plugin.json, CHANGELOG.md

No CRITICAL or IMPORTANT findings. Two low-severity observations below.


SUGGESTION — Confidence: medium

Unvalidated env variable CONFLICT_SCAN_WINDOW silently collapses to a zero-width window

conflict-scan.sh L111

CONFLICT_SCAN_WINDOW is read without validation and forwarded to awk via -v w="$WINDOW_CHARS". AWK's -v interprets escape sequences in the value, and non-integer values (e.g. \n, abc, -1, an empty string set by accident in CI) cause awk's substr(pad, ws+1, ...) calls to treat w as 0. A zero-width window means no polarity tokens can appear near any entity match, and the scan returns zero candidate rows — complete false-negative suppression — with no error or warning emitted. The script is advisory and always exits 0, so there is no gate to trip; the failure mode is silent uselessness rather than code execution. But a CI environment or a .envrc that exports CONFLICT_SCAN_WINDOW with a non-numeric value would silently defeat the pre-scan.

Suggested guard (before the mapfile call):

[[ "$WINDOW_CHARS" =~ ^[1-9][0-9]*$ ]] || WINDOW_CHARS=60

SUGGESTION — Confidence: low

Filenames containing | produce structurally malformed output rows

conflict-scan.sh L176

The awk END block concatenates file paths directly into the output row:

print a[1] ":" a[2] "|" b[1] ":" b[2] "|" ent "|" flags

A path containing a literal | (valid on Linux/macOS) embeds an extra pipe, making the row's field count variable. The immediate consumer is a Claude model that can handle irregular structure, but any downstream tooling that parses the four-field format by splitting on | would misparse the row. No code execution is possible. Real-world risk is low since | in config file paths is unusual, but it would be worth noting the assumption in a comment or guarding it with a gsub(/\|/, "%7C", a[1]) normalization step.


Everything else checked and clean:

  • No eval, no dynamic command construction from file content
  • All $@ paths gate through [[ -f "$file" ]] before reaching awk's getline; no path traversal to unintended files
  • ERE patterns are compile-time string literals; none are derived from scanned file content
  • File content is read as data only — CamelCase entity extraction via awk pattern matching cannot produce awk code execution
  • Scripts are advisory-only, always exit 0, never write or modify any file
  • SKILL.md is explicitly report-only with no --fix path and no auto-apply mechanism
  • ${CLAUDE_PLUGIN_DATA} is used (not a hardcoded ~/.claude/...) for report persistence
  • mapfile reads from a process substitution (< <(...)), not from eval
  • Test script uses mktemp -d with a proper EXIT trap for cleanup; PATH manipulation in Case 16 is correctly scoped to a subshell with the resolved real_bash path held before the scope change

@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: 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".

Comment thread plugins/claude-config/skills/audit-instructions/reference/conflict-criteria.md Outdated
Comment thread plugins/claude-config/skills/audit-instructions/scripts/conflict-scan.sh Outdated
Comment thread plugins/claude-config/skills/audit-instructions/reference/conflict-criteria.md Outdated
…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

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

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


Code Review — PR #1343

  • Load tools
  • Review latest commit diff (680bb57 — exec bit + README)
  • Read all changed files
  • Verify Codex review findings against source
  • Synthesize and post findings

Latest commit (680bb57)

Correctly fixes the exec-bit gap and updates the generated catalog description. Both changes are mechanical and accurate — the description update correctly reflects the added conflicts scope and the new conflict-detection capability.


Codex finding verification

I read plugins/claude-memory/skills/audit/reference/criteria.md lines 107–116 to ground-truth the Codex comments before reporting on them.

Codex P2 · conflict-criteria.md:46 — Auto-memory routing gapConfirmed valid.

C6's definition is explicit: "Do any instructions contradict each other across CLAUDE.md, CLAUDE.local.md, and rules files?" Auto-memory (MEMORY.md) is not listed. The routing table in conflict-criteria.md places a MEMORY.md-vs-CLAUDE.md pair in the C6 bucket, but C6 will not check it. Phase A also excludes auto-memory from the editable set. The result is that auto-memory contradictions with project CLAUDE.md or rules fall through both auditors.

Fix: remove auto-memory from the left-hand cell of the routing table. A pair where one half is MEMORY.md against a CLAUDE.md or rule is cross-layer by the same logic as ~/.claude/rules/ vs project rules — B2's territory.

Fix this →

Codex P2 · conflict-criteria.md:104 — Type D / Gate 1 contradictionConfirmed valid.

Gate 1 requires "the two surfaces can be resident simultaneously." Type D states "only one is loaded." A pair that fails Gate 1 is explicitly removed from the finding set ("any gate failing removes it"). Type D therefore can never be a conflict finding under the criteria's own rules, yet it is listed alongside Types A–C in the conflict taxonomy. This creates contradictory instructions for the lane: either it false-flags non-co-resident pairs, or Type D is structurally unreachable.

Codex's suggestion is correct: Type D describes orphaned instruction drift — a real problem, but not a conflict in the sense the five gates define. It should either be reclassified under its own heading (outside the five-gate taxonomy) or removed entirely, with a pointer to the remediation route.

Fix this →

Codex P2 · conflict-scan.sh:128 — Postposed prohibitions misclassified as mandatesValid limitation, intentional design.

The code checks prewindow ~ prohibit (text before the entity) and, only on failure, window ~ mandate (text around it). A prohibition that follows the entity — "WebFetch must not be used for documentation" — puts must not in the post-entity part of the window, so prewindow ~ prohibit fails and window ~ mandate picks up must (a mandate token), classifying the line as a MANDATE. Two MANDATE records for the same entity across two files yields no pair; the contradiction is silently missed.

The design comment ("a prohibition trailing the entity almost always governs a different object") is accurate for the specific prose pattern the test fixtures target, but does not cover the direct form Entity must not. A narrow post-window prohibition check — e.g. checking the immediately-post-entity text (not the full trailing window) against the prohibition ERE, so the wider trailing window still avoids the "via X… Do not gate" false positive — would close this class. That said, the scan is explicitly advisory and optimized for precision over recall; this is a known tradeoff, not an oversight. Worth noting for the lane's first refinement step.


Prior review items still open

The previous Claude review identified four items. The exec-bit commit does not address them; they remain outstanding:

  1. mapfile requires bash 4+ (conflict-scan.sh:115) — macOS ships bash 3.2 as the default. A version gate or a Gotchas note is needed. Fix this →

  2. Missing awk guardawk is equally required as grep but has no command -v check. The test suite skips on missing awk (lines 48–51) but the script itself would produce an opaque failure. Fix this →

  3. CONFLICT_SCAN_WINDOW unvalidated — a non-numeric value silently produces a zero-width window, causing the scan to return zero candidates with no error. One guard: [[ "$WINDOW_CHARS" =~ ^[1-9][0-9]*$ ]] || WINDOW_CHARS=60

  4. Filenames with | produce malformed rows — low real-world risk, worth a comment if not a guard.


Summary

The exec-bit commit is clean. Two of the three Codex findings are confirmed valid issues in conflict-criteria.md — the auto-memory routing gap (MEMORY.md conflicts are routed to C6 which doesn't check them) and the Type D / Gate 1 contradiction (Type D is unreachable under the five-gate taxonomy). The postposed-prohibition case is a genuine false-negative class but aligns with the documented precision-over-recall tradeoff. The four prior review items remain open. None of these block the advisory function of the skill, but items 1 (auto-memory gap) and 2 (Type D contradiction) are documentation correctness issues worth fixing before merge.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

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


Security Review — PR #1343

  • Load tools
  • Fetch PR diff
  • Read changed files
  • Analyze for security vulnerabilities
  • Post findings

Scope: conflict-scan.sh, conflict-scan.test.sh, SKILL.md, conflict-criteria.md, plugin.json, CHANGELOG.md, README.md

Head SHA: 680bb57b760805cc0e6e95c8391a697c806655d9

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

CONFLICT_SCAN_WINDOW with a non-numeric value silently produces zero candidates

conflict-scan.sh L111

CONFLICT_SCAN_WINDOW is forwarded to awk via -v w="$WINDOW_CHARS" without validation. AWK's -v applies escape-sequence expansion, and a non-numeric value (empty string, abc, -1, or an accidental CI export like \n) causes all substr() width arguments to evaluate to 0, shrinking the polarity window to nothing. The scan then exits 0 emitting "No conflict candidates found." — complete false-negative suppression with no diagnostic. Because this is an advisory tool that always exits 0, no gate trips and the silent failure goes unnoticed.

Suggested guard before the mapfile call:

[[ "$WINDOW_CHARS" =~ ^[1-9][0-9]*$ ]] || WINDOW_CHARS=60

SUGGESTION — Confidence: low

File paths containing | produce structurally malformed output rows

conflict-scan.sh L176

The awk END block concatenates raw file paths into the four-field |-delimited output:

print a[1] ":" a[2] "|" b[1] ":" b[2] "|" ent "|" flags

A path containing a literal | (valid on Linux/macOS) embeds an extra field delimiter. The entity field itself is safe — the CamelCase regex [A-Z][a-z]+([A-Z][a-z]*)+ matches only letters, so entity content cannot inject extra delimiters. Flags are hardcoded strings. The only injection point is the filename. The immediate consumer is a Claude model that handles irregular structure, and real-world risk is low since | in config file paths is unusual; however, any downstream tooling that splits on | would misparse such rows.


Everything else checked and clean:

  • No eval, no system() call, no | getline from file content — file content is processed as pure data only
  • ERE patterns are compile-time string literals; none are derived from scanned file content — no regex injection
  • $@ paths are gated by [[ -f "$file" ]] before reaching awk's getline; awk then opens files directly, not via shell; no path traversal or command injection
  • awk -v entpat/prohibit/mandate/exception/gated all receive hardcoded script literals, not user input
  • POSIX awk -v escape-sequence expansion does not apply to the ERE pattern strings (none contain backslash sequences); only WINDOW_CHARS is user-supplied, and it's used as a number, not as a pattern
  • mapfile -t rows < <(...) reads awk stdout; the process substitution executes no user-derived code
  • printf '%s\n' "${rows[@]}" is safe for any array element contents
  • sort -u receives awk's text output through a pipe; no injection surface
  • Test script uses mktemp -d (mode 0700) with a proper EXIT trap; no world-readable temp files
  • Case 16's PATH=... manipulation is correctly scoped to a subshell with real_bash captured before the scope change
  • ${CLAUDE_PLUGIN_DATA} is used for report persistence (not a hardcoded ~/.claude/...)
  • SKILL.md marks the pass as report-only with no --fix path and no auto-apply mechanism
  • No network calls, no remote egress, no secrets handling in any changed file
  • No GitHub Actions workflows changed

@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: 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".

Comment thread plugins/claude-config/skills/audit-instructions/scripts/conflict-scan.sh Outdated
Comment thread plugins/claude-config/skills/audit-instructions/scripts/conflict-scan.sh Outdated
Comment thread plugins/claude-config/skills/audit-instructions/reference/conflict-criteria.md Outdated
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

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

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


Code Review — PR #1343 (head f125c59)

  • Read CLAUDE.md guidelines
  • Review latest commits diff (b8ce5d4 — seven findings, f125c59 — line budget)
  • Read all changed files in current state
  • Verify prior review findings against current source
  • Synthesize and post findings

What landed in the two latest commits

b8ce5d4 addressed all seven findings flagged across the prior review rounds. Reading against the current file state, each fix is correctly applied:

  • Auto-memory routing gapconflict-criteria.md:50–56: routing table now correctly routes any pair with a ~/.claude/ side or auto-memory MEMORY.md to I12 rather than C6. The reasoning paragraph is accurate — C6's check text names only CLAUDE.md, CLAUDE.local.md, and rules files.

  • Type D / Gate 1 contradictionconflict-criteria.md:135–140: split-brain correctly reclassified as orphaned instruction drift, outside the five-gate conflict taxonomy. The conflict types are now A–C only.

  • Residency table coverageconflict-criteria.md:85–86: prompt-type hooks and output styles now have rows with explicit UNVERIFIED markers. Pairs involving them report as residency-unknown.

  • Postposed prohibitionconflict-scan.sh:139: prewindow ~ prohibit || postwindow ~ prohibit. The sentence-break truncation at lines 172–173 correctly prevents the trailing-clause false positive (Case 18 pins it).

  • Single-word backtick entityconflict-scan.sh:97: ENTITY_ERE now includes `[A-Z][a-z]+`; backticks stripped from entity name so bare and backticked mentions pair together (Case 21).

  • Opt-in conditional requirementconflict-scan.sh:112–113: CONDITIONAL_ERE added; suppression now requires both gated AND conditional to match. Case 22 pins the distinction.

  • Description-vs-body exampleconflict-criteria.md:272–284: correctly reclassified as a calibration case for a near-miss the gates reject (gate 3 fails — a description that omits a capability prescribes no incompatible action).

Test count grew from 24 to 30; cases 17–22 cover the three scanner fixes plus their must-not-flag companions.


Issues still open after f125c59

1. SKILL.md line budget — f125c59 claim does not hold

The commit is titled "keep SKILL.md inside its line budget." The current file is 204 lines. check-skill.sh warns at LINE_SOFT_CAP=200 (line 134 of check-skill.sh, condition at line 427). A skill-quality:check run against the current tree will produce a line-count WARN, not a clean 0-warnings pass. The fix trimmed 2 lines (206 → 204); 4 more need to come out.

Fix this →

2. mapfile requires bash 4+ — still unaddressed

conflict-scan.sh:124:

mapfile -t rows < <(

mapfile is a bash 4.0 builtin. macOS ships bash 3.2 as the default, and #!/usr/bin/env bash resolves to it. On 3.2 the user gets mapfile: command not found — the kind of opaque failure that wastes time to diagnose. The SKILL.md Gotchas section (line 189) only mentions Windows/Git Bash with no macOS note.

Either add a version gate after the grep guard:

if (( BASH_VERSINFO[0] < 4 )); then
  echo "ERROR: bash 4+ required (macOS default is 3.2 — brew install bash)" >&2
  exit 2
fi

…or at minimum add a sentence to the Gotchas: "macOS ships bash 3.2 by default — install bash 5 via Homebrew and invoke with its absolute path."

Fix this →

3. Missing awk guard — still unaddressed

The test file at lines 48–51 correctly skips when awk is absent. The script itself has a command -v grep guard but no matching command -v awk check. awk is equally essential and equally absent on minimal container images. The asymmetry is confusing to anyone reading the guards in parallel.

Fix this →

4. CONFLICT_SCAN_WINDOW not validated — still unaddressed

conflict-scan.sh:120: WINDOW_CHARS="${CONFLICT_SCAN_WINDOW:-60}" — forwarded to awk via -v w="$WINDOW_CHARS" without validation. A non-numeric export (empty string, abc, or accidental CI value) causes all substr() width arguments to evaluate to 0, silently returning zero candidates with no error. The guard is a one-liner:

[[ "$WINDOW_CHARS" =~ ^[1-9][0-9]*$ ]] || WINDOW_CHARS=60

Fix this →


Low-severity (noted, not blocking)

Filenames with |conflict-scan.sh:198: file paths are concatenated directly into |-delimited output. A path containing a literal | (valid on Linux/macOS) embeds an extra delimiter. The immediate consumer is a model that handles irregular structure, and such paths are rare in config file trees, so no change is blocking. A comment noting the assumption would prevent future confusion.


Summary

b8ce5d4 correctly resolves all seven flagged findings — the routing, taxonomy, residency coverage, and scanner correctness fixes all check out against the source. The test suite grew to 30 assertions and covers all three scanner fixes with both positive cases and their must-not-flag counterparts. Four prior findings remain unaddressed: the line-budget claim in f125c59 does not hold (204 lines, soft cap 200), and the bash 4+ portability gap, missing awk guard, and unvalidated window env var all carry over from the original review. Items 2–4 are one-to-three-liner fixes; item 1 needs 4 more lines trimmed from SKILL.md.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

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


Security Review — PR #1343

  • Read CLAUDE.md guidelines
  • Fetch PR diff
  • Read changed files (conflict-scan.sh, conflict-scan.test.sh, conflict-criteria.md, SKILL.md, plugin.json, CHANGELOG.md, README.md)
  • Analyze for security vulnerabilities
  • Post findings

Scope: 7 files changed across the 4 commits on this branch, HEAD f125c59813b63c1297bb4dadfdc2d87b7f44f834

No CRITICAL or IMPORTANT findings.


What was checked

Injection surfaces

  • conflict-scan.sh passes six regex patterns to awk via -v. All six (entpat, prohibit, mandate, exception, gated, conditional) are compile-time string literals defined with single-quote heredocs — no user-supplied content enters them.
  • WINDOW_CHARS (from CONFLICT_SCAN_WINDOW) is the only user-controlled value forwarded to awk. It is received via -v w="$WINDOW_CHARS", properly double-quoted against shell expansion. Within awk, w is used only in substr width arithmetic — it cannot inject awk code regardless of value.
  • awk -v does apply C-style escape-sequence expansion to the value, but the only user-supplied -v argument is w, which is used as a number. An escape sequence there collapses to 0, producing the already-documented silent false-negative — not a code-execution surface.
  • File content is processed by awk via getline line < file and matched against the hardcoded ERE patterns. It is never passed to system(), | cmd, or any awk code-execution primitive.
  • printf '%s\n' "${rows[@]}" receives awk's stdout through an array; %s formatting suppresses interpretation.

Path handling

  • Input paths come from $@, gated by [[ -f "$file" ]] && printf '%s\n' "$file" before reaching awk. This correctly accepts symlinks to regular files (by design — SKILL.md's scope includes installed plugin-cache content as read-only participants). The script never writes to any path.
  • The only write path referenced is ${CLAUDE_PLUGIN_DATA}/audit-instructions/last-audit.md in SKILL.md — it uses the plugin data variable, not a hardcoded user path.

The b8ce5d4 scanner additions specifically reviewed:

  • Backtick-stripped entity extraction: gsub(//, "", ent)` operates on an awk string; the stripped value is used only as an associative-array key. No code execution from entity content.
  • postwindow / sentence-boundary truncation: match(post, /[.;!?]/) applies a hardcoded literal pattern to file content treated as data. Safe.
  • CONDITIONAL_ERE added for opt-in suppression: hardcoded single-quoted literal, same analysis as the other ERE constants.

Test script (Case 16 PATH isolation)

real_bash=$(command -v bash) is captured before PATH="$empty_path_dir" scopes in. The subshell runs the real bash binary, not a PATH-resolved one. Correct.

GitHub Actions

No .github/workflows/ files changed in this PR.

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 valid

Both were already reported in the earlier automated security reviews on this PR; I list them here for completeness, not as new findings.

  1. CONFLICT_SCAN_WINDOW non-numeric value silently produces zero candidates (L120) — a CI export or .envrc that sets this to an empty string or non-number causes the window width to evaluate to 0 in awk, and the scan emits "No conflict candidates found." with exit 0. Because the script is advisory and always exits 0, no gate fires and the failure is silent. Not a code-execution surface; impact is a missed audit, not a security breach. Confidence: medium.

  2. Filenames containing | produce structurally malformed output rows (L198) — the awk END block concatenates raw file paths into |-delimited output without sanitizing them. A path containing a literal | adds an extra field delimiter. The entity field and flags are safe (CamelCase regex and hardcoded strings, respectively). No code execution; risk is limited to downstream tooling that splits on |. Confidence: low.

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

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

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


Code Review — PR #1343 (latest push)

  • Read CLAUDE.md guidelines
  • Review latest commit diff (b8ce5d4, f125c59, c3f606c)
  • Read changed files
  • Verify prior review findings against current source
  • Synthesize and post findings

Summary

b8ce5d4 closes seven of the open review findings across conflict-scan.sh, conflict-criteria.md, and SKILL.md. f125c59 / c3f606c trim SKILL.md to fit the line budget. All seven are resolved correctly. Three items from earlier reviews remain open.


Resolved — confirmed against current source

1. Postposed prohibitions (conflict-scan.sh)Fixed.
The new postwindow parameter passes post-entity text (from pad, already lowercased) to classify(). The caller truncates it at the first sentence-ending punctuation mark before passing it, so `WebFetch` must not be used classifies as a prohibition while a trailing clause past a . still does not. The mstart/mlen copy is essential here: match(post, /[.;!?]/) clobbers RSTART/RLENGTH, and the old code's loop advance would have used the wrong offsets. The copy lands before any subsequent match() call — correct.

Window offset accounting for postwindow: substr(pad, e + 2, w)e is the absolute entity-end in line (1-indexed); pad = " " tolower(line) " " shifts by one, so e + 2 is the character immediately following the entity in pad. Correct.

Case 17 and 18 in the test suite pin both halves (catches / must-not-flag). ✓

2. Single-word tool identifiers (conflict-scan.sh)Fixed.
ENTITY_ERE gains the `[A-Z][a-z]+` alternative. gsub(//, "", ent)` strips backticks so a backtick-wrapped and a bare CamelCase mention of the same tool share the same bucket key. Case 21 pins this pairing. Requiring backticks for single-word matches is the right discriminator — bare sentence-initial capitals (Case 20) are suppressed by the absence of backticks. ✓

3. Opt-in as subject vs. gated condition (conflict-scan.sh)Fixed.
CONDITIONAL_ERE is added, and suppression now requires window ~ gated && window ~ conditional. "Never use `X` for opt-in prompts" matches GATED_ERE (the opt-in token) but not CONDITIONAL_ERE (no "only when", "unless", "requires", etc.), so it is classified as a prohibition — not suppressed. Case 22 pins this. ✓

4. Auto-memory routing gap (conflict-criteria.md)Fixed.
The routing table now correctly reads "Anything else — including any pair with a ~/.claude/ side, and any pair involving auto-memory" belongs to I12. The justification (C6's check text names only "CLAUDE.md, CLAUDE.local.md, and rules files") is sourced correctly from the C6 criteria file. The old table's silent gap — routing MEMORY.md contradictions to C6, which never checks them — is closed. ✓

5. Type D / Gate 1 contradiction (conflict-criteria.md)Fixed.
Split-brain is removed from the conflict type taxonomy and reclassified as "orphaned instruction drift — not a contradiction today, but the state a contradiction grows out of." The gate 1 / Type D contradiction (a co-residency gate failure means the pair can never be a finding, so Type D was unreachable) is resolved correctly. ✓

6. Residency table incomplete for inventoried surfaces (conflict-criteria.md)Fixed.
The co-residency table now covers skill bundled reference//context/ files, agent definitions, skill fields in agent definitions, prompt-type hooks (UNVERIFIED), and output styles (UNVERIFIED). The UNVERIFIED entries are the right posture — the hooks and output-styles pages were not fetched, so reporting residency-unknown rather than guessing a load model is consistent with how the precedence table handles silent cases. Agent definitions co-loading with the full CLAUDE.md hierarchy is cited from the subagents "What loads at startup" page. ✓

7. Example 2 (same-file non-conflict) (conflict-criteria.md)Fixed.
The example is reclassified as "a near-miss the gates correctly reject — description-versus-body divergence." The analysis is accurate: gate 3 fails because omitting a capability does not prescribe an action incompatible with performing it. Keeping it as a calibration case (an auditor that grades summary omissions as contradictions will bury real findings) is more useful than removing it. ✓


Still open

Three 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 requires bash 4+ — not guarded or documented

conflict-scan.sh:124:

mapfile -t rows < <(

mapfile is a bash 4.0 builtin. macOS ships bash 3.2 by default; #!/usr/bin/env bash resolves to it. The Gotchas section in SKILL.md still names only Windows/Git Bash. An operator on macOS gets mapfile: command not found with no guidance. Either add a version gate (after the grep check):

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 awk guard

The script exits 2 when grep is absent (line 73–76) but has no matching command -v awk check. awk is equally required. The test suite already guards its own awk check (lines 48–51); the script itself would produce an opaque error from the pipe. Fix this →

3. CONFLICT_SCAN_WINDOW unvalidated (low severity)

conflict-scan.sh:120:

WINDOW_CHARS="${CONFLICT_SCAN_WINDOW:-60}"

A non-numeric value (empty string, abc, -1, or an accidental CI export like \n) causes awk's substr() width to evaluate to 0, shrinking the polarity window to nothing. The scan exits 0 reporting "No conflict candidates found" with no diagnostic — complete false-negative suppression. Suggested guard:

[[ "$WINDOW_CHARS" =~ ^[1-9][0-9]*$ ]] || WINDOW_CHARS=60

One minor observation

CONDITIONAL_ERE uses requires? (matching "require"/"requires") while MANDATE_ERE uses require[ds]? (also matching "required"). An instruction like "Use `X` when user_config.fetch is required" would match MANDATE_ERE (making it a mandate candidate) but not CONDITIONAL_ERE (failing the suppression check). This is a narrow edge case — "required" as a conditional gate marker is unusual phrasing — and the precision-first design documented in the PR accepts these kinds of gaps. Noted for the lane's first refinement step.


Overall

The 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.
| Branch

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

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


Security Review — PR #1343 (HEAD c3f606c)

  • Load tools
  • Review PR diff
  • Read changed files (conflict-scan.sh, conflict-scan.test.sh, conflict-criteria.md, SKILL.md, plugin.json, CHANGELOG.md)
  • Analyze for security vulnerabilities
  • Post findings

Scope: 7 changed files across 5 commits. HEAD c3f606ce7e782a986d2cd1813b7c9de257042608.

No CRITICAL or IMPORTANT findings.


What was checked — latest commits (b8ce5d4, f125c59, c3f606c)

New entity-matching logic (b8ce5d4)

ENTITY_ERE now includes `[A-Z][a-z]+` as a second alternative for backtick-wrapped single-word tools. The pattern is a compile-time shell string literal, passed to awk via -v entpat. Awk's -v applies C-escape-sequence expansion — backtick has no special meaning in that expansion set (\n, \t, \\, \", \a, \b, \f, \r). No injection surface.

gsub(//, "", ent)strips the backtick delimiters before using the entity as an associative-array key. After stripping, the entity string contains only[A-Za-z]characters — it is never passed tosystem(), | cmd`, or any awk execution primitive. Safe.

Dual-condition opt-in suppression (b8ce5d4)

GATED_ERE and CONDITIONAL_ERE are both compile-time constants. The two-condition check (window ~ gated && window ~ conditional) applies both against file content treated as data. No injection surface.

postwindow truncation (b8ce5d4)

post = substr(pad, e + 2, w)
if (match(post, /[.;!?]/)) post = substr(post, 1, RSTART - 1)

The regex /[.;!?]/ is a hardcoded literal; it is not derived from file content. The match() call applies it to post, a substring of the lowercased, space-padded line. No injection surface. Arithmetic (e + 2, RSTART - 1) operates on awk integers; no overflow concern.

New test cases 17–22 (b8ce5d4)

All use mktemp -d with the existing EXIT trap. Fixture content is heredoc-written. Case 16 PATH isolation (real_bash=$(command -v bash) captured before the stripped PATH) is unchanged and correct.

SKILL.md line-count fix (f125c59, c3f606c)

Prose edits only — removed duplicate sections to satisfy the line budget. No execution surface.


Two prior SUGGESTION-level findings — confirmed still open at HEAD

Both were reported in earlier automated reviews and remain unaddressed.

SUGGESTION — Confidence: medium

CONFLICT_SCAN_WINDOW with a non-numeric value silently produces zero candidates

conflict-scan.sh L120

WINDOW_CHARS="${CONFLICT_SCAN_WINDOW:-60}"

WINDOW_CHARS is forwarded to awk via -v w="$WINDOW_CHARS" without numeric validation. A non-integer value (empty string, abc, -1, a CI export like \n) causes every substr() width argument to evaluate to 0 in awk, shrinking the polarity window to nothing. The scan then exits 0 emitting "No conflict candidates found." — complete false-negative suppression with no diagnostic. The advisory-only / always-exit-0 contract means no gate fires and the silent failure goes unnoticed.

Suggested guard before the mapfile call:

[[ "$WINDOW_CHARS" =~ ^[1-9][0-9]*$ ]] || WINDOW_CHARS=60

Fix this →


SUGGESTION — Confidence: low

Filenames containing | produce structurally malformed output rows

conflict-scan.sh L198

print a[1] ":" a[2] "|" b[1] ":" b[2] "|" ent "|" flags

File paths are concatenated into the four-field |-delimited output without sanitization. The entity field is safe (the gsub(//, "", ent)strip plus the[A-Za-z]-only result of matching ENTITY_EREmeans no|can appear there); flags are hardcoded strings. A path containing a literal|— valid on Linux/macOS — embeds an extra delimiter. No code execution; risk is limited to downstream tooling that splits on|. Real-world exposure is low since |` in config file paths is unusual.


Confirmed clean

  • No eval, no system(), no | getline from file content, no dynamic command construction
  • All $@ paths gate through [[ -f "$file" ]] before reaching awk's getline; file content is processed as pure data
  • All six ERE patterns (entpat, prohibit, mandate, exception, gated, conditional) are compile-time string literals; none derive from file content or user input
  • mapfile -t rows < <(awk ... | sort -u) reads from a process substitution; no user-derived code is executed
  • printf '%s\n' "${rows[@]}" is safe for any array element content
  • Test script: mktemp -d with mode-0700 directory and EXIT trap; no world-readable temp files; Case 16 PATH isolation is correctly scoped
  • No network calls, no credential handling, no secrets referenced in any changed file
  • No .github/workflows/ files changed
  • ${CLAUDE_PLUGIN_DATA} used for report persistence (not a hardcoded ~/.claude/... path)
  • SKILL.md marks the pass report-only with no --fix path and no auto-apply mechanism

No new security findings at HEAD c3f606c.

@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: 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".

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

The source article is named with its URL rather than left as "a
practitioner article", so a reader auditing this ADR's premises can
reach the thing the digests measured. The digests themselves prune with
the contract slice, which is why the pointer replaces them rather than
supplementing them.
… 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.

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

@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: 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".

Comment thread plugins/claude-config/skills/audit-instructions/scripts/conflict-scan.sh Outdated
#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

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.

@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown

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.

@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown

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

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: 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".

Comment thread README.md
Comment thread plugins/claude-config/skills/audit-instructions/scripts/conflict-scan.sh Outdated
Comment thread plugins/claude-config/skills/audit-instructions/SKILL.md Outdated
…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

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude encountered an error after 3s —— 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: 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".

Comment thread plugins/claude-config/skills/audit-instructions/SKILL.md Outdated
Comment thread plugins/claude-config/skills/audit-instructions/SKILL.md Outdated
…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

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

(`~/.claude/projects/<project>/memory/`, owned by `claude-memory`), org-managed policy CLAUDE.md,

P2 Badge Resolve relocated auto-memory before conflict inventory

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

Comment thread plugins/claude-config/skills/audit-instructions/scripts/conflict-scan.sh Outdated
…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

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.

… 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

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.

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

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.

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

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.

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

@kyle-sexton
kyle-sexton merged commit 640487b into main Jul 25, 2026
26 checks passed
@kyle-sexton
kyle-sexton deleted the feat/cross-surface-conflict-detector branch July 25, 2026 23:44

@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: 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".

Comment on lines +199 to +200
- 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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])'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

kyle-sexton added a commit that referenced this pull request Jul 25, 2026
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
kyle-sexton added a commit that referenced this pull request Aug 9, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant