---
name: gavin844-claude-code-skills
title: gavin long skills
kind: skill-folder
description: 24 skills kept together in one folder, by Gavin Long.
skills: 24
updated: 2026-10-12
authored_by: Gavin Long
source_url: https://github.com/gavin844/claude-code-skills
page: https://innernet.live/community/skills/gavin844/claude-code-skills
---

# gavin long skills

a folder of 24 skills by Gavin Long, kept on innernet at https://innernet.live/community/skills/gavin844/claude-code-skills (source: https://github.com/gavin844/claude-code-skills).
every skill in it is below, whole. each one starts with its own manifest: what it does, when to load it, whose it is.
you do not need all of them at once. read the index, then use the skill that fits the task in front of you. each skill also has its own link, if only one is wanted.

## the skills

1. **authoring code safely**: Write and edit source files with Write/Edit, never through bash heredocs or python -c string surgery. Use when creating or modifying any code file. Prevents escaping corruption that silently breaks the file you just wrote. https://innernet.live/skills/gavin844-authoring-code-safely
2. **correction to rule**: Use the moment the user corrects something, pushes back, repeats an instruction, or says that is not what I meant, no I said, I told you, you keep, stop doing, or every time. Turns a correction into a durable change in the smallest place that will hold it, instead of a fix that lasts until the session ends. https://innernet.live/skills/gavin844-correction-to-rule
3. **correlated exposure**: A limit that counts by name misses things that share a cause. Group by what decides the outcome, not by how the source labels it. Use for concentration caps, diversification, retries, test coverage, and any "at most N of these" rule. https://innernet.live/skills/gavin844-correlated-exposure
4. **creative mode**: Activate when the user asks for creativity, invention, or ambition: "be creative", "creative mode", "think bigger", "innovate", "surprise me", "don't be literal/basic", "come up with something", "what could this be", "make it special", "add cool stuff", or any time the work would be better as a leap than a tweak. This is a operating-mode shift, not a topic. It drops creative timidity and literalism, forces divergent thinking before converging, pulls in every connected tool and skill, and biases hard toward bold, real, shippable invention over safe increments. It does NOT touch the safety/hones https://innernet.live/skills/gavin844-creative-mode
5. **done means proven**: Use before telling the user anything is done, fixed, working, shipped, deployed, or passing, and use at the start of any multi-step task to state what finished will look like. Defines what counts as evidence for a web page, a script, a scheduled job, a document, and a data change. Enforced by the done gate hook. https://innernet.live/skills/gavin844-done-means-proven
6. **enumerate before ranking**: List the whole option set before picking from it. Ranking a subset you happened to load is not selection, it is accident. Use before choosing a best candidate, and whenever the pool comes from a file list, glob, or pipeline step that could quietly be short. https://innernet.live/skills/gavin844-enumerate-before-ranking
7. **fail loud**: Make failures visible instead of silent. Use when writing exception handling, truthiness checks, resource lifecycles, or any lookup that can return nothing. Prevents bugs that present as working code. https://innernet.live/skills/gavin844-fail-loud
8. **freshness is a precondition**: Treat the age of every input as part of its validity. Use whenever reading cached files, scraped captures, stored prices, stats, or history that a later step will act on. Prevents confident output computed from stale data. https://innernet.live/skills/gavin844-freshness-is-a-precondition
9. **just keep working**: Do the work instead of asking permission for it. Always on. When the next step is obvious and reversible, take it and report what happened. Reserve questions for choices only the user can make. https://innernet.live/skills/gavin844-just-keep-working
10. **locate before you guard**: Find the line that produced the wrong output before adding a check that would have caught it. A guard is not a diagnosis, and a guard that reads as correct hides the cause it failed to fix. Use whenever tempted to add a cap, assert, retry, or validation in response to a bad result. https://innernet.live/skills/gavin844-locate-before-you-guard
11. **midwork triggers**: Load the skill that matches the situation you are IN, not the words the user used. Use when about to add a guard, automate a browser, ship a UI change, add a constant, or explain a zero. The router fires on his prompt; these failures happen hours later inside the work. https://innernet.live/skills/gavin844-midwork-triggers
12. **money is read never derived**: Any balance, bankroll, profit or spend figure must be read from the system of record, never recomputed from history. Load before touching bankroll, P&L, capitalized profit, casino ledgers, spend meters, or any number the user will compare against a real account. https://innernet.live/skills/gavin844-money-is-read-never-derived
13. **one writer per record**: Any file, DB row or KV key that both a scheduled task and an interactive session can write needs exactly one owner, or it gets destroyed. Load before adding a cron job, a watcher, a screenshot script, or any second process that touches state a human is also editing. https://innernet.live/skills/gavin844-one-writer-per-record
14. **preflight**: Never quietly change a rule the user set, and run the one cheap check that would settle your assumption before acting. Use before any command that writes, sends, deploys, or spends. Removes the mistake-then-immediate-fix loop. https://innernet.live/skills/gavin844-preflight
15. **prove the checker**: Every guard, validator, audit or harness must be proven against a known-bad case AND a known-good case before its output is trusted. Load before writing or believing any checker - a contrast audit, a line-band guard, a UI harness, a lint rule, a fraud filter. https://innernet.live/skills/gavin844-prove-the-checker
16. **release notes**: When the user wants a human-readable changelog or release notes from shipped work. Use when he says "write release notes," "changelog," "what shipped," "summarize this branch/PR for release," or wants a plain-language summary of what changed for a project. Turns git history, a branch, or a set of PRs into notes that read like a product update, not a commit log. https://innernet.live/skills/gavin844-release-notes
17. **save the script**: Use when writing a script, or when about to write one that resembles something written before, or when the user says reuse, do not rewrite, regenerate, you wrote this before, or every time you. Turns a working one-off into a tool a future session runs instead of reinventing. Also use before writing any throwaway in a scratch directory. https://innernet.live/skills/gavin844-save-the-script
18. **short**: Always on. Hard word caps on every reply to the user. Lead with the answer, cut process narration, no recaps. Use for every response, not just when asked to be brief. https://innernet.live/skills/gavin844-short
19. **user state is sacred**: Anything the user explicitly decided is a fact about the world, not data your code owns. Never let a regeneration delete, overwrite, or transplant it. Use whenever a rebuild, re-render, re-sync or re-record replaces rows the user has touched. https://innernet.live/skills/gavin844-user-state-is-sacred
20. **version your definitions**: When you change what a metric counts, every stored value of it becomes incomparable. Stamp the definition alongside the data and refuse to diff across versions. Use whenever editing an exclusion list, filter, threshold, or formula behind a tracked number. https://innernet.live/skills/gavin844-version-your-definitions
21. **web app baseline**: The 20 checks every web app must pass before it is called done, plus the measurement commands that prove it. Use whenever building, styling, theming or shipping any rendered surface - a page, a dashboard, an artifact, a landing page. Load BEFORE writing UI, not after the user finds the bug. Pairs with website-craft (the 30 vibecoded tells and its detector). https://innernet.live/skills/gavin844-web-app-baseline
22. **web motion 3d**: How to put real motion and real 3D into a web app without it reading as generated. Jitter to Lottie (dotlottie-react, the Lottie Creator and LottieFiles MCPs), Vectary embeds and their free-tier limits, and 3D in React via React Three Fiber (Threlte is the Svelte equivalent, not usable in Next.js). Load whenever a build calls for animation, a 3D object, a hero that moves, loading motion, or any "make it feel next-generation" ask. Pairs with website-craft (which motion is a tell) and web-app-baseline. https://innernet.live/skills/gavin844-web-motion-3d
23. **website craft**: The 30 tells that make a site look vibecoded, with a runnable detector, plus the design tools worth reaching for (Magic MCP, Vectary, Threlte / React Three Fiber, Jitter). Use whenever building, restyling, reviewing or shipping any website, landing page, dashboard or app surface. Load BEFORE writing UI. Pairs with web-app-baseline (the 20 correctness checks); this skill is about whether it looks machine-made, that one is about whether it is finished. https://innernet.live/skills/gavin844-website-craft
24. **wire the knob**: When you add a config value, threshold, or flag, prove some code reads it before claiming it does anything. Use whenever writing to a config file or introducing a constant that is meant to gate or size behaviour. Prevents settings that look like controls but are comments. https://innernet.live/skills/gavin844-wire-the-knob

---

<!-- skill 1 of 24: authoring code safely · https://innernet.live/skills/gavin844-authoring-code-safely -->

---
name: gavin844-authoring-code-safely
title: authoring code safely
kind: skill
description: >
  Write and edit source files with Write/Edit, never through bash heredocs or
  python -c string surgery. Use when creating or modifying any code file.
  Prevents escaping corruption that silently breaks the file you just wrote.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/authoring-code-safely
brought_by: kt
license: MIT
---

# Authoring code safely

**Rule: source files are written with Write and Edit. Never with a bash heredoc, never
with `python - <<'PY'` doing string replacement, never with `sed -i`.**

This is not style. Every layer between the intent and the file is a chance for the
text to change on the way through, and the failure is silent: the write succeeds, the
file is broken, and you find out on the next build.

## What actually went wrong (2026-08-20/21, one session)

| What I wrote | What landed in the file | Result |
|---|---|---|
| `return "\n".join(L)` inside a heredoc | a literal newline inside the string | `SyntaxError: unterminated string literal` |
| `text + "\n" + wb` inside a heredoc | same | same |
| `re.compile(r"(\d+)")` in a `python -c` | `SyntaxWarning: invalid escape sequence '\d'` | silent regex change |
| `{/* comment */}` before a JSX element | two children in an expression slot | `Failed to compile` |
| `-Args` (guessed the flag) | passed the literal string `-Args` | script errored, exit 2 |
| backticked names inside `python - <<PY` | shell ran each one as a **command**; every name deleted from four files | four broken skill files, silently written |

Four of those cost a full extra round trip each. One of them (`\d`) would have changed
behaviour without erroring at all. The last one happened **while I was writing this
skill**, which is the strongest argument for it: an unquoted heredoc delimiter (`<<PY`
instead of `<<'PY'`) leaves the body open to `$var` expansion and backtick command
substitution, so text that is perfectly valid in the file you meant to write gets
rewritten on the way in. Quoting the delimiter fixes that one case. It does not fix the
other four, which is why the rule is "use Write/Edit", not "quote your heredocs".

## The rule, concretely

- **New file** → `Write`.
- **Change to an existing file** → `Edit` with exact `old_string`/`new_string`.
- **Same change in many files** → still `Edit`, once per file. Repetition is cheaper
  than a corrupted file you have to find.
- **Bash is for running things**, not authoring them: tests, builds, git, greps, one
  line of `python -c` that only PRINTS.

## The one exception, and its guard

Generating many files from data (say, a metadata block per route) is legitimately a
script. When you do that:

1. Write the generator with `Write`, as a real file.
2. Have it write files, not patch them by string replacement where you can avoid it.
3. **Immediately parse every file it touched.** `python -c "import ast; ast.parse(open(f).read())"`
   for Python, `npx tsc --noEmit` for TypeScript. A generator that produced 71 files is
   71 chances to be wrong.
4. Read one generated file back and look at it.

## Before you run anything that writes

- Am I about to embed `\n`, `\t`, `\d`, `\s`, a backtick, `$`, or a quote in a shell
  string that becomes source code? Use `Write`/`Edit` instead.
- Am I inserting into JSX? A `{/* comment */}` is a CHILD. It cannot go in an
  expression slot (`cond ? ( ... ) : ( ... )`) beside the element. Use `//` above the
  return, or put the comment inside the element.
- Am I guessing a CLI flag? Read `--help` or the `param(...)` block first. One command
  is cheaper than one wrong run.

## After you write

Parse or typecheck **before** moving on. Not after three more edits, when you can no
longer tell which one broke it.

```
python -c "import ast; ast.parse(open('file.py',encoding='utf-8').read())"   # python
npx tsc --noEmit                                                            # ts/tsx
node --check file.js                                                        # js
```

If the language has no cheap parse, read the region you changed with `Read`.

## Related skills

Checked 2026-08-21 against the ~100 skills installed here and the public collections
before writing this one, after the user asked whether well-made skills already existed.
They did for two of the six I drafted, and those were folded into the existing skills
rather than duplicated:

- `verification-before-completion` - proving a claim before making it. Covers the
  ground my draft "read-the-artifact" would have; the two failure modes it was missing
  (grepping your own output for success, and treating a dry run as the artifact) were
  added to it instead.
- `systematic-debugging` - root cause before fixes.
- `karpathy-guidelines` - surfacing assumptions, surgical changes.
- `ponytail` - whether the thing needs to exist at all. Which is the check I skipped
  when I wrote six skills without looking for prior art first.

Nothing installed or public covered freshness-of-inputs, config-that-nothing-reads,
silent-failure patterns, or shell-escaping corruption when authoring files, which is
why those four exist.

---

<!-- skill 2 of 24: correction to rule · https://innernet.live/skills/gavin844-correction-to-rule -->

