Codex GoC Command
When this skill says goc ..., resolve the executable before running the
command:
- In the
game-of-cardssource checkout, useuv run goc .... - If
gocis already onPATH, usegoc .... - 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 bothskills/andbin/. - If the plugin root is not obvious from the loaded skill path, locate the helper with:
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
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.