When to invoke
Invoke when the user says "tidy up the deck", "check for stale cards", "hygiene pass", "clean up the queue", "archive old", "audit the deck", or invokes /refine-deck. Covers retagging stale cards, pruning 90-day unverified parks, surfacing defunct file:line references, surfacing orphaned dependencies (epics with no children, meta-fix families not wired, log.md migration TODOs), surfacing engineer-jargon titles for retitling, and proposing new canonical tags (XP refactor mercilessly + Kanban continuous improvement).
Preflight
If any ! block below shows goc: command not found, Permission for this action has been denied, or no such file or directory: .game-of-cards/deck/, stop and invoke Skill(kickoff) first. Kickoff detects which setup step is missing (CLI not installed, Bash allowance not granted, project state not scaffolded) and walks the user through it. Re-invoke this skill only after kickoff completes.
Context (project-local extension)
!cat .game-of-cards/hooks/refine-deck.md 2>/dev/null || true
Refine the deck
Every iteration the BOARD gets better, not just the code on it: this
skill is the recurring hygiene tax that keeps the deck's read-pattern
guarantee alive as filing slows down and rot accumulates. The hook
above may extend the flow with project-specific categories or
thresholds (scope rules in reference.md § Rationale).
Surface rot and act on it before commit. Two action paths depending on the finding's nature:
- Hygiene findings (mechanical: stale
unverifiedparks, defunct file:line cites, missing summaries, orphaned-edge mechanical wires) — apply the edit directly; a non-firing tag row is the one exception, reported and never edited. - Structural findings (epic-shaped clusters, missing
canonical-reference families, contribution-recall proposals,
meta-decision umbrellas, newly-emergent tag candidates surfaced
by a project hook's pattern-discovery pass) — file via
Skill(create-card), disprove viaSkill(advance-card) <title> disproved, or park--tag unverifiedper Step 4.5. "Surfaced and discussed in chat" is not a disposition.
Long-form material lives in reference.md — a sibling file in
this skill's directory. Read the named section only when the
situation actually applies:
| Situation | reference.md section |
|---|---|
| Why this pass exists; hook scope rules | Rationale |
| Running the four orphaned-dependency sub-checks | Orphaned-dependency sub-check scripts |
| Anchoring cites; what the recipe declines | Citation anchor check |
goc quality-pass --llm |
Quality-pass --llm flag |
| What the Step 4 report should look like | Example Step 4 output |
| Which findings Step 4.5 covers, escape valve | Step 4.5 scope notes |
Step 1 — sanity floor
!b=.claude/skills/_goc-bootstrap.sh; if [ -f $b ]; then sh $b validate; else goc validate; fi 2>&1 || echo "[refine-deck] validate found rot; the skill body below will route you through fixing it"
If validate fails with half-edge errors, run goc repair-edges to
preview the missing reverse-edge writes, then goc repair-edges --apply and re-run goc validate. If repair reports a structural
cycle, park that card for human review instead of guessing which edge
is wrong. Fix unknown tags / missing required fields FIRST too.
Hygiene runs on a valid deck. The precondition above is intentionally
soft-gated so a failing validator surfaces its output into this
skill rather than blocking the skill load.
Step 2 — survey by category
Stale unverified parks
!b=.claude/skills/_goc-bootstrap.sh; if [ -f $b ]; then sh $b --tag unverified -v; else goc --tag unverified -v; fi 2>&1 || true
For each entry: check created against today's date. Cards parked
90 days that nobody has reproduced or refuted are decay candidates. Options:
- Retry the falsifying recipe. If the body's
"what-evidence-would-falsify-it" recipe is now feasible (infra
exists, sweep budget available), run it. On evidence: drop the
unverifiedtag (promote) or flip todisproved. - Demote to disproved. If three independent rounds have failed
to reproduce, the lead is dead.
Skill(advance-card) <title> disprovedwith a one-line "Three rounds attempted; no reproduction" rebuttal. - Keep parked. Add a one-line note in
log.mdexplaining why this round didn't have the budget; the 90-day clock resets.
Stale-open cards (no log activity)
!b=.claude/skills/_goc-bootstrap.sh; if [ -f $b ]; then sh $b --status open --json; else goc --status open --json; fi 2>&1 | head -100
Cards with status: open whose log.md has no entries in 60+
days are at risk of being forgotten. For each: read the body,
decide if the lead is still real, and either:
- Re-prioritize via
Skill(next-card)(recommend it on the next loop iteration). - Escalate the gate from
nonetodecisionif blocking on a framing question. - Flip to
disprovedif the original evidence has rotted away.
Defunct file:line citations
Check each open card's cites against current code with an ANCHOR
test, not a bounds test. An in-range line number is no evidence the
cite is current: a file that grew keeps every old number valid while
the code that was there moved down, so line ≤ EOF can only fire on
a file that SHRANK. Compare what is AT the cited line now against
what the card says is there.
Scope by what the cite CLAIMS, not by where it sits. A cite asserting
where code lives NOW is in scope in prose and inside a fenced block
alike — the fenced form is a COMMENT LABEL, a # or // marker before
the cite on its line. A cite that is part of a dated record — pasted
grep -n output (path:line:content), a reproduce.py transcript, a
quoted error — is OUT of scope: rewriting its number fabricates output
the command never produced. Repair the labels; leave the records and
report their count apart from the declines below. Undecidable → record.
Per cite (long form: reference.md § Citation anchor check):
- Resolve the path — cards write
engine.py:Nforgoc/engine.py:N; prefer a non-mirror match. A range names one BLOCK: map both endpoints, then check the PAIR — emit only if it is ordered (start <= end) and the span still fits a block. An unordered pair or an implausible span is a DECLINE, reported. A range that ARRIVES incoherent is an earlier pass's damage, not drift: its endpoints anchor to whatever they were last moved onto, so re-mapping launders the corruption. Report it; never rewrite it. - Anchor = that line's text at the commit that last WROTE the number:
walk
git log --follow --format=%H -- <card>/README.mdoldest → newest and take the newest commit where the cite token turns from absent to present — the filing commit for a virgin cite, the repair commit for one an earlier pass rewrote. Anchoring a repaired number at the filing commit reads unrelated code and moves the cite onto it. Presence is SET MEMBERSHIP over that version's extracted cite tokens, never a substring search:path:Nmust not read as present insidepath:N-M, or it inherits the range's older anchor. And a token the card holds at TWO OR MORE in-scope occurrences is undecidable — the occurrences share one history, so no walk anchors them apart. DECLINE it, report it, leave the numbers alone. - Refuse a trivial anchor BEFORE comparing it: a blank, a bare brace,
or anything under ~12 chars matches everywhere, so re-finding it at
the cited offset is no evidence the cite is current. DECLINE and
report it, never
current. Else anchor ≠ the line in HEAD → the cite is defunct. - Relocate the anchor text in HEAD and rewrite the number only on
a UNIQUE match — step 3 already refused the lines that match
everywhere. Never guess. When the anchor is a
def/classline whose exact text is gone, retry on a uniquedef <name>(/class <name>(: a definition is identified by its NAME, not by its parameter list, so a keyword-only argument appended to a signature must not read as "the code was refactored away". Two or more definitions of that name is an ambiguous match — DECLINE. The name rule feeds the endpoint mapper only; step 1's pair check still decides what a range emits.
Cites the recipe declines — ambiguous occurrence, trivial anchor,
anchor gone, ambiguous match, incoherent pair — are REPORTED for a
human to read, never silently skipped. Anchor text that exists nowhere,
with no unique definition of its name either, usually means the cited
code was refactored away: re-read the card and,
if the refactor also fixed the defect, close via Skill(finish-card)
with a note "fixed incidentally by ".
End the step by RE-RUNNING the decision phase over the cards you just wrote: a correctly repaired deck is a FIXED POINT, so it must propose ZERO further repairs. Every per-cite rule passes on a second-round proposal — real anchor, unique match, confident rewrite onto the wrong line — so the re-run is the only thing that catches a pass repairing its own output. A non-empty second round is a recipe defect to file, not more rewrites to apply.
Missing summaries
Pre-2026-05-01 cards may have empty or absent summary: fields.
Surface these:
goc --status all --json | \
jq '.[] | select(.summary == "" or .summary == null) | .title'
For each surfaced card: read the body, write a ≤3-sentence summary into the frontmatter. Mechanical doc edit; no status change.
Tags without firing predicates
Per Skill(card-schema), a tag must satisfy its own row, not a
fixed text window; the row's check column says whether that is
scorable or a judgment (reference.md § Tag sweeps). Survey 5–10
random cards per round. Report, never strip — a non-firing row
costs a line of output, not curated grouping.
Orphaned dependencies
Relational rot the validator cannot see: it enforces edge SYMMETRY
at commit time but not edge ABSENCE — epics with zero linked
children; meta-fix cards whose body lists a family roster but carry
zero edges; open cards with legacy **Depends on:** / **Next:** / **Part of:** body markers but empty schema arrays; unactioned
log.md migration TODOs (formerly parent: X / formerly spawned_from: X). Run the four sub-checks in reference.md
§ Orphaned-dependency sub-check scripts, judge each surfaced card's
edge direction, and wire it via goc advance X --by Y
(symmetric-by-construction, so the validator stays happy). A card
whose family members are code sites has nothing to wire — leave it.
Card metadata quality pass
Title antipatterns + missing-summary scan via:
goc quality-pass --status all
What it surfaces:
- Title antipatterns — same regex predicates
goc newuses to reject filings (engineer-jargon:r88,path-2,phase-3,bug-140,_md_/_py_infixes, camelCase tokens, math symbols). Catches legacy cards filed before the antipattern guard was wired. For each surfaced title: rename viagoc move <old> <new>so cross-references rewrite atomically. - Missing summaries — pre-2026-05-01 cards may lack the
summary:frontmatter field that triage views (goc -v) depend on. For each: read the body, write a ≤3-sentence summary into the YAML.
Step 3 — file new canonical tag candidates
When a coherent body of work emerges that isn't covered by an
existing tag (e.g., a sprint of 6 cards all about a specific
research front), file via Skill(create-card) a card whose DoD is
the SCHEMA.md PR adding the new tag + its predicate. Adding the
tag itself remains a SCHEMA.md PR per the schema's "Adding new
tags" rule; the filing that schedules that PR is imperative. Like
every other structural finding, the candidate either becomes a
card here, gets disproved (the proposed predicate doesn't fire on
a sufficient set), or parks --tag unverified per Step 4.5 — not
a chat-only proposal.
Step 4 — surface and act
For each surfaced issue, output one line documenting the action taken (hygiene) or the card filed / disprove flip / park (structural):
<title>: <issue> → <action>
Sample lines in reference.md § Example Step 4 output.
Step 4.5 — Park-or-disprove unfollowed structural candidates (mandatory)
Project hooks may extend Step 2 with a pattern-discovery pass that surfaces more structural candidates than this round can verify and file. Structural candidates that didn't get applied this round MUST go somewhere durable before commit:
- Filed as a new card via
Skill(create-card). - Disproved via
Skill(advance-card) <title> disproved— when you re-read the cited code and the candidate is wrong on its face. - Unverified via
Skill(create-card) ... --tag unverified— when the candidate has substance but no verification budget this round. Body must include: the candidate's hypothesis with file:line (verbatim quote), why deferred, falsification recipe, the category (Step 2 sub-section) that surfaced it.
Scope, minimum-vs-maximum bound, and the noise escape valve:
reference.md § Step 4.5 scope notes.
Cross-references
Skill(advance-card)— for status flips (disproved / re-open / unblock).Skill(card-schema)— tag application predicates and the schema PR contract for new tags.Skill(create-card)— when a hygiene issue surfaces a NEW defect (e.g., the defunct citation reveals a real bug, not just rot), file via create-card.- Project commit workflow — to land the hygiene edits as a
chore(deck): hygiene pass — <date>commit.