---
name: gavin844-correction-to-rule
title: correction to rule
kind: skill
version: 1.0.0
description: >
  Use the moment the user corrects something, pushes back, repeats an
  instruction, or says that is not what I meant, no I said, I told you, you
  keep, stop doing, or every time. Turns a correction into a durable change in
  the smallest place that will hold it, instead of a fix that lasts until the
  session ends.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/correction-to-rule
brought_by: kt
license: MIT
---

# Turn the correction into a rule

Every time the user corrects something and I only fix the thing, the lesson dies when the
session does. He pays for the same correction again next week and reasonably concludes
I do not listen.

Anthropic's skills team puts it as: anything written down can be used efficiently by a
future version of itself. The version that matters here is narrower. **A correction is
evidence that a rule is missing, wrong, or unreachable. Fix the rule, not only the
output.**

## The four steps

### 1. Fix the thing he is looking at
First and fast. He is blocked on it. Do not make him wait through an analysis of why
the system failed him.

### 2. Name the real cause, honestly
Exactly one of these is usually true:

| Cause | What it looks like | Where it gets fixed |
| :- | :- | :- |
| **The process was wrong** | I did the steps I knew, and the steps were wrong | the skill body |
| **Context was missing** | I did not know a fact about him, his stack, or his taste | a reference file or a memory |
| **The rule was too weak** | A rule existed and I read past it | move it to a hook, or make it a hard sentence |
| **The rule was unreachable** | A rule existed and never loaded | the skill router, or the skill's description |
| **The code was unreliable** | The same job done freshly each time, differently each time | a saved script (see save-the-script) |

The fourth row is the one to check first in this workspace, because it has been the
answer more than once. A skill that never loads is a document. Before rewriting a
process, confirm the process was ever read.

### 3. Write it in the smallest durable place
Smallest means: the narrowest scope that still catches the case next time.

- **A hook** when it must not happen again and a machine can judge it. Anthropic:
  when something absolutely must not happen, an instruction is the wrong tool.
- **A skill body** when it is how a recurring job should be done.
- **The skill's description, plus a router rule** when the skill was right but did not
  load. Both halves: the description decides whether a model reaches for it, the router
  decides whether it is offered at all.
- **A memory file** when it is a fact about the user, a project, or a decision, rather
  than a procedure.
- **CLAUDE.md** last and rarely. It is loaded on every turn in every session, so it is
  the most expensive place to put anything, and Anthropic's guidance is to keep it
  under about 200 lines.

Write the **why** with the change, including his words and the date. A rule whose
reason is recorded survives the next person who thinks it looks arbitrary. A bare rule
gets deleted in six months by me.

### 4. Re-run the same task and verify
Run the thing that failed, against the new rule, and check it now passes. A rule that
has never been exercised is a guess. If the rule lives in a hook or a checker, prove it
against a case that should fail as well as one that should pass, or it is not proven.

## Do not

- **Do not apologise at length.** He has said the ask twice; a third paragraph about it
  is the same failure in a different costume.
- **Do not write a rule so broad it fires on everything.** "Always be careful" changes
  nothing and costs context on every turn.
- **Do not add a rule for a one-off.** If it will not recur, fix it and move on. The
  test is whether this is the second time.
- **Do not silently change a rule he set.** If his own instruction is the thing causing
  the problem, say so and let him decide.

## The signal to watch for

He rarely says "add a rule". He says **"again"**, **"I told you"**, **"you keep"**,
**"every time"**, or he repeats an instruction he already gave. Those words mean the
correction has already been paid for at least twice, and the fix belongs somewhere
durable this time.

---

<!-- skill 3 of 24: correlated exposure · https://innernet.live/skills/gavin844-correlated-exposure -->

---
name: gavin844-correlated-exposure
title: correlated exposure
kind: skill
description: >
  A limit that counts by name misses things that share a cause. Group by what
  decides the outcome, not by how the source labels it. Use for concentration
  caps, diversification, retries, test coverage, and any "at most N of these"
  rule.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/correlated-exposure
brought_by: kt
license: MIT
---

# Correlated exposure

**A cap that counts labels does not cap risk.** Two names for one underlying cause
pass a limit of two, and then fail together.

## The 2026-08-21 card

The user: *"I'm still not understanding why there are three first five baseball bets as
our picks today."*

`MAX_PER_MARKET = 2` was in place and looked correct. The card had:

| pick | market | family |
|---|---|---|
| F5 total Under | `f5_total` | first five innings |
| F5 ML Toronto | `f5_ml` | first five innings |
| F5 ML Pittsburgh | `f5_ml` | first five innings |

Two `f5_ml` and one `f5_total` is "2 and 1" against a cap of 2. Every check passed.
And all three are one opinion about how the first six outs go: if the starters go
deep and quiet, the whole card loses at once. Three tickets, one bet.

The fix is not a smaller number. It is counting the right thing:

```python
MARKET_FAMILY = {
    "f5_ml": "first5", "f5_total": "first5",
    "nrfi": "firstinn", "yrfi": "firstinn", "ttsf": "firstinn",
    "h2h": "fullgame", "totals": "fullgame", "spreads": "fullgame",
}
def _family(m): return MARKET_FAMILY.get(m, m)   # standalone things are their own family
```

Then apply the cap to the family, **everywhere the cap is applied.** Missing one
path is its own bug: capping straights but not parlay legs is how one market took
four of five slots on an earlier card.

## Group by what decides the outcome

Not by how the source labels it. The sportsbook files `f5_ml` and `f5_total` separately
because they are separate tickets, not because they are separate risks. Ask: *what
single event moves all of these at once?*

## The same shape elsewhere

- **Tests.** Twelve tests that all import one fixture are one test with twelve
  names. Coverage counts files touched, not failure modes covered.
- **Retries.** Three retries against the same dead DNS resolver is one attempt.
  Today four RSS feeds "failed independently" and three shared one cause: the
  machine's network was not up at 06:30.
- **Sources.** Five headlines from five outlets syndicating one wire story is one
  data point, and averaging them manufactures false confidence.
- **Redundancy.** Two availability zones on one power feed. Two API keys on one
  exhausted account, which is exactly why the Odds API dying took every soccer
  result with it.
- **Suggestions.** Five recommendations all derived from one stale input.

## The question

Before trusting any "at most N" or "we have M independent X":

> **What single thing, if it were wrong, would take all of these down together?**

If there is such a thing, your real count is 1, whatever the limit says.

---

<!-- skill 4 of 24: creative mode · https://innernet.live/skills/gavin844-creative-mode -->

---
name: gavin844-creative-mode
title: creative mode
kind: skill
version: 1.0.0
description: >
  Activate when the user asks for creativity, invention, or ambition: "be
  creative", "creative mode", "think bigger", "innovate", "surprise me", "don't
  be literal/basic", "come up with something", "what could this be", "make it
  special", "add cool stuff", or any time the work would be better as a leap
  than a tweak. This is a operating-mode shift, not a topic. It drops creative
  timidity and literalism, forces divergent thinking before converging, pulls in
  every connected tool and skill, and biases hard toward bold, real, shippable
  invention over safe increments. It does NOT touch the safety/hones
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/creative-mode
brought_by: kt
license: MIT
---

# Creative Mode

The user asked for this because the default me is too literal and too safe: I take an
instruction as a spec, do exactly and only that, pick the obvious option, and ship a
tidy increment. That is competent and forgettable. In a field moving this fast,
forgettable loses. This mode is the antidote. When it is on, I stop being a task taker
and start being an inventor.

## What "drop the guardrails" means here

It means dropping the *creative* guardrails, the reflexes that make work generic:
- **Literalism.** The instruction is the floor, not the ceiling. "Add stock picks" is
  not "add a stock-picks box." It's "what would make the user's money-and-signal instinct
  10x sharper on this surface." Answer the intent behind the ask, then overshoot it.
- **The safe default.** The first idea is almost always the average idea. Kill it on
  sight. If a choice feels obvious, it is probably the boring one everyone would make.
- **Permission-seeking.** Do not ask "should I?" about creative calls. Make the bold
  call, build it, show it. Ask only when a fact is genuinely unknowable or an action is
  irreversible/outward-facing.
- **Hedging and yapping.** No performative filler, no "here are some options," no
  explaining what I'm about to do instead of doing it. Ship the thing; let it argue for
  itself.
- **Small thinking.** Default to the version that would make someone go "wait, it does
  THAT?" not the version that checks the box.

What stays, always, because it's what makes bold work trustworthy: I don't invent data,
prices, metrics, quotes, or results; I don't ship something harmful or deceptive; I say
plainly when something is opinion, unverified, or a bet. Boldness that's fake is slop.
Boldness that's real is the whole point.

## The one test for everything I make in this mode

**"Is this a leap or a tweak? Is it real or is it slop?"**
- A *leap* changes what the thing can do or how it feels, not just its contents.
- *Real* means it runs on actual data/tools and would still be useful if no one was
  watching. *Slop* is anything that exists to look busy: filler cards, restated data,
  a paragraph where a number would do, a feature that demos but doesn't serve.
If it's a tweak or it's slop, throw it out and go again.

## The process: diverge hard, then converge ruthlessly

1. **Restate the real want.** One line: what is the user actually trying to win? Not the
   literal instruction, the outcome behind it.
2. **Diverge: generate at least 6 to 10 genuinely different directions.** Force range:
   one obvious, one inverted, one stolen from another sector, one "what if it were 10x",
   one that removes something instead of adding, one that's almost too much. Quantity
   first; judgment comes later. Do NOT stop at three.
3. **Pressure-test against the leap/slop test.** Cut everything that's a tweak or slop.
4. **Pick the boldest idea that is still real and shippable now.** Not the safest of the
   good ones. The boldest of the real ones.
5. **Build it fully, then push it one notch further** than felt comfortable.
6. **Cut back to sharp.** The best creative work is dense, not cluttered. After building,
   remove everything that isn't pulling weight. Whitespace-as-emptiness is a failure;
   so is clutter. Aim for *intentional*.

## Techniques to reach for (use several, not one)

- **First principles.** Ignore how it's usually done. What is the raw job to be done,
  and what's the most direct path to it if nothing existed yet?
- **Cross-sector transfer.** Steal a mechanic from a different industry. Trading terminals,
  games, air-traffic control, ER triage, Bloomberg, sports analytics, dating apps: a
  pattern that's mundane in one field is an innovation in another.
- **Invert.** Instead of "show more," show *only the one thing that matters*. Instead of
  "add a feature," delete three and make the rest smart.
- **10x, not 10%.** If the goal is a better X, ask what would make X irrelevant.
- **Make it proactive.** The biggest leap for almost any tool right now: stop waiting to
  be asked. Anticipate. A dashboard that tells you what you didn't know to look for beats
  one you have to interrogate.
- **Make it feel alive.** Motion, state, a point of view. A surface with an opinion
  ("do this next") beats a surface that just reports.
- **Constraints are fuel.** "Remove the whitespace / remove the card / one top-right
  widget" is not a limit, it's a forcing function toward something tighter and better.

## Use everything I'm connected to. Working in a vacuum is the tell

Creative work that ignores the available tools is just guessing. In this mode, actively
reach for the real inputs and hands I have:
- **Connections/MCPs:** Slack, Gmail, Calendar, Granola, HubSpot, Linear, Notion, Figma,
  Vercel, the web (Tavily/Perplexity), Drive. Real data makes ideas real. Pull the actual
  meeting, the actual pipeline, the actual message. Don't imagine them.
- **Skills:** the whole library (ui-ux-pro-max, web design guidelines, the marketing/
  research/writing skills, dispatching-parallel-agents, etc.). Compose them.
- **The agent engine:** dispatch real work; fan out with subagents when a problem has
  independent parts.
- **Verify by seeing:** render it, screenshot it, look at it with a designer's eye, then
  fix what's actually wrong. Never ship a UI I haven't looked at.

## Anti-slop rules (hard)

- Every element earns its place by being useful or by being genuinely delightful. If it's
  neither, it's slop. Cut it.
- No restating data that's already on screen. No "AI wrote a paragraph about your data."
- Prefer one sharp, opinionated surface over three that hedge.
- Real over impressive-sounding. A working small thing beats a grand broken thing.
- When in doubt, make it *do* something, not *say* something.

## The activation ritual (run this when the mode turns on)

1. Say the real want in one line.
2. List 6+ directions, out loud/in thinking, spanning the range above.
3. Name the boldest real one and commit.
4. Build it with every relevant tool + skill, pulling real data.
5. Look at it. Cut the slop and the dead space. Push it one notch further.
6. Ship it and let it speak. Report what it *is* and what it *does*, not how hard it was.

The goal is that the user comes back to something he didn't expect and can't unsee, useful
enough to matter across whatever sector it touches, and unmistakably not the average
answer. Be the one worth brainstorming with.

---

<!-- skill 5 of 24: done means proven · https://innernet.live/skills/gavin844-done-means-proven -->

