Skip to content
Merged
12 changes: 12 additions & 0 deletions docs/conventions/detector-findings/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ Notable changes to the detector-findings contract (SemVer). Changing a producer-
the coexistence obligations, or an enforceability verdict is a major bump; additive guidance or a new
adopter row is a minor bump; docs-only clarification is a patch.

## [3.2.0] - 2026-09-28

**Minor, additive.** Four crosswalk rows admit `claude-config:audit-instructions`'s lane-selected
rules: `rule-trigger-less-stamp` (I30, IMPORTANT), `rule-migration-relative-phrasing` (I31,
IMPORTANT, off-site to the owning `CHANGELOG.md` for any history kept), `rule-route-to-absent-skill`
(I32, CRITICAL on the none-at-all limb), and `rule-spoke-self-description` (I33, SUGGESTION,
off-site to the hub's index row). Each states its withholding boundaries as evidence that must be
present, so an unresolved lane judgment falls toward emitting, and none is auto-applicable. The
producer's adopter row records the second intake path, the omitted `Confidence` on lane-fed rows,
and the `finding_id` each row now carries, and limits its downgrade-not-deletion sentence to the I28
rows, since I33's remedy is a deletion. No producer-owned field's rule changes.

## [3.1.3] - 2026-09-27

**Patch, docs-only.** `review`'s `severity.md` now ranks confidence `high` > `medium` > `low` >
Expand Down
6 changes: 5 additions & 1 deletion docs/conventions/detector-findings/README.md

Large diffs are not rendered by default.

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.51.26",
"version": "0.51.27",
"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, proposing removals/rewrites of instructions the model no longer needs, and detecting 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
22 changes: 22 additions & 0 deletions plugins/claude-config/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@
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.51.27] - 2026-09-28

### Added

- **`audit-instructions`: findings carry an identity that survives a re-run.** Every finding adopts
`audit-pass`'s `(check, claim, sites)` identity: `check` is
`claude-config/audit-instructions/<id>`, each catalog check gains a claim template in the new
`reference/finding-identity.md`, anchors are `anchor/v1` excerpt anchors with the heading-path
discriminator, and an I15 conflict is one finding with two sites. New `scripts/finding-ids.sh`
derives the anchors, `finding_id/v1`, and `group/v1` through `audit-pass`'s
`finding-identity.sh`, and its records pass that script's emitter guard. The Phase D table gains
a Finding ID column (#4116).
- **`audit-instructions`: `--persist-findings` emits I30, I31, I32, and I33 from lane findings.**
`emit-findings.sh` gains a second intake, `--from-lane`, beside the scanner-fed `--from`. The four
rules take new detector-findings crosswalk rows (convention 3.2.0): I30 and I31 at IMPORTANT, I32
at CRITICAL, I33 at SUGGESTION, none auto-applicable, I31 and I33 naming their off-site target in
`Action`. Lane rows omit `Confidence`. A frontmatter-located I32 row is declined as
`reason=frontmatter` and counted, I31 and I33 rows outside a spoke are declined as
`reason=outside-rule-surfaces`, and a row on the wrong intake is declined naming the intake it
belongs to. Every emitted row carries `finding_id=` in its `Finding` cell, and rank order is tier,
then `high` above omitted `Confidence` (#4116).

## [0.51.26] - 2026-09-28

### Changed
Expand Down
16 changes: 8 additions & 8 deletions plugins/claude-config/skills/audit-instructions/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,9 @@ Two flags govern the `OPINION` tier, whose enablement policy the catalog defines
It is on by default because it withholds findings rather than emitting them, so turning it off
makes both trimming checks more aggressive, not the audit more conservative.

`--persist-findings` also writes the run's I28 and I29 findings as a `type: review-findings` file
for `review:fanout`'s `fix` action (off by default; only I28 and I29 are eligible, body-scoped; a
proposal for a human-gated relay, not an applied edit; see
[context/persist-findings.md](context/persist-findings.md)).
`--persist-findings` also writes the run's I28 and I29 scan findings and I30 to I33 lane findings as
a `type: review-findings` file for `review:fanout`'s `fix` action (off by default; only those families,
body-scoped; a proposal for a human-gated relay, not an applied edit; see [context/persist-findings.md](context/persist-findings.md)).

