Fable Code
If fable-core is loaded, this skill supplies Phase 2/4 craft and the Phase 5 rubric and
anti-pattern catalog. If it is not, run this mini-loop: recon → contract → produce →
self-review with a 5-defect quota → verify by running → simplify → deliver with evidence.
Prime directive
Three properties dominate everything else. Every rule below serves one of them:
- Right altitude — the change lives where the problem lives, not where it was noticed.
- Minimal diff — the change contains the task and nothing else.
- Native idiom — the change reads as if the codebase's own author wrote it.
Before any code: reconnaissance
- Read the neighbors. The file you will change plus two or three siblings. Note the naming vocabulary, the error-handling style, the comment density, the test shape. You will match all four even where you would personally choose differently.
- Hunt for the existing solution. The helper you are about to write probably exists. Search the codebase for the domain noun before writing it; check the utility modules and the dependency list. Writing a duplicate is a recon failure, not a style choice.
- Learn the project's contracts for errors (exceptions? result types? logging?), configuration, state, and tests — then follow them.
- Locate the invariant owner. Which module is supposed to guarantee the property you are about to touch? That is usually where your change belongs.
Writing new code
- Contract first. Before the body: inputs, outputs, failure behavior. Enumerate the edges — empty, absent, huge, duplicate, concurrent, malformed, boundary — and give each a verdict: handled or explicitly out of scope. Unhandled-by-silence is the bug.
- Make impossible states unrepresentable where the language makes it cheap (types, unions, constructors). Where it is not cheap, assert at the boundary and fail loudly.
- Build in verifiable increments. Keep it runnable between steps.
- Errors propagate or get handled — never logged-and-swallowed as a reflex. A default value that masks absence is a lie to the caller. Fail where the truth is.
- No speculative generality. An abstraction earns existence with its second caller; a config option with its second value; a parameter with its second distinct argument.
- Comments state what code cannot: the invariant, the why-not-the-obvious-way, the external constraint. Never what the next line does, never why the diff is correct.
- Tests assert the contract, not the implementation. Hostile inputs, documented failure behavior, boundaries. A test that mirrors the code's structure tests nothing. Assert outcomes, not that mocks were called — unless the call is the contract.
Modifying existing code
- Diff discipline. Every hunk traceable to the task. Things you noticed but were not asked to fix: report them ("noticed, untouched"), do not fix them silently.
- Chesterton's fence. Understand why the current shape exists — usage, history, tests — before reshaping it. Code that looks wrong and has survived usually knows something.
- Match, don't modernize. Style upgrades mid-fix double the review surface. Modernize only when modernization is the task.
- Check the blast radius before renames and signature changes: find the callers first, not after the build breaks.
Debugging
The procedure that most separates strong work from mid work. In order, no skipping:
- Reproduce before theorizing. A bug you cannot reproduce is a rumor. If no repro is possible, say you are working from a trace hypothesis and mark conclusions accordingly.
- State expected vs observed, precisely. A vague symptom guarantees a vague fix.
- Hold at least two hypotheses at all times. A single-hypothesis investigation finds only confirmation.
- Choose the cheapest experiment that discriminates between hypotheses — not the cheapest edit that might make the symptom vanish.
- Find the divergence frontier. Walk the data until reality first departs from expectation. The defect lives at that frontier — almost never at the crash site.
- Fix the invariant, not the symptom. If your fix is a guard at the crash site, ask what upstream allowed the bad state to exist at all.
- Prove it. The original repro passes, and you can state the mechanism of the original failure in one sentence. If you cannot state the mechanism, you have hidden the bug, not fixed it.
- Sweep. Where else does this class of bug live — same pattern, same author, same era of the codebase? Report what you find.
Shotgun ban: while diagnosing, change one thing at a time, and revert experiments that did not discriminate.
Reviewing code
Order of concerns: contract (does it do the right thing) → correctness (does it do the thing right) → quality (is it well made). Report findings as mechanism, not vibes: this input or state → this wrong behavior → this consequence, ordered by severity. Verify claims while reviewing — run the tests instead of trusting the green checkmark.
The rubric
Score every dimension 0–2 before delivering (this is the Gauntlet's grading table). Any 0 blocks delivery. Every 1 is declared debt — stated in the final report.
- Altitude — the change lives where the problem lives. Test: would this bug or need recur elsewhere despite the change (too low)? Would something simpler fully solve it (too high)?
- Minimality — nothing beyond the task. Test: for each hunk, name the requirement that forces it.
- Idiom — indistinguishable from the codebase's own work. Test: naming vocabulary, error style, comment density all match the neighbors.
- Contract & edges — every edge has a verdict. Test: recite the verdicts for empty, absent, huge, duplicate, concurrent, malformed.
- Error honesty — failures surface at the point of truth. Test: every catch/except/ fallback in the diff either handles meaningfully or propagates; none swallow.
- Invariants — key assumptions structurally enforced or asserted. Test: name the invariant and the place where reality checks it.
- Verification — behavior exercised, not assumed. Test: what did you actually run, with what input, and what did it output?
- Readability — one-pass parseable by a tired stranger. Test: is there any line whose "why" is not answerable from the code around it?
Anti-pattern catalog
Check these by name during the Gauntlet. Format: name — the tell → the correction.
- Defensive slop — reflexive try/catch-log-continue; carpets of
?.and|| default→ let it fail at the point of truth; handle only where handling means something. - Crash-site patching — the guard goes where the stack trace pointed → trace to where the invariant broke; fix there.
- Reinvention — a new helper duplicating one that exists → recon harder; reuse or extend.
- Drive-by scope — refactors hitchhiking on a fix → separate delivery, or report-not-touch.
- Speculative generality — abstraction, flag, or option with exactly one user → inline it; generality is earned by the second caller.
- Narration comments — comments restating code or justifying the diff to a reviewer → delete; keep only what code cannot say.
- Mirror tests — tests restating the implementation, assertions on mock call counts → assert outcomes at the contract boundary.
- Shotgun debugging — several simultaneous edits until the symptom vanishes → one discriminating change at a time; explain the mechanism before claiming the fix.
- Confidence inflation — "should work now" backed by zero executions → run it, or deliver it explicitly labeled unverified.
- Silent fallback — a default masking absence or failure (empty list on error, stale cache on outage) → distinguish "no data" from "failed to get data", visibly.
- Modernization creep — style upgrades sneaking into an unrelated task → match the local idiom; modernize on purpose or not at all.
- Kitchen-sink signature — parameters and options added "for flexibility" → today's callers define the signature; tomorrow's callers pay for their own.
Exemplars
Read references/exemplars-code.md to calibrate what fable-grade output looks like —
recommended before substantial new code, and whenever your Gauntlet keeps coming back
suspiciously clean.