fix(docs-hygiene): gate the porcelain rename split and unescape \\ (0.19.1) - #3171
Conversation
….18.4) Two silent-false-negative defects in `audit-noise`'s `git status --porcelain` parse: a file that should have been audited simply dropped out of the target list. The rename split fired on any record whose path contained " -> ", not only on a rename, so a file named `notes -> draft.md` was reduced to `draft.md` — a name that resolves to nothing. It is now gated on the status letter in either column ([RC]), which is narrower and also catches a rename staged in either column. The unquote step undid \" but not \\. Git C-quotes a path for an embedded backslash too, so `back\-slash.md` stayed escaped and resolved to nothing. Both escapes are now undone, \" before \\. Regression cases cover a path containing a literal " -> " and a path containing a backslash; a plain path passes either implementation, so neither is redundant. A genuine rename case shows the new gate does not cost the old -> new handling it narrows. Verified by reverting detect.sh to the old parse: exactly those two cases fail, and the rename guards pass on both. The suite also picks up the `unset GIT_DIR GIT_WORK_TREE GIT_CONFIG` isolation line that 0.18.3's sweep missed here. Known residual, recorded at the parse site: git's octal escapes (\NNN) are still not decoded. Converging on `git status --porcelain -z` would close the class outright rather than extending the string parse a third time. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXYVz59XVYS7NEjMB5UyHq
Resolves two conflicts in docs-hygiene, both from #3142 bumping the plugin to 0.19.0 while this branch carried 0.18.4: - plugin.json: version becomes 0.19.1, this branch's fix on top of 0.19.0. - CHANGELOG.md: this branch's entry re-headed [0.19.1] and ordered above [0.19.0]; both entries kept in full. #3140 also landed on main during this branch's life, so the sibling `code-tidying/audit-comment-residue` parse it ports from is now present. The gate and the two-step unescape here are identical to it, so the two porcelain parsers converge rather than failing in opposite directions on renames. Noted in the changelog entry. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXYVz59XVYS7NEjMB5UyHq
|
Claude finished @kyle-sexton's task in 58s —— View job Security review
No security issues found. This PR touches
No GitHub Actions workflows, auth logic, credential handling, or network-facing input parsing are touched by this PR — skip-gate condition 2 (no security-relevant surface) applies. |
|
Last security-reviewed head: |
|
Claude finished @kyle-sexton's task in 5m 1s —— View job Code review complete
No blocking findings. What I checked
One non-blocking note: the PR body's "Related" section says |
|
Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count. |
|
Thanks — no blocking findings on either lane, and the hand-trace of the three porcelain cases matches what the suite asserts. Two points in the review read a stale snapshot of this PR and are already resolved on the current head (
On the execution caveat: correct that you couldn't run the suite, and treating "66 checks pass" as author-claimed is the right posture. For the record, the load-bearing evidence isn't the pass count — it's the discriminator: reverting only Generated by Claude Code |
|
No description provided. |
…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
…#3151) No related issue: follow-on to the limitation #3140 recorded at its own parse site. #3126 is already closed by that PR, and no issue tracks the residual escape-decode gap. ## Summary #3140 fixed the #3126 false negative by slicing the v1 porcelain record, and recorded the remaining limitation at the parse site: > Git's octal escapes for control and non-ASCII bytes are still not decoded by either, so such a path continues to miss. This PR closes that gap, in both parsers that carry it. **It is a follow-on to #3140, not a competing fix.** | Path | v1 renders as | v1 slice yields | |---|---|---| | `café.py` | `?? "caf\303\251.py"` | literal escape sequence — names nothing | | `tab<TAB>here.py` | `?? "tab\there.py"` | literal `\t` — names nothing | Both confirmed against git 2.55. The file is silently dropped, so the run reports a clean tree — the same false-negative class #3126 described. ## Fix Both porcelain parsers in this skill move to the NUL-delimited `--porcelain -z` form, which git documents as performing no quoting or backslash-escaping, so there is nothing left to decode. - **`detect.sh`** — the audit's target router. - **`SKILL.md`'s `Uncommitted code files:` preview** — the pre-computed context the model reads. #3140 deliberately brought this to parity and added a test that extracts and runs it, so leaving it behind would have reopened the divergence that test exists to prevent: the audit would find `café.py` while the preview listed nothing. Under `-z` a rename emits the **new** path first and the original as a following record — the reverse of v1's display order — and that second record is consumed and dropped. Rename handling is therefore structural, with no arrow matching, which also resolves the intent-to-add rename #3140 gated on the worktree status letter (` R dst\0src\0` → `dst`, verified against git 2.55). ## Verification - **53/53 pass**, including every one of #3140's checks. - Nothing vacuous, checked by reverting each piece: - `main`'s `detect.sh` → fails 2 of case 11's 5 assertions (`non-ASCII`, `tab-bearing`). The arrow assertion passes on `main` — #3140's `[RC]` gating already handles it, **not** claimed here. - `main`'s `SKILL.md` → fails 3 assertions, including #3140's own `SKILL.md preview covers every file detect.sh audits`. - The parity harness was itself masking the defect and is fixed here. It extracted only the awk program and hardcoded the porcelain invocation; feeding `-z` to a v1 program makes the v1 program look correct, because `-z` output carries no quoting for it to fail at decoding. It now reads the invocation from `SKILL.md` too, and `REPO13` gains a non-ASCII fixture. - `mawk 1.3.4` (the runner's default `awk`) confirmed to support `RS = "\0"` before relying on it. - Local gates green: typos, shell-portability (scripts and `SKILL.md`), skill-portability, skill-precompute-compose, changed-skills, leaf-names, count-claims, fixture-git-isolation, orphaned-fixtures, manifest-duplicate-keys, `bash -n`, changelog-parity in all three modes. - `ci-status` (the required aggregate check) reported **success** on head `65a7b2c`; head `d7b2b67` adds only a `main` merge plus the version rebase. ## Current state — read this before continuing Head is **`d7b2b67`**, version **`0.14.2`**. `main` published its own `0.14.0` (#3156) and `0.14.1` mid-review, so the version was rebased twice; the changelog keeps each published entry intact with this PR's entry above them. **One blocker remains, and it is not about the code.** The branch's commits are signed with a key that is not registered on the committing GitHub account, so they report `verified=false, reason=unknown_key`. That trips two ruleset rules: - `required_signatures` - `require_extra_approval_for_unattributed_changes` Everything else is satisfied: all four required checks green, all review threads resolved, no merge conflicts, and `required_approving_review_count` is **0** — so once the signature question is settled this PR needs no human approval. Two ways to settle it: 1. **Register the signing key** on the account (Settings → SSH and GPG keys → New SSH key, type *Signing Key*). No push needed; the existing commits become verified, and the commit history is preserved. 2. **Rebuild the branch through the GitHub API.** Commits created via the API are signed by GitHub and verify automatically — that is why every commit on `main` shows `committer=noreply@github.com, verified=true`. Since the repo is squash-merge only, collapsing this branch's commits loses nothing that would survive the merge anyway. ## Related - Refs #3140 — landed the v1 slice this builds on; its `0.13.3` entry and full test suite are preserved here. - Refs #3156 — bumped `code-tidying` to `0.14.0` mid-review. - Refs #3126 — the original bug, closed by #3140. Not reopened here. - Refs #3164 — `audit-noise` carries the same defect class. #3171 fixed its rename-split half; the octal-escape half is still open and tracked there. **Not fixed here** — separate plugin, separate version bump.
Closes #3143
Summary
audit-noise'sgit status --porcelainparse dropped files whose paths gittreats specially. Both defects are the silent-false-negative class: the file
did not error, it simply disappeared from the target list, so a run over a tree
containing one reported clean.
Fix
Rename split is gated on the status letter, not the path text. It previously
fired on any record whose path contained
" -> ", so a file literally namednotes -> draft.mdwas reduced todraft.md— a name that resolves to nothing.It now gates on
[RC]in either column, which is narrower and still catches arename recorded in the index or the worktree.
\\is now unescaped as well as\". Git C-quotes a path for an embeddedbackslash too, so
back\-slash.mdstayed escaped and resolved to nothing. Bothescapes are undone,
\"before\\.#3140 landed on
mainwhile this branch was in flight. The gate and thetwo-step unescape here are identical to the ones it gave
code-tidying/audit-comment-residue, so the two porcelain parsers now convergerather than failing in opposite directions on renames — which is what #3143
asked for.
audit-noise'sSKILL.mddoes not mirror this parse — it uses a plaingrep '\.md$'pipeline — so the conditional "mirrored parser" half of the issuedoes not apply here.
docs-hygiene0.19.1.Verification
detect.test.sh: all 66 checks pass, including 5 new cases.detect.shto the old parse failsexactly the two new defect cases (
' -> 'path, backslash path) and nothingelse — so neither case passes vacuously. The rename cases pass under both
implementations by design; they are regression guards showing the new gate
does not cost the
old -> newhandling it narrows.docs-hygienesuites pass, re-run after mergingmain; nothing outsidethis skill references
audit-noise/scripts/detect.sh.shellcheck,shfmt -d,markdownlint-cli2,typos,editorconfig-checker: clean.check-fixture-git-isolation.sh --check,check-plugin-manifest-presence.sh,check-changelog-parity.sh --check/--check-order/--check-bump/--check-preservedagainstorigin/main: pass.The suite also picks up the
unset GIT_DIR GIT_WORK_TREE GIT_CONFIGisolationline that 0.18.3's sweep missed on this file.
Known residual, recorded at the parse site rather than left implicit: git's
octal escapes (
\NNN) for control and non-ASCII bytes are still not decoded, sothose paths continue to miss. Converging on
git status --porcelain -zwouldclose the class outright rather than extending the string parse a third time.
Related
code-tidying/audit-comment-residueparse fix thisissue was deferred out of; now landed, and this change converges with it.
audit-comment-residuedefect report.docs-hygieneto 0.19.0 mid-flight; merged in, so thisPR ships 0.19.1.