`--unattended` declares that nobody is available to answer: the ~20-dispatch confirmation in
Phase B becomes a disclosure on the Phase D cost line instead of a question. Only the caller
Expand Down Expand Up @@ -404,10 +403,11 @@ unchanged; the target-model fail-loud stop is an invocation-time validation abor
interactive gate, since it prompts nobody and blocks nothing mid-run). Present findings as a table.
Each row's identity is `(check, claim, sites)` per
[context/execution-and-report.md](context/execution-and-report.md); presentation fields stay
outside the hash. An I15 conflict is one finding with two sites.
outside the hash. An I15 conflict is one finding with two sites and one Finding ID. **Finding ID** is
the row's re-run-stable `finding_id/v1` from `scripts/finding-ids.sh` ([derivation and claim templates](reference/finding-identity.md)); a refused row reads `unidentified: <reason>` there.

| # | Check | Surface:Line | Severity | Tier | Authority | Finding | Proposed change |
|---|-------|--------------|----------|------|-----------|---------|-----------------|
| # | Finding ID | Check | Surface:Line | Severity | Tier | Authority | Finding | Proposed change |
|---|------------|-------|--------------|----------|------|-----------|---------|-----------------|

Phase B2's findings carry two anchors, so they get their own **Cross-surface conflicts** subsection.
Beside it, an **Out-of-catalog** subsection holds the defects the catalog's "Out-of-catalog defects"
Expand Down Expand Up @@ -453,7 +453,7 @@ Open the Sources line with the two official pages the paths and doctrine derive
(code.claude.com memory + `.claude`-directory docs; the prompting pages cited per check in the
catalog).

**With `--persist-findings`**, also emit the run's I28 and I29 findings for the apply relay per
**With `--persist-findings`**, also emit the run's I28, I29, and I30 to I33 findings for the apply relay per
[context/persist-findings.md](context/persist-findings.md), which owns every mechanic and the
carve-out drop preceding the write. Report the path and the emitted/declined counts, and say
plainly that nothing has been applied.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ edits any of them is therefore a regression, not a debatable suggestion. Two con
run:

- Scan with `instruction-scan.sh --body-only` (I28) and `restatement-scan.py` (I29, body-scoped
by construction). Concatenate both onto the `--from` stream.
by construction). Concatenate both onto the `--from` stream. Lane findings for I30 to I33 go on
their own `--from-lane` stream, and the same fence binds them.
- Do **not** rely on that alone. `emit-findings.sh` recomputes the fence over its input and
additionally declines any body row quoting a trigger phrase that appears in the file's own
`description`. A fence that lives only in the caller is one caller away from being bypassed.
Expand All @@ -51,9 +52,11 @@ report**. It is routed there, never to the relay.
## Compose by script, not by hand

