Skip to content
Back to skills

Housekeeping

BSecurity

Generate the weekly "🧹 Housekeeping" digest β€” a render-only report of repo drift that needs a human eye (stale unmerged branches, mislabeled issues, stuck in-progress tickets, epics ready to close, idle PRs, label-coverage gaps). Reports only, never mutates. Use when asked for a "housekeeping report", "repo drift", "what's stale", or to run the weekly sweep by hand.

  • 10 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added September 12, 2026
developmentbashnoderailsgitapi

Works with

  • api

Security analysis

B88/100
  • criticalSends environment variables or credentials to an external URL

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

Scanned September 12, 2026

npx -y skills add FriendlyInternet/nuxt-crouton --skill housekeeping --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Housekeeping?

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

Security grade badge for Housekeeping
[![Security: B β€” Skills Directory](https://www.skillsdirectory.com/api/skills/friendlyinternet-housekeeping/badge)](https://www.skillsdirectory.com/skills/friendlyinternet-housekeeping)

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: housekeeping
layer: method
description: Generate the weekly "🧹 Housekeeping" digest β€” a render-only report of repo drift that needs a human eye (stale unmerged branches, mislabeled issues, stuck in-progress tickets, epics ready to close, idle PRs, label-coverage gaps). Reports only, never mutates. Use when asked for a "housekeeping report", "repo drift", "what's stale", or to run the weekly sweep by hand.
---

# Housekeeping digest

A weekly, **report-only** sweep that catches the drift the event-driven jobs miss β€” the
periodic backstop behind epic #633. It posts one rolling comment to a standing
"🧹 Housekeeping" issue listing everything that needs judgment but is too risky to auto-fix.
**It never deletes a branch, changes a label, or edits an issue** β€” the digest issue body is
the only thing it writes.

## What it reports (each section omitted when empty)

- **🌿 Stale unmerged branches** β€” not contained in `main` (still hold unmerged commits) and
  untouched for >N days. Merged-into-`main` branches are *out of scope* (lossless, not a
  human-eye item).
- **🏷️ Issues missing labels** β€” open issues without a `type:*` and/or a component
  (`pkg:`/`app:`/`worker:`/`poc:`) label.
- **πŸ—οΈ Label coverage** β€” `packages/*`Β·`apps/*`Β·`pocs/*`Β·`workers/*` dirs with no matching
  label declared in `.github/labels.yml` (CLAUDE.md treats a missing label as a build
  failure). Report-only β€” never auto-prune (a destructive label sync strips the label from
  every issue).
- **⏳ Stuck in-progress** β€” `status:in-progress` issues with no activity in >N days.
- **βœ… Epics ready to close** β€” all sub-issues closed but the epic still open, split by the
  action each needs (read from the `status:ready-to-close` / `status:needs-postmortem` label
  the labeller stamps β€” see below): *run the postmortem then close* vs *postmortem done, just
  close*. Any entry whose `epic/<number>-*` branch still carries commits not on `main`
  (`git cherry origin/main origin/<branch>` non-empty) is annotated with a ⚠️ warning line β€”
  the exact failure mode from #1976, where four deliverables went missing because an epic
  branch was never merged (#1982). Report-only: it never blocks closing, it just says so.
- **🚧 Epics with active work but not marked in-progress** β€” a sub-issue is
  `status:in-progress` but the epic isn't, so it reads as "open, not started" (#980). The
  labeller reconciles this (see below); the digest just surfaces it.
- **🧭 In-progress under a non-open-epic parent** β€” `status:in-progress` issues whose parent
  isn't a currently-open epic: a closed epic left with unfinished work, or a parent missing
  the `epic` label (#980).
- **πŸ”€ Idle PRs** β€” open PRs with no activity in >N days.
- **πŸ“š Skill freshness** β€” knowledge skills (the `verified:` provenance contract, #1073/#1091)
  whose citations drifted or whose stamp aged out, via `scripts/skill-freshness.mjs` (fs+git, no
  LLM): a cited path that **vanished** (πŸ”΄), a cited file with commits **newer than the stamp**
  (🟑 possibly-stale), or a stamp **> 90d old** (πŸ”΅ re-verify due). Report-only β€” a re-verified,
  re-stamped skill drops off next run (#1100 WS3). Section dropped if the checker can't run.
- **πŸ—‘ Retired projects** β€” `retired/` dirs with a `.retired.json` stamp, showing age and flagging entries past 60 days for full deletion via `/retire-delete`.

## Pipeline (deterministic β€” no LLM, no secrets)

Mirrors `.claude/skills/epic-digest/`:

```
gather.mjs  β†’ housekeeping.data.json   (git for branches + GitHub REST for issues/PRs/labels)
render.mjs  β†’ housekeeping-<date>.md   (JSON β†’ Markdown, sections dropped when empty)
post-comment.sh                        (upsert the one standing "🧹 Housekeeping" issue)
schedule.mjs                           (cadence-as-data: is today a send day per .github/digests.yml?)
```

### Run it by hand

```bash
GITHUB_TOKEN=$(gh auth token) node .claude/skills/housekeeping/gather.mjs > housekeeping.data.json
node .claude/skills/housekeeping/render.mjs housekeeping.data.json --out-dir .
cp housekeeping-*.md housekeeping.md
GH_TOKEN=$(gh auth token) DIGEST_BODY_FILE=housekeeping.md bash .claude/skills/housekeeping/post-comment.sh
```

Branch data needs a full checkout with remote branches present (`git fetch origin
'+refs/heads/*:refs/remotes/origin/*'`); without it the branch section is skipped, not guessed.

## Scheduling & delivery β€” `.github/digests.yml`

Cadence and delivery channel are **config-as-data**, not workflow plumbing (#637). The
workflow cron fires **daily** (cheap wake-up); `schedule.mjs` reads `digests.yml` and exits
early when today isn't a send day β€” so changing "weekly β†’ daily" or "Wed β†’ Mon" is a one-line
edit, never a `cron` change. Delivery is selection over rails that already exist: `issue`
(this standing-issue path) and `email` (Resend, mirrors `red-team-daily.yml`; degrades to a
green no-op when `RESEND_API_KEY` is unset).

```yaml
housekeeping:
  schedule: daily        # daily | weekly:<dow> ; dow = mon|tue|wed|thu|fri|sat|sun
  deliver: [issue]       # issue | email
  # to: [you@example.com]  # required only when deliver includes email
```

## Lossless branch sweep (separate workflow β€” the only auto-delete)

The digest is **report-only**. The one auto-destructive action lives in its own workflow
(`.github/workflows/cleanup-merged-branches.yml` β†’ `prune-merged-branches.mjs`), split off by
blast radius so a digest bug can never delete a branch. It deletes a branch **only** if it is
provably contained in `main` (`git branch -r --merged origin/main`, 0 commits ahead) β€” lossless
by construction. It also skips any branch with an **open PR**, anything newer than
`BRANCH_MIN_AGE_DAYS` (default 1), and `main`/protected refs.

**Report-only until enabled:** it runs dry-run (lists what it *would* delete) unless `APPLY` is
on β€” set the repo variable `CLEANUP_BRANCHES_APPLY=true`, or run it via `workflow_dispatch` with
`apply: true`. Confirm a dry-run looks right before flipping the variable. Complements
`cleanup-epic-branches.yml` (which deletes `epic/*` on PR merge) by sweeping whatever slipped
past an event trigger.

```bash
GITHUB_TOKEN=$(gh auth token) node .claude/skills/housekeeping/prune-merged-branches.mjs        # dry-run
GITHUB_TOKEN=$(gh auth token) APPLY=true node .claude/skills/housekeeping/prune-merged-branches.mjs  # delete
```

## Epic status labels (separate workflow β€” the only auto-label) (#763 / #980)

The epic sections above read labels the digest does **not** set: `status:ready-to-close`,
`status:needs-postmortem`, and `status:in-progress`. They're reconciled by a separate daily
workflow (`.github/workflows/label-ready-epics.yml` β†’ `scripts/label-ready-epics.mjs`), split
off by blast radius exactly like the branch janitor β€” it's the **only** auto-*label* mutation,
and the digest stays report-only. The labeller **derives** each open epic's status from its
children (mutually-exclusive, at most one):
- all sub-issues closed β†’ `status:ready-to-close` if a `<!-- postmortem:done -->` marker exists
  on a comment (left by the `postmortem` skill), else `status:needs-postmortem`;
- β‰₯1 sub-issue `status:in-progress` β†’ `status:in-progress` (the opening side, #980);
- otherwise (open-but-idle or no children) β†’ none.
It removes any managed label the epic no longer qualifies for. The same labels drive the daily
epic-digest email's bands, so both digests agree. Dry-run by default; set repo var
`LABEL_READY_EPICS_APPLY=true` to write.

## Tuning

- `HOUSEKEEPING_STALE_DAYS` (env, default `14`) β€” the "no activity in N days" threshold.
- `HOUSEKEEPING_ISSUE_TITLE` (env, default `🧹 Housekeeping`) β€” the standing issue title.

The scheduled run lives in `.github/workflows/housekeeping.yml`.

Files in this skill

  • SKILL.md7.7 KB
  • gather.mjs15.9 KB
  • post-comment.sh1.8 KB
  • prune-merged-branches.mjs4.3 KB
  • render.mjs8.7 KB
  • schedule.mjs4.5 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.