Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/claude-config/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "claude-config",
"version": "0.27.1",
"version": "0.27.2",
"description": "Seven configuration-health skills (plus setup) for a repo's Claude Code configuration: audit (settings.json / .mcp.json / hooks / plugins / permissions drift), audit-automation-gaps (evidence-gated verdicts on automation gaps), audit-permission-grants (allow-rule / allowed-tools grants for auto-mode durability and portability), audit-instructions (locally-owned instruction surfaces vs current model capability — proposes removals/rewrites of instructions the model no longer needs, and detects cross-surface instruction conflicts), audit-prompting-postures (the additive lane — posture guidance the prompting guide says a component's purpose needs but the component does not carry), audit-pass (one coordinated, ordered, resumable pass over a named target — three-scope inventory, run-time-derived exclusion set, stable finding identity, suppression memory, resume, one human gate — delegating every check to the plugin that owns it), and unhobble (the empirical bare-baseline experiment: reversibly strip a repo's standing instructions, log real stumbles against the current model, re-add only what evidence earns).",
"author": {
"name": "Melodic Software",
Expand Down
75 changes: 75 additions & 0 deletions plugins/claude-config/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,81 @@
All notable changes to the `claude-config` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.27.2]

### Fixed

- **`conflict-scan.sh`: the coordinated-directive boundary honored only a subset of the mandate
tokens, so the most common phrasing silently dropped real conflicts.** `COORD_ERE` carried a
hand-copied token list that had fallen behind `MANDATE_ERE`: `use`, `present`, and `ask` were in
the classifier and absent from the coordinator. ``Never use `Bash` and use `Read` `` therefore
found no boundary, `Read` inherited the leading `never`, and its pair with ``Never use `Read` ``
went unreported — while ``Always use `Read` `` produced a false conflict from the same misreading.
`always` masked the gap throughout, being present in both lists. The coordinator is now COMPOSED
from the two classifier alternations rather than restated, so the divergence that caused this is
unrepresentable; two regression cases cover the bare-`use` and `present` forms.

- **`audit-instructions`: the skill hardcoded `~/.claude` in the very paths its own rule forbids
hardcoding.** Phase A resolves the user root as `${CLAUDE_CONFIG_DIR:-~/.claude}` and says never to
hardcode it, yet the auto-memory entrypoint, the scope-filter list, and the memory-layer surface
list all named `~/.claude` literally. `CLAUDE_CONFIG_DIR` relocates the whole tree including
`projects/`, so a hardcoded default read a store the session no longer writes. Every operative path
now resolves against that root; quoted upstream text and the `${CLAUDE_CONFIG_DIR:-~/.claude}` form
itself are unchanged.

- **`audit-instructions`: I3 named a `skills:` preload as a valid deferral destination, which defers
nothing.** The check rejects `@path` imports because they load unconditionally, then offered a
preload — but the full content of each skill named in an agent's `skills:` field is injected into
every dispatch, exactly the load profile the check exists to avoid, as the skill's own co-residency
table states. I3 now permits only conditional runtime invocation, and says to report that no safe
deferral is available rather than proposing a preload.

- **`audit-instructions`: Phase A never inventoried a subagent's own memory, so a criterion it
declares in scope could not fire.** The co-residency table graded an agent-definition-versus-its-own
`memory` contradiction as real, but no inventory step reached that `MEMORY.md`. Phase A now
enumerates it per scope (`user` under the resolved user root, `project`, `local`), and the
co-residency table carries its own row.

- **`audit-instructions`: the liveness gate resolved a closed five-input list that omitted hook
enablement.** A hook that cannot fire carries no live instruction text, so a pass comparing against
it grades a dead surface. The gate now resolves `disableAllHooks` **per settings scope** — a user,
project, or local disable cannot reach managed hooks, so managed hook text stays live and must not
be dropped with the rest — together with `allowManagedHooksOnly` and its force-enabled-plugin
exemption.

- **`audit-instructions`: a nested project memory pair was routed to a check that never discovers the
file.** The skill routed any project-scope pair to `claude-memory`'s C6, which discovers with
`find . -maxdepth 1`, so a `src/api/CLAUDE.md` pair was graded by neither pass. The boundary is now
**root-level** project, matching the routing table the criteria reference already owned. Scoped
narrowly: `.claude/rules/**` still routes to C6, whose rules discovery is recursive.

- **`conflict-scan.sh`: `and` coordinating an opposite directive was not a window boundary.** "Always
use `Read` and never use `Bash`" against "Never use `Read`" yielded zero candidates, because the
first entity's window swallowed the second directive's `never` and took its polarity; the same line
with `but never` yielded one. A **bare** `and` cannot be the boundary — "never use `Bash` and
`Grep`" is one directive over two objects, and cutting there strips the token governing the second.
The boundary therefore requires a polarity token after the coordinator, and is consumed
asymmetrically: a leading window resumes after the coordinator alone so that token still classifies
its entity. Three cases cover the fix and the false negative it must not introduce.

