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 docs/conventions/detector-findings/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ side.
| ai-slop/audit/rule-stacked-hedging | Two stacked hedges in one phrase ("could potentially", "might possibly", and the catalog's list) on a prose line, outside the exempt contexts above. Mechanical phrase selection, no withholding verdict; counted declines. | A doubled hedge produces no wrong result and weakens no caller (CRITICAL fails). IMPORTANT fails: no stated rule, and redundant hedging has no nameable degradation trigger — the claim's uncertainty is stated either way. SUGGESTION holds: one hedge and two hedges both express the uncertainty; the finding is a redundancy preference. | SUGGESTION | No, remediated by `/ai-slop:audit fix` — choosing which hedge states the real uncertainty is a claim-strength judgment |
| claude-config/audit-instructions/rule-coercive-emphasis | A forced-compliance emphasis marker (`CRITICAL:`, `IMPORTANT:`, `You MUST`, `MANDATORY`, `ALWAYS use`, `NEVER skip`) on an instruction line, **body-scoped**: frontmatter is fenced out, as is any body line quoting a `'trigger phrase'` that appears in the file's own `description`. Case-sensitive — the all-caps marker IS the signal (threshold: any occurrence; the fired marker travels in the `Finding` cell). Mechanical byte-sequence selection with no withholding verdict, so the fail-safe criterion is met by construction; the criteria row's carve-outs (emphasis guarding a destructive or security gate, a stated hard precondition, a document *about* the pattern) are the model lane's, and every fence decline is counted in `## Surfaces`. | CRITICAL fails every limb: instruction prose computes nothing, so no concrete input, caller, or subsequent otherwise-correct change is made to produce a wrong, unsafe, or absent result — what the emphasis changes is how likely a trigger is to fire, which is a distribution over behavior rather than a determined result. IMPORTANT's degradation-with-a-named-trigger limb then matches, and the trigger is nameable rather than hypothetical: the first session in which the over-emphasized instruction fires on input its actual condition does not cover, spending a tool or skill invocation the condition never warranted. Official guidance supplies the mechanism, not merely a preference — prompting written against undertriggering "may now overtrigger", so this is a stated upstream rule the surface violates, not a choice among phrasings that all work. SUGGESTION's catch-all is therefore never reached: the alternatives do not both work, one overtriggers. | IMPORTANT | No — contained to `Location`, but the repair is a **downgrade, never a deletion**: the directive survives verbatim and only its volume changes, and deciding the normal-conditional phrasing that preserves the instruction is a rewrite judgment. Verbatim survival is asserted apart from **sentence-initial capitalization forced by dropping a leading wrapper** (`…MUST resolve` → `Resolve`) — a change the official source's own worked example makes too (`use` → `Use`); any other wording change means the remediation overreached |
| claude-config/audit-instructions/rule-blanket-tool-default | A blanket tool default ("default to using/running/calling", "if in doubt, use", "always use", "use even when") on an instruction line, under the identical body-scope fences and counted declines as the emphasis rule above. Mechanical phrase-list selection, case-folding, no withholding verdict. | The same walk, on the same mechanism and the same official source, which is why the two rules share a tier: a blanket default is the second arm of one defect — prompting written against undertriggering that no longer exists. CRITICAL fails identically (the phrasing determines no wrong result). IMPORTANT's degradation limb matches with the same nameable trigger, sharpened by the guide stating the consequence outright — "Instructions like 'If in doubt, use [tool]' will cause overtriggering" — so the cost is a named behavioral one, not a register preference, and SUGGESTION is never reached. | IMPORTANT | No — contained to `Location`, but the repair replaces the blanket with the targeted condition it stood in for; recovering that condition is judgment, and the instruction itself is kept, never deleted |
| docs-hygiene/audit-noise/rule-negation-without-positive | A prohibition (`never`, `do not`, `don't`, `avoid`, `must not`, `should not`) with no positive alternative stated in the same sentence, **body-scoped**: `detect.sh` never leaves frontmatter, fenced code, exempt sections or opt-out-marked content, and the writer independently re-fences frontmatter and declines any body line quoting a `'trigger phrase'` that appears in the file's own `description` / `when_to_use`. Selection is per SENTENCE, on the backtick-unwrapped line, and case-folded (threshold: any occurrence; the fired prohibition travels in the `Finding` cell). | CRITICAL fails every limb: instruction prose computes nothing, so no concrete input, caller, or subsequent otherwise-correct change is made to produce a wrong, unsafe, or absent result — a prohibition without its positive leaves the target under-specified rather than determined wrong. IMPORTANT's **stated-rule** limb then matches directly, and does so without needing the degradation limb: `docs-hygiene:write-for-agents` "Prompt the positive" is a rule this fleet already adopted in writing — "Write what to do, not what to avoid" — so a bare prohibition is a surface violating a stated rule rather than one phrasing among several that all work. Official guidance supplies the same mechanism (*"Do not use markdown"* → *"Your response should be composed of smoothly flowing prose paragraphs"*). SUGGESTION's catch-all is therefore never reached. | IMPORTANT | No — contained to `Location`, but the repair rewrites to the positive target the prohibition implies, and recovering that target is a rewrite judgment: the constraint must survive while its framing changes. Matches the disposition of both `audit-instructions` rules for the same reason |
| docs-hygiene/audit-noise/rule-negation-without-positive | An **imperative** prohibition (`never`, `do not`, `don't`, `avoid`, `must not`, `should not`) opening the line, on a line that **closes its own sentence**, with no positive alternative stated in that sentence. Both scope gates are load-bearing and measured: without them the rule fired 1053 times on an 85-file sample (99% of all findings), with them 69 — descriptive prose, an already-paired mid-sentence cue, a hard-wrapped continuation and a table row are all out of scope. **Body-scoped**: `detect.sh` never leaves frontmatter, fenced code, exempt sections or opt-out-marked content, and the writer independently re-fences frontmatter and declines any body line quoting a `'trigger phrase'` that appears in the file's own `description` / `when_to_use`. Selection is per SENTENCE, on the backtick-unwrapped line, and case-folded (threshold: any occurrence; the fired prohibition travels in the `Finding` cell). | CRITICAL fails every limb: instruction prose computes nothing, so no concrete input, caller, or subsequent otherwise-correct change is made to produce a wrong, unsafe, or absent result — a prohibition without its positive leaves the target under-specified rather than determined wrong. IMPORTANT's **stated-rule** limb then matches directly, and does so without needing the degradation limb: `docs-hygiene:write-for-agents` "Prompt the positive" is a rule this fleet already adopted in writing — "Write what to do, not what to avoid" — so a bare prohibition is a surface violating a stated rule rather than one phrasing among several that all work. Official guidance supplies the same mechanism (*"Do not use markdown"* → *"Your response should be composed of smoothly flowing prose paragraphs"*). SUGGESTION's catch-all is therefore never reached. | IMPORTANT | No — contained to `Location`, but the repair rewrites to the positive target the prohibition implies, and recovering that target is a rewrite judgment: the constraint must survive while its framing changes. Matches the disposition of both `audit-instructions` rules for the same reason |
| docs-hygiene/audit-noise/rule-negation-hard-guardrail | **Non-emitting.** A prohibition whose sentence also carries a safety-critical marker (`secret`, `credential`, `token`, `password`, `api key`, `force-push`, `--force`, `rm -rf`, `destructive`, `irreversible`, `data loss`, `production`, `security`, `vulnerab`, `rewrite history`) — a hard guardrail whose constraint a positive form cannot carry, where the prohibition IS the correct shape. | **Boundary ground, not a tier test** — the contract's "Findings that never reach a relay". This disposition is argued from the Boundary because a tier test can only ever return a tier, and the claim here is that the candidate is not a defect at all: the sibling `write-for-agents` rule itself preserves the negation "when the positive form genuinely loses the constraint". Reaching for a tier test to justify the non-emission would look argued while arguing nothing. **Fail-safe direction:** the carve-out requires its marker to be PRESENT on the sentence, so absence of that evidence selects the emitting rule above — an unresolved judgment can never withhold. The same holds for the paired-positive boundary (`instead`, `rather than`, `prefer`, `in place of`, `in favour of`) and the worked-example boundary (a `->` / `→` demonstration): each requires positive evidence, and both were checked rather than only the one easier to argue. | *(never reaches the relay)* | n/a — reported to the human only, and suppressed from the human report too, so one candidate carries one disposition on every surface this producer emits to |

The map is flat across the emitting rules on purpose — within each producer's set every emitting rule
Expand Down
2 changes: 1 addition & 1 deletion plugins/docs-hygiene/.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": "docs-hygiene",
"version": "0.21.0",
"version": "0.21.1",
"description": "Documentation-hygiene toolkit: compress (flavor-trim markdown with a semantic-diff safety net), audit-noise (classify markdown noise), extract-ssot (deduplicate repeated content into a single source of truth), audit-encapsulation (detect citations into skill-private surfaces), rename-references (sweep stale references after renames), audit-derivability (classify whether a whole document earns its existence — could a fresh agent re-derive it from the code?), audit-progressive-disclosure (grade instruction files against a load-tier model for split opportunities and hub/spoke disclosure defects), write-for-agents (authoring-time doctrine that fires while agent-consumed markdown is being written), and write-for-humans (the same moment for the other reader — end-user READMEs, RFCs, release notes and guides — resolving the consuming project's own style guide first).",
"author": {
"name": "Melodic Software",
Expand Down
46 changes: 46 additions & 0 deletions plugins/docs-hygiene/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,51 @@
# Changelog — docs-hygiene plugin

## [0.21.1]

### Fixed

- **`audit-noise`'s `negation` shape was unusably broad as shipped in 0.21.0 (#3201).** Measured on
an 85-file sample of this repo's own tracked markdown: **1053 findings, 12.4 per file, 99% of all
findings the skill produced.** The eight older shapes produced 10 between them. At that rate the
shape swamps the human report on every run and would flood the apply relay under
`--persist-findings`. Two scope gates bring it to **69** on the same files, with every other
shape's count byte-identical (3/2/2/1/1/1 before and after), so the whole delta is this shape:

- **Imperative only.** The cue must open the **sentence**, after list, blockquote,
task-list-checkbox and emphasis markers.
`docs-hygiene:write-for-agents` "Prompt the positive" is a rule about *instructions*, so
descriptive prose ("Older versions do not support this flag", "the config never loads") was
never in its scope. A mid-sentence cue is excluded by construction and that is correct — a
correctly paired sentence puts the cue *after* its positive ("Prefer X; never Y"), so the test
declines exactly what is already compliant.
- **The line must close its own sentence.** This repo hard-wraps prose and the pairing rule is per
sentence, so a continuation line cannot be shown to lack a positive that sits on the next line.
The same test excludes a table row, which ends in `|`.

**Both narrowings are #3180's**, established there against a 1140-file corpus sweep. That PR was
open against the same issue while #3194 was built and merged over it; this adopts the calibration
work rather than discarding it.

**The cost is stated, not hidden:** a subject-led instruction ("The agent must not emit a bare
summary") no longer selects. Pinned by an assertion so a future widening cannot pass silently.

**The remaining gap is stated too (#3204).** The imperative gate is applied per *sentence* rather
than per line, because a line-level gate admits the whole line on its first sentence and then lets
a later descriptive one be reported. That is correct, and it is also why the count is 69 rather
than the 31 a line-level gate produced: 38 genuine imperative prohibitions sit as a *later*
sentence on their line and were being withheld. Sampling those additions found both real findings
and a residual false-positive class — a positive alternative supplied as a bare imperative after a
separator ("Never confirm X — delegate to Y") is not recognised, because pairing is matched against
a fixed marker list. #3180 solves that with a closed function-word stoplist; adopting it is #3204.

- **`emit-findings.sh`'s cell escaping is now idempotent.** A naive `gsub` double-escaped a pipe the
source had already escaped — `a \| b` became `a \\| b`, which GFM reads as a literal backslash
followed by a **live** delimiter, splitting the row so the fix action misreads it. This repo writes
literal `\|` in its own tables, so the case is real rather than theoretical. Already-escaped pipes
are parked on a sentinel and restored single-escaped. **Also identified in #3180**, which notes the
sibling producers (`ai-slop`, `claude-config:audit-instructions`) carry the same latent defect in
their own copies — out of scope here, worth its own sweep.

## [0.21.0]

### Added
Expand Down
Loading