fix(disk-hygiene): unblock guarded engine lane and bounded large-root audits - #286
Conversation
… audits Fixes from a live Windows user-profile audit where the sanctioned lane was unusable end to end: - Add --data-root to scan/preview/apply. The runtime exports CLAUDE_PLUGIN_DATA to hook processes but not shell tool subprocesses, so the engine could never locate its generated-state root through the guarded Bash lane. The guard now validates the flag against its own hook-process value, discloses the authorized root in denial guidance, and fails closed without hook authority. - Add --max-depth bounded scans: truncated directories are recorded, reported as coverage gaps, and blocked from plans via a truncated-not-inventoried preview blocker. Profile-root audits previously died at the 250k entry cap before inventorying a single loose file. - Extend the guard to the PowerShell tool: engine invocations are hard-denied (Bash stays the only engine lane); known deletion spellings and .NET Delete calls surface a final human permission prompt instead of running silently. - Compute the os_autoclean advisory before the walk and include it in scan failure payloads; add a stderr progress heartbeat every 25k entries. - Document the unsupported-platform manual handoff (exact-list approval, per-path revalidation, reversible deletion preferred) and the 8.3 short-name tilde gotcha; add tmp_* and scratch* baseline hints. - Bump plugin to 0.3.0 with a CHANGELOG; add engine and guard tests (61 pass). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 5m 35s —— View job PR Review — fix(disk-hygiene): unblock guarded engine lane and bounded large-root auditsTasks
SummaryThe PR addresses a real-world audit failure cleanly. The Findings1. Entry-cap check is off-by-one — entry #250,001 is added before the error firesif len(entries) > MAX_SNAPSHOT_ENTRIES:
raise HygieneError(...)
2.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 57dd369840
ℹ️ 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".
Resolve disk-hygiene conflicts: combine the userConfig kill-switch migration (main) with the --data-root/--max-depth/PowerShell-lane additions (branch) in CHANGELOG.md and skills/clean/SKILL.md, and complete main's HOOK_DISK_HYGIENE_ENABLED -> CLAUDE_PLUGIN_OPTION_DISK_HYGIENE_ENABLED rename onto the two branch-added test_hygiene.py references.
|
Claude finished @kyle-sexton's task in 3m 55s —— View job Tasks
PR Review — fix(disk-hygiene): unblock guarded engine lane and bounded large-root auditsNote: The triggering sync is a merge of SummaryThe core design (guarded engine lane, Findings1. Truncated candidate does not short-circuit before
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1675a9d502
ℹ️ 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".
…ery to the scan's --max-depth Addresses bot-review findings on the truncated-candidate and --max-depth work in this PR: - preview() short-circuits current_descendants() for a candidate already carrying the truncated-not-inventoried blocker instead of still walking its live subtree without limit -- the exact unbounded traversal --max-depth exists to avoid, on the same profile/root audits this PR targets. - annotate_tracked() scopes its `git ls-files` call with a target-relative base pathspec plus a `:(exclude)` entry per truncated directory, instead of always listing the whole repository, so a bounded scan cannot still trigger an unbounded VCS query and capture its entire output. - The snapshot entry-cap check is now inclusive (`>=`), so at most MAX_SNAPSHOT_ENTRIES land in the snapshot before the cap error fires. - current_descendants()'s HygieneError (raised on a mount-state read failure) is now caught alongside PermissionError/OSError so one candidate's transient failure blocks just that candidate instead of aborting the whole preview. - main() resets the DATA_ROOT_OVERRIDE module global in a finally block so it can never leak past its own invocation. Adds regression coverage: a truncated candidate no longer triggers current_descendants(), annotate_tracked()'s git invocation is scoped to inventoried paths, and the apply command accepts an authorized --data-root the same way preview already did. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 26s —— View job Tasks
PR Review — fix(disk-hygiene): unblock guarded engine lane and bounded large-root auditsReviewed commit: This is a follow-up to the two prior review rounds. The new commit addresses every finding that was flagged across both reviews plus both Codex P2 items. Detailed verification below. All prior findings resolvedFinding 1 (was blocking): Truncated candidate no longer calls
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e933fc3795
ℹ️ 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".
…ncated preview candidate
Follow-up to the prior commit's current_descendants() short-circuit:
two more per-candidate checks in preview() ran unconditionally and were
just as unbounded for a candidate already carrying the
truncated-not-inventoried blocker --
- tracked_blocker() calls discover_current_repositories(), whose visit()
helper does an unbounded recursive os.scandir() walk of the candidate's
live subtree looking for VCS markers.
- candidate_handle_state() delegates to handle_state(), which on POSIX
runs `lsof -Fn +D <path>`, itself unbounded for a directory.
Both are now skipped once truncated-not-inventoried is already present,
with handle_state/handle_detail reporting ("unverified",
"truncated-not-inventoried") instead of silently defaulting to "clear" --
honest that the check didn't run, not a claim that it passed. The
candidate is already hard-blocked from planning either way, so nothing
is given up.
Extends test_preview_skips_live_descendant_walk_for_truncated_candidate
(renamed test_preview_skips_live_recursive_checks_for_truncated_candidate)
to assert tracked_blocker and candidate_handle_state are never invoked
for the truncated candidate, and that the reported handle state reflects
the skip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 44s —— View job Tasks
PR Review — fix(disk-hygiene): unblock guarded engine lane and bounded large-root auditsReviewed commit: This commit addresses the remaining Codex P2 item from the third review round. All prior findings remain resolved. Full verification below. Remaining Codex P2 finding resolved
|
| Finding | Commit | Status |
|---|---|---|
Truncated candidate calls current_descendants (was blocking) |
e933fc3 |
✅ resolved |
Truncated candidate calls tracked_blocker / candidate_handle_state (Codex P2) |
4ecf1e2 |
✅ resolved |
Entry-cap off-by-one (> → >=) |
e933fc3 |
✅ resolved |
Missing guard test for apply --data-root |
e933fc3 |
✅ resolved |
HygieneError from current_descendants escapes preview() |
e933fc3 |
✅ resolved |
DATA_ROOT_OVERRIDE global not reset on exit |
e933fc3 |
✅ resolved |
Bottom line
All flagged findings across three review rounds are resolved. The truncated-candidate preview path is now fully short-circuited at every live-I/O point (current_descendants, tracked_blocker/discover_current_repositories, candidate_handle_state/lsof +D). The implementation is correct and the test coverage is well-targeted. Ready to merge.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4ecf1e2b26
ℹ️ 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".
…ncated too
scan_tree()'s visit() stopped recursing into three kinds of directory
without visiting their children: a VCS boundary (.git and friends), a
hard-protected path (e.g. a Windows shell folder like Documents), and a
--max-depth cutoff -- but only the third case ever appended to
truncated_paths. A candidate sitting on one of the first two was
therefore invisible to preview()'s truncated-not-inventoried blocker and
its live-check short-circuits added in the prior two commits, so a plan
naming a large protected folder or VCS-boundary directory could still
walk it live and unbounded through current_descendants,
tracked_blocker, and candidate_handle_state -- the exact class of
problem those short-circuits exist to prevent.
All three stop-recursing branches now append to truncated_paths, since
all three share the same underlying fact: this directory's descendants
were not inventoried. SKILL.md's description of truncated_paths is
widened to match ("cut off by --max-depth, a protected root, or a VCS
boundary") -- the field's meaning was already broader than the doc
said; the doc already separately called unvisited protected roots a
coverage gap, just not tied to this machine-readable field.
New tests: a protected folder's children land in truncated_paths from a
plain scan_tree() call (no --max-depth involved), and a preview()
candidate on that protected folder skips current_descendants,
tracked_blocker, and candidate_handle_state exactly like a --max-depth
truncated candidate does. Extracted the three-function refuse-if-called
mock helper (previously inline in one test) to a shared refuse_call()
so both truncation sources share one assertion helper instead of two
copies.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 32s —— View job Tasks
PR Review — fix(disk-hygiene): unblock guarded engine lane and bounded large-root auditsReviewed commit: This commit addresses the final Codex P2 item from the fourth review round. All prior findings remain resolved. Detailed verification below. Final Codex P2 finding resolvedVCS-boundary and protected-directory entries now land in
|
| Finding | Commit | Status |
|---|---|---|
VCS-boundary / protected dirs not in truncated_paths (Codex P2, round 4) |
fc6f46c |
✅ resolved |
Truncated candidate calls current_descendants (was blocking, round 2) |
e933fc3 |
✅ resolved |
Truncated candidate calls tracked_blocker / candidate_handle_state (Codex P2, round 3) |
4ecf1e2 |
✅ resolved |
annotate_tracked unbounded on bounded scan (Codex P2, round 2) |
e933fc3 |
✅ resolved |
Entry-cap off-by-one (> → >=) |
e933fc3 |
✅ resolved |
Missing guard test for apply --data-root |
e933fc3 |
✅ resolved |
HygieneError from current_descendants escapes preview() |
e933fc3 |
✅ resolved |
DATA_ROOT_OVERRIDE global not reset on exit |
e933fc3 |
✅ resolved |
What's working well (unchanged from prior reviews)
- Guard token-length arithmetic for scan
{7, 9, 11, 13, 15}and apply{14, 16}— all correct truncated-not-inventoriedblocker logic — ancestor, descendant, and exact-match cases all handled correctly; now covers all three truncation sources uniformly--max-depthguard regex[1-9][0-9]{0,3}— rejects"007"and"-1"at the guard layer- Approval token anti-replay — binding to nonce + snapshot digest + tier + candidates is solid
os_autoclean_advisorypre-walk placement — included in failure payloads; no structural refactoring needed_data_root_keyusesstrict=False— correct for not-yet-created data directories- Standing-policy isolation in tests via
mock.patch.object(hygiene, "standing_policy_paths", ...) plugin.jsonversion bump to 0.3.0 and CHANGELOG format — accurate
Bottom line: All findings across four review rounds and all Codex P2 items are fully resolved. The implementation is mechanically correct, the test coverage is comprehensive and well-targeted, and no regressions are introduced. Ready to merge.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fc6f46c8fc
ℹ️ 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".
The prior commit's entry-cap fix (`>` -> `>=`) checked the count AFTER appending each entry, so a target with exactly MAX_SNAPSHOT_ENTRIES inventoryable entries now aborted too -- len(entries) == the cap makes `>=` true immediately after the last legitimate append, even though "exceeds N" is false at exactly N. That silently broke the configured maximum as a usable, inclusive limit (and would reject any test or deployment that sets the cap to match its exact expected entry count). The check now runs before each append instead of after: raise once len(entries) already equals the cap, i.e. once appending would be the (N+1)th entry, so the snapshot never exceeds N items and a target with exactly N never trips the check at all. Adds test_entry_cap_allows_exactly_the_configured_maximum and test_entry_cap_rejects_one_more_than_the_configured_maximum to pin both sides of the boundary directly against scan_tree(), rather than only through the coarser capped-scan CLI test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 57s —— View job Tasks
PR Review — fix(disk-hygiene): unblock guarded engine lane and bounded large-root auditsReviewed commit: This commit addresses the final Codex P2 item raised after the Final Codex P2 finding resolvedCap check moved before
|
| Finding | Commit | Status |
|---|---|---|
| Cap check off-by-one — now checks before append (Codex P2, round 5) | 47aa4bb |
✅ resolved |
VCS-boundary / protected dirs not in truncated_paths (Codex P2, round 4) |
fc6f46c |
✅ resolved |
Truncated candidate calls tracked_blocker / candidate_handle_state (Codex P2, round 3) |
4ecf1e2 |
✅ resolved |
Truncated candidate calls current_descendants (was blocking, round 2) |
e933fc3 |
✅ resolved |
annotate_tracked unbounded on bounded scan (Codex P2, round 2) |
e933fc3 |
✅ resolved |
Missing guard test for apply --data-root |
e933fc3 |
✅ resolved |
HygieneError from current_descendants escapes preview() |
e933fc3 |
✅ resolved |
DATA_ROOT_OVERRIDE global not reset on exit |
e933fc3 |
✅ resolved |
Bottom line
All findings across five review rounds and all Codex P2 items are fully resolved. The cap-check reorder is correct: exactly-at-cap scans succeed, one-over-cap scans raise, and the error message accurately describes the rejected state. The two new boundary tests are precise and well-named. Ready to merge.
|
Summary reply to the five
All six items are now resolved (five fixed in code, one accepted-and-documented as an intentional tradeoff). The full commit sequence: |
…#355) ## Summary Tranche 2 of wave #314 (epic #313, dim 8): the battle-tested markdown-format exemplar from #342 (six adversarial review rounds), cloned to the nine collision-free shape-A plugins. | Plugin | Version | apply write path | |---|---|---| | actionlint | 0.4.0 | none — guidance only | | bash-format | 0.5.0 | none — guidance only | | powershell-format | 0.4.0 | none — guidance only (module install is user-scope guidance) | | eol-normalizer | 0.4.0 | none — guidance only | | desktop-notification | 0.4.0 | none — probes only the current OS family's channel deps | | biome-format | 0.4.0 | `apply install-biome` — repo package manager (lockfile → packageManager → npm; Plug'n'Play → guidance) | | ruff-format | 0.4.0 | `apply install-ruff` — only into a managed env the repo already uses; never creates one | | firecrawl | 0.3.0 | none — defers to the plugin's lazy-install design; reports `FIRECRAWL_API_KEY` presence without printing it | | playwright | 0.3.0 | `apply install-cli` — stated-before-running global npm install | Contract invariants everywhere: `check` reads the plugin's own hook/skill source as single source of truth and probes what it actually does (no hand-copied lists to drift); `apply` re-checks first, names every write path in the argument-hint, states it before running, re-verifies after every remediation; never writes the plugin cache, user settings, or `pluginConfigs`; non-interactive when the action is supplied; `disable-model-invocation: true`. Also backfills the undocumented `[0.3.1]` CHANGELOG entries in actionlint and powershell-format (#298's hook-utils resync, verified via `git log -S`). **Deferred for lane ownership** (per the org WIP survey): disk-hygiene (PR #286 touches its plugin.json/CHANGELOG) and guardrails (PR #305 ditto) — they join a later tranche after those PRs land. ## Verification - `validate-plugins.sh`, `validate-plugin-contracts.mjs` (28 setup skills, 1513 files), markdownlint (55 files), `typos`: all green locally. ## Related - Part of #314 - Part of #313 No linked issue: tranche 2 of #314 — the issue closes with the wave's final tranche. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary Tranche 3 of wave #314 (epic #313, dim 8): all 17 remaining shape-B setups onto the uniform check/apply contract — refactors that preserve each plugin's own verification logic, plus two creates where the audit showed the listing was imprecise (claude-config shipped no setup; kindle-dedrm's setup was a router action, now a dedicated skill with the router delegating to it). | Plugin | Version | Notable | |---|---|---| | machine-health | 0.6.0 | interview → apply path; non-interactive disable/deprecate/demote/approve args | | source-control | 0.9.0 | carry-in: stale `install --config` reconfigure advice → fresh-install-only semantics | | toolchain | 0.4.0 | delivers the apply action its 0.3.0 entry forecast; per-ecosystem non-interactive scope | | claude-config | 0.6.0 | CREATE (audit: no setup shipped); README gains the bash prereq (dim-10 nit) | | claude-ops | 0.12.0 | freshness riders preserved verbatim; evals re-routed to explicit `apply` | | skill-quality | 0.5.0 | fixes README's dim-6 mis-framing of settings writes | | songwriting | 0.5.0 | one deliberate interactive exception: destructive-collision confirmation | | ai-briefing | 0.6.0 | verify+install fusion split (`apply install-build-deps`); 4 stale cross-refs in generate updated | | context7 | 0.4.0 | CLI/MCP dual-path check; `apply install-cli` gated | | discovery | 0.7.0 | gains the missing non-interactive arg path | | knowledge | 0.8.0 | ingest provisioners behind `apply install-deps` (verified standalone + idempotent) | | code-tidying / codebase-health | 0.5.0 | lanes / merged-config checks split from interview | | kindle-dedrm | 0.4.0 | dedicated setup skill; pinned-tag + Key_Finder guards untouched (referenced, not restated) | | planning | 0.20.0 | two-concern check incl. standards-index version delta; toggle carries `--config` semantics | | repo-fleet-hygiene | 0.2.0 | check split over existing arg grammar | | work-items | 0.11.0 | probes the tracker-seam gates as authoritative, never restates them | Contract refinements from tranches 1–2 embedded throughout: opt-in absence = INFO vs broken = FAIL; verify-after-remediation; secrets presence-only; env-var changes defer verification to a fresh session; `--config` fresh-install-only reconfigure guidance; non-interactive with complete arguments. Write-behavior evals re-routed to explicit `apply`. Root README catalog regenerated. With this PR, 27 of 31 plugins conform. Remaining: bug-report + miro (check-only carve-out PR with its one-line doctrine sanction), disk-hygiene + guardrails (deferred for the #286/#305 lanes), then the contract-gate check/apply rule as the closing tranche. ## Verification - `validate-plugins.sh` (incl. regenerated catalog), `validate-plugin-contracts.mjs` (31 setup skills, 1537 files), markdownlint (725 files), `typos`: all green locally. Agents additionally ran skill-quality checks on the six plugins in group D (all PASS). ## Related - Part of #314 - Part of #313 No linked issue: tranche 3 of #314 — the issue closes with the wave's final tranche. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ort, miro) (#361) ## Summary Resolves the wave #314 design question (raised in the issue before mass-refactoring bug-report/miro): where a plugin's **entire** configuration surface is native `userConfig` — no tracked project config, no prerequisite an install could resolve — an `apply` action would have nothing to write except the `pluginConfigs` the setup contract itself forbids. - **Doctrine** (`docs/PLUGIN-PHILOSOPHY.md`, setup section): one added sentence sanctioning the check-only shape for exactly that case. Kept in its own PR so the doctrine change is reviewable in isolation. - **bug-report 0.5.0**: `check` is the sole action; the interactive "ask whether reports should be private or repo-resident" step becomes a stated tradeoff with a recommendation (non-interactive); reconfigure guidance states `--config`'s fresh-install-only semantics; rechecks defer to a fresh session since the rendered value is injected at load. - **miro 0.2.0**: `check [verify-api]` — the optional read-only credential probe (list one board, count only) becomes an explicit argument instead of an in-flow question; token never read, printed, or persisted (unchanged). MCP server version realigned across `package.json`, the server `Implementation` version, the lockfile, and the rebuilt committed bundle (drift check + typecheck green; lockfile delta is the two version fields). With this, 29 of 31 wave plugins conform. Remaining: disk-hygiene + guardrails (deferred behind the #286/#305 lanes) and the closing contract-gate rule. ## Verification `validate-plugins.sh`, `validate-plugin-contracts.mjs`, markdownlint, `typos`, miro bundle drift check + typecheck: all green locally. ## Related - Part of #314 - Part of #313 No linked issue: carve-out slice of #314 — the issue closes with the wave's final tranche. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
) ## Summary Closing tranche of wave #314 (epic #313, dim 8) — with this PR, every plugin in the fleet conforms to the uniform setup contract and the contract is machine-enforced. - **disk-hygiene 0.4.0** — setup skill created (deferred until #286 landed): `check` probes Python 3.11+ (absolute-interpreter note), conditional Git, and the current OS family's documented lane (Linux `lsof` optional-execution note, macOS audit-only by design as INFO); guidance-only `apply`; disabled-toggle downgrade. - **guardrails 0.7.0** — setup skill created (deferred until #305 landed): `check` reads the guard scripts + `hooks.json` as truth and probes Bash 5.0+, `jq` (absence = every guard fails open — surfaced as FAIL), all eight guard toggles, the `cli-flag-verify` scan surface, and the `block-dangerous-git` allowlist; guidance-only `apply`. - **review 0.14.0** — its setup sat outside the wave list and was caught by the new gate on first run: adopts `check | apply` while keeping the standards-contract binding's by-reference discipline and explicit-confirmation gates intact. - **Contract gate** — `validate-plugin-contracts.mjs` now enforces the shape on every `skills/setup/SKILL.md`: `check` leads the `argument-hint`, the read-only check action is documented, and `apply` exists unless the skill declares the doctrine's check-only userConfig-only carve-out (#361). 33 setup skills pass fleet-wide; future setups are gated, not hand-reviewed. ## Verification `validate-plugins.sh`, `validate-plugin-contracts.mjs` (33 setup skills, 1541 files), markdownlint, `typos`: green locally. The gate change was validated by its own first catch (review). ## Related - Closes #314 - Part of #313 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…#869) ## Summary Closes the two gaps #286 left open from issue #287: the `clean` skill's step 2 said to inspect "hinted or suspicious" entries but never defined "suspicious" for an entry with no hint, so unhinted agent-session droppings at a home-directory root (a status snapshot, a one-off export) were never triaged even though the scan snapshot already records every walked entry. ## Fix - `plugins/disk-hygiene/skills/clean/SKILL.md` §2: added a paragraph defining "suspicious" for a user-home target — any loose root-level entry not in `protected_exact_names` and not matching a recognizable app/config convention. This single guidance addition realizes both of #286's open findings (the undefined "suspicious" criteria, and the agent-session status-file heuristic) since they're the same positional gap: the engine already snapshots every walked entry with a possibly-empty `hints` list, so the data was always available, just never acted on. - `plugins/disk-hygiene/skills/clean/reference/baseline-policy.json` is intentionally **untouched**. The issue's `tmp_*` hint-glob gap already shipped in #286/v0.3.0. The remaining class of entries (`melo-lap-001-*.json`, `org_usage.json`) shares no common name shape — the issue itself calls a generic glob "admittedly hard" — so adding a baseline-policy.json pattern here would mean fabricating a naming convention the evidence doesn't support. The positional SKILL.md guidance is the mechanism the issue's own notes suggest as the alternative. - Bumped `plugins/disk-hygiene/.claude-plugin/plugin.json` 0.4.1 → 0.4.2 and added a matching CHANGELOG entry. ## Verification - Confirmed the linchpin empirically before writing the guidance: `hygiene.py`'s snapshot walk (`scripts/hygiene.py:608-615`) appends every walked entry with `"hints": matching_hints(...)` (possibly empty), not just hinted ones — so the new guidance has real data to act on. - Ran the repo's skill-quality gate: `bash scripts/check-changed-skills.sh origin/main` — PASS, 0 errors (SKILL.md 235/500 lines, well under the hard cap; only a pre-existing soft line-count WARN, unrelated to this change). Closes #287 ## Related - #286 landed the `tmp_*`/`scratch*` baseline-policy.json hints and explicitly scoped this issue's remaining two findings as open; this PR closes them. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
No related issue: this PR emerged from a live disk-hygiene audit session rather than a pre-filed tracker item, and its defects fixed span several undocumented gaps together (see below).
Related
tmp_*/scratch*baseline-hint addition (defect 7 below) covers only that report's hint-glob gap; its other two gaps (an agent-session status-file heuristic, and SKILL.md's undefined "suspicious" triage criteria) remain open and untouched here.Why
A live audit of a Windows user profile (
/disk-hygiene:cleanon the profile root) hit every one of these in sequence; full defect log in the session run notes. The engine was unusable through its own sanctioned lane, and the guard did not cover the platform's primary shell.Defects fixed
CLAUDE_PLUGIN_DATAis exported to hook processes and interpolated into skill content, but not into Bash/PowerShell tool subprocesses (plugins reference), so every guard-approved scan died withCLAUDE_PLUGIN_DATA is required for generated state, and the guard's literal-word tokenizer offered no way to supply it. New--data-rootflag on scan/preview/apply; the guard validates the value against theCLAUDE_PLUGIN_DATAits own hook process received, discloses the authorized root in denial guidance (alongside the interpreter path), and fails closed without hook authority. Env var remains a fallback.Bash|PowerShell(hooks guide: matchers accept any tool name): engine invocations from PowerShell are hard-denied, deletion spellings / .NET Delete calls become a final human permission prompt (same bar as engine apply), read-only support work passes. Documented as a raised bar, not fail-closed — Bash stays the deny-unknown engine lane.--max-depthbounded scan; truncated directories land intruncated_paths, are reported as coverage gaps, and are mechanically blocked from plans by a newtruncated-not-inventoriedpreview blocker (defense in depth on top of the live descendant-set comparison).--max-depth.os_autocleanadvisory unreachable for its primary target. Advisory (Storage Sense / systemd-tmpfiles) is computed pre-walk and included in scan failure payloads, so a capped profile scan still surfaces it.AskUserQuestionbar, per-path revalidation, reversible (Recycle Bin/Trash) deletion preferred.tmp_*(medium ceiling) andscratch*(low ceiling) baseline hints; the audit's 12 confirmed leftovers carried zero hints.~as an expansion character, which includes Windows short paths; documented (long-form paths required; guard disclosures are long-form).Verification
python -m unittest test_hygiene: 64 tests, OK (4 pre-existing platform skips), on Windows / Python 3.14.--max-depthliteral validation, PowerShell lane (engine deny, deletion-spelling ask, read-only defer), engine truncation + preview blocker,--data-rootwithout env, capped-scan advisory + guidance, apply-command--data-rootauthorization, preview's live-descendant-walk short-circuit for a truncated candidate, andannotate_tracked's git-query path scoping.Delivery
disk-hygiene0.2.0 -> 0.3.0 with a new plugin CHANGELOG (version bump is the delivery vehicle per the migration playbook).🤖 Generated with Claude Code