Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/disk-hygiene/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "disk-hygiene",
"version": "0.21.9",
"version": "0.22.0",
"description": "Context-aware disk hygiene for arbitrary directory trees: inventories orphaned and temporary artifacts, classifies evidence into review tiers, and offers exact-path cleanup only after a fresh safety preview and explicit per-tier approval. The target is read-only by default; OS-managed paths, links and mount points, VCS-tracked content without the complete checkout evidence bundle, changed entries, and live-handle uncertainty fail closed.",
"author": {
"name": "Melodic Software",
Expand Down
25 changes: 25 additions & 0 deletions plugins/disk-hygiene/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,31 @@
All notable changes to the `disk-hygiene` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.22.0]

### Added

- **clean:** `scan --quiet` omits `children_rollup` from stdout and shortens the closing note,
keeping `status`, `target`, `snapshot`, the three coverage terms, `empty_directory_count`, both
byte totals, `truncated_paths`, `errors`, `policy_sources` and `os_autoclean`. The snapshot file
carries the rollup in full in both modes, so the flag drops a duplicate rather than data. Default
output is unchanged: a caller already parsing the rollup off stdout keeps it. On a real
`--max-depth 1` home-directory scan the payload fell from 7,247 to 893 bytes (88 %); on a
200-child target, from 67,262 to 5,052 (93 %), because the rollup is the only part that grows
with the frontier.
- **clean:** the Bash guard admits `--quiet` as a third valueless scan flag, at most one per
invocation and with no trailing value, so the flag is reachable without widening the grammar. No
pipe, redirect, or shell-operator allowance is added; that rejection is unchanged.
- **clean:** `--root-children` scans get their own quiet note, and report `empty_directory_count`
on stdout like an ordinary scan does. Root-children mode's default note carries a coverage
qualification nothing else on stdout encodes: the volume root and every skipped
OS-owned/hidden/system/reparse entry were never walked, so the inventory is partial by
construction and `children_rollup` covers the selected children only. The skipped entries are
recorded as `root_children_skipped` in the snapshot alone and `truncated_paths` does not stand in
for them, so quieting that sentence away would drop a fact rather than a duplicate. The quiet
root-children note keeps the coverage sentence and drops only the rollup prose, and the field set
`--quiet` documents now holds in both scan modes rather than in ordinary scans alone.

## [0.21.9]

### Changed
Expand Down
15 changes: 11 additions & 4 deletions plugins/disk-hygiene/skills/clean/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: "Audit an arbitrary directory tree for orphaned, temporary, stale-lock, failed-write, partial-download, and empty leftover artifacts; classify evidence into confidence tiers; and optionally remove exact validated paths after explicit per-tier approval. Read-only by default and manual-only. Use when: 'audit this directory', 'find orphaned files', 'what junk can I clean up', 'reclaim disk space', 'find temp or lock leftovers', 'clean up my home directory'. Skip when: repository cache/build cleanup belongs to repo-hygiene, a product has its own prune/GC command, or the target is an OS-managed root."
argument-hint: "[--execute] [--policy <policy.json>] [--max-depth <N>] [--confirmed-large-scan] [--root-children [--root-child <name>]...] <target-directory>"
argument-hint: "[--execute] [--policy <policy.json>] [--max-depth <N>] [--confirmed-large-scan] [--quiet] [--root-children [--root-child <name>]...] <target-directory>"
user-invocable: true
disable-model-invocation: true
hooks:
Expand Down Expand Up @@ -39,12 +39,17 @@ primary objective; reclaimed bytes are secondary.** Read

