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.37.2",
"version": "0.38.0",
"description": "Nine 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-permission-state (the permission rules actually in effect — every settings scope merged with per-rule provenance, what auto mode drops on entry, config written where nothing reads it, and which managed intents are enforced versus loosenable), draft-auto-mode-rules (interview and draft a paste-ready autoMode classifier block; prints only, never writes), 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
74 changes: 74 additions & 0 deletions plugins/claude-config/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,80 @@
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.38.0]

### Added

- **`audit-pass` ships an executable for the run state it had only ever described.**
`skills/audit-pass/scripts/run-state.sh` derives the run directory, writes and classifies the
lease, and appends to the epoch-scoped partial. Until now the skill was the only audit skill in
this plugin with no `scripts/` directory at all, while `SKILL.md` and ten `reference/*.md` spelled
out a lease path, a refresh discipline, a two-sided liveness window, a `released` tombstone,
`owner_epoch` fencing and an append-only partial as prose. The gap has a sharper form than "no
scripts": `lib/state-key.sh`, whose own header records the keying scheme as *`audit-pass`'s, reused
rather than reinvented*, was called by `audit-instructions`, `audit-prompting-postures` and
`claude-memory:audit` — every skill except the one that specified it. `paths` now calls it, so the
skill runs on its own scheme rather than describing it for others.