- **`audit-instructions`: I14's startup set omitted `./.claude/CLAUDE.md`.** A project keeping its
memory there loads it at launch exactly as `./CLAUDE.md` would, so naming only the bare path let the
redundant read of the active file escape the check. Both supported root locations are now in the
set, matching what Phase A already inventories.

- **`audit-instructions`: I14 exempted supporting documents unconditionally, ignoring startup
imports.** `@path` imports are expanded into context at launch, recursively, so a startup file
carrying `@AGENTS.md` or `@docs/CONTRIBUTING.md` makes that document resident and an instruction to
go read it is the redundant retrieval the check exists to find. The exemption now applies only to
what no active startup import reaches, resolved the way I15 already resolves imports.

- **`setup` and the README: `awk` and `sort` were scoped to one skill and are used by three.**
`check-plugin-drift.sh` (both), `permission-rule-check.sh` (both), and `fix-plugin-drift.sh`
(`sort`) call them with no prerequisite check, so `audit` and `audit-permission-grants` fail
mid-run on a bare `command not found` rather than on a named prerequisite. Only
`conflict-scan.sh` probes and `exit 2`s. Both surfaces now name all three skills, and the README's
requirements section names `awk` and `sort` alongside `jq` and `curl`.

## [0.27.1]

### Changed
Expand Down
14 changes: 11 additions & 3 deletions plugins/claude-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,17 @@ from `raw.githubusercontent.com` (read-only; a failed fetch degrades to SKIP).
## Requirements

