Skip to content

feat(docs-hygiene): negation-without-positive detector on audit-noise, wired to the findings relay (0.20.0) - #3180

Closed
kyle-sexton wants to merge 6 commits into
mainfrom
feat/3123-negation-without-positive
Closed

feat(docs-hygiene): negation-without-positive detector on audit-noise, wired to the findings relay (0.20.0)#3180
kyle-sexton wants to merge 6 commits into
mainfrom
feat/3123-negation-without-positive

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

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-noise gains a sixth shape that does, and becomes this plugin's first detector-findings producer.

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; the claude-4-best-practices URL now redirects to claude-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.

  • The cue (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.
  • The line must end its sentence. This repo hard-wraps prose, so a continuation line cannot be shown to lack a positive that sits on the next line, and the pairing rule is per sentence. A table row ends in | 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 .md minus **/evals/fixtures/** and CHANGELOG.md), read every new finding individually, and dropped two cue classes as a result:

Findings T1 T2
Baseline 43 9 34
First-draft patterns 119 9 110
Shipped 109 9 100

The middle row is the point of the exercise. Reading its 76 new findings produced the two tightenings:

  • A third-person -s verb after Never/Avoid is 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-led s — process, discuss, focus, bypass, guess — are excluded from the test rather than caught by it. Stated limitation: a plural noun reads the same way, so Avoid sleeps in tests. also declines. Recorded in the crosswalk row.
  • Emphasis runs sit between a sentence terminator and its following space (…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 (citation 3, enum-list 5, ghost-ref 34, scope-meta 1, 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: songwriting 14, then source-control and machine-health at 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:

  • A ## Surfaces count 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 states count=not-tallied-v1, with a test pinning the literal so a future count=0 cannot silently assert full coverage.
  • The tier argument was self-refuting — it argued both that no degradation trigger is nameable and that the source guidance shows the positive form steers better. Resolved by naming what the guidance actually describes: an effect on the reading model, not a defect in the artifact under review. SUGGESTION stands, honestly.
  • flush_record leaked an orphan fragment forward, composing a row that was well-formed, plausible, and wrong — a Location the 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-approved git commit — contradicting the read-only rule this very slice wrote. Narrowed to read verbs.
  • Two assertions were vacuous, including the AC6 frontmatter test: its cue was not line-initial, so it passed with 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.
  • Four precision defects (the -s test misreading plural nouns after Avoid; 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 at plugins/debugging/skills/debug/SKILL.md:90.

One reported miss did not reproduce: rhyme-worksheets.md:896 sits 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 the evals/fixtures and CHANGELOG.md exclusions this skill documents, which is why its totals differ from the table above.

The relay wiring

--persist-findings emits a conforming type: review-findings artifact for review:fanout fix, per docs/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-positive has a severity-crosswalk row, so the other five have no tier to look up and are counted per rule id in ## Surfaces with reason=no-severity-crosswalk-row rather 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. Following testing:audit, the artifact now states the limit (count=not-tallied-v1) instead of inventing a number, and a test pins the literal so a future count=0 cannot silently assert full coverage.

Three repo-root spellings, and the third is load-bearing. On Git Bash git rev-parse --show-toplevel answers C:/Users/u/AppData/Local/Temp/t/repo while the caller reached the same directory as /tmp/t/repo. Matching only the git form leaves every Location absolute — and nothing reports it, because an absolute path is still a well-formed cell. The primary anchor is now derived from the caller's own pwd minus 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:

Read-only. No Edit, no Write, no mutating Bash ops. The author owns every treatment edit.

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-slop was checked as precedent and rejected for this: its read-only prose reconciles read-only with its fix verb, and audit-noise has no fix verb. The structural match is #3176's report-only audit-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 (.md extension, type:, exact branch:, 7-column parseable table, no record to subtract, readable ISO-8601 date:, repo-relative Location) → remediations applied at each Location → protected surfaces verified untouched → re-scan returns no candidates. 18 passed, 0 failed.

Gate Result
detect.test.sh All 79 checks passed (was 61)
emit-findings.test.sh (new) All 38 checks passed
relay proof 18 passed, 0 failed
mutation checks frontmatter skip disabled → 4 fail; emitter guard removed → 4 fail
skill-quality:check (source checkout) PASS — 0 errors, 1 warning; all 6 base-ref trigger phrases preserved; 145/500 lines
check-detector-findings-crosswalk.sh --check OK, 23 rule rows
check-fixture-git-isolation.sh --check OK
check-changelog-parity.sh --check OK
check-skill-count-claims.sh --check OK
check-cross-plugin-source-drift.sh --check OK
generate-cheatsheet.mjs --check in sync
check-evals-quality.sh PASS (5 warnings, all pre-existing)
shellcheck / shfmt / markdownlint / typos / editorconfig clean

The one skill-quality warning (no Gotchas surface) is pre-existing#3162 reports the same single warning on this skill.

Notes for the reviewer

Fix

audit-noise gains a negation-without-positive shape and becomes this plugin's first detector-findings producer, 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-findings adds an opt-in emission of a conforming type: review-findings artifact for review:fanout fix, per docs/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


🤖 Generated with Claude Code

https://claude.ai/code/session_014QH5jtNyrDCYYsDgxMxkHk

kyle-sexton and others added 5 commits August 23, 2026 02:50
…, 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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +103 to +105
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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`. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

🔧 Lane claim — melo-lap-001-merge-20260823T045045Z (conflict-batch coordinator)

Claiming this PR to diagnose and attempt conflict resolution (/source-control:resolve-conflicts) against current main. Will push if resolution succeeds, or report the specific blocker if not. No merge will be performed by this pass.

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).

Copy link
Copy Markdown
Contributor Author

Superseded by #3194, but your calibration is being adopted — and it was right

Apologies: 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 now

After merging, I measured the shipped shape against an 85-file sample of this repo's own tracked markdown:

Shape #3194 as merged
negation 1053
the other eight, combined 10

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 --persist-findings.

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 0.21.1, crediting this PR as the source in the changelog, the PR body, and the code comment at the call site. The tracking issue is #3201.

What is not carried over

Being 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:

  • the 1140-file corpus sweep and its before/after table
  • the two-reviewer audit (1 CRITICAL, 12 IMPORTANT) and its fixes — the esc() non-idempotency, the flush_record orphan-fragment leak, the mutation-verified fixtures
  • the count=not-tallied-v1 honesty fix on ## Surfaces
  • the three-repo-root-spellings fix for Git Bash, which you flag as latent in the two sibling emitters
  • your SUGGESTION tier argument, which is more carefully reasoned than the IMPORTANT I shipped

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 esc() and repo-root ones in particular look like real defects still live in the merged code and the sibling producers.

Closing as superseded-but-harvested rather than duplicate.


Generated by Claude Code

kyle-sexton added a commit that referenced this pull request Aug 23, 2026
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
kyle-sexton added a commit that referenced this pull request Aug 23, 2026
…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
kyle-sexton added a commit that referenced this pull request Aug 23, 2026
… (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.
kyle-sexton added a commit that referenced this pull request Aug 23, 2026
…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>
@kyle-sexton
kyle-sexton deleted the feat/3123-negation-without-positive branch August 24, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(docs-hygiene): negation-without-positive detector on audit-noise, wired to the findings relay

1 participant