fix(docs-hygiene): gate audit-noise's rename split and unescape backslashes (0.18.4) - #3168
fix(docs-hygiene): gate audit-noise's rename split and unescape backslashes (0.18.4)#3168claude[bot] wants to merge 2 commits into
Conversation
…lashes (0.18.4) Closes #3143 Two defects in `audit-noise`'s `git status --porcelain` parse made markdown files vanish from a no-argument run, surfacing as a reassuring `files=0` rather than as an error. 1. The rename split fired on any record whose path contained `" -> "`, not only on a rename, so a file literally named `notes -> draft.md` was reduced to `draft.md` — a name matching nothing on disk. The split is now gated on an `R`/`C` status letter in either porcelain column. Both columns matter: a rename staged as intent-to-add (`mv old new && git add -N new`) records in the worktree column, so gating on the index column alone would miss it. 2. C-quoted paths unescaped `\"` but not `\\`, so a path containing a backslash stayed escaped and resolved to nothing. Both forms are now unescaped, `\"` before `\\` — order matters, or the backslash pass re-creates a quote the quote pass has already consumed. Also fixes the same defect class in SKILL.md's `Uncommitted .md files:` preview line, which shares the class rather than the code: it filtered with `grep '\.md$'`, and a C-quoted path ends with the closing quote, so every spaced, arrowed, backslashed or quoted markdown file was dropped from the preview. Now `grep -E '\.md"?$'`. Six regression cases added, each verified to fail against the unfixed surface and pass against the fixed one (7 failures before, 73/73 after). The suite builds its first git fixtures, so it also picks up the #2872 git-environment isolation guard. Case F extracts the grep from SKILL.md and executes it, so the two surfaces cannot drift apart silently. Neither surface decodes git's octal escapes for control and non-ASCII bytes; that limitation is recorded at both sites. Converging on `--porcelain -z` would close the class outright and is the better long-term fix, not taken here because it changes rename records from `old -> new` into two separate fields. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ec3EJLqCD2kjdzagyryg4
Resolves the docs-hygiene version and CHANGELOG conflicts against 0.19.0, which landed on main after this branch was cut. The fix rebases onto it: - plugin.json: 0.18.4 -> 0.19.1 (main's 0.19.0 is the new base). - CHANGELOG.md: the #3143 entry becomes [0.19.1] and sits above [0.19.0]; both entries kept in full. detect.sh, detect.test.sh and SKILL.md took no conflicting change from main — the audit-noise fix and its six regression cases survive intact. Re-verified against the merged tree: 73/73 tests, shellcheck, shfmt, markdownlint, check-skill PASS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ec3EJLqCD2kjdzagyryg4
|
Claude finished @kyle-sexton's task in 1m 5s —— View job Security review complete ✅
No security issues found. This PR touches only the Reviewed specifically for injection risk in the changed
No CRITICAL, IMPORTANT, or SUGGESTION findings to report. |
|
Last security-reviewed head: |
|
Claude finished @kyle-sexton's task in 6m 43s —— View job Claude finished the code review
SummaryReviewed the porcelain-parse fix in The core fixes are correct. I cross-checked the gated rename-split condition ( One finding posted inline (detect.sh:142): the claim that the Other things checked, no issues found:
|
| local_path="${local_path#\"}" | ||
| local_path="${local_path%\"}" | ||
| local_path="${local_path//\\\"/\"}" | ||
| local_path="${local_path//\\\\/\\}" |
There was a problem hiding this comment.
Correctness of the stated rationale (not the code): the "order matters" claim for this two-pass unescape appears to be false.
The comment (L135-137), the PR description, and detect.test.sh case E (L520-535) all assert that \" must be unescaped before \\, or "the backslash pass re-creates a quote the quote pass has already consumed."
Working through ${var//pattern/repl}'s non-overlapping left-to-right semantics on git's C-quote alphabet (only two escape tokens exist: \\→\ and \"→", both 2 chars, distinguished by their second character), a straddle position formed from the tail of one token and the head of the next can never spuriously equal either target pattern — the second character of a straddle is always the fixed head-char (\), which only matches the \\ pattern's second slot, never \"'s. That holds for every token-type combination, so scanning for either pattern always resolves at true token boundaries regardless of which pass runs first.
Concretely, for case E's fixture (both\".md, encoded as \\\"= 4 chars: \,\,\,"):
- quote-first then backslash-first:
\\\"→ (quote pass)\\"→ (backslash pass)\"✓ - backslash-first then quote-first:
\\\"→ (backslash pass)\\"→ (quote pass)\"✓
Both orders land on the same correct 2-char result. So case E doesn't actually distinguish the orders — swapping the two ${local_path//...} lines would still pass it. (I derived this by hand; I wasn't able to execute a script in this sandbox to confirm empirically, so treat this as worth a 30-second sanity check by literally swapping L141/L142 and re-running detect.test.sh.)
This doesn't make the shipped code wrong — the order chosen is fine — but the comment and the test's stated purpose assert an invariant that isn't real, which could mislead a future refactor into thinking the order is load-bearing. Note the ported-from sibling (code-tidying/audit-comment-residue/scripts/detect.sh:118-124) makes no such order claim, which lines up with this.
|
Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count. |
|
No description provided. |
|
Closing as superseded — but the half of this PR that #3171 declined was right, and it is being carried forward rather than dropped. What happened. #3171 merged as 0.19.1 and landed the What was not redundant. #3171 stated that the mirrored-parser half of #3143 "does not apply here", because Reproduced against Carried forward in #3187, which takes this PR's one-line Closing here rather than rebasing because most of this diff would now conflict with the landed 0.19.1 work for no gain. Generated by Claude Code |
…paths (0.20.1) (#3187) 0.19.1 (#3171) fixed the porcelain parse in audit-noise's detect.sh and left SKILL.md's `Uncommitted .md files:` pre-computed-context line untouched. That line previews the same discovery with a grep rather than with the parse, so it shared the defect class without sharing the code, and survived the fix that removed the class everywhere else. Git C-quotes any path it treats specially, and a quoted porcelain record ends with the closing quote, not `.md`. `grep '\.md$'` therefore matched nothing for `my notes.md`, `notes -> draft.md`, or `back\-slash.md`, and the preview reported them absent with no signal — the same silent false negative, reaching the model one surface earlier. The regression test extracts the grep out of SKILL.md and executes it rather than restating it, since a restatement keeps passing while the real line rots. Carries forward the SKILL.md half of #3168, whose detect.sh half landed via #3171. Refs #3143
Closes #3143
Summary
audit-noise'sgit status --porcelainparse had two defects that made markdown files disappear from a no-argument run. Both failed silently — the file dropped out of the audit and the run reported a reassuringfiles=0rather than an error, the same false-negative class as #3126 in the siblingcode-tidying/audit-comment-residue.Fix
1. Ungated rename split (
scripts/detect.sh). The split fired on any record whose path contained" -> ", not only on a rename, so a file literally namednotes -> draft.mdwas reduced todraft.md— a name matching nothing on disk.Gating on the status letter is both narrower and complete. Both columns matter: X is the index status and Y the worktree status, and a rename staged only as intent-to-add lands in Y (
mv old new && git add -N newemits" R old -> new"), so gating on X alone would leave that record unsplit and unresolvable.2.
\\not unescaped (scripts/detect.sh).\"→"was handled;\\→\was not, so a path such asback\-slash.mdstayed escaped and resolved to nothing.local_path="${local_path//\\\"/\"}" + local_path="${local_path//\\\\/\\}"Order matters —
\"before\\, or the backslash pass re-creates a quote the quote pass has already consumed. Case E covers the interleaved form.3. Same defect class in
SKILL.md. TheUncommitted .md files:preview line shares the class rather than the code — it filters with a grep, not withdetect.sh's parse. A C-quoted path ends with the closing quote, not.md, so every spaced, arrowed, backslashed or quoted markdown file was dropped from the preview:Note the two parsers previously failed in opposite directions on renames —
audit-noiseover-split (ungated),audit-comment-residuepre-#3140 under-split (index column only) — so this is a port of the gated-on-both-columns form, not a copy of either.Verification
Six regression cases added to
scripts/detect.test.sh, each built as its own git fixture so a correctly-parsed file cannot keepfiles=above zero and mask another arm's disappearance." -> "survives intact\"+\\proves the unescape order.mdpath (extracted and executed, so the surfaces cannot drift)Each case was verified to fail against the unfixed surface and pass against the fixed one — not asserted by inspection:
Gates run against the source checkout:
The one
check-skillwarning (no Gotchas surface) is pre-existing and untouched by this PR.The suite builds its first git fixtures, so it also picks up the #2872 git-environment isolation guard (
unset GIT_DIR GIT_WORK_TREE GIT_CONFIG).Known residual, recorded at both sites rather than left implicit: neither surface decodes git's octal escapes for control and non-ASCII bytes, so those paths still miss. Converging both skills on
git status --porcelain -zwould close the whole class outright — NUL-delimited, unquoted paths — at the cost of handling renames as two separate fields instead ofold -> new. That is the better long-term fix and is deliberately not taken here.docs-hygiene0.18.3 → 0.18.4, with the matching CHANGELOG entry.Related
Refs #3126— the sibling defect incode-tidying/audit-comment-residue, whose fix this ports.Refs #3140— the open PR carrying that fix; the gated-on-both-columns form comes from its post-fix parser. This PR does not depend on it merging.Refs #2872— the fixture git-environment isolation guard the new git fixtures rely on.🤖 Generated with Claude Code
https://claude.ai/code/session_018ec3EJLqCD2kjdzagyryg4
Generated by Claude Code