Skip to content
Back to skills

Hygiene

ASecurity

Start-of-day fleet sweep before any development: scan every project registered on this machine for open pull requests, merged or abandoned branches and worktrees, a red default branch, and open GitHub security alerts (code scanning, secret scanning, Dependabot), plus invalid Claude Code project settings via `claude doctor`, and the disk debris agent runs leave on the machine (landed scratch clones, build output, stale compiler caches, session and test-fixture temp). Cleans up what is mechanic...

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 19, 2026
testingrustgoshellnodegitapisecurity

Works with

  • claude code
  • api
  • mcp

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned October 19, 2026

npx -y skills add xkazm04/ai-registry --skill hygiene --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Hygiene?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Hygiene
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/xkazm04-hygiene/badge)](https://www.skillsdirectory.com/skills/xkazm04-hygiene)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: hygiene
description: "Start-of-day fleet sweep before any development: scan every project registered on this machine for open pull requests, merged or abandoned branches and worktrees, a red default branch, and open GitHub security alerts (code scanning, secret scanning, Dependabot), plus invalid Claude Code project settings via `claude doctor`, and the disk debris agent runs leave on the machine (landed scratch clones, build output, stale compiler caches, session and test-fixture temp). Cleans up what is mechanical itself, then dispatches one Sonnet worker per project to ship, repair, merge, delete or fix the rest onto main/master, and hands back only what needs a human. Use at the start of a day, when branches, PRs and alerts have piled up across the fleet, or when the disk is filling."
category: ai-native
memory: project
version: 1.2.0
tags: fleet, hygiene, pull-requests, branches, worktrees, security-alerts, ship, dispatch, sonnet-workers, start-of-day
---

# Hygiene

The fleet accumulates debris faster than anyone looks at it: autopilot PRs that
passed CI and were never merged, branches whose content landed by squash weeks ago,
Claude Code worktrees from sessions nobody remembers, a default branch that went red
overnight, security alerts nobody opened. Each item is small. Together they tax every
development session that starts on top of them - a stale checkout, a conflicting PR,
a failing gate that is not yours.

**This skill clears that before the day's work starts.** It is a dispatcher, not a
fixer: an instrument classifies everything, the director applies the mechanical class
itself, and one Sonnet worker per project does the rest in isolated worktrees.

What it is not: `/maintain` reduces code debt inside one repo, `/librarian` maintains
this registry's knowledge, `/ci-triage` diagnoses one red build (workers borrow its
method). Hygiene owns the delivery surface - PRs, branches, worktrees, the default
branch's CI, GitHub's alert queues - and the **debris** dev processes leave in a checkout:
crash dumps, bytecode caches, scratch folders, a dot-directory a tool created at the root
because it did not know where to put its working files.

## Invocation

```
/hygiene                  # full run: scan -> mechanical -> dispatch -> verify -> report
/hygiene scan             # plan only, touches nothing
/hygiene kp,ascent        # full run limited to these project slugs
/hygiene status           # print today's plan.md and applied.jsonl, touch nothing
/hygiene disk             # the disk lane alone: scan, apply mechanical, report (no GitHub, no workers)
/hygiene reflect          # land what the last runs taught into this file and LESSONS.md
```

## Standing authorization and its limits

Operator, 2026-09-15: hygiene merges, deletes, implements and pushes to the default
branches of fleet repos **without asking**. The fleet has one owner; the gates are the
guard, not a confirmation round trip. So never stop mid-run to ask "may I merge #37?".

What the authorization does NOT cover, for the director and every worker:

- `--no-verify`, `*_SKIP_GATE=1`, `LEFTHOOK=0`, `HUSKY=0`, or force-pushing a default
  branch. A gate that blocks is an answer. `--force-with-lease` on a feature branch is fine.
- Discarding uncommitted changes anywhere, ever. Stale WIP goes to the operator list.
- Touching a worktree under `AppData/Roaming/com.personas.desktop/worktrees/`. The
  Personas app created it, tracks it, and finalizes it itself.
- Resolving or dismissing a **secret-scanning** alert. Rotation is a human act.
- Dismissing a code-scanning or Dependabot alert without a concrete, recorded reason.
- Anything the instrument marks `hands-off`. Something live touched it.
- `git push` into systedo-case (its settings deny it on purpose): server-side
  `gh pr merge` only.
