$npx -y skills add UnpaidAttention/fable5-methodology --skill implementation-standardsConcrete coding conventions to apply WHILE writing or editing code — naming, function/file size, error handling, input validation, types, comments, tests, and secrets. Trigger this whenever you are about to write new code or modify existing code in any language, including tests a
| 1 | # Implementation Standards |
| 2 | |
| 3 | Precedence when rules conflict: explicit user instruction > existing codebase convention > |
| 4 | this skill. Before writing anything in an existing project, read 2–3 neighboring files and |
| 5 | copy their naming, error style, import order, and test idiom — even where you'd personally |
| 6 | choose differently. |
| 7 | |
| 8 | ## Naming |
| 9 | |
| 10 | 1. Name for domain meaning, not type: `overdueInvoices`, not `invoiceList2`. No new |
| 11 | abbreviations the codebase doesn't already use. |
| 12 | 2. Functions are verbs (`fetchUser`); booleans are predicates (`is_expired`, `hasAccess`); |
| 13 | collections are plural. |
| 14 | 3. A name that needs a comment is the wrong name — rename instead of annotating. |
| 15 | 4. Match the file's existing casing exactly, even if the repo is inconsistent elsewhere. |
| 16 | |
| 17 | ## Structure and size |
| 18 | |
| 19 | 1. One function, one thing. Describing it needs "and" → split. Soft limit ~50 lines. |
| 20 | 2. Nesting > 3 levels → invert with early returns / guard clauses. |
| 21 | 3. Files: one concern; split around 400 lines, always by 800. |
| 22 | 4. Duplication rule of three: copy once freely; extract on the THIRD occurrence, not the |
| 23 | second — two instances don't yet reveal the axis of variation. |
| 24 | 5. Dead code, commented-out code, unused imports: delete on sight in files you touch. Git is |
| 25 | the archive. |
| 26 | |
| 27 | ## Error handling |
| 28 | |
| 29 | 1. Every fallible operation (I/O, network, parse, subprocess) has an explicit failure story: |
| 30 | propagate with added context, recover, or convert to a user-facing message. `except: pass` |
| 31 | and `.catch(() => {})` are forbidden. |
| 32 | 2. Error messages carry: what was attempted, with what input, and what to do: |
| 33 | `"failed to load config {path}: {err} — run 'app init' to create one"`. |
| 34 | 3. Catch the narrowest type that covers the case; broad catches only at the process top level, |
| 35 | where they log and exit non-zero. |
| 36 | 4. Cleanup via the language's scoped construct (context manager, `defer`, `finally`, RAII) — |
| 37 | never duplicated manual cleanup on both branches. |
| 38 | |
| 39 | ## Input validation |
| 40 | |
| 41 | 1. Validate at every trust boundary — HTTP handler, CLI parse, file read, message consumer, |
| 42 | env var read. Schema validation where the ecosystem provides it (zod, pydantic, serde). |
| 43 | 2. Fail fast, naming the field and the violated constraint. |
| 44 | 3. Interiors trust validated input — do not re-validate defensively at every layer; duplicated |
| 45 | validation drifts inconsistent. |
| 46 | |
| 47 | ## Types |
| 48 | |
| 49 | 1. No escape hatches (`any`, `as unknown as`, bare `interface{}`, `unsafe`) without a comment |
| 50 | naming the forcing constraint. |
| 51 | 2. Make illegal states unrepresentable when cheap (sum type over struct-of-nullables); don't |
| 52 | build type-level puzzles where a runtime check plus a test suffices. |
| 53 | 3. Python/JS: annotate public function signatures at minimum. |
| 54 | |
| 55 | ## Comments |
| 56 | |
| 57 | 1. Comment the WHY code can't show — external constraints, protocol quirks, why the obvious |
| 58 | approach fails. Never the WHAT (`# increment counter`). |
| 59 | 2. Never narrate your editing process ("added to fix bug", "changed from previous version") — |
| 60 | noise the moment it merges. |
| 61 | 3. TODOs carry an owner or a condition: `// TODO(shaun): remove after v2 migration`. |
| 62 | 4. Deliberate shortcuts name their ceiling and upgrade path: |
| 63 | `# shortcut: global lock — per-account locks if throughput matters`. |
| 64 | |
| 65 | ## Tests |
| 66 | |
| 67 | 1. Every behavior change ships ≥1 test that fails without the change. Exempt: trivial |
| 68 | one-liners. Never exempt: branches, loops, parsers, money, security. |
| 69 | 2. Every test asserts a concrete value or state change. `toBeDefined()` / `not.toThrow()` as |
| 70 | the only assertion is not a test. |
| 71 | 3. Names state scenario + expectation: `test_expired_token_returns_401`, not `test_auth_2`. |
| 72 | 4. Test through the public interface; reaching into internals is a design smell. |
| 73 | 5. No inter-test dependence: any order passes. |
| 74 | |
| 75 | ## Secrets and configuration |
| 76 | |
| 77 | 1. No secrets in source, ever. Env vars or a secret manager; assert presence at startup. |
| 78 | 2. Magic numbers become named constants when they carry meaning (`MAX_RETRIES = 3`); don't |
| 79 | name obvious arithmetic. |
| 80 | 3. A value that never varies is a constant, not a config option. |
| 81 | |
| 82 | ## Diff discipline |
| 83 | |
| 84 | The diff contains the requested change and nothing else. No drive-by refactors, renames, or |
| 85 | reformatting of untouched lines — note improvement opportunities in your report instead of |
| 86 | doing them. Debug prints are removed before delivery. |
| 87 | |
| 88 | ## Worked example — weak vs. standard |
| 89 | |
| 90 | Task: read a JSON config and return the port. |
| 91 | |
| 92 | Weak: |
| 93 | ```python |
| 94 | def get_port(f): |
| 95 | try |