/find-stale-artifacts
This skill is host-language-neutral: it inspects plans, specs, and report
artifacts rather than application source syntax. Its Python scripts are the
installed implementation, not a claim that the host project is Python.
You are the orchestrator for a SUSPECT skill that audits working-artifact
directories for accumulated cruft. Sibling to /find-dormant (which
covers code) — this one covers plans, specs, and report scan dirs.
Why this exists
Skills like /find-*, /triage-debt, and /audit-decisions write
per-run output under reports/<skill>/scan-<TS>/. Plans and specs land
under ai-docs/plans/ and ai-docs/specs/ and pick up status frontmatter
(abandoned, proposed, complete, etc.) over their lifecycle. Without
a periodic forcing function, both surfaces accumulate:
- Plans that were
abandoned months ago and now just sit there.
- Plans frozen at
proposed because the work was deferred and never
picked back up.
- Scan directories from old runs that nobody will ever read again.
- Top-level
reports/*.md notes (merge-time WIP, ad-hoc audits) that
outlive their relevance.
The discipline this skill enforces is status frontmatter + age +
reference-graph — three signals across a small fixed list of artifact
directories. If a candidate doesn't fit those three signals, it doesn't
belong in this skill.
Scope
- Default project root:
. (the current working directory).
- Default plans dir:
<root>/ai-docs/plans/.
- Default reports dir:
<root>/reports/.
- Output:
reports/find-stale-artifacts/<scan-id>/.
- No deletes; detection only.
How success is judged
- The run creates a fresh scan dir under
reports/find-stale-artifacts/<scan-id>/ with detections.jsonl,
report.md, and findings.json.
- Each command's exit code is honored; stop on non-zero and report the
failing command instead of rendering stale detections.
- Handoff identifiers are valid: every
findings.json record uses one of
the pattern names in Findings and carries file / lineno evidence.
- No silent drops: the JSONL record count matches
findings.summary.findings_total and the findings array length.
- A zero-finding run is successful only when those artifacts exist and
report.md says Findings: 0.
Pipeline
set -euo pipefail
SCAN_ID="scan-$(date -u +%Y%m%d-%H%M%S)"
REPORT_DIR="reports/find-stale-artifacts/$SCAN_ID"
mkdir -p "$REPORT_DIR"
.venv/bin/python .claude/skills/find-stale-artifacts/scripts/detect.py \
--output "$REPORT_DIR/detections.jsonl"
.venv/bin/python .claude/skills/find-stale-artifacts/scripts/report.py \
--detections "$REPORT_DIR/detections.jsonl" \
--output-md "$REPORT_DIR/report.md" \
--output-json "$REPORT_DIR/findings.json" \
--target "ai-docs/plans+reports/"
# Update the latest symlink
ln -snf "$SCAN_ID" "reports/find-stale-artifacts/latest"
Findings
abandoned_plan: an ai-docs/plans/*.md declares status: abandoned.
Always fires. Recommendation: delete the file (the abandonment is
recorded in git history; the file itself is no longer load-bearing). If
the work paused rather than terminated, capture the pointer in the
relevant backlog and delete the plan anyway — empty templates with
status notes are noise.
stale_plan: an ai-docs/plans/*.md is in an "in-flight" status
(draft, proposed, scoped, impacted, architected) but its
frontmatter date: is older than the soft budget (default 60 days).
Recommendation: refresh, promote, or abandon. A plan that sits in
proposed for two months without progress is signal that the work
was either silently shelved or silently completed via a different
surface.
aged_scan_dir: a reports/<skill>/scan-<TS>/ directory is older than
the soft budget (default 30 days) AND is not the target of the
sibling latest symlink. Recommendation: delete unless the scan is
referenced from a tracked artifact (a spec, plan, ADR, or BACKLOG
entry). The skill emits the grep'd reference list in the finding so
the human can confirm.
orphan_toplevel_report: a *.md at the top level of reports/
(not under any subdirectory) hasn't had a git commit touch it in N
days (default 30) AND isn't in the known-active list (BACKLOG.md,
skill-ecosystem-backlog.md). Recommendation: confirm it's still
working state or delete. Top-level files have no skill-imposed
rotation; they accumulate manually until someone notices.
Calibration
The three thresholds are soft budgets:
--max-plan-age-days 60: plans should move through their workflow in
weeks, not months. A plan parked at proposed for >60 days is almost
certainly stale.
--max-scan-age-days 30: scan directories are per-run audit trails;
the most recent run is what /fix-workflow and /triage-debt read.
Older scans are kept for archaeology, not active reference. 30 days
is generous — drop to 14 if reports/ is growing too fast.
--max-toplevel-age-days 30: top-level reports are working notes by
convention; if a human hasn't touched one in a month, it's almost
certainly cruft.
Replay case
When scripts/detect.py changes, replay a disposable project with one
abandoned_plan, one stale_plan, one old non-latest aged_scan_dir,
and one old orphan_toplevel_report. Use low age thresholds if needed.
The expected findings.json buckets are those four pattern names, and the
record count must match detections.jsonl.
When things go sideways
| Case |
Signal |
Response |
| Target absent |
ai-docs/plans/ or reports/ is missing under --root. |
Let the detector's zero-finding output stand if it exits 0; name the absent surface in the report. |
| Zero findings |
detections.jsonl is empty and report.md says Findings: 0. |
Treat as clean only after confirming the intended --root, plans subdir, and reports subdir were used. |
| Script non-zero exit |
Any command exits non-zero. |
Stop the pipeline, paste the command and stderr, and do not run report.py against stale detections. |
Next Skills
- Manual delete is the usual response. Stage candidates with
git rm <plan> for tracked plans, or rm -r <scan-dir> for
gitignored scan dirs.
/decide if the audit reveals a tradeoff worth recording (e.g. "we
keep accepting stale_plan for our skill-ecosystem work — extend the
budget or add a paused status").
/prevent-regression if a particular drift class recurs often enough
to justify a CI check (e.g. "no plan stays at proposed for >90
days").
Notes for the orchestrator
Stage 1 skeleton. The four bands above cover the cases that
motivated the skill (the canonical-findings-ledger plan, the merge-
note-wip-refactor.md file, the 56 scan dirs in reports/). Future
bands worth considering only with real evidence:
complete_spec_aged: specs with status: complete / DONE
older than N days. Skipped because completed specs are the audit
trail and have lower volume.
proposed_decision_with_abandoned_plan: belongs to
/audit-decisions, not here.
dead_scan_reference: a tracked file references a scan dir that
no longer exists. Add when an actual case appears.
Age signal preference. For plans, use the frontmatter date:
field — immune to checkout resets and matches how the lifecycle is
documented. For top-level reports without frontmatter, use
git log -1 --format=%ct (last commit timestamp). For scan dirs,
parse the directory name (scan-YYYYMMDD-HHMMSS) — the timestamp is
in the name itself.
Reference-graph check. For aged_scan_dir, run grep -rl --exclude-dir=reports for the scan-id across the repo. A scan dir
referenced from a tracked plan/spec/ADR/BACKLOG line should NOT fire
— those references are the audit trail the scan exists to support.
Latest symlink. Skip the directory the sibling latest symlink
points at, regardless of age. The most recent scan is always
load-bearing.
Known-active top-level list. BACKLOG.md and
skill-ecosystem-backlog.md are the working backlog files. Any
reports/<file>.md not in this list is an orphan_toplevel_report
candidate. If the list grows, lift it into a config file.
1---2name: find-stale-artifacts3description: Detect stale working artifacts — abandoned plans, proposed plans untouched past a soft staleness budget, aged `scan-*` report directories, and orphan top-level report files. Defends attention against the accumulation of working artifacts that outlive the work. SUSPECT skill, sibling to /find-dormant for non-code surfaces.4---56# /find-stale-artifacts78This skill is host-language-neutral: it inspects plans, specs, and report9artifacts rather than application source syntax. Its Python scripts are the10installed implementation, not a claim that the host project is Python.1112You are the orchestrator for a SUSPECT skill that audits working-artifact13directories for accumulated cruft. Sibling to `/find-dormant` (which14covers code) — this one covers plans, specs, and report scan dirs.1516## Why this exists1718Skills like `/find-*`, `/triage-debt`, and `/audit-decisions` write19per-run output under `reports/<skill>/scan-<TS>/`. Plans and specs land20under `ai-docs/plans/` and `ai-docs/specs/` and pick up status frontmatter21(`abandoned`, `proposed`, `complete`, etc.) over their lifecycle. Without22a periodic forcing function, both surfaces accumulate:2324- Plans that were `abandoned` months ago and now just sit there.25- Plans frozen at `proposed` because the work was deferred and never26 picked back up.27- Scan directories from old runs that nobody will ever read again.28- Top-level `reports/*.md` notes (merge-time WIP, ad-hoc audits) that29 outlive their relevance.3031The discipline this skill enforces is **status frontmatter + age +32reference-graph** — three signals across a small fixed list of artifact33directories. If a candidate doesn't fit those three signals, it doesn't34belong in this skill.3536## Scope3738- Default project root: `.` (the current working directory).39- Default plans dir: `<root>/ai-docs/plans/`.40- Default reports dir: `<root>/reports/`.41- Output: `reports/find-stale-artifacts/<scan-id>/`.42- No deletes; detection only.4344## How success is judged4546- The run creates a fresh scan dir under47 `reports/find-stale-artifacts/<scan-id>/` with `detections.jsonl`,48 `report.md`, and `findings.json`.49- Each command's exit code is honored; stop on non-zero and report the50 failing command instead of rendering stale detections.51- Handoff identifiers are valid: every `findings.json` record uses one of52 the `pattern` names in Findings and carries `file` / `lineno` evidence.53- No silent drops: the JSONL record count matches54 `findings.summary.findings_total` and the `findings` array length.55- A zero-finding run is successful only when those artifacts exist and56 `report.md` says `Findings: 0`.5758## Pipeline5960```bash61set -euo pipefail62SCAN_ID="scan-$(date -u +%Y%m%d-%H%M%S)"63REPORT_DIR="reports/find-stale-artifacts/$SCAN_ID"64mkdir -p "$REPORT_DIR"65.venv/bin/python .claude/skills/find-stale-artifacts/scripts/detect.py \66 --output "$REPORT_DIR/detections.jsonl"67.venv/bin/python .claude/skills/find-stale-artifacts/scripts/report.py \68 --detections "$REPORT_DIR/detections.jsonl" \69 --output-md "$REPORT_DIR/report.md" \70 --output-json "$REPORT_DIR/findings.json" \71 --target "ai-docs/plans+reports/"7273# Update the latest symlink74ln -snf "$SCAN_ID" "reports/find-stale-artifacts/latest"75```7677## Findings7879- `abandoned_plan`: an `ai-docs/plans/*.md` declares `status: abandoned`.80 Always fires. Recommendation: delete the file (the abandonment is81 recorded in git history; the file itself is no longer load-bearing). If82 the work paused rather than terminated, capture the pointer in the83 relevant backlog and delete the plan anyway — empty templates with84 status notes are noise.8586- `stale_plan`: an `ai-docs/plans/*.md` is in an "in-flight" status87 (`draft`, `proposed`, `scoped`, `impacted`, `architected`) but its88 frontmatter `date:` is older than the soft budget (default 60 days).89 Recommendation: refresh, promote, or abandon. A plan that sits in90 `proposed` for two months without progress is signal that the work91 was either silently shelved or silently completed via a different92 surface.9394- `aged_scan_dir`: a `reports/<skill>/scan-<TS>/` directory is older than95 the soft budget (default 30 days) AND is not the target of the96 sibling `latest` symlink. Recommendation: delete unless the scan is97 referenced from a tracked artifact (a spec, plan, ADR, or BACKLOG98 entry). The skill emits the grep'd reference list in the finding so99 the human can confirm.100101- `orphan_toplevel_report`: a `*.md` at the top level of `reports/`102 (not under any subdirectory) hasn't had a git commit touch it in N103 days (default 30) AND isn't in the known-active list (`BACKLOG.md`,104 `skill-ecosystem-backlog.md`). Recommendation: confirm it's still105 working state or delete. Top-level files have no skill-imposed106 rotation; they accumulate manually until someone notices.107108## Calibration109110The three thresholds are soft budgets:111112- `--max-plan-age-days 60`: plans should move through their workflow in113 weeks, not months. A plan parked at `proposed` for >60 days is almost114 certainly stale.115- `--max-scan-age-days 30`: scan directories are per-run audit trails;116 the most recent run is what `/fix-workflow` and `/triage-debt` read.117 Older scans are kept for archaeology, not active reference. 30 days118 is generous — drop to 14 if `reports/` is growing too fast.119- `--max-toplevel-age-days 30`: top-level reports are working notes by120 convention; if a human hasn't touched one in a month, it's almost121 certainly cruft.122123## Replay case124125When `scripts/detect.py` changes, replay a disposable project with one126`abandoned_plan`, one `stale_plan`, one old non-latest `aged_scan_dir`,127and one old `orphan_toplevel_report`. Use low age thresholds if needed.128The expected `findings.json` buckets are those four pattern names, and the129record count must match `detections.jsonl`.130131## When things go sideways132133| Case | Signal | Response |134|---|---|---|135| Target absent | `ai-docs/plans/` or `reports/` is missing under `--root`. | Let the detector's zero-finding output stand if it exits 0; name the absent surface in the report. |136| Zero findings | `detections.jsonl` is empty and `report.md` says `Findings: 0`. | Treat as clean only after confirming the intended `--root`, plans subdir, and reports subdir were used. |137| Script non-zero exit | Any command exits non-zero. | Stop the pipeline, paste the command and stderr, and do not run `report.py` against stale detections. |138139## Next Skills140141- Manual delete is the usual response. Stage candidates with142 `git rm <plan>` for tracked plans, or `rm -r <scan-dir>` for143 gitignored scan dirs.144- `/decide` if the audit reveals a tradeoff worth recording (e.g. "we145 keep accepting `stale_plan` for our skill-ecosystem work — extend the146 budget or add a `paused` status").147- `/prevent-regression` if a particular drift class recurs often enough148 to justify a CI check (e.g. "no plan stays at `proposed` for >90149 days").150151## Notes for the orchestrator152153- **Stage 1 skeleton.** The four bands above cover the cases that154 motivated the skill (the canonical-findings-ledger plan, the merge-155 note-wip-refactor.md file, the 56 scan dirs in `reports/`). Future156 bands worth considering only with real evidence:157 - `complete_spec_aged`: specs with `status: complete` / `DONE`158 older than N days. Skipped because completed specs are the audit159 trail and have lower volume.160 - `proposed_decision_with_abandoned_plan`: belongs to161 `/audit-decisions`, not here.162 - `dead_scan_reference`: a tracked file references a scan dir that163 no longer exists. Add when an actual case appears.164165- **Age signal preference.** For plans, use the frontmatter `date:`166 field — immune to checkout resets and matches how the lifecycle is167 documented. For top-level reports without frontmatter, use168 `git log -1 --format=%ct` (last commit timestamp). For scan dirs,169 parse the directory name (`scan-YYYYMMDD-HHMMSS`) — the timestamp is170 in the name itself.171172- **Reference-graph check.** For `aged_scan_dir`, run `grep -rl173 --exclude-dir=reports` for the scan-id across the repo. A scan dir174 referenced from a tracked plan/spec/ADR/BACKLOG line should NOT fire175 — those references are the audit trail the scan exists to support.176177- **Latest symlink.** Skip the directory the sibling `latest` symlink178 points at, regardless of age. The most recent scan is always179 load-bearing.180181- **Known-active top-level list.** `BACKLOG.md` and182 `skill-ecosystem-backlog.md` are the working backlog files. Any183 `reports/<file>.md` not in this list is an `orphan_toplevel_report`184 candidate. If the list grows, lift it into a config file.