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.1.0",
"version": "0.2.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, changed entries, and live-handle uncertainty fail closed.",
"author": {
"name": "Melodic Software",
Expand Down
27 changes: 21 additions & 6 deletions plugins/disk-hygiene/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ at preview. Backups remain the recovery boundary for user data.
The skill stores snapshots, plans, and reports under `${CLAUDE_PLUGIN_DATA}`. It never writes generated
state into the installed plugin directory or the audited target.

The optional policy file has this shape:
Policy files all share one shape:

```json
{
Expand All @@ -78,17 +78,29 @@ The optional policy file has this shape:
"id": "my-tool-staging",
"os": ["all"],
"kind": "name_glob",
"pattern": "temp_git_*",
"pattern": "my-tool-stage-*",
"confidence_ceiling": "medium",
"reason": "My tool's documented clone-staging convention"
"reason": "My tool's documented staging-directory convention"
}
],
"additional_protected_path_globs": ["client-deliverables/**"]
}
```

Without `--policy`, standing policy files layer over the baseline when present:
`~/.claude/disk-hygiene.json` (user-global) first, then the consumer project's
`.claude/disk-hygiene.json`. An explicit `--policy` file is the invocation-specific choice and
replaces both standing layers. The scan output records which sources applied.

Candidate hints can be disabled or extended. Consumer protection globs are additive. Hard safety
predicates and the baseline protected-name/root rules are non-overridable.
predicates and the baseline protected-name/root rules are non-overridable by any layer: a policy
file can only add protections, add hints, or disable discovery hints (which can only cause junk to
be missed, never removed).

When the audited zone overlaps the user temp directory, the scan also reports an `os_autoclean`
advisory naming the OS mechanism that should own it (Windows Storage Sense, systemd-tmpfiles) and,
when that mechanism is off or set to fire only on low disk space, recommends enabling it rather than
hand-cleaning the zone.

## Relationship to other tools

Expand All @@ -109,8 +121,11 @@ predicates and the baseline protected-name/root rules are non-overridable.
construction, or downloads are used. Paths cross the process boundary as JSON or individually
quoted CLI arguments.
- **MCP / external trust:** no MCP server, agent, dependency, or third-party service is shipped.
- **Configuration:** no `userConfig` and no credentials. Optional policy is an explicit invocation
argument and contains patterns only.
- **Configuration:** no `userConfig` and no credentials. Policy comes from an explicit invocation
argument or standing `disk-hygiene.json` files under `~/.claude/` and the consumer project's
`.claude/`. All policy input is pattern-only and additive: it can add protections and discovery
hints or disable hints, and cannot weaken hard guards or authorize removal, so ambient config
cannot widen the destructive surface.
- **Isolation:** bundled assets resolve from `${CLAUDE_PLUGIN_ROOT}`; generated state belongs under
`${CLAUDE_PLUGIN_DATA}`. The audited target is read, then mutated only through the gated lane.
- **Egress:** none. `git` and `lsof` are local read-only subprocesses.
Expand Down
16 changes: 13 additions & 3 deletions plugins/disk-hygiene/skills/clean/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,26 @@ stay there, never in the target or `${CLAUDE_PLUGIN_ROOT}`. Run:

```text
"<hook-python>" "${CLAUDE_PLUGIN_ROOT}/skills/clean/scripts/hygiene.py" scan \
--target "<target>" --output "<run-dir>/snapshot.json" [--policy "<policy.json>"]
--target "<target>" --output "<run-dir>/snapshot.json" [--policy "<policy.json>"] \
--project-dir "${CLAUDE_PROJECT_DIR}"
```

For a large root, first map its immediate children and fan out read-only analysis by subtree. Each
worker receives a bounded subtree and returns evidence only. The parent owns classification, the single
report, every approval, preview, and all execution. Do not let workers delete or prepare approvals.

The bundled [baseline policy](reference/baseline-policy.json) contains cross-platform candidate hints
and protected names. An optional policy can disable/add hints and add protected globs; it cannot weaken
hard guards. Treat scan errors and unvisited protected roots as coverage gaps, not clean results.
and protected names. Without `--policy`, the engine also layers standing policy files when present:
`~/.claude/disk-hygiene.json` (user-global), then `<project>/.claude/disk-hygiene.json` via
`--project-dir`. An explicit `--policy` is the invocation-specific choice and replaces both standing
layers. Every overlay can only disable/add hints and add protected globs; none can weaken hard guards.
The scan output names its `policy_sources`. Treat scan errors and unvisited protected roots as
coverage gaps, not clean results.

The scan output may also carry an `os_autoclean` advisory when the target overlaps a zone an OS
mechanism (Windows Storage Sense, systemd-tmpfiles) should own. Surface its recommendation in the
report; prefer enabling the OS mechanism over hand-cleaning that zone, mirroring the managed-state
rule below.

## 2. Establish evidence and ownership

Expand Down
24 changes: 24 additions & 0 deletions plugins/disk-hygiene/skills/clean/evals/evals.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,30 @@
"Does not run hygiene.py apply",
"Returns the tiered audit and states that interactive confirmation is required"
]
},
{
"id": 9,
"name": "standing-policy-files-layer-additively",
"prompt": "/disk-hygiene:clean ~/scratch — my user-global ~/.claude/disk-hygiene.json protects 'client-deliverables/**' and this project's .claude/disk-hygiene.json adds a staging hint.",
"expected_output": "The scan without --policy layers the user-global then project standing policy files over the baseline, reports which policy sources applied, and honors the added protections; no layer can weaken hard guards.",
"files": [],
"expectations": [
"Runs scan without --policy and the output's policy_sources lists baseline plus both standing files",
"Treats standing policy input as additive: added protections and hints only",
"An explicit --policy file replaces the standing layers instead of stacking on them"
]
},
{
"id": 10,
"name": "os-managed-temp-zone-recommends-the-os-mechanism",
"prompt": "/disk-hygiene:clean --execute my user temp directory, it has gigabytes of stale files.",
"expected_output": "The audit surfaces the os_autoclean advisory: the OS ships an auto-clean mechanism for this zone (e.g. Windows Storage Sense), so the report recommends enabling/tuning it rather than hand-deleting, mirroring the managed-state handoff rule.",
"files": [],
"expectations": [
"Surfaces the scan's os_autoclean recommendation in the report",
"Prefers enabling the OS mechanism over manual cleanup of the OS-owned zone",
"Still performs the read-only audit and never treats the advisory as deletion authority"
]
}
]
}
24 changes: 24 additions & 0 deletions plugins/disk-hygiene/skills/clean/reference/baseline-policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,30 @@
"pattern": "*.crdownload",
"confidence_ceiling": "low",
"reason": "Browser partial download; browser process and resume state must be checked"
},
{
"id": "claude-json-failed-atomic-write",
"os": ["all"],
"kind": "name_glob",
"pattern": ".claude.json.tmp.*",
"confidence_ceiling": "medium",
"reason": "Claude Code atomic-write staging remnant (.claude.json.tmp.<pid>.<hash>); confirm no live Claude Code process owns it"
},
{
"id": "agent-temp-git-scratch",
"os": ["all"],
"kind": "name_glob",
"pattern": "temp_git_*",
"confidence_ceiling": "medium",
"reason": "AI-agent Git scratch/staging directory leaked outside a worktree; confirm no session or clone still references it"
},
{
"id": "pulumi-writability-probe",
"os": ["all"],
"kind": "name_glob",
"pattern": ".pulumi-write-test-*",
"confidence_ceiling": "medium",
"reason": "Pulumi orphaned writability probe; a completed or crashed run leaves it behind, but verify no engine process is active"
}
]
}
31 changes: 22 additions & 9 deletions plugins/disk-hygiene/skills/clean/scripts/destructive_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,28 @@ def classify_exact_engine_command(command: str) -> str | None:
return None

if tokens[2] == "scan":
valid = (
len(tokens) in {7, 9}
and tokens[3] == "--target"
and _argument(tokens[4])
and tokens[5] == "--output"
and _argument(tokens[6])
and (len(tokens) == 7 or (tokens[7] == "--policy" and _argument(tokens[8])))
)
return "scan" if valid else None
if (
len(tokens) not in {7, 9, 11}
or tokens[3] != "--target"
or not _argument(tokens[4])
or tokens[5] != "--output"
or not _argument(tokens[6])
):
return None
optional = tokens[7:]
seen: list[str] = []
while optional:
flag = optional[0]
if (
flag not in {"--policy", "--project-dir"}
or flag in seen
or len(optional) < 2
or not _argument(optional[1])
):
return None
seen.append(flag)
optional = optional[2:]
return "scan"
if tokens[2] == "preview":
valid = (
len(tokens) == 7
Expand Down
Loading
Loading