# Curate

> Review codebase quality — find stale decisions, knowledge gaps, and implicit dependencies

- Skill: `telefrek/curate` (Agent Skill)
- Install (CLI): `npx skillmds@latest add telefrek/curate`
- Raw SKILL.md: https://api.skillmd.com/api/skills/telefrek/curate/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: telefrek (https://skillmd.com/u/telefrek)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/telefrek/curate

---


# /curate [--init] [--deeper] [--verify]

Correlation engine that combines vallorcine's structured history with git data
to find things that individual features, decisions, and research sessions
couldn't see because they each had a narrower scope.

**What it finds:**
1. ADR pressure — decisions under concentrated change (scope being actively modified)
2. ADR gravity — files implicitly related to decisions but not in their scope
3. Hub files — shared dependencies across 3+ decisions (fragility/test concerns)
4. ADR drift — code diverging from architectural decisions
5. Stale KB — research that may have better approaches now given what's been built
6. Implicit dependencies — gaps between independently-designed features
7. Orphaned areas — high-churn files with no structured knowledge behind them
8. Unspecified shared types — foundational types referenced by 3+ specs with no spec
9. Spec obligations — DRAFT specs with unresolved conflicts blocking approval
10. Spec-code drift — specs whose domain code changed after the spec was written
11. Cross-reference gaps — KB entries and ADRs with missing related/source links
12. Missing `@spec` annotations — APPROVED specs with reqs that lack impl-side or test-side annotations
13. Aging open obligations — obligations on specs that haven't been committed in 30+ days
14. Link rot — KB-cited URLs that no longer resolve (4xx or connection failures)
15. Falsification-lens staleness — APPROVED specs authored before a falsification lens shipped that match the lens's keywords (candidates for re-falsification under the newer lens)
16. KB filename collisions — entries sharing a filename across folders (silently fragments search and pattern-recurrence evidence)
17. KB schema drift — frontmatter that does not match `.kb/_refs/frontmatter.md` (missing/bad-enum fields, confidence overclaim, path/frontmatter mismatch)
18. KB type/location mismatch — adversarial findings outside `patterns/<concern>/` or feature-footprints outside `architecture/feature-footprints/`
19. KB citation drift in source — `// KB:` / `# KB:` citations in changed source files that point at missing entries or whose entry's `applies_to` doesn't include the source file (closes the loop with the `check-kb-ref.sh` PostToolUse hook)
20. Spec graduation candidates — specs marked `status: DEPRECATED` that are still `state: APPROVED` (still entering `/spec-resolve` bundles despite the author's intent to retire them — finalize displacement or retract the status)
21. Spec corpus xref drift — corpus rollup of unresolvable `decision_refs` / `kb_refs` across all specs (per-spec `spec-validate.sh` warnings are easy to miss at scale)
22. Spec annotation coverage rollup — corpus-level summary across the four buckets (fully covered / drift / bare-only / unannotated). Frames `/spec-backfill --all` vs row-by-row routing decision.
23. ADRs without spec coverage — accepted/proposed ADRs that no spec references via `decision_refs`. Architectural intent without an operational contract.

**Flags:**
- `--init` — first-time scan (ignores last-scanned SHA, good for new installs)
- `--deeper` — scan 6 months instead of default 3
- `--obligation-age-days <n>` — override aging threshold for open obligations (default: 30)
- `--max-specs-traced <n>` — cap @spec annotation traces per run (default: 50)
- `--verify` — focused pass over verification-shaped candidates only
  (link-rot, falsification-staleness). Skips the broader correlation
  flow. Dismissals persist to `.curate/verify-dismissed.txt` so future
  scans don't re-prompt the same items.
- `--analysis <name>` — when used with `--verify`, restrict to a single
  analysis: `link-rot`, `falsification-stale`, or `all` (default: `all`).

This command feels like a colleague who noticed something and is offering to help,
not a task manager assigning work.

## Verify mode

When invoked with `--verify`, this skill runs the same scan but presents
ONLY the verification-shaped candidates from Analyses 21 (link rot) and
22 (falsification staleness). The broader correlation flow (ADR
pressure, hub files, spec-code drift, cross-ref repair, etc.) is
skipped — those signals belong to the regular `/curate` cadence.

Verify mode is a separate cadence: run it before major work (release
prep, audit kickoff) or monthly, when the heavier verification pass
is justified. Regular `/curate` stays drift-shaped; verify mode is
verification-shaped.

**Per-candidate flow:**
- Each link-rot row → AskUserQuestion with options: refresh via
  `/research`, mark accepted, dismiss, skip.
- Each falsification-stale row → AskUserQuestion with options: run
  depth pass via `/spec-author <id> --depth-pass-only --lens <name>`,
  decline (lens does not apply), dismiss, skip.

**Dismissal persistence.** When the user picks "dismiss" in verify
mode, append a row to `.curate/verify-dismissed.txt`:

```
<analysis>|<candidate-key>|<dismiss-date>|<reason>
```

Where `<candidate-key>` is the URL for link-rot or `<spec-id>:<lens>`
for falsification-staleness. Future verify-mode runs read this file
and skip any candidate whose key is dismissed (the underlying
`scan-summary.md` still surfaces them for non-verify `/curate` runs;
the dismissal only applies to the verify-mode prompt loop).

When the user picks "skip" instead of "dismiss", nothing is recorded
— the candidate resurfaces next verify-mode run.

---

## Subagent contract — MANDATORY for every dispatch

Every Agent dispatch this skill issues (invoke-architect, invoke-research,
invoke-spec-author, or any future side-effect that uses the Agent tool)
MUST be given this preamble at the top of the dispatched skill's prompt:

> **Subagent contract:** Honor `rules/completeness-contract.md` (load-bearing —
> no silent deferrals; trigger phrases = escalation signals, not completion
> modes). If you cannot complete assigned scope, escalate via AskUserQuestion
> with user-validatable proof. A return claiming COMPLETE alongside deferred
> items is a contract violation.

When dispatched skills return, the curator MUST run the validation script
BEFORE marking the finding `resolved`:

```bash
mkdir -p /tmp/vallorcine
return_file=/tmp/vallorcine/curate-return-"<finding-key>".txt
printf '%s\n' "$FULL_RETURN_TEXT" > "$return_file"
bash .claude/scripts/validate-subagent-return.sh "$return_file" 2>/tmp/vallorcine/validator-stderr.txt
rc=$?
```

- `rc=0` → mark finding `resolved` and continue.
- `rc=1` → trigger phrase detected. Surface to user via AskUserQuestion with
  validator stderr. Block `resolved` until user clears.
- `rc=2` → tooling error. Log and treat as `rc=0`.

## Step 0 — Pre-flight

Check that `.curate/` directory exists. If not, create it.

Read `.curate/curation-state.md` if it exists — extract the last-scanned SHA
from the Scan State section. (Older installs may still carry a Review Log
section in this file. Migrate it to the append-only review log on first run:)

```bash
if [[ -f .curate/curation-state.md ]]; then
  bash .claude/scripts/curate-review-log.sh migrate \
    .curate/curation-state.md .curate/review-log.md
fi
```

Migration is idempotent — re-running on a migrated state file is a no-op.
After this point, the review log lives at `.curate/review-log.md` and is
append-only. Never edit it by hand.

Read the unresolved items the user previously deferred or noted-for-later.
These will be re-surfaced in Step 2.5 alongside any new findings:

```bash
PRIOR_UNRESOLVED=$(bash .claude/scripts/curate-review-log.sh unresolved \
  .curate/review-log.md 2>/dev/null || true)
```

`PRIOR_UNRESOLVED` is a list of `<key>|<status>|<description>|<date>`
records. Empty when this is the first run or every prior finding was
resolved/dismissed.

**Stuck-marker recovery.** Scan `.curate/_dispatches/` for any
unacknowledged finding-resolution markers from a previous `/curate`
run that crashed mid-dispatch:

```bash
STUCK_MARKERS=$(bash .claude/scripts/dispatch-marker.sh stuck \
  .curate/_dispatches 2>/dev/null || true)
```

Each row is `<finding-key>|<dispatched-at>|<has-result>|<failure-reason>`.

If non-empty, surface BEFORE Step 1 (the scan run). **Batch the prompt
when N > 2 markers exist** (2026-05-11 adversarial MED #3) — per the
kit-development "Interactive prompt standard," dynamic lists with >4
items should use summary options rather than firing N consecutive
AskUserQuestions in a row.

- **N == 1**: single AskUserQuestion with three options —
  **"Re-dispatch"** (clear marker, include in this run),
  **"Skip for now"** (leave marker; next run will resurface it),
  **"Investigate manually"** (print marker JSON via
  `dispatch-marker.sh status .curate/_dispatches <finding-key>` and
  stop the `/curate` run).

- **N == 2**: two sequential AskUserQuestions, same option set.
  Acceptable user friction.

- **N > 2 (the batching path)**: one AskUserQuestion with summary
  options that apply to all stuck markers:
    - **"Re-dispatch all"** — clears all markers and includes each
      finding in this run
    - **"Skip all"** — leaves every marker; next run resurfaces them
    - **"Walk one at a time"** — falls back to the per-marker prompt
      (N consecutive AskUserQuestions, one per marker, same option
      set as the N==1 case)
    - **"Investigate manually"** — print all marker JSONs and stop
  This caps user interruption at one prompt regardless of N — the
  pre-fix flow with N=5 markers fired 5 sequential prompts before the
  scan even started.

This mirrors `/work-resume rule 0` (PR #79) and `/spec-backfill` C0 —
any unacknowledged marker pre-empts the normal flow because it
represents a previous dispatch whose result was never reconciled.

Display opening header:
```
───────────────────────────────────────────────
🔍 CURATION · scanning for quality signals
───────────────────────────────────────────────
```

---

## Step 0.5 — Index verification (self-healing)

Before scanning, run the index verification script to catch and repair any
index inconsistencies from previous crashes:

```bash
bash .claude/scripts/index-verify.sh --both 2>&1
```

If repairs are made, the script outputs what was fixed. Note these for the
findings presentation — they're bookkeeping fixes the user should know about
but don't need to act on.

If the script doesn't exist (older install), skip silently.

---

## Step 1 — Run the scan script

Build the scan command:

```bash
bash .claude/scripts/curate-scan.sh [--init] [--window <months>] \
  [--obligation-age-days <n>] [--max-specs-traced <n>]
```

- Default: `--window 3` (3 months, capped at 500 commits)
- If `--init` flag: pass `--init`
- If `--deeper` flag: pass `--window 6`
- If the user passes `--obligation-age-days` or `--max-specs-traced`, forward them
- The `--verify` flag does NOT change the scan invocation — the script still
  runs every analysis. Verify mode only narrows which candidates the
  pick-list presents in Step 3.

Run the script. If it exits with "No new commits since last scan," report that
and ask if the user wants to force a rescan with `--init`.

### Step 1.1 — Verify scan completeness via sentinel (REQUIRED)

After the scan script exits, before reading the summary in Step 2,
check that `.curate/scan-summary.md` ends with the scan-complete
sentinel. The sentinel is the last line and looks like:

```
✓ Scan complete: <iso-date> · max_specs_traced=<N> · specs_traced=<M> · scan_mode=<full|incremental> · window_months=<W>
```

A missing sentinel means the script exited before its final block —
the summary was partially written and **must NOT be read as
authoritative**. Common causes: Claude Code's default Bash timeout
(jlsm-sized repos can run 10–15 min if Analysis 18 traces all
APPROVED specs), manual Ctrl-C, context compaction mid-run.

**Check:**

```bash
tail -1 .curate/scan-summary.md | grep -q "^✓ Scan complete:"
```

If absent → the scan was interrupted. Surface to the user via
AskUserQuestion:

- **"Re-run with `--init --max-specs-traced 0`"** — recommended for
  large repos. Skips the per-spec annotation trace (Analysis 18),
  which is the only analysis that typically pushes runtime past 60s.
  Annotation-coverage rollup data will be absent; if needed, run a
  follow-up trace pass with `--max-specs-traced 50` (or higher) once
  the fast scan confirms the rest of the corpus is clean.
- **"Re-run with `--init`"** — fresh full scan; same timeout risk.
- **"Read partial summary anyway"** — only safe when the missing
  analyses are known to not apply (small repos, first-time setup).
  The user takes responsibility.

If the sentinel IS present, surface its contents to the user in one
line so they see the scope of what ran:

```
Scan complete: <date> · traced <M>/<approved-count> specs · window <W>m
```

**No-op scan detection** (CRIT 5, 2026-05-11 adversarial). When the
scan script exits "No new commits since last scan" or "No commits
found in scan range," it overwrites scan-summary.md with a `no-op`
marker — the sentinel includes the literal substring `· no-op ·`.
Detect this:

```bash
if tail -1 .curate/scan-summary.md | grep -q "· no-op ·"; then
    # No-op scan — prior findings already addressed or no qualifying
    # activity in window. Do NOT proceed into Step 2 with the prior
    # summary as if it were fresh.
fi
```

If detected, surface to the user via AskUserQuestion:

- **"Force a fresh scan (`--init`)"** — re-runs against the full window
  regardless of `LAST_SHA`.
- **"Expand the window (`--window-months N`)"** — useful when commit
  activity falls outside the default window.
- **"Stop — nothing to curate right now"** — exit cleanly.

Without this check, prior findings (potentially days/weeks old) get
re-presented as if they were just discovered — confusing the user and
re-surfacing already-resolved drift.

If `specs_traced=0` in the sentinel AND APPROVED specs exist in the
manifest, note that the annotation-coverage rollup (Analysis 18b) and
the per-spec annotation gap analyses (Analysis 18) did not run for
this scan. Offer to schedule a follow-up trace pass before closing.

---

## Step 1.5 — Verify-mode branch

If the user invoked `/curate --verify`:

1. **Filter Step 2 to subsections 2p (link rot) and 2q (falsification
   staleness) only.** Skip 2a-2o and 2r entirely. The other signals
   belong to the regular `/curate` cadence.
2. **Apply the analysis filter.** If the user passed
   `--analysis link-rot`, only run 2p. If `--analysis falsification-stale`,
   only run 2q. Default (or `--analysis all`) runs both.
3. **Read the dismissed-state file** at `.curate/verify-dismissed.txt`
   (touch it if missing). The format is one row per dismissal:
   `<analysis>|<candidate-key>|<date>|<reason>`. Build a transient set
   of dismissed keys.
4. **Filter candidates by dismissed-state.** When walking the candidate
   list from the scan summary, skip any candidate whose key matches a
   dismissed entry. The user already declined to act on it; don't
   re-prompt until the dismissed entry is manually removed.
5. **Step 3 pick list shows ONLY verify-mode items.** No other findings
   surface. The cold-start framing is also skipped — verify mode
   assumes structured artifacts already exist.
6. **Step 4 routing in verify mode.** When the user picks "dismiss" on
   a candidate, append a new row to `.curate/verify-dismissed.txt`:
   - For link-rot: `link-rot|<url>|<YYYY-MM-DD>|<one-line reason>`
   - For falsification-stale: `falsification-stale|<spec-id>:<lens>|<YYYY-MM-DD>|<one-line reason>`
   When the user picks "skip", do NOT write to the dismissed file —
   "skip" defers; "dismiss" persists.

If the user did NOT pass `--verify`, skip this entire section and run
Step 2 normally with all subsections.

---

## Step 2 — Read and correlate

Read `.curate/scan-summary.md` (the script's output).

Also read (if they exist):
- `.decisions/CLAUDE.md` — active decisions index
- `.kb/CLAUDE.md` — KB root index
- `.feature/CLAUDE.md` — active and archived features

### 2a — ADR drift detection

**ADR Pressure** (from "ADR Pressure" in scan summary):
1. ADRs with 2+ constrained files changed in the scan window
2. Higher pressure % = more of the decision's scope is actively changing
3. Read the ADR and assess: is the code evolving within the decision, or away from it?
4. High pressure (>60%) → strong signal for re-evaluation

**ADR Gravity** (from "ADR Gravity" in scan summary):
1. Files that co-change with ADR-constrained files but aren't in the ADR's scope
2. These are implicit relationships — the decision's influence is wider than documented
3. Assess: should these files be added to the ADR's `files:` field, or is the
   co-change coincidental?
4. High gravity (5+ unconstrained files for one ADR) → potential **isolation problem**.
   The decision may have drawn the boundary in the wrong place. Flag for `/architect`
   review with framing: "This decision's actual dependency footprint is larger than
   its documented scope — worth re-evaluating the boundary."

**Hub Files** (from "Hub Files" in scan summary):
1. Files co-changing with 3+ ADRs' constrained areas
2. These are fragility points — changes here ripple across multiple decisions
3. Flag as test coverage concerns: "This file is a shared dependency across
   <N> architectural decisions. Worth ensuring test coverage is solid."

**Flat artifact correlations** (from "Artifact Correlations" where Type is ADR):
1. Individual ADR file references not captured by pressure (single-file changes)
2. Read the referenced ADR, compare stated approach against changed files
3. Check if "Conditions for Revision" have been met by recent changes

### 2b — KB + hindsight review

For each entry in "Stale KB Entries":
1. Note the KB file and how long since last research
2. Cross-reference with "Churn Hotspots" — is the area the KB covers actively changing?
3. Check if any ADRs were made since the KB entry was written that might change
   which options are viable

For each entry in "Artifact Correlations" where Type is KB:
1. Note that implementation has changed since research was done
2. Flag if the changes suggest the research conclusions may need updating

### 2c — Implicit dependency detection

Using "Co-change Clusters" and "Artifact Correlations" where Type is FEATURE:
1. Identify file pairs that co-change but were designed in separate features
2. Check if cross-feature test coverage exists for the shared files
3. Flag gaps where independently-designed features share files without
   cross-coverage

### 2d — Orphaned areas

From "Orphaned Areas" in the scan summary:
1. Identify high-churn files with no KB, ADR, or feature coverage
2. These are backfill candidates — areas the codebase is actively changing
   but that have no structured knowledge behind them

### 2e — Test-source drift

From "Test-Source Drift" in the scan summary:
1. Source files that changed but their corresponding tests didn't
2. Cross-reference with feature archives — were these files part of features
   that should have had test updates?
3. Flag files where the drift is significant (3+ source commits with no test change)
4. This catches within-feature drift where implementation evolved but tests
   didn't keep pace

### 2f — Backfill candidates (implicit decisions)

From "Backfill Candidates" in the scan summary:
1. Archived feature domains that made implicit decisions (no governing ADR)
2. For each candidate, assess whether the decision is significant enough to
   warrant formal documentation
3. Present as items the user can decide, draft as ADR, defer, or dismiss
4. This subsumes the standalone `/decisions backfill` command — curate is the
   single entry point for finding undocumented decisions

### 2g — Out-of-scope items (deferred work in accepted ADRs)

From "Out-of-Scope Items" in the scan summary:
1. Items from "What This Decision Does NOT Solve" sections of confirmed ADRs
   that have no corresponding deferred decision stub
2. These are architectural concerns the team explicitly scoped out when making
   a decision — they are effectively deferred work invisible to `/decisions triage`
3. Group items by parent ADR for presentation
4. For each item, the user can: create a deferred stub, skip, or create all
   stubs from that parent ADR at once

### 2h — Spec coverage analysis

**Guard:** Only run this step if `.spec/` exists. If no spec directory, skip
entirely — don't mention specs or suggest setting up specs.

From "Spec Coverage Gaps" in the scan summary (if present):

**Unspecified shared types:**
1. Types referenced by 3+ specs that have no spec of their own
2. These are foundational types with implicit contracts — multiple specs
   depend on their behavior but nobody has defined what that behavior is
3. Rank by reference count — higher count = more dependent specs = bigger risk

**Specs with open obligations:**
1. Specs with `[UNRESOLVED]` or `[CONFLICT]` markers or `open_obligations`
   in frontmatter
2. These are blocking downstream work — DRAFT specs can't be relied on until
   obligations are resolved
3. Higher obligation count = more blocking

**Obligation registry (from _obligations.json):**
1. Open obligations from the centralized registry — spec requirements where the
   code does not match the spec. These are the gap between what was specified
   and what was built.
2. Group by spec for display. Show affected requirement count and blocked_by.
3. Route to `/work-decompose "<group>" --from-obligations` to convert
   obligations into a work group with proper WD ordering. This is the primary
   action — obligations without a work group have no implementation path.
4. Higher affected-requirement count = larger implementation gap.

**Spec-code drift:**
1. Specs whose domain files have been committed since the spec was created
2. Higher commit count = more likely the spec no longer matches reality
3. Cross-reference with ADR pressure — if the same area has both ADR pressure
   and spec drift, it's a stronger signal

**Undecided absent behaviors:**
1. Specs with `[ABSENT]` requirements — behaviors that downstream specs assume
   but the implementation doesn't provide
2. These are unresolved design decisions: each `[ABSENT]` requirement needs an
   explicit promote/preserve/defer choice
3. Higher count = more implicit assumptions without backing decisions

**Orphaned specs (no matching source code):**
1. APPROVED specs whose subject tokens were not found in any source file
2. These may describe behavior that was removed without updating the spec
3. For each orphaned spec, use AskUserQuestion with options:
   - **"Verify with /spec-verify"** — run spec-verify to check if the
     behavior still exists (subject token search may have missed it)
   - **"Mark as INVALIDATED"** — the behavior was removed; mark the spec
     as INVALIDATED with `displacement_reason: "behavior removed — detected
     by curate scan"`
   - **"Skip for now"** — defer to a later curation pass

### 2i — Cross-reference repair candidates

**Guard:** Only run this step if "Cross-Reference Candidates" section exists in
the scan summary. If absent, skip entirely.

From "Cross-Reference Candidates" in the scan summary:

**KB entries with missing related links (tag overlap):**
1. Entry pairs that share 2+ tags but have no `related` link between them
2. Higher tag overlap = stronger signal that these entries should reference each other
3. Entries in different categories are more valuable links — same-category entries
   are already navigable via category indexes
4. Assess whether the overlap is meaningful: shared tags like "performance" +
   "caching" between a caching strategy and a benchmarking entry → likely related.
   Shared tags like "java" + "testing" between unrelated entries → coincidental.

**KB entries with overlapping applies_to:**
1. Entries that target the same source files/patterns but don't reference each other
2. These likely describe different aspects of the same code — a `related` link
   helps the Research Agent find all relevant context when loading one entry
3. Stronger signal than tag overlap because file paths are specific

**ADR evaluation references not in KB Sources:**
1. KB entries cited in evaluation.md scoring that don't appear in the ADR's
   KB Sources Used table
2. These are missing traceability links — the ADR used this research during
   evaluation but doesn't formally reference it
3. Fix is straightforward: add the missing row to the KB Sources table

### 2j — Deferred audit feedback

**Guard:** Only run this step if "Deferred Audit Feedback" section exists
in the scan summary. If absent, skip entirely.

From "Deferred Audit Feedback" in the scan summary:

1. Each row is a `spec-updates.md` or `kb-suggestions.md` file from a
   completed audit where the user skipped or deferred the feedback loop
2. These contain ready-made spec requirements and KB pattern suggestions
   that a prior audit produced — they don't need re-analysis, just review
   and application
3. Present as high-priority pick list items — the work is already done,
   applying it is cheap

When the user picks one of these items:
- Read the file at the path shown in the scan summary
- Present the contents using the same apply/review/skip (for specs) or
  create/select/skip (for KB) menus from the audit feedback loop
  (see audit SKILL.md Job 5a/5b for the exact flow)
- After applying: rename the file from `<name>.md` to `<name>.applied.md`
  so it won't be picked up by future curate scans or audit feedback loops

### 2k — Decisions roadmap needed

**Guard:** Only run this step if "Decisions Roadmap Needed" section exists
in the scan summary. If absent, skip entirely.

From "Decisions Roadmap Needed" in the scan summary:

1. There are 10+ deferred decisions with no current roadmap
2. Present as a high-priority pick list item: "N deferred decisions need
   planning — run `/decisions roadmap` to cluster and prioritize"
3. When the user picks this item: suggest running `/decisions roadmap` in
   a separate session (roadmap is a planning skill, not a curate action)

### 2l — Work group health

**Guard:** Only run this step if any "Work Group:" section exists in the scan
summary. If absent, skip entirely.

**Displaced dependencies:**
1. Work definitions that depend on specs now INVALIDATED
2. These WDs are effectively BLOCKED by a spec that no longer exists
3. For each, use AskUserQuestion with options:
   - **"Author replacement spec"** → suggest `/spec-author` for the missing spec
   - **"Update WD to remove dependency"** → the WD no longer needs this artifact
   - **"Skip for now"** — defer

**Stalled work groups:**
1. Work groups with no WD activity in 14+ days
2. Present the group name, total WDs, completed WDs, and days since last activity
3. For each, use AskUserQuestion with options:
   - **"Check status"** → run `/work-status "<group>"`
   - **"Skip"** — acknowledged, no action needed

**Artifact drift:**
1. WDs whose artifact dependencies were modified after the WD was written
2. The artifact still exists but its content changed — the WD's assumptions
   may be stale
3. For each, use AskUserQuestion with options:
   - **"Review WD"** → read the WD and the changed artifact, assess impact
   - **"Skip"** — the change was minor and doesn't affect the WD

### 2m — Spec annotation coverage gaps

**Guard:** Only run this step if "Spec Annotation Coverage Gaps" section exists
in the scan summary. If absent, skip entirely.

From "Spec Annotation Coverage Gaps" in the scan summary:

**Requirements missing impl- or test-side annotations:**
1. Rows with gap "test-only → missing impl annotation" mean a test is tagged
   with `@spec <sid>.Rn` but no implementation file carries the same tag. The
   requirement may be unimplemented, or the impl exists but was never annotated.
2. Rows with gap "impl-only → missing test annotation" mean implementation is
   tagged but no test is. The requirement may be untested, or a test exists
   but was never annotated.
3. Higher req count per spec = bigger coverage hole.

**APPROVED specs with no annotations at all:**
1. The spec is APPROVED but `spec-trace` found zero `@spec` references in
   source or test. Either the code was never annotated, or the spec no longer
   describes any implemented behavior (overlap with orphaned-spec detection).
2. These are higher-priority than single-requirement gaps because the whole
   spec's traceability is missing.

For each finding, use AskUserQuestion with options:
- **"Backfill via /spec-backfill"** → routes to `/spec-backfill <spec-id>`
  to walk uncovered requirements and apply annotations to existing code.
  This is the right tool for the "no annotations at all" case and for
  single-requirement gaps where the implementation already exists.
- **"Verify via /spec-verify"** → use when the gap may indicate spec→code
  drift (the spec describes behavior that may no longer be in the code, or
  vice versa). `/spec-verify` classifies and repairs spec violations; it is
  heavier than `/spec-backfill` and not the default for pure annotation
  backfill.
- **"Accept gap with justification"** → the gap is intentional (e.g. the
  requirement is pure documentation; no runtime behavior to annotate). Record
  in the curation state review log with a short justification.
- **"Skip for now"** — defer to next /curate pass

### 2m-drift — Annotation drift (partial coverage below 50%)

**Guard:** Only run this step if the "Annotation drift — APPROVED specs
below 50% coverage" subsection exists under "Spec Annotation Coverage Gaps"
in the scan summary. If absent, skip entirely.

This subsection lists APPROVED specs whose annotation coverage has slipped
below 50% — they have *some* annotations (so they are not in the
unannotated bucket above) but are drifting. Rows are ordered by uncovered-
percentage descending, then by spec-file age descending.

If there are 4+ drifted specs, ask the user once whether to handle them
individually or run a corpus walk. Use AskUserQuestion with options:
- **"Run /spec-backfill --all"** → the corpus walk catches drift across
  every spec in one pass; progress persists in `.spec/backfill-log.md`
  so the user can break out and resume.
- **"Walk specs one at a time"** → fall through to per-spec routing below.

For each drifted spec (or each one when walking individually), use
AskUserQuestion with options:
- **"Backfill via /spec-backfill <spec-id>"** → routes to per-spec walk.
- **"Skip for now"** — defer to next /curate pass.
- **"Dismiss as intentional"** — record in the review log so this spec
  no longer appears in drift findings (e.g. an aspirational spec where
  partial coverage is by design).

### 2n — Aging open obligations

**Guard:** Only run this step if "Aging Open Obligations" section exists in
the scan summary. If absent, skip entirely.

From "Aging Open Obligations" in the scan summary:

1. Each row shows a spec ID, age in days since the spec file was last committed,
   and the obligation text.
2. The age is a proxy — the obligation has survived that long without the spec
   being touched, suggesting it's drifted out of active attention.
3. Higher age = more drift. 60+ days is a strong signal; 30-60 is a reminder.

For each aging obligation, use AskUserQuestion with options:
- **"Resolve via /spec-author"** → run `/spec-author` on the spec to either
  author the missing behavior as new requirements or close the obligation as
  intentional
- **"Resolve via /spec-resolve"** → use `/spec-resolve` to work through
  `[UNRESOLVED]` / `[CONFLICT]` markers if the obligation is a conflict
- **"Close as stale"** → the obligation is no longer relevant; remove it from
  the spec's `open_obligations` frontmatter with a short note in the spec's
  design narrative explaining the closure
- **"Skip for now"** — defer to next /curate pass

### 2p — Link rot in KB entries

**Guard:** Only run this step if "Link Rot in KB Entries" section exists in
the scan summary. If absent, skip entirely.

From "Link Rot in KB Entries" in the scan summary:

1. Each row shows a status code, the KB entry path, the URL, and when it
   was last checked. Status `000` indicates a connection failure (DNS,
   timeout, refused). Status `4xx` indicates the server responded but the
   resource is gone.
2. Dead URLs in KB entries silently rot the knowledge: the stored fact
   looks authoritative but the source it claims to ground no longer
   exists. Higher-impact when the KB entry is heavily cross-referenced.
3. The script caches per-URL results (7-day TTL) so the same dead URLs
   don't trigger fresh `curl` requests on every scan.

For each candidate, use AskUserQuestion with options:
- **"Refresh via /research"** (description: "Run /research <subject> to
  re-investigate the topic and replace the dead citation with a current
  source")
- **"Verify via /curate --verify"** (description: "Confirm via WebFetch
  that the URL is genuinely gone before refreshing — useful when transient
  connection failures may have produced false positives")
- **"Mark as accepted"** (description: "The URL is gone but the cited
  fact is still valid in the KB; record acceptance in the curation
  review log so the same URL doesn't resurface")
- **"Skip"** — defer to next /curate pass

If "Refresh": invoke `/research "<subject inferred from KB entry>" context: "curate: dead citation at <kb-path>, URL <url> returned <status>"`.
If "Mark as accepted": append the URL to a `link-rot-accepted` block in
`.curate/curation-state.md`. The cache continues to track it; future scans
surface it but the review log shows the user's prior acceptance.

### 2q — Falsification-lens staleness

**Guard:** Only run this step if "Falsification Lens Staleness" section
exists in the scan summary. If absent, skip entirely.

From "Falsification Lens Staleness" in the scan summary:

1. Each row shows an APPROVED spec, its git first-commit-touched date,
   the lens whose introduction date post-dates the spec, and the keyword
   from the spec body that matched the lens's pattern.
2. The signal: the spec's original Pass 2 falsification predates this
   lens shipping, so attack categories the lens covers (e.g., adversary-
   model patterns from the security lens shipped in v0.14.2) may not
   have been considered.
3. The match is heuristic. A spec mentioning "auth" doesn't necessarily
   need a security depth pass; the user judges whether the lens applies.

For each candidate, use AskUserQuestion with options:
- **"Run depth pass via /spec-author"** (description: "Run
  `/spec-author <spec-id> --depth-pass-only --lens <lens>` to re-falsify
  the spec under the named lens; findings flow through the standard
  arbitration UI")
- **"Decline — lens does not apply"** (description: "The keyword match
  is incidental; the spec's scope does not actually need the lens's
  attack categories. Records a decline so future scans don't resurface
  this lens for this spec")
- **"Skip"** — defer to next /curate pass

If "Run depth pass": invoke
`/spec-author <spec-id> --depth-pass-only --lens <lens>` directly.
If "Decline": append a `falsification-decline` row to
`.curate/curation-state.md` keyed by `<spec-id>:<lens>`. The script
continues to surface this candidate, but the review log shows the
user's prior decline.

### 2o — Subdivision candidates (mature specs that may want to subdivide)

**Guard:** Only run this step if "Subdivision Candidates" section exists in
the scan summary. If absent, skip entirely.

From "Subdivision Candidates" in the scan summary:

1. Each row shows a spec that has grown past one file's worth of behavior
   (≥50 reqs OR ≥15K tokens) AND shows multiple distinct concerns
   (≥2 section headers, no single section dominating).
2. Subdivision is a **natural progression** for these specs — the parent
   stays a full spec retaining cross-cutting requirements, while concern-
   specific reqs move to child specs. The detection is heuristic; a spec
   that looks subdividable on paper may turn out to be a single tightly-
   coupled concern that just happens to have multiple section headers.
3. The script already filters out specs where one section holds ≥90% of
   the requirements (those are mature-but-singular and not real candidates).

For each candidate, use AskUserQuestion with options:
- **"Subdivide via /spec-split"** → run `/spec-split <spec-id>`. The skill
  will propose concern boundaries from the spec's existing section
  structure, confirm with you (with edit option), and execute the split
  with @spec annotation rewrites + automatic rollback on validation
  failure.
- **"Decline — concerns are interlocked"** → mark this spec as a recent
  decline so a future /curate pass doesn't surface it again immediately.
  Add a one-line note to the spec's design narrative explaining why it
  shouldn't subdivide (e.g. "single algorithm, requirement clusters
  reflect implementation phases, not separable concerns").
- **"Defer"** → the spec is a candidate but you're not ready to subdivide
  this session. It will resurface at the next /curate run.

The "Decline — concerns are interlocked" option is important. Subdivision
fragments a coherent contract when forced; it should never be automatic.
Honest declines are a feature, not a failure.

### 2r — KB structural drift

**Guard:** Only run this step if any of the following sections exist in the
scan summary: "KB Filename Collisions", "KB Schema Drift", "KB Type/Location
Mismatch". If none exist, skip entirely.

These three analyses share a goal — keep the KB's structure aligned with
`.kb/_refs/frontmatter.md` so search, cross-reference repair, and type-aware
loaders work. They are presented together because the user's typical action
is the same: review one entry's drift and apply the patch.

**KB filename collisions** (from "KB Filename Collisions" in scan summary):

1. Each row shows a filename that exists at 2+ paths under different
   folders. Cross-folder collisions silently fragment grep, pattern-
   recurrence evidence, and `kb-search.sh` ranking. A reader running
   `grep partial-init-no-rollback.md .kb` gets N hits and cannot tell
   which is canonical.
2. Resolve by renaming the lesser-used variant(s) to disambiguate (e.g.,
   `builder-pre-validation-mutation.md` and `multi-step-init-no-rollback.md`).
3. Alternatively, if the collision is intentional and benign, dismiss with a
   one-line reason — but the default assumption is that a collision is drift.

For each collision, use AskUserQuestion with options:
- **"Rename one variant"** (description: "Choose which path keeps the name; the
  other gets a more specific filename. /curate updates references that point
  at the renamed file.")
- **"Merge into one entry"** (description: "If the entries cover the same
  pattern, merge into the canonical location and delete the duplicate.
  Append the deleted entry's `## Audit Findings` history to the kept entry.")
- **"Dismiss as intentional"** (description: "Record in review log; won't
  resurface")
- **"Skip"** (description: "Defer to next /curate pass")

**KB schema drift** (from "KB Schema Drift" in scan summary):

1. Each row is one issue per entry. Issue codes:
   - `missing-frontmatter` — no YAML block at top.
   - `missing-<field>` — required core or type-specific field absent.
   - `bad-<field>` — value outside the allowed enum (e.g.
     `research_status: foo`).
   - `legacy-<field>` — deprecated value in use (e.g.
     `research_status: archived` should be `deprecated`).
   - `confidence-overclaim` — `confidence: high` without ≥2 corroborating
     sources or `## Found in` entries.
   - `topic-mismatch` / `category-mismatch` — frontmatter field disagrees
     with the file's path (path is canonical).
2. An entry with multiple issues appears multiple times. Resolve them
   together when picking that entry.
3. Drift breaks tag-based search, cross-reference repair, and the
   type-aware loaders in `/research`, audit, and feature-retro.

For each entry (group rows by path), use AskUserQuestion with options:
- **"Apply patches"** (descri

…(truncated)