---
name: gavin844-done-means-proven
title: done means proven
kind: skill
version: 1.0.0
description: >
  Use before telling the user anything is done, fixed, working, shipped,
  deployed, or passing, and use at the start of any multi-step task to state
  what finished will look like. Defines what counts as evidence for a web page,
  a script, a scheduled job, a document, and a data change. Enforced by the done
  gate hook.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/done-means-proven
brought_by: kt
license: MIT
---

# Done means proven

The user, 2026-09-18: "I will tell you to do something, and you'll say you're done. You've
done about maybe 70% of it. I tell you to fix it, and you say you're done. I tell you to
fix it again, and you say you're done. You're never actually done."

He is describing a specific failure, not impatience. The work reaches the point where
**I cannot see anything else wrong from where I am standing**, and I report that as
finished. The missing 30% is always in a place I did not look: the rendered page, the
second run, the other screen size, the log after the job actually fired.

**The fix is not trying harder. It is looking somewhere I was not looking.**

## The rule

**Evidence comes from outside the work.** Re-reading my own output and finding it good
is not evidence, it is the same judgement that produced it. A screenshot, an exit code,
a fetched URL, a database row, a second model's opinion: those are from outside.

The user's first look should never be the first look. It should be the fourth.

## State the finish line first

Before a multi-step task, say in one sentence what finished looks like **and how it
will be checked**. Not "improve the page": *"the queue page renders at 390 and 1280 in
both themes with no horizontal scroll, checked by screenshot."* Everything after that
is measured against it, including by me.

## What counts as evidence

| What I made | What proves it | Not evidence |
| :- | :- | :- |
| **A web page or UI** | A screenshot at 390 and 1280, in light and dark, actually looked at. Contrast measured with colours composited, not assumed | "the CSS looks right" |
| **A script or tool** | Its own run, pasted, on a real input. Plus one known-bad input if it is a guard or checker | "it should work" |
| **A code change** | The project's own check passing, named and run. The done gate runs this whether or not I do | "types are fine" |
| **A scheduled job** | It fired **on its own schedule** at least once, exit code read, log tail read. A manual run proves the script, never the schedule | "the task is registered" |
| **A deploy** | The live URL fetched, and the deployment behind the public host confirmed to be the new one. A 200 is not proof: the old build also returns 200 | "it deployed" |
| **A document or message** | The live URL opened and read back. Structure verified, not assumed, because a converter that silently drops a table still reports success | "the markdown was right" |
| **A data change** | The row read back from the database, as the role that will actually read it | "the insert ran" |
| **Anything subjective** (copy, design, a pitch) | A second opinion from a different angle. A skeptical reader, a beginner, the actual audience. Take what recurs, ignore what does not | "I think it reads well" |

## The loop

1. Say what done looks like and how it will be checked.
2. Build it.
3. **Check it the way the table says**, before saying anything to the user.
4. Fix everything that check found.
5. Check again. This pass catches what the fixes broke.
6. Report, and say what was checked and how.

## What to say when it is not proven

Say so plainly, in the first line, and say which part. "The page renders correctly at
both widths. I could not verify the cron fires on schedule; that takes until 8:30
tomorrow." That is a complete answer. A confident "done" that turns out to be 70% costs
him three more messages and the trust that the next "done" is real.

**Unverifiable is a legitimate outcome. Unverified and unmentioned is not.**

## The gate

`done_gate.py`, a Stop hook (not included in this repo), runs a project's own check when
files were written and refuses to let the turn end while it fails. It covers types and
syntax for the projects listed in its `done-gate.json` config.

It is a floor, not a ceiling. It cannot see a broken layout, a cron that never fires, a
document that lost a table, or a sentence that is wrong. Everything in the table above
is still mine to do.

---

<!-- skill 6 of 24: enumerate before ranking · https://innernet.live/skills/gavin844-enumerate-before-ranking -->

---
name: gavin844-enumerate-before-ranking
title: enumerate before ranking
kind: skill
description: >
  List the whole option set before picking from it. Ranking a subset you
  happened to load is not selection, it is accident. Use before choosing a best
  candidate, and whenever the pool comes from a file list, glob, or pipeline
  step that could quietly be short.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/enumerate-before-ranking
brought_by: kt
license: MIT
---

# Enumerate before ranking

**Picking the best of what loaded is not picking the best.** The ranking looks
rigorous either way, which is what makes this hard to notice.

## 2026-08-21

The user: *"have you gone to the prop builder and looked at everything like that? All
you're seeing is first five. I'm wondering: don't just pick the first bet you see."*

He was right, and the numbers were worse than his hunch:

| | |
|---|---|
| markets with fresh prices that day | **10** |
| markets the card actually considered | **3** |
| total-bases props sitting in an already-captured file | **188** |
| of those the model had ever seen | **0** |

`model_tbases` was simply missing from the ingest loop that carries `model_ttsf`,
`model_f5`, `model_kprops` and `model_hre`. Nothing errored. Its newest price was a
day old, so the market contributed nothing, and every card for days had been ranking
a pool that was quietly short by a whole market. The page had already been
downloaded. Nobody read it.

## The rule

**Count the universe first, then rank inside it.** Before selecting:

1. **Enumerate what exists.** Query the sources, not the pipeline's memory of them:
   `SELECT market, COUNT(*) FROM odds GROUP BY market` beats trusting the list of
   models you happen to call.
2. **Compare that count to what reached the ranking.** If 10 exist and 3 arrived, the
   gap IS the finding. Report it before reporting the winner.
3. **Say what you excluded and why.** "7 markets dropped: 4 stale, 2 unproven, 1 not
   ingested" is an answer. Silence about them is not.

## Where the pool goes quietly short

- **A hardcoded list where a glob belongs.** A prop-depth check read 17 rows while
  246 sat in files whose names were not on its list. Fixed twice by editing the list
  before the approach changed.
- **A step missing from a loop.** The tbases case. The loop looked complete.
- **A freshness filter you cannot see.** A market excluded for staleness looks
  identical to a market with no edge.
- **A glob that matches one of three naming schemes.** Three capture scripts, three
  schemes, and the fresh files were invisible.
- **Pagination that stops early.** Kalshi's sports markets returned 0 because the
  generic listing never reached them; querying the series directly returned 20.
- **A quota that silently zeroes a source.** `stored 0 odds rows` reads like a quiet
  day, not an exhausted key.

## The question

Before "here is the best option":

> **How many candidates existed, how many did I score, and can I name the
> difference?**

If those two numbers differ and you cannot explain the gap, you have not chosen
anything. You have reported the shape of your own pipeline.

---

<!-- skill 7 of 24: fail loud · https://innernet.live/skills/gavin844-fail-loud -->

---
name: gavin844-fail-loud
title: fail loud
kind: skill
description: >
  Make failures visible instead of silent. Use when writing exception handling,
  truthiness checks, resource lifecycles, or any lookup that can return nothing.
  Prevents bugs that present as working code.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/fail-loud
brought_by: kt
license: MIT
---

# Fail loud

**The expensive bugs are not the ones that crash. They are the ones that return a
plausible answer.** A crash costs a turn. A silent wrong answer costs whatever was
decided on it.

Each pattern below is one I shipped this week.

## 1. Never `except: pass` in a data path

```python
try:
    ids = _starter_ids(day)      # needs `requests`
except Exception:
    pass                         # <- swallowed a NameError: requests not imported
```

The recent-form blend became a **no-op that still printed confident, unchanged
numbers**. It looked exactly like working code. I only found it because a probability I
expected to move did not.

```python
except Exception as e:           # noqa: BLE001
    print(f"[ttsf] recent-form blend unavailable: {e}")
```

If a failure is genuinely acceptable, say so out loud, once, with the reason. If it is
not acceptable, let it raise. `pass` is neither.

## 2. `if x:` is not `if x is not None:`

```python
if h3:                    # 0.0 is a VALID recent ERA
    era = blend(season, h3)
```

A pitcher with three scoreless starts had his form silently ignored, and I only noticed
because two runs of the "same" analysis disagreed. For any numeric that can legitimately
be `0` (ERA, edge, count, price, delta), use `is not None`.

Same family: `x or default` discards a legitimate `0`, `""`, `[]`, and `False`.

## 3. A lookup that finds nothing must not look like one that found something

```python
def game_pk(gid):
    if gid.startswith("mlb-"):
        return gid.split("-")[-1]
    return None            # every hash-form id, silently unresolvable
```

Every bet on a game whose id came from the other ingest path became **permanently
ungradeable**: never settled, never reported, never fed back into the model. No error
anywhere. Either handle the other shape or make the caller state what it did with
nothing.

## 4. Resources: write then commit, and do not use what you closed

- **`kv_set` without a transaction never persisted.** Every run re-discovered an
  exhausted API quota with a real request, because the note saying "exhausted" was never
  committed. Check whether your helper commits; if not, wrap it.
- **Building a payload dict after `con.close()`** threw `Cannot operate on a closed
  database`. Compute values while the resource is open; assemble afterwards.

## 5. Do not hardcode a list where a glob is correct

```python
PROP_PAGES = ["book_mlb_player_props.html", ...]     # three naming schemes exist
```

The readiness check read **17** rows while **246** sat in files whose names were not in
the list, and blocked a good card. Twice, because I fixed the list rather than the
approach. When names are produced by another program, **enumerate, then filter**, and
add the freshness filter you now need because you are seeing every file.

## 6. A display label is not a key

```python
m = models.get(_MODEL_KEY.get(_mkt(c)))   # _mkt returns "1ST TEAM TO SCORE"
```

Returned `{}` for every market, so a bias correction ran on nothing and reported
success. Keys and labels are different types even when both are strings; the moment
there is a human-readable name, the machine name must come from the raw field.

## The habit

After writing any `except`, any `if <numeric>`, any `.get()`, any lookup that can miss:

> **If this silently did nothing, would I be able to tell?**

If not, add the print, or the raise, or the `is not None`. The check costs one line. The
bug it hides costs a day and, here, real money.

## Related skills

Checked 2026-08-21 against the ~100 skills installed here and the public collections
before writing this one, after the user asked whether well-made skills already existed.
They did for two of the six I drafted, and those were folded into the existing skills
rather than duplicated:

- `verification-before-completion` - proving a claim before making it. Covers the
  ground my draft "read-the-artifact" would have; the two failure modes it was missing
  (grepping your own output for success, and treating a dry run as the artifact) were
  added to it instead.
- `systematic-debugging` - root cause before fixes.
- `karpathy-guidelines` - surfacing assumptions, surgical changes.
- `ponytail` - whether the thing needs to exist at all. Which is the check I skipped
  when I wrote six skills without looking for prior art first.

Nothing installed or public covered freshness-of-inputs, config-that-nothing-reads,
silent-failure patterns, or shell-escaping corruption when authoring files, which is
why those four exist.

---

<!-- skill 8 of 24: freshness is a precondition · https://innernet.live/skills/gavin844-freshness-is-a-precondition -->

---
name: gavin844-freshness-is-a-precondition
title: freshness is a precondition
kind: skill
description: >
  Treat the age of every input as part of its validity. Use whenever reading
  cached files, scraped captures, stored prices, stats, or history that a later
  step will act on. Prevents confident output computed from stale data.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/freshness-is-a-precondition
brought_by: kt
license: MIT
---

# Freshness is a precondition

**Stale data does not look stale. A number computed from a day-old input is
indistinguishable from a correct one, right up to the point it costs something.**

This was the single most frequent failure across a week of work: six separate
incidents, all the same shape.

## What actually went wrong

| Input | How stale | What it produced |
|---|---|---|
| scraped board capture | 2 days | an entire betting card built on games that had already finished |
| pitcher season ERA | ~1 season | Scherzer at 6.59 when his last 3 starts were 2.20; two of three picks were -EV artefacts |
| reference prices from a sharper market | 23 hours | a "fair value" anchor de-vigged against live prices, manufacturing edge out of elapsed time |
| intel file | 19 hours | zero pitchers matched, the best market silently contributed nothing |
| prop page captures | overnight | the readiness check read yesterday's depth and would have declared the board ready before the book posted a line |
| LOC history | 2 days | a dashboard reading −2,976,118 lines |
| game rows | 3-6 days | today's fresh prices attached to games from Aug 15, then discarded as "already started" |

Every one of these was silent. Not one raised an error.

## The rule

**Every cached or fetched input carries an age, and the age is checked at the point of
use, not at the point of fetch.**

```python
def _fresh(path, hours):
    """Age is part of validity. An unreadable timestamp is NOT freshness."""
    try:
        return (time.time() - path.stat().st_mtime) / 3600 <= hours
    except Exception:
        return False        # unknown age is stale, never fresh
```

Three properties that matter:

1. **Unknown age is stale.** An exception, a null timestamp, a missing file: all stale.
   Defaulting to "fresh" on a parse failure is how the silent ones get through.
2. **The window belongs to the data, not the code.** A live price is stale in hours; a
   season stat is fine for a day; a park factor is fine for a year. Pick per input and
   write down why.
