# Refine Deck

> Deck hygiene pass — retag stale cards, prune 90-day unverified parks, surface defunct references, orphaned dependencies, and jargon titles. AUTO-INVOKE on "tidy up the deck", "hygiene pass", "clean up the queue", or /refine-deck. The board itself gets refactored each iteration.

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

---


## Codex GoC Command

When this skill says `goc ...`, resolve the executable before running the
command:

- In the `game-of-cards` source checkout, use `uv run goc ...`.
- If `goc` is already on `PATH`, use `goc ...`.
- If this skill is loaded from the Game of Cards Codex plugin, use the
  bundled helper at `<plugin-root>/skills/_goc-bootstrap.sh ...`; the plugin
  root is the parent directory that contains both `skills/` and `bin/`.
- If the plugin root is not obvious from the loaded skill path, locate the
  helper with:

```bash
GOC_BOOTSTRAP=$(find "$HOME/.codex/plugins/cache" -path '*/game-of-cards/*/skills/_goc-bootstrap.sh' -type f -perm -111 -exec ls -t {} + 2>/dev/null | head -n 1)
test -n "$GOC_BOOTSTRAP" || { echo "GoC Codex plugin bootstrap not found" >&2; exit 127; }
"$GOC_BOOTSTRAP" --help
```

Use that helper path in place of bare `goc` for the rest of the skill. Do not
edit deck files directly just because `goc` is not on `PATH`.


## 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 `unverified` parks, 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 via
  `Skill(advance-card) <title> disproved`, or park
  `--tag unverified` per 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
  `unverified` tag (promote) or flip to `disproved`.
- **Demote to disproved.** If three independent rounds have failed
  to reproduce, the lead is dead. `Skill(advance-card) <title>
  disproved` with a one-line "Three rounds attempted; no
  reproduction" rebuttal.
- **Keep parked.** Add a one-line note in `log.md` explaining 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 `none` to `decision` if blocking on a
  framing question.
- Flip to `disproved` if 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):

1. Resolve the path — cards write `engine.py:N` for `goc/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.
2. Anchor = that line's text at the commit that last WROTE the number:
   walk `git log --follow --format=%H -- <card>/README.md` oldest →
   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:N` must not read as present inside
   `path: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.
3. 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.
4. 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`/`class` line
   whose exact text is gone, retry on a unique `def <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 <commit-hash>".

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:

```bash
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:

```bash
goc quality-pass --status all
```

What it surfaces:

- **Title antipatterns** — same regex predicates `goc new` uses 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 via `goc 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:

1. **Filed** as a new card via `Skill(create-card)`.
2. **Disproved** via `Skill(advance-card) <title> disproved` — when
   you re-read the cited code and the candidate is wrong on its
   face.
3. **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.

