{
  "markdown": "<div align=\"center\">\n\n# upkeep\n\n**Cross-platform system cleanup and updater Skill for Claude Code**\n\nDiscovery-based disk audit, cleanup, and one-command updates for macOS 14+, Linux (Debian/Ubuntu, Fedora/RHEL, Arch), and WSL2. Finds orphaned app data, stale caches, dead LaunchAgents, Linux package cruft, systemd journal bloat, and configuration drift. Also updates AI skills (upkeep, gstack, etc.), Claude Code plugins, and package managers (brew, apt/dnf/pacman, snap, flatpak, npm, pipx, gems, rustup, bun, deno, mise, uv) in one sweep.\n\n[![macOS](https://img.shields.io/badge/macOS-14%2B-000?logo=apple&logoColor=white)](https://www.apple.com/macos/)\n[![Linux](https://img.shields.io/badge/Linux-Debian%20%7C%20Fedora%20%7C%20Arch-FCC624?logo=linux&logoColor=black)](https://www.kernel.org/)\n[![WSL2](https://img.shields.io/badge/WSL2-supported-4EAA25?logo=windowsterminal&logoColor=white)](https://learn.microsoft.com/windows/wsl/)\n[![Claude Code](https://img.shields.io/badge/Claude_Code-skill-7C3AED?logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTIgMkw0IDdWMTdMMTIgMjJMMjAgMTdWN0wxMiAyWiIgZmlsbD0id2hpdGUiLz48L3N2Zz4=&logoColor=white)](https://docs.anthropic.com/en/docs/claude-code)\n[![Version](https://img.shields.io/github/v/release/KyleNesium/upkeep?color=green)](https://github.com/KyleNesium/upkeep/releases)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\n</div>\n\n---\n\n<details>\n<summary><strong>Table of Contents</strong></summary>\n\n- [Why upkeep?](#why-upkeep)\n- [Prerequisites](#prerequisites)\n- [Install](#install)\n- [Usage](#usage)\n- [How It Works](#how-it-works)\n- [Cleanup Categories](#cleanup-categories)\n- [Modes](#modes)\n- [Updating](#updating)\n- [Safety](#safety)\n- [Privacy](#privacy)\n- [Architecture](#architecture)\n- [Contributing](#contributing)\n- [Changelog](#changelog)\n- [Security](#security)\n- [Test Coverage](#test-coverage)\n- [License](#license)\n\n</details>\n\n---\n\n## Why upkeep?\n\nmacOS doesn't clean up after you. Every time you remove an app, install a dev tool, or run a build, it leaves data behind — caches, orphaned support files, stale LaunchAgents, old iOS backups. Over months and years this compounds into dozens of gigabytes that macOS never reclaims automatically.\n\nMost cleanup tools work from a hardcoded list of known apps and paths. **upkeep is discovery-based**: it looks at what's actually installed and cross-references what's left over, so it catches orphaned data from tools that aren't on any list — renamed apps, one-off installers, anything.\n\nFirst deep clean on a migrated Mac typically recovers **10–50GB**. Monthly quick sweeps keep dev caches and Electron bloat in check with minimal effort.\n\n---\n\n## Prerequisites\n\n### Supported platforms\n\n- **macOS 14+** (Sonoma or later) — full 15-phase coverage\n- **Linux** — Debian/Ubuntu (apt), Fedora/RHEL (dnf), Arch (pacman). Optional: snap, flatpak.\n- **WSL2** on Windows 10/11 — Ubuntu, Debian, Fedora, or Arch distro. /mnt/c bridge required for Windows-side bonus phases.\n\n### Required\n\n- **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** (for `/upkeep` slash command)\n\n### Optional (detected at runtime — skipped gracefully if absent)\n\n- **macOS:** Homebrew, Xcode Command Line Tools, Docker\n- **Linux/WSL2:** apt / dnf / pacman, snap, flatpak, systemd (for journalctl vacuum)\n- **Both:** git (for skill updates), node/npm/bun/pipx/uv/cargo/etc. for their respective cache cleanups\n\n---\n\n## Platform Support\n\nupkeep routes each phase based on detected OS. Phases skip cleanly on platforms where they don't apply (e.g., Homebrew on Linux, systemd journal on macOS) with a visible \"skipped\" note — never errors.\n\n| Platform       | Package managers                                                  | Platform-specific phases                                                                     |\n|----------------|-------------------------------------------------------------------|----------------------------------------------------------------------------------------------|\n| macOS 14+      | brew, mas                                                         | Homebrew, LaunchAgents, Xcode, iOS backups, Electron caches, orphaned app data (mdfind)      |\n| Ubuntu/Debian  | apt, snap (opt), flatpak (opt)                                    | apt clean, ~/.cache sweep, journalctl vacuum, snap/flatpak cleanup, orphaned kernels + .deb  |\n| Fedora/RHEL    | dnf, flatpak (opt)                                                | dnf clean, ~/.cache sweep, journalctl vacuum, flatpak cleanup, orphaned .rpm + kernels       |\n| Arch           | pacman, flatpak (opt)                                             | pacman -Sc, ~/.cache sweep, journalctl vacuum, flatpak cleanup, orphaned packages            |\n| WSL2 (Windows) | Linux pkg mgr + Windows audit (winget, scoop, choco — audit only) | Everything Linux offers + Windows %TEMP% and %LOCALAPPDATA% npm/pip cache audit via /mnt/c   |\n\nDetection runs once at the top of every skill invocation via `uname -s`, `/etc/os-release`, and `uname -r | grep -qi microsoft` (for WSL2). The result is exported as `$OS_TYPE`, `$OS_DISTRO`, and `$PKG_MGR` for every downstream phase.\n\nCross-platform phases that run everywhere: disk baseline (Phase 1), dev tool caches (Phase 3), Docker (Phase 7), project build artifacts (Phase 8), stale logs (Phase 9), shell config audit (Phase 10), large files (Phase 12), trash (Phase 13), pipx tools (Phase 15).\n\n---\n\n## Install\n\n> **Status:** awaiting acceptance into the `claude-community` marketplace ([submission pending](https://claude.ai/settings/plugins/submit)). Until then, install directly from this repository as a personal marketplace.\n\nIn Claude Code, run these two slash commands:\n\n```\n/plugin marketplace add KyleNesium/upkeep\n/plugin install upkeep@KyleNesium\n```\n\nThe first command registers this repository as a marketplace named `KyleNesium`. The second installs the `upkeep` plugin from it.\n\nThen run `/reload-plugins` (or restart Claude Code) — the skill is then available as `/upkeep` (and the namespaced variants `/upkeep:audit`, `/upkeep:cleandeep`, `/upkeep:cleanquick`, `/upkeep:update`).\n\nTo update later:\n\n```\n/plugin update upkeep@KyleNesium\n```\n\nTo remove:\n\n```\n/plugin uninstall upkeep@KyleNesium\n/plugin marketplace remove KyleNesium\n```\n\nAlternative for local hacking: `git clone` this repo and load it with `--plugin-dir` (no marketplace registration needed):\n\n```bash\ngit clone https://github.com/KyleNesium/upkeep ~/src/upkeep\nclaude --plugin-dir ~/src/upkeep/upkeep\n```\n\n---\n\n## Usage\n\n### Slash commands\n\n```\n# Mode selector — asks if no keyword detected\n/upkeep                     # asks which mode\n/upkeep deep                # full 15-phase audit + cleanup\n/upkeep quick               # routine cache + brew sweep\n/upkeep audit               # full scan, report only, no changes\n\n# Direct sub-skill commands (bypass mode selector)\n/upkeep:cleandeep           # full 15-phase cleanup, no prompt\n/upkeep:cleanquick          # fast sweep (phases 1-3, 8, 11, 13), no prompt\n/upkeep:audit               # report-only scan, no prompt\n\n# Update mode\n/upkeep:update              # asks which update sub-mode\n/upkeep:update audit        # check what's outdated, no changes\n/upkeep:update skills       # update AI skills only\n/upkeep:update packages     # upgrade brew, npm, pip, gems, etc.\n/upkeep:update all          # skills + packages, full sweep\n```\n\n### Trigger phrases\n\nClaude will load the skill automatically when you ask for a cleanup in natural language. Some examples that work:\n\n- \"clean up my mac\"\n- \"free up disk space\"\n- \"audit my mac — what's taking up space?\"\n- \"I just migrated from my old Mac, do a deep clean\"\n- \"quick cleanup\"\n- \"find orphaned app data\"\n- \"update everything\"\n- \"update my AI skills\"\n- \"upgrade my packages\"\n- \"check for updates\"\n\nThe skill scans your system, presents a summary table with reclaimable space per category, and asks before removing anything. Before/after disk usage comparison at the end.\n\n> [!TIP]\n> Run `/upkeep deep` after migrating to a new Mac — migrations carry over gigabytes of orphaned data from apps you no longer use.\n\n---\n\n## How It Works\n\n**Discovery-based, not hardcoded.** Instead of checking a fixed list of known apps and caches, the skill:\n\n1. Lists what's actually installed in `/Applications/`\n2. Scans `~/Library/Application Support/`, `~/Library/Containers/`, and `$HOME` dotdirs\n3. Cross-references to find **orphaned data** — directories that belong to apps no longer installed\n4. Scans `~/.cache/*/` and `~/Library/Caches/*/` for any large cache, not just known ones\n5. Auto-discovers project workspace directories (`~/workspace`, `~/dev`, `~/code`, etc.)\n\nThis catches cleanup targets that a hardcoded list would miss — new tools, renamed apps, one-off installers.\n\n---\n\n## Cleanup Categories\n\n| # | Category | Platform | Deep | Quick | Audit | What it finds | Typical savings |\n|---|----------|----------|:----:|:-----:|:-----:|---------------|----------------|\n| 1 | Baseline | all | ✓ | ✓ | ✓ | Disk state snapshot for before/after comparison | — |\n| 2 | Homebrew | macOS | ✓ | ✓ | ✓ | Outdated packages, stale downloads, orphan deps, deprecated formulae | 500MB – 5GB |\n| 3 | Dev caches | all | ✓ | ✓ | ✓ | npm, bun, yarn, pnpm, uv, pip, Playwright, Go, cargo, CocoaPods, Gradle, Maven, Dart/Flutter, Swift PM, Terraform, asdf, volta, mise, Deno, Bundler, Bazel, and more | 1 – 10GB |\n| 4 | Orphaned app data | macOS | ✓ | | ✓ | Application Support, Containers, dotfiles, Saved State, Crash Reports | 0 – 20GB |\n| 5 | LaunchAgents | macOS | ✓ | | ✓ | Stale or unloaded agents from removed apps | < 100MB |\n| 6 | Xcode & dev tools | macOS | ✓ | | ✓ | DerivedData, Archives, iOS DeviceSupport, Simulators | 1 – 20GB |\n| 7 | Docker | all | ✓ | | ✓ | Unused images/containers, orphaned Docker.app data | 0 – 30GB |\n| 8 | Build artifacts | all | ✓ | report | ✓ | node_modules, .venv, .next, dist, \\_\\_pycache\\_\\_, target, Pods, .build, out, coverage, .nx across repos | 0 – 10GB |\n| 9 | Stale logs | all | ✓ | | ✓ | `~/Library/Logs/` from removed apps, rotated log files | 100MB – 2GB |\n| 10 | Shell config | all | ✓ | | ✓ | Dead PATH entries, aliases to missing binaries, broken sources | report only |\n| 11 | Electron caches | macOS | ✓ | ✓ | ✓ | Slack, Spotify, VS Code, Discord cache bloat | 200MB – 3GB |\n| 12 | Large files | all | ✓ | | ✓ | Leftover .dmg, .pkg, .iso, .zip installers | 0 – 10GB |\n| 13 | Trash | all | ✓ | ✓ | ✓ | `~/.Trash/` contents | varies |\n| 14 | iOS backups | macOS | ✓ | | ✓ | Local iPhone/iPad backups (can be 50-100GB+) | 10 – 100GB |\n| 15 | pipx tools | all | ✓ | | ✓ | Unused CLI tools installed via pipx | 100MB – 2GB |\n\nPlatform column: `all` = runs on macOS/Linux/WSL2. `macOS` = skipped with a \"skipped (macOS only)\" note on Linux/WSL2. Linux adds its own phases (package cache, ~/.cache sweep, journalctl, snap/flatpak, orphaned kernels) documented in the [Platform Support](#platform-support) section. WSL2 adds Windows %TEMP% and %LOCALAPPDATA% bonus phases on top of Linux coverage.\n\n---\n\n## Modes\n\n### Deep\n\nFull 15-phase audit. Use after migrating to a new Mac, or as a periodic deep clean. Covers everything and offers to clean what it finds. Typical recovery on a migrated Mac: **10-50GB**.\n\n### Quick\n\nPhases 1-3, 8, 11, 13 only. Covers Homebrew, dev tool caches, build artifacts (report only), Electron app caches, and Trash. Skips the slower discovery scans. Good for monthly maintenance. Typical recovery: **1-5GB**.\n\n### Audit\n\nAll 15 phases, but **never offers to remove anything**. Pure report — shows what's reclaimable, where space is going, and what might be stale. Use when you want visibility without making changes.\n\n### Update\n\nSeparate from cleanup entirely. As of v1.6 a single OS-aware shell orchestrator serves macOS, Linux, and WSL2: it discovers outdated tools, plans the upgrade order with cross-manager risk flags, and presents one approval gate instead of per-category Y/N fatigue. Four sub-modes:\n\n| Sub-mode | What it does |\n|----------|--------------|\n| `update audit` | Scan everything — show what's outdated, no changes |\n| `update skills` | Git-pull AI skills in `~/.claude/skills/` + `~/.codex/skills/`, and refresh outdated Claude Code plugins |\n| `update packages` | macOS: brew, mas, macOS updates. Linux: snap, flatpak (apt/dnf/pacman as manual sudo steps). All: npm, pipx, gems, uv, bun |\n| `update all` | Skills + plugins first, then packages — full sweep, single approval gate |\n\nOn macOS the flow flags cross-manager risks before you approve (e.g. `brew:node` upgrade ⇒ npm globals may need rebuild; `brew:openssl` upgrade ⇒ ruby native gems like `nokogiri` need recompile; system Ruby 2.x ⇒ `gem update` auto-uses `--user-install`), and post-flight runs `brew doctor` + PATH-shadow re-check. On Linux/WSL2 the same gate surfaces apt/dnf/pacman upgrades as **manual `sudo …` steps** (never auto-run — upkeep never uses sudo) while snap, flatpak, and the language managers are auto-applied; WSL2 Windows managers are audit-only.\n\n**Claude Code plugins (v1.7).** upkeep flags only the plugins that are genuinely behind — it compares each plugin's active version in `installed_plugins.json` against the version its marketplace declares, so you no longer get told to \"update all N plugins\" on every run. By default it reads the marketplace clone already on disk; add **`--fresh`** (e.g. `/upkeep:update all --fresh`) to git-fetch each marketplace first and compare against its upstream manifest, catching updates the local clone hasn't pulled yet. During apply it refreshes the marketplace git source (`--ff-only`, fenced to `~/.claude/plugins/marketplaces/*`). The actual cache reinstall has **no headless path** — completing a plugin update needs `/plugin update <name>` plus a Claude Code relaunch — so upkeep hands that off as a manual step with the exact commands, and never rewrites Claude Code's plugin state itself.\n\n**Risk handling (v1.7).** When the plan carries flagged compatibility risks, the gate offers **\"apply all except flagged risks\"** (drops the categories a warning implicates) as the default, alongside an \"apply *including* flagged risks\" path guarded by an explicit confirmation. A standing disclaimer reminds you the risk matrix is **not exhaustive** — an unflagged upgrade can still break something.\n\nNothing applies without your approval. `softwareupdate` (macOS system updates) always gets an extra restart warning, even under \"Apply all\".\n\n---\n\n## Updating\n\nSay **\"update everything\"** in Claude Code, or run `/upkeep:update`.\n\nFour sub-modes:\n\n| Mode | What it does |\n|------|--------------|\n| `update audit` | Check what's outdated across skills, plugins + packages — no changes |\n| `update skills` | Git-pull all AI skills (upkeep, gstack, any others in `~/.claude/skills/`) + refresh outdated Claude Code plugins |\n| `update packages` | Upgrade brew, npm globals, pipx, gems, rustup, bun, deno, mise, uv, mas, macOS updates |\n| `update all` | Skills + plugins first, then packages — full sweep |\n\nEverything is confirmation-gated. Nothing applies without your approval. Destructive or disruptive operations (macOS system updates, brew toolchain changes) get extra warnings.\n\nAll three platforms (macOS, Linux, WSL2) use the same single-shot, single-gate flow as of v1.6 — see the version notes below.\n\n### v1.3: AI update advisor (macOS)\n\nDiscovery and apply remained hardcoded for security (see v1.2 hardening), but v1.3 wraps three reasoning agents around them so the approval gate and final report tell you *what each upgrade means*, not just *what's outdated*:\n\n- **`changelog-reader`** — for every major bump and every cross-manager-flagged upgrade, fetches official release notes from an allowlisted set of upstream hosts (`github.com`, `nodejs.org`, `python.org`, `rubygems.org`, etc.) and returns a severity grade (`low|medium|high|critical`), a one-sentence summary, the concrete breaking changes, and any CVEs explicitly fixed. The grep-CHANGELOG approach from v1.2 is replaced.\n- **`project-impact`** — walks your workspace roots (`~/workspace`, `~/Github`, `~/Projects`, `~/src`, `~/code`, `~/dev` — whichever exist) and reads per-language manifests (`package.json`, `Gemfile`, `pyproject.toml`, `go.mod`, `Cargo.toml`, `.tool-versions`, `Dockerfile`) to surface \"node 26 will affect N of your projects\" callouts. Reads only — never executes manifest scripts.\n- **`failure-diagnoser`** — fires only when something actually failed during apply. Reads the relevant log excerpt and identifies the root cause from known patterns (missing build deps, version constraints, permission errors, broken native modules after brew major bump), then proposes 1–3 ranked fix options. **All suggested commands are surfaced as text only and are never auto-executed** — you copy them into your shell yourself.\n\nThe advisor is layered on top of the v1.2 hardcoded-dispatcher contract: none of these agents author shell commands the orchestrator will execute, all their outputs pass through allowlist-projection sanitization, and the diagnoser's `command` field has an additional denylist scrub for destructive patterns (`rm -rf`, `--force`, `chmod 777`, `curl | sh`, etc.).\n\n### v1.4: discovery & synthesis to bash (macOS)\n\nThe v1.3 advisor was useful but the four parallel scout agents + the compatibility synthesizer agent added ~2 minutes of LLM overhead before the approval gate, on top of any actual `brew update` time. v1.4 replaces that mechanical work with two bundled scripts:\n\n- **`scripts/discover.sh`** runs the four discovery sweeps (skills, native, language, shadow) concurrently in pure bash + `jq`, including `brew update` so the outdated list is accurate before approval. ~15s wall time vs the agent flow's ~90s+.\n- **`scripts/synthesize.sh`** does the semver classification, compat-matrix edge materialisation, and ETA bake-ins in deterministic `jq`. ~300ms vs the agent's 5–10s.\n\nSame security invariants as v1.3 (hardcoded dispatcher, allowlisted tool ids, discovery sanitization, trust-on-first-use) — only the implementation language changed.\n\n### v1.5: single-shot orchestrator (macOS)\n\nv1.4 cut the *script* time but the SKILL.md was still structured as 5+ LLM turns (discover → synthesize → enrich → apply → post-flight → diagnose → report), each turn paying ~3–5s of round-trip overhead. v1.5 collapses the whole macOS flow into **two LLM turns**:\n\n- **`scripts/update.sh plan <mode>`** composes `discover.sh` + `synthesize.sh`, writes the full plan to a temp file in `~/.claude/data/`, and emits a compact JSON envelope for SKILL.md to render the approval gate.\n- **`scripts/update.sh apply <plan-file>`** reads the plan, runs the hardcoded dispatcher, post-flight (`brew doctor`, PATH-shadow re-check, resolution re-check), the new pattern-table failure diagnoser, history write, and emits a report JSON for SKILL.md to render.\n\n`brew update` is TTL-cached against `~/Library/Caches/Homebrew/api/formula.jws.json` mtime (1h default; `UPKEEP_NO_CACHE=1` to bypass), removing the 8–14s long pole on warm runs. The v1.3 LLM `failure-diagnoser` agent is replaced by **`scripts/diagnose.sh`** — a hand-authored pattern table covering ~80% of real failures (Ruby version, native build deps, EACCES, broken pipx venv, `dyld` load, arch mismatch, solver constraints, brew post-install). ~100ms vs the agent's 10–20s, deterministic, easier to extend.\n\nThe two enrichment agents (`changelog-reader`, `project-impact`) from v1.3 are now opt-in via `--advisor` and run *after* the gate in parallel with apply, so they never block the user reaching the approval gate.\n\nUser-perceived pre-gate latency on a real machine drops from ~60–90s (v1.4 with multi-turn skill overhead) to **~5s on warm cache** — 12–18× speedup. All v1.2/v1.3/v1.4 security invariants preserved verbatim, plus three new ones in v1.5: canonical-path containment for skill repo pulls (rejects `../` and symlinks), TOCTOU-safe plan-file write via `mktemp -d` + atomic rename, and TSV-row validation in `diagnose.sh`.\n\n### v1.6: Linux/WSL2 fast-path port\n\nv1.5 only sped up macOS — Linux and WSL2 still ran the slow v1.0 sequential flow. v1.6 ports the single-shot orchestrator to **all three platforms**: `discover.sh` detects the OS (`uname` + `/etc/os-release`) and `update.sh`/`synthesize.sh` branch internally, so there is now one fast path and one approval-gate UX everywhere. The legacy sequential flow (old SKILL.md Steps 1–6, ~330 lines) is retired.\n\nThe defining constraint is the **sudo boundary**: apt/dnf/pacman require root and upkeep never runs sudo, so those upgrades are surfaced as **manual `sudo …` steps** and are deliberately excluded from the apply dispatcher's allowlist (the allowlist guard is the hard guarantee a malformed plan can't smuggle a root command into execution). User-scoped managers — language tools, `snap refresh`, `flatpak update -y`, skills git pulls — are auto-applied. On WSL2, Windows package managers (winget/scoop/choco) are detected and surfaced **audit-only**. `scripts/diagnose.sh` gains four Linux failure patterns (dpkg/apt lock, dnf metadata/conflict, snap change-in-progress, flatpak runtime-missing).\n\nLinux/WSL2 paths are contract-tested via a `UPKEEP_OS_OVERRIDE` / `UPKEEP_PKG_MGR_OVERRIDE` seam plus PATH-stubbed fake package managers (the dev box is macOS); a pre-merge adversarial parser review fixed four real bugs in the package-manager output parsers. Live validation against a real Linux/WSL2 box is the remaining follow-up.\n\n### v1.7: Claude Code plugin updates + risk-exclusion gate\n\nThrough v1.6, `/upkeep:update` listed **every** installed plugin as a `/plugin update` manual step on every run — whether or not it was behind. v1.7 makes plugins first-class: discovery reads `installed_plugins.json` and compares each `<plugin>@<marketplace>`'s active version against the version its marketplace's `marketplace.json` declares (`sort -V`), so **only genuinely-outdated plugins are flagged**. Add **`--fresh`** (e.g. `/upkeep:update all --fresh`) to git-fetch each marketplace first and compare against its upstream manifest, catching updates the local clone hasn't pulled yet. Apply refreshes the marketplace git source `--ff-only`, canonical-path-fenced to `~/.claude/plugins/marketplaces/*`. The cache reinstall has **no headless path** — `/plugin update <name>` is interactive-only and needs a Claude Code relaunch to take effect — so upkeep hands that off as a manual step and never rewrites `installed_plugins.json` itself.\n\nThe approval gate gains an **\"apply all except flagged risks\"** default when the plan carries compatibility warnings (it drops the categories a warning implicates), alongside an \"apply *including* flagged risks\" path guarded by an explicit confirmation, plus a standing disclaimer that the compat matrix is **not exhaustive** — an unflagged upgrade can still break something. New test seams `UPKEEP_INSTALLED_PLUGINS` / `UPKEEP_PLUGIN_MARKETPLACES`; 30 new tests bring the suite to 103.\n\n### v1.7.1: automated releases\n\nv1.7.0 merged to `main` but sat unreleased — the GitHub release lagged a version behind because tagging was a manual `git tag` + `gh release create` step with nothing enforcing it. `.github/workflows/release.yml` now fires on any push to `main` that changes `VERSION`: it creates the `vX.Y.Z` tag and publishes a release whose notes are the matching `## [X.Y.Z]` section of the changelog. It is idempotent (no-ops if the tag already exists, so a one-off manual tag is never clobbered) and ships a placeholder body rather than failing on a missing changelog section. No skill, script, or user-facing behavior changed.\n\nWhen you run `/upkeep` it checks once per day whether a newer version is available. Both install layouts are supported: git-cloned skills compare `HEAD` against `origin/main`, and plugin-managed installs compare the installed `plugin.json` against the marketplace clone. If the check finds an update, you'll be asked whether to update first or continue with the current version. The narrow entrypoints (`/upkeep:audit`, `/upkeep:cleandeep`, `/upkeep:cleanquick`) skip the check — re-enter via `/upkeep` if you want the prompt.\n\nTo disable the daily check entirely: `export UPKEEP_SKIP_UPDATE_CHECK=1`\n\n---\n\n## Safety\n\n| Rule | Detail |\n|------|--------|\n| Installed apps | Never removes data for apps currently in `/Applications/` |\n| Claude data | Never touches `~/.claude/` or `~/Library/Application Support/Claude/` |\n| Apple system dirs | Never removes `com.apple.*` directories |\n| Keychains & Prefs | Never touches `~/Library/Keychains/` or `~/Library/Preferences/` |\n| Size reporting | Always reports sizes before any removal |\n| LaunchAgents | Always unloads before deleting plist files |\n| Approval required | Always asks before removing brew packages, LaunchAgents, or ambiguous items |\n| No sudo | Never runs sudo — surfaces exact commands for you to run manually |\n| iOS backups | Explicit warning about backup implications before removal |\n| Docker images | Two-tier options: safe prune vs aggressive prune with clear warnings |\n\n---\n\n## Privacy\n\n- **Fully local** — no network calls, no telemetry, no data collection\n- All operations run on your machine using standard system commands (rm, brew, apt/dnf/pacman, launchctl, systemctl, etc.)\n- The skill only reads filesystem metadata (directory sizes, file lists) and presents findings to you\n- Nothing is removed without explicit approval\n\n### Privacy Policy\n\n**Effective date:** April 14, 2026\n\nupkeep is a Claude Code plugin that runs entirely on your local machine. This policy explains what data the plugin accesses and how it is handled.\n\n**Data collection:** None. upkeep makes zero network requests. No telemetry, analytics, crash reports, or usage data are collected or transmitted.\n\n**Data access:** The plugin reads filesystem metadata (directory names, file sizes, modification dates) in standard system locations (`~/Library/` and `/Applications/` on macOS; `~/.cache/` and package manager metadata on Linux/WSL2; and project workspace directories on all platforms). This metadata is used solely to identify cleanup candidates and calculate reclaimable disk space. File contents are never read.\n\n**Data storage:** upkeep stores no data. It produces no log files, databases, or caches of its own. All findings are presented in the Claude Code conversation and exist only in that session.\n\n**Data deletion:** When upkeep removes files (with your explicit approval), it uses standard system commands (`rm`, `brew cleanup` on macOS, `apt/dnf/pacman autoremove` on Linux, `launchctl` for macOS agents). No copies are made. Deleted files go to Trash where applicable, or are permanently removed where noted.\n\n**Third parties:** No data is shared with Anthropic, the plugin author, or any third party. The plugin has no server component.\n\n**Changes:** Updates to this policy will be noted in the repository's commit history. The effective date above reflects the latest revision.\n\n**Contact:** For questions about this policy, open an issue on [GitHub](https://github.com/KyleNesium/upkeep/issues).\n\n---\n\n## Architecture\n\n```\nupkeep/\n├── .claude-plugin/\n│   └── marketplace.json           # Marketplace manifest\n├── .github/\n│   ├── ISSUE_TEMPLATE/\n│   └── PULL_REQUEST_TEMPLATE.md\n├── upkeep/\n│   ├── .claude-plugin/\n│   │   └── plugin.json            # Plugin metadata\n│   └── skills/\n│       ├── upkeep/\n│       │   ├── SKILL.md           # /upkeep — mode selector + all 15 phases\n│       │   └── reference/\n│       │       ├── dev-tool-caches.md      # Cache paths by tool\n│       │       ├── apple-system-dirs.md    # Protected system directories\n│       │       └── known-cli-dotdirs.md    # CLI tool dotdir ownership\n│       ├── audit/\n│       │   └── SKILL.md           # /upkeep:audit — report-only scan (all 15 phases)\n│       ├── cleandeep/\n│       │   └── SKILL.md           # /upkeep:cleandeep — full 15-phase cleanup\n│       ├── cleanquick/\n│       │   └── SKILL.md           # /upkeep:cleanquick — fast sweep (phases 1-3, 8, 11, 13)\n│       └── update/\n│           └── SKILL.md           # /upkeep:update — update skills + package managers\n├── CHANGELOG.md\n├── CONTRIBUTING.md\n├── LICENSE\n├── README.md\n└── SECURITY.md\n```\n\nEach skill is a `SKILL.md` — a structured prompt that Claude Code follows when invoked. Sub-skills (`audit`, `cleandeep`, `cleanquick`, `update`) are direct-invocation shortcuts; `/upkeep` is the mode-selector entry point that routes to the same logic. Reference files provide lookup tables for cache locations, protected directories, and CLI tool ownership. No runtime dependencies, no binaries, no build step.\n\n---\n\n## Contributing\n\n1. Fork the repo\n2. Edit the relevant `SKILL.md` — the main skill at `upkeep/skills/upkeep/SKILL.md`, or a sub-skill (`audit/`, `cleandeep/`, `cleanquick/`, `update/`)\n3. Test by running the affected command in Claude Code pointed at your fork\n4. Open a PR with a description of what you changed and why\n\nIdeas for contributions:\n- New cleanup categories (e.g., Time Machine local snapshots, Rosetta 2 cache)\n- Smarter orphan detection heuristics\n- Platform support beyond macOS\n\n---\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for release history.\n\n---\n\n## Security\n\nupkeep runs locally and modifies your filesystem. See [SECURITY.md](SECURITY.md) for the full security model, guarantees, and vulnerability reporting process.\n\n---\n\n## Test Coverage\n\n**207 tests** across 2 automated test files (99 `clean` + 108 `update`). Run with\n`bash tests/test-clean-skill.sh` and `bash tests/test-update-skill.sh` (both\npass under `/bin/bash`, macOS 3.2.57). The `clean` suite includes a pre-merge\nsecurity-regression set (shell-config command-injection, future-dated manifest\nTTL bypass, quoted-path handling, and symlink-target deletion refusal) from\nadversarial reviews; the `update` suite covers compat bump-gating and the\nfail-safe disk-refuse guard.\n\nAs of v1.8 the cleanup skills (`audit`/`cleanquick`/`cleandeep`) are no longer\nprompt-orchestrated — they are thin two-turn wrappers over a shared `clean.sh`\nengine (`upkeep/skills/upkeep/scripts/{clean.sh,clean-validate.sh,lib/common.sh}`),\nthe same single-shot architecture `update` uses. The destructive path is moved\nout of LLM prose into a tested, hardcoded path-safety validator.\n\n### `clean.sh` cleanup engine (v1.8) — `tests/test-clean-skill.sh`\n\n| Area | Tests | What's covered |\n|------|-------|----------------|\n| Path-safety validator | ~11 | Containment under SAFE_ROOTS; PROTECTED denylist override; per-action shape (electron cache-leaf, launchagent single-plist + homebrew.mxcl exclusion, mobilesync per-backup); attack battery (traversal, symlink escape, outside-roots, leading-dash, spaces); >256-char path preserved |\n| Apply re-validation | ~8 | 15-min manifest TTL refusal; TOCTOU vanished / type-swap / size-drift skip; report_only never deleted; `--drop` category exclusion; per-item isolation; manifest consumed |\n| Discover engine | ~13 | audit/quick/deep modes; needs_approval; dual path repr; atomic manifest write + created_at; build-artifacts report_only in quick; Claude cache excluded; xcode, ios_backup, large_file, orphan-app-data (report_only) sections |\n| Non-path actions | ~5 | brew cleanup/autoremove + docker prune discover (PATH-stubbed); apply invokes the real command; pipx tool-name metachar rejection |\n| Linux/WSL2 branch | ~4 | apt cache as sudo manual step; snap disabled-revision → snap_remove; flatpak unused → flatpak_unused; snap_remove identifier validation (via OS-override seam) |\n| Shell-config editor | ~8 | dead source/path-alias lines removed; conditional / `&&` / live lines kept; cp backup created; result passes `zsh -n` (edit + validate + auto-restore) |\n| Eager-discovery prewarm | ~8 | prewarm writes a stable manifest; discover reuses within TTL; `UPKEEP_NO_REUSE` + stale-TTL force a fresh scan; hook no-op when disabled; plugin.json/hooks.json valid |\n| Wrapper + umbrella structure | ~22 | All three wrappers reference the engine, version 1.8.0, no broad `Bash(rm *)`/`Edit` grants; audit never applies; quick/deep two-turn; umbrella routes to clean.sh with the self-update gate preceding cleanup execution |\n| Shared helpers | ~3 | `_sanitize_text` caps free-text/strips control chars (never paths); `_detect_os` override seam |\n\n### `update` skill (v1.1–v1.7) — `tests/test-update-skill.sh`\n\n| Area | Tests | What's covered |\n|------|-------|----------------|\n| Syntax | 4 | `bash -n` across all four `scripts/*.sh` |\n| `update.sh plan` contract | ~6 | Valid JSON, plan_file (non-symlink), audit short-circuit, summary shape |\n| Security invariants | ~4 | No `command`/`preconditions` in plan JSON or stored file, `DATA_DIR` 0700 |\n| Mode filters | 2 | `skills` excludes package groups; `packages` excludes plugin-update steps |\n| `diagnose.sh` validation | ~7 | Tool allowlist, numeric rc, kind, path-traversal / non-absolute / metachar log paths |\n| `diagnose.sh` denylist | ~13 | Destructive-command filter (rm -rf, curl\\|sh, eval, dd of=/dev, …) vs allowed fixes |\n| `update.sh apply` contract | ~5 | Empty/`--drop` CSV safety, report JSON shape, plan-file cleanup |\n| `jq`-missing contract | 2 | `{\"error\":…}` to stdout when `jq` absent |\n| Linux/WSL2 fast path (v1.6) | ~29 | OS-detection seam, `discover_native_linux` shape, dnf exit-100 + Obsoleting-section exclusion, apt from-less line parsing, flatpak app-ID column, WSL2 `winget.exe` detection, sudo boundary (apt → manual steps not ordered_groups), snap/flatpak auto-apply, allowlist rejection of sudo managers, Linux diagnose patterns, macOS regression guard |\n| Plugin updates + risk gate (v1.7) | 30 | Outdated-plugin detection (installed_plugins.json vs marketplace version: outdated flagged, current/absent skipped, real-version-vs-marketplace-\"unknown\" not mis-flagged, version resolved from plugin's own plugin.json when the marketplace omits it incl. `source` subdir + `./` root, `..`/absolute `source` path-traversal blocked, version surfacing, `plugins_outdated` count); `--fresh` upstream-manifest fetch (stale local clone hides update without it, detected with it); `risk_categories` computation (cause→category mapping, empty-when-no-warning, intersection-with-groups guard); plan-contract field/type guards; plan-output surfacing; packages-mode plugin-group exclusion; apply-phase marketplace pull (ff-only success + HEAD advance, path-containment refusal, `--drop=plugins` skip-but-still-hand-off) |\n\n### Skill-level coverage (live invocation)\n\n| Command | What's validated |\n|---------|-----------------|\n| `/upkeep` | Mode selection routing, keyword detection, self-update gate before cleanup execution (v1.8) |\n| `/upkeep:cleandeep` | Two-turn wrapper over `clean.sh discover deep` → gate → apply (v1.8) |\n| `/upkeep:cleanquick` | Two-turn wrapper over `clean.sh discover quick`; build artifacts report-only (v1.8) |\n| `/upkeep:audit` | One-turn `clean.sh discover audit`, zero mutations, never truncates (v1.8) |\n| `/upkeep:update` (Linux / WSL2, v1.6) | Single-shot fast path: OS-aware discovery, snap/flatpak auto-apply, apt/dnf/pacman as manual sudo steps, WSL2 Windows audit-only, single approval gate |\n| `/upkeep:update` (macOS, v1.1) | Parallel scouts, compatibility synthesizer, single approval gate, parallel apply, post-flight (brew doctor, PATH shadow, deprecation aggregator), history-tuned ETA |\n| `/upkeep:update` (security, v1.2) | Hardcoded apply dispatcher (no `eval`), allowlisted tool ids, denylist + length-cap discovery sanitization, exact-match remote URL validation, first-encounter trust gate for third-party skill repos, Discover/Approve/Apply turn separation, atomic + `flock`-serialized history writer |\n| `/upkeep:update` (regression-fix, v1.2.2) | macOS skills apply phase actually pulls trusted git skill repos (was a silent no-op since v1.2.0); skills-scout no longer fetches from untrusted remotes; router Update Mode redirects to `/upkeep:update` instead of duplicating its logic |\n| `/upkeep:update` (advisor, v1.3) | `changelog-reader` allowlisted-host fetches, `project-impact` manifest walk under `$HOME` workspace roots, `failure-diagnoser` per-tool log slicing with text-only fix surfaces, destructive-command denylist on diagnoser output |\n| `/upkeep:update` (fast discovery, v1.4) | `scripts/discover.sh` four-section parallel discovery (~15s vs v1.3's ~90s), `scripts/synthesize.sh` deterministic plan synthesis (~300ms), `brew update` inside discovery for accurate outdated lists, enrichment gating on majors / medium+ compat edges only |\n| `/upkeep:update` (single-shot, v1.5) | `scripts/update.sh plan` + `scripts/update.sh apply` two-turn flow, `brew update` TTL cache (`formula.jws.json` mtime, 1h default), `scripts/diagnose.sh` 8-pattern failure table replacing LLM agent, opt-in `--advisor` post-gate enrichment, canonical-path skill containment, TOCTOU-safe plan-file write (`mktemp -d` + atomic rename), `DATA_DIR` mode 0700, TSV-row validation in `diagnose.sh`, JSON-to-stdout error contract on missing `jq`, bash 3.2 compatibility (no `declare -A`) |\n| `/upkeep:update` (Linux/WSL2 port, v1.6) | One OS-aware orchestrator for macOS/Linux/WSL2, `discover_native_linux` (apt/dnf/pacman audit + snap/flatpak + WSL2 Windows managers), sudo boundary (apt/dnf/pacman excluded from dispatcher allowlist → manual steps only), snap/flatpak auto-apply, `os.type`-branched synthesizer, 4 new Linux diagnose patterns, `UPKEEP_OS_OVERRIDE`/`UPKEEP_PKG_MGR_OVERRIDE` test seam, legacy v1.0 sequential flow retired |\n| `/upkeep:update` (plugins + risk gate, v1.7) | Claude Code plugin outdated detection (`installed_plugins.json` vs marketplace `marketplace.json` version) — only genuinely-behind plugins flagged; marketplace git source refreshed `--ff-only` (canonical-path containment to `~/.claude/plugins/marketplaces/*`, deduped, dirty/detached skipped); cache reinstall handed off as a `/plugin update` + relaunch manual step (no headless path — never rewrites `installed_plugins.json`); \"apply all except flagged risks\" gate option + explicit confirmation for applying flagged risks; standing compatibility disclaimer; `UPKEEP_INSTALLED_PLUGINS`/`UPKEEP_PLUGIN_MARKETPLACES` test seams |\n\n---\n\n## License\n\n[MIT](LICENSE)\n",
  "bytes": 38410,
  "sha": "607a9a1326865107c77474d60e5a4040ec70efac6edd526eb35185cdf7b375ec",
  "repo_slug": "kylenesium/upkeep",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_kylenesium_upkeep_upkeep_e126bc8e/readme"
}