Parse `$ARGUMENTS` as the complete user-facing surface: optional `--execute`, optional
`--policy <file>`, optional `--max-depth <N>`, optional `--confirmed-large-scan`, optional
`--quiet`, optional
`--root-children` with zero or more `--root-child <name>`, and one target directory. Remaining
engine flags (`--output`, `--project-dir`, `--data-root` on scan; `--snapshot`, `--plan`,
`--report`, `--confirm-tier`, `--approval-token`, `--paths`, and `--vcs-evidence` on the other
subcommands) are supplied by this skill's command templates, not typed by the user.
`--execute` means "deletion may be offered" on every platform, the gated engine lane where the
platform supports it, the manual handoff elsewhere; it is not approval. `--max-depth <N>` bounds a
platform supports it, the manual handoff elsewhere; it is not approval. `--quiet` shapes the
scan's stdout and nothing else: it omits `children_rollup` and shortens the closing note, leaving
every counter, byte total, coverage gap, error and policy source in place. The snapshot file
carries the rollup in full in both modes, so read per-child detail there and pass `--quiet`
whenever the run only needs the frontier summary. `--max-depth <N>` bounds a
scan to depth N (preferred for large targets); `--confirmed-large-scan` opts into an unbounded
full walk after the human clears the [confirmation gate](#confirmation-gate)'s scan-scope row.
`--root-children` is the only way to address an OS-managed volume root (for example `C:\` or `/`):
Expand Down Expand Up @@ -135,7 +140,7 @@ stay there, never in the target or `${CLAUDE_PLUGIN_ROOT}`. Run:
"<hook-python>" "${CLAUDE_PLUGIN_ROOT}/skills/clean/scripts/hygiene.py" scan \
--target "<target>" --output "<run-dir>/snapshot.json" [--policy "<policy.json>"] \
--project-dir "${CLAUDE_PROJECT_DIR}" --data-root "${CLAUDE_PLUGIN_DATA}" \
[--max-depth <N>] [--confirmed-large-scan] \
[--max-depth <N>] [--confirmed-large-scan] [--quiet] \
[--root-children [--root-child <name>]...]
```

Expand Down Expand Up @@ -242,7 +247,9 @@ returns `depth-cut`/`null` for every NON-EMPTY child: the frontier is complete,
fanning a deeper scan out over that subtree, report those rows as coverage gaps, never as small or clean.
`scan-complete` also carries `unhinted_entries`, `entries` minus `hinted_entries`, every inventoried entry no hint
judged. So quote hint coverage as a rate: 7 hinted of 40,247 is 0.017 %, nothing like "7 findings". Fields, reasons
and the measurement: [the safety model](reference/safety-model.md).
and the measurement: [the safety model](reference/safety-model.md). When a run needs the frontier ranked but not
the rows themselves in context, add `--quiet`: the rollup stays complete in the snapshot and stops being duplicated
onto stdout, where one row per immediate child dominates a wide target's payload.

**Relocation is out of scope.** This skill offers exactly two outcomes per finding, keep it, or approve its
exact path for deletion. There is no relocation lane and no move primitive in the engine, by design: a move
Expand Down
26 changes: 24 additions & 2 deletions plugins/disk-hygiene/skills/clean/reference/safety-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,10 @@ guard as `--plugin-root` and mapped to `<plugins>/data/<id>` per the documented
layout. A host that can substitute `${CLAUDE_PLUGIN_DATA}` itself may instead pass it directly as
`--authorized-data-root`, and the `CLAUDE_PLUGIN_DATA` environment variable is honored last; absent
every channel the flag fails closed. `--max-depth` accepts only a bare positive-integer literal.
`--confirmed-large-scan` and `--root-children` are the valueless scan flags; the guard permits at
most one of each and rejects any trailing value, so the scan grammar stays exact.
`--confirmed-large-scan`, `--quiet` and `--root-children` are the valueless scan flags; the guard
permits at most one of each and rejects any trailing value, so the scan grammar stays exact.
`--quiet` is admitted because it shapes the engine's stdout only: it reaches no path, and skips no
check, that the same invocation without it would not already reach.

Deriving the data root from `${CLAUDE_PLUGIN_ROOT}` couples to the one undocumented part of that
layout — the `cache/<marketplace>/<name>/<version>` shape of the installation root (the install root
Expand Down Expand Up @@ -442,6 +444,26 @@ NON-EMPTY child is not knowable without walking it, so every such child reads `d
loose files and empty children; a per-child total is bought by fanning a deeper scan out over that
subtree.

The roll-up is written to the snapshot file on every run, so `scan --quiet` omits it from stdout
and nothing else. The two copies are otherwise identical, and the snapshot is the copy the engine
treats as the record: the flag drops a duplicate, never data. Quiet output keeps `snapshot`,
`status`, `target`, the three coverage terms, `empty_directory_count`, both byte totals,
`truncated_paths`, `errors`, `policy_sources` and `os_autoclean`, so every field a keep-or-review
Comment thread
kyle-sexton marked this conversation as resolved.
decision rests on survives, and it replaces the closing note with a short one naming where the
rows went. That field set holds in `--root-children` mode too, which reports
`empty_directory_count` on stdout for the same reason an ordinary scan does. The default stays the
full payload: a caller already parsing `children_rollup` off stdout must not be quietened by an
upgrade.

Root-children mode's quiet note is its own. That mode's default note carries a coverage
qualification the ordinary one has no reason to: the volume root itself and every skipped
OS-owned, hidden, system or reparse entry were never walked, so the inventory is partial by
construction. Nothing else on stdout encodes that. The skipped entries are recorded as
`root_children_skipped` in the snapshot alone, and `truncated_paths` does not stand in for them,
so a quiet note that dropped the qualification would be dropping a fact rather than a duplicate.
The quiet root-children note therefore keeps the coverage sentence and drops only the rollup
prose.

The `scan-complete` summary reports hint coverage in three terms — `entries`, `hinted_entries`, and
`unhinted_entries` (`entries` minus `hinted_entries`). The third is what makes the first two
readable: without a denominator for what no hint judged, a run that annotated 7 of 40,247 entries is
Expand Down
18 changes: 11 additions & 7 deletions plugins/disk-hygiene/skills/clean/scripts/destructive_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -883,16 +883,20 @@ def classify_exact_engine_command(command: str, authority: str | None) -> str |
or not _argument(tokens[6])
):
return None
# --confirmed-large-scan and --root-children are the valueless scan
# flags; strip at most one of each so the remainder is the pure
# --confirmed-large-scan, --quiet and --root-children are the valueless
# scan flags; strip at most one of each so the remainder is the pure
# flag/value-pair grammar every other optional follows. --root-child
# is repeatable (one basename per occurrence) and is stripped next.
# --quiet only shapes the engine's stdout, so admitting it widens no
# capability: it cannot reach a path the same invocation without it
# could not already reach.
optionals = list(tokens[7:])
confirmed = optionals.count("--confirmed-large-scan")
if confirmed > 1:
return None
if confirmed:
optionals.remove("--confirmed-large-scan")
for valueless in ("--confirmed-large-scan", "--quiet"):
occurrences = optionals.count(valueless)
if occurrences > 1:
return None
if occurrences:
optionals.remove(valueless)
root_children = optionals.count("--root-children")
if root_children > 1:
return None
Expand Down
Loading
Loading