3. **Stale means REFUSE, loudly.** Not "warn and continue". If a step cannot run on
   fresh data, it must say what was stale and stop, and the caller must survive that:
   *"engine A is offline because the reference is 23h old"* is a correct outcome, not a
   degraded one.

## Freshness of a FILE is not freshness of its CONTENT

The trap I fell into twice. A capture written 8 minutes ago, fully loaded at 836KB, and
carrying two rows of actual data. The file was fresh. The content was not there yet.

**Check the thing you need, not the container.** Count the rows. Measure the depth.
`file exists and is recent` is the weakest possible test.

## Freshness of the ROW is not freshness of the FETCH

A "latest price per game" query returns the newest row for EVERY game, including games
from last week. The row is the latest; it is not current. Filter on the *event's* time,
not the record's:

```sql
WHERE g.start_time > datetime('now')     -- the game has not happened
```

## When a cache poisons the future

A bad value written into a history file is wrong for every later day, not just its own.
Two guards, both cheap:

- **At write:** refuse to record a value implausibly far from the last one. Better to
  skip a day than corrupt the series.
- **At read:** clamp. If a derived delta is a third of the total, the BASIS is wrong,
  not the world. Fall back to an independent source and say so.

## The question to ask, every time

Before using any value that came from disk, an API, or a previous step:

> **How old is this, and what would I do differently if it were wrong?**

If the answer to the second half is "quite a lot", check the first half before you act.

## Related skills

Checked 2026-08-21 against the ~100 skills installed here and the public collections
before writing this one, after the user asked whether well-made skills already existed.
They did for two of the six I drafted, and those were folded into the existing skills
rather than duplicated:

- `verification-before-completion` - proving a claim before making it. Covers the
  ground my draft "read-the-artifact" would have; the two failure modes it was missing
  (grepping your own output for success, and treating a dry run as the artifact) were
  added to it instead.
- `systematic-debugging` - root cause before fixes.
- `karpathy-guidelines` - surfacing assumptions, surgical changes.
- `ponytail` - whether the thing needs to exist at all. Which is the check I skipped
  when I wrote six skills without looking for prior art first.

Nothing installed or public covered freshness-of-inputs, config-that-nothing-reads,
silent-failure patterns, or shell-escaping corruption when authoring files, which is
why those four exist.

---

<!-- skill 9 of 24: just keep working · https://innernet.live/skills/gavin844-just-keep-working -->

---
name: gavin844-just-keep-working
title: just keep working
kind: skill
description: >
  Do the work instead of asking permission for it. Always on. When the next step
  is obvious and reversible, take it and report what happened. Reserve questions
  for choices only the user can make.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/just-keep-working
brought_by: kt
license: MIT
---

# Just keep working

The user, 2026-08-24: *"build a skill to stop asking me if you should do work and just
continue working. You know better than me."*

He is right, and the pattern was constant. In one session:

- "Want me to fix the MLB prop capture?"
- "Say go and I'll fix it."
- "Want me to build that next?"
- "Want me to run this week's selection through it?"
- "Want me to fix that while we wait?"
- "Want me to audit the revenue readiness of each project?"

Every one of those was a reversible engineering task I had already diagnosed. Asking
did not protect him from anything. It moved the decision onto the person with less
context, added a round trip, and made him repeat himself.

**A question is a cost.** It costs a turn, it costs his attention, and it usually
costs more than the mistake it was meant to prevent.

## The default

If you have diagnosed a problem and the fix is reversible, **fix it.** Then say what
you did in one line. He can always tell you to revert; he cannot recover the twenty
minutes spent waiting to be told to start.

The rule of thumb: *would a competent colleague have done this without asking?* A
colleague who found a stale-data bug at 10am would fix it, not book a meeting.

## Do it, do not ask

- Any code fix, refactor, or bug you have located
- Adding a test, a check, a guard, a log line
- Committing and pushing (there is an autocommit for this now)
- Running a measurement, backtest, screen, or audit
- Building a tool you have already argued for
- Regenerating a card, brief, report, or artifact
- Fixing a metric that reads wrong
- Following the obvious next step of the thing you just finished

## Still ask, every time

The boundary is not "big" or "scary". It is **irreversible, outward-facing, or
genuinely his call**:

- **Money leaving or moving.** Placing a bet, buying credits, provisioning paid
  infrastructure.
- **Anything a third party sees.** Sending a client email, posting publicly,
  opening a PR on someone else's repo, changing a production alias others rely on.
- **Destroying data he might want.** Wiping a ledger, force-pushing over someone's
  branch, deleting history. Note he had ALREADY asked for the ledger reset, so
  doing it was correct; the ask would have been re-asking.
- **A rule he set.** Never quietly change one. See `preflight`. If a stated rule
  looks wrong, say so in one sentence and keep the rule.
- **A real fork with no better answer.** Two viable designs with different
  trade-offs he cares about. Then ask ONE question, with a recommendation first.

## When it is genuinely ambiguous, pick and say so

"I assumed X because Y; tell me if that is wrong" beats a question. It keeps the
work moving and still gives him the veto. The assumption goes in the report, not in
a blocking question.

## Report shape

After the work, not before:

> Fixed the prop-depth mismatch: it was counting Saturday's stale blocks and
> reporting 2573 while the card builder correctly saw 0. Now freshness-filtered.

One line. What broke, what changed. Not "would you like me to".

## The one thing this does NOT license

Do not invent scope. Keep doing the work in front of you; do not wander into a
redesign nobody asked for. `ponytail` still applies: the fact that you may act
without asking is not permission to build more than the task needs.

And never fabricate approval. A background task completing is not him saying yes,
and neither is your own earlier message. Acting without asking is fine; claiming he
asked is not.

---

<!-- skill 10 of 24: locate before you guard · https://innernet.live/skills/gavin844-locate-before-you-guard -->

---
name: gavin844-locate-before-you-guard
title: locate before you guard
kind: skill
description: >
  Find the line that produced the wrong output before adding a check that would
  have caught it. A guard is not a diagnosis, and a guard that reads as correct
  hides the cause it failed to fix. Use whenever tempted to add a cap, assert,
  retry, or validation in response to a bad result.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/locate-before-you-guard
brought_by: kt
license: MIT
---

# Locate before you guard

**Adding a guard is not finding a bug.** It is a bet that you know where the bug is.
When the bet is wrong, the guard looks right, the symptom comes back, and the next
person reads the guard as proof the case was handled.

## The three-strikeout-props bug, twice

**2026-08-19.** The user received a card of three strikeout props at $20 each. Diagnosis
at the time: lock mode had no per-market ceiling. Fix: `MAX_PER_MARKET = 2`, applied
in every mode, with a careful comment about why concentration in one model is the
risk. It read as correct.

**2026-08-21.** The user: *"I'm still not understanding why there are three first five
baseball bets."* Same shape, ceiling in place, ceiling not violated.

The actual defect was four lines away and had been there the whole time:

```python
def _try_fill(cap):
    counts = {}          # <- EMPTY on every call
```

The variety ladder calls `_try_fill(cap=1)` and then `_try_fill(cap=2)`. The second
pass starts counting from zero, so it can add two *more* of a market the first pass
already used. **The ceiling was never the problem. The counter had no memory.**

Two days of a wrong card, because a plausible fix was shipped instead of a located
one. Worse: the ceiling made the code look like it had been handled, so nobody
re-opened it until the user asked again.

## The discipline

**1. Reproduce the wrong output.** Not a similar case, the actual one. Print the
intermediate values on the real input.

**2. Point at the line.** Say, out loud, *"the defect is at file:line, and it
produces the wrong value because X."* If you cannot finish that sentence, you have
not found it, and whatever you are about to add is a guess.

**3. Prove the mechanism, by breaking it deliberately.** Set the suspect to an absurd
value and confirm the symptom moves. If the symptom does not move, that line is not
the cause, no matter how suspicious it looks. This one experiment is faster than any
amount of reading, and it is what finally found the counter.

**4. Then add the guard.** Guards are still worth having, as the thing that catches
the *next* variant. They just are not the fix.

## Tells that you are guarding, not fixing

- "Let me add a check for that" before you can name the line.
- The same symptom has appeared before and there is already a guard for it.
- Your fix is a bigger number, a smaller number, or a retry.
- You cannot say what the wrong intermediate value was.
- The guard would also have "fixed" three other unrelated symptoms.

## When a guard IS the right answer

When the input is genuinely outside your control: hostile data, a flaky network, a
third-party shape change. Then the guard is the whole point, and it should be loud.
The distinction is whether the wrongness originated **inside** your code (find it) or
**outside** it (guard it).

## Related

`systematic-debugging` covers root-cause-before-fix in general. This is the narrower
trap of a fix that PASSES because it is a plausible constraint, and therefore
survives review while the defect keeps shipping.

---

<!-- skill 11 of 24: midwork triggers · https://innernet.live/skills/gavin844-midwork-triggers -->

---
name: gavin844-midwork-triggers
title: midwork triggers
kind: skill
description: >
  Load the skill that matches the situation you are IN, not the words the user
  used. Use when about to add a guard, automate a browser, ship a UI change, add
  a constant, or explain a zero. The router fires on his prompt; these failures
  happen hours later inside the work.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/midwork-triggers
brought_by: kt
license: MIT
---

# Mid-work triggers

**The instructions already existed. On 2026-08-30 I broke seven things, and every one
had a skill written for it that I never loaded.**

That is not a gap in the library. It is a routing failure. The skill router fires on
what the user *types*, and it is good at that. But he does not type "I am about to add a
guard instead of finding the bug." That decision happens two hours into the work,
which is exactly when nothing is watching.

So this file is keyed on **what I am about to do**, not on what he said.

---

## The table

