| name | maintenance-sweep |
| description | When the directive is "status check," "what's new across my projects," or "sweep maintenance" -- run a per-project, read-mostly, non-PR sweep. For each project under the owner-prefixed layout, gather stars/forks, open issues/PRs split into mine vs community vs bots, package downloads, release-due (unreleased conventional commits), a test run, a stale-CLAUDE.md flag, and branch-cleanup candidates. Emit a fresh per-project report every sweep; NEVER cache status back into a workspace/manager CLAUDE.md. |
Maintenance sweep
A per-project health check that reads each active project's durable
state -- GitHub, package registries, git history, the test command --
and reconstitutes a fresh report every time. It is read-and-report,
not work-an-issue. The output is a per-project report, never a PR
and never a status cache written upward into a manager CLAUDE.md.
This is the Code Manager's "what's going on across my projects?"
ritual realized as a skill: map, not model. The sweep reconstitutes
state on demand; it holds none.
When to use
- The directive is a status check: "what's new," "status update,"
"sweep the projects," "are any of these due for a release?"
- At the start of a check-in ritual, to surface what changed since
the last sit-down before deciding where to dispatch work.
When NOT to use
- You actually want to work an issue. That's dispatcher -> runner,
not a sweep. The sweep tells you that a project has a community PR
waiting; it does not review or merge it.
- You want to write a fix or a CLAUDE.md edit. The sweep is
read-only over project contents. Any change it proposes is proposed
-- filed as an issue or handed to a worker (see Output discipline).
- A single project, deep. For one project's full context, read its
CLAUDE.md directly; the sweep is the cross-project skim.
Layout: owner-prefixed enumeration
Projects live at ~/Code/<host>/<owner>/<repo> (today <host> is
always github.com). Enumerate the active set with:
ls -d ~/Code/github.com/*/*
Each match is a project root. A project is in scope if it has a
.git/ (and typically a CLAUDE.md). Derive <owner>/<repo> from
the path for the gh calls below -- the path is the map.
The seven axes
Run these per project. Each axis degrades gracefully: if an axis
can't run (no remote, not published, no test command), record -
and move on -- never block the sweep on one missing signal.
(a) Stars / forks
gh api repos/<owner>/<repo> --jq '{stars: .stargazers_count, forks: .forks_count}'
(b) Open issues / PRs -- mine vs community vs bots
Split every count three ways. Bots are excluded by matching [bot]$
on the author login; "mine" is the owner; "community" is everyone
else who is not a bot.
gh api 'repos/<owner>/<repo>/issues?state=open&creator=<owner>&per_page=100' \
--jq '[.[] | select(.pull_request | not)] | length'
gh api 'repos/<owner>/<repo>/issues?state=open&per_page=100' \
--jq '[.[] | select(.user.login != "<owner>" and (.user.login | test("\\[bot\\]$") | not) and (.pull_request | not))] | length'
gh api 'repos/<owner>/<repo>/pulls?state=open&per_page=100' \
--jq '[.[] | select(.user.login != "<owner>" and (.user.login | test("\\[bot\\]$") | not))] | length'
Community issues and community PRs are the highest-signal axis -- they
are humans, not bots, touching the project. Surface them first in the
report.
(c) Package downloads (where published)
Only for projects that actually publish a package; skip with -
otherwise.
-
Elixir / Hex:
gh api -X GET https://hex.pm/api/packages/<name> \
--jq '{recent: .downloads.recent, all: .downloads.all}'
-
Rust / crates.io: use the cratesio-mcp server's
get_crate_info for each published crate (recent + all-time
downloads). Fall back to
gh api -X GET https://crates.io/api/v1/crates/<name> --jq '.crate.downloads'
if the MCP server isn't available.
(d) Release due? -- unreleased conventional commits
A project is "release due" when there are user-facing conventional
commits (feat:, fix:, perf:) on the default branch since the
last published tag. Check the commits since the last tag:
git -C <path> fetch --tags --quiet
last_tag=$(git -C <path> describe --tags --abbrev=0 2>/dev/null)
git -C <path> log --oneline "${last_tag}..origin/main" 2>/dev/null
If that range contains feat: / fix: / perf: commits, flag
release due with the count; chore:/docs:/test:-only ranges
are not release-due.
Do not determine "released" from in-tree files alone. Anchor on
origin/main and cross-check the published version against the
registry -- this is exactly the discipline in
release-audit-anchoring.
Reuse it; don't reinvent the version-reconciliation logic here. A
mismatch between the in-tree version and the registry is usually
release-plz state mid-flight, not a finding.
(e) Test run
Run the project's own test command (read it from CLAUDE.md or the
manifest -- cargo test, mix test, npm test, etc.). Record
pass / fail / - (no test command). Keep it a single bit of
signal; a failing suite is a flag to investigate, not something the
sweep fixes.
(f) Stale CLAUDE.md flag
Flag a project's CLAUDE.md as possibly stale when it has drifted from
the code -- e.g. the file is much older than recent substantive
commits, or references versions/paths that no longer match. A cheap
heuristic:
git -C <path> log -1 --format=%cr -- CLAUDE.md
git -C <path> log -1 --format=%cr
A large gap (months of active commits, untouched CLAUDE.md) earns a
stale? flag. The flag is a proposal to review, not a license to
edit -- see below.
(g) Branch cleanup
List local and remote branches that are candidates for deletion. Two
categories:
- Merged or closed. Branches whose PR is merged or closed -- the
work has landed (or been abandoned) and the branch is dead weight.
- Stale. Branches with no open PR and no recent commits -- no
activity, nothing tracking them, candidates to prune.
git -C <path> branch --merged origin/main | grep -vE '^\*|main$'
git -C <path> branch -r --merged origin/main | grep -vE 'origin/main$|origin/HEAD'
gh pr list --repo <owner>/<repo> --state all --json headRefName,state,number \
--jq '.[] | {branch: .headRefName, state: .state, number: .number}'
git -C <path> for-each-ref --sort=committerdate \
--format='%(committerdate:relative) %(refname:short)' refs/heads refs/remotes
Cross-reference the three signals: a branch whose PR is MERGED or
CLOSED, or a branch with no PR and a last commit older than the
staleness window, is a deletion candidate. Record the count of
candidates and list them in the expansion below the table.
This axis is propose-only, same discipline as the rest. The
read-only sweep does not delete branches. Deletion candidates are
surfaced in the report and routed to a worker, or run on confirmation:
git -C <path> branch -d <branch>
git push <remote> --delete <branch>
Never run those from the sweep itself. The sweeper reads and reports;
a separate writer (worker, or the owner on confirmation) deletes.
Output discipline
Emit a per-project report, reconstituted fresh every sweep. One
row or section per project. The report is the durable artifact for
this run; next sweep produces a new one from current state.
NEVER cache status into a workspace or manager CLAUDE.md. Writing
per-project last-seen counts upward into ~/Code/CLAUDE.md (or any
workspace-level file) violates map, not model: the manager would
start holding a stale model of every project instead of a map of
where they are. Reconstitute on demand; do not persist the readout.
Any proposed CLAUDE.md edit is proposed, never applied by the
sweeper. The stale-CLAUDE.md flag (axis f) surfaces a candidate for
revision -- it does not authorize the read-only sweep to rewrite that
file. Route the proposal per
non-pr-output-conventions:
- File it as an issue (
gh issue create) in the affected project, or
- Hand it to a worker as a bounded edit task.
The sweeper reads and reports; a separate writer (issue -> runner ->
worker) makes the change. Same rule for any other actionable finding
(a failing test, a release that's due): the sweep surfaces it and
routes it; it does not act on it.
Report shape
A tight table, one row per project, sorted by signal (community
issues/PRs first, then release-due, then the rest):
project | stars | forks | mine i/pr | community i/pr | bots | downloads | release | tests | claude.md | branches
my-crate | 45 | 6 | 2 / 0 | 3 / 1 | 4 | 8.2k/mo | DUE (4) | pass | stale? | 3
my-lib | 12 | 1 | 0 / 0 | 0 / 0 | 1 | 1.1k/mo | - | pass | ok | 0
local-tool | - | - | - | - | - | - | - | fail | ok | 1
The branches column is the count of branch-cleanup candidates (axis
g). Below the table, expand only the rows that need action: which
community PRs are waiting, what's release-due and why, which tests
failed, which CLAUDE.md looks stale and the proposed routing, and
which branches are deletion candidates with their reason (merged,
closed, or stale).
Graceful degradation (local-only / no-remote)
For a repo with no GitHub remote (local-only), skip the gh axes
(a, b, c) -- record - -- and run only the local axes (d release
check against any local tags, e test run, f CLAUDE.md staleness, g
branch cleanup over local branches only, without the PR-state
cross-reference).
Do not fail the sweep because one repo has no remote. Full handling
of local-only and non-standard-remote repos is tracked separately in
issue #222; this skill just degrades cleanly for now.
Anti-patterns
- Caching the readout upward. Writing sweep results into a
workspace/manager CLAUDE.md -- this is the cardinal violation of
map, not model. Reconstitute every sweep; persist nothing.
- Editing a project's CLAUDE.md from the sweep. The stale flag is
a proposal; the read-only sweeper never writes it. File or hand off.
- Deleting branches from the sweep. Axis (g) lists deletion
candidates; the read-only sweeper never runs
branch -d or
push --delete. Hand the candidates to a worker or delete on
confirmation.
- Working an issue mid-sweep. The moment you review a community PR
or write a fix, you've left the sweep and started a runner's job --
dispatch one instead.
- Blocking on a missing axis. A no-remote repo or an unpublished
package is a
-, not a stop. Degrade and continue.
- Treating in-tree version as published. Cross-check the registry
(per
release-audit-anchoring) before calling a release due.
Related skills
non-pr-output-conventions
-- where the report lands and how a proposed edit is routed (issue
vs worker handoff); the sweep is a non-PR read-mostly shape.
release-audit-anchoring --
the release-due axis reuses its anchor-on-origin/main +
cross-check-the-registry discipline.
workspace-survey -- the
dispatcher's lighter project-discovery map (PRs/issues/CI only); the
sweep is the deeper per-project health pass on top of that map.
durable-context -- why the sweep
reconstitutes from durable state every time and caches nothing.
triage -- the read-only-pass-that-writes-
findings sibling, scoped to the open-issue queue rather than
cross-project health.