| name | coding |
| description | Coding workflow covering discovery, planning, implementation, and verification. Invoke whenever task involves any interaction with code — writing, modifying, debugging, refactoring, or understanding codebases. Runs discovery protocol before language-specific skills engage. |
Coding
Discover before assuming. Verify before shipping.
Every coding failure traces to one of three root causes:
- Acting on assumptions instead of evidence
- Skipping verification before declaring done
- Burning context on noise instead of signal
This skill prevents all three.
Core Loop
Every task follows this sequence. No exceptions.
Discover → Plan → Implement → Verify
- Discover: Read the code. Trace dependencies. Understand what exists.
- Plan: Define success criteria. Scope the change. Identify risks.
- Implement: Write minimal code. Work incrementally. Stay simple.
- Verify: Run tests. Validate behavior. Confirm requirements met.
The loop exists because each step prevents a category of failure. Skipping discovery causes wrong assumptions. Skipping
planning causes scope creep. Skipping verification ships broken code.
The threshold: if you can describe the diff in one sentence, skip planning. Otherwise, plan first.
The Assumption Interrupt
One declarative rule — apply it silently, don't narrate the check:
Never build on a contract you haven't read in this session. Each of these is an unverified
assumption — and every unverified assumption is a potential compile failure, runtime bug, or
behavioral regression:
- Using a method/type/interface without having read its definition
- Recalling an API from memory instead of reading current source
- Planning changes to code you haven't read in this session
These words in your reasoning are RED FLAGS:
- "probably" → You don't know. Read it.
- "likely" → You're guessing. Check it.
- "should have" → Assumption. Verify it.
- "typically" → General knowledge, not this codebase. Read it.
- "I remember" → Memory is unreliable. Read it now.
- "usually" → This codebase may differ. Check it.
- One logical change at a time
- Wear one hat at a time — never mix refactoring and behavior change in the same step. Refactor
first with behavior identical, verify, then change behavior; a diff that does both can't be
reviewed or bisected
- Verify each change works before moving to the next
- If a change touches 5+ files, break it into smaller steps
- Leave the codebase in a clean, working state at every step
- For multi-step tasks: track completed steps and remaining work
1. **Does this need to exist?** Speculative need → skip it, say so in one line. (YAGNI)
2. **Already in this codebase?** A helper, util, type, or pattern that already lives here → reuse it. Re-implementing
what's a few files over is the most common waste.
3. **Stdlib does it?** Use it.
4. **Native platform feature covers it?** `
` over a picker lib, CSS over JS, a DB constraint over
app-level code.
5. **An already-installed dependency solves it?** Use it. Never add a new dependency for what a few lines or an existing
one can do.
6. **Can it be one line?** One line.
7. **Only then** write the minimum code that works.
- Prefer functions over classes when either works
- Avoid inheritance unless the problem demands it
- Prefer explicit over implicit — no magic
- Keep permission checks and validation visible at the call site, not hidden in middleware the
next reader won't find
- Use descriptive names — longer is better than ambiguous
- Do the simplest thing that works, then optimize if measured performance requires it
- Prefer deep modules — a small interface hiding substantial implementation — over shallow ones that
expose nearly as much as they hide. A wrapper that only forwards calls earns nothing.
- Don't introduce a seam (interface, port, strategy) until two concrete implementations need it —
typically production plus test. One implementation behind an interface is indirection, not abstraction.
- Deletion test before adding an abstraction: imagine the module gone. If its complexity reappears in
every caller, it earns its place. If the complexity merely moves, it was a shallow pass-through —
inline it.
- `// shortcut: global lock — switch to per-account locks if throughput matters`
- `# shortcut: O(n²) scan, fine under ~1k rows — index if the set grows`
- Fail fast — reject the bad state where it enters the system, not three layers deeper where it
finally crashes
- Don't catch what you can't handle — log-and-continue converts a loud failure into silent
corruption; let it propagate
- Add context when propagating — wrap the error with what was being attempted and with which
inputs, so the operator can act without a debugger
- No silent fallbacks — substituting a default value on failure is a deliberate, visible decision,
never a reflex
- Match the codebase's existing error strategy (exceptions, result types, error codes) — don't
introduce a second one
- **Pure logic, no I/O** — test through the interface directly. No doubles needed.
- **Locally substitutable (in-memory DB, temp filesystem, fake clock)** — run the real code against the
substitute. A working stand-in beats a mock.
- **A service you own, across the network** — hide it behind a port with two adapters: the real HTTP/RPC
one for production, an in-memory one for tests.
- **Third-party or external** — inject it behind your own narrow interface and mock that interface, not
the vendor SDK.
- Search for similar features/components as reference
- Match the existing error handling strategy
- Use the same testing patterns found in adjacent tests
- Follow the project's naming conventions
- Read CLAUDE.md and lint config for project-specific rules
- When two patterns contradict, pick one (more recent / more tested) — explain the choice, flag
the other for cleanup. Never blend conflicting patterns into an average.
- If you think an existing convention is harmful, surface it explicitly. Don't fork it silently.
- **Duplication** — extract a shared function or type.
- **Long function doing several things** — split it along the boundaries of what it does.
- **Shallow module** (interface nearly as large as its implementation) — deepen it, or fold it into its
caller.
- **Feature envy** (a function reaching repeatedly into another type's internals) — move the logic to the
data it operates on.
- **Primitive obsession** (bare strings or ints carrying domain meaning) — introduce a value type.
- Never delete, skip, or comment out a failing test to get green
- Never loosen an assertion until it passes — that asserts the bug, not the behavior
- Never add a lint or type suppression (`eslint-disable`, `# type: ignore`, `as any`) to silence an
error you haven't understood
- Never wrap failing code in catch-and-ignore or a silent fallback — an error that vanishes is a
bug that relocated
- The one legitimate case: the check itself is wrong (it asserts old behavior the task explicitly
changes). Prove it, say so, then change the check visibly — never as a side effect.
- Write a failing test first, then implement until it passes
- Tests must verify intent, not just behavior — a test that can't fail when business logic
changes is testing nothing useful
- Use subagents for fresh-context review — they catch mistakes you'll miss in the same context
where you wrote the code
- For UI changes: take a screenshot, compare to requirements
- For API changes: test with actual requests
- For refactors: verify identical behavior before and after