Skip to content

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

Merged
kyle-sexton merged 4 commits into
mainfrom
claude/work-items-integration-o22y7w
Aug 23, 2026
Merged

feat(docs-hygiene): negation-without-positive detector on audit-noise, wired to the findings relay (0.21.0)#3194
kyle-sexton merged 4 commits into
mainfrom
claude/work-items-integration-o22y7w

Conversation

@kyle-sexton

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

Copy link
Copy Markdown
Contributor

Closes #3123

Summary

Adds a ninth noise shape to docs-hygiene:audit-noise — a prohibition with no positive alternative stated in the same sentence — and makes the skill a conforming detector-findings producer, reusing the wiring #3120 established in claude-config:audit-instructions. This is the audit-side completion of a doctrine the fleet already adopted on the write side (docs-hygiene:write-for-agents "Prompt the positive").

Fix

The detector (D3). negation, Tier 2 — its treatment includes an edit, so it cannot be Tier 3. Selection is per sentence, case-folded, whole-word, on the backtick-unwrapped line. Reading the unwrapped form rather than the inline-code strip the five older shapes read is load-bearing: a hard-guardrail marker is routinely the code span itself (`--force`, `rm -rf`), so stripping it would erase the very evidence the carve-out needs, turning a guardrail into a false finding rather than a withheld one.

The read-only rule is amended, not quietly violated. The rule read "No Edit, no Write, no mutating Bash ops", which as written forbade the producer contract this PR adds. It now states the distinction in its own text: read-only binds every audited target unconditionally, while the findings artifact is a new file in the gitignored memory tier, written only under --persist-findings, and is a proposal for a human-gated relay rather than an applied edit. The rule widens exactly that far — no audited file becomes writable, and a bare invocation still writes nothing.

All three withholding boundaries are evidence-gated, so an unresolved candidate is EMITTED (the crosswalk's admission test 2, checked on every boundary rather than only the one easiest to argue): a paired positive, a hard guardrail whose constraint a positive form cannot carry, and a worked example each require their evidence present on the sentence. Every marker matches as a whole word, and sentences are split so an embedded abbreviation cannot merge two clauses — both are cases where a shortcut would let one clause's carve-out silently swallow another clause's finding. The carve-out lives in the shared scanner, so the human report and the relay file give one candidate one disposition.

Crosswalk rows. rule-negation-without-positive at IMPORTANT, argued from severity.md's stated-rule limb rather than the degradation limb both audit-instructions rules walk. Auto-applicable: No, matching both sibling rules: contained to Location, but recovering the positive target is a rewrite judgment. Plus a non-emitting rule-negation-hard-guardrail row that states which ground it uses (the Boundary's "never reaches a relay", never a tier test).

Verification