- **Shipping commits hygiene did not author**, when the push is not a clean fast-forward
  sync. Operator decision, 2026-10-04 (revises 2026-09-15): unpushed commits on a primary's
  default branch are routinely another session's or another machine's already-committed
  work, and syncing that is exactly what a start-of-day sweep is for - so a clean
  fast-forward push (not diverged, checkout idle, repo does not deny push, default branch
  not a production-deploy target) is `mechanical` (`push-primary`), not a worker action.
  What still goes to the operator as `primary-unpushed`: diverged history (needs a
  merge/rebase judgment call), a live checkout, a push-denied repo, or a production-deploy
  default branch (see below). The dispatch prompt remains the explicit ask for anything a
  worker itself authors - merges, deletions, fixes.

Merging **or pushing** to `master` on ascent or systedo-case **is a production deploy**
(Vercel Git integration). CI green before either is not optional there, and there is no CI
run yet to check for commits that were never pushed - so `push-primary` never fires for
`ascent:master` (`PRODUCTION_DEPLOY_DEFAULT` in `hygiene-scan.mjs`); systedo-case is already
excluded via its push-deny settings. Widen that set only for a repo whose default branch
deploys outside its own CI.

## Never classify anything yourself

`node scripts/hygiene-scan.mjs` is the instrument. It resolves the fleet through
`scripts/lib/projects.mjs`, fetches every checkout, and writes
`$(git rev-parse --git-common-dir)/hygiene/<date>/plan.json` + `plan.md`: per project
the open PRs with check rollups, every local and remote branch classified (`merged`,
`squash-merged`, `patch-on-default`, `open-pr`, `closed-pr`, `no-pr`, `unpushed`,
`merged-pr-then-more-commits`), every worktree with its owner and activity, default
branch CI per workflow, the three security surfaces, and a list of **actions** in four
classes:

| class | who | examples |
|---|---|---|
| `mechanical` | the script's `--apply` | delete branches whose content is on the default branch (tip archived to `refs/hygiene-archive/<date>/` first), remove clean idle Claude Code/temp worktrees, prune missing worktrees, set `origin/HEAD`, fast-forward an idle primary checkout, **push an idle primary's clean fast-forward-able unpushed commits** (`push-primary` - skipped if diverged, live, push-denied, or a production-deploy default branch), **delete untracked junk-named debris** (`*.stackdump`, `__pycache__`, `*.pyc`, `*.orig/.rej/.swp`, `Thumbs.db`, `.DS_Store`, `nul`, editor `~` files) |
| `worker` | one Sonnet per project | ship or repair open PRs, fix a red default branch, triage unmerged branches, fix code-scanning/Dependabot alerts |
| `operator` | the human, in the final report | `debris-candidate` (untracked top-level scratch-looking entries: `tmp*`, `out`, `backup*`, `*.bak/.log/.tmp`, an unknown dot-directory), `tracked-junk` (a junk name that is committed), secret alerts, stale uncommitted WIP, a primary checkout parked on a feature branch, `primary-unpushed` that is diverged/live/push-denied/production-deploy, Personas-app worktree backlog, security posture (scanning not configured) |
| `hands-off` | nobody | anything with reflog, commit or dirty-file activity inside `--live-minutes` (default 120), and PRs updated inside it |

If the plan looks wrong, fix `scripts/hygiene-scan.mjs` - never override a class by
hand in the run. A classification the director overrides once is overridden
differently tomorrow.

Re-verify any change to it against **a project you already know is red and one you know is
green**, before and after. Every wrong version of the 2026-09-21 CI-selection fix made the
table calmer than the truth - an all-green control set would have passed all three.

## Phase 0 - Preflight

1. `gh auth status` must show a logged-in account with repo scope. Its `admin:repo_hook`
   hint on 403/404 responses is noise.
2. `node scripts/run-board.mjs list`, then
   `node scripts/run-board.mjs claim --skill hygiene --source fleet-<date> --run hyg-<date>`.
   A second hygiene run on the same day is a CONTENDED answer (exit 3): stop.
   The claim goes stale without heartbeats, and the worker phase lasts hours: beat it at
   every phase boundary and after each worker report
   (`node scripts/run-board.mjs beat --run hyg-<date> --phase <phase>`).
3. `ListAgents` - note how many peer sessions are busy. Informational only: the
   instrument's liveness check is what gates actions, since session names do not say
   which checkout they are in.

## Phase 1 - Scan

```
node scripts/hygiene-scan.mjs [--only <slugs>]
```