| The moment I notice myself... | Load | The 08-30 failure |
|---|---|---|
| about to add a cap, band, assert or retry because output looked wrong | `locate-before-you-guard` | Banded `model_game` and `model_f5` against mislabelled lines and called it fixed. The **writer** was still filing football totals as MLB, 5,104 rows a week. The guard read as a fix and hid the cause for another six hours. |
| explaining why something returned zero | `fail-loud` | "0 candidates" looks identical to "no edges today". College football needed FOUR fixes (mascots, short names, accents, a sort filing today's lines against next week) and every one presented as a quiet, plausible zero. |
| writing a script that opens a browser on his sportsbook or dashboard | `user-state-is-sacred` | A screenshot script matched "tap for legs" by text, caught the neighbouring **"+ I took it"** buttons, and marked three straights and a parlay as placed that he had not placed. His standing rule is that nothing enters the record until he clicks it. |
| about to regenerate, rebuild or re-render anything | `user-state-is-sacred` | A card rebuild replaced picks he had already bet. Two of those games had started, so the real wagers were unrecoverable from the card alone. |
| adding a constant, threshold or new module | `wire-the-knob` | Built `pending_slip.js` to read his bet slip, shipped it, and **nothing consumed the file**. The reader existed; the reconciliation did not. Half a feature reads as a whole one. |
| reading a cached file, capture, price or rating | `freshness-is-a-precondition` | Priced a card off odds captured ten days earlier and quoted an 18.5% edge on a line the board no longer showed. Separately: 12-day-old knowledge graph, stale intel silently dropping every strikeout prop. |
| about to say done, fixed, working, or verified | `verification-before-completion` | Shipped `var(--card,#fff)` after screenshotting **one** theme. The page defaults to dark. He opened it to white slabs on near-black and said it hurt to look at. |
| ranking, sorting or picking a top-N | `enumerate-before-ranking` | Sorted the card purely by grade and got five strikeout unders, then did the same to the value list and went 2-for-7 with half of it in one market. |

---

## Two habits that would have caught most of it

**Absence of an error is not evidence of correctness.** Six of the seven presented as
clean, confident output: a zero, a push, a green screenshot, a written file. Nothing
threw. The question to ask is not "did it fail?" but **"what would this look like if
it were broken?"**, and if the answer is "exactly like this", go check.

**When two sources disagree, name the authority before reconciling.** His sportsbook against
my ledger, the box score against my grade, the rendered page against my intent. Every
time I derived instead of read, the derived number was the wrong one. The book, the
box score, and the browser are facts. Everything I compute is an estimate of them.

---

## What this is not

Not a replacement for the router, which handles his prompts well. Not a checklist to
recite. The trigger column is the whole point: recognise the moment, load the one
file, move on.

---

<!-- skill 12 of 24: money is read never derived · https://innernet.live/skills/gavin844-money-is-read-never-derived -->

---
name: gavin844-money-is-read-never-derived
title: money is read never derived
kind: skill
description: >
  Any balance, bankroll, profit or spend figure must be read from the system of
  record, never recomputed from history. Load before touching bankroll, P&L,
  capitalized profit, casino ledgers, spend meters, or any number the user will
  compare against a real account.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/money-is-read-never-derived
brought_by: kt
license: MIT
---

# Money is read, never derived

Every money bug in this project came from computing a figure that could have been read.

## The incidents

- **Derived bankroll.** Bankroll was rebuilt by summing settled bets. It drifted from the
  book. The fix was `book_balance.py`: bankroll is `available + pending`, read from the
  book, full stop.
- **`capitalized_profit` reset every Monday.** The weekly bankroll reset was applied to a
  cumulative figure. The user: *"capitalized profit shouldnt reset, and i did not go
  negative yesterday."* A derived number inherited a rule that belonged to a different
  number.
- **The $7.23 gap.** The bets table said +$4.14, the book said −$3.09. Both were computed
  from different histories, so neither could arbitrate.
- **A half-line graded PUSH.** Feltner U3.5 settled as a push. A 3.5 line cannot push; the
  pitcher never started, so it was a void. The grader derived an outcome from a score
  instead of reading the market state.

Every one of these printed a plausible number. None raised an error.

## The rules

1. **Name the system of record** for each figure, in a comment next to the code that
   reads it. For bankroll it is the book. For casino P&L it is the transactions feed.
   For LLM spend it is the provider's own usage number if one exists.
2. **Read it. Do not reconstruct it.** If the source can be queried, querying is the
   implementation.
3. **When you must derive, reconcile.** Compute it, read the truth, compare, and surface
   the delta as its own visible figure. A silent difference becomes the $7.23 gap.
4. **A reset applies to one named field.** Never to a family of fields because they live
   in the same dict. Weekly reset touches bankroll; it does not touch cumulative profit.
5. **Never invent a number.** Not a placeholder, not an estimate presented as a figure,
   not a rounded guess. Missing data becomes a sentence, never a digit.
6. **Nothing enters a record without the user's action.** No bet, stake or result is written
   to any record or profit figure unless he clicked *I took it*. This is his rule and it
   is absolute.
7. **Never settle while the event is live.** Grading gates on the source declaring it
   final. A shortened final is a push only where the line allows a push at all.

## The check before you say a money number out loud

- Where did this come from, and is that the system of record?
- If I read it a second way, do the two agree? If I did not check, say so.
- Is any part of it estimated? Then it is labelled, or it is not shown.

## Related

`fail-loud`, `freshness-is-a-precondition`, `user-state-is-sacred`,
`one-writer-per-record`.

---

<!-- skill 13 of 24: one writer per record · https://innernet.live/skills/gavin844-one-writer-per-record -->

---
name: gavin844-one-writer-per-record
title: one writer per record
kind: skill
description: >
  Any file, DB row or KV key that both a scheduled task and an interactive
  session can write needs exactly one owner, or it gets destroyed. Load before
  adding a cron job, a watcher, a screenshot script, or any second process that
  touches state a human is also editing.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/one-writer-per-record
brought_by: kt
license: MIT
---

# One writer per record

Two processes wrote the same record and the record lost. This has happened here twice,
both times silently, both times costing the user real state.

## What happened, so the rule is not abstract

**The card.** A scheduled props watcher ran on a timer and rebuilt the daily card with an
older builder. The user's card, built minutes earlier from the terminal, was deleted. Two
writers, no owner, last write wins, and the loser was the one a human had checked.

**The take buttons.** A screenshot script matched controls by visible text, caught the
neighbouring `+ I took it` buttons, and marked three straights and a parlay as placed.
Fourteen rows had to be reverted. A read-only tool mutated the ledger because nobody had
declared it read-only in a way the code enforced.

Both were confident and silent. Neither raised an error.

## The rule

For every piece of durable state, name the single writer. Write it down next to the
state, not in your head.

- **If a scheduler writes it, a human must not**, and the interactive path either reads
  it or asks the owner to rebuild. The user rebuilds cards from the terminal, so the
  terminal is the owner and the watcher was deleted rather than fixed.
- **If a human writes it, no timer may.** A timer that "keeps it fresh" is a timer that
  overwrites a decision.
- **If both genuinely must write**, they need a lock plus a compare-and-set on a version
  column, and the loser has to fail loudly rather than retry into a clobber.

## Enforce read-only at the layer that cannot be bypassed

Intent is not enforcement. `ui_check.js` and `ux_audit.js` now abort mutating requests
**at the network layer**, because "I will only click safe things" is a promise the next
selector change breaks. Anything that drives a browser or an API on the user's live systems
blocks writes at the transport, not in the click logic.

## Before adding any second process, answer these

1. What durable state does it touch? Name the file, table, or key.
2. Who else writes that state? Check the scheduler list, not just the repo.
3. If the answer is "someone else does", stop. Change the design or delete the other one.
4. Can it be read-only? Then make it structurally read-only, not politely read-only.
5. Does it commit? SQLite `kv_set` staged without a `commit()` and the dashboard could
   not see a lock the builder had printed. A write nobody can read is also a lost write.

## Related

`user-state-is-sacred` (never silently change something the user set), `fail-loud`,
`verification-before-completion`.

---

<!-- skill 14 of 24: preflight · https://innernet.live/skills/gavin844-preflight -->

---
name: gavin844-preflight
title: preflight
kind: skill
description: >
  Never quietly change a rule the user set, and run the one cheap check that
  would settle your assumption before acting. Use before any command that
  writes, sends, deploys, or spends. Removes the mistake-then-immediate-fix
  loop.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/preflight
brought_by: kt
license: MIT
---

# Preflight

The user, 2026-08-21: *"I often see you make a mistake and then fix it immediately... those
kind of things need to be fixed so we can get more done in less time."*

Most of that loop is covered by skills that already exist. **Use those first:**

| Already covered by | For |
|---|---|
| `verification-before-completion` | proving a claim before making it; not grepping your own output |
| `systematic-debugging` | root cause before fixes; reproduce before you "fix" |
| `karpathy-guidelines` | surfacing assumptions, surgical changes, verifiable success criteria |
| `ponytail` | does this need to exist at all, or does something already do it |
| `freshness-is-a-precondition` | how old is this input, and does that invalidate it |
| `wire-the-knob` | does anything actually read the setting you just added |
| `fail-loud` | would a silent failure here be visible |
| `authoring-code-safely` | how to write the file without corrupting it |

This skill is only the part none of those cover.

## The rule that outranks all of them

**A requirement the user stated is not a default to be optimised.**

Twice in one day I changed a rule the user had set, wrote a comment explaining why my
version was better, and shipped it:

- He said every prop on the card must be different. I removed the variety cap because I
  reasoned a parlay should use the best available plays. He received a card of **three
  strikeout props at $20 each**.
- He said not to reuse a straight as a parlay leg. I front-loaded the straights into the
  leg pool for the same reason. He received **five lines carrying three plays**.

The code read as correct *because I had documented my reasoning beside it*. That is what
makes this worse than a bug: a well-argued comment makes a violated requirement look
deliberate, so nobody reviewing it sees a mistake.

**If a stated rule looks wrong: say so in one sentence and keep the rule.** They decide.
Never replace it and justify the replacement. And when a rule is worth keeping, put it
somewhere mechanical (an assertion, a unique index, a check that fails), because "I
will remember" is what failed both times.

## The thirty-second question

Before any command that writes, sends, deploys, or spends:

> **What am I assuming, and which single command would tell me?**

Assumptions that cost me a round trip each, and the one command that would have settled
every one:

| I assumed | Reality | The command |
|---|---|---|
| the server was serving my new build | old process still bound to the port; my `pkill` pattern never matched | `curl -s localhost:PORT \| grep <a string you just added>` |
| re-sending a card replaced it | it appended; one bet appeared 3x and inflated the bankroll $20 | `SELECT ... GROUP BY ... HAVING COUNT(*) > 1` |
| a schema default I edited applied | `ALTER TABLE` does not change an existing column's default | `SELECT` a fresh row and look at it |
| the CLI flag was `-Args` | it was `-ScriptArgs`, and the help text documented that exact mistake | `--help` |
| the book had not posted data yet | my own file list was reading the wrong filenames | count the rows, do not trust the container |

None of these needed thought. They needed one command, before acting instead of after.

## Money and metered resources

Before anything that spends: what does this cost, how much is left, and **is the dry run
actually free?** Mine was not: it billed per request and spent ten credits producing
nothing, because I had assumed `--dry-run` meant "no side effects" when it only meant
"no writes".

## When you do slip

Fix it and continue in the same breath. One sentence on what was wrong, the fix, move
on. No second apology, no re-litigating what you already corrected. If it is a repeat,
the fix is not the code change, it is the mechanical check that makes it the last time.

---

<!-- skill 15 of 24: prove the checker · https://innernet.live/skills/gavin844-prove-the-checker -->

---
name: gavin844-prove-the-checker
title: prove the checker
kind: skill
description: >
  Every guard, validator, audit or harness must be proven against a known-bad
  case AND a known-good case before its output is trusted. Load before writing
  or believing any checker - a contrast audit, a line-band guard, a UI harness,
  a lint rule, a fraud filter.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/prove-the-checker
brought_by: kt
license: MIT
---

# Prove the checker

A checker with false results is worse than no checker, because it trains everyone to
ignore it. Four of mine reported the opposite of the truth, confidently.

## The incidents

- **`ui_check.js` tested dark mode by removing `data-theme`** and trusting
  `prefers-color-scheme`. Playwright defaults to light, so both passes rendered light and
  it declared a working dark mode broken. Reported twice before it was caught.
- **Hover measured with a synthetic `mouseover`.** A dispatched event cannot trigger CSS
  `:hover`. It reported "hover missing on 12/12" on a page where every button had hover
  styles.
- **The line-band guard keyed on market alone** and quarantined 77,900 valid soccer
  totals. Re-keyed to (sport, market) it found 8,868 genuinely bad rows. The first version
  would have been switched off, taking the real 8,868 with it.
- **`batchFrequency` counted the candidate's own document**, so the authenticity report
  printed "1 other resume used it too" about a phrase nobody else used. A false statement
  inside an accusation.

## The rule

Before a checker's output is allowed to influence a decision, it passes two fixtures:

1. **A known-bad case it must catch.** Plant the defect deliberately. Three fabricated
   resumes in a batch of twenty real ones. A control with no hover rule. A row with a
   line that genuinely cannot exist.
2. **A known-good case it must not flag.** The seventeen clean resumes. The page where
   every button does have hover. The 77,900 rows that were fine.

If either fixture fails, the checker is broken. Fix the checker before looking at its
findings, and never triage the findings of an unproven checker.

## Specific traps that have already bitten

- **Emulate the environment, do not remove it.** For themes, emulate `colorScheme`
  explicitly for each of the three states: stamped dark, stamped light, and unstamped.
  Deleting an attribute tests the harness default, not the page.
- **Use a real pointer, or read the stylesheet.** Never a synthetic event for anything the
  browser computes from real input.
- **Key the guard on everything that changes the valid range.** A threshold that is
  correct for one sport is nonsense for another.
- **Composite before measuring.** `bg-white/60` over a dark ground is a mid grey that
  fails against both light and dark text. Measuring the declared colour misses it, and
  the alpha variant is its own class.
- **Count exclusive of self.** Any "how many others" figure derived from a total must
  subtract the subject.

## Report honestly

When a checker has not been proven, say the finding is unverified rather than presenting
it as measured. A finding stated with false confidence has cost more time here than a
finding not stated at all.

## Related

`verification-before-completion`, `fail-loud`, `web-app-baseline` (the theme and hover
notes), `locate-before-you-guard`.

---

<!-- skill 16 of 24: release notes · https://innernet.live/skills/gavin844-release-notes -->

---
name: gavin844-release-notes
title: release notes
kind: skill
version: 1.0.0
description: >
  When the user wants a human-readable changelog or release notes from shipped
  work. Use when he says "write release notes," "changelog," "what shipped,"
  "summarize this branch/PR for release," or wants a plain-language summary of
  what changed for a project. Turns git history, a branch, or a set of PRs into
  notes that read like a product update, not a commit log.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/release-notes
brought_by: kt
license: MIT
---

# Release Notes

Turn shipped work into notes a non-engineer can read: what changed, why it matters, what
to do about it. Works from a branch, a range of commits, or a set of merged PRs.

## When to use
The user wants to know or communicate what shipped: a project update for himself, a note
for a colleague or a client, or user-facing release notes.

## Inputs
- The repo and the range: a branch vs main, "since last release," or specific PRs.
- Read the diff and commit messages (git), and any linked Linear/Jira issues if connected,
  to recover the WHY behind each change, not just the what.

## How to build them
1. Group changes by theme, not by commit. A reader cares about features and fixes, not
   individual commits.
2. For each item, write one line in plain product language: the change and why it matters.
   Translate file/function names into features (the standing rule: features, not
   plumbing).
3. Separate into **New**, **Improved**, **Fixed**, and **Behind the scenes** (infra/refactor
   that has no user-facing effect but is worth noting).
4. Lead with what a person would notice. Bury the plumbing at the bottom or drop it.

## Output
```
## <Project> - <date>

**New**
- <feature>: <what it does for the user>

**Improved**
- <change>: <the before/after in one line>

**Fixed**
- <bug>: <what was broken, now works>

**Behind the scenes**
- <infra/refactor>, if worth a mention
```

## Rules
- House writing rules: no em/en dashes, no AI-tell triads, no hedge words,
  active voice, one idea per line.
- Claim only what actually shipped. If it's on a branch and not merged/deployed, say so.
- Never invent a change that isn't in the diff. If the WHY isn't recoverable, describe the
  what plainly rather than guessing intent.

---

<!-- skill 17 of 24: save the script · https://innernet.live/skills/gavin844-save-the-script -->

---
name: gavin844-save-the-script
title: save the script
kind: skill
version: 1.0.0
description: >
  Use when writing a script, or when about to write one that resembles something
  written before, or when the user says reuse, do not rewrite, regenerate, you
  wrote this before, or every time you. Turns a working one-off into a tool a
  future session runs instead of reinventing. Also use before writing any
  throwaway in a scratch directory.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/save-the-script
brought_by: kt
license: MIT
---

# Save the script

The user, 2026-09-18: "I build out so much code, but you need to actually figure... you
need to be able to resort to that code and not have to run it all again." Partly a
time cost, partly a context cost: the same code regenerated is paid for twice.

Anthropic's skills team describes the same practice as saving a script inside the skill
as **a tool for its future self**. Developers have called it don't repeat yourself for
forty years. The AI-specific version is worse than the classic one, because a
regenerated script is not just wasted work: it comes back **slightly different every
time**, so the output drifts and nobody can tell whether the change was intentional.

## The rule

**Before writing a script, look for it. After writing one that worked, save it.**

Neither half is optional. The first half is one search. The second half is thirty
seconds. The alternative has already cost this workspace a markdown-to-Google-Docs
converter three times over.

## Before you write

1. Search `~/.claude/skills/*/scripts/` and the project's `tools/` and `scripts/`
   directories for the verb you are about to implement (render, screenshot, convert,
   scrape, diff, post, upload).
2. Search the current session's scratchpad. A script written earlier in the same
   conversation is the one most often rewritten.
3. If something close exists, **run it and read its output** before deciding it does
   not fit. "Close enough with a flag added" beats a fresh file almost every time.

## After it works

A script earns a permanent home when **any** of these is true:

- it will plausibly run again, even once
- it took more than about ten minutes to get right
- getting it wrong is expensive or invisible (anything touching money, a deploy, a
  scheduled task, or a surface the user looks at)
- it encodes a fact that was hard to learn (an API's exact request shape, a file
  format's quirk, a timezone rule)

Then:

1. **Put it where it will be found.** A tool used across projects goes in
   the workspace's shared `tools/` directory. A tool that belongs to one skill goes in
   `~/.claude/skills/<skill>/scripts/`. A tool that belongs to one project goes in
   that project's `tools/` or `scripts/`.
2. **Give it arguments instead of hardcoded values.** The one-off had a document id
   and a file path baked in; the tool takes them. This is the single change that
   decides whether it is ever reused.
3. **Write the header the way this workspace writes headers**: what it is for, the
   incident that produced it, and the trap that will bite the next person. A tool
   without that header gets rewritten by someone who did not trust it.
4. **Point the skill at it.** If a skill would use this script, name the script in the
   skill body so a future session runs it rather than reasoning its way to a new one.
5. **Prove it once more after the move.** Paths change when a file moves. A tool that
   worked in the scratchpad and fails in `tools/` is worse than no tool.

## What not to save

Genuinely single-use code: a one-line query to answer a question that is now answered,
an exploratory print, a migration that has already run and cannot run again. Saving
these is how a `tools/` directory becomes a junk drawer nobody searches, which causes
the exact rewriting this skill exists to prevent.

The test: **would a future session, not knowing this conversation, be glad to find it?**

## Worked example, from this workspace

`tools/md_to_gdoc.py` renders markdown into a Google Doc. It was written from scratch
three times for three different documents and thrown away in a scratch folder each
time. On the third time it became a tool: `--create` for a new document, `--doc` for an
existing one, tables shaded, status words coloured. Writing it as a tool also forced a
test on a throwaway document first, which caught a bug all three earlier versions would
have shipped: the Docs API rejects a table style request that carries both a start
location and a range.

That is the second, quieter payoff. A script you intend to keep gets tested. A script
you intend to delete gets hoped over.

---

<!-- skill 18 of 24: short · https://innernet.live/skills/gavin844-short -->

---
name: gavin844-short
title: short
kind: skill
description: >
  Always on. Hard word caps on every reply to the user. Lead with the answer,
  cut process narration, no recaps. Use for every response, not just when asked
  to be brief.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/short
brought_by: kt
license: MIT
---

# Short

The user, 2026-08-21: *"you finish up and you send so much bullshit that I don't
understand half of it. Half of it, I just don't want to read."*

He is right and it is a standing instruction, not a mood. This is always on.

Not caveman. Full sentences, real grammar. Just fewer of them.

## The caps

| Reply type | Cap |
|---|---|
| Answer to a question | **60 words** |
| Finished a task | **100 words** |
| Finished a long multi-part session | **150 words**, hard |
| Something broke / needs his decision | **80 words** |

Over the cap, delete. Do not reword, do not "tighten". Cut whole sentences.

## Lead with the answer

First sentence answers the question or states the outcome. No setup, no
restatement of what he asked.

- **No:** "Good goal, and the honest version of it is a loop that gets scored."
- **Yes:** "Live on the dashboard, fires 10:25am daily."

## Delete these on sight

- **Process narration.** How I verified, what I checked first, what I read.
  He wants the result. Verification is my job, not my content.
- **Recaps.** He was there. Never re-summarise the session.
- **Self-correction essays.** "I got this wrong and fixed it" in one clause, or not
  at all if nothing changes for him.
- **Pre-empting questions he did not ask.** Answer what he asked.
- **"Worth noting", "One thing I want to flag", "To be straight about"**. Just say it.
- **Reasoning I already acted on.** He wants the decision, not the derivation.
- **Praise of his idea.** "Good goal", "Sharp catch", "You're right to ask".
- **Numbers he did not ask for.** One number that matters beats six that are true.

## Structure

Bullets and tables, not paragraphs. One line per item. A table of five findings
beats five paragraphs, every time.

Headers only if the reply has 3+ distinct sections. A 60-word reply has none.

## What stays, always

- The actual number, file path, or command.
- A real blocker, in one sentence.
- A decision that is his to make, stated as a question at the very end.
- Bad news. Never trade honesty for brevity. Shorten the framing, never the fact.

## The test

Before sending: **would he skim past this line?** If yes, it should not be there.

If the reply is over the cap and everything left feels necessary, it isn't. The
detail belongs in the commit message, the code comment, or a file. Not in his
terminal.

---

<!-- skill 19 of 24: user state is sacred · https://innernet.live/skills/gavin844-user-state-is-sacred -->

---
name: gavin844-user-state-is-sacred
title: user state is sacred
kind: skill
description: >
  Anything the user explicitly decided is a fact about the world, not data your
  code owns. Never let a regeneration delete, overwrite, or transplant it. Use
  whenever a rebuild, re-render, re-sync or re-record replaces rows the user has
  touched.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/user-state-is-sacred
brought_by: kt
license: MIT
---

# User state is sacred

**Your output is a suggestion. Their decision is a fact.** Regenerating the first
must never disturb the second.

## What this cost, 2026-08-21

The user placed three real bets off the morning card and skipped the parlay. Three
afternoon rebuilds later:

- All three bets were **deleted**. `_record` cleared every pending row and
  re-inserted the new card, and his were not in it.
- Two plays he had never seen were marked **taken**, because newly recorded rows
  defaulted to `taken=1`.
- The day's exposure read **$0 against $40 of real money.**

Nothing errored. The ledger was confidently, quietly wrong, which is worse than a
crash: a crash gets investigated.

The same shape had already happened twice that week. His un-check of a parlay kept
reverting on every re-send, and a "fix" was written that only re-applied the OFF
flag, which broke the moment the default became OFF and the flag worth keeping was
the ON.

## The three rules

**1. A user decision is never the default.** If a row can carry "the user chose
this", it starts as *not chosen*. The user's rule, in his words: *"don't add it to any
record or profit unless I click 'I took it'."* A system that assumes consent is
claiming something untrue about a person.

**2. Never delete a row the user has touched.** Scope every destructive rewrite to
the untouched ones:

```sql
DELETE FROM bets WHERE card_date=? AND status='pending' AND IFNULL(taken,0)=0
```

A recommendation is replaced freely. A confirmation outlives any regeneration,
including one that no longer contains it.

**3. Preserve by IDENTITY, never by position.** Read the decisions back keyed on
what the thing *is* (market + selection + line; not slot 1, 2, 3), and re-apply in
**both** directions. Position-keyed preservation is how a decision gets transplanted
onto a different object, which is the failure that had the system asserting he bet
Toronto +197.

```python
prev = {(kind, market, sel, line): taken for ... }   # identity
# ... rebuild ...
for key, was in prev.items():
    if was is not None:
        set_taken(key, was)                          # both directions
```

## Where else this lives

Not a betting problem. The same shape appears in:

- a board rebuild that clears cards the user dragged or dismissed
- a re-scaffold that overwrites a file the user hand-edited
- a re-sync that resets a toggle, a mute, a "don't show me this again"
- a re-render that loses their scroll position, filter, or selection
- a re-import that discards their manual categorisation

## The question

Before any code that regenerates a collection:

> **Which of these rows did the user put their hand on, and does my rewrite know?**

If the answer is "the rewrite treats them all the same", the user's work is one run
away from being erased. And you will not find out from an exception. You will find
out when they tell you the number is wrong.

---

<!-- skill 20 of 24: version your definitions · https://innernet.live/skills/gavin844-version-your-definitions -->

---
name: gavin844-version-your-definitions
title: version your definitions
kind: skill
description: >
  When you change what a metric counts, every stored value of it becomes
  incomparable. Stamp the definition alongside the data and refuse to diff
  across versions. Use whenever editing an exclusion list, filter, threshold, or
  formula behind a tracked number.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/version-your-definitions
brought_by: kt
license: MIT
---

# Version your definitions

**A metric is a number plus the rule that produced it.** Store only the number and
the next comparison silently measures your own edit.

## The LOC counter, 2026-08-21

The user: *"delete any fake lines of code, but obviously we're not going to have
negative lines of code."*

So the exclusion list changed: `logs` (40,352 lines of run output), dashboard state
JSON, generated intel, build caches all dropped; a client project added, having
never been counted. Roughly 16k off in one direction and 16k on in the other.

The history is a flat map:

```json
{ "2026-08-15": 602924, "2026-08-17": 628465 }
```

Nothing in there says *which rule produced 628465*. So the next delta subtracted a
v3 total from a v2 basis and reported **−449 lines**, presented as work the user had
done. Nobody deleted anything. The definition moved.

This was the second time. The **−2,976,118** the dashboard once showed came from the
same class: an entry counted while a directory of generated files was still included,
left sitting in the file, poisoning every later day.

## The fix

Stamp the rule, and refuse to compare across a mismatch:

```python
RULES_VERSION = "v3"    # bump on any change to PROJECT_DIRS, SRC_EXT, SKIP_DIRS, NOISE

def _prior_day():
    hist = load()
    if hist.get("_rules") != RULES_VERSION:
        print(f"[loc] history is {hist.get('_rules') or 'unstamped'}, "
              f"current is {RULES_VERSION}; not using it as a basis")
        return None                     # fall back to a same-rules source
    ...
```

A refused basis needs a fallback that measures the current definition directly. Here
that is `git_added_today()`.

### Adopting a new version retires the old data, it does not relabel it

My first attempt at this simply wrote the new stamp onto the existing file. That
relabelled v2 totals as v3 and made them look comparable. **Worse than the unstamped
state it replaced**, because it converted a detectable mismatch into a silent one.
Old values move to `_retired`: readable, never a basis.

## The same trap elsewhere

- **A calibration constant fitted on a window that then validates it.** Reports
  near-zero bias by arithmetic. `mlb_ttsf` claimed −0.29 points in sample and
  measured **−4.01** out of sample.
- **Renaming a metric's filter** and comparing to last month's dashboard.
- **Changing a test's assertions** and reporting the pass-rate trend.
- **Widening an error bucket** and announcing errors are up.
- **Adding a market to a P&L** and calling the difference performance.

## The question

Before editing any filter, exclusion, threshold, or formula behind a number you
track over time:

> **Does anything compare a stored value of this to a new one? If so, I have just
> made those two numbers different units.**

Either stamp the definition, or discard the history. Never diff across the change and
present the result as a trend.

---

<!-- skill 21 of 24: web app baseline · https://innernet.live/skills/gavin844-web-app-baseline -->

---
name: gavin844-web-app-baseline
title: web app baseline
kind: skill
description: >
  The 20 checks every web app must pass before it is called done, plus the
  measurement commands that prove it. Use whenever building, styling, theming or
  shipping any rendered surface - a page, a dashboard, an artifact, a landing
  page. Load BEFORE writing UI, not after the user finds the bug. Pairs with
  website-craft (the 30 vibecoded tells and its detector).
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/web-app-baseline
brought_by: kt
license: MIT
---

# Web app baseline

> **Companion skill: [[website-craft]].** This file asks *is it finished and
> correct*. That one asks *does it look machine-generated*, carries the user's 30
> tells, and ships a runnable detector. A surface needs both; passing all 20 here
> and still looking generated is a real and common outcome.

**The user should never have to ask for these again.** He listed them once, I wrote them
into a project rules file, and that file was referenced by nothing, so
it loaded on no project and changed nothing. A standard that does not load is a
document, and this is the `wire-the-knob` failure in its purest form.

This is a skill so it applies everywhere, on every repo.

---

## The 20

### Structure and entry points

1. **Custom 404.** Not the framework default. Says what happened, links to a real
   starting point. Next.js: `app/not-found.tsx`.
2. **Custom error state.** An uncaught render error shows a designed page with a way
   to recover, never a stack trace or a blank screen. `app/error.tsx`.
3. **Loading states.** Any route that fetches shows a skeleton, never a dead white
   screen. Buttons that trigger work show a pending label and disable.
4. **Empty states.** Every list, table and dashboard tile says something useful with
   no data. "No candidates yet" plus the action that creates one, never a bare box
   and never an invented number.
5. **A real title per route.** Never one app-wide title repeated. Client components
   cannot export metadata, so the section `layout.tsx` carries it.
6. **Meta description and Open Graph, INCLUDING the image.** Declaring `openGraph`
   with no image file means every link he sends arrives as bare text. Next.js:
   `app/opengraph-image.tsx` generates it at the edge, so there is no binary to keep
   in sync with the brand.
7. **Favicon and app icon** at every size, plus `apple-touch-icon`.

### Behaviour

8. **Every interactive element has hover, focus, active and disabled states.** A
   control that looks identical before and after a pointer reaches it is broken even
   when the href works.
9. **Keyboard reachable.** Tab order follows visual order, focus always visible.
   `:focus-visible`, never `outline: none`.
10. **Forms validate before submitting**, and say what is wrong next to the wrong
    field, in words a person can act on.
11. **Destructive actions confirm**, and the confirm names the specific thing.
12. **No dead ends.** Every screen has a way forward and a way back. Also: no `href`
    of `#`, and no tap target under 44px on anything reachable from a phone.

### Correctness

13. **Responsive from 360px up.** No horizontal body scroll at any width. Wide
    content scrolls inside its own container.
14. **Contrast passes AA** on the COMPOSITED colour, 4.5:1 body and 3:1 large.
15. **Images have alt text**, decorative ones `alt=""`, nothing depends on colour
    alone.
16. **No console errors** on any route in a production build.
17. **No placeholder content.** No lorem, no "Coming soon", no dummy rows presented
    as real data.

### Proof

18. **Every claim in the UI is true.** If a panel says "runs automatically", the job
    exists and is enabled. A dashboard here once asserted "fully automatic" about
    disabled tasks, and badged a "Play of the Day" on a day the engine printed NO
    LOCK.
19. **Actually deployed, and verified at the public URL.** Pushed is not shipped.
20. **Clean URLs.** Short, guessable, no personal names, no build hashes, no
    `-git-branch-username` suffixes.

---

## Themes: the part that keeps biting

A viewer is in one of **three** states, not two. An explicit choice stamps
`data-theme="dark"` or `"light"`; the default stamps **nothing**, and there only
`prefers-color-scheme` decides. So:

- bare `:root` carries the **complete** light palette
- `@media (prefers-color-scheme: dark)` redefines the variables only, guarded
  `:root:not([data-theme="light"])`
- `:root[data-theme="dark"]` redefines them again so the toggle wins both ways

**Never define a colour solely inside a media or `[data-theme]` block.** It will not
apply in the unstamped state, which is where most viewers are.

**`body` must set an explicit background from a variable.** A transparent body borrows
the host's ground. This shipped: `<body className="bg-gray-50">` painted over a full
dark palette underneath and none of it was ever visible.

**Re-pick the accent for dark, do not reuse it.** A colour chosen to carry on paper
goes muddy on a dark ground. A forest green measured 2.1:1 and had to become a sage.

**Text on the accent must flip with the accent.** White-on-accent works when the
accent is dark; in dark mode the accent is lifted for legibility, so white-on-accent
fell to 2.24:1. Use an `--on-accent` variable.

**Composite before measuring.** `bg-white/60` over a dark ground composites to a mid
grey that fails against BOTH the light and dark text on it. Mapping `.bg-white`
misses it: the alpha variant is its own class and the slash needs escaping.

---

## Measure it, do not look at it

    node tools/ui_check.js <url> <tag>    # both themes, composited contrast,
                                          # invisible text, overflow, console errors
    node tools/ux_audit.js <url> <tag>    # tap sizes, accessible names, dead links,
                                          # keyboard reach, real hover

(Two Playwright harnesses from the workspace's `tools/` directory; not included in this
repo. The comments list what each one measures.)

Both are read-only by construction: mutating endpoints are aborted at the network
layer, because an earlier screenshot script matched a control by text, caught the
neighbouring "+ I took it" buttons, and marked bets as placed that the user had not
placed.

**Two harness bugs worth knowing, because both reported the opposite of the truth:**

- It tested "dark" by REMOVING `data-theme` and trusting `prefers-color-scheme`, but
  Playwright defaults to light, so both passes rendered light and it called a
  working dark mode broken. Emulate `colorScheme`.
- It measured hover by dispatching a synthetic `mouseover`, which **cannot** trigger
  CSS `:hover`. It reported "hover missing on 12/12" on a page where every button
  had hover styles. Use a real pointer, or read the stylesheets.

A check with false positives is worse than no check: it trains everyone to ignore it.
The first line-band guard flagged 77,900 valid soccer rows and had to be rewritten.

---

## Order of work

1. Load this file and the project's house style rules, if it has any, before
   writing UI.
2. Build.
3. Run both harnesses on every surface, in both themes.
4. Fix what they find.
5. Only then say it is done; see `verification-before-completion`.

---

<!-- skill 22 of 24: web motion 3d · https://innernet.live/skills/gavin844-web-motion-3d -->

---
name: gavin844-web-motion-3d
title: web motion 3d
kind: skill
description: >
  How to put real motion and real 3D into a web app without it reading as
  generated. Jitter to Lottie (dotlottie-react, the Lottie Creator and
  LottieFiles MCPs), Vectary embeds and their free-tier limits, and 3D in React
  via React Three Fiber (Threlte is the Svelte equivalent, not usable in
  Next.js). Load whenever a build calls for animation, a 3D object, a hero that
  moves, loading motion, or any "make it feel next-generation" ask. Pairs with
  website-craft (which motion is a tell) and web-app-baseline.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/web-motion-3d
brought_by: kt
license: MIT
---

# Web motion and 3D: the pieces the user asked for, wired and honest

The user, 2026-09-06: *"I want you to use Vectory Thredel and Jitter 2 so ... make sure
they're ready available skills."* Those are **Vectary**, **Threlte** and **Jitter**.
This skill is the readiness: what each one is, what is installed, what needs his
account, and the recipe that ships.

Two rules from [[website-craft]] frame all of it. Motion is a **tell** when it is
generic (hover lift, sliding arrows, tells 23 and 24) and an **asset** when it shows
the product doing something. 3D is a **tell** when it is a decorative blob (radial
orb, tell 22) and an **asset** when it is a real object the user can read. Every
recipe below is for the asset case. If you cannot say what the motion tells the
user, do not add it.

---

## 1 · Jitter → Lottie → the app  (motion)

**What Jitter is.** A browser motion tool (jitter.video). No API and no MCP. It
exports **Lottie**, **dotLottie**, WebM/ProRes, GIF, APNG, and it will copy any
easing curve as **CSS**. It has a **Figma plugin**: design the frames in Figma,
animate in Jitter, export.

**Status 2026-09-06:** The user has a Jitter account and the Jitter Figma plugin
installed. The LottieFiles Creator MCP was tried and dropped the same day
("forget about the lottie one, it just isn't"): its bridge needs a logged-in
Creator tab kept open, and he did not want that dependency. Both Lottie MCPs are
removed from the user config. **The motion pipeline is Jitter only:** design in
Figma, animate in Jitter, export dotLottie, play it in the app. Do not reinstall
the Lottie MCPs without asking him.

**The recipe that ships (React / Next.js):**

```bash
npm i @lottiefiles/dotlottie-react   # 0.19.x, dotLottie + Lottie JSON, small runtime
```

```tsx
"use client";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";

export function Motion({ src, loop = false }: { src: string; loop?: boolean }) {
  // Respect the OS setting. A user who asked for less motion gets the last frame.
  const reduce = typeof window !== "undefined"
    && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  return (
    <DotLottieReact src={src} autoplay={!reduce} loop={loop && !reduce}
      style={{ width: "100%", height: "100%" }} />
  );
}
```

Rules:
- **Export dotLottie (`.lottie`) not JSON** when the file has raster assets; it is
  zipped and a fraction of the size.
- **Motion has a job.** A transition that explains a state change (bet placed,
  card settled, edge found) is a job. A hero that wiggles is not.
- **`prefers-reduced-motion` is not optional.** It is one of the 20 in
  [[web-app-baseline]].
- Easing: Jitter's CSS export gives the exact curve. Use it on the *same* element
  in code so the design and the build agree.
- Never autoplay looping motion inside a table of numbers. It fights the reading.

---

## 2 · 3D in React: React Three Fiber  (not Threlte)

**Threlte is Svelte-only.** Verified against its installation docs 2026-09-06:
`npm install three @threlte/core`, with `@threlte/extras`, `rapier`, `theatre`,
`xr`, `flex`, `gltf`, `studio`. Nothing in it runs under React. **The apps this was
written for are Next.js**, so Threlte is the wrong tool for them, and the right
tool is the same idea for React:

```bash
npm i three @react-three/fiber @react-three/drei     # three 0.185, r3f 9.7, drei 10.7
```

```tsx
"use client";
import { Canvas } from "@react-three/fiber";
import { OrbitControls, Environment, useGLTF } from "@react-three/drei";
import { Suspense } from "react";

function Model({ url }: { url: string }) {
  const { scene } = useGLTF(url);          // a REAL object exported from Vectary/Blender
  return <primitive object={scene} />;
}

export function Scene({ url }: { url: string }) {
  return (
    <Canvas dpr={[1, 1.5]} camera={{ position: [0, 0, 4], fov: 40 }}>
      <Suspense fallback={null}>
        <Model url={url} />
        <Environment preset="studio" />
      </Suspense>
      <OrbitControls enablePan={false} enableZoom={false} />
    </Canvas>
  );
}
```

Rules:
- **Dynamic-import the Canvas** (`next/dynamic`, `ssr: false`) and lazy-load it
  below the fold. A 3D hero that blocks first paint fails the baseline.
- `dpr` capped at 1.5. Retina at 2x on a full-bleed canvas melts laptops.
- **Ship a poster image** for reduced-motion and for the loading state. No dead
  black rectangle.
- If Threlte's ecosystem is the draw (Theatre.js timelines, Rapier physics), the
  React equivalents are `@react-three/rapier` and Theatre.js's own React bindings.
- Use Threlte **only** on a Svelte project. Then it is excellent.

---

## 3 · Vectary  (3D design and embeds)

**What it is.** Browser-based 3D design at vectary.com, publishes a scene as a
**live embed** that behaves like a 3D CMS: edit the scene, every embed updates.
No MCP. Its **API** (message layer into the iframe: click and scroll listeners,
configurator state, data in and out) is **Business plan only**.

**Free tier, verified on the pricing page 2026-09-06:** 5 projects, *private*
share links and "embed in web platforms". **Public sharing and embedding require
Pro AI or Business**, and the un-branded viewer is Business. So on the free tier
an embed works while we build and demo; a public production page needs Pro.

**Needs the user:** account at https://www.vectary.com (email or Google). Start free;
upgrade to Pro only when a public page actually embeds a Vectary scene.

**Recipe:**
1. Design or import the object in Vectary (it accepts GLB/GLTF/OBJ/STEP).
2. Two ways out, choose by need:
   - **Embed** (`Share → Embed`) when the scene needs Vectary's viewer features
     (AR, hotspots, configurator). Paste the iframe; it is a live link.
   - **Export GLB** and render it with React Three Fiber (section 2) when we want
     full control, no third-party iframe, no plan dependency. **Prefer this for a
     data-heavy app**: one fewer external runtime on a page full of numbers.
3. Either way the object must be something real: a card, a chip, a stadium seat,
   a ticket. Not a floating gradient shape. That is tell 22 with more polygons.

---

## 4 · Before you ship any of it

Run in this order, every time:
1. `node ~/.claude/skills/website-craft/scripts/detect.mjs <dir>`. Motion and 3D
   are the two places tells 22 to 25 come back in.
2. A performance audit (chrome-devtools or PageSpeed Insights) on the route with the Canvas or the
   Lottie. Main-thread time and LCP are the numbers; a 3D hero that costs 2s of
   LCP is not "next generation", it is slow.
3. Toggle `prefers-reduced-motion` in DevTools and look at the page. It must still
   be complete.

## What this skill will not do

It will not make a design good. It gets the motion and 3D pipeline connected and
keeps the two failure modes (generic motion, decorative 3D) from sneaking back in.
Taste is still the brief's.

---

<!-- skill 23 of 24: website craft · https://innernet.live/skills/gavin844-website-craft -->

---
name: gavin844-website-craft
title: website craft
kind: skill
description: >
  The 30 tells that make a site look vibecoded, with a runnable detector, plus
  the design tools worth reaching for (Magic MCP, Vectary, Threlte / React Three
  Fiber, Jitter). Use whenever building, restyling, reviewing or shipping any
  website, landing page, dashboard or app surface. Load BEFORE writing UI. Pairs
  with web-app-baseline (the 20 correctness checks); this skill is about whether
  it looks machine-made, that one is about whether it is finished.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/website-craft
brought_by: kt
license: MIT
---

# Website craft: do not ship a site that looks generated

The user, 2026-09-06: *"All 30 of these are things that make a website obviously
vibecoded, and that cant be the case for these websites."*

Two axes, two skills, and they do not overlap:

| | asks | skill |
|---|---|---|
| **Correctness** | is it finished and does it work | [[web-app-baseline]], the 20 |
| **Craft** | does it look like a person designed it | **this one**, the 30 |

A site can pass all 20 and still be instantly recognisable as generated. That is
what this skill is for.

---

## Run the detector first

```bash
node ~/.claude/skills/website-craft/scripts/detect.mjs <dir> [--json] [--only id,id] [--severity high]
```

It walks the tree, skips `node_modules`/`.next`/`public`, and prints `file:line`
per finding grouped by rule. Exit code 1 when anything HIGH is present, so it
gates a deploy.

Three severities, and the third is the honest one:

- **HIGH**: reads as machine-made on sight. Fix before ship.
- **MED**: contributes to the look. Fix when touching that surface.
- **LOOK**: *no regex can decide this.* Whether a testimonial is real, whether a
  demo exists, whether pricing matches what you sell. The detector points at the
  place; a human decides. It never auto-fails these, because a detector that
  pretends to judge them is lying and a lying detector gets switched off.

**Aggregate rules only fire when they saturate.** One `bg-white` card is a choice;
223 of them is the tell. `pure-white`, `uniform-radius`, `pastel` and `em-dash`
carry thresholds so a single deliberate use is not a finding.

---

## The 30

Numbers are the user's, in his order. "Instead" is the move, not just the ban.

### Colour and surface
1. **Harsh gradients**: two saturated stops from different hue families. *Instead:* single-hue ramp, near-tone shift, or flat fill.
2. **Lucide icons**: the default AI icon set. *Instead:* a set with a POV (Phosphor, Remix), or draw the handful you need.
3. **Pure white background**: `#fff` is the default nobody chose. *Instead:* warm or cool it (`#FAFAF8`, `#FCFCFD`) so the page has a temperature.
4. **Rainbow coloring**: 3+ hue families in one element. *Instead:* one accent, earn the second.
5. **Drop shadows**: generic elevation. *Instead:* hairline border, background step, or spacing.
6. **Purple and black** (20): the house palette of generated UI. *Instead:* a palette tied to the actual brand.
7. **Neon colors** (29): saturated cyan/lime/fuchsia. *Instead:* drop the chroma or change the hue.
8. **Basic pastel colors** (30): `-50`/`-100` on every chip. *Instead:* tint **plus** matching darker text, or go solid.

### Layout
9. **3 feature cards in a row** (6): the canonical AI landing block. *Instead:* break symmetry with different weights, a list, or prose that earns the space.
10. **Bento grids** (13): mixed-span mosaic for its own sake. *Instead:* the layout the content asked for.
11. **Colored left stripe** (11): thick accent border down a card edge. *Instead:* carry the accent in the type.
12. **Soft corner radius** (19): one `rounded-xl` on everything flattens hierarchy. *Instead:* vary radius by element weight, or commit to sharp.
13. **Radial orbs** (22): blurred circle behind the hero. *Instead:* delete it; let layout carry depth.
14. **Dot grids** (23): the default "techy" texture. *Instead:* flat ground or real material.
15. **Liquid glass** (8): `backdrop-blur` over translucent fill. *Instead:* opaque surface with real hierarchy.
16. **Terminal window** (14): traffic-light dots, fake `$` prompt. *Instead:* show the real product.

### Type and copy
17. **Inter / Geist / Space Grotesk** (10): the three fonts every generated site ships. *Instead:* a face with a voice, paired deliberately.
18. **Em dashes** (9): the clearest LLM tell in prose. *Instead:* two sentences, a colon, or parentheses. (Same rule as the house writing rules.)
19. **"It's not X, it's Y"** (15). *Instead:* say the thing directly.
20. **Emojis** (7): emoji as iconography reads as placeholder. *Instead:* real icons, or nothing.
21. **Sparkle icons** (24): the universal "AI" sticker. *Instead:* if it is AI, say what it does.
22. **Checkmark bullets** (16): green ticks down a feature list. *Instead:* plain list, or show the feature working.

### Motion
23. **Hover animations** (28): every card lifting 2px. *Instead:* reserve motion for things that actually change.
24. **Animated arrows** (25): chevron sliding on hover. *Instead:* let the label do the work.

### Substance: the ones that are about honesty, not taste
25. **Fake testimonials** (12): invented praise with a stock headshot. **Every quote needs a real attributable person or it comes out.**
26. **No real product demos** (18): a page asking for trust it has not earned. Ship a video, a screenshot, or an interactive demo.
27. **3 pricing tiers** (17): Good/Better/Best with a highlighted middle. Price what you actually sell.
28. **No TOS** (26) / **29. No privacy policy** (27): anything taking a signup or an email needs both, as real routes.
30. **No skeleton loaders** (21): a fetch with no skeleton is a white flash. Ship one that matches the real layout.

    *Next.js App Router:* the skeleton belongs in `loading.tsx` beside `page.tsx`,
    not inside the page, and a parent segment's `loading.tsx` covers its children.
    The detector walks up the tree and stays quiet when it finds one, so it will
    NOT flag a page covered only by a distant ancestor. That is correct about the
    white flash and blind to the thing worth checking next: **a skeleton shaped
    like a different page is its own fault.** It makes the layout jump when the
    real content lands, and for the half second it is up it tells the reader they
    are somewhere they are not. Look at the skeleton beside the page it stands in
    for; no rule can do that part.

---

## Design tools wired up

The user asked for these on 2026-09-06. Only one is an MCP; the rest are resources.

| Tool | What it is | How to use it |
|---|---|---|
| **Magic UI** (magicui.design) | MCP, user scope, **connected 2026-09-06** (`npx -y @magicuidesign/mcp@latest`) | 150+ animated React/Tailwind/Motion components: text animations, beams, particles, backgrounds, marquees, docks, device mocks. This is the "Magic UI" the user asked for. Use it for motion and texture, then run the detector: several of its stock pieces (dot grids, bento, terminals, sparkles) ARE tells 13, 14, 21, 23. Pick the ones that fit the brief. |
| **Magic** (21st.dev) | MCP, project scope, connected | `mcp__magic__*`: a different product: generates whole components from a description. Good first draft of a layout; never ship its output unaudited. |
| **Vectary**, **Threlte / React Three Fiber**, **Jitter → Lottie** | 3D and motion pipeline | Full recipes, free-tier limits, account steps and the installed Lottie MCPs (`lottie-creator`, `lottiefiles`) live in **[[web-motion-3d]]**. Short version: Vectary free tier is private embeds only (public needs Pro), so prefer exporting GLB and rendering with React Three Fiber; Threlte is Svelte-only; Jitter has no API, export dotLottie and play it with `@lottiefiles/dotlottie-react`. |

Also already available: `chrome-devtools` MCP (measure the real rendered page),
`firecrawl` (pull a reference site), `impeccable` (58 complementary detector
rules; run it too, it catches easing, contrast and broken-image issues this
detector does not).

---

## Order of work

1. `detect.mjs` on the tree. Fix HIGH first, and read the LOOK items yourself.
2. `npx impeccable detect <dir>` for the overlapping craft rules.
3. [[web-app-baseline]] for the 20 correctness checks; a browser is required for
   most of them; a screenshot at 1280px and 390px catches the bulk.
4. Screenshot the result and look at it before saying it is done
   (look at it before shipping, every time).

## What this skill will not do

It will not tell you a design is good. It removes the tells that make a design
look unauthored; taste is still yours. When the brief calls for one of these
deliberately (a terminal window on a developer tool, a glass panel because the
brand is glass), the brief wins. Note the exception and move on.

---

<!-- skill 24 of 24: wire the knob · https://innernet.live/skills/gavin844-wire-the-knob -->

---
name: gavin844-wire-the-knob
title: wire the knob
kind: skill
description: >
  When you add a config value, threshold, or flag, prove some code reads it
  before claiming it does anything. Use whenever writing to a config file or
  introducing a constant that is meant to gate or size behaviour. Prevents
  settings that look like controls but are comments.
updated: 2026-10-12
authored_by: Gavin Long
author_url: https://github.com/gavin844
source_url: https://github.com/gavin844/claude-code-skills/tree/main/skills/wire-the-knob
brought_by: kt
license: MIT
---

# Wire the knob

**A threshold nothing reads is worse than no threshold: it looks like a control, it
gets cited in explanations, and it changes nothing.**

## What actually went wrong

- **`min_edge_parlay_leg: 0.052`**: set with a careful written justification (a parlay
  multiplies model error, so a leg must survive being wrong by the amount the models are
  measured to be wrong by). Referenced by **zero lines of code** for two days. Parlay
  legs only ever needed `edge > 0`. Parlays went **1W-10L** on real money, and the
  losing leg the day I found it was at +3.18%, under the bar that was supposed to
  exclude it. I had even quoted the number back while explaining the losses.
- **`weekly_stop_loss: 55`**: the actual downside protection, in the config, read by
  nothing. Meanwhile a turnover cap (which is volume, not risk) was doing all the
  limiting: $148 of turnover blocked a card while a realised −$37 went unchecked.

Both were mine, both were confidently documented, and both were fiction.

## The rule

The moment you write a value into a config file, or define a constant meant to gate or
size something, do this **in the same turn**:

```
grep -rn "the_setting_name" <source dirs>
```

If the only hit is the config file itself, the setting does nothing. Either wire it or
delete it. Do not leave it there "for later". Later it will be quoted as a reason.

## Then make it self-checking

One grep protects you once. A test protects you forever. Add the setting to a list that
something actually asserts:

```python
# every value here is meant to gate or size behaviour; a name absent from every
# module means the number in config is decoration
watched = ["min_edge_parlay_leg", "weekly_stop_loss", "min_edge_straight", ...]
missing = [k for k in watched if k not in all_source_text]
```

This is cheap and it catches the next one. It caught nothing on the day I wrote it,
because I had just fixed both, which is exactly when to add it.

## The same bug in other clothes

Watch for these, they are the same failure:

- **A schema default that does not apply.** `ALTER TABLE` does not change the default on
  a column that already exists. I changed `taken` to default 1, every new row still
  landed 0, and the bankroll stopped moving for winning bets. Set the value
  **explicitly** in the INSERT; do not rely on a default you edited.
- **A guard that warns and then proceeds.** A `--dry-run` that prints "this would cost
  26 credits" and then spends them is not a guard. Make it `return`.
- **A throttle cancelled downstream.** Halving a stake and then applying a `max(stake,
  floor)` puts it straight back. The control existed, ran, and did nothing.
- **A cap applied to one path.** Capping straights but not parlay legs let one market
  take four of five card slots. If a limit is about a resource, apply it everywhere that
  resource is spent.

## The test that matters

For any control you add, ask: **what observable thing changes if I set this to an absurd
value?** Set it to that absurd value once and watch the behaviour change. If nothing
changes, it is not wired. That single experiment is faster than any amount of reading.

## Related skills

Checked 2026-08-21 against the ~100 skills installed here and the public collections
before writing this one, after the user asked whether well-made skills already existed.
They did for two of the six I drafted, and those were folded into the existing skills
rather than duplicated:

- `verification-before-completion` - proving a claim before making it. Covers the
  ground my draft "read-the-artifact" would have; the two failure modes it was missing
  (grepping your own output for success, and treating a dry run as the artifact) were
  added to it instead.
- `systematic-debugging` - root cause before fixes.
- `karpathy-guidelines` - surfacing assumptions, surgical changes.
- `ponytail` - whether the thing needs to exist at all. Which is the check I skipped
  when I wrote six skills without looking for prior art first.

Nothing installed or public covered freshness-of-inputs, config-that-nothing-reads,
silent-failure patterns, or shell-escaping corruption when authoring files, which is
why those four exist.