Scope is stated plainly, because the point of the change is that a contract should not read as
enforced when nothing enforces it. The script owns path derivation,
`lease acquire|heartbeat|release|classify`, and `partial append`. It does **not** own stale-lease
adoption (the `owner_epoch` compare-and-set) or §7 assembly; those stay the run's own discipline,
and §3 and §7 now say so in as many words instead of leaving a reader to assume mechanism.
(#2280, F3, F5)
- **Three negative tests, not only passing ones.** `run-state.test.sh` mutates a copy of the script
to delete exactly one check and asserts the mutated copy reaches the outcome the real one refuses:
the two-sided window's *lower* bound (delete it and a future `heartbeat_at` pins a dead run `live`
forever, so every `--resume` refuses an abandoned run — assertion 3.9), the `..` rejection in
`--run-id`, and the segment-shape check that keeps an absolute id from walking the run directory
out of the plugin's namespace. A test that would still pass with the check deleted proves nothing,
and both id checks guard the same door `lib/state-key.sh` documents defending on the remote-URL
side. (#2280, F3)

Review of this change caught one more of the same class before it shipped: because §3 now documents
`--stale-after` as an operator lever, a value of **0** would have been accepted, and a lease
recording a zero window satisfies the staleness test the moment it is written — born abandoned, and
adoptable by `--resume` out from under the run that just wrote it. It is refused rather than
clamped (a clamp hands a caller a window it did not choose and then reports on it), with an
assertion. `--skew-grace 0` stays legal: "tolerate no forward clock jump" is a coherent choice and
inverts nothing.

### Changed

- **The lease's refresh contract now describes something a skill-driven run can keep.** §3 specified
a **60-second** wall-clock heartbeat with a 5-minute staleness threshold derived from it. A skill
acts between tool calls and has no timer, so that cadence named a mechanism no run could provide —
the same defect as specifying a lease and shipping no writer. Refresh is now boundary-driven
(acquire, each lane's persistence point, release), and each lease records the `stale_after_s` and
`skew_grace_s` its writer committed to, so `classify` reads the thresholds from the artifact rather
than assuming its own — which is what the section's own "two implementations must reach it
identically" concern actually needed. The default threshold moves 5 minutes → 30: with
boundary-driven refresh a single delegated lane can outlast five minutes, and a threshold shorter
than a lane classifies a *running* pass as abandoned, which is the unsafe direction because it lets
`--resume` adopt a live run's artifact. (#2280, F5)
- **The `/doctor` handoff's instruction to the operator is no longer false by construction.** Phase 4
marks that lane `open`, closable only by `--resume`; `--resume` reads the partial, not the report;
and nothing wrote a partial. The report therefore told the operator to come back with a flag that
had no artifact to attach to. The `open` terminator now goes through `partial append` at the moment
Phase 4 records the handoff, never deferred to Phase 6 assembly — which is exactly where a run that
does not reach Phase 6 loses it. The second link in the same path is closed too: §5's "run
manifest" is now stated as the partial's own lane records rather than a separate file, which is
what §7 already required ("completion state is derivable from the artifact rather than tracked
beside it and able to disagree with it"). Making the partial real while leaving completion state in
a file nothing writes would have moved the defect rather than fixed it. (#2280, F12)
- **Phase 3's cost mitigation now names something that exists.** The passage bounds lane *count*,
explicitly declines to bound intra-lane fan-out, and mitigates with "let incremental persistence
carry the rest" — persistence that was prose, so an intra-lane overrun degraded into nothing
resumable. The disclaimer is unchanged and the `partial append` call still bounds nothing; what
changed is that an overrun now costs the lanes still running rather than the whole pass.
(#2280, F13)

A note on evidence, since the originating report leans on a runtime observation. What is verifiable
from this repository is the **specification-versus-implementation gap** — a fully specified lease,
partial and manifest with no executable behind any of them — and that is the whole basis for these
entries. Whether any particular past run failed to write a lease is not something the tree can
confirm, and nothing here asserts it.

## [0.37.2]

### Fixed
Expand Down
81 changes: 64 additions & 17 deletions plugins/claude-config/skills/audit-pass/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ editing tools from the pool while this skill is active, so the report-only contr
the tool set, not of model obedience. `Write` is kept — run state and the report persist under
`${CLAUDE_PLUGIN_DATA}`.

**`scripts/run-state.sh` writes under that same plugin data directory and nowhere else** — never
inside a target repository. It takes the data directory as an argument rather than discovering one,
and validates both path segments it contributes: `lib/state-key.sh` refuses a remote URL that would
become traversing directory components, and a `--run-id` outside `[A-Za-z0-9][A-Za-z0-9_.-]*` is
refused here. The run-state writes were always sanctioned; what changed is that a script performs
them.

## Scope boundary (route out)

- **One instruction surface against the model-capability catalog** → `/claude-config:audit-instructions`
Expand Down Expand Up @@ -127,7 +134,23 @@ Resolve the target root, compute the state key, and take the lock posture for th
runs take no lock and run concurrently; an applying run takes an exclusive advisory lock and refuses
rather than queues. All specified in
[reference/run-state-and-resumability.md](reference/run-state-and-resumability.md). With `--resume`,
read the run manifest and carry forward every lane whose input digest is unchanged.
read the lease, then read the partial's lane records and carry forward every lane whose input digest
is unchanged.

**Do not derive the run directory or hand-write the lease.** `scripts/run-state.sh` does both, which
is what makes this phase a mechanism rather than a description of one:

```bash
S="${CLAUDE_PLUGIN_ROOT}/skills/audit-pass/scripts/run-state.sh"
bash "$S" paths --plugin-data "${CLAUDE_PLUGIN_DATA}" --run-id "<run-id>"
Comment thread
kyle-sexton marked this conversation as resolved.
bash "$S" lease acquire --run-dir "<run-dir>" --run-id "<run-id>"
```

`paths` derives `<plugin-data>/runs/<state-key>/<run-id>` through the plugin's own `lib/state-key.sh`
— the library whose header records the keying scheme as *this skill's*, and which until now three
other skills called and this one did not. Pass `--plugin-data` explicitly: `${CLAUDE_PLUGIN_DATA}`
substitutes in this text but is **not** exported to the Bash tool's environment, so a shell cannot
expand it.

**`--resume` never attaches to a run that is still going.** Concurrent read-only runs are safe
because each owns its own partial artifact; resume is the one operation that reaches into *another*
Expand All @@ -137,14 +160,20 @@ terminating records to one file, and highest-terminated-attempt assembly becomes
the interruption-tolerance mechanism producing a report neither run performed.

So every active run, read-only included, maintains a **lease**, and `--resume` reads it before it
reads the manifest. The lease is fully specified in
[reference/run-state-and-resumability.md](reference/run-state-and-resumability.md) §3 — its path,
refresh interval, and staleness threshold — because "on the same heartbeat the applying lock uses"
named a mechanism that did not exist and left the classification unimplementable. A **live** lease means the run is still
going: resume exits non-zero naming the run id rather than attaching. A **stale** lease means the run
was interrupted and its artifact is resumable. The lease is not a lock — it excludes nothing, blocks
no concurrent read-only run, and grants no exclusivity; it answers the one question resume has to ask
and previously could not.
reads the partial. `run-state.sh lease classify --run-dir <run-dir>` prints the verdict:
a **live** lease means the run is still going, and resume exits non-zero naming the run id rather
than attaching; a **stale** lease means the run was interrupted and its artifact is resumable; a
`released` tombstone is resumable immediately; `missing` means there is nothing to attach to. The
lease is not a lock — it excludes nothing, blocks no concurrent read-only run, and grants no
exclusivity; it answers the one question resume has to ask and previously could not.

Refresh it at every lane's persistence point (`lease heartbeat`) and write the tombstone on a clean
exit (`lease release`). The full specification — path, contents, the two-sided liveness window, and an
explicit statement of **which clauses the script enforces and which remain the run's own discipline**
— is in [reference/run-state-and-resumability.md](reference/run-state-and-resumability.md) §3. Read
that split before relying on any of it: the section specified a refresh interval and a staleness
threshold against no writer at all, which is the same shape as "on the same heartbeat the applying
lock uses" — a mechanism named rather than provided.

**The scan baseline is captured after the inventory is frozen and before any lane reads.** The
digest spans every inventoried scope, so it cannot be computed before Phase 1 has produced that
Expand Down Expand Up @@ -284,13 +313,25 @@ lanes. Route it out (`skill-quality:check` when installed).

Persist each lane's findings to the partial artifact **as that lane completes**, never buffered to
the end — a lane is complete when its terminating record is in the partial, and every record carries
its attempt id so an abandoned re-attempt is discardable rather than merely older.
its attempt id so an abandoned re-attempt is discardable rather than merely older. The write is one
call per record — `bash "$S" partial append --run-dir "<run-dir>" --record '<json-line>'` — and the
lease is refreshed at the same boundary. The partial is named `findings.partial.<owner_epoch>.jsonl`
after the epoch the lease holds, so it cannot be written without a lease to classify it: a record
resume could not attribute to a live-or-abandoned run is worse than no record.

**The lane count is bounded by the delegated interfaces, not chosen here** — one per scope value the
instruction catalog accepts, plus one for the memory layer — so it is a handful, and a per-run
dispatch ceiling would never bind. What is *not* bounded here is the fan-out inside a lane: the
delegated catalogs spawn their own subagents. So cap concurrency at 3–5 lanes and let incremental
persistence carry the rest — it is what degrades a blown session ceiling into a resumed run.
delegated catalogs spawn their own subagents, and this pass cannot reach inside one to cap it. So cap
concurrency at 3–5 lanes and let incremental persistence carry the rest — it is what degrades a blown
session ceiling into a resumed run.

**That mitigation now names something that exists.** "Let incremental persistence carry the rest" was
the load-bearing answer to the *one* cost dimension this passage declines to bound, and until
`run-state.sh` shipped the persistence it named was prose — so an intra-lane overrun, the failure
mode this paragraph is explicitly about, degraded into nothing resumable. The `partial append` call
above **bounds nothing**, and the disclaimer stands unchanged; what it buys is that an overrun costs
the lanes still running rather than the whole pass.

## Phase 4 — The `/doctor` handoff

Expand All @@ -317,10 +358,16 @@ sweep to run again.
would have made the promised resume impossible: §7 needs a terminating record to assemble a report at
all, while §5 skips any lane whose state is complete and whose digest is unchanged — so a lane that
was both terminated *and* complete would be carried forward untouched on every resume, and the
outstanding handoff would never close. So the record terminates the attempt for assembly and the
manifest records the lane's state as **incomplete**. `--resume` therefore re-runs it, which for a
delegated lane means re-prompting rather than re-scanning. `handed-back` and `declined` are
completions; only `open` is not.
outstanding handoff would never close. So the record terminates the attempt for assembly and marks
the lane's state **incomplete**. `--resume` therefore re-runs it, which for a delegated lane means
re-prompting rather than re-scanning. `handed-back` and `declined` are completions; only `open` is
not.

**That instruction to the operator is only true if the terminating record is actually written.**
`--resume` reads the partial, not the report, so a report telling the operator to come back with
`--resume` against a partial nothing wrote is a false instruction in the one artifact they act on. So
the `open` terminator goes through `partial append` at the moment Phase 4 records the handoff — never
deferred to Phase 6 assembly, which is exactly where a run that does not reach Phase 6 loses it.

## Phase 5 — Apply, only under `--fix`

Expand All @@ -337,7 +384,7 @@ with a **fresh-context (non-fork) subagent** as the stated fallback.
The apply-verify step and delegated lanes that mandate subagent dispatch **require** that dispatch.
When the Agent tool is blocked, unavailable, or the session cannot spawn subagents:

1. **Record per-lane verification mode** in the run manifest and assembled report (`verified` |
1. **Record per-lane verification mode** in the lane's terminating record and the assembled report (`verified` |
`inline` | `skipped`) for every lane that mandates independent verification.
2. **Mark unverified findings.** Proposals or applied fixes that did not receive an independent
verifier MUST carry an `(unverified)` marker and MUST NOT be presented as resolved.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ completes. Append-only is what makes §5 real: a single JSON document would be r
every append, which is exactly the operation an interrupted run leaves half-done. A lane's final
record is its terminating record.

**The append is `scripts/run-state.sh partial append`, not a hand-rolled redirection.** It takes the
epoch from the lease — so the partial cannot exist without the lease `--resume` reads first — refuses
a record that is not a single-line JSON object, and returns the file it appended to. §5 states which
clauses of the run-state contract that script enforces and which remain the run's own discipline;
**assembly is among the latter**, so the selection rules below are performed by the run, not by an
executable.

**Completion is read from the terminator's state, not from its presence.** A terminator lets
assembly render the lane; whether the lane is *done* is a separate question, and conflating them
would carry an outstanding `/doctor` handoff forward on every resume instead of closing it. A
Expand Down Expand Up @@ -136,4 +143,9 @@ the run and target identity, the resolved version of every catalog consulted, an
| `verification` | per-lane verification mode (`verified` \| `inline` \| `skipped`) for lanes that mandate independent subagent dispatch; omitted only when every such lane verified |

**Resume reads the partial, not the report**, so completion state is derivable from the artifact
rather than tracked beside it and able to disagree with it.
rather than tracked beside it and able to disagree with it. §5 makes the same point from the other
side: the run manifest is these lane records, not a second file — a manifest beside the partial is
precisely the thing that could disagree with it. And the instruction the report gives the operator —
come back with `--resume` — is only true because the partial is written by a script as each lane
terminates, Phase 4's `open` handoff included. Stated as a contract against an artifact nothing
wrote, it was a false instruction in the one artifact the operator acts on.
Loading