End-to-end relay proof, run rather than inspected:

  • Detector on the fixture fires on exactly the 3 bare prohibitions and no-flags all 7 paired/guardrail/worked-example lines.
  • emit-findings.sh wrote a conforming type: review-findings file (repo-relative Location, IMPORTANT/high, rule id leading the Finding cell).
  • review:fanout fix --yes located it, admitted it on type: + exact branch:, merged, classified (2 surface-only, per the crosswalk's Auto-applicable: No), and wrote a digest-keyed consumption record.
  • Re-running Step 1's merge-set logic then reports SUBTRACTED on name + digest — the file is correctly retired from the next merge set.

Fences proven against forged input, so the writer's own fence is under test, not the scanner's: a forged frontmatter row, a body line quoting a trigger phrase from its own description, a forged non-crosswalk shape, and a ..-traversing path are each declined with a counted reason.

Gates (local, against the source checkout):

Gate Result
detect.test.sh All 125 checks passed
emit-findings.test.sh All 32 checks passed
all docs-hygiene suites pass
check-skill.sh PASS — 0 errors; all 7 base-ref trigger phrases preserved
check-detector-findings-crosswalk.sh PASS
check-changelog-parity.sh (--check, --check-bump, --check-order) PASS
typos / shellcheck / shfmt / markdownlint PASS
cheat-sheet sync in sync

Review round addressed

Eight findings across both automated reviewers, each reproduced by running the detector before being fixed. Two were the failure class this rule set exists to prevent — silent withholding on a carve-out boundary:

# Finding Disposition
1 secretary satisfied the secret guardrail; preferentially satisfied the prefer pairing Fixedsilent finding loss. Every marker fenced to a whole word; prefer inflections enumerated, vulnerab kept stemmed but bounded
2 Sentence splitter collapsed the whole line on an embedded abbreviation Fixedsilent finding loss, the most serious of the round. See e.g. the credential rotation policy. Never call the tool directly. split into one sentence, so the guardrail suppressed the prohibition. Now peeled right-to-left with a greedy leading .*, making an abbreviation over-split — the fail-safe direction. The comment claiming "over-split" was wrong and is corrected
3 don.t matched donut (raised independently by both reviewers) Fixed — apostrophe class, not .
4 Fired marker came from the first prohibition on the line Fixeddetect.sh carries Finding marker:, keeping ONE implementation of the sentence walk rather than a mirror free to drift
5 Lexical repo fence admitted a ..-traversing path Fixed — fails closed. Residual recorded at the site: an in-repo symlink pointing out still resolves past a lexical test
6 branch: unquoted when YAML implicitly types it Fixedtrue / null / no / 123 / dates quoted; ordinary names stay plain
7 allowed-tools granted unscoped Bash(git:*) Fixed — narrowed to the two subcommands used. The blanket grant authorized mutating git ops the read-only rule added in this same diff disclaims
8 negation is scoped to one physical line, so a soft-wrapped sentence is judged in pieces Deferred → #3195. Distinct from #2: that was a within-line splitter bug and is fixed; this is the cross-line case, whose fix changes detect.sh's iteration model and its interaction with every existing fence. False-positive direction only, never a silent withhold. Recorded as a known limitation in SKILL.md "Hard rules"

Each fix carries a regression test; the suites grew 115 → 125 and 23 → 32.

Related

Caveat — lane provenance

This ran without a work-items seam claim. The seam's coordination verbs are unavailable in the session that produced this PR: gh is 2.45 against a required ≥ 2.94, and GH_TOKEN is invalid (gh api 403), so no assignee/lease was acquired and claim-before-dispatch did not run. Flagged rather than left silent — if another lane was working #3123 concurrently, this PR would not have detected the collision.

…, wired to the findings relay (0.21.0)

Adds a ninth noise shape to `audit-noise` — a prohibition with no positive
alternative stated in the same sentence — and makes the skill a conforming
detector-findings producer, reusing the wiring #3120 established in
`claude-config:audit-instructions`.

The read-only hard rule is amended rather than quietly violated. It read "No
Edit, no Write, no mutating Bash ops", which as written forbade the producer
contract this adds. It now states the distinction in its own text: read-only
binds every audited target unconditionally, while the findings artifact is a
new file in the gitignored memory tier, written only under --persist-findings,
and is a proposal for a human-gated relay rather than an applied edit.

All three withholding boundaries are evidence-gated, so an unresolved candidate
is emitted: a paired positive, a hard guardrail whose constraint a positive form
cannot carry, and a worked example each require their evidence present on the
sentence. The carve-out lives in the shared scanner, so the human report and the
relay file give one candidate one disposition.

Closes #3123
…egration-o22y7w

# Conflicts:
#	plugins/docs-hygiene/.claude-plugin/plugin.json
#	plugins/docs-hygiene/CHANGELOG.md
#	plugins/docs-hygiene/skills/audit-noise/scripts/detect.test.sh
@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 7m 40s —— View job


Code review: docs-hygiene:audit-noise negation detector (0.21.0)

  • Gather context (PR diff via gh pr diff, changed files, CLAUDE.md)
  • Understand the request (run /review:code-review per instructions)
  • Review changed files for correctness/maintainability
  • Post findings (inline + summary)

Scope: reviewed the diff against origin/main for correctness and maintainability, per /review:code-review's criteria (security findings are scoped to this repo's claude-security-review lane and left out here). Focused on the new detection logic in noise-shapes.sh and the composition logic in emit-findings.sh, since that's where a defect would actually change behavior — the doc/changelog/eval additions are prose and were skimmed for internal consistency, no issues found there.