Read `plan.md`. Then **spot-check before trusting it** (a plausible count is not an
assertion): for one `mechanical` row and one `hands-off` row, verify the claim straight
from git (`git rev-list --count origin/<default>..<sha>`, `git -C <wt> log -1 --format=%cr`,
`git -C <wt> reflog -1 --date=relative`). If either disagrees, stop and fix the script.

**Harness settings (Tier 1).** The same scan runs `claude doctor` in every checkout - a
read-only, model-free validation of `.claude/settings*.json` and `.mcp.json` - and files
any finding as an `operator` row, `harness-settings` (settings are the trust surface:
permissions, hooks, MCP servers, so a human decides). The `harness` column reads `clean`,
a finding count, or `not-run`, and `not-run` is also a PROBLEM line: the tool exits 0
even when it reports invalid settings, so the instrument reads the "Invalid settings"
block and never the exit code, and a run that produced no report is not a clean one.
Know what it cannot see: it is a schema check, so unknown keys, unknown tool names,
allow rules made unreachable by a deny, and a missing MCP binary all pass it, and
malformed JSON hides every other error in that file - fix the JSON and re-scan. The
judgment tier (`claude -p "/doctor" --permission-mode plan`, per project, weekly or after
a harness upgrade) is NOT part of this sweep: its findings are a source to verify, per
`librarian/sources/2026-10-08-claude-code-doctor.md`, never cleanups to apply.
`--no-harness` skips Tier 1 on a machine without `claude` on PATH.

Then the disk lane: `node scripts/hygiene-disk.mjs` (minutes: it walks the scratch root and
temp in full, because a liveness gate on a deletion has to see every file). Spot-check one
`remove-scratch-checkout` row from git (`git -C <dir> rev-list HEAD --not --remotes`, `git -C <dir>
status --porcelain`) before trusting the class.

`/hygiene scan` ends here: print the table and the operator list.

## Debris - what counts and what never does