The bundled scripts run in `bash` (Claude Code's Bash-tool shell on every platform;
[Git Bash](https://code.claude.com/docs/en/setup#set-up-on-windows) on native Windows) and require
`jq`; the plugin-drift check additionally requires `curl`. Run `/claude-config:setup` (`check` by
default) to verify these prerequisites; `apply` gives platform install guidance and re-verifies.
[Git Bash](https://code.claude.com/docs/en/setup#set-up-on-windows) on native Windows). The
JSON-parsing scripts require `jq`; the plugin-drift check additionally requires `curl`; and `awk`
and `sort` are required across three skills, not one — `audit`'s plugin-drift check (both) and its
fix (`sort`), `audit-permission-grants`' rule check (both), and `audit-instructions`' conflict pass
(both). Only the conflict pass probes for them and `exit 2`s naming the one that is missing; the
others call them unguarded, so an absent `awk` or `sort` surfaces there as a bare `command not
found` partway through a run. Both ship with every POSIX userland, so a missing one means a minimal
shell environment (Git Bash, a `busybox` shim) rather than an absent package — install a full
userland (Git for Windows; `gawk`/`mawk` plus `coreutils` on Linux; `brew install gawk coreutils`
on macOS). Run `/claude-config:setup` (`check` by default) to verify these prerequisites; `apply`
gives platform install guidance and re-verifies.

## License

Expand Down
60 changes: 48 additions & 12 deletions plugins/claude-config/skills/audit-instructions/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ concerns its siblings already cover — route rather than re-answer:
repeated stumble evidence — is `unhobble` (same plugin): this skill judges instruction *text*
against doctrine; unhobble measures the *model*.

On **memory-layer surfaces** (CLAUDE.md, CLAUDE.local.md, `.claude/rules/`, `~/.claude/rules/`),
On **memory-layer surfaces** (CLAUDE.md, CLAUDE.local.md, `.claude/rules/`, and `rules/` under the
user root Phase A resolves),
this skill runs only the model-era checks I6–I28. It never runs or reports the hygiene checks
I1–I5 (line-necessity, length, placement, inferable content, rule-to-hook) on these surfaces —
that instruction-memory hygiene layer belongs to the `claude-memory` plugin. When that plugin is
Expand Down Expand Up @@ -75,7 +76,7 @@ never which surfaces are read. Phase A always inventories the full comparison se
relation between two surfaces and a scoped run still needs the counterpart:

- `claude-md` — findings on user + project CLAUDE.md and CLAUDE.local.md
- `rules` — findings on `.claude/rules/` and `~/.claude/rules/`
- `rules` — findings on `.claude/rules/` and `rules/` under the user root Phase A resolves
- `skills` — findings on skill bodies and their context/reference files
- `agents` — findings on agent definition markdown
- `hooks` — findings on hook instruction text: prompt-type hook text, and handler output injected
Expand Down Expand Up @@ -174,15 +175,29 @@ official memory and `.claude`-directory docs (cited in the report's Sources line

**The tree does not decide what is live.** Before the inventory is handed to any lane, resolve the
session's effective liveness controls — the launch directory, the merged `claudeMdExcludes`,
`--setting-sources`, and the additional-directory inputs — then drop what they exclude and add the
memory files they contribute. A walk of the project tree alone both invents surfaces that are dead
in this session and misses live ones that are not in the tree at all. The controls, their official
sources, and the `liveness-unresolved` marking for values an out-of-session inventory cannot read
are in [reference/conflict-criteria.md](reference/conflict-criteria.md), which owns the gate; name
the resolved controls in the report's tier-transparency line.
`--setting-sources`, the additional-directory inputs, **and effective hook enablement** — then drop
what they exclude and add the memory files they contribute. A walk of the project tree alone both
invents surfaces that are dead in this session and misses live ones that are not in the tree at all.
The controls, their official sources, and the `liveness-unresolved` marking for values an
out-of-session inventory cannot read are in
[reference/conflict-criteria.md](reference/conflict-criteria.md), which owns the gate; name the
resolved controls in the report's tier-transparency line.

**Hook enablement is a liveness control, and configuration alone does not establish it.**
`disableAllHooks` turns off hooks without removing them, so an enabled plugin's handler, or one wired
in project settings, sits on disk unable to run — and a gate that cannot run this session constrains
nothing to compare against. Its reach is all hooks with exactly one carve-out, and that carve-out is
what makes this a per-scope resolution rather than a per-file one: set in user, project, or local
settings it cannot disable **managed** hooks, so managed hook text stays live against it and must not
be dropped with the rest; only a managed-level `disableAllHooks` reaches those too. The mirror
control, `allowManagedHooksOnly`, cuts the other way and blocks user, project, and plugin hooks,
exempting plugins force-enabled in managed `enabledPlugins`. Omit every hook surface they disable —
both kinds, prompt-type and context-injecting alike, since neither reaches this session when the
handler never fires — and report both resolved values with the other controls.

Exclude from the **editable** set, and hold for the routing subsection: auto-memory
(`~/.claude/projects/<project>/memory/`, owned by `claude-memory`), installed plugin-cache content,
(`projects/<project>/memory/` under the resolved user root, owned by `claude-memory`), installed
plugin-cache content,
and any managed materialization per the Scope boundary. Record each surface found and each surface
skipped, so the report's tier-transparency line can name both.

Expand All @@ -193,7 +208,10 @@ involving one still carries the no-change representation and its routing recomme

- **Auto memory, when it is on** — the `MEMORY.md` entrypoint at the effective auto-memory location
(the highest-precedence scope that sets `autoMemoryDirectory`, otherwise
`~/.claude/projects/<project>/memory/`). **Resolve the effective enabled state first, by
`projects/<project>/memory/` under the **resolved** user root above, never a hardcoded
`~/.claude` — `CLAUDE_CONFIG_DIR` moves `projects/` with the rest of the tree, so a hardcoded
default misses the live `MEMORY.md` and compares against a store the session no longer
writes). **Resolve the effective enabled state first, by
precedence — not by any single scope's value.** `CLAUDE_CODE_DISABLE_AUTO_MEMORY` is authoritative
wherever it is set (`=1` off, `=0` on, even against `autoMemoryEnabled: false`); with the variable
unset, apply settings precedence (managed > local > project > user) to `autoMemoryEnabled`, which
Expand All @@ -209,6 +227,21 @@ involving one still carries the no-change representation and its routing recomme
actually loads is compared (the first 200 lines or 25KB); topic files beside it are read on demand
and are not resident. Ownership is unchanged: `claude-memory` still owns auto memory, and a finding
here routes there rather than editing it.
- **Each enabled agent's own memory, under that same gate** — an agent definition carrying a
`memory` field gets its **own** memory directory, separate from the main conversation's and named
per agent, and that subagent reads and writes its own `MEMORY.md` there. The field's value is the
scope, and each scope has its own location: `user` → `agent-memory/<name-of-agent>/` under the
**resolved** user root above (never a hardcoded `~/.claude`, for the reason the entry above gives),
`project` → `.claude/agent-memory/<name-of-agent>/`, `local` →
`.claude/agent-memory-local/<name-of-agent>/`.
[reference/conflict-criteria.md](reference/conflict-criteria.md) keeps an agent-definition-versus-
its-own-memory contradiction in scope precisely because those two *do* co-reside in that subagent,
so this inventory has to reach it: enumerate that `MEMORY.md` for every inventoried agent whose
definition enables the field, under the same loaded-portion bound. The gate is the effective state
resolved just above: subagent memory is part of auto memory, so with auto memory off the `memory`
field has no effect and the subagent launches without the memory instructions or the memory tool
access — an agent memory left on disk after the switch flipped is not inventoried.
Read-only and `claude-memory`-owned exactly as the main entrypoint is.
- **Org-managed policy** — the managed-policy `CLAUDE.md`, any `claudeMd` value in managed settings,
and hook instruction text configured in managed settings, of **both** kinds above. All three are
live instruction text, and a managed hook contradicting a project skill is exactly the conflict I15
Expand Down Expand Up @@ -398,5 +431,8 @@ catalog).
- Not memory-layer hygiene — checks I1–I5 on CLAUDE.md/rules route to `claude-memory`'s `audit`
skill when installed, and upstream-owned plugin-cache or managed materializations route to the
owning repository rather than being edited here.
- 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.
- Does not grade a contradiction whose two halves both sit in **root-level** project `CLAUDE.md` /
`CLAUDE.local.md` / `.claude/rules/**` — that is `claude-memory:audit`'s C6. A **nested**
`CLAUDE.md` / `CLAUDE.local.md` side keeps the pair here, because C6 never discovers that file;
[reference/conflict-criteria.md](reference/conflict-criteria.md) owns the routing table and its
evidence.
Loading
Loading