2 findings, posted inline:

  1. noise-shapes.sh:439don.t / shouldn.t use an unescaped regex . (intended to match both apostrophe styles per the comment above it), but . matches any character, so it also matches unrelated words like "donut". A sentence like "Buy a donut before the meeting." would false-positive as a negation finding.
  2. noise-shapes.sh:416-429 — the sentence splitter's own comment claims abbreviations like "e.g. " cause over-splitting (fail-safe direction); tracing the anchored regex shows the opposite: an abbreviation period not followed by whitespace makes the whole match fail on the first loop iteration, so the entire remaining line becomes one "sentence." That can let a hard-guardrail or paired-positive word elsewhere on the line silently suppress an unrelated, real prohibition later on the same line — directly contrary to this PR's stated fail-safe-toward-emitting design goal. Neither of these is exercised by the new eval fixtures or detect.test.sh cases.

Both come with a suggested fix and a "Fix this →" link. I did not verify these empirically (couldn't execute scripts in this environment — Bash access here is limited to read-only git/gh), so they're based on careful manual regex tracing rather than a run repro; worth a quick sanity-check run of detect.sh against the two example sentences above before merging.

Everything else in the diff (the read-only hard-rule amendment, the crosswalk rows, emit-findings.sh's fence logic for frontmatter/trigger-phrase/no-crosswalk-row, the CHANGELOG/README updates) reads as consistent with the stated design and didn't turn up other correctness issues on inspection. The PR body's own verification claims (gate results, eval counts) are author-reported and I did not re-run them — noting that per instructions rather than restating them as independently confirmed.

@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 2m 50s —— View job


Security Review

Task list

  • Gather context (PR diff, changed files, custom instructions)
  • Understand the request — instructed to invoke /review:security-review for this PR
  • Run /review:security-review and follow its instructions exactly
  • Post findings to this comment

Scope checked: all 14 changed files (shell scripts, SKILL.md, contract docs, changelogs, eval fixtures). No .github/workflows changes in this PR, so no Actions-trigger/permissions review was needed.

Finding (IMPORTANT)

allowed-tools grants unscoped git:*, contradicting this PR's own read-only hard ruleplugins/docs-hygiene/skills/audit-noise/SKILL.md#L6 (inline comment posted).

This PR adds "Bash(git:*)" to the skill's allowed-tools so emit-findings.sh can resolve the current branch and repo root. But emit-findings.sh only ever runs git branch --show-current and git rev-parse --show-toplevel, while Bash(git:*) is unscoped and also authorizes git reset --hard, git checkout -- <file>, git clean -fd, git push --force, etc. That directly conflicts with the Hard rules section this same PR edits a few lines later:

Read-only on every audited target. No Edit, no Write, no mutating Bash op against any file this skill audits.

Since this skill reads arbitrary tracked markdown as data, an injected instruction in an audited file could steer the agent toward a mutating git call the skill's own stated contract says is impossible — right now that's enforced only in prose, not in the tool grant. Suggested a narrowed grant (git branch --show-current, git rev-parse --show-toplevel only) inline, matching the precision of the other entries in that same array.

Noted in the inline comment: plugins/ai-slop/skills/audit/SKILL.md already ships the identical unscoped pattern, so this isn't a novel mistake — but this PR is where it's newly introduced for audit-noise, in the same diff that writes the "no mutating Bash op" invariant, so the inconsistency is local to this PR and worth fixing here.

Everything else reviewed, no issues found

  • scripts/emit-findings.sh — all dynamic values (branch name, file paths, excerpts) go through awk -v, never shell string interpolation; YAML scalar quoting for branch: correctly handles leading indicator characters; frontmatter/trigger-phrase/out-of-repo fences are re-verified in the script rather than trusted from the caller, as documented.
  • scripts/lib/noise-shapes.sh — pure regex/string classification, no eval, no command execution on audited content.
  • scripts/emit-findings.test.sh — correctly unsets GIT_DIR/GIT_WORK_TREE/GIT_CONFIG before fixture git init to avoid redirecting into the caller's repo.
  • No secrets/credentials handling, no network calls in the scripts themselves (the one raw.githubusercontent.com fetch is documentation instructing the agent to read the same repo's own contract doc, not code).
  • No GitHub Actions workflow files touched.
    · Branch: claude/work-items-integration-o22y7w

@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: bcb7734139

ℹ️ 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 thread plugins/docs-hygiene/skills/audit-noise/scripts/emit-findings.sh
Comment thread plugins/docs-hygiene/skills/audit-noise/scripts/lib/noise-shapes.sh Outdated
Comment thread plugins/docs-hygiene/skills/audit-noise/scripts/lib/noise-shapes.sh Outdated
Comment thread plugins/docs-hygiene/skills/audit-noise/scripts/lib/noise-shapes.sh Outdated
Comment thread plugins/docs-hygiene/skills/audit-noise/scripts/lib/noise-shapes.sh
Comment thread plugins/docs-hygiene/skills/audit-noise/scripts/emit-findings.sh Outdated
Comment thread plugins/docs-hygiene/skills/audit-noise/scripts/emit-findings.sh
Comment thread plugins/docs-hygiene/skills/audit-noise/SKILL.md Outdated
@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown

Last security-reviewed head: 9fce71fcb2a4f34d64a2a281e387219db8392772. On the next push, the relevance gate compares only the commits since this SHA; delete this comment to force a full re-review.

Seven findings from the automated reviewers on #3194, each reproduced before
being fixed:

- fence every carve-out marker to a whole word. Bare substrings let `secretary`
  satisfy the `secret` guardrail and `preferentially` satisfy the `prefer`
  pairing, silently dropping real findings on the withholding boundaries
- match the contraction with an apostrophe class, not `.` (`don.t` matched
  `donut`)
- carry the fired marker from the sentence that triggered, via a new
  `Finding marker:` field, so a carved-out leading sentence no longer points
  review at the guardrail. Keeps one implementation of the sentence walk
- fail closed on a `..` path segment in the out-of-repo fence
- quote `branch:` when YAML would implicitly type it (`true`, `null`, `123`,
  a date)
- narrow `allowed-tools` from `Bash(git:*)` to the two subcommands actually
  used; the blanket grant authorized mutating git ops the read-only rule added
  in this same release disclaims

Deferred and filed as #3195: the shape is scoped to one physical line, so a
soft-wrapped sentence is judged in pieces. False-positive direction only, never
a silent withhold; recorded as a known limitation until that lands.

Refs #3195
Comment thread plugins/docs-hygiene/skills/audit-noise/scripts/lib/noise-shapes.sh Outdated
Comment thread plugins/docs-hygiene/skills/audit-noise/scripts/lib/noise-shapes.sh Outdated
@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 21s —— View job


Security Review

Task list

  • Gather context (PR diff, changed files, custom instructions)
  • Understand the request — instructed to invoke /review:security-review for this PR at head e0121e0
  • Run /review:security-review and follow its instructions exactly
  • Post findings to this comment

Scope checked: commits since the last security-reviewed head (a782771) — this push adds exactly one commit, e0121e0, which addresses reviewer findings from the prior round. No other files changed.

Previously-flagged IMPORTANT finding: resolved

The earlier round flagged plugins/docs-hygiene/skills/audit-noise/SKILL.md for granting unscoped "Bash(git:*)", which contradicted this PR's own "no mutating Bash op" read-only rule. This commit narrows the grant to exactly the two subcommands the script uses:

"Bash(git branch --show-current:*)", "Bash(git rev-parse --show-toplevel:*)"

Verified against actual usage in emit-findings.sh (git branch --show-current) and #L155 (git rev-parse --show-toplevel) — no mismatch, no remaining mutating-git surface in the allowed-tools grant. Resolved.

Other changes in this commit (non-security correctness fixes, reviewed for regressions)

  • Whole-word fencing on the carve-out markers (secret, prefer, etc.) and the apostrophe-class fix for don't/shouldn't — these tighten a documentation-linting regex, not a security boundary; reviewed and no injection/interpolation risk (matched text still only reaches printf '%s\n', never eval).
  • Out-of-repo path fence now fails closed on any .. path segment (emit-findings.sh#L308) — correctly closes the traversal case Codex flagged. The commit's own comment discloses the residual gap (an in-repo symlink pointing outside still resolves past a lexical prefix check, since awk has no portable canonicalizing syscall) rather than claiming full closure — acceptable given this operates on the invoking agent's own audited-file list, not untrusted external input, and the gap is now documented rather than silent.
  • branch: YAML scalar quoting extended to implicitly-typed values (true, null, 123, dates) — correctly prevents the relay's exact-match admission from silently mis-typing a real branch name.

Conclusion

No new security issues introduced by this push. The one prior security-relevant finding (over-broad allowed-tools git grant) is fixed and verified against actual call sites.
· Branch: claude/work-items-integration-o22y7w

Comment thread plugins/docs-hygiene/skills/audit-noise/scripts/lib/noise-shapes.sh
@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown

Claude has reviewed this PR 3 times. The lane skips further automatic reviews after 5; deleting this comment resets the count.

@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 2m 27s —— View job


Code Review

Task list

  • Gather context (PR diff, changed files, custom instructions)
  • Understand the request — instructed to invoke /review:code-review for this PR
  • Run /review:code-review and follow its instructions exactly
  • Post findings as inline PR comments + summary

Scope

This is the CODE-REVIEW lane (correctness/maintainability); security is scoped to the separate /review:security-review lane, which already ran on this PR. Reviewed the full diff origin/main...HEAD (15 files), with focus on the latest commit e0121e0 since it's the delta since the last review pass.

Summary: all 7 prior findings are genuinely fixed

Commit e0121e0 ("address review findings on the negation detector") responds to the 6 Codex findings + 1 Claude finding from the earlier review pass on bcb7734. I read each fix against the original defect rather than trusting the commit message:

  1. Whole-word marker fencing (plugins/docs-hygiene/skills/audit-noise/scripts/lib/noise-shapes.sh:449-460) — secret/prefer substrings now require (^|[^[:alnum:]]) ... ([^[:alnum:]]|$) boundaries, so secretary/preferentially no longer collide. New regression fixture (negation-substrings.md) in detect.test.sh covers exactly this.
  2. Apostrophe class, not .don${AUDIT_NOISE_APOS}t with AUDIT_NOISE_APOS="['’]" replaces the bare-dot wildcard that matched donut. Covered by a new negation-donut.md fixture.
  3. Marker attributiondetect.sh now emits a Finding marker: field carried from the sentence that actually triggered (AUDIT_NOISE_FIRED_MARKER, set inside audit_noise_line_has_negation_without_positive), and emit-findings.sh's fired_marker() prefers the supplied value over its whole-line fallback scan. Verified the global is set once per line before the per-shape print loop reads it (detect.sh:292-316), so no cross-shape clobbering.
  4. Path traversal fail-closedemit-findings.sh:306 adds abs ~ /(^|\/)\.\.(\/|$)/ before the lexical-prefix repo check, correctly declining any .. path segment (checked it doesn't false-positive on names like foo..bar). Documented residual: an in-repo symlink pointing outside still isn't caught (no portable canonicalizing syscall in awk) — that's accurately disclosed as a known limitation, not silently swept under the rug.
  5. YAML implicit-typed branch: quoting — new yaml_implicit_typed() covers bools/null/~, ints, floats, ISO dates, and 0x/0o/0b literals. Reasonable coverage of the common YAML 1.1 gotcha set.
  6. allowed-tools narrowedBash(git:*)Bash(git branch --show-current:*) + Bash(git rev-parse --show-toplevel:*), matching the only two git invocations in emit-findings.sh (lines 119, 155) and SKILL.md's own preview line. No longer conflicts with the "no mutating Bash op" hard rule added in the same PR.
  7. Soft-wrap sentence limitation — not silently dropped; documented as a known limitation in SKILL.md's Hard rules with the correct failure direction (false positive, never a silent withhold) and filed as fix(docs-hygiene): audit-noise's negation shape misreads sentences that markdown soft-wraps #3195.

Each fix has a corresponding new regression test in detect.test.sh / emit-findings.test.sh that reproduces the original defect and asserts the fix. I did not find any new correctness issues introduced by this fix commit, and the rest of the diff (crosswalk rows, changelogs, plugin version bump, README) is internally consistent (0.20.1 → 0.21.0, "eight" → "nine" shapes, crosswalk rows for both the emitting and non-emitting rule).

Verification note: I could not execute the shell test suites myself in this sandboxed environment (Bash requires interactive approval I don't have here), so the "All 115/23 checks passed" claims in the PR description are author-claimed and unverified by me — my confirmation above is from reading the fixture/assertion pairs added in the same commit, not from running them.

No new inline findings — nothing here would need a [Fix this →] link.

… abbreviation

Eighth review finding, and the most serious: the left-anchored splitter could
not skip a period not followed by whitespace, so `e.g.` made the match fail on
the first iteration and the entire line became one "sentence".

On `See e.g. the credential rotation policy. Never call the tool directly.` the
guardrail word in the first clause then suppressed the real prohibition in the
second — silent finding loss, which is the one outcome the carve-outs exist to
make impossible, reached through the splitter rather than through a marker.

Sentences are now peeled right-to-left with a greedy leading `.*`, splitting at
every terminator that IS followed by whitespace. An abbreviation merely
over-splits, and over-splitting only narrows the window a suppressing marker can
act from. The header comment claimed the opposite behaviour and was wrong.

Regression tests cover both directions: the abbreviation case now flags with the
later sentence's marker, and a guardrail genuinely in the same sentence still
carves out.

Copy link
Copy Markdown
Contributor Author

Review round addressed — 9fce71fc

Thanks both. Every finding was reproduced by running the detector before being fixed, not accepted on inspection — and two of them turned out to be the failure class this rule set is specifically built to prevent (silent withholding on a carve-out boundary), so they were worth the round.

Fixed

# Finding Note
1 secretary satisfied the secret guardrail; preferentially satisfied the prefer pairing Silent finding loss. Every marker is now fenced to a whole word; prefer inflections enumerated so the verb still pairs, vulnerab kept deliberately stemmed but bounded on the left
2 Sentence splitter collapsed the line on an embedded abbreviation Silent finding loss, and the most serious of the round. Confirmed exactly as traced: See e.g. the credential rotation policy. Never call the tool directly. split into one sentence, and the guardrail suppressed the prohibition. Now peeled right-to-left with a greedy leading .*, so an abbreviation over-splits instead — the fail-safe direction. The header comment claiming "over-split" was simply wrong and is corrected
3 don.t matched donut (raised independently by both reviewers) Apostrophe class, not .
4 Fired marker came from the first prohibition on the line detect.sh now carries a Finding marker: field, so there stays one implementation of the sentence walk rather than a mirror in the writer free to drift
5 Lexical repo fence admitted a ..-traversing path Fails closed on a .. segment. Residual recorded at the site rather than implied: an in-repo symlink pointing outward still resolves past a lexical test
6 branch: unquoted when YAML implicitly types it true / null / no / 123 / dates are quoted; ordinary names stay plain scalars
7 allowed-tools granted unscoped Bash(git:*) Narrowed to the two subcommands actually used — the blanket grant authorized mutating git ops the read-only rule added in this same diff disclaims

Each carries a regression test. The suites grew 115 → 125 and 23 → 32.

Deferred, with a filed issue

#3195negation is scoped to one physical line, so a sentence markdown soft-wraps is still judged in pieces. Distinct from finding 2: that one was a within-line splitter bug and is fixed; this is the cross-line case, whose fix changes detect.sh's iteration model and its interaction with every existing fence (frontmatter, code fences, section exemptions, opt-out markers). The error direction is a false positive, never a silent withhold, so it costs reviewer attention rather than coverage. Recorded as a known limitation in SKILL.md "Hard rules" until #3195 lands.

One note back

the two findings below are backed by careful manual regex-grammar tracing rather than an executed repro

Both traces were correct, including the subtle one — the splitter analysis was right about the mechanism (anchored match failing on the first iteration) and about the consequence. Worth recording, since the reviewer flagged its own confidence as reduced for lack of a repro.


Generated by Claude Code

@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 53s —— View job


Security Review

Task list

  • Gather context (PR diff since last reviewed head, changed files, custom instructions)
  • Understand the request — instructed to invoke /review:security-review for this PR at head 9fce71f
  • Run /review:security-review and follow its instructions exactly
  • Post findings to this comment

Scope checked: commits since the last security-reviewed head (e0121e0) — this push adds exactly one commit, 9fce71f, which fixes the sentence splitter's abbreviation-collapse bug flagged in review. Three files changed: noise-shapes.sh, detect.test.sh, CHANGELOG.md.

Skip gate: no security-relevant surface in this diff

audit_noise_split_sentences_into in noise-shapes.sh is rewritten to peel sentences right-to-left instead of left-anchored, fixing the silent-finding-loss bug where an embedded abbreviation (e.g. e.g.) made a left-anchored match fail on the first iteration and collapse the whole line into one sentence. This is pure Bash regex/array logic over already-read file content:

  • No eval, no command substitution built from matched text, no new Bash/Read/Write/Edit capability introduced.
  • Matched substrings only ever populate an output array consumed internally (_audit_noise_sentences_out); nothing reaches a shell command line, a file path, or a network call.
  • The detect.test.sh additions and CHANGELOG.md update are fixture/prose-only, no executable surface.
  • No allowed-tools / permissions / workflow changes in this commit — the prior round's Bash(git:*) over-broad-grant finding (already fixed in e0121e0) is untouched here.

No new security issues introduced by this push. Nothing to report.
· Branch: claude/work-items-integration-o22y7w

@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 3m 49s —— View job


Code review: docs-hygiene:audit-noise negation detector (0.21.0) — re-review at 9fce71f

  • Gather context (git show/git diff on commits since the last full code-review pass, prior review threads, CLAUDE.md)
  • Understand the request (/review:code-review, scoped to correctness/maintainability — security is the other lane's job)
  • Verify prior review findings were addressed at this new head
  • Review the newest commit (9fce71f, sentence-splitter fix) for correctness
  • Post findings

Scope: this lane last reviewed a7827716 (2 findings posted). Two commits landed since — e0121e0 (addresses 7 findings from both automated reviewers) and 9fce71f (fixes the 8th, most serious finding: the sentence splitter under-splitting around abbreviations). I re-verified each claimed fix against the current code rather than trusting the PR body's disposition table.

All previously-flagged issues verified fixed at this head:

Finding Verified at
don.t/shouldn.t unescaped . matching "donut" noise-shapes.sh:460-467AUDIT_NOISE_APOS="['’]" character class, NEG_DONUT regression test added
secretary/preferentially substring collisions on withholding boundaries noise-shapes.sh:466,474,485 — every alternative now fenced `(^
Fired marker taken from first prohibition on the line, not the triggering sentence detect.sh:302-308 emits Finding marker:; emit-findings.sh:320-334 prefers it, falling back to the old whole-line scan only for pre-existing detector output
..-traversing path admitted by the lexical repo fence emit-findings.sh:296-307 — fails closed on any .. segment; residual (in-repo symlink escaping outward) is disclosed in the same comment, not silently left
branch: unquoted when YAML implicitly types it (true/null/123/dates) emit-findings.sh:178-194yaml_implicit_typed() covers booleans/null, ints, floats, ISO dates, hex/octal/binary
Unscoped Bash(git:*) contradicting the read-only hard rule SKILL.md:6 — narrowed to git branch --show-current / git rev-parse --show-toplevel, matching the two call sites in emit-findings.sh
Sentence splitter under-splitting on a mid-line abbreviation (e.g. e.g.), letting an earlier guardrail silently suppress a later, unrelated prohibition noise-shapes.sh:416-446

On the splitter fix specifically (the newest, least-reviewed code): it now peels sentences right-to-left with a greedy ^(.*[.!?])[[:space:]]+(.*)$, collecting tail fragments and reversing them back into original order. I traced this by hand against several cases — the PR's own repro (See e.g. the credential rotation policy. Never call the tool directly. → 3 sentences, correctly isolating the real prohibition), the same-sentence guardrail case (must still carve out), a no-trailing-punctuation final sentence, and a sentence with an embedded abbreviation immediately after a real prohibition (Never do X, i.e. don't do Y.) — the last one over-splits into two fragments, but that's the documented, accepted tradeoff (over-splitting only narrows a suppressing marker's window; it cannot silently withhold, which is the property this whole shape is built to preserve). audit_noise_line_has_negation_without_positive still returns on the first offending sentence per line, so over-splitting can't cause a double-count either. Both new regression cases (NEG_ABBREV, NEG_SAME) in detect.test.sh exercise exactly the two directions that matter.

I did not execute the test suites myself — Bash execution in this environment requires interactive approval that isn't available here, so detect.test.sh (122 checks) and emit-findings.test.sh (32 checks) results are author-reported and traced by hand rather than independently re-run. Everything I could statically verify (regex behavior, control flow, the crosswalk/allowed-tools/YAML-quoting fixes) checks out.

No new findings. Nothing further to flag on e0121e0 or 9fce71f beyond what the two automated reviewers already surfaced and what the author already fixed. The deferred soft-wrapped-sentence limitation (#3195) is a reasonable scope cut — false-positive direction only, documented in SKILL.md, not a regression risk for this PR.
· Branch: claude/work-items-integration-o22y7w

@kyle-sexton
kyle-sexton merged commit 702967d into main Aug 23, 2026
52 checks passed
@kyle-sexton
kyle-sexton deleted the claude/work-items-integration-o22y7w branch August 23, 2026 16:39
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.
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