/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:
- ADR pressure — decisions under concentrated change (scope being actively modified)
- ADR gravity — files implicitly related to decisions but not in their scope
- Hub files — shared dependencies across 3+ decisions (fragility/test concerns)
- ADR drift — code diverging from architectural decisions
- Stale KB — research that may have better approaches now given what's been built
- Implicit dependencies — gaps between independently-designed features
- Orphaned areas — high-churn files with no structured knowledge behind them
- Unspecified shared types — foundational types referenced by 3+ specs with no spec
- Spec obligations — DRAFT specs with unresolved conflicts blocking approval
- Spec-code drift — specs whose domain code changed after the spec was written
- Cross-reference gaps — KB entries and ADRs with missing related/source links
- Missing
@specannotations — APPROVED specs with reqs that lack impl-side or test-side annotations - Aging open obligations — obligations on specs that haven't been committed in 30+ days
- Link rot — KB-cited URLs that no longer resolve (4xx or connection failures)
- 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)
- KB filename collisions — entries sharing a filename across folders (silently fragments search and pattern-recurrence evidence)
- KB schema drift — frontmatter that does not match
.kb/_refs/frontmatter.md(missing/bad-enum fields, confidence overclaim, path/frontmatter mismatch) - KB type/location mismatch — adversarial findings outside
patterns/<concern>/or feature-footprints outsidearchitecture/feature-footprints/ - KB citation drift in source —
// KB:/# KB:citations in changed source files that point at missing entries or whose entry'sapplies_todoesn't include the source file (closes the loop with thecheck-kb-ref.shPostToolUse hook) - Spec graduation candidates — specs marked
status: DEPRECATEDthat are stillstate: APPROVED(still entering/spec-resolvebundles despite the author's intent to retire them — finalize displacement or retract the status) - Spec corpus xref drift — corpus rollup of unresolvable
decision_refs/kb_refsacross all specs (per-specspec-validate.shwarnings are easy to miss at scale) - Spec annotation coverage rollup — corpus-level summary across the four buckets (fully covered / drift / bare-only / unannotated). Frames
/spec-backfill --allvs row-by-row routing decision. - 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.txtso future scans don't re-prompt the same items.--analysis <name>— when used with--verify, restrict to a single analysis:link-rot,falsification-stale, orall(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:
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 findingresolvedand continue.rc=1→ trigger phrase detected. Surface to user via AskUserQuestion with validator stderr. Blockresolveduntil user clears.rc=2→ tooling error. Log and treat asrc=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:)
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:
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:
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/curaterun).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 .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 .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
--initflag: pass--init - If
--deeperflag: pass--window 6 - If the user passes
--obligation-age-daysor--max-specs-traced, forward them - The
--verifyflag 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:
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:
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 ofLAST_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:
- 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
/curatecadence. - 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. - 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. - 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.
- 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.
- 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.
- For link-rot:
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):
- ADRs with 2+ constrained files changed in the scan window
- Higher pressure % = more of the decision's scope is actively changing
- Read the ADR and assess: is the code evolving within the decision, or away from it?
- High pressure (>60%) → strong signal for re-evaluation
ADR Gravity (from "ADR Gravity" in scan summary):
- Files that co-change with ADR-constrained files but aren't in the ADR's scope
- These are implicit relationships — the decision's influence is wider than documented
- Assess: should these files be added to the ADR's
files:field, or is the co-change coincidental? - High gravity (5+ unconstrained files for one ADR) → potential isolation problem.
The decision may have drawn the boundary in the wrong place. Flag for
/architectreview 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):
- Files co-changing with 3+ ADRs' constrained areas
- These are fragility points — changes here ripple across multiple decisions
- Flag as test coverage concerns: "This file is a shared dependency across architectural decisions. Worth ensuring test coverage is solid."
Flat artifact correlations (from "Artifact Correlations" where Type is ADR):
- Individual ADR file references not captured by pressure (single-file changes)
- Read the referenced ADR, compare stated approach against changed files
- Check if "Conditions for Revision" have been met by recent changes
2b — KB + hindsight review
For each entry in "Stale KB Entries":
- Note the KB file and how long since last research
- Cross-reference with "Churn Hotspots" — is the area the KB covers actively changing?
- 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:
- Note that implementation has changed since research was done
- 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:
- Identify file pairs that co-change but were designed in separate features
- Check if cross-feature test coverage exists for the shared files
- Flag gaps where independently-designed features share files without cross-coverage
2d — Orphaned areas
From "Orphaned Areas" in the scan summary:
- Identify high-churn files with no KB, ADR, or feature coverage
- 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:
- Source files that changed but their corresponding tests didn't
- Cross-reference with feature archives — were these files part of features that should have had test updates?
- Flag files where the drift is significant (3+ source commits with no test change)
- 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:
- Archived feature domains that made implicit decisions (no governing ADR)
- For each candidate, assess whether the decision is significant enough to warrant formal documentation
- Present as items the user can decide, draft as ADR, defer, or dismiss
- This subsumes the standalone
/decisions backfillcommand — 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:
- Items from "What This Decision Does NOT Solve" sections of confirmed ADRs that have no corresponding deferred decision stub
- These are architectural concerns the team explicitly scoped out when making
a decision — they are effectively deferred work invisible to
/decisions triage - Group items by parent ADR for presentation
- 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:
- Types referenced by 3+ specs that have no spec of their own
- These are foundational types with implicit contracts — multiple specs depend on their behavior but nobody has defined what that behavior is
- Rank by reference count — higher count = more dependent specs = bigger risk
Specs with open obligations:
- Specs with
[UNRESOLVED]or[CONFLICT]markers oropen_obligationsin frontmatter - These are blocking downstream work — DRAFT specs can't be relied on until obligations are resolved
- Higher obligation count = more blocking
Obligation registry (from _obligations.json):
- 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.
- Group by spec for display. Show affected requirement count and blocked_by.
- Route to
/work-decompose "<group>" --from-obligationsto convert obligations into a work group with proper WD ordering. This is the primary action — obligations without a work group have no implementation path. - Higher affected-requirement count = larger implementation gap.
Spec-code drift:
- Specs whose domain files have been committed since the spec was created
- Higher commit count = more likely the spec no longer matches reality
- Cross-reference with ADR pressure — if the same area has both ADR pressure and spec drift, it's a stronger signal
Undecided absent behaviors:
- Specs with
[ABSENT]requirements — behaviors that downstream specs assume but the implementation doesn't provide - These are unresolved design decisions: each
[ABSENT]requirement needs an explicit promote/preserve/defer choice - Higher count = more implicit assumptions without backing decisions
Orphaned specs (no matching source code):
- APPROVED specs whose subject tokens were not found in any source file
- These may describe behavior that was removed without updating the spec
- 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):
- Entry pairs that share 2+ tags but have no
relatedlink between them - Higher tag overlap = stronger signal that these entries should reference each other
- Entries in different categories are more valuable links — same-category entries are already navigable via category indexes
- 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:
- Entries that target the same source files/patterns but don't reference each other
- These likely describe different aspects of the same code — a
relatedlink helps the Research Agent find all relevant context when loading one entry - Stronger signal than tag overlap because file paths are specific
ADR evaluation references not in KB Sources:
- KB entries cited in evaluation.md scoring that don't appear in the ADR's KB Sources Used table
- These are missing traceability links — the ADR used this research during evaluation but doesn't formally reference it
- 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:
- Each row is a
spec-updates.mdorkb-suggestions.mdfile from a completed audit where the user skipped or deferred the feedback loop - 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
- 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>.mdto<name>.applied.mdso 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:
- There are 10+ deferred decisions with no current roadmap
- Present as a high-priority pick list item: "N deferred decisions need
planning — run
/decisions roadmapto cluster and prioritize" - When the user picks this item: suggest running
/decisions roadmapin 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:
- Work definitions that depend on specs now INVALIDATED
- These WDs are effectively BLOCKED by a spec that no longer exists
- For each, use AskUserQuestion with options:
- "Author replacement spec" → suggest
/spec-authorfor the missing spec - "Update WD to remove dependency" → the WD no longer needs this artifact
- "Skip for now" — defer
- "Author replacement spec" → suggest
Stalled work groups:
- Work groups with no WD activity in 14+ days
- Present the group name, total WDs, completed WDs, and days since last activity
- For each, use AskUserQuestion with options:
- "Check status" → run
/work-status "<group>" - "Skip" — acknowledged, no action needed
- "Check status" → run
Artifact drift:
- WDs whose artifact dependencies were modified after the WD was written
- The artifact still exists but its content changed — the WD's assumptions may be stale
- 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:
- Rows with gap "test-only → missing impl annotation" mean a test is tagged
with
@spec <sid>.Rnbut no implementation file carries the same tag. The requirement may be unimplemented, or the impl exists but was never annotated. - 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.
- Higher req count per spec = bigger coverage hole.
APPROVED specs with no annotations at all:
- The spec is APPROVED but
spec-tracefound zero@specreferences in source or test. Either the code was never annotated, or the spec no longer describes any implemented behavior (overlap with orphaned-spec detection). - 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-verifyclassifies and repairs spec violations; it is heavier than/spec-backfilland 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.mdso 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 " → 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:
- Each row shows a spec ID, age in days since the spec file was last committed, and the obligation text.
- The age is a proxy — the obligation has survived that long without the spec being touched, suggesting it's drifted out of active attention.
- 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-authoron the spec to either author the missing behavior as new requirements or close the obligation as intentional - "Resolve via /spec-resolve" → use
/spec-resolveto 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_obligationsfrontmatter 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:
- Each row shows a status code, the KB entry path, the URL, and when it
was last checked. Status
000indicates a connection failure (DNS, timeout, refused). Status4xxindicates the server responded but the resource is gone. - 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.
- The script caches per-URL results (7-day TTL) so the same dead URLs
don't trigger fresh
curlrequests on every scan.
For each candidate, use AskUserQuestion with options:
- "Refresh via /research" (description: "Run /research 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:
- 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.
- 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.
- 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:
- 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).
- 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.
- 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):
- 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.shranking. A reader runninggrep partial-init-no-rollback.md .kbgets N hits and cannot tell which is canonical. - Resolve by renaming the lesser-used variant(s) to disambiguate (e.g.,
builder-pre-validation-mutation.mdandmulti-step-init-no-rollback.md). - 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 Findingshistory 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):
- 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: archivedshould bedeprecated).confidence-overclaim—confidence: highwithout ≥2 corroborating sources or## Found inentries.topic-mismatch/category-mismatch— frontmatter field disagrees with the file's path (path is canonical).
- An entry with multiple issues appears multiple times. Resolve them together when picking that entry.
- 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)