Interactive, review-first smart-reaper triage from inside a session. Drives the Tier-2 content-judgment pass (the SAME /freshness-triage mechanism the scriptable `reaper --judge` subcommand uses) over the current idle candidate set, presents each candidate's keep/close verdict with its cited HEAD evidence for operator review, and closes a bead ONLY on explicit operator confirmation — mutating no bead state until you approve. Honors every reaper exemption (reaper:keep, the MinPriority floor, f...
Installs into .claude/skills of the current project.
Are you the author of Reaper?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/kurofune-reaper)
---
# Generated by djinn-gen-adapters from theme=djinn. DO NOT EDIT — regenerate via djinn-gen-adapters.
# djinn-compat: 1
name: reaper
description: "Interactive, review-first smart-reaper triage from inside a session. Drives the Tier-2 content-judgment pass (the SAME /freshness-triage mechanism the scriptable `reaper --judge` subcommand uses) over the current idle candidate set, presents each candidate's keep/close verdict with its cited HEAD evidence for operator review, and closes a bead ONLY on explicit operator confirmation — mutating no bead state until you approve. Honors every reaper exemption (reaper:keep, the MinPriority floor, follow-up grace, structural type protections) and the djinn-yume invariant (idle-age alone never closes). Triggers on: /reaper, interactive reaper, smart-reaper triage, review dead beads, content-aware reaping in session, triage the idle backlog."
---
<!-- djinn-compat-check: begin -->
**Before anything else:** run `djinn skills check --here` in the repo you are working in. If it exits non-zero, stop: relay its output to the user verbatim — it names the fix — and run no other step of this skill. On exit 0, continue.
<!-- djinn-compat-check: end -->
# /reaper — Interactive Smart-Reaper Triage
## Overview
`/reaper` is the **interactive, review-first** trigger for the smart reaper. It
drives the **Tier-2 content-judgment pass** over the current idle candidate set
from inside a session and presents each candidate's **keep / close**
verdict — with the cited HEAD evidence behind it — for operator review, then
closes a bead **only on your explicit confirmation**.
> **No `defer` from this surface.** `/reaper` only ever produces **keep** or
> **close**: the `/freshness-triage` edge yields a high-confidence dead verdict
> (`already-done`/`obsolete` → **close**) or `fresh` → **keep**. The reaper's
> `defer` disposition (`reaper.Defer`, `internal/djinn/reaper/judgment.go`) is a
> **scriptable-edge-only** state reachable from `reaper --judge`; this
> interactive surface never emits it.
It is the in-session sibling of the **scriptable `reaper --judge` subcommand**
(`cmd/reaper`, the scheduled/headless surface). The two share ONE judgment
mechanism — the existing **`/freshness-triage`** skill — so they can never
diverge in how a bead is judged. They differ only in trigger and disposition:
| | scriptable `reaper --judge` | `/reaper` (this skill) |
|---|---|---|
| Trigger | scheduled / CLI, `--live` to act | in-session slash command |
| Judgment | drives `/freshness-triage` headlessly (`claude -p`) | drives `/freshness-triage` in-session |
| Close disposition | acts under `--live` (unattended) | **review-first; closes only on explicit operator approval** |
| Default | mutate nothing without `--live` | **mutate nothing until you approve** |
This skill is **NOT** the cheap `SessionStart` dry-run hook (that runs `reaper`
with no flags and never judges — it costs no tokens). `/reaper` is on-demand,
costs tokens (one `/freshness-triage` pass per eligible candidate), and needs an
operator in the loop — that is the whole point of the review-first gate.
It is a **single-pass** triage: one `/freshness-triage` judgment per candidate,
one review table, one confirmation. It does not iterate or fan out.
## Scope boundary — what this skill does and does NOT do
- It judges whether a bead's **premise is still real** (via `/freshness-triage`)
and offers genuinely-dead beads for close. It does **NOT** re-plan work, judge
acceptance-criteria quality, or close a bead on idle-age alone.
- It **never** closes a bead the reaper exemptions protect, and **never** closes
a bead with no positive deadness verdict — idle-age alone is advisory only
(the **djinn-yume invariant**).
- It **mutates no bead state** — no `bd close`, no defer — until you explicitly
approve the proposed closes (the confirmation gate, below).
## Process
### Step 1 — Gather the candidate set AND its exemption metadata (read-only)
Read the same idle candidate set the reaper tool reads, via `bd stale`:
```bash
bd stale -d 7 -s open -n 0 --json
```
`bd stale` takes **flags**, not positional args: `-d 7` is the idle-day window
(the reaper's `IdleThreshold`, 168h), `-s open` is the candidate status, and
`-n 0` lifts the default 50-row cap so the WHOLE idle set is triaged (the reaper
adapter passes `-n 0` for exactly this reason — `bd stale` otherwise truncates at
50). Mirror the production reaper defaults so `/reaper` triages the same set the
scriptable subcommand would. If the operator named a different window or status,
pass it through; otherwise use these defaults. An empty result means nothing is
idle — report "no idle candidates" and stop.
**`bd stale --json` is NOT sufficient by itself for Step 2.** Its records carry
only `id, title, description, design, acceptance_criteria, notes, status,
priority, issue_type, owner, created_at, created_by, updated_at` — **no `labels`
field and no `dependencies` field at all.** But the Step-2 exemption predicate
(below) needs each candidate's **labels** (for `reaper:keep` and `review-follow-up`)
and **dependency types** (for the `discovered-from` follow-up signal). So you MUST
enrich the stale set before filtering — do not try to apply the `reaper:keep`,
`review-follow-up`-grace, or `discovered-from` exemptions from `bd stale` output
alone (you cannot, and silently skipping them would offer an exempt bead for
close — an AC3 violation).
Enrich with one additional read-only query — `bd list --json` carries **both**
`labels` and `dependencies[]` (each dep entry has a `type` field, e.g.
`discovered-from`, `parent-child`):
```bash
bd list --json -n 0 # every bead, WITH labels + dependencies[].type
```
`-n 0` lifts `bd list`'s row cap the same way it does for `bd stale` (the default
caps well under the full backlog). **Join on `id`:** for each candidate the
`bd stale` set returned, look up the same `id` in the `bd list` output and attach
its `labels` array and its `dependencies[].type` set. A candidate's:
- **`reaper:keep` / `review-follow-up` exemption** reads from the joined `labels`.
- **`discovered-from` exemption** reads from the joined `dependencies[]` — the
candidate is an auto-filed follow-up when any joined dep has
`type == "discovered-from"` (`{"depends_on_id": "...", "type": "discovered-from"}`).
- **priority / `issue_type` / `updated_at`** are present in BOTH outputs; either
copy is fine.
This is the documented enrichment join — `bd list --json -n 0` is the single
command that supplies the labels and dependency types `bd stale --json` omits, so
Step 2 can apply every exemption from in-band data with no out-of-band lookup.
(Equivalently, a per-candidate `bd show <id> --json` carries `labels` and a
`dependency_type` field; prefer the single `bd list` join over N `bd show` calls.)
### Step 2 — Apply the exemptions FIRST (AC3 — never offer an exempt bead for close)
Before judging anything, **filter out every exempt candidate**. This mirrors the
pure `reaper.JudgmentEligible` / `judgmentExempt` predicate
(`internal/djinn/reaper/judgment.go`) exactly — do NOT invent a different
exemption set. Read each candidate's `labels` and `dependencies[].type` from the
**Step-1 enrichment join** (`bd list --json -n 0`), NOT from the bare `bd stale`
record — the `reaper:keep`, `review-follow-up`, and `discovered-from` checks below
all depend on those joined fields, which `bd stale --json` does not carry. A
candidate is **exempt** (skip it; it is NEVER offered for close, it falls through
to *keep/advisory*) when ANY of these holds:
- It carries the **`reaper:keep`** label (the operator retention override) — read
from the joined `labels`.
- It is **not age-reapable** (an iter bead / structurally protected bead —
`beads.IsAgeReapable` is false).
- Its type is **`epic`**, or any type NOT in the reapable set
(`task`, `feature`, `bug`, `chore`).
- Its priority is **above the floor** — `Priority < MinPriority` (default
`MinPriority = 3`, so P0 / P1 / P2 beads are protected; only P3 / P4 are
eligible).
- It is an **auto-filed follow-up** (joined `labels` contain `review-follow-up`,
OR a joined `dependencies[]` entry has `type == "discovered-from"`) with a
**non-zero `UpdatedAt`** still **within its grace
window** — `now - UpdatedAt ≤ IdleThreshold + FollowupGrace` (168h + 14d). A
follow-up PAST the grace window is no longer protected. **Caveat (mirrors
`judgmentExempt`, `judgment.go:228`):** the grace check is gated on
`&& !UpdatedAt.IsZero()` — a follow-up with a **ZERO `UpdatedAt`** (never
updated) does NOT take the grace exemption; it falls through to non-exempt and
is judged like any other candidate.
Surface the exempt candidates in the review table as **`keep (exempt: <which
rule>)`** so the operator sees they were considered and protected — do not
silently drop them. Only the NON-exempt candidates proceed to judgment, so no
token is spent judging a bead that can never be closed (a cost guard AND the AC3
correctness guarantee).
### Step 2b — Disclose the eligible-candidate count (cost preview)
Before firing any `/freshness-triage` call, report the count of **ELIGIBLE
(non-exempt) candidates** to the operator — judgment is **O(n)** in that count
(one `/freshness-triage` pass per eligible bead, each costing tokens). For a
large backlog (e.g. 250+ eligible) this discloses the token cost BEFORE it is
spent, so the operator can scope the window down (Step 1 flags) or proceed
knowingly. Keep it to one line, e.g. `42 eligible candidates to judge (42
/freshness-triage passes); proceeding.`
### Step 3 — Judge each eligible candidate via `/freshness-triage` (AC4 — reuse, do not reinvent)
For each NON-exempt candidate, drive the **`/freshness-triage`** skill against the
current worktree at HEAD — the SAME mechanism the scriptable `reaper --judge`
subcommand drives headlessly. Do **NOT** improvise a fresh judgment; invoke the
named procedure so the in-session and scriptable surfaces stay in lockstep. Pass
the candidate's bead id; `/freshness-triage` reads `bd show <id>` itself and emits
its fenced `verdict` sidecar.
Map each `/freshness-triage` verdict to a `/reaper` disposition exactly as the
scriptable edge's `parseFreshnessVerdict` does (`cmd/reaper/judge.go`):
| `/freshness-triage` verdict | `/reaper` disposition | Close reason |
|---|---|---|
| `fresh` | **keep** | (premise still real — not offered for close) |
| `already-done` | **close** (high confidence) | the sidecar `reason` (the retired/landed premise) |
| `obsolete` | **close** (high confidence) | the sidecar `reason` (the removed/redesigned premise) |
| (unrecognized / no fence) | **keep** | bias toward keep on any uncertainty |
Two guardrails carried over from the scriptable edge:
- **A dead verdict (`already-done`/`obsolete`) with NO `reason` is downgraded to
keep** — a close must cite the retired premise (AC1); an empty reason cannot, so
do not offer it for close.
- **Cross-check the sidecar's `bead` field against the candidate id.** If
`/freshness-triage` emits a verdict whose `bead` names a DIFFERENT id (LLM
confusion, or a crafted description), the verdict is **not attributable** —
leave that candidate untouched (keep), do not close it on another bead's verdict.
A **BLANK** sidecar `bead` is **tolerated** (the skill simply omitted it) — only
a **present-and-different** id is non-attributable (mirrors `judge.go:168`:
`sb != "" && sb != candidateID`).
A candidate `/freshness-triage` cannot judge (skill error, no parseable sidecar)
is left **untouched (keep)** with the failure noted — one un-judgeable bead never
aborts the pass and never becomes a close.
### Step 4 — Present the review table (AC1)
Present every candidate, grouped by disposition, with its cited reason:
```
CLOSE (genuinely dead — your approval required):
djinn-abcd P4 "Old foo migration" — obsolete: foo subsystem removed in djinn-wxyz (commit 7ab9c28); the cited handler no longer exists at HEAD
djinn-efgh P3 "Duplicate bar task" — already-done: bar shipped via djinn-1234; cited test TestBar passes at HEAD
KEEP (premise still real / exempt / no deadness signal):
djinn-ijkl P3 "Live feature work" — fresh: the named endpoint still does not exist; work is real
djinn-mnop P2 "Important refactor" — keep (exempt: P2 above MinPriority floor)
djinn-qrst P4 "reaper:keep roadmap" — keep (exempt: reaper:keep label)
djinn-uvwx P4 "Idle but live" — keep (no positive deadness signal; idle-age alone never closes — djinn-yume)
```
Each CLOSE row MUST name its cited HEAD evidence (the `/freshness-triage` reason).
A candidate with no positive deadness verdict appears under KEEP, never CLOSE —
this is the djinn-yume invariant made visible: idle-age alone never graduates to a
close offer.
### Step 5 — Confirmation gate (AC2 — close ONLY on explicit operator approval)
This is the load-bearing interactive affordance. After presenting the table:
- **Mutate NO bead state yet.** Up to this point `/reaper` has run only read-only
commands (`bd stale`, `bd show`, `/freshness-triage`). Nothing has been closed.
- **Ask the operator to approve the CLOSE set explicitly.** Offer: approve **all**
proposed closes, approve a **subset** (name the ids), or **none**. Do not
proceed on silence, an ambiguous reply, or a generic "looks good" that does not
clearly authorize closing — when in doubt, ask again. Default is **review-first:
no close happens without an explicit yes.**
- **On approval, close ONLY the approved beads:**
```bash
bd close <id> --reason "reaper content-judgment close: <the cited /freshness-triage reason>"
```
Close exactly the approved ids — never a bead the operator did not name, never an
exempt or keep bead. After the approved closes, persist the bead-state to the
Dolt remote so collaborators see the closures:
```bash
bd dolt commit -m "reaper: interactive content-judgment closes (<ids>)"
bd dolt push
```
- **If the operator approves none**, mutate nothing and report that the triage was
review-only — the candidates remain open for a future pass.
Report a final summary: which beads were closed (with their cited reasons), which
were kept, and which were exempt.
## Why this exists
The reaper's deterministic Tier-1 sweep (`PlanStaleness`) and the scheduled Tier-2
`reaper --judge` subcommand both act unattended. `/reaper` is the **operator's**
surface: a content-aware reaping pass an operator can run from within a session
and **review before any bead is closed**, seeing each keep/close verdict with
its cited HEAD evidence rather than hand-triaging the whole idle backlog. The
review-first confirmation gate is what makes it safe to run interactively — the
unattended `--live` subcommand acts on its own judgment; `/reaper` acts only on
yours. Driving the SAME `/freshness-triage` mechanism the scriptable subcommand
drives is what guarantees the two surfaces never disagree about whether a bead is
dead (AC4): one judgment, two triggers.