From 15aee317818f09f0f8351788eabac481a02b9253 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Mon, 20 Jul 2026 04:55:24 -0400 Subject: [PATCH] fix(repo-hygiene): emit RESTORED>0 warning on git-tree-reset exit-7 failure path (#605) The exit-7 clean-failure path (git clean -fdx failed for a non-locked-file cause) emitted `RestoredTracked: N` but skipped the human-visible `WARNING: restored N tracked file(s)` message the success path prints when RESTORED>0. An operator on the failure path could therefore miss that the reparse-point restore guard fired (data-loss recovery), even though the machine-readable line was present. Mirror the success path's exact warning (same wording, same RESTORED>0 condition) onto the failure path so both paths surface restore-guard activity identically. Add a test that drives the exit-7 path with a partial clean that deletes a tracked file before failing (RESTORED=1) and asserts the warning now appears; verified it fails without the fix. Bump repo-hygiene 0.4.0 -> 0.4.1. Co-Authored-By: Claude Sonnet 5 --- .../repo-hygiene/.claude-plugin/plugin.json | 2 +- plugins/repo-hygiene/CHANGELOG.md | 15 +++++++ .../skills/clean/scripts/git-tree-reset.sh | 8 ++++ .../clean/scripts/git-tree-reset.test.sh | 41 +++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/plugins/repo-hygiene/.claude-plugin/plugin.json b/plugins/repo-hygiene/.claude-plugin/plugin.json index 74fcd7e3..2ead9dbf 100644 --- a/plugins/repo-hygiene/.claude-plugin/plugin.json +++ b/plugins/repo-hygiene/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "repo-hygiene", - "version": "0.4.0", + "version": "0.4.1", "description": "Repo hygiene action-router: /repo-hygiene:clean sweeps reclaimable caches, build artifacts, and stale git metadata, and can realign the working tree to a fresh-pull state — dry-run-first, with destructive tiers gated behind explicit confirmation and a session-scoped destructive-command guard. Ecosystem targets are detected at runtime; secrets, runtime dependencies, and skill data are preserved by default.", "author": { "name": "Melodic Software", diff --git a/plugins/repo-hygiene/CHANGELOG.md b/plugins/repo-hygiene/CHANGELOG.md index ddc01705..5638985d 100644 --- a/plugins/repo-hygiene/CHANGELOG.md +++ b/plugins/repo-hygiene/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to the `repo-hygiene` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.4.1] + +### Fixed + +- **`git-tree-reset` — exit-7 clean-failure path now emits the restore-guard + warning identically to the success path.** When `git clean -fdx` fails for a + non-locked-file cause (exit 7) after the restore guard recovered one or more + tracked files deleted via reparse-point traversal (`RestoredTracked: N`, N>0), + the path now prints the same `WARNING: restored N tracked file(s)` message the + success path already emits under that condition. Previously the warning was + emitted only on the success path, so an operator hitting the failure path saw + the machine-readable `RestoredTracked: N` line but not the human-visible signal + that data-loss recovery fired — parity between both paths for this specific + signal. (#605) + ## [0.4.0] ### Added diff --git a/plugins/repo-hygiene/skills/clean/scripts/git-tree-reset.sh b/plugins/repo-hygiene/skills/clean/scripts/git-tree-reset.sh index b8c7b8a5..f1c941f6 100755 --- a/plugins/repo-hygiene/skills/clean/scripts/git-tree-reset.sh +++ b/plugins/repo-hygiene/skills/clean/scripts/git-tree-reset.sh @@ -253,6 +253,14 @@ if [[ "$CLEAN_RC" -ne 0 && "${UNREMOVABLE:-0}" -eq 0 ]]; then printf 'AppliedClean: failed\n' printf 'RestoredTracked: %s\n' "${RESTORED:-0}" printf 'Unremovable: %s\n' "0" + # Surface restore-guard activity identically to the success path: a clean that + # errored mid-run may still have deleted tracked files (reparse-point traversal) + # before failing, and the machine-readable RestoredTracked line alone can be + # missed. Emit the same human-visible warning the success path prints so an + # operator is not left unaware that data-loss recovery fired on the failure path. + if [[ "${RESTORED:-0}" -gt 0 ]]; then + printf 'WARNING: restored %s tracked file(s) deleted via reparse-point traversal (junction/symlink into tracked dir).\n' "$RESTORED" >&2 + fi exit 7 fi diff --git a/plugins/repo-hygiene/skills/clean/scripts/git-tree-reset.test.sh b/plugins/repo-hygiene/skills/clean/scripts/git-tree-reset.test.sh index 9500b62e..44b42415 100755 --- a/plugins/repo-hygiene/skills/clean/scripts/git-tree-reset.test.sh +++ b/plugins/repo-hygiene/skills/clean/scripts/git-tree-reset.test.sh @@ -237,6 +237,47 @@ assert_contains "clean failure emits RestoredTracked label" "$out" "RestoredTrac assert_contains "clean failure emits Unremovable label (uniform apply-path contract)" "$out" "Unremovable: 0" assert_file_exists "clean failure leaves untracked intact (clean did not silently succeed)" "$R4/scratch.txt" +# --- 12. clean failure with restore-guard activity emits the RESTORED>0 warning --- +# Parity fix (#605): on the exit-7 failure path, when the restore guard recovers a +# tracked file (a clean that deleted tracked files via reparse-point traversal +# before erroring), the human-visible `WARNING: restored N tracked file(s)` message +# the success path prints must also appear — not just the machine-readable +# RestoredTracked: N line. Simulate a partial clean via a shim that deletes a +# tracked file and THEN exits non-zero, so `git ls-files --deleted` reports it and +# the restore guard recovers it (RESTORED>0) on the failure path. +R5="$TEST_TMPDIR/repo-clean-fail-restore" +git init "$R5" >/dev/null 2>&1 +git -C "$R5" config user.email "t@example.com" +git -C "$R5" config user.name "Test" +echo tracked >"$R5/tracked.txt" +git -C "$R5" add -A +git -C "$R5" commit -m "init" >/dev/null +git -C "$R5" branch -M main +git -C "$R5" checkout -b feat/clean-fail-restore >/dev/null 2>&1 +git -C "$R5" branch -u main >/dev/null 2>&1 + +RESTORE_SHIM="$TEST_TMPDIR/git-clean-restore-shim" +mkdir -p "$RESTORE_SHIM" +cat >"$RESTORE_SHIM/git" <&2 + exit 1 +fi +exec "$REAL_GIT" "\$@" +SHIMEOF +chmod +x "$RESTORE_SHIM/git" + +rc=0 +out="$(PATH="$RESTORE_SHIM:$PATH" bash -c "cd '$R5' && bash '$RESET' --apply" 2>&1)" || rc=$? +assert_exit "clean failure with restore exits 7" 7 "$rc" +assert_contains "clean failure emits AppliedClean: failed" "$out" "AppliedClean: failed" +assert_contains "clean failure reports a positive RestoredTracked count" "$out" "RestoredTracked: 1" +assert_contains "clean failure path emits the RESTORED>0 warning (parity with success path)" "$out" "WARNING: restored 1 tracked file(s) deleted via reparse-point traversal" +assert_file_exists "restore guard recovered the tracked file on the failure path" "$R5/tracked.txt" + if [[ $FAILED -ne 0 ]]; then echo "FAILED: $FAILED test(s)" exit 1