The scan looks only at **untracked, not-ignored** entries of each primary checkout
(`git ls-files --others --exclude-standard --directory`: an untracked directory is one row).
Ignored paths are intentional; ordinary untracked files are somebody's WIP and are never
listed. Three tiers: *junk* names are deleted by `--apply` after re-checking that the entry
is still untracked, idle past `--live-minutes`, inside the repo and free of symlinks or
junctions (a recursive delete that follows one wiped a primary's `node_modules`); *candidate*
names go to the operator with size and age, because a script cannot know whether `out/` or
`.personas-web/` holds the only copy of something; *tracked-junk* is a content change and is
reported only. Debris deletion is **not recoverable** (no archive ref exists for an untracked
file) - that is why the junk list stays short and name-based. Widen it only with a LESSONS
entry showing the name was never content. The rule for the operator triage: ask for a decision
and, once made, either delete it, add it to `.gitignore` if the tool keeps recreating it, or
fix the process that wrote it there.

## Disk lane - what runs leave on the machine

A checkout's `git status` never shows where the bytes went. On 2026-10-15 one curator night
had left 337 folders in the scratch root in thirteen hours, about 200 GB: a clone of a fleet
project per claim, each with its own install, `.next` and cargo `target`, because the run
removed its registry worktree on landing and never the project clone. Beside it sat a 62 GB
cargo `incremental/` cache (one entry per feature set ever built), 36 GB of Claude session
temp and about 100,000 test-fixture temp dirs, one suite alone 19,147. `scripts/hygiene-disk.mjs`
is the instrument for that surface; it writes `disk.json`/`disk.md` beside `plan.json`.

| area | mechanical | operator |
|---|---|---|
| scratch root (`<drive>/t`) | a checkout idle past `--idle-hours` whose HEAD is landed (worktree: on another ref of its repo; clone: every commit on its remotes, or each local commit's patch-id in its fleet home) and whose dirty paths are noise only (`next-env.d.ts`, `*.generated.ts`, untracked husky shims, a whitespace or line-ending diff); a build-output folder (`*target*`, `tgt*`, `cargo-target-*`) once idle | a checkout with WIP or unlanded commits; any `hyg-*` entry (the dead-worker rule); an unknown folder after `--temp-days` |
| build caches | cargo `incremental/` crate dirs in fleet checkouts untouched past `--idle-hours`; an out-of-tree build-output sibling of a checkout after `--temp-days` | the same sibling before `--temp-days` |
| temp | a user-temp entry or a Claude session dir untouched `--temp-days` (7), or 48 h and 100 MB | `temp-leak`: a name family with hundreds of members - a suite that never removes its `mkdtemp` dirs; fix it in that repo |
| models | - | a local model store over 1 GB (operator 2026-10-15: none wanted on this machine) |

Everything inside `--idle-hours` is `hands-off` (`.git/index` is not activity: scanners refresh
it). Deletion goes through `scripts/lib/safe-remove.mjs`, which removes a symlink or junction as
a link and never enters it, and clears the read-only bit git puts on objects. `--apply`
re-measures and re-classifies each row immediately before deleting and requires the same answer
(the same HEAD for a checkout). A run that makes scratch entries cleans its own with
`node scripts/hygiene-disk.mjs --reap <dir>...` at the end of each task: the same rules at idle
floor 0, kept entries printed with the reason. curator's night does this per claim.

`/hygiene disk` is this lane alone: the Phase 0 claim, `hygiene-disk.mjs`, the spot-check,
`--apply`, and the report's **Disk** section. No GitHub calls, no workers.

## Phase 2 - Mechanical

```
node scripts/hygiene-scan.mjs --apply [--only <slugs>]
```

Every action re-verifies its precondition immediately before acting (tip unchanged,
still idle, still clean, not now the base of a PR) and logs DONE or SKIP to
`applied.jsonl`. SKIP is a normal answer. Read the SKIP reasons: a pattern
(e.g. every remote delete rejected by a hook) is a LESSONS entry.

Recovery, if ever needed: `git for-each-ref refs/hygiene-archive/` in the checkout, then
`git branch <name> <sha>` or `git push origin <sha>:refs/heads/<name>`.

Then `node scripts/hygiene-disk.mjs --apply`; it appends to `disk-applied.jsonl`. Disk deletions
are not recoverable, which is why a checkout goes only when its commits are provably elsewhere.

## Phase 3 - Dispatch

One worker per project that has at least one `worker` action; each one touches only its
own repository. **Run at most 6 at a time**, largest plans first (kp, tracklight, ascent
before the one-action projects), and start the next as each finishes. Thirteen concurrent
workers on 2026-09-15 tripped GitHub's secondary (abuse-detection) rate limit - 403s on
merges and branch deletes while `gh api rate_limit` still read 5000/5000 - and doubled
scan times in the middle of the run.

```
node scripts/hygiene-scan.mjs --brief <slug> > <scratchpad>/brief-<slug>.md

Agent({
  description: "hygiene <slug>",
  subagent_type: "general-purpose",
  model: "sonnet",
  prompt: "Read <scratchpad>/brief-<slug>.md in full and carry it out. It is your whole task."
})
```

`--brief` assembles the prompt from files - `references/worker-brief.md` verbatim, the
project's slice of `plan.json` (`worker` actions in full, `operator` rows as context so the
worker does not "fix" them, `hands-off` targets so it does not touch them) and its
`references/fleet-quirks.md` sections. Never retype or paraphrase a block: a hand-built
extraction on 2026-09-15 silently dropped kp's quirks section through shell quoting.
Handing the worker a file path keeps a 20 KB kp block out of the director's own context.

Order inside a project is the worker's job (brief, "Order of work"). Across projects
there is none.

Record every dispatch in `<plan dir>/dispatch.jsonl` - one line per worker with `slug`,
the agent id from the spawn result, and the dispatch time. It is the only way back to a
worker that dies mid-run - **inside this session only**: a new session's `SendMessage` to
that id answers "No transcript found" (2026-09-26, reaching the 2026-09-24 ascent worker).

Save each worker's report to `<plan dir>/reports/<slug>.md` the moment it arrives, before
starting the next worker. A director that dies otherwise takes every report with it: the
2026-09-24 run dispatched ten workers and left no report, no verify and no record of what
they shipped.

A project whose previous-run worker died mid-item (its `C:/t/hyg-<slug>-*` worktrees are
still there, and the scan lists them as stale WIP) gets no worker until the owner clears
them. Say so in `dispatch.jsonl` and in the report.

While workers run, do not touch their repositories. Wait for the notifications. A
notification whose text is not the fenced JSON report means the worker is still going
(it ended a turn waiting on CI) - never count it as done.

**A worker that fails mid-run** (an API session limit killed three at once on 2026-09-15):
after the limit resets, snapshot its repo first (`C:/t/hyg*` worktrees and their state,
`gh pr list --state all`, default-branch runs), then `SendMessage` to its agent id - that
resumes it from its own transcript, so it still knows what it pushed. Order it to re-read
live state before acting, finish only the in-flight item, start nothing new, clean up, and
report. Never dispatch a fresh worker onto a repo a dead one was halfway through.

## Phase 4 - Verify

A worker's report is a claim. For each project:

0. **Start from what actually landed, not from the report:**
   `git -C <path> fetch origin` then
   `git -C <path> log --reverse --format='%h %an | %s' <plan defaultSha>..origin/<default>`.
   Every commit there must be accounted for by a merged PR, a report's `pushes[].commits`,
   or a mechanical `push-primary` DONE row in `applied.jsonl`; one that is not is either a
   sibling session's work (check its author and time) or something the worker forgot to
   report (tracklight 2026-09-15: three store fixes hidden behind a push's tip SHA). Steps
   2 and 5 run against this list - `push-primary` commits included, since they were not
   authored by this run and still need the same post-push CI check.
1. Every PR the report says merged: `gh pr view <n> -R <repo> --json state,mergedAt`.
2. Every push to a default branch: `gh run list -R <repo> --branch <default> --limit 5`
   shows the run for that SHA completed green (or still in progress - then
   `gh run watch <id> --exit-status`). A red default branch after a hygiene push is an
   outage the run caused: dispatch a repair worker for that project before reporting.
3. The primary checkout is untouched: `git -C <path> branch --show-current` matches, and the
   **sorted set** of `git --no-optional-locks status --porcelain` paths equals `plan.json`'s
   `primary.dirtyPaths` (except an `ff-primary` or `delete-debris` DONE row, or a live primary whose own
   session moved on - then name the differing paths and judge whether a worker could have
   written them). A count is not enough: a worker that edits the wrong tree and reverts a
   different file leaves the count intact. Also read `git -C <path> config --local --list`
   and `.git/info/exclude`: a test fixture run by a pre-push hook from a worktree writes
   into the real repository's config (2026-09-19: `core.bare`, `core.worktree`, a fixture
   `user.name`, a replaced exclude file), and none of that shows in branch or dirty paths.
4. The worker left no worktrees or helper branches behind:
   `git -C <path> worktree list | grep hyg` and `git -C <path> branch --list 'hyg*'` are
   empty (except the head branch of a PR the report lists as left open). Attribute any
   `C:/t/hyg*` directory by `git -C <dir> remote get-url origin`, never by its name.
5. **Every worker commit that touches tests is checked for a weakened gate** - the likeliest
   way "make CI green" goes wrong. For each pushed SHA:
   `git show <sha> -- '*test*' '*spec*' | grep -cE '^-.*(expect\(|assert)'` versus the `^+`
   count, and `grep -cE '^\+.*(\.skip|\.only|\.fixme|#\[ignore\]|pytest\.mark\.skip)'`.
   A new skip marker fails verification. A net loss of assertions is read line by line:
   acceptable only when the removed assertions pressed UI or APIs that no longer exist
   (gravity 2026-09-15: a Board-tab pair deleted with the tab itself), never when the
   behaviour still exists.

Then re-scan (`node scripts/hygiene-scan.mjs --out <plan dir>/after`) and compare the
table rows before and after.

## Phase 5 - Report and release

Write `<plan dir>/report.md` and print it:

- **Shipped** - PRs merged, commits pushed, alerts fixed, each with the default-branch CI result.
- **Removed** - branch and worktree counts per project (archive refs noted).
- **Disk** - GB freed per area and free space after; every `temp-leak` family (repo and suite
  to fix) and scratch checkout kept for WIP.
- **Needs you** - every `operator` row plus every worker `operator` item, one line each,
  most urgent first (secret alerts, then red CI the workers could not fix, then WIP).
- **Left alone** - hands-off counts and why.
- **Before / after** table.

Append a `LESSONS.md` entry when the run taught something (a misclassification, a
repo quirk, a worker failure mode). Add repo quirks to `references/fleet-quirks.md`.
`node scripts/run-board.mjs release --run hyg-<date>`.

## Tuning

- `--live-minutes` (120) - raise it on a day with many live autopilot sessions.
- `--idle-hours` (24) - the floor before a clean, merged worktree may be removed.
- A project that should never be touched: leave it out of `projects.json` on this
  machine, or pass `--only`.

Files in this skill

  • LESSONS.md44 KB
  • SKILL.md21.7 KB
  • references/fleet-quirks.md14.8 KB
  • references/worker-brief.md16.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.