feat(docs-hygiene): negation-without-positive detector on audit-noise, wired to the findings relay (0.20.0) - #3180
feat(docs-hygiene): negation-without-positive detector on audit-noise, wired to the findings relay (0.20.0)#3180kyle-sexton wants to merge 6 commits into
Conversation
…, wired to the findings relay (0.20.0) `write-for-agents` "Prompt the positive" is the write-side rule; nothing audited it afterwards. `audit-noise` gains a sixth shape that does, and becomes the plugin's first detector-findings producer. Scope is the decidable core. The cue must be line-initial, so descriptive negations never select, and the line must end its sentence, because this corpus hard-wraps prose and a continuation line cannot be shown to lack a positive that sits on the next line. Tier 2: a prohibition is sometimes the correct form, so the scanner surfaces and a reviewer rules. Calibrated against the repo's own 1136-file corpus; two cue classes were dropped after reading every finding — third-person `-s` capability rosters, and emphasis runs hiding a sentence boundary from the positive-clause test. Only `rule-negation-without-positive` carries a severity-crosswalk row, so the other five shapes are counted as declined with `reason=no-severity-crosswalk-row` rather than emitted. The adoption forced the read-only hard rule to separate target mutation (still absolute) from artifact emission (bounded, opt-in, human-gated) in the rule's own text, rather than shipping a detector that quietly violated it. Closes #3123 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014QH5jtNyrDCYYsDgxMxkHk
…omputes
The crosswalk row and `persist-findings.md` both said the negation rule's
decline classes "are counted per rule in `## Surfaces`". They are not: a
candidate declined at selection — by the third-person `-s` test, or by a
section, fence, frontmatter or opt-out-marker exemption — never becomes a
detector record, so the emitter never sees one to count. Only the five
shapes carrying no crosswalk row are counted.
The contract names this exact failure ("Omit rather than fabricate — an
invented `## Surfaces` line asserts coverage that was never attempted"), and
the crosswalk gate cannot catch it: it checks that a cell is non-empty and
argued, never that the claim is true.
Following `testing:audit`'s precedent, the artifact now states the limit
rather than inventing a number, and a test pins the literal so a future
`count=0` cannot silently assert full coverage instead.
Also: the third-person test declines a plural noun the same way it declines
a verb (`Avoid sleeps in tests.`), now stated in the row; the flag must be
stripped before `detect.sh`, whose parser exits 2 on any unrecognized `-*`;
and the calibration corpus is 1140 files, not 1136.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QH5jtNyrDCYYsDgxMxkHk
`just`, `simply`, `then` lead an imperative without being one, so the
positive-clause test looks THROUGH them to the next word. With nothing
after them the loop had nowhere to look, and the adverb fell through as if
it were the content word it was standing in front of — silently suppressing
a real finding ("Never resolve the thread, just.").
Both directions are now pinned: a dangling adverb reports, an adverb-led
imperative ("Don't diagnose yet. Just mark.") still suppresses. Only the
pair distinguishes "looked through" from "treated as content".
No corpus effect — a full re-sweep of the 1140-file corpus is byte-identical
per file to the previous one, which is what makes this a latent-edge fix
rather than a calibration change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QH5jtNyrDCYYsDgxMxkHk
…ion detector
Two fresh-context reviewers audited the artifact with the author's rationale
withheld. Every finding they reproduced is fixed here.
Detector:
- The third-person `-s` decline is scoped to `Never`. Applied to `Avoid` it
read a PLURAL NOUN as a verb and dropped real imperatives ("Avoid
conditions the transcript cannot show."). Every capability roster it must
decline opens with `Never`, so narrowing loses nothing — and the admission
test wants an unresolved case to emit.
- The contrast test is word-anchored. Unanchored, "preferred" and
"preference" silently declined a bare prohibition on evidence the rule
never had.
- Trailing emphasis is peeled before the sentence-terminator test, so a fully
bolded directive fires. `**Never edit the generated file.**` ends in `*`,
and bolded directives are everywhere in this corpus.
- A clause is re-stripped after the transparent-adverb peel and its leading
emphasis / link-open characters removed, so "_Read it_ from the
environment." is seen as the alternative it is.
- `Don’t` (curly apostrophe) is a cue. Spelled as an alternative rather than
a bracket expression, which matches the multibyte character byte-wise.
Emitter:
- `flush_record` resets on the no-shape path. Without it an orphan fragment
leaked its line and excerpt into the NEXT record and composed a row that
was well-formed, plausible and wrong.
- `esc()` masks an already-escaped pipe, so a source `\|` no longer
round-trips to `\|` — which GFM reads as a literal backslash plus a LIVE
delimiter, splitting the cell.
- `Confidence` is OMITTED, never `high`. What is uncertain is defect-hood,
and the contract routes realness uncertainty to that field;
`rule-mock-only-oracle` is the worked precedent.
- `fired_cue` loops the marker peel like the detector does, so a nested
marker no longer reports `cue="unknown"`.
- A record missing its location is refused loudly instead of emitting a
Location the relay cannot fence to.
- `Location` falls back to `~` for a path outside the repo, per the writer
contract; the declined-shape loop counts from `split()` rather than a
literal.
Tests — two assertions were proven vacuous and are now load-bearing, each
verified BY MUTATION:
- The AC6 frontmatter test asserted the one constraint #3118 calls
load-bearing with a fixture whose cue was not line-initial, so it passed
with detect.sh's frontmatter skip deleted. Block scalars put the values on
their own lines; disabling the skip now fails four checks.
- The untabled-shape assertion keyed on a blank line and could never match.
It now keys on the shapes' real locations; deleting the emitter's guard
fails four checks.
- The capability roster moved out of the "paired" fixture, which it was
never an instance of, and both opt-out markers gained controls proving the
marked lines fire unmarked.
Docs:
- The crosswalk row no longer argues both that no degradation trigger is
nameable AND that the source guidance shows the positive form steers
better. The steering effect is a property of the reading model, not a
degradation of the artifact under review — which keeps SUGGESTION honest
instead of self-refuting.
- The `Bash(git:*)` grant auto-approved `git commit`, contradicting the
read-only rule this slice just wrote. Narrowed to read verbs.
- The consumer-precedence mapping is declared NOT performed in v1 rather
than instructed as an `Edit` the skill holds no grant for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QH5jtNyrDCYYsDgxMxkHk
The four precision fixes shift the measurement, so the table is re-derived from one clean sweep rather than adjusted: 43 findings -> 109, the 66 new ones all of the new shape. Per-shape counts for the five existing shapes are byte-identical to the baseline, so the whole delta is still the new shape. Net corpus effect of the review round is +1 — the fully bolded directive at plugins/debugging/skills/debug/SKILL.md:90, which the trailing-emphasis peel recovered. The `-s` scoping fix has no corpus delta: the line reported as a miss sits inside a fenced block and never reaches the shape scan, so the fix prevents the class rather than recovering an instance. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014QH5jtNyrDCYYsDgxMxkHk
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ab5e34b180
ℹ️ 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".
| if ! LC_ALL=C grep -q '^Summary total: files=' "$FROM"; then | ||
| echo "emit-findings.sh: $FROM has no detect.sh Summary total row; not detector output" >&2 | ||
| exit 3 |
There was a problem hiding this comment.
Refuse detector runs that scanned zero files
When the target directory contains no Markdown files, all targets are excluded, or chunking selects nothing, detect.sh emits Summary total: files=0 ..., which passes this check and causes the emitter to write a findings artifact claiming the rule returned no result. This contradicts context/persist-findings.md, which requires writing nothing when nothing was scanned, and gives the relay false coverage evidence; parse the files count and stop without creating the output when it is zero.
Useful? React with 👍 / 👎.
|
|
||
| | `ai-slop:audit` | Conforming, default-on for repo-examining runs | The first prose detector, and the first with a rule-sourced tier spread (twelve SUGGESTION style rules, three IMPORTANT generation-residue rules — each argued in its crosswalk row). Selection is fully mechanical (byte-sequence, phrase-list, and density scans; no withholding verdict), so the fail-safe criterion is met by construction; its decline evidence is the in-file ignore markers, config path exemptions, and code-fence stripping, counted per rule in `## Surfaces` from the detector's own `Summary` rows. The findings file is model-persisted by the skill (the deterministic `detect.sh` emits a parseable report only), per its `context/persist-findings.md` read of this contract: fetch this contract before the first write and refuse to persist when unreachable. Leads every `Finding` cell with the qualified rule id and the fired condition in the run's own values (the zero-tolerance marker or the density/threshold/hits/words tuple); `Confidence` is `high` on every row. Judgment-rubric findings never enter the file — no crosswalk row, no relay. **The first producer to declare producer-owned remediation**, and the case that section was written from: only `rule-utm-params` is auto-applicable, and its other fourteen rows are contained to `Location` yet safe only under the rewrite discipline in this plugin's own `reference/rewrite-guide.md`. Each of those fourteen rows leads its `Auto-applicable` cell with ``No, remediated by `/ai-slop:audit fix` `` — the code-span form the grammar states — which the relay resolves through the qualified rule id every emitted row already leads its `Finding` cell with — so those rows route to that action instead of the cleanup route's `/simplify`, which reads no findings file and loads no rewrite guide. **The declaration required no change to what this producer emits**, which is the point of siting it in the crosswalk: its emitted `Action` cells already describe the repair and already name the fix action on the rules with nothing more specific to say. After that fix runs the skill re-runs the detector and re-emits, so no stale file survives its own remediation. Omits `tier:`, `## By dimension`, and `## Unparsed`. | | ||
|
|
||
| | `docs-hygiene:audit-noise` | Conforming, opt-in | The first producer whose emitted set is a strict SUBSET of its own classifier, and the case that makes "no crosswalk row, no relay" visible rather than silent: it detects six markdown noise shapes, exactly one of which (`rule-negation-without-positive`) has a row here, so the other five never enter the file and are counted per rule id in `## Surfaces` with `reason=no-severity-crosswalk-row`. They still reach the human through the ordinary report — the boundary is which findings reach the RELAY, not which are found. Selection is a mechanical line scan; its two decline classes are stated in the rule row and neither is a judgment call, so the fail-safe-toward-emitting criterion is met by construction rather than by a fall-through rule. `Confidence` is OMITTED on every row, never `high`: what is uncertain is defect-hood — a prohibition is sometimes the correct form — which is the case `ai-slop/audit/rule-mock-only-oracle` established for the high-or-omitted rule. Persists behind `--persist-findings`; a bare invocation reports and stops. **The adoption forced an amendment to the skill's own read-only hard rule**, which read "No `Edit`, no `Write`, no mutating `Bash` ops" and so forbade the producer contract as written: it now separates TARGET MUTATION (still absolute — the skill never edits an audited document) from ARTIFACT EMISSION (permitted, bounded to one file at the resolved destination, and a proposal for a relay a human still gates). Shipping a detector that quietly violated its own stated rule was the alternative. Leads every `Finding` cell with the qualified rule id and the fired condition in the run's own values (`cue="Never", positive-clauses=0`); `Confidence` is `high` on every row; `Location` is repo-relative and IS the remediation site, so the off-site disposition is not in play. It declares **no** remediation owner and that is correct rather than an omission — `audit-noise` ships no fix action, so there is no invocation a consumer could run, and its cells keep the plain `No — <reason>` form while Step 4's judgment fence surfaces the rows. Omits `tier:`, `## By dimension`, and `## Unparsed`; keeps `## Surfaces`. | |
There was a problem hiding this comment.
Keep the adopter confidence declaration consistent
This adopter row first says that Confidence is omitted on every row and never high, but later in the same row says it is high on every row. The emitter, its tests, the new crosswalk entry, and context/persist-findings.md all implement omission, so the latter statement makes the published conformance contract self-contradictory and can mislead future maintainers into changing the producer incorrectly.
Useful? React with 👍 / 👎.
|
🔧 Lane claim — melo-lap-001-merge-20260823T045045Z (conflict-batch coordinator) Claiming this PR to diagnose and attempt conflict resolution ( |
Composes this PR's negation-without-positive shape (audit-noise's 6th) with main's three residue shapes from #3162/#3187 (audit-noise's 7th-9th), for a combined nine-shape classifier. Renumbers this PR's version from the colliding 0.20.0 to 0.21.0 (main already shipped 0.20.0 and 0.20.1), and updates every "six/eight shapes" claim across CHANGELOGs, SKILL.md, README, the detector-findings crosswalk, emit-findings.sh's declined-shape roster, and its test suite to the composed total of nine (one crosswalk row, eight declined).
Superseded by #3194, but your calibration is being adopted — and it was rightApologies: this PR was open against #3123 with the corpus calibration already done, and #3194 was built and merged over it. That was my error, and the mechanism is worth recording:
Your narrowing was the right call, and I have the numbers nowAfter merging, I measured the shipped shape against an 85-file sample of this repo's own tracked markdown:
12.4 negation findings per file — 99% of everything the skill produced. Against your measured 109 across 1140 files, that is roughly 100× noisier. The shape as merged swamps the human report and would flood the relay under Applying your two gates — imperative-only (line-initial after list/blockquote/emphasis markers) and the line must close its own sentence — takes the same 85 files from 1053 → 31, with every other shape's count byte-identical. Both gates are exactly the narrowings you established, and both hold up empirically. #3202 lands them as What is not carried overBeing straight about what is being lost by closing this rather than rebasing it — several things here are better than what merged, and are not in #3202:
If you would rather reopen this and reconcile it forward instead of taking #3202, say so and I will close #3202 — the merged state is not sacred and several of the items above are worth having on their own. Otherwise those are worth filing individually; the Closing as superseded-but-harvested rather than duplicate. Generated by Claude Code |
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 carry the same latent defect in their own copies of esc() — out of scope here. Refs #3201
…e lint Three review findings on #3202, each reproduced before being fixed: - shell-portability-lint FAILED on `\>` — a GNU word-boundary operator elsewhere in the toolchain. The lead pattern now lives in a variable with `>` unescaped, which also stops the unquoted `>` confusing the [[ ]] parser - the imperative gate was per LINE, so a line opening imperatively carried a later DESCRIPTIVE sentence into a finding — exactly the prose the gate exists to exclude, re-entering behind a compliant opener. Now per sentence - the lead stripper missed `1)` ordered items and task-list checkboxes, both ordinary ways this repo writes a directive; missing them withheld silently The per-sentence gate moves the measured count on the 85-file sample from 31 to 69, because 38 genuine imperative prohibitions sit as a LATER sentence on their line and were being withheld. Sampling those additions found real findings plus a residual false-positive class: a positive supplied as a bare imperative after a separator ("Never confirm X — delegate to Y") is not recognised, because pairing matches a fixed marker list. #3180 solves that with a closed function-word stoplist; filed as #3204 rather than guessed at here. Refs #3204
… (0.21.1) (#3202) Closes #3201 Adopts the two scope gates established in #3180: the negation cue must open the line (after list, blockquote and emphasis markers), and the line must close its own sentence. On an 85-file corpus sample this takes the negation shape from 1053 findings to 31, with every other shape's count byte-identical. Refs #3194, #3180, #3123, #3195.
…resolve repo-root spellings (0.3.6, 0.39.4) (#3242) ## Summary #3202 fixed two latent defects in `docs-hygiene:audit-noise` `emit-findings.sh` only. Each producer carries its own copy, so the same defects were still live in `ai-slop` and `claude-config:audit-instructions`. **Defect 1 — `esc()` is not idempotent.** A naive `gsub(/\|/, "\\|")` turns `a \| b` into `a \\| b`. GFM reads that as a literal backslash followed by a live delimiter, so the row splits and the fix action misreads it. This repo writes literal `\|` in its own tables. **Defect 2 — repo-root spelling mismatch.** `git rev-parse --show-toplevel` can answer `C:/Users/…` while the caller is at `/tmp/…` (Git Bash). Consequences differed per producer and were confirmed individually: - `ai-slop` **failed open** — Location stayed absolute and nothing reported it, because an absolute path is still a well-formed cell. - `claude-config` **failed closed** — a path it could not prove was under the root was declined as `outside-repo-root`, so an in-repo finding was silently missed. ## Fix `esc()` now parks an already-escaped pipe on a sentinel first, matching #3202. Root resolution prefers the caller `pwd` minus git `--show-prefix`, with git's two spellings as fallbacks, matching the #3180 form that #3202 did not actually land. Shared code was considered and declined: plugins are portable and there is no existing cross-plugin emit-findings library. The three copies now agree on the same two helpers instead. ai-slop 0.3.6 (0.3.5 is #3179), claude-config 0.39.3. **docs-hygiene residual (not fixed here):** #3202 shipped the idempotent `esc()` but not the three-spelling root. That producer still resolves root via `git rev-parse --show-toplevel` only. ## Verification - `plugins/ai-slop/skills/audit/scripts/detect.test.sh` — 98/98 (idempotent esc + symlink spelling mismatch) - `plugins/claude-config/skills/audit-instructions/scripts/emit-findings.test.sh` — 81/81 (same two cases; fail-closed fence still declines a true out-of-repo path) - `scripts/check-changelog-parity.sh --check-bump origin/main` — pass - markdownlint on both changelogs — 0 issues ## Related Closes #3203 Refs #3180, #3202 --------- Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
Closes #3123
Summary
docs-hygiene:write-for-agents"Prompt the positive" tells authors to write what to do rather than what to avoid. Nothing audited it afterwards.audit-noisegains a sixth shape that does, and becomes this plugin's firstdetector-findingsproducer.Official guidance supplies the worked pair the shape is built around — Prompting best practices, "Control the format of responses": "Tell Claude what to do instead of what not to do",
"Do not use markdown in your response"→"Your response should be composed of smoothly flowing prose paragraphs."(Verified by fetch; theclaude-4-best-practicesURL now redirects toclaude-prompting-best-practices, and the citation uses the canonical one.)The detector
Scope is the decidable core, and the two narrowings are the whole reason the rate is survivable in an instruction-heavy corpus.
Do not,Do NOT,Don't,Never,Avoid) must be line-initial, after list, blockquote and emphasis markers. Descriptive negations — "the script does not read the config" — never select; only an imperative prohibition does.|and is excluded by the same test.A mid-sentence prohibition ("Prefer X; never Y") is out of scope by construction: correctly paired prose puts the cue after the positive, so the line-initial test already declines it. The same property means the new shape-table row cannot self-match — verified: zero findings anywhere under
plugins/docs-hygiene/skills/audit-noise/.Tier 2, deliberately. A prohibition is sometimes the correct form, and deciding a given hard guardrail cannot be phrased positively is a judgment. The scanner surfaces; a reviewer rules. That is what Tier 2 means here, and
SKILL.md's own tier semantics force it — a finding whose ruling includes an edit is Tier 2 or 1 by definition.Calibration — measured, not guessed
Swept this repository's own 1140-file tracked-markdown corpus (all tracked
.mdminus**/evals/fixtures/**andCHANGELOG.md), read every new finding individually, and dropped two cue classes as a result:The middle row is the point of the exercise. Reading its 76 new findings produced the two tightenings:
-sverb afterNever/Avoidis a capability roster describing a component ("Never audits a target that is not a git repository", "Never mutates without presenting the change set"), not an instruction to anyone. An imperative never takes that form. Imperatives ending in a doubled or vowel-leds— process, discuss, focus, bypass, guess — are excluded from the test rather than caught by it. Stated limitation: a plural noun reads the same way, soAvoid sleeps in tests.also declines. Recorded in the crosswalk row.…dialog.** Leave it open.), so the sentence split never saw that boundary and reported the positive as absent. Stripping emphasis before splitting is what lets it be seen.A closed function-word stoplist decides whether a clause after a separator names an alternative, rather than an allow-list of imperative verbs: testing what a clause is NOT generalizes, where a verb list only ever covers the verbs its author thought of. Leading adverbs ("Just mark.", "Simply re-run it.") are looked through rather than stopped on.
No existing shape's behavior changed — per-shape counts are byte-identical across every sweep (
citation3,enum-list5,ghost-ref34,scope-meta1, before and after), so the entire delta is the new shape. Run cost is unmoved: 9m26s → 9m39s over 1140 files.The 66 shipped findings spread across 27 plugin areas (densest:
songwriting14, thensource-controlandmachine-healthat 5). Most are legitimate hard guardrails a reviewer will dismiss — which is the designed outcome for a Tier 2 shape, not noise.Independent review
Two fresh-context reviewers audited the artifact with my rationale withheld. Between them they reproduced 1 CRITICAL, 12 IMPORTANT and a set of SUGGESTIONs; every finding either was already fixed in a later commit on this branch or is fixed here. The ones worth naming:
## Surfacescount nothing computed. The row claimed the negation rule's declines "are counted per rule" — they are not, because a candidate declined at selection never becomes a detector record. The crosswalk gate cannot catch this: it checks a cell is non-empty and argued, never that it is true. Now statescount=not-tallied-v1, with a test pinning the literal so a futurecount=0cannot silently assert full coverage.flush_recordleaked an orphan fragment forward, composing a row that was well-formed, plausible, and wrong — aLocationthe fix action would fence a remediation to that the excerpt never came from.esc()was not idempotent: a source\|round-tripped to\\|, which GFM reads as a literal backslash plus a live delimiter, splitting the cell. This repo writes literal\|in its own tables.Bash(git:*)auto-approvedgit commit— contradicting the read-only rule this very slice wrote. Narrowed to read verbs.detect.sh's frontmatter skip deleted outright. Both fixtures are rebuilt and verified by mutation — disabling the skip now fails 4 checks; deleting the emitter's guard fails 4 checks.-stest misreading plural nouns afterAvoid; an unanchored contrast test declining on "preferred"; a fully bolded directive never firing; a clause dropped after the adverb peel). Net corpus effect +1 — the bolded directive atplugins/debugging/skills/debug/SKILL.md:90.One reported miss did not reproduce:
rhyme-worksheets.md:896sits inside a fenced code block, so it never reaches the shape scan at all — that reviewer's instrumented replay bypassed the fence handling, and its corpus (1188 files) also omitted theevals/fixturesandCHANGELOG.mdexclusions this skill documents, which is why its totals differ from the table above.The relay wiring
--persist-findingsemits a conformingtype: review-findingsartifact forreview:fanout fix, perdocs/conventions/detector-findings/README.md. Opt-in; a bare invocation reports and stops.Exactly one of the six shapes reaches the relay. Only
rule-negation-without-positivehas a severity-crosswalk row, so the other five have no tier to look up and are counted per rule id in## Surfaceswithreason=no-severity-crosswalk-rowrather than emitted. They still reach the human through the ordinary report — the boundary is which findings reach the relay, not which are found. (#3176's precedent, applied to a producer whose emitted set is a strict subset of its own classifier.)Tier is SUGGESTION, argued in the row on the same walk as
rule-em-dash: CRITICAL fails every limb because the phrasing of an instruction computes nothing; IMPORTANT fails because a shipped default names no stated rule of the consuming repo, and no degradation trigger is nameable — the prohibition and its positive restatement carry the same constraint, so a reader following either complies. SUGGESTION's test holds. The row says explicitly what the tier does not claim: that every flagged line should change.A count nothing computes was removed before it shipped. The first draft's row claimed the negation rule's declines "are counted per rule in
## Surfaces". They are not — a candidate declined at selection never becomes a detector record, so the emitter never sees one. The crosswalk gate cannot catch that: it checks a cell is non-empty and argued, never that the claim is true. Followingtesting:audit, the artifact now states the limit (count=not-tallied-v1) instead of inventing a number, and a test pins the literal so a futurecount=0cannot silently assert full coverage.Three repo-root spellings, and the third is load-bearing. On Git Bash
git rev-parse --show-toplevelanswersC:/Users/u/AppData/Local/Temp/t/repowhile the caller reached the same directory as/tmp/t/repo. Matching only the git form leaves everyLocationabsolute — and nothing reports it, because an absolute path is still a well-formed cell. The primary anchor is now derived from the caller's ownpwdminus git's reported prefix. (The two sibling emitters carry the same latent bug; out of scope here, flagged for the fleet.)The read-only hard rule
The brief called this the real work of the slice, and it was. The rule read:
Emitting a findings file is a write, so as written the rule forbade the producer contract. Its intent was always that the audit never edits the document under audit, and it now says so in its own text rather than by implication: target mutation stays absolute, while artifact emission is permitted and bounded — one file, at the convention-resolved destination, under
--persist-findings, as a proposal for a relay a human still gates. Never an applied treatment, never inside the audited corpus, and a bare invocation still writes nothing.Not widened past what the findings write requires.
ai-slopwas checked as precedent and rejected for this: its read-only prose reconciles read-only with itsfixverb, andaudit-noisehas no fix verb. The structural match is #3176's report-onlyaudit-instructions.Verification
Run, not inspected.
End-to-end relay proof (AC 4), scripted and reproducible, on a scratch repo and branch: detector runs → self-ignore guard → emit to the rung-5 findings home → every
fix-pass-mode.md"Step 1" admission clause checked (.mdextension,type:, exactbranch:, 7-column parseable table, no record to subtract, readable ISO-8601date:, repo-relativeLocation) → remediations applied at eachLocation→ protected surfaces verified untouched → re-scan returns no candidates. 18 passed, 0 failed.detect.test.shemit-findings.test.sh(new)skill-quality:check(source checkout)check-detector-findings-crosswalk.sh --checkcheck-fixture-git-isolation.sh --checkcheck-changelog-parity.sh --checkcheck-skill-count-claims.sh --checkcheck-cross-plugin-source-drift.sh --checkgenerate-cheatsheet.mjs --checkcheck-evals-quality.shThe one
skill-qualitywarning (no Gotchas surface) is pre-existing — #3162 reports the same single warning on this skill.Notes for the reviewer
docs-hygieneversions; this is a merge-time reconcile, not a defect here.audit-noise's shape table and both tiercasearms — the same three surfaces this touches. The conflict is textual and mechanical (one table row, twocasearms, the count words); neither delivers the other's shape. Checked before building: feat(docs-hygiene): audit-noise gains three residue shapes (0.20.0) #3162 deliversplan-reference/conversational-antecedent/ticket-pr-residue, not this one.Fix
audit-noisegains anegation-without-positiveshape and becomes this plugin's firstdetector-findingsproducer, closing #3123. The detector is a mechanical, tightly-scoped scan: it flags a line-initial imperative negation (Do not,Do NOT,Don't,Never,Avoid) only when the line ends its own sentence and states no positive alternative (no contrast word —instead/rather/prefer— and no content clause after a separator), calibrated against this repository's 1140-file tracked-markdown corpus and refined through independent review to 9 baseline / 100 shipped Tier-2 findings with no change to any existing shape's behavior.--persist-findingsadds an opt-in emission of a conformingtype: review-findingsartifact forreview:fanout fix, perdocs/conventions/detector-findings/README.md— a bare invocation still only reports. Because emitting that one artifact is technically a write, the skill's read-only hard rule is amended in its own text to separate target mutation (still absolute — the audited document is never edited) from artifact emission (permitted and bounded to that one opt-in file), rather than shipping a detector that quietly violated its previously-stated rule.Related
mainfirst and added three moreaudit-noiseshapes (plan-reference,conversational-antecedent,ticket-pr-residue), independently claiming the samedocs-hygiene0.20.0 version this PR did. This PR does not close it; the two shape sets were composed together (all nine shapes coexisting) and the version conflict reconciled during the merge intomain.detect.shfix this PR's original 0.20.0 version bump assumed as its unreleased base, later joined by a follow-up fix toSKILL.md's preview line at 0.20.1. This PR does not close either; both landed onmainahead of this branch and are preserved intact in the mergedCHANGELOG.md.🤖 Generated with Claude Code
https://claude.ai/code/session_014QH5jtNyrDCYYsDgxMxkHk