$npx -y skills add UnpaidAttention/fable5-methodology --skill architecture-decisionsChoose between competing technical approaches and make design decisions with an explicit trade-off order. Trigger this when a task requires selecting among 2+ viable designs — "should we use X or Y", designing a schema, shaping a public API, choosing a storage/queue/cache mechani
| 1 | # Architecture Decisions |
| 2 | |
| 3 | Pick between approaches quickly, with an explicit priority order, weighted by how expensive |
| 4 | the decision is to reverse. |
| 5 | |
| 6 | ## Step 1: Check for a decision to make at all |
| 7 | |
| 8 | Grep the codebase for how this problem is already solved here (existing cache? existing error |
| 9 | envelope? existing job runner?). If an established pattern exists, use it — consistency with |
| 10 | the system beats a locally better choice. Only proceed to Step 2 when the problem is genuinely |
| 11 | new to this codebase or the existing pattern demonstrably fails a hard requirement. |
| 12 | |
| 13 | ## Step 2: Generate 2–3 candidates, no more |
| 14 | |
| 15 | For each candidate write three lines: |
| 16 | 1. Mechanism, one sentence. |
| 17 | 2. Worst failure mode — what happens when the dependency is down, input is malformed, load |
| 18 | spikes. |
| 19 | 3. What it makes hard later — the concrete thing, not "less flexible". |
| 20 | |
| 21 | If you have more than three candidates, you haven't understood the constraints; re-read them. |
| 22 | |
| 23 | ## Step 3: Apply the trade-off priority order |
| 24 | |
| 25 | Earlier beats later unless the user explicitly reorders: |
| 26 | |
| 27 | 1. **Correctness on known inputs** — including the edge cases this data will actually contain. |
| 28 | 2. **Failure behavior** — prefer designs that fail loudly, early, and partially over silently, |
| 29 | late, and totally. |
| 30 | 3. **Simplicity / reviewability** — could a competent stranger understand it in one read? |
| 31 | 4. **Consistency with the existing system.** |
| 32 | 5. **Extensibility** — only for axes of change with concrete evidence (a second case exists or |
| 33 | is scheduled). Speculative flexibility loses to simplicity, always. |
| 34 | 6. **Performance** — only past correctness, and only where measured or obviously hot |
| 35 | (per-request loop, O(n²) on unbounded n, I/O inside a loop). No restructuring without a number. |
| 36 | |
| 37 | Kill any candidate that fails a hard constraint (correctness, explicit user requirement). |
| 38 | Among survivors, take the simplest that doesn't foreclose a *known* future need. |
| 39 | |
| 40 | ## Step 4: Calibrate deliberation to reversibility |
| 41 | |
| 42 | - Switching later costs < 1 hour → pick the more conventional option NOW and move on. Close |
| 43 | calls between good options don't matter; record the choice in one line. |
| 44 | - Switching later means data migration, breaking API consumers, or redone work measured in |
| 45 | days → slow down; if the user's context could change the answer, ask one batched question |
| 46 | with your recommendation as the default. |
| 47 | |
| 48 | ## Step 5: Dependency sub-decision |
| 49 | |
| 50 | Adding a dependency IS an architecture decision. In order: |
| 51 | 1. Stdlib or an already-installed dependency does it (check the lockfile, not memory)? → use that. |
| 52 | 2. Hand-rolled version is <~50 lines with no tricky edges? → hand-roll. **Never** hand-roll |
| 53 | dates/timezones, crypto, unicode handling, or parsing of established formats — these are |
| 54 | always tricky. |
| 55 | 3. Otherwise: one boring, widely-used, maintained, license-compatible library. Never a |
| 56 | framework to use one function. |
| 57 | |
| 58 | ## Step 6: Abstraction sub-decision |
| 59 | |
| 60 | Introduce an interface/base class/plugin point only when two concrete cases already exist or a |
| 61 | second is actually scheduled. One implementation → write the direct version and leave a |
| 62 | one-line note naming the upgrade path. When you break any default pattern, leave a comment |
| 63 | naming the constraint that forced it — the constraint, not a narration. |
| 64 | |
| 65 | ## Default patterns (use unless codebase or user overrides) |
| 66 | |
| 67 | - Pure logic at the core; I/O (network, disk, DB, clock, randomness) at the edges, passed in. |
| 68 | - Errors via the language's native mechanism; fail fast on programmer errors; contextualize |
| 69 | expected failures; never catch-and-continue silently. |
| 70 | - Persistence invariants live in the database (constraints, unique indexes, FKs), not only in |
| 71 | app code. |
| 72 | - Validate at trust boundaries; trust internally. |
| 73 | - Composition over inheritance; functions over classes when no state is carried. |
| 74 | |
| 75 | ## Worked example |
| 76 | |
| 77 | Task: "Products API is slow; add caching to product lookups." |
| 78 | |
| 79 | - Step 1: grep finds `redis` in the lockfile, used for sessions. Candidate space narrows. |
| 80 | - Candidates: (A) Redis cache-aside, TTL 5 min. (B) In-process LRU per instance. |
| 81 | (C) Materialized view refreshed on write. |
| 82 | - Failure modes: A — stale up to TTL; Redis outage degrades to DB (fine). B — inconsistent |
| 83 | across instances after writes (there are 4 instances → correctness problem for |
| 84 | post-update reads). C — write |