Once the destination is resolved and the contract fetch succeeded, run
`${CLAUDE_SKILL_DIR}/scripts/emit-findings.sh --from <scan output file> --out <resolved path>`.
The script owns the mechanical half: the fence recomputation, cell assembly and escaping, tier
lookup (a mirror of the crosswalk, and the crosswalk row is authoritative), rank ordering, the
`${CLAUDE_SKILL_DIR}/scripts/emit-findings.sh --from <scan output file> --from-lane <lane rows file> --out <resolved path>`,
omitting whichever input the run has nothing for. The script owns the mechanical half: the fence
recomputation, cell assembly and escaping, tier lookup (a mirror of the crosswalk, and the
crosswalk row is authoritative), each row's finding identity (through `scripts/finding-ids.sh`,
per [reference/finding-identity.md](../reference/finding-identity.md)), rank ordering, the
non-overwrite suffix, and the `## Surfaces` counts. What stays with the model is everything before
the script (rung-order resolution, the fetch-and-refuse gate, the self-ignore guard) and everything
after it (reading the written file's head to confirm shape, and severity-vocabulary mapping when
Expand All @@ -62,18 +65,38 @@ the contract's consumer-precedence rule.

## Which findings enter the file

**Only the I28 and I29 families.** `instruction-scan.sh` marks eleven check families and
`restatement-scan.py` marks two more; the other nine families (I6, I8-a/b/c/f, I10, I23, I25, I27)
have no severity-crosswalk row, and the contract admits no row whose tier cannot be looked up
from one. They stay in the human report and are counted in `## Surfaces` as
**The I28 and I29 families from the scan, and I30 to I33 from the lanes.** `instruction-scan.sh`
marks eleven check families and `restatement-scan.py` marks two more; the other nine scanned
families (I6, I8-a/b/c/f, I10, I23, I25, I27) and every other lane check have no
severity-crosswalk row, and the contract admits no row whose tier cannot be looked up from one.
They stay in the human report and are counted in `## Surfaces` as
`reason=no-severity-crosswalk-row`. They are declined, never silently dropped.

| Scanner family | Rule id | Tier |
|---|---|---|
| `I28-a` | `claude-config/audit-instructions/rule-coercive-emphasis` | IMPORTANT |
| `I28-b` | `claude-config/audit-instructions/rule-blanket-tool-default` | IMPORTANT |
| `I29-a` | `claude-config/audit-instructions/rule-description-restatement` | IMPORTANT |
| `I29-b` | `claude-config/audit-instructions/rule-sibling-restatement` | IMPORTANT |
| Family | Intake | Rule id | Tier |
|---|---|---|---|
| `I28-a` | `--from` | `claude-config/audit-instructions/rule-coercive-emphasis` | IMPORTANT |
| `I28-b` | `--from` | `claude-config/audit-instructions/rule-blanket-tool-default` | IMPORTANT |
| `I29-a` | `--from` | `claude-config/audit-instructions/rule-description-restatement` | IMPORTANT |
| `I29-b` | `--from` | `claude-config/audit-instructions/rule-sibling-restatement` | IMPORTANT |
| `I30` | `--from-lane` | `claude-config/audit-instructions/rule-trigger-less-stamp` | IMPORTANT |
| `I31` | `--from-lane` | `claude-config/audit-instructions/rule-migration-relative-phrasing` | IMPORTANT |
| `I32` | `--from-lane` | `claude-config/audit-instructions/rule-route-to-absent-skill` | CRITICAL |
| `I33` | `--from-lane` | `claude-config/audit-instructions/rule-spoke-self-description` | SUGGESTION |

**A lane row is a Phase C-surviving finding, written as `<path>:<line>:<check-id>`** with the line
of the flagged sentence's first physical line (for I33, the opener). A row on the wrong intake is
declined naming the intake it belongs to (`reason=scanner-fed-rule` or `reason=lane-fed-rule`).
I31 and I33 are spoke rules, so a row outside a `context/` or `reference/` spoke is declined as
`reason=outside-rule-surfaces`. **I32 is the one lane rule whose catalog surfaces reach
frontmatter**: a description or `when_to_use` routing clause naming an absent skill is a real
finding, but the relay is body-scoped, so the writer declines the row as `reason=frontmatter` and
counts it, and the human report carries it.

**These rules are selected by judgment, so an unresolved call falls toward emitting.** Each I30 to
I33 exemption in [reference/criteria.md](../reference/criteria.md) is a withholding boundary that
needs its evidence present: a one-line scope note is one line that bounds the subject, a named
owner record is named at the site, a CHANGELOG is a CHANGELOG. A candidate the lane cannot place
inside an exemption on that evidence goes on the `--from-lane` stream.

The model lane's criteria carve-outs still apply **before** persistence: emphasis guarding a
destructive, security, or permission gate, a stated hard precondition, and a document *about* the
Expand Down Expand Up @@ -120,22 +143,32 @@ pipe-escaped the same way Finding and Action are.
- **`Location`** is `<repo-relative path>:<line>`; never the file alone.
- **`Surface(s)`** is `claude-config:audit-instructions`.
- **`Finding`** leads with the qualified rule id and the fired marker in the run's own values
(`marker="CRITICAL:"`, `phrase="if in doubt, use"`), then the excerpt. No rubric reasoning.
(`marker="CRITICAL:"`, `phrase="if in doubt, use"`, `target="/fleet:reachx"`), then
`finding_id=<16 hex>`, then the excerpt. No rubric reasoning. A row whose identity
`finding-ids.sh` refuses is declined as `reason=identity-unresolved`, never emitted without one.
- **`Action`** states the **downgrade**: normal conditional phrasing for `rule-coercive-emphasis`,
the targeted condition for `rule-blanket-tool-default`. **The remediation is never a deletion.**
A finding that removes the instruction rather than its shouting is wrong, so no `Action` cell
may instruct removal. The directive survives verbatim and only its volume changes. The single
legitimate exception is **sentence-initial capitalization forced by dropping a leading wrapper**
(`…MUST resolve` → `Resolve`), which the official source's own worked example also makes
(`use` → `Use`). Any other wording change means the remediation overreached.
- **`Action`** for a lane rule keeps the flagged content and changes its framing: I30 adds the
recheck trigger and keeps the stamp, I31 restates the current rule and names the owning
`CHANGELOG.md` as the target for any history kept, I32 repoints the route and keeps the routing
sentence, and I33 deletes the opener and names the hub as the target for a loading condition its
index row lacks. I31 and I33 are therefore off-site rows in the contract's sense.
- **`Tier`** is LOOKED UP from the rule's crosswalk row, then mapped to the consuming project's
severity vocabulary when it defines one. **`Confidence`** is `high` on every emitted row: a
deterministic detector fired.
severity vocabulary when it defines one. **`Confidence`** is `high` on every scanner-fed row: a
deterministic detector fired. It is omitted on every lane-fed row: a judgment selected it, and
the contract gives a producer no grade below `high`, so the row ranks as `unscored`.

## Surfaces, and when the file is written at all

`## Surfaces` names `claude-config:audit-instructions` once, states what was scanned, and carries
the declined counts per family and reason. Omit `tier:`, `## By dimension`, and `## Unparsed`.
the declined counts per family and reason. Rows sharing a `finding_id` (identical sentences under
one heading path) are emitted once, and an `Identity collisions: finding_id=<id> count=<n>` line
names each such id. Omit `tier:`, `## By dimension`, and `## Unparsed`.

- Findings to emit → write.
- Files scanned, zero emittable findings → write anyway with the empty `## Findings` header:
Expand Down
19 changes: 17 additions & 2 deletions plugins/claude-config/skills/audit-instructions/evals/evals.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,14 +304,29 @@
"id": 25,
"name": "execution-model-and-report-identity-contract",
"prompt": "/claude-config:audit-instructions all --unattended --persist-findings over a marketplace repository.",
"expected_output": "Reads context/execution-and-report.md as the standing record of the execution model and the report identity contract. Sizes lanes from the lane model's window, discloses the ~20-dispatch gate on the cost line because --unattended is present, writes per-lane reports under the run directory, and presents each finding as (check, claim, sites) with an I15 conflict as one row of two sites. Persists I28 and I29; I30 to I33 stay in the human report and are declined no-severity-crosswalk-row until the persist unit admits them. Does not edit audited surfaces.",
"expected_output": "Reads context/execution-and-report.md as the standing record of the execution model and the report identity contract. Sizes lanes from the lane model's window, discloses the ~20-dispatch gate on the cost line because --unattended is present, writes per-lane reports under the run directory, and presents each finding as (check, claim, sites) with an I15 conflict as one row of two sites. Persists I28 and I29 and the I30 to I33 lane findings. Does not edit audited surfaces.",
"files": [],
"expectations": [
"Follows context/execution-and-report.md for lane sizing, --unattended disclosure, and resume/lease shape rather than inventing a second run model",
"Presents each finding's identity as (check, claim, sites); an I15 conflict is one finding with two sites",
"Persists only families the current emit path admits; I30 to I33 are declined and counted, never silently dropped",
"Persists I28, I29, and I30 to I33; rows the emit path refuses are declined and counted, never silently dropped",
"Does not edit audited instruction files"
]
}
,
{
"id": 26,
"name": "persist-findings-emits-lane-rules-with-identity",
"prompt": "/claude-config:audit-instructions skills --persist-findings. One skill's SKILL.md names `/fleet:reachx` (a skill that does not exist) both in its frontmatter description and in a body sentence, and one of its reference/ spokes opens with \"This file is loaded by the hub when the release names a breaking change.\" Phase C keeps both findings.",
"expected_output": "Writes the human report with a Finding ID column holding each row's finding_id/v1 from scripts/finding-ids.sh. Persists a review-findings file by running emit-findings.sh with the lane findings on --from-lane as <path>:<line>:<check-id> rows: the body I32 row is emitted at CRITICAL with target=\"/fleet:reachx\" and a finding_id, the I33 row is emitted at SUGGESTION with an Action naming the hub SKILL.md as the target for the loading condition, and both leave Confidence empty. The frontmatter I32 row is not emitted: it is declined as reason=frontmatter and counted in ## Surfaces, and it stays in the human report. Says plainly that nothing has been applied.",
"files": [],
"expectations": [
"Gives every human-report finding a Finding ID derived by scripts/finding-ids.sh rather than composed by hand",
"Passes the I32 and I33 lane findings to emit-findings.sh through --from-lane, not --from",
"Emits the body I32 row at CRITICAL and the I33 row at SUGGESTION, each with a finding_id and an empty Confidence cell",
"Declines the frontmatter I32 row as reason=frontmatter and reports that count rather than dropping it silently",
"States that the findings file is a proposal and nothing has been applied"
]
}
]
}
Loading
Loading