@causyne/agent 0.0.0-stage → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +102 -0
- package/README.md +31 -2
- package/SECURITY.md +24 -0
- package/bin/causyne.js +13 -0
- package/docs/ARCHITECTURE.md +63 -0
- package/docs/PROVENANCE.md +29 -0
- package/docs/SECURITY_REVIEW.md +89 -0
- package/docs/THIRD_PARTY_NOTICES.md +49 -0
- package/native-skills/ai-engineering/context-window-management/SKILL.md +43 -0
- package/native-skills/ai-engineering/llm-evaluation/SKILL.md +46 -0
- package/native-skills/ai-engineering/model-cost-and-latency/SKILL.md +46 -0
- package/native-skills/ai-engineering/prompt-construction/SKILL.md +42 -0
- package/native-skills/ai-engineering/retrieval-augmented-generation/SKILL.md +42 -0
- package/native-skills/ai-engineering/structured-output-and-schemas/SKILL.md +45 -0
- package/native-skills/ai-engineering/tool-calling-agents/SKILL.md +43 -0
- package/native-skills/android/app-performance-profiling/SKILL.md +42 -0
- package/native-skills/android/background-work-scheduling/SKILL.md +47 -0
- package/native-skills/android/compose-state-and-recomposition/SKILL.md +43 -0
- package/native-skills/android/gradle-build-configuration/SKILL.md +41 -0
- package/native-skills/android/lifecycle-and-leaks/SKILL.md +46 -0
- package/native-skills/android/play-store-release/SKILL.md +43 -0
- package/native-skills/android/room-and-persistence/SKILL.md +40 -0
- package/native-skills/backend/authentication-and-sessions/SKILL.md +45 -0
- package/native-skills/backend/background-jobs-and-queues/SKILL.md +42 -0
- package/native-skills/backend/caching-strategy/SKILL.md +44 -0
- package/native-skills/backend/graceful-shutdown/SKILL.md +42 -0
- package/native-skills/backend/rate-limiting-and-quotas/SKILL.md +40 -0
- package/native-skills/backend/rest-api-design/SKILL.md +40 -0
- package/native-skills/backend/webhook-delivery-and-receipt/SKILL.md +43 -0
- package/native-skills/cloud/cost-optimisation/SKILL.md +46 -0
- package/native-skills/cloud/iam-least-privilege/SKILL.md +43 -0
- package/native-skills/cloud/infrastructure-as-code/SKILL.md +43 -0
- package/native-skills/cloud/multi-region-and-failover/SKILL.md +43 -0
- package/native-skills/cloud/serverless-patterns/SKILL.md +47 -0
- package/native-skills/data/data-quality-monitoring/SKILL.md +51 -0
- package/native-skills/data/etl-pipeline-reliability/SKILL.md +43 -0
- package/native-skills/data/exploratory-analysis/SKILL.md +43 -0
- package/native-skills/data/metric-definition/SKILL.md +42 -0
- package/native-skills/data/notebook-to-production/SKILL.md +43 -0
- package/native-skills/database/backup-and-recovery/SKILL.md +43 -0
- package/native-skills/database/connection-pooling/SKILL.md +42 -0
- package/native-skills/database/data-integrity-and-constraints/SKILL.md +42 -0
- package/native-skills/database/query-optimisation/SKILL.md +38 -0
- package/native-skills/database/safe-migrations/SKILL.md +47 -0
- package/native-skills/database/schema-design/SKILL.md +38 -0
- package/native-skills/database/transactions-and-isolation/SKILL.md +41 -0
- package/native-skills/design/colour-system-construction/SKILL.md +38 -0
- package/native-skills/design/design-direction-brief/SKILL.md +41 -0
- package/native-skills/design/motion-and-transitions/SKILL.md +38 -0
- package/native-skills/design/responsive-strategy/SKILL.md +39 -0
- package/native-skills/design/spacing-and-rhythm/SKILL.md +38 -0
- package/native-skills/design/type-scale-and-hierarchy/SKILL.md +39 -0
- package/native-skills/design/visual-qa-loop/SKILL.md +42 -0
- package/native-skills/devops/docker-multi-stage-builds/SKILL.md +60 -0
- package/native-skills/devops/github-actions-ci/SKILL.md +60 -0
- package/native-skills/devops/incident-triage/SKILL.md +38 -0
- package/native-skills/devops/kubernetes-deployment-hardening/SKILL.md +64 -0
- package/native-skills/devops/observability-baseline/SKILL.md +38 -0
- package/native-skills/devops/secret-management/SKILL.md +40 -0
- package/native-skills/devops/terraform-state-and-modules/SKILL.md +36 -0
- package/native-skills/devops/zero-downtime-deploys/SKILL.md +40 -0
- package/native-skills/git/bisect-regression-hunt/SKILL.md +43 -0
- package/native-skills/git/branch-strategy/SKILL.md +39 -0
- package/native-skills/git/commit-hygiene/SKILL.md +41 -0
- package/native-skills/git/history-recovery/SKILL.md +44 -0
- package/native-skills/git/large-repository-surgery/SKILL.md +46 -0
- package/native-skills/git/merge-conflict-resolution/SKILL.md +42 -0
- package/native-skills/media/cinematic-3d-shot/SKILL.md +104 -0
- package/native-skills/mobile/app-startup-optimisation/SKILL.md +41 -0
- package/native-skills/mobile/cross-platform-tradeoffs/SKILL.md +42 -0
- package/native-skills/mobile/deep-linking/SKILL.md +39 -0
- package/native-skills/mobile/mobile-storage-security/SKILL.md +45 -0
- package/native-skills/mobile/offline-first-sync/SKILL.md +42 -0
- package/native-skills/mobile/push-notifications/SKILL.md +38 -0
- package/native-skills/performance/algorithmic-complexity/SKILL.md +43 -0
- package/native-skills/performance/concurrency-and-parallelism/SKILL.md +42 -0
- package/native-skills/performance/load-testing/SKILL.md +42 -0
- package/native-skills/performance/memory-and-leak-hunting/SKILL.md +41 -0
- package/native-skills/performance/network-and-payload-efficiency/SKILL.md +42 -0
- package/native-skills/performance/profiling-methodology/SKILL.md +46 -0
- package/native-skills/python/async-correctness/SKILL.md +40 -0
- package/native-skills/python/cli-tool-construction/SKILL.md +44 -0
- package/native-skills/python/data-processing-pipelines/SKILL.md +38 -0
- package/native-skills/python/environment-isolation/SKILL.md +41 -0
- package/native-skills/python/packaging-and-distribution/SKILL.md +56 -0
- package/native-skills/python/pytest-suite-design/SKILL.md +39 -0
- package/native-skills/python/type-hints-and-static-checking/SKILL.md +42 -0
- package/native-skills/security/authorization-model/SKILL.md +42 -0
- package/native-skills/security/crypto-usage/SKILL.md +48 -0
- package/native-skills/security/dependency-supply-chain/SKILL.md +42 -0
- package/native-skills/security/input-validation-and-injection/SKILL.md +44 -0
- package/native-skills/security/secure-file-uploads/SKILL.md +46 -0
- package/native-skills/security/security-headers-and-csp/SKILL.md +44 -0
- package/native-skills/security/threat-modelling/SKILL.md +46 -0
- package/native-skills/testing/end-to-end-test-stability/SKILL.md +42 -0
- package/native-skills/testing/property-based-testing/SKILL.md +46 -0
- package/native-skills/testing/regression-test-from-a-bug/SKILL.md +41 -0
- package/native-skills/testing/snapshot-and-visual-regression/SKILL.md +42 -0
- package/native-skills/testing/test-doubles-and-mocking/SKILL.md +43 -0
- package/native-skills/testing/test-strategy-and-pyramid/SKILL.md +39 -0
- package/native-skills/testing/writing-good-assertions/SKILL.md +42 -0
- package/native-skills/web/accessible-components/SKILL.md +45 -0
- package/native-skills/web/api-client-resilience/SKILL.md +39 -0
- package/native-skills/web/bundle-size-reduction/SKILL.md +38 -0
- package/native-skills/web/core-web-vitals/SKILL.md +40 -0
- package/native-skills/web/css-layout-systems/SKILL.md +41 -0
- package/native-skills/web/form-validation-ux/SKILL.md +39 -0
- package/native-skills/web/react-state-architecture/SKILL.md +41 -0
- package/native-skills/web/ssr-hydration-correctness/SKILL.md +45 -0
- package/package.json +68 -4
- package/src/agentLoop.js +2458 -0
- package/src/appChannel.js +109 -0
- package/src/approvalGrants.js +66 -0
- package/src/auditLog.js +52 -0
- package/src/autoUpdate.js +12 -0
- package/src/capabilities.js +36 -0
- package/src/checkpoints.js +1199 -0
- package/src/cliMode.js +638 -0
- package/src/commands.js +805 -0
- package/src/compiledSkills.js +540 -0
- package/src/config.js +130 -0
- package/src/contextManager.js +498 -0
- package/src/cortex/autoCondenser.js +192 -0
- package/src/cortex/provisioner.js +27 -0
- package/src/cortex/vectorDb.js +447 -0
- package/src/designPalettes.js +99 -0
- package/src/devMode.js +6 -0
- package/src/embodimentSession.js +180 -0
- package/src/entrypoint.js +194 -0
- package/src/execSafe.js +21 -0
- package/src/gitSnapshot.js +378 -0
- package/src/mcp/jsonRpc.js +165 -0
- package/src/mcp/mcpClient.js +136 -0
- package/src/mcp/mcpRegistry.js +207 -0
- package/src/memory.js +62 -0
- package/src/memoryManager.js +124 -0
- package/src/modelCatalogue.js +132 -0
- package/src/modelIds.js +42 -0
- package/src/modelRouter.js +145 -0
- package/src/outputStore.js +246 -0
- package/src/perception.js +124 -0
- package/src/planCache.js +437 -0
- package/src/procSignal.js +32 -0
- package/src/providers/anthropic.js +408 -0
- package/src/providers/base.js +328 -0
- package/src/providers/dashscope-byok.js +86 -0
- package/src/providers/deepseek.js +80 -0
- package/src/providers/factory.js +37 -0
- package/src/providers/gemini.js +346 -0
- package/src/providers/moonshot.js +73 -0
- package/src/providers/muse.js +169 -0
- package/src/providers/ollama.js +104 -0
- package/src/providers/openai-compatible.js +84 -0
- package/src/providers/openai.js +20 -0
- package/src/providers/qwen-byok.js +76 -0
- package/src/providers/xai.js +73 -0
- package/src/provisioning/capabilityConsent.js +24 -0
- package/src/provisioning/capabilityProvisioner.js +271 -0
- package/src/provisioning/capabilityTools.js +29 -0
- package/src/reporting.js +373 -0
- package/src/sandbox.js +529 -0
- package/src/sessionStore.js +279 -0
- package/src/skillsManager.js +288 -0
- package/src/slopDetector.js +307 -0
- package/src/taskBuffer.js +45 -0
- package/src/taskShape.js +241 -0
- package/src/textDiff.js +150 -0
- package/src/tokenTracker.js +560 -0
- package/src/tool-scripts/ax_snapshot.applescript +39 -0
- package/src/tool-scripts/ax_snapshot.ps1 +10 -0
- package/src/tool-scripts/desktop_act.applescript +58 -0
- package/src/tool-scripts/resolve_automator.py +119 -0
- package/src/toolPacks.js +524 -0
- package/src/tools/archive.js +63 -0
- package/src/tools/astryxOracle.js +78 -0
- package/src/tools/axSnapshot.js +165 -0
- package/src/tools/blenderAutomator.js +349 -0
- package/src/tools/browserAutomator.js +252 -0
- package/src/tools/checkSystemSpecs.js +64 -0
- package/src/tools/clipboard.js +36 -0
- package/src/tools/codeSearchAst.js +101 -0
- package/src/tools/codeSearcher.js +230 -0
- package/src/tools/codeTools.js +262 -0
- package/src/tools/codebaseSearch.js +75 -0
- package/src/tools/createSkill.js +129 -0
- package/src/tools/deferredMcpSearch.js +246 -0
- package/src/tools/desktopAct.js +208 -0
- package/src/tools/desktopOpen.js +34 -0
- package/src/tools/dynamicLoader.js +44 -0
- package/src/tools/executeProceduralMedia.js +93 -0
- package/src/tools/fsDelete.js +51 -0
- package/src/tools/fsEdit.js +105 -0
- package/src/tools/fsFind.js +128 -0
- package/src/tools/fsList.js +77 -0
- package/src/tools/fsMove.js +57 -0
- package/src/tools/fsRead.js +87 -0
- package/src/tools/fsSearch.js +198 -0
- package/src/tools/fsWrite.js +41 -0
- package/src/tools/gameAiBehaviorTree.js +208 -0
- package/src/tools/gameAnimationRig.js +194 -0
- package/src/tools/gameAssetFetcher.js +372 -0
- package/src/tools/gameAssetHarvester.js +210 -0
- package/src/tools/gameAudioSynth.js +267 -0
- package/src/tools/gameBuilder.js +886 -0
- package/src/tools/gameCanvasAudit.js +101 -0
- package/src/tools/gameCinematicSequencer.js +151 -0
- package/src/tools/gameDataForge.js +124 -0
- package/src/tools/gameDialogueTreeGen.js +166 -0
- package/src/tools/gameEconomyBalancer.js +161 -0
- package/src/tools/gameEntitySpawner.js +207 -0
- package/src/tools/gameLoreForge.js +153 -0
- package/src/tools/gameQuestArcBuilder.js +152 -0
- package/src/tools/gameSceneBuilder.js +158 -0
- package/src/tools/gameShaderForge.js +181 -0
- package/src/tools/gameTilemapGenerator.js +164 -0
- package/src/tools/gameUiHudBuilder.js +292 -0
- package/src/tools/gameVfxEmitterGen.js +108 -0
- package/src/tools/gameWebRuntimeInspector.js +91 -0
- package/src/tools/generateMediaViaApi.js +358 -0
- package/src/tools/gitExec.js +54 -0
- package/src/tools/godotAutomator.js +437 -0
- package/src/tools/manageLocalAiDependencies.js +220 -0
- package/src/tools/memoryWrite.js +99 -0
- package/src/tools/multiEdit.js +107 -0
- package/src/tools/palmierAutomator.js +104 -0
- package/src/tools/pdfReader.js +112 -0
- package/src/tools/persistentShell.js +148 -0
- package/src/tools/printer.js +316 -0
- package/src/tools/procTerm.js +91 -0
- package/src/tools/processList.js +52 -0
- package/src/tools/readNativeSkill.js +39 -0
- package/src/tools/registry.js +277 -0
- package/src/tools/resolveAutomator.js +102 -0
- package/src/tools/screenshot.js +75 -0
- package/src/tools/subagentOrchestrator.js +305 -0
- package/src/tools/sysRun.js +125 -0
- package/src/tools/todoWrite.js +75 -0
- package/src/tools/toolDispatch.js +127 -0
- package/src/tools/toolForge.js +124 -0
- package/src/tools/updatePlan.js +88 -0
- package/src/tools/viewImage.js +82 -0
- package/src/tools/visionInspector.js +154 -0
- package/src/tools/webFetch.js +74 -0
- package/src/tools/webSearch.js +82 -0
- package/src/tui/ask.js +164 -0
- package/src/tui/banner.js +158 -0
- package/src/tui/block.js +152 -0
- package/src/tui/chatRenderer.js +953 -0
- package/src/tui/completion.js +424 -0
- package/src/tui/dialogs.js +606 -0
- package/src/tui/diff.js +296 -0
- package/src/tui/history.js +135 -0
- package/src/tui/home.js +217 -0
- package/src/tui/invocation.js +105 -0
- package/src/tui/keys.js +443 -0
- package/src/tui/lineEditor.js +256 -0
- package/src/tui/overlay.js +256 -0
- package/src/tui/prompt.js +1258 -0
- package/src/tui/selectList.js +335 -0
- package/src/tui/stage.js +330 -0
- package/src/tui/stageSurface.js +609 -0
- package/src/tui/statusLine.js +254 -0
- package/src/tui/text.js +345 -0
- package/src/tui/theme.js +393 -0
- package/src/tui/toolDisplay.js +176 -0
- package/src/tui/transcript.js +235 -0
- package/src/tui/viewport.js +337 -0
- package/src/uncertainty.js +271 -0
- package/src/userSkills.js +265 -0
- package/src/utils/cli_ui.js +0 -0
- package/src/utils/inquirer.js +12 -0
- package/src/utils/paths.js +37 -0
- package/src/version.js +38 -0
- package/src/vfeTracker.js +220 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
// Generated by scripts/compile_skills.js. Readable product skills.
|
|
2
|
+
export const COMPILED_SKILLS = {
|
|
3
|
+
"ai-engineering": {
|
|
4
|
+
"context-window-management": {
|
|
5
|
+
"name": "context-window-management",
|
|
6
|
+
"description": "Handle long conversations or large inputs with a language model. Use when context is exhausted, when cost is high, or when a model forgets earlier instructions.",
|
|
7
|
+
"content": "---\nname: context-window-management\ndescription: Handle long conversations or large inputs with a language model. Use when context is exhausted, when cost is high, or when a model forgets earlier instructions.\n---\n\n# Context Window Management\n\n## The two costs\n\nTokens cost money, and attention is finite. A model given a hundred thousand tokens attends unevenly across them — material in the middle is reliably weakest. Filling the window is not the same as using it.\n\n## Workflow\n\n1. Measure where the tokens go.\n - System prompt, tool definitions, retrieved context, conversation history, current input. One of these is usually dominant and unexamined — tool definitions in particular grow silently.\n - **Done when:** the token budget is broken down by section.\n\n2. Order for attention and for caching at once.\n - Stable content first (system, tools, reference material), volatile last (recent turns, current question). This maximises the cacheable prefix AND puts the question at the end, where recall is strongest.\n - **Done when:** the prefix is byte-identical across calls and the question is last.\n\n3. Compact history rather than truncating it.\n - Dropping the oldest messages loses the original intent, which is usually the most important thing in the conversation. Keep the first message, summarise the middle, keep recent turns verbatim.\n - **Done when:** the original task survives any amount of compaction.\n\n4. Spill large tool outputs instead of inlining them.\n - Store the full output, put a summary and a reference in the context, and let the model request more. A single large file read should not consume a third of the window.\n - **Done when:** no single tool result dominates the context.\n\n5. Retrieve rather than include.\n - Do not put a whole document in the prompt when a retrieval step can select the relevant part. This is cheaper and usually more accurate, because it removes distractors.\n - **Done when:** included material is selected rather than wholesale.\n\n6. Handle the limit before you hit it.\n - Detect approaching the ceiling and compact proactively. An overflow error mid-task loses the work.\n - **Done when:** the system compacts before overflow rather than failing.\n\n## Verification\n\n- The token budget is reported by section.\n- Cache hit rate is measured on repeated calls.\n- A long conversation retains the original instruction after compaction.\n- Approaching the limit triggers compaction, not an error.\n"
|
|
8
|
+
},
|
|
9
|
+
"llm-evaluation": {
|
|
10
|
+
"name": "llm-evaluation",
|
|
11
|
+
"description": "Measure whether a model, prompt, or pipeline change is an improvement. Use before shipping any prompt or model change, and when quality is being debated from anecdotes.",
|
|
12
|
+
"content": "---\nname: llm-evaluation\ndescription: Measure whether a model, prompt, or pipeline change is an improvement. Use before shipping any prompt or model change, and when quality is being debated from anecdotes.\n---\n\n# LLM Evaluation\n\n## Why anecdotes fail here\n\nModel output is non-deterministic and its failures are uneven. A change that fixes the three cases you tried can break twenty you did not. Without a fixed set, every prompt change is a guess and quality drifts in both directions unnoticed.\n\n## Workflow\n\n1. Build the set from real failures.\n - Start with cases the system got wrong in production. Fifty realistic cases beat five hundred synthetic ones. Include the ambiguous, the adversarial, and the out-of-scope.\n - **Done when:** the set contains every failure mode you have actually observed.\n\n2. Choose the grader to match the task.\n - Exact match or a schema check for structured output — cheap and exact, use it wherever it applies.\n - A programmatic check for verifiable properties: does the code run, is the citation real, is the total correct.\n - A model grader for open-ended quality, with a rubric and a calibration set showing it agrees with human judgement.\n - Human review for the final call on anything user-facing.\n - **Done when:** every case has a grader and model graders are calibrated against human labels.\n\n3. Fix the seed and pin the model version.\n - A provider silently updating a model version invalidates every comparison. Pin explicitly and record it with the results.\n - **Done when:** results record the exact model version and parameters.\n\n4. Compare against a baseline, always.\n - A score means nothing alone. Run the current version and the change on the same set, and report both.\n - **Done when:** every result is a paired comparison.\n\n5. Look at regressions, not just the average.\n - A change that lifts the mean while breaking five previously-passing cases is usually not worth shipping. Report what got worse.\n - **Done when:** the per-case diff is reviewed, not just the aggregate.\n\n6. Run it in CI.\n - A manual evaluation is run when someone remembers. Gate on the regression count.\n - **Done when:** a prompt change with regressions fails the build.\n\n## Verification\n\n- The set covers every known failure mode.\n- Results are paired against a baseline on a pinned model version.\n- Per-case regressions are reported.\n- CI runs the evaluation on prompt changes.\n"
|
|
13
|
+
},
|
|
14
|
+
"model-cost-and-latency": {
|
|
15
|
+
"name": "model-cost-and-latency",
|
|
16
|
+
"description": "Reduce the cost or latency of a language-model feature. Use when spend is higher than expected, when responses are slow, or when choosing a model.",
|
|
17
|
+
"content": "---\nname: model-cost-and-latency\ndescription: Reduce the cost or latency of a language-model feature. Use when spend is higher than expected, when responses are slow, or when choosing a model.\n---\n\n# Model Cost and Latency\n\n## Where the money goes\n\nInput tokens, usually. A long system prompt sent on every request dominates spend far more often than output length does. Measure before optimising anything.\n\n## Workflow\n\n1. Instrument per-call cost and latency.\n - Input tokens, output tokens, cached tokens, model, and latency, tagged by feature. Without this you are optimising by anecdote.\n - **Done when:** cost per feature is visible on a dashboard.\n\n2. Maximise prompt caching.\n - Put stable content first and keep the prefix byte-identical. A cached prefix is dramatically cheaper and faster, and this is usually the single largest available win — before any model change.\n - **Done when:** the cache hit rate is measured and the prefix is stable.\n\n3. Route by difficulty.\n - Most requests do not need the largest model. Use a small fast model by default and escalate on a measurable signal — low confidence, a failed validation, or a complexity classifier.\n - **Done when:** the routing rule is stated and its accuracy is measured on the evaluation set.\n\n4. Stream to fix perceived latency.\n - Time to first token is what users feel. Streaming makes a slow response feel responsive without making it faster.\n - **Done when:** the user sees output within a few hundred milliseconds.\n\n5. Cut the input before cutting the model.\n - Trim the system prompt, remove unused tool definitions, retrieve less and rank better. Halving the input often costs no quality at all, where a smaller model might.\n - **Done when:** every section of the prompt has been justified against its token cost.\n\n6. Cache complete responses where the input repeats.\n - Identical or near-identical requests do not need a second call. Semantic caching catches paraphrases, with a similarity threshold tuned to avoid wrong hits.\n - **Done when:** repeated questions are served without a model call.\n\n7. Verify quality did not regress.\n - Every cost reduction must be checked against the evaluation set. A cheaper wrong answer is not cheaper.\n - **Done when:** the evaluation score is unchanged or the trade-off is stated explicitly.\n\n## Verification\n\n- Cost per request is reported before and after.\n- Cache hit rate is measured.\n- Evaluation scores are unchanged, or the regression is quantified and accepted.\n"
|
|
18
|
+
},
|
|
19
|
+
"prompt-construction": {
|
|
20
|
+
"name": "prompt-construction",
|
|
21
|
+
"description": "Write or repair a prompt for a language model. Use when model output is inconsistent, when a prompt has grown by accretion, or when building any LLM-backed feature.",
|
|
22
|
+
"content": "---\nname: prompt-construction\ndescription: Write or repair a prompt for a language model. Use when model output is inconsistent, when a prompt has grown by accretion, or when building any LLM-backed feature.\n---\n\n# Prompt Construction\n\n## The shape that works\n\nRole and task, then constraints, then the input, then the required output shape. Put the instruction BEFORE long context: attention to instructions degrades when they are buried after thousands of tokens of material.\n\n## Workflow\n\n1. State the task in one sentence, then the constraints as a list.\n - Prose paragraphs of mixed instruction and context are where prompts go to become unmaintainable. A list is auditable.\n - **Done when:** the task is one sentence and every constraint is a separate line.\n\n2. Say what NOT to do, explicitly.\n - The highest-leverage lines in most prompts. \"Do not invent a citation.\" \"Do not answer if the context does not contain the answer.\" Negative constraints are what stop the confident wrong answer.\n - **Done when:** the failure modes you have actually observed each have a prohibition.\n\n3. Specify the output format precisely, and validate it.\n - Ask for a named schema and parse the result. Where the provider supports structured output or tool calling, use it — that is a hard guarantee rather than a request.\n - **Done when:** the response is parsed into a validated type, and a parse failure is handled.\n\n4. Show, do not only tell.\n - Two or three examples covering the boundary cases outperform paragraphs of description. Include a hard case and an ambiguous one; examples that are all easy teach nothing.\n - **Done when:** examples cover the cases the description leaves ambiguous.\n\n5. Order for caching.\n - Put stable content first — system instructions, tool definitions, long reference material — and volatile content last. Providers cache a matching prefix, so this ordering is the single largest cost reduction available on a repeated prompt.\n - **Done when:** the prefix is identical across calls.\n\n6. Change one thing at a time and measure.\n - A prompt edited by feel, several changes at once, cannot be attributed. Keep a small evaluation set.\n - **Done when:** each change has a before and after score on the evaluation set.\n\n## Verification\n\n- The output parses into the declared schema every time across the evaluation set.\n- Each observed failure mode has a constraint addressing it.\n- The stable prefix is identical between calls.\n"
|
|
23
|
+
},
|
|
24
|
+
"retrieval-augmented-generation": {
|
|
25
|
+
"name": "retrieval-augmented-generation",
|
|
26
|
+
"description": "Build or fix a retrieval system that feeds a language model. Use when answers cite the wrong source, when retrieval returns irrelevant chunks, or when building search over documents.",
|
|
27
|
+
"content": "---\nname: retrieval-augmented-generation\ndescription: Build or fix a retrieval system that feeds a language model. Use when answers cite the wrong source, when retrieval returns irrelevant chunks, or when building search over documents.\n---\n\n# Retrieval-Augmented Generation\n\n## Where these systems actually fail\n\nAlmost never in the generation step. They fail in retrieval — the right passage was not in the top results, so the model answered from its own parameters and sounded equally confident. Debug retrieval first, always.\n\n## Workflow\n\n1. Evaluate retrieval on its own.\n - Build a set of questions with known correct sources. Measure recall at k. If the right chunk is not retrieved, no prompt change can fix the answer.\n - **Done when:** recall at k is measured and reported separately from answer quality.\n\n2. Chunk on structure, not on length.\n - Split at headings, paragraphs and other semantic boundaries. A fixed character split cuts sentences in half and produces chunks that mean nothing alone. Include the document title and heading path in each chunk so it is self-describing.\n - **Done when:** a randomly sampled chunk is comprehensible in isolation.\n\n3. Combine keyword and vector search.\n - Vector search misses exact identifiers, error codes and rare proper nouns; keyword search misses paraphrase. Hybrid retrieval with reciprocal rank fusion beats either alone on almost every corpus.\n - **Done when:** both retrievers run and their results are fused.\n\n4. Rerank the candidates.\n - Retrieve broadly, then rerank with a cross-encoder and keep a few. This consistently produces the largest quality gain per unit of effort in the whole pipeline.\n - **Done when:** a reranking stage exists and its effect on recall is measured.\n\n5. Make grounding enforceable.\n - Instruct the model to answer only from the context and to say so when the context is insufficient. Require a citation per claim, and verify the citation points at retrieved text.\n - **Done when:** a question with no supporting context produces a refusal, not an invention.\n\n6. Handle the update path.\n - Documents change. Re-embedding everything on each change does not scale; track content hashes and re-embed only what changed.\n - **Done when:** updating one document does not require a full reindex.\n\n## Verification\n\n- Retrieval recall is measured against a labelled set.\n- An out-of-corpus question is refused rather than answered.\n- Every claim in an answer maps to a retrieved passage.\n"
|
|
28
|
+
},
|
|
29
|
+
"structured-output-and-schemas": {
|
|
30
|
+
"name": "structured-output-and-schemas",
|
|
31
|
+
"description": "Get reliable structured data out of a language model. Use when parsing model output fails, when JSON is malformed, or when output must feed another system.",
|
|
32
|
+
"content": "---\nname: structured-output-and-schemas\ndescription: Get reliable structured data out of a language model. Use when parsing model output fails, when JSON is malformed, or when output must feed another system.\n---\n\n# Structured Output and Schemas\n\n## The ordering of defences\n\n1. Use the provider's structured output or tool-calling mode. This constrains generation and is a guarantee, not a request.\n2. Where unavailable, ask for a schema and validate.\n3. Repair or retry on failure.\n4. Never regex your way through malformed output — that fails silently on the case you did not anticipate.\n\n## Workflow\n\n1. Define the schema first, in code.\n - A single definition generates both the prompt's schema description and the runtime validator. Two hand-written copies drift, and the drift shows up as an unparseable response in production.\n - **Done when:** the schema has one source and both uses derive from it.\n\n2. Keep the schema shallow and named.\n - Deep nesting and unions raise the failure rate. Descriptive field names do real work — `total_amount_usd` produces better output than `amt`, because the name is part of the instruction.\n - **Done when:** nesting is shallow and every field name is self-explanatory.\n\n3. Make the uncertain case representable.\n - If the model may not know a value, the schema needs a null or an explicit unknown. Without one, it will invent a plausible value — the schema forces a shape, not a truth.\n - **Done when:** every field the model might not know can express that.\n\n4. Validate, then retry with the error.\n - On a validation failure, send the error back and ask for a correction. This recovers most failures in one extra call. Bound the retries.\n - **Done when:** a validation failure triggers a bounded repair loop.\n\n5. Validate semantics, not just shape.\n - A schema check confirms it is a number, not that it is the right number. Range checks, referential checks, and cross-field consistency belong after parsing.\n - **Done when:** semantic checks run after schema validation.\n\n6. Log every failure with the raw output.\n - Failure patterns tell you what to fix in the schema or the prompt. Without the raw text there is nothing to learn from.\n - **Done when:** raw output is retained on every parse failure.\n\n## Verification\n\n- The evaluation set parses successfully every time.\n- A deliberately impossible request produces an explicit unknown, not an invention.\n- A malformed response is repaired within the retry bound or fails cleanly.\n"
|
|
33
|
+
},
|
|
34
|
+
"tool-calling-agents": {
|
|
35
|
+
"name": "tool-calling-agents",
|
|
36
|
+
"description": "Build an agent that calls tools. Use when adding function calling, when an agent loops without progressing, or when tool results are mishandled.",
|
|
37
|
+
"content": "---\nname: tool-calling-agents\ndescription: Build an agent that calls tools. Use when adding function calling, when an agent loops without progressing, or when tool results are mishandled.\n---\n\n# Tool-Calling Agents\n\n## The loop\n\nModel proposes a call → the system validates and executes it → the result returns to the model → repeat until done. Every failure in an agent is in one of those four steps, and the validation step is the one most often skipped.\n\n## Workflow\n\n1. Describe tools for the model, not for a developer.\n - The description is the model's only documentation. Say when to use it AND when not to. A parameter schema with descriptions and examples per field prevents most malformed calls.\n - **Done when:** each tool says both when to use it and when not to.\n\n2. Validate every call before executing.\n - The arguments are model output — untrusted. Parse into a schema, check ranges, and check authorization for the CALLER, not for the agent. An agent running with broad privilege on behalf of a limited user is a privilege escalation.\n - **Done when:** every call is schema-validated and authorized as the user.\n\n3. Return errors as information, not as exceptions.\n - A failed call should return a message the model can act on: what was wrong and what to try instead. Throwing ends the loop; a good error lets it recover, which is most of the value of the agent.\n - **Done when:** every failure returns an actionable message.\n\n4. Bound the loop from three directions.\n - Maximum steps, maximum wall time, and maximum spend. Also detect repetition: the same call with the same arguments twice means the agent is stuck, and no further iteration will help.\n - **Done when:** all three bounds exist and repetition is detected.\n\n5. Keep the context from growing without limit.\n - Tool results accumulate quickly. Summarise or drop old results, but never drop the original instruction. Truncate large outputs and offer a way to read more.\n - **Done when:** a long run stays within the context window without losing the task.\n\n6. Require approval for irreversible actions.\n - Deleting, sending, paying, deploying. The human sees what will happen before it happens, and dismissing the question means no.\n - **Done when:** every irreversible tool requires explicit confirmation.\n\n## Verification\n\n- A malformed call is rejected with a message the model can act on.\n- A deliberately looping task terminates on the step or time bound.\n- An irreversible action cannot execute without confirmation.\n- A tool call cannot exceed the calling user's own permissions.\n"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"android": {
|
|
41
|
+
"app-performance-profiling": {
|
|
42
|
+
"name": "app-performance-profiling",
|
|
43
|
+
"description": "Diagnose slowness, jank, or battery drain in an Android app. Use when scrolling stutters, when startup is slow, or when users report heat or battery use.",
|
|
44
|
+
"content": "---\nname: app-performance-profiling\ndescription: Diagnose slowness, jank, or battery drain in an Android app. Use when scrolling stutters, when startup is slow, or when users report heat or battery use.\n---\n\n# Android Performance Profiling\n\n## Measure on the device that hurts\n\nA flagship hides everything. Profile on a mid-tier or low-tier device, on a release build with minification on. A debug build's numbers are not related to what users experience.\n\n## Workflow\n\n1. Name the symptom as a metric.\n - Startup time, frame duration, memory ceiling, or wakeup count. \"Feels slow\" cannot be verified as fixed.\n - **Done when:** there is a number and a baseline measurement.\n\n2. For jank, find the long frames.\n - The system tracing tool shows what ran during a dropped frame. The usual causes: work on the main thread, an unbounded layout pass, a bitmap decoded during scroll, or an over-broad recomposition.\n - **Done when:** the dropped frames have a named cause in the trace.\n\n3. For startup, separate the phases.\n - Process creation, application initialisation, first activity, first frame. Library initialisers running eagerly at startup are the most common cause; move them behind lazy initialisation.\n - **Done when:** each phase has a duration and the largest is identified.\n\n4. Add a baseline profile.\n - It ships ahead-of-time compilation hints for the startup and scroll paths, typically a double-digit percentage improvement for a small amount of work.\n - **Done when:** a baseline profile is generated from a realistic journey and included in the release.\n\n5. For memory, look at retention, not allocation.\n - The heap dump shows what is held and by what. Bitmaps and cached lists dominate. Allocation churn matters less than a growing retained set.\n - **Done when:** the retained heap returns to baseline after navigating away.\n\n6. For battery, count wakeups and network calls.\n - Every wakeup costs a radio transition. Batch network work and use scheduling constraints rather than timers.\n - **Done when:** wakeups during idle are near zero.\n\n## Verification\n\n- The chosen metric has improved against the recorded baseline, with both numbers stated.\n- Measurement was done on a release build on a mid-tier device.\n- Frame duration stays under the budget through the affected interaction.\n"
|
|
45
|
+
},
|
|
46
|
+
"background-work-scheduling": {
|
|
47
|
+
"name": "background-work-scheduling",
|
|
48
|
+
"description": "Schedule work that must survive app death on Android. Use when a task must run later, on a network connection, or periodically, and when a service or timer is being considered.",
|
|
49
|
+
"content": "---\nname: background-work-scheduling\ndescription: Schedule work that must survive app death on Android. Use when a task must run later, on a network connection, or periodically, and when a service or timer is being considered.\n---\n\n# Android Background Work\n\n## Choosing the mechanism\n\n- **Coroutine in a ViewModel** — work that only matters while the screen is open. Dies with the screen, correctly.\n- **WorkManager** — deferrable work that must complete even across app death and reboot. The default for anything durable.\n- **Foreground service** — user-visible, user-initiated, ongoing work: playback, navigation, an active upload. Requires a notification and a declared type.\n- **Alarm manager** — exact time triggers only, such as an alarm clock. Restricted, and misused for everything else.\n\nAlmost every \"run this later\" is WorkManager. Background execution limits mean a plain background service will simply be killed.\n\n## Workflow\n\n1. State the durability requirement.\n - Must it survive the screen closing? The app being killed? A reboot? Each answer moves the choice down the list above.\n - **Done when:** the mechanism is chosen with the requirement written down.\n\n2. Declare constraints instead of polling.\n - Network type, charging, storage, idle. The system batches constrained work across apps, which is why it costs far less battery than a timer.\n - **Done when:** the work declares every condition it needs rather than checking and rescheduling.\n\n3. Make the worker idempotent and re-runnable.\n - It can be stopped and retried at any point. Partial progress must not corrupt state.\n - **Done when:** running the worker twice produces one effect.\n\n4. Deduplicate with unique work.\n - Enqueue unique work with an explicit existing-work policy. Without it, a screen that enqueues on every open schedules the same job repeatedly.\n - **Done when:** repeated enqueues produce one scheduled job.\n\n5. Return the right result.\n - Success, retry for a transient failure, failure for a permanent one. Returning failure for a network blip loses the work; returning retry for a validation error retries forever.\n - **Done when:** each failure branch returns the matching result.\n\n6. Respect the observed limits.\n - Work is deferred, not immediate. Aggressive battery optimisation on some devices delays it further. Never promise the user a precise time.\n - **Done when:** the UI does not claim work will happen at a specific moment.\n\n## Verification\n\n- The work completes after force-stopping the app and rebooting.\n- Enqueuing repeatedly results in one job.\n- Battery historian or the WorkManager inspector shows no unexpected wakeups.\n"
|
|
50
|
+
},
|
|
51
|
+
"compose-state-and-recomposition": {
|
|
52
|
+
"name": "compose-state-and-recomposition",
|
|
53
|
+
"description": "Write or fix Jetpack Compose UI. Use when a screen stutters, when state resets on rotation, or when a composable recomposes more than expected.",
|
|
54
|
+
"content": "---\nname: compose-state-and-recomposition\ndescription: Write or fix Jetpack Compose UI. Use when a screen stutters, when state resets on rotation, or when a composable recomposes more than expected.\n---\n\n# Compose State and Recomposition\n\n## The model\n\nA composable is a function of state. When state changes, Compose re-runs the functions that READ it. Performance problems are almost always the wrong scope reading the state, and correctness problems are almost always state stored in the wrong place.\n\n## Workflow\n\n1. Hoist state to the lowest common owner.\n - A composable that owns state cannot be reused or previewed with different values. Take state as a parameter and emit events upward.\n - **Done when:** every reusable composable is stateless and driven by parameters.\n\n2. Survive configuration change and process death.\n - `remember` survives recomposition only. `rememberSaveable` survives configuration change. A ViewModel with a saved-state handle survives process death.\n - Choose by what the user would be upset to lose.\n - **Done when:** rotating and restoring from background both preserve what the user expects.\n\n3. Read state as late as possible.\n - Reading a frequently changing value at the top of a screen recomposes the whole screen. Pass a lambda that reads it, and read inside the small composable that displays it — or use the graphics-layer overload for scroll-driven values so it never recomposes at all.\n - **Done when:** a high-frequency value recomposes only the element that displays it.\n\n4. Give list items stable keys.\n - `items(list, key = { it.id })`. Without keys, inserting at the front recomposes every item and loses their internal state.\n - **Done when:** inserting at the head of a list does not disturb the rest.\n\n5. Keep side effects in effect handlers.\n - A composable body may run many times, in any order, and be abandoned. Anything with a side effect belongs in `LaunchedEffect`, `DisposableEffect`, or `SideEffect`, keyed correctly.\n - **Done when:** no network call, no logging and no mutation happens in a composable body.\n\n6. Watch out for unstable parameters.\n - A parameter Compose cannot prove stable forces recomposition every time. Prefer immutable types and persistent collections; mark types stable only when the guarantee is real.\n - **Done when:** the compiler's stability report shows the hot composables as skippable.\n\n## Verification\n\n- Layout inspector recomposition counts stay flat during interaction that should not change a given element.\n- Rotation preserves state.\n- Scrolling a long list stays smooth on a mid-tier device.\n"
|
|
55
|
+
},
|
|
56
|
+
"gradle-build-configuration": {
|
|
57
|
+
"name": "gradle-build-configuration",
|
|
58
|
+
"description": "Configure or repair an Android Gradle build. Use when a build fails after a dependency change, when build times are long, or when version numbers are scattered across files.",
|
|
59
|
+
"content": "---\nname: gradle-build-configuration\ndescription: Configure or repair an Android Gradle build. Use when a build fails after a dependency change, when build times are long, or when version numbers are scattered across files.\n---\n\n# Android Gradle Configuration\n\n## Workflow\n\n1. Centralise versions in the version catalog.\n - `gradle/libs.versions.toml` holds every version, library and plugin. Versions hardcoded across module build files drift, and a mismatch between two artifacts of the same library produces link errors that name neither.\n - **Done when:** no module build file contains a literal version string.\n\n2. Keep configuration out of the execution path.\n - Reading a file or running a command at configuration time runs on every build, including `--dry-run`. Wrap it in a provider so it runs only when the task does.\n - **Done when:** the configuration phase takes under a second on a warm build.\n\n3. Enable the correctness features deliberately.\n - Configuration cache and build cache both give large wins and both expose incorrectly declared tasks. Turn them on and fix what breaks — what breaks was already wrong.\n - **Done when:** a second build with no changes reports mostly up-to-date or cached tasks.\n\n4. Separate build types honestly.\n - Debug: no minification, debuggable, a distinct application id suffix so it installs alongside release.\n - Release: minification and resource shrinking on, and a signing config that reads from the environment or a properties file that is not committed.\n - **Done when:** debug and release can be installed on the same device simultaneously.\n\n5. Keep the minification rules with the code they protect.\n - Consumer rules belong in the library module that needs them, not in the app's file. A library whose rules live in the app breaks for every other consumer.\n - **Done when:** each module ships the rules its own code requires.\n\n## Common failures\n\n- **Duplicate class:** two dependencies bundling the same library. Resolve with a dependency constraint, never by excluding blindly.\n- **Manifest merger conflict:** read the merged manifest report; add `tools:replace` only after understanding which value should win.\n- **Method count:** enable multidex or, better, shrink what is actually unused.\n\n## Verification\n\n- A clean build succeeds and an incremental build is substantially faster.\n- Release and debug variants both install and run.\n- No version literal exists outside the catalog.\n"
|
|
60
|
+
},
|
|
61
|
+
"lifecycle-and-leaks": {
|
|
62
|
+
"name": "lifecycle-and-leaks",
|
|
63
|
+
"description": "Fix an Android memory leak or lifecycle crash. Use when the app crashes after rotation, when a callback fires on a destroyed component, or when memory grows across navigations.",
|
|
64
|
+
"content": "---\nname: lifecycle-and-leaks\ndescription: Fix an Android memory leak or lifecycle crash. Use when the app crashes after rotation, when a callback fires on a destroyed component, or when memory grows across navigations.\n---\n\n# Android Lifecycle and Leaks\n\n## What leaks\n\nAnything long-lived holding a reference to anything short-lived. An activity is short-lived — it dies on every rotation. A singleton, a static field, a running coroutine, or a registered listener that outlives it keeps the whole view tree in memory.\n\n## The recurring causes\n\n1. A non-static inner class or a lambda capturing the activity, held by something longer-lived.\n2. A listener or receiver registered and never unregistered.\n3. A coroutine launched in a scope that is never cancelled.\n4. A singleton constructed with the activity context instead of the application context.\n5. A handler with a delayed message posted from a destroyed component.\n\n## Workflow\n\n1. Confirm the leak before theorising.\n - LeakCanary in debug names the retaining chain. The chain is the answer; guessing from the symptom rarely is.\n - **Done when:** you have the retaining reference chain.\n\n2. Match the scope to the lifetime.\n - `lifecycleScope` for work tied to the screen, `viewModelScope` for work tied to the ViewModel, and `repeatOnLifecycle(STARTED)` for collecting a flow only while visible. Collecting a flow in `onCreate` without it keeps collecting behind a locked screen.\n - **Done when:** every coroutine and every collection is bound to a lifecycle.\n\n3. Pair every registration with a removal.\n - Register in `onStart`, unregister in `onStop`. Or use a `DefaultLifecycleObserver` so the pairing cannot be forgotten.\n - **Done when:** every registration has a matching removal in the symmetric callback.\n\n4. Pick the right context.\n - Application context for anything stored beyond a screen. Activity context only for UI: inflating, theming, dialogs.\n - **Done when:** no long-lived object holds an activity context.\n\n5. Never hold a view reference past the view's life.\n - In a fragment, null the binding in `onDestroyView`. The fragment outlives its view, and that gap is where the classic fragment leak lives.\n - **Done when:** the binding is nulled and access after destruction is impossible.\n\n## Verification\n\n- LeakCanary reports no leak after rotating and navigating repeatedly.\n- Memory returns to baseline after navigating away and forcing a collection.\n- No crash after rotating during an in-flight network call.\n"
|
|
65
|
+
},
|
|
66
|
+
"play-store-release": {
|
|
67
|
+
"name": "play-store-release",
|
|
68
|
+
"description": "Prepare an Android release for the Play Store. Use when building a release bundle, when a submission is rejected, or when setting up signing.",
|
|
69
|
+
"content": "---\nname: play-store-release\ndescription: Prepare an Android release for the Play Store. Use when building a release bundle, when a submission is rejected, or when setting up signing.\n---\n\n# Play Store Release\n\n## Workflow\n\n1. Build an app bundle, not an APK.\n - The store requires it and it produces per-device downloads that are substantially smaller. Test the bundle with the bundle tool so you exercise what users will actually install, not a universal APK.\n - **Done when:** a device-specific APK generated from the bundle installs and runs.\n\n2. Get signing right and keep it recoverable.\n - Enrol in Play App Signing so the upload key can be rotated if lost. Losing the signing key without it means the app can never be updated — a new listing is the only path.\n - The keystore and its passwords never enter version control.\n - **Done when:** signing config reads credentials from outside the repository and the release build is verified signed.\n\n3. Match the version codes to the release.\n - Version code strictly increasing, version name meaningful to a human. A duplicate code is rejected at upload.\n - **Done when:** the code is higher than every previously uploaded build.\n\n4. Test the minified build, not the debug one.\n - Minification removes code reached only by reflection. Serialisation, dependency injection and any reflective library need keep rules, and the crash appears only in release.\n - **Done when:** the release build has been exercised through every major flow on a real device.\n\n5. Complete the declarations honestly.\n - Data safety, permissions, target API level, and the declaration for any sensitive permission. These are the most common rejection causes, and an inaccurate data safety form is a policy violation rather than a formatting error.\n - **Done when:** every declared item matches what the code actually does.\n\n6. Stage the rollout.\n - Internal testing, then a small percentage. Watch the crash rate before widening. A full rollout of a broken build reaches everyone before the first report arrives.\n - **Done when:** the release starts below full rollout with a stated widening criterion.\n\n7. Upload the mapping file.\n - Without it every release crash report is unreadable.\n - **Done when:** deobfuscation is confirmed on a test crash.\n\n## Verification\n\n- The bundle uploads and passes pre-launch checks.\n- A minified build passes a full manual pass on a physical device.\n- A test crash appears deobfuscated in the console.\n"
|
|
70
|
+
},
|
|
71
|
+
"room-and-persistence": {
|
|
72
|
+
"name": "room-and-persistence",
|
|
73
|
+
"description": "Add or fix local persistence on Android. Use when adding a database, when a schema change crashes on upgrade, or when database work runs on the main thread.",
|
|
74
|
+
"content": "---\nname: room-and-persistence\ndescription: Add or fix local persistence on Android. Use when adding a database, when a schema change crashes on upgrade, or when database work runs on the main thread.\n---\n\n# Room and Persistence\n\n## Invariants\n\n1. Every schema change needs a migration. Destructive fallback in a shipped app deletes the user's data.\n2. No database access on the main thread. Room enforces this by default — that enforcement must not be disabled.\n3. Queries return `Flow` so the UI updates when the data changes, rather than re-querying manually.\n\n## Workflow\n\n1. Commit the schema to version control.\n - Set the schema export directory. The exported JSON is what makes migrations testable and what tells a reviewer the schema changed.\n - **Done when:** the schema JSON is committed and updated with every version bump.\n\n2. Write the migration with the version bump, in the same commit.\n - A version bump without a migration crashes every existing install on first launch. Automated migrations handle simple cases; anything renaming or transforming data needs an explicit one.\n - **Done when:** every version has a migration path from the previous one.\n\n3. Test the migration against real data.\n - `MigrationTestHelper` creates the old schema, inserts rows, runs the migration and asserts on the result. An untested migration is a hypothesis.\n - **Done when:** a test migrates a populated old-version database and asserts the data survived.\n\n4. Index what you filter and sort on.\n - Room warns about a missing index on a foreign key. A full table scan on a list screen is invisible on a test database and unusable at ten thousand rows.\n - **Done when:** every column in a WHERE or ORDER BY has an index, or its absence is justified.\n\n5. Keep transactions narrow but complete.\n - Multi-step writes are annotated as a transaction so a crash cannot leave a half-state. Do not hold a transaction open across a network call.\n - **Done when:** no multi-step write can be interrupted into an inconsistent state.\n\n## Verification\n\n- Migration tests pass from every previous version.\n- Strict mode reports no disk access on the main thread.\n- A list screen with ten thousand rows scrolls smoothly.\n"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"backend": {
|
|
78
|
+
"authentication-and-sessions": {
|
|
79
|
+
"name": "authentication-and-sessions",
|
|
80
|
+
"description": "Implement login, sessions, or token handling. Use when authentication is being added, when tokens are stored in local storage, or when passwords are being hashed.",
|
|
81
|
+
"content": "---\nname: authentication-and-sessions\ndescription: Implement login, sessions, or token handling. Use when authentication is being added, when tokens are stored in local storage, or when passwords are being hashed.\n---\n\n# Authentication and Sessions\n\n## Non-negotiables\n\n1. Passwords are hashed with a memory-hard function — argon2id, or bcrypt where argon2 is unavailable. Never a general-purpose hash, salted or not: those are designed to be fast, which is precisely the wrong property.\n2. Tokens live in `HttpOnly`, `Secure`, `SameSite` cookies. A token in local storage is readable by any script on the page, which turns any cross-site scripting bug into full account takeover.\n3. Every authentication decision happens on the server. A client-side check is a display convenience, never a control.\n\n## Workflow\n\n1. Use a library or a provider.\n - Authentication has many correct-looking implementations that are subtly broken. Prefer an established library or an identity provider over hand-rolling.\n - **Done when:** the approach is named and its security assumptions are stated.\n\n2. Get session lifetime right.\n - Short-lived access token, longer-lived refresh token, and rotation on every refresh. Detect reuse of a rotated refresh token and revoke the whole family — that is the signal a token was stolen.\n - **Done when:** replaying an old refresh token invalidates the session.\n\n3. Make logout actually revoke.\n - A stateless token cannot be revoked, so either keep server-side session state or maintain a denylist until expiry. Deleting the cookie alone leaves a valid token in the attacker's hands.\n - **Done when:** a captured token stops working after logout.\n\n4. Rate-limit and lock out.\n - Per-account and per-address limits on login and on password reset. Without them, credential stuffing runs unimpeded.\n - **Done when:** repeated failures are throttled and the throttle is tested.\n\n5. Keep the error messages uniform.\n - \"Invalid email or password\" for both cases. Distinguishing them enumerates accounts. The same applies to password reset: always report that a mail was sent.\n - **Done when:** the response and its timing are the same for a known and an unknown account.\n\n6. Regenerate the session identifier on privilege change.\n - On login and on role elevation. Without this, an identifier planted before login remains valid after it.\n - **Done when:** the identifier differs before and after login.\n\n## Verification\n\n- Password hashes use a memory-hard function with sensible parameters.\n- The auth cookie has HttpOnly, Secure and SameSite set.\n- A revoked refresh token fails and invalidates its family.\n- Login responses are indistinguishable for unknown and wrong-password accounts.\n"
|
|
82
|
+
},
|
|
83
|
+
"background-jobs-and-queues": {
|
|
84
|
+
"name": "background-jobs-and-queues",
|
|
85
|
+
"description": "Move work out of a request or add a job queue. Use when a request handler does slow work inline, when a job can be lost on restart, or when retries cause duplicate effects.",
|
|
86
|
+
"content": "---\nname: background-jobs-and-queues\ndescription: Move work out of a request or add a job queue. Use when a request handler does slow work inline, when a job can be lost on restart, or when retries cause duplicate effects.\n---\n\n# Background Jobs and Queues\n\n## The two properties that matter\n\nDelivery is AT-LEAST-ONCE in every real queue, so every job must be idempotent. And a job that fails forever must end up somewhere a human will look, or it will fail silently for months.\n\n## Workflow\n\n1. Move only what is safe to defer.\n - The user must not need the result to complete their action. If they do, the work belongs in the request, and the fix is to make it faster.\n - **Done when:** the request returns something meaningful without waiting for the job.\n\n2. Enqueue an identifier, never a payload.\n - Pass the record id and let the worker read the current state. A serialised payload is stale by the time it is processed, and it bloats the queue.\n - **Done when:** job arguments are identifiers and primitives only.\n\n3. Make every handler idempotent.\n - Running twice must equal running once. Key side effects on a stable identifier, or record completion transactionally with the effect.\n - **Done when:** running the same job twice produces one effect, verified by test.\n\n4. Set retry policy per failure class.\n - Transient (network, lock timeout): retry with exponential backoff and jitter. Permanent (validation, missing record): fail immediately to the dead-letter queue. Retrying a permanent failure burns capacity forever.\n - **Done when:** the handler distinguishes the two and behaves differently.\n\n5. Give the dead-letter queue an owner.\n - Alert on its depth. A dead-letter queue nobody watches is a directory where work goes to be forgotten.\n - **Done when:** a non-zero depth raises an alert.\n\n6. Enqueue only after the transaction commits.\n - Enqueuing inside a transaction that later rolls back leaves a job for a record that does not exist. Use a transactional outbox or an after-commit hook.\n - **Done when:** a rolled-back transaction enqueues nothing.\n\n## Verification\n\n- Running a job twice produces one effect.\n- A permanently failing job reaches the dead-letter queue without retrying indefinitely.\n- Killing a worker mid-job leaves the job re-runnable.\n"
|
|
87
|
+
},
|
|
88
|
+
"caching-strategy": {
|
|
89
|
+
"name": "caching-strategy",
|
|
90
|
+
"description": "Add or fix caching. Use when a cache serves stale data, when a cache miss storm overloads a database, or when caching is being introduced to fix a slow endpoint.",
|
|
91
|
+
"content": "---\nname: caching-strategy\ndescription: Add or fix caching. Use when a cache serves stale data, when a cache miss storm overloads a database, or when caching is being introduced to fix a slow endpoint.\n---\n\n# Caching Strategy\n\n## Before adding a cache\n\nA cache is a correctness risk taken deliberately in exchange for speed. Confirm the slowness is real, measured, and not a missing index — an index fixes the cause; a cache hides it and adds a staleness bug.\n\n## Workflow\n\n1. Decide the invalidation before the caching.\n - Time-based expiry: simple, and the staleness window must be acceptable to the product, not just to you.\n - Event-based: invalidate on write. Correct, and every write path must remember. One that forgets is a bug that only appears in production.\n - **Done when:** the invalidation rule is written down and every write path honours it.\n\n2. Build the key deliberately.\n - Include everything the value depends on: identifier, version, locale, and the viewer's permissions. A cache key missing the permission dimension serves one user's data to another. This is the most damaging caching bug and it is common.\n - **Done when:** two users with different permissions cannot share a key.\n\n3. Prevent the stampede.\n - When a hot key expires, every concurrent request misses and hits the origin at once. Use a lock so one request refills while others serve the stale value, or refresh ahead of expiry.\n - **Done when:** expiring a hot key under load produces one origin call, not many.\n\n4. Cache negatives too, briefly.\n - A missing record queried repeatedly bypasses the cache entirely. Cache the absence with a short expiry.\n - **Done when:** repeated lookups of a nonexistent key do not reach the origin each time.\n\n5. Fail open.\n - If the cache is unreachable the application serves from the origin, more slowly. A cache outage must not be an application outage.\n - **Done when:** stopping the cache leaves the application working.\n\n6. Measure the hit rate.\n - A cache below roughly 80% hit rate is usually keyed wrongly or expiring too fast, and is adding complexity for little gain.\n - **Done when:** the hit rate is instrumented and reported.\n\n## Verification\n\n- Hit rate is measured and reported.\n- Stopping the cache degrades performance without breaking correctness.\n- A write invalidates every affected key, verified by test.\n- Two users with different permissions receive different data.\n"
|
|
92
|
+
},
|
|
93
|
+
"graceful-shutdown": {
|
|
94
|
+
"name": "graceful-shutdown",
|
|
95
|
+
"description": "Make a service shut down cleanly. Use when a restart drops requests, when jobs are lost on deploy, or when a container takes the full termination grace period to exit.",
|
|
96
|
+
"content": "---\nname: graceful-shutdown\ndescription: Make a service shut down cleanly. Use when a restart drops requests, when jobs are lost on deploy, or when a container takes the full termination grace period to exit.\n---\n\n# Graceful Shutdown\n\n## The sequence\n\nStop accepting new work → finish work in flight → release resources → exit. A process that exits on the signal drops every live request, and under a rolling deploy that is a burst of user-visible errors on every release.\n\n## Workflow\n\n1. Handle the termination signal, and the interrupt too.\n - The orchestrator sends the termination signal and then waits a grace period before killing. Handle both signals; a container ignoring the signal always takes the full grace period.\n - **Done when:** the process exits on signal in well under the grace period.\n\n2. Fail readiness before you stop listening.\n - Mark the instance unready first, then wait for the load balancer to notice — typically a few seconds — and only then stop accepting connections. Closing the listener immediately means the balancer is still routing to a closed socket.\n - **Done when:** there is a deliberate delay between failing readiness and closing the listener.\n\n3. Drain in-flight work with a deadline.\n - Wait for open requests to finish, but not forever. A deadline shorter than the orchestrator's grace period, then force the close.\n - **Done when:** shutdown completes within the deadline whether or not work finished.\n\n4. Stop background workers cleanly.\n - Stop pulling new jobs, let the current one finish or return it to the queue. A job abandoned mid-flight must be re-runnable — which is why job handlers are idempotent.\n - **Done when:** no job is lost or half-applied when a worker stops.\n\n5. Release resources in reverse order of acquisition.\n - Close the server, then the queue consumers, then the database pool. Closing the pool first fails the requests still draining.\n - **Done when:** shutdown logs show the reverse order and no error.\n\n6. Never exit non-zero on a clean shutdown.\n - The orchestrator reads a non-zero exit as a crash and may back off the whole deploy.\n - **Done when:** a signalled shutdown exits 0.\n\n## Verification\n\n- A rolling restart under load produces zero failed requests.\n- The process exits well inside the grace period.\n- Stopping a worker mid-job leaves the job re-runnable.\n"
|
|
97
|
+
},
|
|
98
|
+
"rate-limiting-and-quotas": {
|
|
99
|
+
"name": "rate-limiting-and-quotas",
|
|
100
|
+
"description": "Add rate limiting or usage quotas to a service. Use when an endpoint can be called without bound, when one client can degrade service for others, or when abuse is suspected.",
|
|
101
|
+
"content": "---\nname: rate-limiting-and-quotas\ndescription: Add rate limiting or usage quotas to a service. Use when an endpoint can be called without bound, when one client can degrade service for others, or when abuse is suspected.\n---\n\n# Rate Limiting and Quotas\n\n## Workflow\n\n1. Choose the dimension before the algorithm.\n - Per user for fairness, per address for anonymous abuse, per endpoint for cost control, and globally for capacity protection. Most services need several at once.\n - **Done when:** each limit names its dimension and the abuse it prevents.\n\n2. Pick the algorithm to match the traffic.\n - Token bucket: allows bursts up to the bucket size, refills steadily. The right default for user-facing APIs.\n - Sliding window: smoother, no burst allowance, more storage.\n - Fixed window: cheapest, but allows double the limit across a boundary.\n - **Done when:** the choice is stated and the burst behaviour is acceptable.\n\n3. Enforce where the state is shared.\n - Per-instance counters mean the effective limit is multiplied by the instance count. Use a shared store with an atomic increment.\n - **Done when:** scaling out does not change the effective limit.\n\n4. Answer with the right status and headers.\n - 429, with `Retry-After` and the `RateLimit` headers. A limit a client cannot see is a limit they will hit repeatedly.\n - **Done when:** a limited response tells the client exactly when to retry.\n\n5. Identify the client honestly.\n - Behind a proxy, the connecting address is the proxy. Read the forwarded header only from a trusted proxy — otherwise a client can spoof it and bypass the limit entirely.\n - **Done when:** the address is taken from a trusted hop and spoofing is tested.\n\n6. Fail closed at the edge, open in the middle.\n - If the limiter store is down, an authentication endpoint should reject; an internal read path should allow. State which each one does.\n - **Done when:** the behaviour when the store is unavailable is decided per endpoint.\n\n## Verification\n\n- Exceeding the limit returns 429 with `Retry-After`.\n- The limit holds when the service runs on several instances.\n- A spoofed forwarded header does not raise the limit.\n"
|
|
102
|
+
},
|
|
103
|
+
"rest-api-design": {
|
|
104
|
+
"name": "rest-api-design",
|
|
105
|
+
"description": "Design or review an HTTP API. Use when endpoints are being added, when responses are inconsistent, or when clients need to special-case error handling.",
|
|
106
|
+
"content": "---\nname: rest-api-design\ndescription: Design or review an HTTP API. Use when endpoints are being added, when responses are inconsistent, or when clients need to special-case error handling.\n---\n\n# REST API Design\n\n## Workflow\n\n1. Model resources, not actions.\n - `POST /orders`, not `POST /createOrder`. The verb is the method. When an operation genuinely is not a resource change (`/search`, `/reindex`), say so explicitly rather than contorting a noun.\n - **Done when:** every path is a noun and every method carries the verb.\n\n2. Use status codes the way clients expect.\n - 200 with a body, 201 with a `Location`, 204 with none. 400 malformed, 401 unauthenticated, 403 authenticated but not permitted, 404 absent, 409 conflict, 422 semantically invalid, 429 rate-limited.\n - Never return 200 with an error inside the body. Every client's error handling depends on the status.\n - **Done when:** no successful-looking status carries an error payload.\n\n3. Give errors one shape.\n - A stable machine-readable code, a human message, and a field-level detail list where relevant. RFC 9457 problem details is a reasonable default.\n - Never leak a stack trace or an internal identifier to a client.\n - **Done when:** every error response validates against one schema.\n\n4. Paginate every collection from day one.\n - Cursor-based, not offset-based: offset pagination skips and repeats rows when the underlying data changes between pages. An unpaginated list endpoint is an outage waiting for the table to grow.\n - **Done when:** every collection endpoint has a documented limit and cursor.\n\n5. Version before you need to.\n - A path prefix (`/v1/`) is the simplest thing that works. Adding a version after clients exist is far harder than starting with one.\n - **Done when:** every route carries a version.\n\n6. Make writes idempotent.\n - PUT and DELETE are idempotent by definition. POST needs an `Idempotency-Key` header if a retry must not duplicate the effect — and clients on flaky networks WILL retry.\n - **Done when:** replaying a write with the same key produces one effect.\n\n## Verification\n\n- An OpenAPI document describes every route and matches the implementation.\n- Every error path returns the shared error shape with the correct status.\n- Replaying a POST with the same idempotency key creates one record.\n"
|
|
107
|
+
},
|
|
108
|
+
"webhook-delivery-and-receipt": {
|
|
109
|
+
"name": "webhook-delivery-and-receipt",
|
|
110
|
+
"description": "Send or receive webhooks. Use when integrating a third-party callback, when webhook handlers time out, or when duplicate deliveries cause duplicate effects.",
|
|
111
|
+
"content": "---\nname: webhook-delivery-and-receipt\ndescription: Send or receive webhooks. Use when integrating a third-party callback, when webhook handlers time out, or when duplicate deliveries cause duplicate effects.\n---\n\n# Webhook Delivery and Receipt\n\n## Receiving\n\n1. **Verify the signature before parsing.**\n - Compute the expected signature over the RAW body and compare in constant time. Parsing first means acting on unverified input, and many frameworks mutate the body during parsing so the signature can no longer be checked at all.\n - **Done when:** an unsigned or wrongly signed request is rejected with 401 before any parsing.\n\n2. **Check the timestamp.**\n - Reject anything older than a few minutes. Without this, a captured request can be replayed indefinitely.\n - **Done when:** a replayed old request is rejected.\n\n3. **Acknowledge fast, process later.**\n - Persist the event and return 200 within a couple of seconds; do the work in a job. Senders time out quickly and retry, and a slow handler turns one event into many.\n - **Done when:** the handler returns before doing any real work.\n\n4. **Deduplicate on the event id.**\n - Delivery is at-least-once from every provider. Store the id with a uniqueness constraint and ignore repeats.\n - **Done when:** delivering the same event twice produces one effect.\n\n5. **Return the right status.**\n - 200 for accepted, including for a duplicate. 4xx only for something a retry cannot fix — a 4xx usually makes the sender stop permanently. 5xx to ask for a retry.\n - **Done when:** the status semantics are documented per branch.\n\n## Sending\n\n1. **Sign every request** over the raw body with a shared secret and include a timestamp.\n2. **Retry with exponential backoff and jitter**, for hours, then dead-letter and notify.\n3. **Give each event a stable id** so the receiver can deduplicate.\n4. **Time out fast** — a few seconds — so one slow receiver cannot block the queue.\n - **Done when:** a receiver that never responds does not degrade delivery to others.\n\n## Verification\n\n- An unsigned request is rejected.\n- The same event delivered twice produces one effect.\n- The handler responds in under two seconds under load.\n- A failing receiver ends up in the dead-letter queue with an alert.\n"
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"cloud": {
|
|
115
|
+
"cost-optimisation": {
|
|
116
|
+
"name": "cost-optimisation",
|
|
117
|
+
"description": "Reduce cloud spend. Use when a bill grows unexpectedly, before scaling up, or during a cost review.",
|
|
118
|
+
"content": "---\nname: cost-optimisation\ndescription: Reduce cloud spend. Use when a bill grows unexpectedly, before scaling up, or during a cost review.\n---\n\n# Cloud Cost Optimisation\n\n## Where the money usually is\n\nIdle resources, oversized instances, data transfer, and storage nobody deleted. Rarely the thing people assume. Find out before acting.\n\n## Workflow\n\n1. Attribute the spend before changing anything.\n - Tag everything by team, environment and service, then break the bill down. An unattributed bill cannot be reduced deliberately.\n - **Done when:** most of the spend is attributed to a named owner.\n\n2. Delete what is not used.\n - Unattached volumes, old snapshots, idle load balancers, unused addresses, forgotten environments. This is the largest and easiest win in most accounts and requires no architectural change.\n - **Done when:** every unattached resource is deleted or justified.\n\n3. Right-size from measurement.\n - Instances provisioned for a peak that never came. Look at actual utilisation over weeks and resize. Under 20% sustained is over-provisioned.\n - **Done when:** every instance's size is justified by observed utilisation.\n\n4. Match the storage class to the access pattern.\n - Data read once a year should not sit in the hot tier. Lifecycle rules move it automatically, and this is often a large saving on old logs and backups.\n - **Done when:** every bucket has a lifecycle policy.\n\n5. Look hard at data transfer.\n - Cross-zone and cross-region traffic is billed and frequently accidental — a service talking to a database in another zone, or a chatty pattern that should be batched. This line is often larger than compute and almost never examined.\n - **Done when:** transfer charges are attributed to specific flows.\n\n6. Commit only to a proven baseline.\n - Reserved capacity or savings plans for load you are certain persists. Committing to a spike locks in the wrong number for a year.\n - **Done when:** commitments cover only the measured steady-state.\n\n7. Alert on the anomaly, not the total.\n - A budget alert at month end is too late. Alert on a daily rate change.\n - **Done when:** a sudden daily increase raises an alert within a day.\n\n## Verification\n\n- Spend is attributed by owner.\n- Before and after monthly figures are reported.\n- Anomaly alerting is in place.\n"
|
|
119
|
+
},
|
|
120
|
+
"iam-least-privilege": {
|
|
121
|
+
"name": "iam-least-privilege",
|
|
122
|
+
"description": "Configure cloud permissions or service accounts. Use when creating a role, when a policy grants broad access, or during a security review.",
|
|
123
|
+
"content": "---\nname: iam-least-privilege\ndescription: Configure cloud permissions or service accounts. Use when creating a role, when a policy grants broad access, or during a security review.\n---\n\n# IAM Least Privilege\n\n## The default failure\n\nA wildcard policy attached because something did not work and the error message did not say which permission was missing. It works, so it stays, and it is a full compromise waiting for one leaked credential.\n\n## Workflow\n\n1. Give every workload its own identity.\n - Shared credentials make it impossible to tell what did something, and impossible to revoke one consumer without breaking the others.\n - **Done when:** each workload has a distinct identity.\n\n2. Start from deny and add what fails.\n - Run with no permissions, read the access-denied errors, and grant exactly those. This is slower than starting broad and narrowing, and it is the only approach that actually converges.\n - **Done when:** the policy contains only permissions the workload demonstrably needs.\n\n3. Scope to resources, not just to actions.\n - Read on one bucket, not read on all buckets. Most policies get the action right and leave the resource as a wildcard, which is most of the exposure.\n - **Done when:** no resource field is a bare wildcard.\n\n4. Use short-lived credentials.\n - Workload identity federation or the platform's instance identity. A long-lived key in an environment variable is a key in every backup, log and image.\n - **Done when:** no static long-lived credential exists for any workload.\n\n5. Add conditions where they cost nothing.\n - Source address range, requiring encryption in transit, or requiring multi-factor for sensitive actions. Each closes an avenue at no operational cost.\n - **Done when:** high-value permissions carry a condition.\n\n6. Review with the analyser and remove unused grants.\n - Cloud providers report permissions granted but never used. That report is the shortest path to a smaller blast radius.\n - **Done when:** permissions unused for a stated period are removed.\n\n## Verification\n\n- No policy contains a wildcard action on a wildcard resource.\n- Each workload has its own identity with its own policy.\n- No long-lived static credential exists.\n- The access analyser reports no unused permission older than the review period.\n"
|
|
124
|
+
},
|
|
125
|
+
"infrastructure-as-code": {
|
|
126
|
+
"name": "infrastructure-as-code",
|
|
127
|
+
"description": "Manage cloud infrastructure through code. Use when resources were created by hand, when environments have drifted, or when setting up a new environment.",
|
|
128
|
+
"content": "---\nname: infrastructure-as-code\ndescription: Manage cloud infrastructure through code. Use when resources were created by hand, when environments have drifted, or when setting up a new environment.\n---\n\n# Infrastructure as Code\n\n## The rule\n\nIf it was created by hand, it does not exist reproducibly. A console change is invisible, unreviewable, and gone when the account is recreated. Every persistent resource is defined in code.\n\n## Workflow\n\n1. Import what already exists before writing anything new.\n - Most environments have hand-made resources. Import them into state so the code describes reality; writing new code alongside undocumented resources creates conflicts and duplicates.\n - **Done when:** a plan against the existing environment shows no changes.\n\n2. Make the environment a parameter.\n - One module, several variable files. Copied directories per environment drift within weeks, and the drift is discovered during an incident.\n - **Done when:** adding an environment requires only a new variables file.\n\n3. Separate state by blast radius.\n - Networking, data stores and applications in separate states. One state file for everything means a mistake in the application layer can plan a change to the database.\n - **Done when:** an application change cannot plan a change to a data store.\n\n4. Review the plan as code review.\n - The plan output goes on the pull request. Reviewing the source without the plan misses what the provider will actually do — particularly the resources it intends to replace.\n - **Done when:** every change is reviewed with its plan attached.\n\n5. Guard destructive changes.\n - Deletion protection on data stores, and a lifecycle rule preventing accidental destruction. A resource replacement that recreates a database is the classic catastrophic apply.\n - **Done when:** no plan can destroy a data store without an explicit override.\n\n6. Detect drift on a schedule.\n - A periodic plan that reports differences. Someone will change something in the console; the question is whether you find out in a day or during an incident.\n - **Done when:** a scheduled drift check reports differences to an owner.\n\n## Verification\n\n- A plan against production shows no unexplained changes.\n- A new environment can be created from code alone.\n- Data stores are protected from accidental destruction.\n- Drift detection runs on a schedule.\n"
|
|
129
|
+
},
|
|
130
|
+
"multi-region-and-failover": {
|
|
131
|
+
"name": "multi-region-and-failover",
|
|
132
|
+
"description": "Design for regional failure or high availability. Use when planning for an outage, when latency varies by geography, or when a single region is a single point of failure.",
|
|
133
|
+
"content": "---\nname: multi-region-and-failover\ndescription: Design for regional failure or high availability. Use when planning for an outage, when latency varies by geography, or when a single region is a single point of failure.\n---\n\n# Multi-Region and Failover\n\n## Be honest about the cost\n\nMulti-region multiplies infrastructure cost and operational complexity, and introduces data consistency problems that do not exist in one region. Most systems should first make one region genuinely resilient — multiple availability zones, tested backups, tested restore. Reach for multi-region when the requirement demands it, not by default.\n\n## Workflow\n\n1. State the requirement in numbers.\n - How much data may be lost, how long recovery may take, and what an hour of downtime costs. These decide whether the complexity is justified.\n - **Done when:** the recovery objectives and the cost of downtime are written down.\n\n2. Choose the posture deliberately.\n - Backup and restore: cheapest, hours to recover. Warm standby: minutes, moderate cost. Active-active: seconds, highest cost and hardest consistency.\n - **Done when:** the posture is chosen against the stated numbers.\n\n3. Solve the data problem first.\n - This is the hard part, and everything else is easy by comparison. Asynchronous replication means a failover loses the replication lag. Synchronous costs latency on every write. Active-active means conflicts, and you must define the resolution rule.\n - **Done when:** the replication mode and its data-loss window are stated.\n\n4. Make failover a routine, tested operation.\n - Automate it, document it, and rehearse it on a schedule. An untested failover fails when it is needed — that is the observed pattern, not a caution.\n - **Done when:** a failover has been performed in a rehearsal within the last quarter.\n\n5. Do not forget what is regional but invisible.\n - Certificates, secrets, DNS records, container registries, queues, and scheduled jobs. Failover fails on the one dependency nobody listed.\n - **Done when:** every regional dependency has a plan.\n\n6. Plan the failback.\n - Returning to the primary region requires reconciling data written to the secondary. This is frequently harder than the failover and is usually not planned.\n - **Done when:** failback has been rehearsed and its data reconciliation is defined.\n\n## Verification\n\n- Recovery objectives are stated and the posture meets them.\n- A failover rehearsal has been performed and timed.\n- Every regional dependency is inventoried.\n- Failback has been rehearsed.\n"
|
|
134
|
+
},
|
|
135
|
+
"serverless-patterns": {
|
|
136
|
+
"name": "serverless-patterns",
|
|
137
|
+
"description": "Build or fix serverless functions. Use when cold starts are slow, when a function times out, or when a function exhausts a downstream resource.",
|
|
138
|
+
"content": "---\nname: serverless-patterns\ndescription: Build or fix serverless functions. Use when cold starts are slow, when a function times out, or when a function exhausts a downstream resource.\n---\n\n# Serverless Patterns\n\n## What is different\n\nThe execution environment is created and destroyed outside your control, may be reused, and may run thousands of copies at once. Every problem specific to serverless comes from one of those three.\n\n## Workflow\n\n1. Move initialisation outside the handler.\n - Code outside the handler runs once per environment and is reused across invocations. Clients, configuration and connection setup belong there. Inside the handler it runs on every single invocation.\n - **Done when:** no client is constructed inside the handler.\n\n2. Never rely on local state persisting, and never assume it will not.\n - A warm environment retains its filesystem and memory. Writing a temporary file and not cleaning it up will eventually fill the space; caching in memory works but must tolerate a cold start.\n - **Done when:** the function is correct whether or not the environment is reused.\n\n3. Bound the concurrency to protect what is downstream.\n - A thousand concurrent functions each opening a database connection will exhaust the database. Set a reserved concurrency limit, and use a connection proxy or a data API rather than direct connections.\n - **Done when:** peak concurrency cannot exceed what downstream can serve.\n\n4. Make every handler idempotent.\n - Event sources deliver at least once. A retry after a timeout means the work may already have happened.\n - **Done when:** processing the same event twice produces one effect.\n\n5. Set timeouts deliberately, on both ends.\n - The function timeout must exceed the sum of its outbound call timeouts, and each outbound call must have one. Otherwise the function times out with no useful log.\n - **Done when:** every outbound call has a timeout shorter than the function's.\n\n6. Handle failure explicitly.\n - A dead-letter target for every asynchronous invocation, and an alert on its depth. Without one, failures vanish.\n - **Done when:** failed invocations land somewhere with an alert.\n\n7. Address cold starts only if measured.\n - Smaller packages, a faster runtime, and provisioned concurrency where the latency genuinely matters. Provisioned concurrency costs money continuously — measure first.\n - **Done when:** cold start latency is measured and the mitigation is justified.\n\n## Verification\n\n- Clients are created once per environment, not per invocation.\n- A load test does not exhaust downstream connections.\n- The same event delivered twice produces one effect.\n- Failed asynchronous invocations reach a dead-letter target.\n"
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"data": {
|
|
142
|
+
"data-quality-monitoring": {
|
|
143
|
+
"name": "data-quality-monitoring",
|
|
144
|
+
"description": "Detect data problems before people act on them. Use when bad data has reached a dashboard, or when setting up a new data source.",
|
|
145
|
+
"content": "---\nname: data-quality-monitoring\ndescription: Detect data problems before people act on them. Use when bad data has reached a dashboard, or when setting up a new data source.\n---\n\n# Data Quality Monitoring\n\n## The failure that costs most\n\nNot the pipeline that breaks — that gets noticed. The pipeline that keeps running while the data quietly goes wrong. Those are discovered weeks later, after decisions have been made on the numbers.\n\n## The dimensions worth monitoring\n\n- **Freshness** — is the newest record as recent as it should be.\n- **Volume** — is the row count within its normal band.\n- **Schema** — have columns or types changed upstream.\n- **Distribution** — has a column's shape shifted beyond its normal variation.\n- **Uniqueness and referential integrity** — are keys still unique, do references still resolve.\n\n## Workflow\n\n1. Baseline before alerting.\n - Measure normal for a few weeks. Alerting on a guessed threshold produces noise, and a noisy alert is an ignored alert.\n - **Done when:** each check has a threshold derived from observed variation.\n\n2. Check freshness first.\n - It catches the most failures for the least effort. A stale partition means the pipeline is broken regardless of what else passes.\n - **Done when:** every dataset has a maximum-age alert.\n\n3. Detect schema change at ingestion.\n - An upstream column rename or type change is silent and corrupting. Compare the incoming schema to the expected one and fail on an unexpected difference.\n - **Done when:** a schema change stops the pipeline rather than propagating.\n\n4. Monitor distributions on the columns that matter.\n - Null rate, distinct count, and the quantiles of numeric columns. A null rate moving from 1% to 40% is a broken upstream field, and no error will be raised.\n - **Done when:** key columns have distribution checks with bands.\n\n5. Route alerts to an owner.\n - Every dataset has a name attached. An alert to a shared channel with no owner is an alert nobody acts on.\n - **Done when:** each dataset's alerts have a named owner.\n\n6. Surface the check results next to the data.\n - A dashboard reading a dataset that failed its checks should say so. Otherwise the consumer cannot know.\n - **Done when:** consumers can see the freshness and check status of what they are reading.\n\n## Verification\n\n- A deliberately stale partition raises an alert.\n- An upstream column rename stops the pipeline.\n- A spike in the null rate is detected.\n- Every alert has an owner.\n"
|
|
146
|
+
},
|
|
147
|
+
"etl-pipeline-reliability": {
|
|
148
|
+
"name": "etl-pipeline-reliability",
|
|
149
|
+
"description": "Build or fix a data pipeline. Use when a scheduled job fails silently, when a rerun duplicates data, or when downstream numbers are wrong.",
|
|
150
|
+
"content": "---\nname: etl-pipeline-reliability\ndescription: Build or fix a data pipeline. Use when a scheduled job fails silently, when a rerun duplicates data, or when downstream numbers are wrong.\n---\n\n# ETL Pipeline Reliability\n\n## The two properties\n\nIdempotence and observability. A pipeline that cannot be safely re-run cannot be fixed after a failure, and one that fails silently produces wrong numbers that people act on for weeks.\n\n## Workflow\n\n1. Make every run idempotent.\n - Re-running for the same period must produce the same result, not double it. Partition the output by period and replace the partition wholesale, rather than appending.\n - **Done when:** running the same period twice yields identical output.\n\n2. Partition by logical time, not by run time.\n - A job for yesterday should produce yesterday's partition whenever it runs. Keying on the run time makes a backfill impossible and a late run wrong.\n - **Done when:** the output partition is determined by the data's period.\n\n3. Validate at every boundary.\n - Row counts within an expected range, no unexpected nulls in required columns, keys unique, and totals reconciling with the source. Fail the run rather than writing bad data — downstream consumers cannot tell the difference.\n - **Done when:** each stage has assertions that stop the pipeline on violation.\n\n4. Quarantine bad records, do not drop them.\n - Write rejects with the reason to a separate location. Silently dropping rows makes numbers wrong with no trace.\n - **Done when:** every rejected record is retrievable with its reason.\n\n5. Alert on staleness and on volume, not only on errors.\n - The dangerous failure is the job that succeeds while producing nothing, or a tenth of the usual rows. Alert if the newest partition is older than expected, or the volume moves outside its band.\n - **Done when:** an empty successful run raises an alert.\n\n6. Make backfill a normal operation.\n - Parameterise the period and support running a range. A pipeline that cannot backfill cannot recover from an outage.\n - **Done when:** a range backfill runs with one command and produces correct partitions.\n\n## Verification\n\n- Re-running a period produces identical output.\n- A deliberately corrupted input fails the run rather than writing.\n- An empty run triggers an alert.\n- A backfill over a week produces correct partitions.\n"
|
|
151
|
+
},
|
|
152
|
+
"exploratory-analysis": {
|
|
153
|
+
"name": "exploratory-analysis",
|
|
154
|
+
"description": "Analyse an unfamiliar dataset. Use when receiving new data, before modelling, or when a metric looks wrong.",
|
|
155
|
+
"content": "---\nname: exploratory-analysis\ndescription: Analyse an unfamiliar dataset. Use when receiving new data, before modelling, or when a metric looks wrong.\n---\n\n# Exploratory Data Analysis\n\n## The order\n\nUnderstand the data before asking it questions. An analysis built on a misunderstood column produces a confident wrong answer, and nothing downstream will catch it.\n\n## Workflow\n\n1. Establish the shape and provenance.\n - Row count, column count, types, and where the data came from — including what was filtered before you received it. Data that arrives pre-filtered has a selection bias you must know about.\n - **Done when:** you can state what one row represents, precisely.\n\n2. Profile every column.\n - Missing rate, cardinality, range, and the most frequent values. For dates, the minimum and maximum — future dates and epoch-zero dates are both common and both indicate a bug upstream.\n - **Done when:** every column has been profiled and anomalies are noted.\n\n3. Ask what the missing values mean.\n - Missing at random is very different from missing because the field only applies to some rows, and different again from missing because a system failed for a period. The three demand different handling and only investigation distinguishes them.\n - **Done when:** each column with missing values has a stated explanation.\n\n4. Check the identifiers.\n - Are the keys unique? Do foreign keys resolve? Duplicate rows from a bad join are the most common silent corruption in an analysis, and they inflate every aggregate.\n - **Done when:** key uniqueness and referential integrity have been verified.\n\n5. Plot the distributions before summarising them.\n - A mean describes a unimodal distribution. On a bimodal or heavy-tailed one it describes nothing real. Look at the shape first.\n - **Done when:** every variable you will summarise has been plotted.\n\n6. Look for the seams.\n - A step change in a time series usually means an instrumentation change, not a real change in the world. Check deploy dates before writing a conclusion.\n - **Done when:** discontinuities have been explained or flagged.\n\n## Verification\n\n- The meaning of one row is stated.\n- Every column is profiled and every anomaly explained or flagged.\n- Key uniqueness is verified.\n- Conclusions name their caveats.\n"
|
|
156
|
+
},
|
|
157
|
+
"metric-definition": {
|
|
158
|
+
"name": "metric-definition",
|
|
159
|
+
"description": "Define a business or product metric. Use when two teams report different numbers for the same thing, or before building a dashboard.",
|
|
160
|
+
"content": "---\nname: metric-definition\ndescription: Define a business or product metric. Use when two teams report different numbers for the same thing, or before building a dashboard.\n---\n\n# Metric Definition\n\n## Why the same metric gives two answers\n\nBecause it was never defined. \"Active users\" has a dozen reasonable readings, and every team picks one implicitly. The disagreement is discovered in a meeting, months later, after decisions have been made on both numbers.\n\n## Workflow\n\n1. Write the definition in a sentence a non-engineer can check.\n - Include the population, the event, the window, and the exclusions. \"Users who performed at least one search in the trailing 28 days, excluding internal accounts and bots, counted by account not by device.\"\n - **Done when:** two people can independently implement it and agree.\n\n2. Decide the time semantics explicitly.\n - Which timezone, event time or ingestion time, and how late-arriving data is handled. These three choices change the number and are almost never written down.\n - **Done when:** all three are stated.\n\n3. Name the exclusions.\n - Internal users, test accounts, bots, refunded orders, deleted records. Each one is a decision, and a metric that excludes nothing is usually wrong.\n - **Done when:** every exclusion is listed with a reason.\n\n4. Define it once, in one place.\n - A semantic layer or a shared model that every dashboard queries. The same metric computed in three dashboards will give three answers within a quarter.\n - **Done when:** every consumer reads the same definition.\n\n5. State the expected range and alert outside it.\n - A metric with no expected range cannot be sanity-checked. If it doubles overnight, is that success or an instrumentation bug? Usually the latter.\n - **Done when:** an expected band exists and a breach raises a question.\n\n6. Version the definition.\n - When it changes, the historical series changes. Record when and why, and never silently restate history.\n - **Done when:** definition changes are dated and visible on the chart.\n\n## Verification\n\n- Two independent implementations produce the same number.\n- The definition names population, event, window, timezone, and exclusions.\n- Every dashboard reads one definition.\n"
|
|
161
|
+
},
|
|
162
|
+
"notebook-to-production": {
|
|
163
|
+
"name": "notebook-to-production",
|
|
164
|
+
"description": "Turn analysis code into something that runs reliably. Use when a notebook needs to become a scheduled job, or when analysis results cannot be reproduced.",
|
|
165
|
+
"content": "---\nname: notebook-to-production\ndescription: Turn analysis code into something that runs reliably. Use when a notebook needs to become a scheduled job, or when analysis results cannot be reproduced.\n---\n\n# Notebook to Production\n\n## What breaks in the move\n\nHidden state. A notebook's output depends on which cells were run and in what order, and that order is not recorded. Code that works in a notebook frequently does not work when run top to bottom — which is what any scheduler will do.\n\n## Workflow\n\n1. Prove it runs top to bottom first.\n - Restart the kernel and run all. If it fails, the notebook depended on state that no longer exists, and the analysis result is not reproducible either.\n - **Done when:** a fresh kernel produces the same result end to end.\n\n2. Extract logic into functions in a module.\n - The notebook becomes a thin caller. Functions can be tested, imported and reused; cells cannot.\n - **Done when:** the notebook contains calls and plots, not logic.\n\n3. Replace every hardcoded path and credential.\n - Paths, dates and credentials become parameters or configuration. A hardcoded date silently produces stale results forever.\n - **Done when:** nothing environment-specific is embedded in the code.\n\n4. Pin the environment.\n - A lock file. Analysis results depend on library versions, and a silent upgrade changes numbers without changing code.\n - **Done when:** a fresh environment built from the lock file reproduces the result.\n\n5. Add validation and make failure loud.\n - Assertions on row counts, ranges and key uniqueness. A scheduled job that fails silently produces wrong numbers that people act on.\n - **Done when:** a bad input fails the run visibly.\n\n6. Test the extracted functions.\n - Start with the transformations that carry business logic. These are where a wrong result is both most likely and most costly.\n - **Done when:** the core transformations have tests with known inputs and outputs.\n\n## Verification\n\n- A fresh environment reproduces the result exactly.\n- The module runs from a scheduler with no notebook.\n- Corrupt input fails loudly.\n- The core transformations have tests.\n"
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"database": {
|
|
169
|
+
"backup-and-recovery": {
|
|
170
|
+
"name": "backup-and-recovery",
|
|
171
|
+
"description": "Set up or verify database backups. Use when a system has no tested restore, before a risky migration, or when planning for data loss.",
|
|
172
|
+
"content": "---\nname: backup-and-recovery\ndescription: Set up or verify database backups. Use when a system has no tested restore, before a risky migration, or when planning for data loss.\n---\n\n# Backup and Recovery\n\n## The only thing that counts\n\nA backup that has never been restored is not a backup. It is an untested hypothesis with a storage bill. The restore is the deliverable.\n\n## Workflow\n\n1. State the two numbers first.\n - Recovery point objective: how much data may be lost. Recovery time objective: how long recovery may take. Everything else follows from these, and without them the design is arbitrary.\n - **Done when:** both numbers are written down and agreed.\n\n2. Choose the mechanism to meet them.\n - Nightly dump: hours of potential loss, simple. Continuous archiving with point-in-time recovery: seconds of loss, more moving parts. A replica is high availability, NOT a backup — it replicates the bad DELETE faithfully and instantly.\n - **Done when:** the mechanism demonstrably meets both numbers.\n\n3. Store copies where a single failure cannot reach them.\n - A different region and a different credential. Ransomware and a compromised account both reach anything the production credential can write, which is why immutable or write-once storage matters.\n - **Done when:** a compromised production credential cannot delete the backups.\n\n4. Restore on a schedule, into a real environment.\n - Automate it, time it, and check row counts and a few known records. Do this monthly at minimum.\n - **Done when:** a restore has been performed and timed within the last month.\n\n5. Back up more than the rows.\n - Schema, roles and grants, extensions, sequence positions, and any encryption keys. A restored database nobody can log into is not recovered.\n - **Done when:** a restore into an empty instance produces a working system with no manual steps.\n\n6. Write the runbook.\n - Exact commands, in order, for someone who did not build the system, under pressure, possibly at night.\n - **Done when:** someone else has followed the runbook successfully without help.\n\n## Verification\n\n- A restore into a clean environment has been performed and timed.\n- The restore time meets the recovery time objective.\n- The data loss window meets the recovery point objective.\n- Someone other than the author has run the runbook.\n"
|
|
173
|
+
},
|
|
174
|
+
"connection-pooling": {
|
|
175
|
+
"name": "connection-pooling",
|
|
176
|
+
"description": "Configure database connections for a service. Use when the database refuses connections, when latency spikes under load, or when a serverless function exhausts connections.",
|
|
177
|
+
"content": "---\nname: connection-pooling\ndescription: Configure database connections for a service. Use when the database refuses connections, when latency spikes under load, or when a serverless function exhausts connections.\n---\n\n# Connection Pooling\n\n## The counterintuitive part\n\nA bigger pool is usually slower. A database has a finite number of cores and disks; connections beyond that queue INSIDE the database, where you cannot see them, instead of in your pool where you can. A pool of ten often outperforms a pool of a hundred on the same hardware.\n\n## Workflow\n\n1. Size from the database, not from the traffic.\n - A reasonable starting point is a small multiple of the database's core count, divided across all application instances. Then measure.\n - **Done when:** the total connections across every instance is below the database's limit with headroom.\n\n2. Count every consumer.\n - Application instances, background workers, migration jobs, admin tools, and monitoring. The limit is shared, and the job that fails to connect is usually the one that runs during a traffic peak.\n - **Done when:** the sum of all configured pools plus reserved superuser slots fits the limit.\n\n3. Set every timeout.\n - Connection acquisition, socket, and statement timeouts. A missing statement timeout lets one runaway query hold a connection indefinitely, and the pool drains one connection at a time until the service is down.\n - **Done when:** no query can run unbounded.\n\n4. Add an external pooler for serverless or high instance counts.\n - Each function invocation opening its own connection exhausts the database immediately. A transaction-mode pooler in front is the standard answer — with the caveat that it breaks session-level features like prepared statements and advisory locks.\n - **Done when:** the pooling mode is chosen and its limitations are accounted for in the code.\n\n5. Return connections deterministically.\n - Acquire and release in a `finally` or an equivalent scope guard. A leaked connection on an error path drains the pool slowly and the symptom appears hours later.\n - **Done when:** every acquisition has a guaranteed release.\n\n6. Instrument the pool.\n - Active, idle, and waiting counts. A rising wait count is the earliest signal of saturation, well before latency shows it.\n - **Done when:** pool saturation is visible on a dashboard.\n\n## Verification\n\n- A load test shows stable latency with no connection errors.\n- Total configured connections are within the database limit.\n- A deliberately slow query is terminated by the statement timeout.\n"
|
|
178
|
+
},
|
|
179
|
+
"data-integrity-and-constraints": {
|
|
180
|
+
"name": "data-integrity-and-constraints",
|
|
181
|
+
"description": "Enforce correctness rules in a database. Use when invalid data has been found in production, when rules live only in application code, or when several services write the same table.",
|
|
182
|
+
"content": "---\nname: data-integrity-and-constraints\ndescription: Enforce correctness rules in a database. Use when invalid data has been found in production, when rules live only in application code, or when several services write the same table.\n---\n\n# Data Integrity and Constraints\n\n## Where the rules belong\n\nIn the database. Application-level validation is a user-experience feature; it is bypassed by every migration, every background job, every admin script, and every other service that writes the table. If a rule matters, the database enforces it.\n\n## Workflow\n\n1. Find what is already invalid.\n - Write the constraint as a SELECT first and count the violations. There are almost always some, and adding the constraint blind will fail the migration.\n - **Done when:** the current violation count is known for each intended constraint.\n\n2. Clean the existing data before constraining.\n - Decide per violation class: fix, delete, or quarantine. Record the decision — this is a data change, not a schema change.\n - **Done when:** the violation count is zero.\n\n3. Add the constraint at the right level.\n - NOT NULL for required. UNIQUE for identity. FOREIGN KEY for references, with an explicit ON DELETE rule — the default is usually not what you want. CHECK for value rules. EXCLUDE for non-overlap, such as bookings.\n - **Done when:** every rule has a constraint of the appropriate kind.\n\n4. Choose referential actions deliberately.\n - CASCADE deletes children silently — powerful and dangerous. RESTRICT is the safe default. SET NULL requires the column to be nullable and the meaning of null to be defined.\n - **Done when:** every foreign key states its action and the reason.\n\n5. Add constraints without locking.\n - NOT VALID first, then VALIDATE separately. This avoids a full table scan under an exclusive lock.\n - **Done when:** the constraint is added and validated without a long lock.\n\n6. Test that the constraint actually fires.\n - Write a test that attempts the invalid write and asserts the failure. An untested constraint may be disabled or wrong.\n - **Done when:** a test proves invalid data is rejected.\n\n## Verification\n\n- Every stated business rule maps to a database constraint.\n- A test attempts each invalid write and confirms rejection.\n- The constraints were added without a long lock on a production-scale copy.\n"
|
|
183
|
+
},
|
|
184
|
+
"query-optimisation": {
|
|
185
|
+
"name": "query-optimisation",
|
|
186
|
+
"description": "Make a slow database query fast. Use when an endpoint is slow, when a query times out, or when the database shows high CPU.",
|
|
187
|
+
"content": "---\nname: query-optimisation\ndescription: Make a slow database query fast. Use when an endpoint is slow, when a query times out, or when the database shows high CPU.\n---\n\n# Query Optimisation\n\n## Workflow\n\n1. Read the actual plan, not the estimated one.\n - `EXPLAIN ANALYZE` runs the query and reports what really happened. The estimate can be wildly wrong, and the difference between estimate and actual is itself the most useful diagnostic — it means the statistics are stale.\n - **Done when:** you have a real plan with actual row counts and timings.\n\n2. Find the expensive node.\n - Look for a sequential scan on a large table, a nested loop over many rows, or a sort that spills to disk. One node usually dominates.\n - **Done when:** you can name the node consuming most of the time.\n\n3. Index what is filtered, joined and ordered.\n - A composite index's column order matters: equality columns first, then the range or sort column. An index the planner will not use is usually in the wrong order, or the column is wrapped in a function.\n - **Done when:** the plan shows an index scan and the timing has dropped.\n\n4. Fix the N+1 before optimising the one.\n - One query in a loop over a hundred rows is a hundred round trips. This is far more common than a genuinely slow query, and no index fixes it. Batch or join instead.\n - **Done when:** the request issues a bounded number of queries regardless of result size.\n\n5. Select only what you use.\n - `SELECT *` prevents index-only scans and drags large columns across the wire. Name the columns.\n - **Done when:** no query in the hot path selects columns the caller ignores.\n\n6. Confirm the statistics are current.\n - A plan that is wrong by orders of magnitude usually means stale statistics after a bulk load. Analyse the table and re-check before adding an index that was never needed.\n - **Done when:** estimated and actual row counts are within an order of magnitude.\n\n## Verification\n\n- Report the before and after timing from `EXPLAIN ANALYZE`.\n- The plan shows the intended access path.\n- The request issues a bounded number of queries.\n"
|
|
188
|
+
},
|
|
189
|
+
"safe-migrations": {
|
|
190
|
+
"name": "safe-migrations",
|
|
191
|
+
"description": "Change a production database schema. Use before any ALTER on a live table, when a migration might lock, or when a deploy includes a schema change.",
|
|
192
|
+
"content": "---\nname: safe-migrations\ndescription: Change a production database schema. Use before any ALTER on a live table, when a migration might lock, or when a deploy includes a schema change.\n---\n\n# Safe Migrations\n\n## The two rules\n\n1. Every migration must be safe while the OLD code is still running, because during a deploy it is.\n2. Nothing may hold a long lock on a table that is serving traffic.\n\n## The operations that lock, and their safe form\n\n- **Add a column with a default** — safe on modern PostgreSQL; on older versions it rewrites the table. Add nullable, backfill in batches, then set the default.\n- **Add NOT NULL** — add a CHECK constraint as NOT VALID, validate it separately, then convert. The direct form scans the whole table under an exclusive lock.\n- **Add an index** — always CONCURRENTLY. The plain form blocks writes for the duration.\n- **Rename a column** — never in one step. Add the new one, write to both, migrate readers, then drop.\n- **Change a type** — usually a rewrite. Add a new column, backfill, switch, drop.\n- **Add a foreign key** — add NOT VALID, then validate separately.\n\n## Workflow\n\n1. Classify the migration as additive or destructive.\n - **Done when:** every statement is labelled, and no destructive statement ships alongside the code that stops using it.\n\n2. Set a lock timeout before the statement.\n - `SET lock_timeout = '3s'`. Without it, a migration waiting on a lock queues every subsequent query behind it and takes the site down — the lock queue, not the migration, is what causes the outage.\n - **Done when:** every migration sets a lock timeout and fails fast rather than queueing.\n\n3. Backfill in batches, outside the migration.\n - A single UPDATE over millions of rows holds a long transaction and bloats the table. Batch with a delay, as a job.\n - **Done when:** no migration updates an unbounded number of rows in one statement.\n\n4. Test on a copy with production-scale data.\n - A migration that takes 3ms on a hundred rows can take twenty minutes on fifty million. Only a realistic copy tells you which.\n - **Done when:** the migration has been timed against production-scale data.\n\n5. Write the reversal.\n - Additive migrations reverse cleanly. Destructive ones cannot — which is why they ship last, separately, after the new path is proven.\n - **Done when:** every migration has a tested down path, or is explicitly marked irreversible and shipped alone.\n\n## Verification\n\n- Timed against a production-scale copy.\n- Applied against a running old-code instance with no errors.\n- Lock timeout set; the migration fails rather than queueing.\n"
|
|
193
|
+
},
|
|
194
|
+
"schema-design": {
|
|
195
|
+
"name": "schema-design",
|
|
196
|
+
"description": "Design a database schema or add tables. Use when modelling new data, when a table has grown many nullable columns, or when queries need many joins to answer a simple question.",
|
|
197
|
+
"content": "---\nname: schema-design\ndescription: Design a database schema or add tables. Use when modelling new data, when a table has grown many nullable columns, or when queries need many joins to answer a simple question.\n---\n\n# Schema Design\n\n## Workflow\n\n1. Normalise first, denormalise on evidence.\n - Third normal form is the default. Denormalise only after a measured query problem, and write down which query justified it — otherwise the duplication outlives its reason and starts drifting.\n - **Done when:** every denormalised column names the query it exists for.\n\n2. Choose keys deliberately.\n - A surrogate key (integer or UUIDv7) for identity, a unique constraint on the natural key for correctness. UUIDv4 as a clustered primary key destroys insert locality; UUIDv7 is time-ordered and does not.\n - **Done when:** every table has a primary key and a unique constraint on any natural identifier.\n\n3. Let the database enforce the invariants.\n - NOT NULL, foreign keys, CHECK constraints, and unique indexes. An invariant enforced only in application code will be violated by a migration, a background job, or the second service that writes the table.\n - **Done when:** every stated invariant has a constraint, not just a validation.\n\n4. Pick types precisely.\n - Timestamps with time zone, never a naive local time. Decimal for money, never floating point. An enumerated type or a CHECK for a fixed set, not free text.\n - **Done when:** no money column is floating point and no timestamp is zone-naive.\n\n5. Treat many nullable columns as a modelling smell.\n - A table where different rows use different subsets of columns is usually several entities. Split it, or use a properly typed variant column.\n - **Done when:** each table has one clear meaning per row.\n\n6. Plan for soft deletion or do not do it.\n - A `deleted_at` column means every query must filter on it, and one that forgets leaks deleted data. If you need it, add a view or partial indexes so the filter cannot be forgotten.\n - **Done when:** deleted rows cannot appear through a normal query path.\n\n## Verification\n\n- Every table has a primary key and appropriate foreign keys.\n- Inserting data that violates a business rule is rejected by the database itself.\n- A representative query runs without an unexpected join.\n"
|
|
198
|
+
},
|
|
199
|
+
"transactions-and-isolation": {
|
|
200
|
+
"name": "transactions-and-isolation",
|
|
201
|
+
"description": "Reason about database transactions or fix a concurrency bug. Use when two operations interfere, when a balance or counter goes wrong under load, or when deadlocks appear.",
|
|
202
|
+
"content": "---\nname: transactions-and-isolation\ndescription: Reason about database transactions or fix a concurrency bug. Use when two operations interfere, when a balance or counter goes wrong under load, or when deadlocks appear.\n---\n\n# Transactions and Isolation\n\n## What the default does not protect you from\n\nMost databases default to read committed. That prevents dirty reads and nothing else. Under it, a read-then-write sequence is a race: two transactions both read the old value, both write, and one update vanishes. Almost every \"the counter is wrong\" bug is this.\n\n## Workflow\n\n1. Identify read-modify-write sequences.\n - Any code that SELECTs a value, computes from it, and UPDATEs. These are the races.\n - **Done when:** every such sequence is listed.\n\n2. Fix each one with the cheapest sufficient tool.\n - **Atomic update:** `UPDATE ... SET n = n + 1` — no read, no race. Always prefer this.\n - **Pessimistic lock:** `SELECT ... FOR UPDATE` — locks the row for the transaction. Correct, and it serialises.\n - **Optimistic lock:** a version column, and `UPDATE ... WHERE version = ?` with a retry on zero rows. Best under low contention.\n - **Serializable isolation:** the database detects the anomaly and aborts one transaction. Correct and simple, but you MUST handle the serialisation failure by retrying.\n - **Done when:** each sequence uses one of these and the choice is stated.\n\n3. Keep transactions short and free of external calls.\n - A network request inside a transaction holds locks for the duration of someone else's outage. Do the external work outside and commit the result.\n - **Done when:** no transaction contains a network call.\n\n4. Order locks consistently to avoid deadlocks.\n - A deadlock is two transactions taking the same locks in different orders. Always acquire in a fixed order — by primary key, for example.\n - **Done when:** every multi-row write takes locks in a defined order.\n\n5. Retry serialisation and deadlock failures.\n - These are expected, not exceptional. Retry with backoff, with a bounded count.\n - **Done when:** the retry path exists and is tested.\n\n## Verification\n\n- A concurrency test with many parallel workers produces the correct final value.\n- No transaction performs I/O outside the database.\n- Deadlock and serialisation failures are retried rather than surfaced.\n"
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
"design": {
|
|
206
|
+
"colour-system-construction": {
|
|
207
|
+
"name": "colour-system-construction",
|
|
208
|
+
"description": "Build a colour palette for an interface. Use when colours were chosen individually, when a dark mode is being added, or when contrast is failing.",
|
|
209
|
+
"content": "---\nname: colour-system-construction\ndescription: Build a colour palette for an interface. Use when colours were chosen individually, when a dark mode is being added, or when contrast is failing.\n---\n\n# Colour System Construction\n\n## Build roles, not colours\n\nA palette is a set of ROLES — background, surface, border, muted text, body text, accent, and the status colours. Components ask for a role. This is the only way a theme can be swapped, a dark mode can be added, or a brand colour can be changed without editing every file.\n\n## Workflow\n\n1. Start from the neutrals.\n - Most of an interface is grey. Build a ramp of seven to nine steps from near-background to near-foreground, then assign roles to steps. Getting the neutrals right is most of the work, and it is the step most often skipped.\n - **Done when:** a neutral ramp exists and every structural element uses a step from it.\n\n2. Add one accent, then stop.\n - A single accent used consistently reads as intentional. Three accents read as decoration. Add a second only when a genuine second meaning exists.\n - **Done when:** the number of accents equals the number of distinct meanings.\n\n3. Check contrast as you assign, not afterwards.\n - Body text against its background: at least 4.5:1. Large text and UI borders: at least 3:1. A palette that fails here needs rebuilding, not adjusting.\n - **Done when:** every text-on-background pair in the design meets its ratio.\n\n4. Never encode meaning in hue alone.\n - Error, success and warning must also differ by icon, text, or position. Roughly one in twelve men cannot distinguish red from green.\n - **Done when:** every status is identifiable in greyscale.\n\n5. Define the dark theme by redefining ROLES.\n - Not by inverting the values. Dark mode wants desaturated accents and lighter surfaces raised above the background, which an inversion gets backwards.\n - **Done when:** both themes are defined as role assignments and neither is derived by inversion.\n\n## Verification\n\n- Every colour used is a named role, not a literal value.\n- Contrast checks pass for every text pair in both themes.\n- The interface remains usable rendered in greyscale.\n"
|
|
210
|
+
},
|
|
211
|
+
"design-direction-brief": {
|
|
212
|
+
"name": "design-direction-brief",
|
|
213
|
+
"description": "Choose a visual direction before building any UI, landing page, or graphic. Use at the start of any design work, and whenever output risks looking like a generic template.",
|
|
214
|
+
"content": "---\nname: design-direction-brief\ndescription: Choose a visual direction before building any UI, landing page, or graphic. Use at the start of any design work, and whenever output risks looking like a generic template.\n---\n\n# Design Direction Brief\n\n## Why this comes first\n\nWithout a stated direction, generated design converges on the same defaults every time: a centred hero, a purple-to-blue gradient, three feature cards with outline icons, a rounded sans-serif. It is recognisable on sight and it reads as unconsidered. The direction is what prevents that, and it must be chosen BEFORE any markup is written.\n\n## Workflow\n\n1. Write the brief in four lines.\n - **Audience:** who reads this, in what state of mind.\n - **Feeling:** three adjectives, and three the design must NOT be. The negative list does more work than the positive one.\n - **Reference:** a named movement or era, not \"modern\" or \"clean\" — those are not directions.\n - **Constraint:** one rule that forces a decision. \"No gradients.\" \"Exactly two typefaces.\" \"The grid is visible.\"\n - **Done when:** all four lines are written and the reference names something specific.\n\n2. Pick the reference from an actual vocabulary.\n - Swiss/International (grid, Helvetica-lineage, flush-left, generous whitespace), Brutalist (raw structure, system fonts, hard edges, visible borders), Editorial (serif display, strong hierarchy, wide measure columns), Bauhaus (primary colours, geometric forms), Memphis (asymmetry, pattern, clashing colour), Japanese minimalism (asymmetric balance, negative space, restrained palette), Art Deco (symmetry, metallic accents, high-contrast geometry), Terminal/technical (monospace, fixed grid, minimal chrome).\n - **Done when:** one is named and the others are consciously rejected.\n\n3. Derive the system from the direction, not from habit.\n - Type scale, spacing scale, palette and border treatment all follow from the reference. Swiss does not get rounded corners. Brutalist does not get soft shadows.\n - **Done when:** each system decision can be traced to the reference.\n\n4. Check the negative list before finishing.\n - Read the three \"must not be\" adjectives and look at the result. If any of them fits, the direction was not followed.\n - **Done when:** none of the negative adjectives applies.\n\n## The default trap\n\nIf the output has a centred hero with a gradient headline, three equal cards below it, and rounded everything — the brief was skipped. Go back to step one.\n\n## Verification\n\n- The brief exists in writing.\n- Every system decision traces to the named reference.\n- None of the three negative adjectives describes the result.\n"
|
|
215
|
+
},
|
|
216
|
+
"motion-and-transitions": {
|
|
217
|
+
"name": "motion-and-transitions",
|
|
218
|
+
"description": "Add animation or transitions to an interface. Use when a UI changes state abruptly, when animations feel slow, or when motion is being added for its own sake.",
|
|
219
|
+
"content": "---\nname: motion-and-transitions\ndescription: Add animation or transitions to an interface. Use when a UI changes state abruptly, when animations feel slow, or when motion is being added for its own sake.\n---\n\n# Motion and Transitions\n\n## What motion is for\n\nExplaining a change. Where something came from, where it went, what is loading. Motion that explains nothing is decoration, and decoration on a screen someone uses fifty times a day becomes an irritation.\n\n## Workflow\n\n1. Justify each animation in one sentence.\n - \"This shows the panel came from the button.\" If the sentence cannot be written, remove the animation.\n - **Done when:** every transition has a stated purpose.\n\n2. Set duration by distance and size.\n - Small local changes 100–150ms. Panels and modals 200–300ms. Full-page transitions up to 400ms. Anything over 400ms feels broken, however smooth.\n - **Done when:** no interaction feedback exceeds 400ms.\n\n3. Choose easing by direction.\n - Entering: ease-out — fast start, gentle settle. Leaving: ease-in — the element is going, so get on with it. Moving between two on-screen positions: ease-in-out. Never `linear` for anything physical.\n - **Done when:** entrances and exits use different curves.\n\n4. Animate only compositor-friendly properties.\n - `transform` and `opacity` do not trigger layout. Animating `width`, `height`, `top` or `margin` forces layout on every frame and drops to a visible stutter on a mid-tier device.\n - **Done when:** the performance panel shows no layout recalculation during the animation.\n\n5. Honour the reduced-motion preference.\n - `@media (prefers-reduced-motion: reduce)` must remove or shorten motion. For some people, motion causes nausea — this is an accessibility requirement, not a nicety.\n - **Done when:** with the preference set, transitions are instant or minimal and nothing breaks.\n\n## Verification\n\n- Every animation has a stated purpose.\n- The performance panel shows no layout thrash during motion.\n- With reduced motion enabled the interface is fully functional.\n"
|
|
220
|
+
},
|
|
221
|
+
"responsive-strategy": {
|
|
222
|
+
"name": "responsive-strategy",
|
|
223
|
+
"description": "Make an interface work across screen sizes. Use when a layout breaks on mobile, when breakpoints are copied from a framework, or when a design was built for one width.",
|
|
224
|
+
"content": "---\nname: responsive-strategy\ndescription: Make an interface work across screen sizes. Use when a layout breaks on mobile, when breakpoints are copied from a framework, or when a design was built for one width.\n---\n\n# Responsive Strategy\n\n## Breakpoints come from the content\n\nThe right breakpoint is the width at which THIS layout stops working — found by resizing until it breaks. Device-named breakpoints copied from a framework describe hardware that no longer exists and produce layouts that are wrong on real screens.\n\n## Workflow\n\n1. Design the narrow case first.\n - A single column forces a decision about priority: what does the user need first. Widening then adds; narrowing from a wide design only ever removes, and the removal is arbitrary.\n - **Done when:** the interface is complete and usable at 320px.\n\n2. Prefer intrinsic sizing to breakpoints.\n - `clamp()` for type and spacing, `minmax()` with `auto-fit` for grids, `flex-wrap` for rows. Each of these removes a breakpoint entirely and adapts continuously rather than in jumps.\n - **Done when:** the count of media queries is as low as the layout allows.\n\n3. Use container queries for components.\n - A card in a sidebar and the same card in a main column should lay out differently. That depends on the CONTAINER width, not the viewport, and a media query cannot express it.\n - **Done when:** components respond to their own container.\n\n4. Respect touch and pointer differences.\n - Touch targets at least 44px. Hover-only affordances need a tap equivalent. Test with a real finger, not a narrow browser window.\n - **Done when:** every action is reachable by touch without hovering.\n\n5. Handle the vertical axis too.\n - Landscape phones and split-screen windows are short. A hero sized in `100vh` is unusable there. Use `100dvh` or a `min-height` with content-driven growth.\n - **Done when:** the interface works at 320×480 as well as 1920×1080.\n\n## Verification\n\n- No horizontal scroll from 320px to 2560px.\n- Every interactive target is at least 44px on touch.\n- Zooming text to 200% neither clips nor overlaps content.\n- The layout is usable in a short landscape viewport.\n"
|
|
225
|
+
},
|
|
226
|
+
"spacing-and-rhythm": {
|
|
227
|
+
"name": "spacing-and-rhythm",
|
|
228
|
+
"description": "Set spacing and alignment in an interface. Use when padding values look arbitrary, when a layout feels cramped or loose, or when elements do not line up.",
|
|
229
|
+
"content": "---\nname: spacing-and-rhythm\ndescription: Set spacing and alignment in an interface. Use when padding values look arbitrary, when a layout feels cramped or loose, or when elements do not line up.\n---\n\n# Spacing and Rhythm\n\n## The single rule\n\nEvery space is a step on one scale. The moment an arbitrary value appears — `padding: 13px` — the rhythm is broken, and the eye reads the whole layout as slightly wrong without being able to say why.\n\n## Workflow\n\n1. Define the scale once.\n - A geometric-ish ramp from a 4px or 8px base: 4, 8, 12, 16, 24, 32, 48, 64, 96. Name them and use only these.\n - **Done when:** no spacing value in the codebase is outside the scale.\n\n2. Space by relationship, not by taste.\n - Elements that belong together get less space than elements that do not. This proximity grouping carries more meaning than borders or boxes ever will, and it costs no ink.\n - **Done when:** the grouping is readable from spacing alone, with all borders removed.\n\n3. Give sections room proportional to their weight.\n - A page section needs several times the space of an element within it. Uniform spacing everywhere flattens the structure.\n - **Done when:** the top-level structure is visible when scrolling quickly.\n\n4. Space in one direction.\n - Set margin on one side only (`margin-block-end`, or a flex/grid `gap`). Margins on both sides collapse unpredictably and make spacing impossible to reason about.\n - **Done when:** no element sets both a top and a bottom margin.\n\n5. Align to a small number of edges.\n - Every element should line up with something. Multiple near-but-not-equal left edges is the most visible sign of an unconsidered layout.\n - **Done when:** drawing vertical lines through the layout shows few distinct alignment edges.\n\n## Verification\n\n- Grep the stylesheet for spacing values not on the scale — there should be none.\n- With all borders and backgrounds removed, the grouping is still legible.\n- Overlaying a vertical rule at each alignment edge produces a small number of lines.\n"
|
|
230
|
+
},
|
|
231
|
+
"type-scale-and-hierarchy": {
|
|
232
|
+
"name": "type-scale-and-hierarchy",
|
|
233
|
+
"description": "Set typography for an interface or document. Use when text sizes were picked ad hoc, when headings and body are hard to tell apart, or when long text is hard to read.",
|
|
234
|
+
"content": "---\nname: type-scale-and-hierarchy\ndescription: Set typography for an interface or document. Use when text sizes were picked ad hoc, when headings and body are hard to tell apart, or when long text is hard to read.\n---\n\n# Type Scale and Hierarchy\n\n## What makes typography read as designed\n\nA small number of sizes, related by a ratio, with weight and space doing the rest of the work. Interfaces look amateurish when every element gets its own size, and when hierarchy is expressed only by making things bigger.\n\n## Workflow\n\n1. Build a scale, do not pick sizes.\n - Choose a base (16px for body on the web) and a ratio: 1.2 for dense interfaces, 1.25 for general use, 1.333 or 1.5 for editorial with few levels.\n - Round to whole pixels and keep five to seven steps total. More than that and the steps stop being distinguishable.\n - **Done when:** every size in the design is a step on the scale.\n\n2. Set the measure before the size.\n - 45–75 characters per line for body text. Longer and the eye loses the return; shorter and it breaks rhythm. `max-width: 65ch` is the direct expression of this.\n - **Done when:** body text sits between 45 and 75 characters per line at every breakpoint.\n\n3. Set line height inversely to size.\n - Body 1.5–1.6. Headings 1.1–1.25. A heading at body line height looks loose and disconnected from itself.\n - **Done when:** each level has a line height appropriate to its size.\n\n4. Express hierarchy with three tools, not one.\n - Size, weight, and colour or space. Two levels that differ only in size need a large gap to read as different; two that differ in weight and space can be one step apart.\n - **Done when:** hierarchy survives a squint test — the reading order is obvious out of focus.\n\n5. Restrain the families.\n - One typeface with a real range of weights beats two chosen for contrast. If two, make them genuinely different in category (a serif display against a sans body), never two similar sans faces.\n - **Done when:** the design uses at most two families, with a stated reason for the second.\n\n## Verification\n\n- Every text size in the rendered page is a step on the declared scale.\n- Body measure is within 45–75 characters at all breakpoints.\n- The page passes a squint test: the reading order is clear when blurred.\n"
|
|
235
|
+
},
|
|
236
|
+
"visual-qa-loop": {
|
|
237
|
+
"name": "visual-qa-loop",
|
|
238
|
+
"description": "Check that a built interface actually looks right. Use after implementing any UI, and before declaring visual work finished.",
|
|
239
|
+
"content": "---\nname: visual-qa-loop\ndescription: Check that a built interface actually looks right. Use after implementing any UI, and before declaring visual work finished.\n---\n\n# Visual QA Loop\n\n## The rule\n\nLook at it. A human developer opens the browser; anything building an interface must do the same. Code that compiles and tests that pass say nothing about whether the result is usable or whether it looks like a template.\n\n## Workflow\n\n1. Render it and capture the result.\n - Take a screenshot at a realistic width. Reading the markup is not a substitute — spacing, contrast and hierarchy are only visible when rendered.\n - **Done when:** a screenshot of the actual rendered page exists.\n\n2. Run the squint test.\n - Blur the image mentally or literally. The reading order should be obvious, the primary action should stand out, and the grouping should be visible. If everything has equal weight, the hierarchy failed.\n - **Done when:** the intended reading order survives blurring.\n\n3. Check against the design brief.\n - Re-read the direction that was chosen. Does the result match the named reference and avoid the three negative adjectives?\n - **Done when:** the result matches the brief, or the mismatch is named and fixed.\n\n4. Check the states, not just the happy path.\n - Empty, loading, error, one item, and far too many items. Long names, long text, missing images. Most visual bugs live in the states nobody screenshots.\n - **Done when:** every state has been rendered and looks deliberate.\n\n5. Check both themes and both extremes of width.\n - Light and dark, 320px and 2560px. A design checked at one width in one theme is checked at a quarter of its surface.\n - **Done when:** all four combinations are rendered and correct.\n\n6. If it looks generic, rebuild rather than adjust.\n - A centred hero, a gradient headline, three equal cards and rounded everything is the default shape, not a design. Adjusting its padding does not fix it.\n - **Done when:** the result would not be mistaken for a template.\n\n## Verification\n\n- Screenshots exist for every state, both themes, and both width extremes.\n- The squint test passes.\n- The result traces to the design brief.\n"
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
"devops": {
|
|
243
|
+
"docker-multi-stage-builds": {
|
|
244
|
+
"name": "docker-multi-stage-builds",
|
|
245
|
+
"description": "Build a Dockerfile for a compiled or bundled project. Use when adding containerisation, when an image exceeds a few hundred megabytes, or when a build copies source into the runtime layer.",
|
|
246
|
+
"content": "---\nname: docker-multi-stage-builds\ndescription: Build a Dockerfile for a compiled or bundled project. Use when adding containerisation, when an image exceeds a few hundred megabytes, or when a build copies source into the runtime layer.\n---\n\n# Docker Multi-Stage Builds\n\n## Invariants\n\n1. Compiled and bundled languages get a build stage and a runtime stage. Never one stage.\n2. The final image never runs as `root`.\n3. Secrets never enter a layer. `COPY .env` is a leak even if a later stage deletes it — layers are additive and readable with `docker history`.\n\n## Workflow\n\n1. Read the existing build.\n - Identify the build command, the artefact directory, and the runtime entrypoint.\n - **Done when:** you can name the exact directory the runtime needs and nothing else.\n\n2. Split the stages.\n - Stage one installs every dependency and builds. Stage two starts from a clean base and copies only the artefact plus production dependencies.\n - Order layers by change frequency: manifest first, `install` second, source last. A source edit must not invalidate the dependency layer.\n - **Done when:** editing one source file rebuilds only the final layers.\n\n3. Drop privileges.\n - Create a user in the runtime stage and switch to it before `CMD`.\n - **Done when:** `docker run --rm IMAGE id -u` prints a non-zero uid.\n\n4. Add a `.dockerignore`.\n - At minimum `node_modules`, `.git`, `.env*`, build output, and test fixtures. Without it the whole working tree enters the build context and slows every build.\n - **Done when:** `docker build` reports a context under a few megabytes for a typical app.\n\n## Node template\n\n```dockerfile\nFROM node:24-alpine AS build\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci\nCOPY . .\nRUN npm run build\n\nFROM node:24-alpine\nWORKDIR /app\nENV NODE_ENV=production\nRUN addgroup -S app && adduser -S app -G app\nCOPY package*.json ./\nRUN npm ci --omit=dev && npm cache clean --force\nCOPY --from=build /app/dist ./dist\nUSER app\nEXPOSE 3000\nCMD [\"node\", \"dist/index.js\"]\n```\n\n## Verification\n\n- `docker build -t app .` succeeds.\n- `docker image ls app` shows a size in line with the base image plus the artefact.\n- `docker history app` shows no layer containing a secret file.\n- The container starts and answers a request.\n"
|
|
247
|
+
},
|
|
248
|
+
"github-actions-ci": {
|
|
249
|
+
"name": "github-actions-ci",
|
|
250
|
+
"description": "Write or repair a GitHub Actions workflow for build, test, or release. Use when a repository has no CI, when a workflow is slow, or when a job fails only in CI and not locally.",
|
|
251
|
+
"content": "---\nname: github-actions-ci\ndescription: Write or repair a GitHub Actions workflow for build, test, or release. Use when a repository has no CI, when a workflow is slow, or when a job fails only in CI and not locally.\n---\n\n# GitHub Actions CI\n\n## Workflow\n\n1. Establish what must be true before a merge.\n - Usually: install, lint, test, build. Ask which of those already exist as scripts rather than inventing commands.\n - **Done when:** every gate maps to a command that already runs locally.\n\n2. Pin the trigger narrowly.\n - `pull_request` plus `push` on the default branch. A workflow that runs on every push to every branch burns minutes and teaches people to ignore red.\n - **Done when:** the trigger block names specific branches or paths.\n\n3. Cache the dependency layer.\n - `actions/setup-node` and `actions/setup-python` take a `cache` input; use it rather than hand-rolling `actions/cache`.\n - **Done when:** a second run reports a cache hit and installs in a fraction of the first run's time.\n\n4. Make failure legible.\n - Upload test reports and build logs as artifacts on failure. A red check with no artefact costs someone a local reproduction.\n - **Done when:** a deliberately failing run leaves an artefact that explains the failure.\n\n5. Lock down the token.\n - Set `permissions:` explicitly at the workflow level. The default grant is far wider than a test job needs.\n - **Done when:** `permissions` names only what the job writes, typically `contents: read`.\n\n## Template\n\n```yaml\nname: ci\non:\n pull_request:\n push:\n branches: [main]\npermissions:\n contents: read\nconcurrency:\n group: ci-${{ github.ref }}\n cancel-in-progress: true\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: 24\n cache: npm\n - run: npm ci\n - run: npm test\n```\n\n## Verification\n\n- Push a branch and confirm the workflow runs and passes.\n- Confirm a second run hits the cache.\n- Confirm `concurrency` cancels a superseded run rather than queueing it.\n"
|
|
252
|
+
},
|
|
253
|
+
"incident-triage": {
|
|
254
|
+
"name": "incident-triage",
|
|
255
|
+
"description": "Respond to a production incident or a failing deployment. Use when a service is down, error rates have spiked, or a release must be assessed under time pressure.",
|
|
256
|
+
"content": "---\nname: incident-triage\ndescription: Respond to a production incident or a failing deployment. Use when a service is down, error rates have spiked, or a release must be assessed under time pressure.\n---\n\n# Incident Triage\n\n## Order of operations\n\nStop the bleeding, then find the cause. Diagnosis while users are down is the most common way an incident doubles in length.\n\n## Workflow\n\n1. Establish the blast radius before touching anything.\n - What is failing, for whom, since when, and what changed in that window. The last question resolves most incidents on its own.\n - **Done when:** you can state the start time and the last change before it.\n\n2. Mitigate with the fastest reversible action.\n - Roll back, disable the feature flag, or shift traffic. Do not fix forward under pressure — a hurried fix is a second incident.\n - **Done when:** the user-visible symptom is gone, whether or not the cause is known.\n\n3. Preserve the evidence.\n - Capture logs, metrics windows and a heap or thread dump before restarting anything. A restart usually destroys the only copy of the state that explains the failure.\n - **Done when:** the evidence is saved somewhere that survives the restart.\n\n4. Confirm recovery against a signal, not a feeling.\n - Watch the same error-rate and latency series that showed the problem, for long enough to cover a full request cycle.\n - **Done when:** the series has returned to its pre-incident band and stayed there.\n\n5. Write the timeline while it is fresh.\n - Times, actions, and what each one changed. Blameless: the target is the system that allowed it, not the person who triggered it.\n - **Done when:** someone who was not present can follow the sequence.\n\n## Verification\n\n- The symptom is gone and confirmed by the original alerting signal.\n- Evidence is captured.\n- A follow-up action exists for the cause, separate from the mitigation.\n"
|
|
257
|
+
},
|
|
258
|
+
"kubernetes-deployment-hardening": {
|
|
259
|
+
"name": "kubernetes-deployment-hardening",
|
|
260
|
+
"description": "Write or review a Kubernetes Deployment, Service, or Helm chart. Use when a workload has no resource limits, no probes, or runs privileged.",
|
|
261
|
+
"content": "---\nname: kubernetes-deployment-hardening\ndescription: Write or review a Kubernetes Deployment, Service, or Helm chart. Use when a workload has no resource limits, no probes, or runs privileged.\n---\n\n# Kubernetes Deployment Hardening\n\n## Invariants\n\n1. Every container declares `resources.requests` and `resources.limits`. Without requests the scheduler cannot place the pod sensibly; without limits one pod can starve a node.\n2. Every container declares a readiness probe. Without one, traffic reaches a pod that is still starting.\n3. `securityContext` runs non-root with a read-only root filesystem unless the workload provably needs otherwise.\n\n## Workflow\n\n1. Read the current manifest and name what is missing.\n - Check for: requests, limits, readiness, liveness, securityContext, and an explicit image tag (never `latest`).\n - **Done when:** you have a written list of the missing fields.\n\n2. Set resources from measurement, not from a guess.\n - If no measurement exists, say so and set a conservative starting point, then note that it must be revisited against real usage.\n - **Done when:** requests and limits are present and the basis for the numbers is stated.\n\n3. Separate the two probes.\n - Readiness gates traffic. Liveness restarts. Pointing both at the same endpoint means a slow dependency triggers a restart loop.\n - **Done when:** readiness and liveness use different endpoints or different thresholds.\n\n4. Constrain the security context.\n - `runAsNonRoot: true`, `allowPrivilegeEscalation: false`, `readOnlyRootFilesystem: true`, and drop all capabilities.\n - **Done when:** the pod starts with those settings, or you have named the exact capability it needs and why.\n\n## Template\n\n```yaml\nspec:\n template:\n spec:\n securityContext:\n runAsNonRoot: true\n seccompProfile: { type: RuntimeDefault }\n containers:\n - name: app\n image: registry/app:1.4.2\n resources:\n requests: { cpu: 100m, memory: 128Mi }\n limits: { cpu: 500m, memory: 512Mi }\n readinessProbe:\n httpGet: { path: /readyz, port: 8080 }\n initialDelaySeconds: 3\n livenessProbe:\n httpGet: { path: /livez, port: 8080 }\n periodSeconds: 20\n failureThreshold: 3\n securityContext:\n allowPrivilegeEscalation: false\n readOnlyRootFilesystem: true\n capabilities: { drop: [\"ALL\"] }\n```\n\n## Verification\n\n- `kubectl apply --dry-run=server -f manifest.yaml` accepts it.\n- `kubectl rollout status deploy/app` completes.\n- Killing the readiness endpoint removes the pod from the Service endpoints without restarting it.\n"
|
|
262
|
+
},
|
|
263
|
+
"observability-baseline": {
|
|
264
|
+
"name": "observability-baseline",
|
|
265
|
+
"description": "Add logging, metrics, or tracing to a service. Use when an incident could not be diagnosed from what the service already emits, or when logs are unstructured strings.",
|
|
266
|
+
"content": "---\nname: observability-baseline\ndescription: Add logging, metrics, or tracing to a service. Use when an incident could not be diagnosed from what the service already emits, or when logs are unstructured strings.\n---\n\n# Observability Baseline\n\n## The three questions\n\nInstrumentation exists to answer: is it up, is it slow, and what broke. Anything that answers none of those is noise with a retention cost.\n\n## Workflow\n\n1. Structure the logs.\n - One JSON object per line, with a level, a timestamp, a message, and a request or trace identifier. Never interpolate values into a prose sentence — that makes them unsearchable.\n - **Done when:** every log line parses as JSON and carries a correlation identifier.\n\n2. Emit the four signals that matter for a request-serving service.\n - Request rate, error rate, duration distribution, and saturation (queue depth, pool usage, memory).\n - Use histograms for duration, never averages. An average hides the tail, and the tail is the user experience.\n - **Done when:** a dashboard can show p50, p95 and p99 latency.\n\n3. Propagate a trace identifier across every boundary.\n - Generate at the edge, accept from an incoming header, attach to every outbound call and every log line.\n - **Done when:** one identifier follows a request through every service it touches.\n\n4. Redact before emitting.\n - Tokens, passwords, keys and personal data must be masked at the logging boundary, not filtered downstream.\n - **Done when:** a deliberate log of a credential-shaped value comes out masked.\n\n5. Alert on symptoms, not causes.\n - Alert on error rate and latency, which users feel. Do not alert on CPU, which they do not.\n - **Done when:** every alert would have woken someone for a real user-visible problem.\n\n## Verification\n\n- Trigger an error and follow it from the log line to the trace to the metric.\n- Confirm no alert fires during a normal deploy.\n"
|
|
267
|
+
},
|
|
268
|
+
"secret-management": {
|
|
269
|
+
"name": "secret-management",
|
|
270
|
+
"description": "Handle credentials, API keys, or certificates in an application or pipeline. Use when a secret appears in a config file, in an environment file committed to the repository, or in CI logs.",
|
|
271
|
+
"content": "---\nname: secret-management\ndescription: Handle credentials, API keys, or certificates in an application or pipeline. Use when a secret appears in a config file, in an environment file committed to the repository, or in CI logs.\n---\n\n# Secret Management\n\n## Invariants\n\n1. A secret in version control is compromised, including in deleted files — history retains it. Rotate it; do not merely delete it.\n2. Secrets reach the process through the environment or a mounted file, injected at runtime by a secret store. Never through the image, the repository, or the build args.\n3. Every secret has a rotation path. One that cannot be rotated is a permanent liability.\n\n## Workflow\n\n1. Find what is already exposed.\n - Search the working tree and the history for key-shaped strings: long base64 or hex runs, `-----BEGIN`, and assignments to names containing key, token, secret, or password.\n - **Done when:** you have a list of every exposure, with the file and the commit.\n\n2. Rotate before you clean.\n - Cleaning history without rotating leaves a live credential in every existing clone and in every fork.\n - **Done when:** the old value is revoked at the provider.\n\n3. Move the value to a store.\n - Cloud secret manager, or the platform's own secret object. Reference it by name from config; never inline it.\n - **Done when:** the repository contains the secret's NAME and never its value.\n\n4. Block the next one.\n - Add a pre-commit secret scan and a `.gitignore` entry for the environment file. Commit an example file with placeholder values so the shape is documented without the values.\n - **Done when:** a test commit containing a fake key is rejected locally.\n\n5. Mask in CI.\n - Register every secret with the CI provider so it is masked in logs. A secret echoed by a debug command is a public secret on a public repository.\n - **Done when:** a deliberate echo of the secret shows as a mask in the run log.\n\n## Verification\n\n- No key-shaped string survives in the working tree.\n- The application starts with values supplied only by the environment.\n- A commit containing a fake credential is rejected by the local hook.\n"
|
|
272
|
+
},
|
|
273
|
+
"terraform-state-and-modules": {
|
|
274
|
+
"name": "terraform-state-and-modules",
|
|
275
|
+
"description": "Write or review Terraform. Use when state is stored locally, when a module is copy-pasted between environments, or before a first apply against real infrastructure.",
|
|
276
|
+
"content": "---\nname: terraform-state-and-modules\ndescription: Write or review Terraform. Use when state is stored locally, when a module is copy-pasted between environments, or before a first apply against real infrastructure.\n---\n\n# Terraform State and Modules\n\n## Invariants\n\n1. State lives in a remote backend with locking. Local state means the first concurrent apply corrupts it and the second person destroys production.\n2. `terraform plan` output is read before every apply. An apply nobody read is an outage waiting for a trigger.\n3. Secrets never appear in `.tf` files or in `terraform.tfvars` committed to the repository. State itself contains secrets in plaintext — treat the backend as a secret store.\n\n## Workflow\n\n1. Check the backend first.\n - If `backend \"local\"` or no backend block exists, that is the finding to report before anything else.\n - **Done when:** the backend is remote and versioned, with locking enabled.\n\n2. Parameterise instead of duplicating.\n - Environments differ by variable values, not by copied directories. Use one module and separate `.tfvars` or workspaces.\n - **Done when:** adding a third environment requires a new variables file and no new resource blocks.\n\n3. Pin every provider and module version.\n - `required_providers` with `~>` constraints, and module sources with an explicit `?ref=` tag.\n - **Done when:** `terraform init` in a clean directory resolves to the same versions it did before.\n\n4. Plan, then read the plan.\n - Count creates, updates and destroys. Any unexpected destroy stops the work and gets reported.\n - **Done when:** every line of the plan is explained, especially each destroy.\n\n## Verification\n\n- `terraform fmt -check` and `terraform validate` pass.\n- `terraform plan` shows zero unexplained changes against the current state.\n- The backend block names a remote store and locking is confirmed.\n"
|
|
277
|
+
},
|
|
278
|
+
"zero-downtime-deploys": {
|
|
279
|
+
"name": "zero-downtime-deploys",
|
|
280
|
+
"description": "Design or repair a deployment strategy. Use when a release causes a visible outage, when migrations and code ship together, or when rollback is untested.",
|
|
281
|
+
"content": "---\nname: zero-downtime-deploys\ndescription: Design or repair a deployment strategy. Use when a release causes a visible outage, when migrations and code ship together, or when rollback is untested.\n---\n\n# Zero-Downtime Deploys\n\n## The rule that governs everything else\n\nOld code and new code will run at the same time. Every change must be safe in that window, or the deploy is not zero-downtime regardless of the orchestration.\n\n## Workflow\n\n1. Classify the change.\n - Additive (new column, new endpoint, new field): safe to ship together.\n - Destructive (dropped column, renamed field, changed semantics): must be split across releases.\n - **Done when:** every change in the release is labelled additive or destructive.\n\n2. Split every destructive change into the expand/contract sequence.\n - Release 1: add the new shape, write to both, read from the old.\n - Release 2: read from the new. Deploy, then verify.\n - Release 3: stop writing the old, then drop it.\n - **Done when:** no single release both writes and removes the same shape.\n\n3. Make migrations independent of the deploy.\n - Migrations run before the new code and must be safe for the old code still serving traffic.\n - **Done when:** the migration can run with zero new instances started.\n\n4. Verify rollback before you need it.\n - Roll back in a staging environment and confirm the previous version serves correctly against the migrated schema.\n - **Done when:** rollback has actually been executed once, not merely documented.\n\n5. Drain connections on shutdown.\n - Handle the termination signal: stop accepting new work, finish in-flight work, then exit. A process that exits immediately drops live requests.\n - **Done when:** a rolling restart under load shows zero failed requests.\n\n## Verification\n\n- Run a rolling restart with a load generator active; error count stays at zero.\n- Roll back one version with traffic flowing; error count stays at zero.\n"
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
"git": {
|
|
285
|
+
"bisect-regression-hunt": {
|
|
286
|
+
"name": "bisect-regression-hunt",
|
|
287
|
+
"description": "Find the commit that introduced a bug. Use when something worked before and does not now, and the cause is not obvious from a recent diff.",
|
|
288
|
+
"content": "---\nname: bisect-regression-hunt\ndescription: Find the commit that introduced a bug. Use when something worked before and does not now, and the cause is not obvious from a recent diff.\n---\n\n# Bisect a Regression\n\n## Why this beats reading diffs\n\nBisect is a binary search over history. A thousand commits take ten tests. Reading diffs is linear and relies on recognising the bug on sight, which is exactly what has already failed.\n\n## Workflow\n\n1. Write the test FIRST.\n - A command that exits non-zero when the bug is present and zero when it is not. Without this you will make subjective calls at each step and the search will land on the wrong commit.\n - **Done when:** the command fails on the current commit and passes on a commit you know is good.\n\n2. Find the bounds.\n - A known-bad commit (usually HEAD) and a known-good one. If the good end is unknown, step backwards by powers of two until the test passes.\n - **Done when:** `git bisect start <bad> <good>` is accepted.\n\n3. Automate the search.\n - `git bisect run ./check.sh` runs the test at each step with no further input.\n - The script must exit 125 when the commit cannot be tested at all (a broken build), so bisect skips rather than mislabels it.\n - **Done when:** bisect reports a single first-bad commit.\n\n4. Confirm the verdict.\n - Check out the reported commit and its parent, and run the test on both by hand. Bisect is only as correct as the test.\n - **Done when:** the parent passes and the reported commit fails, verified manually.\n\n5. Reset.\n - `git bisect reset` returns to where you started. Forgetting this leaves the tree detached at an arbitrary commit.\n - **Done when:** `git status` shows the original branch.\n\n## When the history is merges\n\n`git bisect` handles merge commits, but a first-bad merge means the bug came from the merged branch. Re-run bisect within that branch's own commits.\n\n## Verification\n\n- The reported commit fails the test and its parent passes.\n- The change in that commit plausibly explains the symptom.\n- `git status` is back on the original branch.\n"
|
|
289
|
+
},
|
|
290
|
+
"branch-strategy": {
|
|
291
|
+
"name": "branch-strategy",
|
|
292
|
+
"description": "Set up or repair a branching model for a team. Use when merges are painful, when long-lived branches diverge badly, or when there is no agreement on where releases come from.",
|
|
293
|
+
"content": "---\nname: branch-strategy\ndescription: Set up or repair a branching model for a team. Use when merges are painful, when long-lived branches diverge badly, or when there is no agreement on where releases come from.\n---\n\n# Branch Strategy\n\n## The bias\n\nPrefer short-lived branches off a single main line. Almost every branching pain is a symptom of branches living too long, and no model fixes that.\n\n## Workflow\n\n1. Establish one source of truth.\n - `main` is always releasable. Everything branches from it and merges back into it.\n - **Done when:** there is exactly one branch nobody force-pushes.\n\n2. Keep feature branches short.\n - Under a few days. Anything longer merges into a codebase it no longer recognises. Split the work and use a feature flag if the feature is not ready to be seen.\n - **Done when:** the median open branch age is measured in days, not weeks.\n\n3. Choose integration direction deliberately.\n - Rebase a private branch onto main to keep history linear. Merge a shared branch, because rebasing rewrites commits other people have.\n - **Done when:** the rule is written down and matches what people actually do.\n\n4. Protect the main line mechanically.\n - Require review, require CI, forbid force-push. A convention nobody enforces is a convention nobody follows.\n - **Done when:** a direct push to main is rejected by the server.\n\n5. Tag releases; do not branch them.\n - A tag is an immutable pointer. A release branch is a second main line that will drift.\n - Branch a release only to backport a fix to a version that is still supported.\n - **Done when:** every shipped version corresponds to a tag.\n\n## Verification\n\n- Branch protection rejects a direct push and an unreviewed merge.\n- The oldest open feature branch is days old, not months.\n- Every release in the changelog has a matching tag.\n"
|
|
294
|
+
},
|
|
295
|
+
"commit-hygiene": {
|
|
296
|
+
"name": "commit-hygiene",
|
|
297
|
+
"description": "Structure commits and write commit messages. Use when a change is being committed, when a branch has a messy history before review, or when a message says only \"fix\".",
|
|
298
|
+
"content": "---\nname: commit-hygiene\ndescription: Structure commits and write commit messages. Use when a change is being committed, when a branch has a messy history before review, or when a message says only \"fix\".\n---\n\n# Commit Hygiene\n\n## What a commit is for\n\nA commit is a message to whoever runs `git blame` on this line in two years. It should say WHY, because the diff already says what.\n\n## Workflow\n\n1. One commit, one logical change.\n - A refactor and a behaviour change in one commit cannot be reviewed or reverted separately. Split them.\n - `git add -p` stages hunks rather than whole files.\n - **Done when:** each commit could be reverted alone without breaking the build.\n\n2. Write the subject as an instruction.\n - Imperative mood, under about seventy characters, no trailing period: `fix race in session close`, not `fixed a race` or `fixing races`.\n - **Done when:** the subject completes the sentence \"if applied, this commit will …\".\n\n3. Use the body for the why.\n - What was wrong, why this approach, what was considered and rejected. Reference the issue.\n - Skip the body only when the subject is genuinely complete.\n - **Done when:** a reader who disagrees with the change knows what to argue with.\n\n4. Never commit noise.\n - Debug prints, commented-out code, unrelated formatting, generated files that should be ignored.\n - `git diff --cached` before every commit.\n - **Done when:** every line in the staged diff is intentional.\n\n5. Clean the branch before review.\n - Squash the \"fix typo\" commits into what they fix. Do this only on a branch nobody else has pulled.\n - **Done when:** the branch reads as a sequence of deliberate steps.\n\n## Verification\n\n- `git log --oneline origin/main..HEAD` reads as a coherent narrative.\n- No commit message is a single word.\n- `git diff origin/main...HEAD` contains no debug output or unrelated reformatting.\n"
|
|
299
|
+
},
|
|
300
|
+
"history-recovery": {
|
|
301
|
+
"name": "history-recovery",
|
|
302
|
+
"description": "Recover lost commits or undo a destructive git operation. Use after a bad reset, a deleted branch, a force-push over someone else`s work, or an amended commit that ate a change.",
|
|
303
|
+
"content": "---\nname: history-recovery\ndescription: Recover lost commits or undo a destructive git operation. Use after a bad reset, a deleted branch, a force-push over someone else`s work, or an amended commit that ate a change.\n---\n\n# History Recovery\n\n## The fact that makes recovery possible\n\nGit almost never deletes a commit immediately. A commit that nothing references is unreachable, not gone, and stays in the object store until garbage collection runs — usually weeks. `git reflog` is the index of where HEAD has been.\n\n## Workflow\n\n1. Stop and take a snapshot.\n - Do not run further git commands that move HEAD. Copy the whole working directory first if the state is valuable.\n - **Done when:** a copy of the current directory exists outside the repository.\n\n2. Find the lost commit in the reflog.\n - `git reflog` for HEAD, `git reflog show <branch>` for a specific branch.\n - Each entry has a hash. The one just before the destructive command is what you want.\n - **Done when:** you have the hash and have confirmed it with `git show <hash>`.\n\n3. Recover without overwriting.\n - Prefer a new branch: `git branch recovered <hash>`. This is non-destructive and inspectable.\n - Only after inspection: `git reset --hard <hash>` on the original branch.\n - **Done when:** the recovered content is visible on a branch and verified with `git log`.\n\n4. If the reflog is empty, search unreachable objects.\n - `git fsck --lost-found --unreachable` lists dangling commits. `git show` each candidate.\n - **Done when:** the content is found, or you can state that it never reached the object store.\n\n## The specific disasters\n\n- **Bad `reset --hard`:** the previous HEAD is the reflog entry above the reset.\n- **Deleted branch:** `git reflog show` no longer works, but the branch tip is still in `git reflog` under HEAD, or in `fsck --unreachable`.\n- **Bad `commit --amend`:** the pre-amend commit is one reflog entry back.\n- **Force-push over a colleague:** the old remote tip is in their local reflog, and in the remote's reflog if you have server access. Ask before rewriting again.\n- **Detached HEAD with commits:** `git branch <name> HEAD` before checking anything else out.\n\n## Verification\n\n- `git log --oneline` on the recovered branch shows the missing commits.\n- The files that were lost are present with the expected content.\n- Nothing else moved: `git status` is clean.\n"
|
|
304
|
+
},
|
|
305
|
+
"large-repository-surgery": {
|
|
306
|
+
"name": "large-repository-surgery",
|
|
307
|
+
"description": "Remove a large file or a secret from git history, or split a repository. Use when a clone is slow because of a committed binary, or when a credential must be purged from history.",
|
|
308
|
+
"content": "---\nname: large-repository-surgery\ndescription: Remove a large file or a secret from git history, or split a repository. Use when a clone is slow because of a committed binary, or when a credential must be purged from history.\n---\n\n# Large Repository Surgery\n\n## Before anything else\n\nRewriting history breaks every existing clone. Everyone must re-clone or reset. Confirm the team knows, and if the goal is removing a secret, ROTATE THE SECRET FIRST — the rewrite does not reach forks, caches or anyone's existing clone.\n\n## Workflow\n\n1. Measure before you cut.\n - Find the largest objects in history and confirm they are what is actually causing the size.\n - `git count-objects -vH` gives the total; `git rev-list --objects --all` piped through `git cat-file --batch-check` and sorted by size gives the offenders.\n - **Done when:** you can name the paths responsible for most of the repository size.\n\n2. Back up.\n - `git clone --mirror` to a separate location. A rewrite has no undo.\n - **Done when:** the mirror exists and `git log` in it shows the current history.\n\n3. Rewrite with a purpose-built tool.\n - `git filter-repo` is the current tool. `filter-branch` is deprecated, orders of magnitude slower, and easy to get subtly wrong.\n - `git filter-repo --path secrets.env --invert-paths` removes a path from all history.\n - **Done when:** the path no longer appears in `git log --all -- <path>`.\n\n4. Expire and collect.\n - The old objects are still reachable through the reflog until it is expired.\n - **Done when:** `git count-objects -vH` shows the reduced size.\n\n5. Coordinate the push.\n - Force-push all branches and tags, then have everyone re-clone. Anyone who merges an old clone reintroduces the history.\n - **Done when:** every collaborator has confirmed a fresh clone.\n\n## Prevention\n\n- Add the file pattern to `.gitignore`.\n- Use Git LFS for binaries that genuinely belong in the repository.\n- Add a pre-commit size check.\n\n## Verification\n\n- The removed path is absent from `git log --all`.\n- Repository size has dropped as expected.\n- CI passes against the rewritten history.\n"
|
|
309
|
+
},
|
|
310
|
+
"merge-conflict-resolution": {
|
|
311
|
+
"name": "merge-conflict-resolution",
|
|
312
|
+
"description": "Resolve merge or rebase conflicts. Use when a merge stops with conflicts, when a rebase halts, or when conflict markers appear in a file.",
|
|
313
|
+
"content": "---\nname: merge-conflict-resolution\ndescription: Resolve merge or rebase conflicts. Use when a merge stops with conflicts, when a rebase halts, or when conflict markers appear in a file.\n---\n\n# Merge Conflict Resolution\n\n## Invariants\n\n1. Never resolve a conflict by taking one side wholesale without reading both. That silently discards someone's work.\n2. Never commit a file containing `<<<<<<<`, `=======` or `>>>>>>>`.\n3. Run the tests after resolving. A syntactically valid resolution can still be semantically wrong.\n\n## Workflow\n\n1. See the shape of the conflict before editing.\n - `git status` lists the conflicted paths. `git diff --diff-filter=U` shows only the conflicting hunks.\n - **Done when:** you have the list of conflicted files and know which are code and which are generated.\n\n2. Resolve generated files by regenerating, not by editing.\n - Lock files, build output and snapshots: check out one side, then re-run the generator.\n - `git checkout --theirs package-lock.json && npm install`\n - **Done when:** the generated file matches what the tool produces from the merged source.\n\n3. Resolve code by understanding both intents.\n - `git log --merge -p <path>` shows the commits from each side that touched the file. Read them; the right resolution is often neither side but a combination.\n - **Done when:** the resolved hunk preserves the intent of both branches, or you can state which was deliberately dropped and why.\n\n4. Verify before continuing.\n - `git add` the resolved files, then run the tests BEFORE `git rebase --continue` or `git commit`.\n - **Done when:** the test suite passes on the resolved tree.\n\n## Escape hatches\n\n- Abandon the attempt cleanly: `git merge --abort` or `git rebase --abort`. Both return to the pre-merge state and lose nothing.\n- Recover a resolution you already made once: `git rerere` records and replays them.\n\n## Verification\n\n- `grep -rn '<<<<<<<' .` finds nothing outside test fixtures.\n- The test suite passes.\n- `git diff <base>...HEAD` contains no unrelated change.\n"
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
"media": {
|
|
317
|
+
"cinematic-3d-shot": {
|
|
318
|
+
"name": "cinematic-3d-shot",
|
|
319
|
+
"description": "Build a 3D object in Blender and render it as a short cinematic clip. Use when a task asks for a 3D model, a product shot, a turntable, or a rendered animation of a few seconds.",
|
|
320
|
+
"content": "---\nname: cinematic-3d-shot\ndescription: Build a 3D object in Blender and render it as a short cinematic clip. Use when a task asks for a 3D model, a product shot, a turntable, or a rendered animation of a few seconds.\n---\n\n# Cinematic 3D Shot\n\nA render looks amateur for four reasons, always the same four: default grey\nmaterial, one flat light, the camera where Blender left it, and nobody looked at\nthe result before shipping it. Fix those four and an ordinary model reads as a\nproduct shot.\n\n## Workflow\n\n1. Verify Blender before planning anything.\n - Use the local CLI path. Budget a low-cost `renderFrame` whenever you need\n to inspect the current visual result.\n - **Done when:** the local Blender binary is available and the review loop is planned.\n\n2. Clear the scene.\n - Blender opens with a cube, one lamp and a camera. Building around them is\n how the default cube ends up in a client render.\n - **Done when:** `clearScene` has run.\n\n3. Build the silhouette, then the detail.\n - A shape is read as an outline before it is read as a surface. If the form\n does not identify itself at thumbnail size, no amount of material work will\n save it. Use `smartGeometry` — it applies bevel and subdivision, and a\n bevel is what catches a highlight along an edge. Perfectly sharp edges are\n the most common tell of untouched 3D: nothing in the real world has them.\n - **Done when:** the object is recognisable as a flat grey silhouette.\n\n4. Give every visible object a material.\n - `smartMaterial` on each one. One untouched object in a dressed scene reads\n as unfinished more loudly than a whole scene of grey.\n - Match the material to the object's story: a pen barrel is brushed metal or\n lacquered resin, not plastic; the grip is rubber, and rubber is rough where\n metal is not. Contrast between materials is what tells the eye there are\n two parts.\n - **Done when:** no object still carries the default surface.\n\n5. Light with three lights, in this order.\n - `setupStudioLighting` gives key, fill and rim. The ratio does the work:\n the key states the form, the fill keeps the shadow side from going black,\n and the RIM is what separates the subject from the background. A render\n that looks flat is almost always missing the rim.\n - **Done when:** the subject's edge is visibly brighter than what is behind it.\n\n6. Add atmosphere only after the lights exist.\n - `cinematicAtmosphere`. Fog scatters the light you already have, so adding\n it first means tuning against something that will change. Keep the density\n low — visible haze reads as a mistake unless the shot is about weather.\n - **Done when:** there is depth between foreground and background, and the\n subject is still crisp.\n\n7. Frame it deliberately.\n - `autoFrameCamera`. A longer lens (65mm and up) flatters an object; a wide\n lens distorts it and is why so many AI renders look like a fisheye photo of\n a toy. Put the camera slightly above the eye line and the subject a little\n off-centre.\n - **Done when:** the camera is not where Blender put it.\n\n8. Look at it before you animate.\n - `captureViewport`, or one low-sample `renderFrame`, then `view_image` or\n `vision_inspect_ui`. Ask the specific question: is anything still grey, is\n the subject separated from the background, does the silhouette read?\n - **A render nobody has looked at is not a finished render.** This step is\n the difference between the two outcomes, not a formality.\n - **Done when:** you have described what you actually see, not what you\n intended to build.\n\n9. One camera move, and do the arithmetic out loud.\n - Seconds x fps = frames. 8s at 24fps is frames 1-192; set the range\n explicitly or Blender renders its own 250-frame default.\n - ONE move per short shot: a slow orbit, a push-in, or a rise. Three moves in\n eight seconds reads as a screensaver. Ease the ends — constant velocity is\n what makes a move feel mechanical.\n - **Done when:** the frame range matches the requested duration exactly.\n\n10. Render with settings that fit the budget.\n - EEVEE for anything short. Cycles at default samples will spend an entire\n task budget on one frame and is almost never what a few seconds of\n turntable needs.\n - **Done when:** the file exists, and you have opened it to confirm it is\n not zero bytes or a single black frame.\n\n## Failure modes worth naming\n\n- **Everything is grey.** A material step was skipped, or applied to one object.\n- **It looks flat.** No rim light, or fill too close to key.\n- **The object looks like a toy.** Lens too wide, or the camera is too close.\n- **The animation stutters.** Frame range and fps disagree with the duration.\n- **Blender is not installed.** Say so and offer the alternative. Do not loop\n retrying the same missing binary — that spends budget to learn nothing.\n\n## Verification\n\n- The rendered file exists, is larger than a few kilobytes, and opens.\n- Open the first frame and the middle frame. Neither is black, neither is empty,\n and the subject is in frame in both.\n- No object in shot still carries the default grey surface.\n- The subject's edge is brighter than the background directly behind it.\n- Frame count equals requested seconds x fps, exactly.\n- The camera position differs from Blender's default.\n"
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
"mobile": {
|
|
324
|
+
"app-startup-optimisation": {
|
|
325
|
+
"name": "app-startup-optimisation",
|
|
326
|
+
"description": "Reduce mobile app startup time. Use when a cold start takes more than about two seconds, when a splash screen lingers, or when the first frame is blank.",
|
|
327
|
+
"content": "---\nname: app-startup-optimisation\ndescription: Reduce mobile app startup time. Use when a cold start takes more than about two seconds, when a splash screen lingers, or when the first frame is blank.\n---\n\n# App Startup Optimisation\n\n## The three starts\n\nCold (no process), warm (process alive, activity recreated), hot (everything alive). Cold start is what users judge you on, and it is the only one worth optimising first.\n\n## Workflow\n\n1. Measure each phase separately.\n - Process creation, framework initialisation, application setup, first layout, first frame, first useful content. Optimising before you know which dominates is guessing.\n - **Done when:** every phase has a measured duration on a mid-tier device.\n\n2. Move work out of application initialisation.\n - Analytics, crash reporting, image loaders and dependency graphs commonly initialise eagerly. Almost none of them are needed before the first frame. Defer them behind lazy initialisation or a post-first-frame hook.\n - **Done when:** application setup does only what the first screen needs.\n\n3. Make the first screen cheap.\n - The first frame should render from cached or placeholder data, not from a network call. A skeleton that appears instantly beats correct content that appears in three seconds.\n - **Done when:** something meaningful renders before any network response.\n\n4. Do not hide the problem behind a splash screen.\n - An artificial splash delay makes the measured start longer. Use the platform's own splash mechanism, which shows during process creation and costs nothing.\n - **Done when:** no artificial delay exists on the startup path.\n\n5. Ship ahead-of-time compilation hints.\n - A baseline profile covering launch and the first interaction removes interpretation on the critical path.\n - **Done when:** a profile generated from a real launch journey is included in the release.\n\n6. Audit the dependency graph.\n - Each library initialising at startup costs time. Some register a startup provider automatically — check what is actually running before your first line of code.\n - **Done when:** every automatic initialiser is either needed or disabled.\n\n## Verification\n\n- Cold start to first useful content is measured before and after, on a mid-tier device, on a release build.\n- Nothing on the startup path performs network or disk work that the first screen does not need.\n"
|
|
328
|
+
},
|
|
329
|
+
"cross-platform-tradeoffs": {
|
|
330
|
+
"name": "cross-platform-tradeoffs",
|
|
331
|
+
"description": "Decide between native and cross-platform, or work within Flutter or React Native. Use when choosing a mobile stack, or when a cross-platform app hits a platform limit.",
|
|
332
|
+
"content": "---\nname: cross-platform-tradeoffs\ndescription: Decide between native and cross-platform, or work within Flutter or React Native. Use when choosing a mobile stack, or when a cross-platform app hits a platform limit.\n---\n\n# Cross-Platform Trade-offs\n\n## The honest framing\n\nCross-platform buys shared business logic and one team. It costs latency on new platform features, a harder time with deep platform integration, and a debugging path that crosses a bridge. Neither answer is universally right; the wrong move is choosing without naming the trade.\n\n## Workflow\n\n1. Name the platform-specific surface first.\n - Widgets, watch apps, background processing, camera and audio pipelines, accessibility services, deep OS integration. If the product depends on several of these, the shared-code saving is smaller than it looks.\n - **Done when:** the platform-specific requirements are listed before the framework is chosen.\n\n2. Match the choice to the team, not to the benchmark.\n - A team fluent in one ecosystem will ship better software in it than in a technically superior one they are learning. This usually dominates the technical comparison.\n - **Done when:** the decision states the team's existing skills as an input.\n\n3. Isolate the platform boundary.\n - Whatever the choice, platform-specific code lives behind one interface. This keeps the shared code testable and keeps a future migration bounded.\n - **Done when:** platform APIs are reached through a single abstraction per capability.\n\n4. Follow each platform's conventions in the UI.\n - Navigation patterns, back behaviour, share sheets and system fonts differ. An app that ignores this is immediately recognisable as a port, and users read that as low quality.\n - **Done when:** navigation and system interactions match each platform's convention.\n\n5. Test on both, on real devices.\n - A simulator hides performance, permissions, and hardware differences. Cross-platform bugs concentrate exactly where the two platforms differ.\n - **Done when:** every release is exercised on a physical device of each platform.\n\n6. Budget for the bridge.\n - Frequent crossings — per-frame updates, large payloads, high-rate sensors — are the usual performance cliff. Keep hot paths on one side.\n - **Done when:** no hot path crosses the boundary per frame.\n\n## Verification\n\n- The decision is written down with the platform-specific requirements that informed it.\n- Platform code sits behind one abstraction per capability.\n- Both platforms are tested on physical hardware.\n"
|
|
333
|
+
},
|
|
334
|
+
"deep-linking": {
|
|
335
|
+
"name": "deep-linking",
|
|
336
|
+
"description": "Implement deep links or universal links. Use when a link should open the app, when links open the browser instead, or when a link cannot restore a screen directly.",
|
|
337
|
+
"content": "---\nname: deep-linking\ndescription: Implement deep links or universal links. Use when a link should open the app, when links open the browser instead, or when a link cannot restore a screen directly.\n---\n\n# Deep Linking\n\n## Workflow\n\n1. Use verified links, not custom schemes.\n - A custom scheme can be claimed by any app and does not work from most messaging clients. Verified HTTP links (app links / universal links) are owned by your domain and fall back to the web when the app is absent.\n - **Done when:** links use https and are verified against the domain.\n\n2. Publish and verify the association file.\n - The association file must be served over https, with the right content type, no redirect, and the correct fingerprint or team identifier. Verification failing silently is the single most common cause of links opening the browser.\n - **Done when:** platform verification reports the domain as verified on a real device.\n\n3. Make every screen reachable by URL.\n - Design the route table as URLs first. A screen that can only be reached by navigating from the home screen cannot be linked, notified into, or restored.\n - **Done when:** every meaningful screen has a URL.\n\n4. Rebuild the back stack, do not just push.\n - Opening a detail screen from a link with an empty back stack traps the user. Synthesise the parent chain so back goes somewhere sensible.\n - **Done when:** pressing back from a linked screen reaches a reasonable parent, not an exit.\n\n5. Handle the pre-ready link.\n - A link can arrive before authentication or before data is loaded. Store the pending destination, complete the prerequisite, then navigate. Never drop it.\n - **Done when:** a link that requires login lands on the target after login, not on the home screen.\n\n6. Validate the parameters.\n - A link is untrusted input. Validate identifiers and reject anything malformed rather than passing it into a query.\n - **Done when:** a malformed link shows an error rather than crashing.\n\n## Verification\n\n- Links open the app from a message, a browser and a notification, on a real device.\n- Back from a linked screen behaves correctly.\n- A link requiring authentication survives the login flow.\n- A malformed link fails safely.\n"
|
|
338
|
+
},
|
|
339
|
+
"mobile-storage-security": {
|
|
340
|
+
"name": "mobile-storage-security",
|
|
341
|
+
"description": "Store sensitive data on a mobile device. Use when saving tokens, credentials, or personal data locally, or when a security review flags local storage.",
|
|
342
|
+
"content": "---\nname: mobile-storage-security\ndescription: Store sensitive data on a mobile device. Use when saving tokens, credentials, or personal data locally, or when a security review flags local storage.\n---\n\n# Mobile Storage Security\n\n## The threat\n\nA lost or stolen device, a backup extracted to a computer, and on a rooted or jailbroken device, another process reading your files. Plain preferences and plain files are readable in all three cases.\n\n## Workflow\n\n1. Classify before choosing storage.\n - Secrets (tokens, keys, passwords) → the platform keystore or keychain.\n - Personal data → encrypted database or encrypted files.\n - Cache and preferences → plain storage is fine.\n - **Done when:** every stored item has a classification.\n\n2. Put secrets in hardware-backed storage.\n - The platform keystore keeps key material outside the app's address space, and hardware backing means it cannot be extracted even from a rooted device. Require user authentication for the highest-value keys.\n - **Done when:** no token or key is written to plain preferences or a plain file.\n\n3. Never ship a secret inside the app.\n - An API key in the binary is extractable in minutes, whatever the obfuscation. If a key must exist, it belongs on a server that proxies the call.\n - **Done when:** no long-lived credential exists in the shipped artefact.\n\n4. Control what leaves the device in a backup.\n - Exclude secrets and caches from automatic cloud backup explicitly. A backup restored to another device must not carry a session token.\n - **Done when:** the backup rules name what is excluded, and a restore does not carry a live session.\n\n5. Protect data at rest as well as in transit.\n - Encrypted database for personal data, so a file pulled from a backup is not readable.\n - **Done when:** the database file alone is not readable without the device key.\n\n6. Clear on logout.\n - Tokens, cached personal data and derived keys. Leaving them behind means the next user of the device inherits the session.\n - **Done when:** after logout, no personal data or credential remains on disk.\n\n## Verification\n\n- Pulling the app's data directory yields no readable secret.\n- The shipped artefact contains no long-lived credential.\n- Logout leaves no personal data on disk.\n- A restored backup does not carry a live session.\n"
|
|
343
|
+
},
|
|
344
|
+
"offline-first-sync": {
|
|
345
|
+
"name": "offline-first-sync",
|
|
346
|
+
"description": "Make a mobile app work without a network connection. Use when the app shows an error offline, when data is lost on a failed request, or when local and server state diverge.",
|
|
347
|
+
"content": "---\nname: offline-first-sync\ndescription: Make a mobile app work without a network connection. Use when the app shows an error offline, when data is lost on a failed request, or when local and server state diverge.\n---\n\n# Offline-First Sync\n\n## The premise\n\nThe local store is the source of truth for the UI. The network is a background synchroniser. Every screen reads local data and renders immediately; nothing waits on a request. Getting this backwards is why most apps are unusable on a train.\n\n## Workflow\n\n1. Write locally first, always.\n - A user action commits to the local database and the UI updates from it. A sync record is queued separately. The user never waits for a round trip to see their own action.\n - **Done when:** every mutation succeeds and is visible with the network disabled.\n\n2. Queue mutations durably.\n - The outbox survives app death and reboot. Each entry has a stable identifier so a retry cannot duplicate the server-side effect.\n - **Done when:** killing the app mid-sync loses nothing and duplicates nothing.\n\n3. Decide the conflict rule per field, not per record.\n - Last-write-wins is acceptable for a note body and wrong for a counter or a set. State the rule for each field that can be edited on two devices.\n - **Done when:** every conflict-capable field has a stated resolution rule.\n\n4. Sync incrementally.\n - Track a server cursor or updated-at watermark and request only what changed. A full re-fetch on every launch is slow, expensive and pointless.\n - **Done when:** a sync after a small change transfers a small amount of data.\n\n5. Show sync state without blocking.\n - A subtle indicator for pending and failed items. A modal spinner over the whole screen defeats the entire design.\n - **Done when:** pending and failed states are visible and non-blocking.\n\n6. Make failure recoverable by the user.\n - An item that fails permanently needs a visible retry or discard. Silent permanent failure loses user data without telling them.\n - **Done when:** a permanently failed mutation is surfaced with an action.\n\n## Verification\n\n- Every screen renders and every action succeeds in airplane mode.\n- Reconnecting syncs the queue exactly once.\n- Editing the same record on two devices resolves by the stated rule.\n"
|
|
348
|
+
},
|
|
349
|
+
"push-notifications": {
|
|
350
|
+
"name": "push-notifications",
|
|
351
|
+
"description": "Implement push notifications. Use when adding push, when notifications do not arrive on some devices, or when permission is requested at launch.",
|
|
352
|
+
"content": "---\nname: push-notifications\ndescription: Implement push notifications. Use when adding push, when notifications do not arrive on some devices, or when permission is requested at launch.\n---\n\n# Push Notifications\n\n## Workflow\n\n1. Ask for permission in context, never at launch.\n - A permission prompt on first open is denied by most users, and on both platforms a denial is close to permanent. Ask at the moment the value is obvious, after explaining it in your own UI first.\n - **Done when:** the system prompt appears only after an in-app explanation and a user action.\n\n2. Treat the token as volatile.\n - It rotates on reinstall, restore, and sometimes on update. Refresh on every launch and on the rotation callback, and send it to the server with a device identifier so old tokens can be replaced rather than accumulated.\n - **Done when:** reinstalling produces a working token without a duplicate device record.\n\n3. Send data, render locally.\n - A notification payload containing final display text cannot be localised, personalised, or deduplicated. Send identifiers and render from local data where the platform allows.\n - **Done when:** the notification respects the device language and the app's current state.\n\n4. Handle all three app states.\n - Foreground, background, and terminated behave differently on both platforms. The terminated case is the one that is usually missed, and it is the one that matters for a cold launch from a notification.\n - **Done when:** tapping a notification opens the right screen from all three states.\n\n5. Route the tap deterministically.\n - The payload carries a deep link. Handle a link that arrives before the app is ready by queueing it until navigation exists.\n - **Done when:** a cold-start notification tap lands on the correct screen.\n\n6. Respect the user's attention.\n - Channels or categories so the user can mute a class rather than all notifications. Collapse keys so ten updates do not become ten notifications. Never send a notification with no user value.\n - **Done when:** every notification type is in a channel the user can control separately.\n\n## Verification\n\n- Delivery works in foreground, background and terminated states on physical devices.\n- Reinstalling refreshes the token and does not duplicate the device.\n- Muting one channel leaves the others working.\n"
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
"performance": {
|
|
356
|
+
"algorithmic-complexity": {
|
|
357
|
+
"name": "algorithmic-complexity",
|
|
358
|
+
"description": "Fix code that slows down disproportionately as data grows. Use when performance is fine in testing and bad in production, or when a loop contains a lookup.",
|
|
359
|
+
"content": "---\nname: algorithmic-complexity\ndescription: Fix code that slows down disproportionately as data grows. Use when performance is fine in testing and bad in production, or when a loop contains a lookup.\n---\n\n# Algorithmic Complexity\n\n## Where the real wins are\n\nConstant-factor optimisation buys a few percent. Removing a factor of n buys orders of magnitude. On real data, the difference between a nested loop and a hash lookup dwarfs every micro-optimisation combined — and the nested loop is invisible on a test fixture of ten items.\n\n## Workflow\n\n1. Find the nested iteration.\n - A loop containing a search, a filter, an `includes`, or a query. This is the single most common cause of \"fine in dev, unusable in production\".\n - Fix: build a map or set once, then look up in constant time.\n - **Done when:** no loop body performs a linear search over another collection.\n\n2. Look for repeated work that could be done once.\n - A value recomputed on every iteration, a regular expression compiled inside a loop, a sort inside a loop. Hoist it.\n - **Done when:** nothing invariant is computed inside a loop.\n\n3. Choose the structure for the dominant operation.\n - Frequent membership tests: a set. Frequent lookup by key: a map. Ordered range queries: a sorted structure or an index. Using an array for membership testing is the same mistake as the nested loop.\n - **Done when:** the data structure matches the most frequent operation.\n\n4. Push filtering to where the data lives.\n - Fetching ten thousand rows to keep five is ten thousand rows of transfer, allocation and parsing. Filter in the query.\n - **Done when:** the volume fetched is proportional to the volume used.\n\n5. Do not process what you will not show.\n - Paginate, limit, and stream. A list endpoint with no limit is unbounded work triggered by a stranger.\n - **Done when:** every collection operation has a bound.\n\n6. Verify the complexity empirically.\n - Time with n, 10n and 100n. Linear growth is fine; quadratic shows up immediately at 100n.\n - **Done when:** timing scales as the intended complexity predicts.\n\n## Verification\n\n- Timing at three input sizes matches the intended complexity.\n- No loop body contains a linear search.\n- Every collection endpoint is bounded.\n"
|
|
360
|
+
},
|
|
361
|
+
"concurrency-and-parallelism": {
|
|
362
|
+
"name": "concurrency-and-parallelism",
|
|
363
|
+
"description": "Make code faster by running work concurrently, or fix a concurrency bug. Use when work is serialised unnecessarily, or when parallel code produces wrong results.",
|
|
364
|
+
"content": "---\nname: concurrency-and-parallelism\ndescription: Make code faster by running work concurrently, or fix a concurrency bug. Use when work is serialised unnecessarily, or when parallel code produces wrong results.\n---\n\n# Concurrency and Parallelism\n\n## The distinction that decides the mechanism\n\nConcurrency is dealing with many things at once; parallelism is doing many things at once. I/O-bound work needs concurrency — async or threads. CPU-bound work needs parallelism — processes or real threads. Applying the wrong one gives no speedup and sometimes a slowdown.\n\n## Workflow\n\n1. Classify the bottleneck before choosing a tool.\n - Watch CPU during the slow operation. Near 100% on one core is CPU-bound. Low CPU with the process waiting is I/O-bound.\n - **Done when:** the bottleneck is classified with a measurement.\n\n2. Find the actual serialisation point.\n - Parallelising around a shared lock, a single database connection, or a rate-limited API gives no improvement — the queue just moves. Find what everything waits on.\n - **Done when:** the shared resource is identified and shown not to be the limit.\n\n3. Bound the concurrency.\n - Unbounded parallelism exhausts connections, file descriptors, or memory, and usually performs worse than a sensible limit. Use a semaphore or a worker pool sized from measurement.\n - **Done when:** peak concurrency is capped at a measured number.\n\n4. Eliminate shared mutable state, do not lock it.\n - Give each worker its own data and combine at the end. Locks are correct and slow, and every lock is a potential deadlock. Immutability and message passing avoid the whole class.\n - **Done when:** workers share nothing mutable, or every shared item's protection is documented.\n\n5. Order lock acquisition consistently.\n - Deadlock is two paths taking the same locks in different orders. A global order — by identifier — removes it structurally.\n - **Done when:** every multi-lock path acquires in the same order.\n\n6. Test under real contention.\n - A race that never appears with two workers appears with two hundred. Run the concurrency test with many workers, repeatedly, and with a race detector where the language has one.\n - **Done when:** a high-contention test passes repeatedly.\n\n## Verification\n\n- Speedup is measured, not assumed, and is close to what the bottleneck class predicts.\n- A high-contention test produces the correct result over many runs.\n- No shared mutable state is accessed without documented protection.\n"
|
|
365
|
+
},
|
|
366
|
+
"load-testing": {
|
|
367
|
+
"name": "load-testing",
|
|
368
|
+
"description": "Find out how a system behaves under load. Use before a launch, before a traffic event, or after a capacity-related incident.",
|
|
369
|
+
"content": "---\nname: load-testing\ndescription: Find out how a system behaves under load. Use before a launch, before a traffic event, or after a capacity-related incident.\n---\n\n# Load Testing\n\n## What you are looking for\n\nNot the maximum throughput. The point at which latency degrades, and what happens beyond it. A system that quietly falls over at 2× normal traffic is the problem; the exact breaking number is secondary.\n\n## Workflow\n\n1. Define the scenario from real traffic.\n - Endpoint mix, think time, payload sizes and the cache-hit ratio, taken from production logs. A test hammering one endpoint with no think time measures something no user will ever do.\n - **Done when:** the scenario's mix is derived from observed traffic.\n\n2. Test against production-like infrastructure and data.\n - Same instance sizes, same database size, same cache state. A test against an empty database measures the empty database.\n - **Done when:** the environment's differences from production are listed and their effect stated.\n\n3. Ramp rather than spike.\n - Increase load gradually and watch where latency turns upward. That knee is the useful number. A spike test is a separate scenario for autoscaling behaviour.\n - **Done when:** the latency knee is identified.\n\n4. Watch the system, not just the client.\n - CPU, memory, connection pool saturation, database load, queue depth, and error rate. The client only sees the symptom; the cause is inside.\n - **Done when:** the first resource to saturate is named.\n\n5. Push past the knee deliberately.\n - Graceful degradation — shedding load, returning 429, serving stale — is a design property, not an accident. Confirm the system does something sensible rather than collapsing.\n - **Done when:** behaviour beyond capacity is characterised and acceptable.\n\n6. Soak at a sustainable level.\n - Several hours at a realistic load finds leaks, connection exhaustion, and log-disk growth — none of which appear in a ten-minute run.\n - **Done when:** a multi-hour run shows flat memory and stable latency.\n\n## Verification\n\n- The latency knee and the first saturating resource are both reported.\n- Behaviour beyond capacity is documented.\n- A soak run shows no resource growth over time.\n"
|
|
370
|
+
},
|
|
371
|
+
"memory-and-leak-hunting": {
|
|
372
|
+
"name": "memory-and-leak-hunting",
|
|
373
|
+
"description": "Diagnose growing memory or an out-of-memory failure. Use when a process grows over time, when it is killed by the platform, or when garbage collection pauses become long.",
|
|
374
|
+
"content": "---\nname: memory-and-leak-hunting\ndescription: Diagnose growing memory or an out-of-memory failure. Use when a process grows over time, when it is killed by the platform, or when garbage collection pauses become long.\n---\n\n# Memory and Leak Hunting\n\n## Growth versus leak\n\nA leak is unbounded retention: memory that is reachable but never used again. High usage that plateaus is not a leak — it is a cache or a pool doing its job. Distinguish these before investigating, because they need opposite responses.\n\n## Workflow\n\n1. Establish the shape of the growth.\n - Plot resident memory over hours. Linear with traffic suggests per-request retention. Stepwise suggests a cache without a bound. A plateau is not a leak.\n - **Done when:** the growth pattern is characterised.\n\n2. Take two heap snapshots and compare.\n - One after warm-up, one after a period of load, having returned to the same logical state. The DIFFERENCE is the leak; a single snapshot mostly shows normal working set.\n - **Done when:** you have the object types that grew between snapshots.\n\n3. Follow the retaining path.\n - The profiler shows what holds the object. The answer is almost always one of: a module-level collection that only grows, a listener never removed, a closure capturing a large scope, a cache with no eviction, or a timer holding its context.\n - **Done when:** the retaining reference is named.\n\n4. Bound every cache.\n - A map used as a cache with no maximum size and no eviction is the most common leak in any language. Give it a bound and a policy.\n - **Done when:** every long-lived collection has a maximum size.\n\n5. Pair every registration with a removal.\n - Listeners, subscriptions, intervals, and observers. Where the language supports it, use a scope guard so the pairing cannot be forgotten.\n - **Done when:** every registration has a guaranteed removal.\n\n6. Re-measure over the same window.\n - **Done when:** memory returns to baseline after load, over the duration that previously showed growth.\n\n## Verification\n\n- Memory plateaus rather than growing over a long soak test.\n- The retained set returns to baseline after load stops.\n- Every cache has a stated bound.\n"
|
|
375
|
+
},
|
|
376
|
+
"network-and-payload-efficiency": {
|
|
377
|
+
"name": "network-and-payload-efficiency",
|
|
378
|
+
"description": "Reduce time spent on the network. Use when an app is slow on a mobile connection, when payloads are large, or when a screen makes many requests.",
|
|
379
|
+
"content": "---\nname: network-and-payload-efficiency\ndescription: Reduce time spent on the network. Use when an app is slow on a mobile connection, when payloads are large, or when a screen makes many requests.\n---\n\n# Network and Payload Efficiency\n\n## What dominates on a real connection\n\nRound trips, not bandwidth. On a mobile network each round trip costs tens to hundreds of milliseconds regardless of payload size. Eliminating a request is worth far more than shrinking one.\n\n## Workflow\n\n1. Count the requests on the critical path.\n - Waterfall view. Look for sequential chains — a request whose response is needed to start the next. Each link is a full round trip.\n - **Done when:** the length of the longest sequential chain is known.\n\n2. Break the chains.\n - Return what the screen needs in one response, or issue independent requests in parallel. A screen that fetches a user, then their orders, then each order's items is three round trips deep before anything renders.\n - **Done when:** no chain on the critical path is longer than two.\n\n3. Send only what is used.\n - A response with forty fields where the screen shows four wastes serialisation, transfer and parsing. Support field selection, or provide a purpose-built endpoint.\n - **Done when:** responses contain no field the client ignores.\n\n4. Compress and cache correctly.\n - Compression on for text responses. `Cache-Control` with a real max-age for anything stable, and `ETag` for anything revalidatable. A correct 304 costs one round trip and no body.\n - **Done when:** repeat loads are served from cache or revalidated without a body.\n\n5. Paginate and stream large results.\n - Cursor pagination for lists, streaming for large payloads so the client can begin before the end arrives.\n - **Done when:** no endpoint returns an unbounded body.\n\n6. Move the data closer.\n - A CDN for static assets and cacheable responses. Geography is a fixed cost that no amount of optimisation removes.\n - **Done when:** static assets are served from an edge location.\n\n## Verification\n\n- Requests on the critical path are counted before and after.\n- Repeat loads produce 304 responses or cache hits.\n- The app is tested on a throttled connection with added latency, not on the local network.\n"
|
|
380
|
+
},
|
|
381
|
+
"profiling-methodology": {
|
|
382
|
+
"name": "profiling-methodology",
|
|
383
|
+
"description": "Investigate a performance problem. Use whenever something is slow, before optimising anything, and when an optimisation did not help.",
|
|
384
|
+
"content": "---\nname: profiling-methodology\ndescription: Investigate a performance problem. Use whenever something is slow, before optimising anything, and when an optimisation did not help.\n---\n\n# Profiling Methodology\n\n## The rule that saves the most time\n\nMeasure first. Developer intuition about where time goes is wrong most of the time, and an optimisation applied to the wrong place costs the effort plus the added complexity plus the bug it introduces, for no gain.\n\n## Workflow\n\n1. State the goal as a number.\n - \"p95 under 200ms\", not \"faster\". Without a target there is no way to know when to stop, and optimisation has no natural end.\n - **Done when:** the target and the current value are both written down.\n\n2. Measure the right layer.\n - A slow page can be the network, the server, the database, or the client. Establish which before profiling inside one of them. The waterfall or a distributed trace answers this in a minute.\n - **Done when:** the dominant layer is identified.\n\n3. Profile under realistic conditions.\n - Production-scale data, realistic concurrency, a release build, and a representative device or instance. A profile on ten rows on a workstation finds nothing that matters.\n - **Done when:** the profile was taken under conditions resembling production.\n\n4. Read the profile for self time and for the call tree.\n - Self time finds a slow function. The tree finds a fast function called a million times, which is the more common and more valuable finding.\n - **Done when:** you can name the specific call path consuming the time.\n\n5. Change one thing and re-measure.\n - Several changes at once make attribution impossible, and one of them may have made things worse.\n - **Done when:** each change has a before and after number.\n\n6. Stop at the target.\n - Beyond it, further optimisation buys complexity nobody asked for.\n - **Done when:** the target is met and the work has stopped.\n\n## The usual suspects, in order\n\nRepeated work in a loop, an N+1 query, a missing index, unnecessary serialisation, a synchronous call in an async path, and an unbounded collection.\n\n## Verification\n\n- Before and after numbers are reported for the stated metric.\n- The measurement conditions are described.\n- The change is attributable to one modification.\n"
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
"python": {
|
|
388
|
+
"async-correctness": {
|
|
389
|
+
"name": "async-correctness",
|
|
390
|
+
"description": "Write or debug asyncio code. Use when async code runs slower than sync, when a coroutine is never awaited, or when the event loop appears to block.",
|
|
391
|
+
"content": "---\nname: async-correctness\ndescription: Write or debug asyncio code. Use when async code runs slower than sync, when a coroutine is never awaited, or when the event loop appears to block.\n---\n\n# Asyncio Correctness\n\n## The failure that causes most of the others\n\nA blocking call inside a coroutine stops the ENTIRE event loop, not just that task. `time.sleep`, `requests.get`, a synchronous file read, or a CPU-heavy loop will serialise everything the program is doing. Async code that is slower than sync code almost always has one of these.\n\n## Workflow\n\n1. Find the blocking calls.\n - Any synchronous I/O library, any `time.sleep`, any long pure-Python computation.\n - Fix: use the async equivalent (`httpx` over `requests`, `aiofiles` or a thread for files), or push the work off the loop with `asyncio.to_thread` for I/O and a process pool for CPU.\n - **Done when:** no coroutine performs synchronous I/O or long computation.\n\n2. Make concurrency explicit.\n - `await` in sequence is sequential. To run concurrently, use `asyncio.TaskGroup` (3.11+), which also propagates exceptions and cancels siblings correctly.\n - `asyncio.gather` without `return_exceptions` leaves siblings running after one fails.\n - **Done when:** work intended to be concurrent uses a TaskGroup and finishes in roughly the time of the slowest item.\n\n3. Never create a task without keeping a reference.\n - The loop holds only a weak reference; a bare `asyncio.create_task(...)` can be garbage-collected mid-flight and the work silently vanishes. Hold the task, or use a TaskGroup.\n - **Done when:** every created task is owned by something.\n\n4. Handle cancellation as a normal path.\n - `CancelledError` must propagate. Catching it broadly makes a task uncancellable and hangs shutdown. Cleanup belongs in `finally`, and must not await anything slow.\n - **Done when:** cancelling the top-level task exits promptly.\n\n5. Bound the concurrency.\n - Ten thousand simultaneous requests will exhaust file descriptors or get the client blocked. Use a `Semaphore`.\n - **Done when:** peak concurrency is capped at a stated number.\n\n## Verification\n\n- Run with `PYTHONASYNCIODEBUG=1`; there are no \"coroutine was never awaited\" or slow-callback warnings.\n- Concurrent work completes in about the time of the slowest item, not the sum.\n- Interrupting the program exits cleanly rather than hanging.\n"
|
|
392
|
+
},
|
|
393
|
+
"cli-tool-construction": {
|
|
394
|
+
"name": "cli-tool-construction",
|
|
395
|
+
"description": "Build a Python command-line tool. Use when a script parses sys.argv by hand, when it has no help output, or when it cannot be used in a pipeline.",
|
|
396
|
+
"content": "---\nname: cli-tool-construction\ndescription: Build a Python command-line tool. Use when a script parses sys.argv by hand, when it has no help output, or when it cannot be used in a pipeline.\n---\n\n# Python CLI Construction\n\n## What separates a tool from a script\n\nDiscoverable help, correct exit codes, stdout carrying only the result, and behaviour that composes with other tools. A script that prints progress to stdout cannot be piped, and that is usually the first thing a user tries.\n\n## Workflow\n\n1. Use a parser, not manual argument handling.\n - `argparse` is in the standard library and sufficient. `typer` or `click` add ergonomics at the cost of a dependency.\n - Every option gets a help string. `--help` is the tool's primary documentation.\n - **Done when:** `--help` alone explains how to use the tool.\n\n2. Separate the streams.\n - Results to stdout. Progress, warnings and errors to stderr. Mixing them makes the tool unpipeable and its output unparseable.\n - **Done when:** `tool ... > out.txt` leaves a file containing only results, with progress still visible on the terminal.\n\n3. Exit with meaning.\n - 0 for success, non-zero for failure. A tool that exits 0 after failing breaks every script that calls it.\n - **Done when:** a deliberate failure produces a non-zero exit code.\n\n4. Read from stdin when no file is given.\n - `-` conventionally means stdin. This makes the tool a filter and lets it compose.\n - **Done when:** `cat file | tool` behaves as `tool file`.\n\n5. Keep the logic out of the entry point.\n - `main()` parses arguments and calls a function that takes plain values. That function is testable without spawning a process.\n - **Done when:** the core behaviour has tests that do not invoke the CLI.\n\n6. Handle interruption quietly.\n - Catch the keyboard interrupt at the top level and exit 130 without a traceback. A stack trace on Ctrl+C looks like a crash.\n - **Done when:** interrupting prints nothing alarming and exits 130.\n\n## Verification\n\n- `--help` documents every option.\n- Redirecting stdout captures only results.\n- A failure path exits non-zero.\n- The core logic has tests that bypass argument parsing.\n"
|
|
397
|
+
},
|
|
398
|
+
"data-processing-pipelines": {
|
|
399
|
+
"name": "data-processing-pipelines",
|
|
400
|
+
"description": "Process files or datasets in Python. Use when a script loads an entire file into memory, when processing is slow, or when a pipeline fails partway through with no recovery.",
|
|
401
|
+
"content": "---\nname: data-processing-pipelines\ndescription: Process files or datasets in Python. Use when a script loads an entire file into memory, when processing is slow, or when a pipeline fails partway through with no recovery.\n---\n\n# Data Processing Pipelines\n\n## The two failures\n\nLoading everything into memory, and having no way to resume. Both are invisible on the sample file and fatal on the real one.\n\n## Workflow\n\n1. Stream rather than load.\n - Iterate the file; do not read it whole. `csv.reader` over a file object, `json` lines one per line, `pandas.read_csv(chunksize=...)`, or Polars in lazy mode.\n - **Done when:** peak memory is roughly constant regardless of input size.\n\n2. Make the pipeline a chain of generators.\n - Each stage takes an iterable and yields an iterable. Nothing materialises a list. This composes, stays constant-memory, and is testable stage by stage.\n - **Done when:** no stage builds a list of the whole dataset.\n\n3. Validate at the boundary and quarantine the failures.\n - Bad rows are normal in real data. Validate on entry, write rejects to a separate file with the reason, and continue. A pipeline that stops on row 400,000 wastes the previous 399,999.\n - **Done when:** a deliberately malformed row lands in the reject file and processing continues.\n\n4. Make the whole thing resumable and idempotent.\n - Checkpoint progress, and key output by a stable identifier so re-running overwrites rather than duplicates. Write to a temporary path and rename on completion so a partial file is never mistaken for a finished one.\n - **Done when:** interrupting mid-run and restarting produces the same result as an uninterrupted run.\n\n5. Parallelise only after measuring.\n - Find out whether the bottleneck is I/O or CPU. Threads help I/O; processes help CPU; neither helps if the bottleneck is a single slow query upstream.\n - **Done when:** the bottleneck is named and the chosen mechanism matches it.\n\n## Verification\n\n- Peak memory stays flat as input size grows tenfold.\n- A malformed row is quarantined, not fatal.\n- Killing the process mid-run and restarting yields the same output.\n"
|
|
402
|
+
},
|
|
403
|
+
"environment-isolation": {
|
|
404
|
+
"name": "environment-isolation",
|
|
405
|
+
"description": "Install Python dependencies or run a Python script. Use before any pip install, when no virtual environment is active, or when a project has no dependency manifest.",
|
|
406
|
+
"content": "---\nname: environment-isolation\ndescription: Install Python dependencies or run a Python script. Use before any pip install, when no virtual environment is active, or when a project has no dependency manifest.\n---\n\n# Python Environment Isolation\n\n## The rule that has no exceptions\n\nNever install into the system interpreter. On macOS and most Linux distributions the system Python is a dependency of the operating system, and a global install can break packaging tools, the desktop, or the boot path. Modern pip refuses by default (PEP 668) — that refusal is correct and must not be defeated with `--break-system-packages`.\n\n## Workflow\n\n1. Detect what is already there before creating anything.\n - Active environment: the `VIRTUAL_ENV` variable is set.\n - Project-local: a `.venv/` or `venv/` directory.\n - Managed: `poetry.lock`, `uv.lock`, `Pipfile.lock`, or `environment.yml` — each implies its own tool and its own commands.\n - **Done when:** you know which manager the project uses, or that it has none.\n\n2. Use the project's manager if it has one.\n - `uv sync`, `poetry install`, `pipenv install`, `conda env create -f environment.yml`. Mixing plain pip into a managed project desynchronises the lock file.\n - **Done when:** dependencies are installed through the project's own tool.\n\n3. Create an environment if none exists.\n - `python3 -m venv .venv` then activate. `uv venv` is faster where uv is available.\n - Add `.venv/` to `.gitignore`.\n - **Done when:** `VIRTUAL_ENV` is set and points into the project.\n\n4. Chain activation into every command you run.\n - A tool invocation is a fresh shell, so activation does not persist between them. Every command must carry it: `source .venv/bin/activate && pytest`. On Windows: `.venv\\Scripts\\activate`.\n - **Done when:** every command that touches Python includes activation.\n\n5. Record what you installed.\n - Add the dependency to `pyproject.toml` or `requirements.txt`. An install that is not recorded disappears on the next clean checkout.\n - **Done when:** a fresh clone plus an install reproduces the environment.\n\n## Verification\n\n- `which python` resolves inside the project directory.\n- `python -c \"import sys; print(sys.prefix)\"` prints the environment path, not the system path.\n- The new dependency appears in the manifest.\n"
|
|
407
|
+
},
|
|
408
|
+
"packaging-and-distribution": {
|
|
409
|
+
"name": "packaging-and-distribution",
|
|
410
|
+
"description": "Package a Python project for distribution or make it installable. Use when a project has setup.py only, when imports fail after install, or before publishing to an index.",
|
|
411
|
+
"content": "---\nname: packaging-and-distribution\ndescription: Package a Python project for distribution or make it installable. Use when a project has setup.py only, when imports fail after install, or before publishing to an index.\n---\n\n# Packaging and Distribution\n\n## The current shape\n\n`pyproject.toml` with a PEP 621 `[project]` table. `setup.py` is legacy; `setup.cfg` is superseded. A new project should have neither.\n\n## Workflow\n\n1. Choose the layout.\n - `src/` layout: the package lives in `src/mypkg/`. This is the correct default, because it makes it impossible to import the package from the working directory — so the tests exercise the INSTALLED package, which is what users get.\n - Flat layout imports from the source tree and hides packaging bugs until release.\n - **Done when:** the package is under `src/` and the tests still pass.\n\n2. Declare the metadata once.\n - Name, version, description, requires-python, dependencies, and the entry points. Read the version from a single source — either the package or the manifest — never both.\n - **Done when:** `python -m build` produces a wheel and a source distribution without warnings.\n\n3. Pin ranges, not exact versions, for a library.\n - A library that pins exact versions makes itself uninstallable alongside anything else. An application may pin exactly, and should, via a lock file.\n - **Done when:** the dependency specifiers match the kind of project.\n\n4. Test the built artefact, not the source tree.\n - Install the wheel into a clean environment and import it. A package that works in-tree and fails on install has a missing `package-data` entry or a wrong layout, and only this step catches it.\n - **Done when:** the wheel installs and imports in a fresh environment.\n\n5. Check what is inside the artefact.\n - `python -m zipfile -l dist/*.whl`. Look for missing data files and for accidentally included test or secret files.\n - **Done when:** the wheel contains exactly what should ship.\n\n## Template\n\n```toml\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"mypkg\"\nversion = \"0.1.0\"\nrequires-python = \">=3.11\"\ndependencies = [\"httpx>=0.27,<1.0\"]\n\n[project.scripts]\nmypkg = \"mypkg.cli:main\"\n```\n\n## Verification\n\n- `python -m build` succeeds with no warnings.\n- The wheel installs into a clean environment and the entry point runs.\n- The archive listing contains no test files and no credentials.\n"
|
|
412
|
+
},
|
|
413
|
+
"pytest-suite-design": {
|
|
414
|
+
"name": "pytest-suite-design",
|
|
415
|
+
"description": "Write or restructure Python tests. Use when tests share mutable state, when a suite is slow, or when failures are hard to read.",
|
|
416
|
+
"content": "---\nname: pytest-suite-design\ndescription: Write or restructure Python tests. Use when tests share mutable state, when a suite is slow, or when failures are hard to read.\n---\n\n# Pytest Suite Design\n\n## Workflow\n\n1. Make each test independent.\n - A test that depends on another having run first will fail under `-p no:randomly`, under `-n auto`, and in any order but the current one. Shared module-level state is the usual cause.\n - **Done when:** the suite passes with tests shuffled and with parallel execution.\n\n2. Use fixtures for setup, not for cleverness.\n - A fixture should produce one thing and clean it up with `yield`. Scope it as narrowly as correctness allows: `function` by default, wider only when construction is genuinely expensive AND the object is immutable.\n - A mutable `session`-scoped fixture is shared state with extra steps.\n - **Done when:** no fixture wider than `function` scope is mutated by a test.\n\n3. Parametrise instead of duplicating.\n - `@pytest.mark.parametrize` turns eight near-identical tests into one with eight cases, each reported separately. Give the cases `ids` so a failure names the case rather than an index.\n - **Done when:** no two tests differ only by literal values.\n\n4. Assert on behaviour, not implementation.\n - Assert what the function returns and what it does to the world. Asserting that a private helper was called with particular arguments locks the implementation and makes every refactor a test rewrite.\n - **Done when:** the tests still pass after an internal refactor that preserves behaviour.\n\n5. Isolate the outside world.\n - No test touches the real network, the real clock, or a real database unless it is explicitly an integration test and marked as one. `monkeypatch`, `freezegun`, and `responses` cover most of it.\n - **Done when:** the unit suite passes with the network disabled.\n\n6. Make failures self-explanatory.\n - One logical assertion per test, and a name that states the expected behaviour: `test_expired_token_is_rejected`, not `test_auth_2`.\n - **Done when:** a failure name alone communicates what broke.\n\n## Verification\n\n- `pytest -p no:randomly --randomly-seed=last` and `pytest -n auto` both pass.\n- The unit suite passes with no network access.\n- Every test name reads as a statement about behaviour.\n"
|
|
417
|
+
},
|
|
418
|
+
"type-hints-and-static-checking": {
|
|
419
|
+
"name": "type-hints-and-static-checking",
|
|
420
|
+
"description": "Add or fix type annotations in Python. Use when a codebase has no hints, when a type checker reports many errors, or when a function signature is ambiguous.",
|
|
421
|
+
"content": "---\nname: type-hints-and-static-checking\ndescription: Add or fix type annotations in Python. Use when a codebase has no hints, when a type checker reports many errors, or when a function signature is ambiguous.\n---\n\n# Type Hints and Static Checking\n\n## Where annotations pay\n\nPublic function signatures and data structures. Annotating every local variable adds noise without adding information — the checker infers those.\n\n## Workflow\n\n1. Turn the checker on before writing annotations.\n - `mypy` or `pyright`. Start non-strict on an existing codebase; a first run in strict mode on untyped code produces thousands of errors and gets switched off.\n - **Done when:** the checker runs and its current error count is recorded.\n\n2. Annotate boundaries first.\n - Public functions, class attributes, and anything crossing a module edge. This is where a wrong assumption actually costs something.\n - **Done when:** every public function has annotated parameters and a return type.\n\n3. Use the modern syntax.\n - `list[str]` not `List[str]`. `str | None` not `Optional[str]`. `dict[str, int]` not `Dict[str, int]`. The `typing` aliases are deprecated.\n - **Done when:** no import from `typing` exists that a builtin generic replaces.\n\n4. Never reach for `Any` to silence an error.\n - `Any` disables checking for everything it touches and spreads. If the type is genuinely unknown, `object` is honest and forces a narrowing check at the point of use.\n - **Done when:** every remaining `Any` has a comment saying why.\n\n5. Ratchet strictness upward.\n - Enable one strict flag at a time and fix the errors it produces. `disallow_untyped_defs` module by module via per-module settings is the usual path.\n - **Done when:** the error count is zero at the current strictness and one more flag is enabled.\n\n6. Type the tricky shapes properly.\n - `TypedDict` for a dictionary with known keys, `Protocol` for structural typing instead of an abstract base class, `Literal` for a fixed set of strings, and `TypeVar` for a function whose return type depends on its input.\n - **Done when:** no function returns a bare `dict` where a `TypedDict` describes it.\n\n## Verification\n\n- The type checker reports zero errors at the configured strictness.\n- CI runs the checker and fails on a new error.\n- No new `Any` was introduced without a justification.\n"
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
"security": {
|
|
425
|
+
"authorization-model": {
|
|
426
|
+
"name": "authorization-model",
|
|
427
|
+
"description": "Decide who may do what. Use when adding permissions, when a user can access another user`s data, or when authorization checks are scattered across handlers.",
|
|
428
|
+
"content": "---\nname: authorization-model\ndescription: Decide who may do what. Use when adding permissions, when a user can access another user`s data, or when authorization checks are scattered across handlers.\n---\n\n# Authorization Model\n\n## The distinction that gets missed\n\nAuthentication is who you are. Authorization is what you may do. A system with excellent authentication and no authorization lets any logged-in user read every record — and that class of bug (broken object-level authorization) is consistently the most common serious web vulnerability.\n\n## Workflow\n\n1. Check ownership on every single object access.\n - Not just on the list endpoint. `GET /orders/123` must verify that order 123 belongs to the caller. Relying on the UI not to show the link is not a control — the identifier is guessable and the endpoint is public.\n - **Done when:** every by-identifier endpoint verifies the caller's relationship to that object.\n\n2. Deny by default.\n - The absence of a rule means no. A system where an unmatched route falls through to allow will leak on the first forgotten annotation.\n - **Done when:** a new endpoint with no rule is inaccessible.\n\n3. Centralise the decision, distribute the enforcement.\n - One function answers \"may this subject perform this action on this object\". Handlers call it; they do not reimplement it. Scattered inline checks drift, and the one that drifts is the hole.\n - **Done when:** there is one authorization entry point and every handler uses it.\n\n4. Enforce at the data layer where you can.\n - A query scoped to the caller's tenant by construction cannot leak, even from a handler that forgot to check. Row-level security or a mandatory scope on the repository turns a class of bug into an impossibility.\n - **Done when:** the default query path cannot return another tenant's rows.\n\n5. Model roles as permissions, not as titles.\n - Check `may_refund_order`, not `is_admin`. Title checks scattered through the code make adding a role a codebase-wide change.\n - **Done when:** no branch tests a role name directly.\n\n6. Log the denials.\n - A spike in denials is either a bug or an attack. Both are worth seeing.\n - **Done when:** denials are logged with subject, action and object.\n\n## Verification\n\n- A test attempts to read another user's object by identifier and receives 404 or 403.\n- A new endpoint without a rule is denied.\n- The default repository query is tenant-scoped.\n"
|
|
429
|
+
},
|
|
430
|
+
"crypto-usage": {
|
|
431
|
+
"name": "crypto-usage",
|
|
432
|
+
"description": "Encrypt, hash, sign, or generate randomness. Use whenever cryptography is being written or reviewed.",
|
|
433
|
+
"content": "---\nname: crypto-usage\ndescription: Encrypt, hash, sign, or generate randomness. Use whenever cryptography is being written or reviewed.\n---\n\n# Cryptography Usage\n\n## The rule\n\nDo not build it. Use a vetted library at the highest level of abstraction available. Cryptographic code fails silently — it produces output that looks correct and provides no security — so \"it works\" tells you nothing.\n\n## The correct primitives\n\n- **Password storage** — argon2id, or bcrypt. Never a general-purpose hash: they are fast by design, which is exactly wrong here.\n- **Symmetric encryption** — an authenticated mode: AES-GCM or XChaCha20-Poly1305. Unauthenticated encryption is malleable, and the attack is practical.\n- **Random values** — the platform's cryptographically secure generator. Never a general-purpose random function for a token, an identifier, or a nonce.\n- **Comparing secrets** — constant-time comparison. A normal comparison returns early on the first differing byte and leaks the secret through timing.\n- **Signing** — Ed25519, or ECDSA on P-256. Verify with the algorithm you expect, never with the one named in the token.\n- **Key derivation** — HKDF from a high-entropy secret, argon2 or scrypt from a password.\n\n## Workflow\n\n1. State what you are protecting against.\n - Encryption at rest protects a stolen disk, not a compromised application. Naming the threat prevents solving the wrong problem.\n - **Done when:** the threat and what the mechanism does not cover are both written down.\n\n2. Never reuse a nonce with a key.\n - Nonce reuse in GCM is catastrophic — it reveals the plaintext relationship and the authentication key. Generate randomly, or use a counter that provably never repeats. XChaCha20's larger nonce makes random generation safe.\n - **Done when:** nonce generation is shown never to repeat for a given key.\n\n3. Plan key rotation from the start.\n - Store a key identifier with the ciphertext so old data stays readable while new data uses a new key. Retrofitting this requires decrypting everything.\n - **Done when:** ciphertext carries a key identifier.\n\n4. Keep keys out of the application.\n - A key management service or the platform keystore. A key in the environment is a key in every crash dump, log aggregator, and process listing.\n - **Done when:** no key material appears in source, config, or environment.\n\n5. Verify before you use.\n - Check the authentication tag or signature BEFORE decrypting or parsing. Acting on unverified data is the whole vulnerability.\n - **Done when:** every verification precedes every use.\n\n## Verification\n\n- No hand-rolled primitive exists.\n- Secret comparison is constant-time.\n- Nonce generation cannot repeat.\n- Keys are external to the application and rotatable.\n"
|
|
434
|
+
},
|
|
435
|
+
"dependency-supply-chain": {
|
|
436
|
+
"name": "dependency-supply-chain",
|
|
437
|
+
"description": "Add, update, or audit dependencies. Use when introducing a package, when an advisory appears, or when reviewing what a project pulls in.",
|
|
438
|
+
"content": "---\nname: dependency-supply-chain\ndescription: Add, update, or audit dependencies. Use when introducing a package, when an advisory appears, or when reviewing what a project pulls in.\n---\n\n# Dependency Supply Chain\n\n## The exposure\n\nA dependency runs with your process's full privileges. Its dependencies do too, and a typical project has hundreds it never chose. Most real compromises come through this path rather than through your own code.\n\n## Workflow\n\n1. Justify each direct dependency before adding it.\n - What does it do, how much does it weigh, how many transitive dependencies does it bring, when was it last released, how many maintainers. A one-function package with forty dependencies is a bad trade.\n - **Done when:** the addition is justified against writing it yourself.\n\n2. Check the package is the one you mean.\n - Typosquatting relies on a plausible name. Confirm the exact name, the repository link, and the download count. A brand-new package with a familiar name is the classic attack.\n - **Done when:** the package's repository and history are confirmed.\n\n3. Commit the lock file and install from it.\n - `npm ci`, `pip install -r` with hashes, `poetry install`, `cargo build --locked`. An install that resolves fresh versions in CI means the artefact you tested is not the artefact you shipped.\n - **Done when:** CI installs from the lock file and fails if it is out of date.\n\n4. Refuse install scripts you have not read.\n - A lifecycle script runs arbitrary code at install time, in your CI, with your credentials. Where the ecosystem allows disabling them by default, do.\n - **Done when:** the install-time script surface is known and minimised.\n\n5. Automate advisory scanning, and triage rather than ignore.\n - Run the ecosystem audit in CI. For each finding, decide: upgrade, replace, or accept with a written reason and an expiry. A permanently ignored advisory is a decision nobody revisits.\n - **Done when:** every open advisory has an action or a dated acceptance.\n\n6. Pin what you cannot verify.\n - Actions and containers by digest, not by tag. A mutable tag can be repointed after review.\n - **Done when:** CI actions and base images are pinned to digests.\n\n## Verification\n\n- The lock file is committed and CI installs from it.\n- The audit runs in CI and has no unreviewed findings.\n- Every CI action is pinned by digest.\n"
|
|
439
|
+
},
|
|
440
|
+
"input-validation-and-injection": {
|
|
441
|
+
"name": "input-validation-and-injection",
|
|
442
|
+
"description": "Handle data that came from a user, a request, or a third party. Use when building a query, a command, a template, or a path from external input.",
|
|
443
|
+
"content": "---\nname: input-validation-and-injection\ndescription: Handle data that came from a user, a request, or a third party. Use when building a query, a command, a template, or a path from external input.\n---\n\n# Input Validation and Injection\n\n## The single principle\n\nNever build a command out of a string containing untrusted data. Every injection class — SQL, shell, template, path, header, log — is the same mistake: data crossing into a position where it is read as instructions.\n\n## By interpreter\n\n- **SQL** — parameterised queries only. Bind the value; never interpolate. Identifiers that must be dynamic come from an allowlist, because they cannot be bound.\n- **Shell** — pass an argument array to the spawn API, never a joined string, and never through a shell. Where a shell is unavoidable, an allowlist of complete commands is the only safe form.\n- **Path** — resolve, then verify the result is inside the permitted root. `../` traversal survives naive prefix checks and survives decoding twice.\n- **HTML** — contextual escaping, applied by the template engine. Never build markup by concatenation. Untrusted markup goes through a sanitiser with an allowlist.\n- **Headers and logs** — strip carriage returns and newlines. A newline in a header splits the response; in a log it forges an entry.\n- **Deserialisation** — never deserialise untrusted data into arbitrary types. Parse into a declared schema.\n\n## Workflow\n\n1. Map every entry point.\n - Request bodies, query strings, headers, cookies, uploads, webhooks, environment, third-party API responses, and data read back from your own database that was written by a user.\n - **Done when:** every source of external data is listed.\n\n2. Validate on the way in, against a schema.\n - Type, range, length, format, and an allowlist where the set is finite. Reject rather than sanitise: sanitising guesses at intent and is bypassable.\n - **Done when:** every entry point parses into a validated type before use.\n\n3. Escape on the way out, per context.\n - The same value needs different escaping in HTML, in an attribute, in JavaScript, in a URL. One \"escape\" function applied everywhere is wrong in at least three of those.\n - **Done when:** escaping is applied by the sink, matched to the sink.\n\n4. Enforce size limits everywhere.\n - Body size, field length, array length, upload size, and nesting depth. Without them, one request exhausts memory.\n - **Done when:** every unbounded input has a stated limit.\n\n## Verification\n\n- No query, command, or path is built by string concatenation with external data.\n- Traversal payloads are rejected.\n- A newline in a header value does not split the response.\n- Oversized input is rejected before allocation.\n"
|
|
444
|
+
},
|
|
445
|
+
"secure-file-uploads": {
|
|
446
|
+
"name": "secure-file-uploads",
|
|
447
|
+
"description": "Accept file uploads from users. Use when adding an upload feature, or when uploaded files are served back to users.",
|
|
448
|
+
"content": "---\nname: secure-file-uploads\ndescription: Accept file uploads from users. Use when adding an upload feature, or when uploaded files are served back to users.\n---\n\n# Secure File Uploads\n\n## Why this is its own topic\n\nAn upload endpoint lets a stranger place a file of their choosing on your infrastructure. The failure modes span storage exhaustion, remote code execution, stored cross-site scripting, and server-side request forgery — and the naive implementation hits several of them.\n\n## Workflow\n\n1. Limit size before reading.\n - Enforce at the reverse proxy AND in the application. Streaming a body into memory to check its length is already the denial of service.\n - **Done when:** an oversized upload is rejected before the body is buffered.\n\n2. Determine the type from the content, not the name.\n - Read the magic bytes. The extension and the declared content type are both attacker-controlled. Validate against an allowlist of types you actually support — never a denylist.\n - **Done when:** a file renamed to a permitted extension is rejected on its real content.\n\n3. Never use the supplied filename.\n - Generate your own identifier and store the original name only as a display label. The supplied name can contain traversal sequences, null bytes, or a name that means something to the filesystem.\n - **Done when:** the stored path is derived entirely from server-generated values.\n\n4. Store outside the web root, and serve deliberately.\n - Files in a served directory can be requested directly, which turns an uploaded script into an executed one. Store in object storage or outside the root, and serve through a handler that sets the content type and `Content-Disposition: attachment`, plus `X-Content-Type-Options: nosniff`.\n - **Done when:** an uploaded HTML file downloads rather than renders.\n\n5. Serve user content from a separate origin.\n - Same-origin user content means a served file can script against your session. A distinct domain contains it.\n - **Done when:** user uploads are served from a domain that holds no session.\n\n6. Process images out-of-process and re-encode.\n - Image parsers are a large attack surface. Re-encoding strips embedded payloads and metadata, including location data users did not intend to share.\n - **Done when:** stored images are re-encoded and stripped of metadata.\n\n7. Scan, and quarantine until clean.\n - **Done when:** a file is unreachable until it has passed scanning.\n\n## Verification\n\n- An oversized upload is rejected early.\n- A renamed executable is rejected on content inspection.\n- An uploaded HTML file downloads rather than executes.\n- Uploaded images carry no location metadata.\n"
|
|
449
|
+
},
|
|
450
|
+
"security-headers-and-csp": {
|
|
451
|
+
"name": "security-headers-and-csp",
|
|
452
|
+
"description": "Configure HTTP security headers or a Content Security Policy. Use when hardening a web application, or when a security scan reports missing headers.",
|
|
453
|
+
"content": "---\nname: security-headers-and-csp\ndescription: Configure HTTP security headers or a Content Security Policy. Use when hardening a web application, or when a security scan reports missing headers.\n---\n\n# Security Headers and CSP\n\n## The headers that matter, in order of value\n\n1. **Content-Security-Policy** — the only one that meaningfully mitigates cross-site scripting. Also the only one requiring real work.\n2. **Strict-Transport-Security** — forces https for a long max-age. Include subdomains once you are sure.\n3. **X-Content-Type-Options: nosniff** — stops the browser guessing a content type, which is how an uploaded text file becomes a script.\n4. **Referrer-Policy: strict-origin-when-cross-origin** — stops paths and query strings leaking to third parties.\n5. **X-Frame-Options / frame-ancestors** — clickjacking. Prefer the CSP directive.\n6. **Permissions-Policy** — deny camera, microphone and geolocation unless used.\n\n## Workflow\n\n1. Deploy CSP in report-only mode first.\n - A strict policy applied directly will break the site. Report-only collects violations without enforcing, and the report tells you what the site actually depends on.\n - **Done when:** a week of reports has been collected and reviewed.\n\n2. Build the policy from a nonce, not from an allowlist.\n - Host allowlists are routinely bypassable through a permitted CDN. A per-response nonce with `strict-dynamic` is the current guidance and is far more robust.\n - **Done when:** the policy uses a per-response nonce and no host wildcard.\n\n3. Remove what the policy forbids, rather than weakening the policy.\n - Inline handlers and inline styles are what force `unsafe-inline`, and `unsafe-inline` removes most of the protection. Move them to files or nonce them.\n - **Done when:** the policy contains no `unsafe-inline` and no `unsafe-eval`.\n\n4. Keep a reporting endpoint after enforcement.\n - Violations in production are either a bug you introduced or an injection attempt. Both are worth seeing.\n - **Done when:** violations continue to be collected after enforcement begins.\n\n5. Set the headers at one layer.\n - The reverse proxy or a single middleware. Headers set in several places conflict, and the browser takes the most restrictive — usually not the one you were reading.\n - **Done when:** each header is emitted exactly once.\n\n## Verification\n\n- An observatory or header scan reports no missing header.\n- The CSP contains no `unsafe-inline`, no `unsafe-eval`, and no wildcard host.\n- An injected inline script is blocked and reported.\n- Every response carries each header exactly once.\n"
|
|
454
|
+
},
|
|
455
|
+
"threat-modelling": {
|
|
456
|
+
"name": "threat-modelling",
|
|
457
|
+
"description": "Assess the security of a feature or system. Use before building anything that handles money, personal data, or authentication, and during a security review.",
|
|
458
|
+
"content": "---\nname: threat-modelling\ndescription: Assess the security of a feature or system. Use before building anything that handles money, personal data, or authentication, and during a security review.\n---\n\n# Threat Modelling\n\n## The four questions\n\nWhat are we building, what can go wrong, what will we do about it, and did we do a good job. Everything else is elaboration.\n\n## Workflow\n\n1. Draw the data flow.\n - Components, data stores, external entities, and the trust boundaries between them. A boundary is anywhere data crosses from less trusted to more trusted — that is where controls belong and where they are usually missing.\n - **Done when:** every trust boundary in the feature is marked.\n\n2. Enumerate threats per boundary.\n - Walk the categories: spoofing identity, tampering with data, repudiation, information disclosure, denial of service, elevation of privilege. Apply each to each boundary rather than brainstorming freely — free brainstorming misses whole categories.\n - **Done when:** every boundary has been walked through all six.\n\n3. Rank by realistic impact and likelihood.\n - Not everything gets fixed. Say which are accepted and why, and record the acceptance.\n - **Done when:** each threat is mitigated, transferred, or accepted in writing.\n\n4. Name a specific control per mitigated threat.\n - \"Validate input\" is not a control. \"Reject requests whose body exceeds 1MB at the reverse proxy\" is.\n - **Done when:** every mitigation names a concrete, testable mechanism.\n\n5. Test the controls.\n - A control nobody attempted to bypass is a hypothesis. Write the test that tries.\n - **Done when:** each control has a test attempting to defeat it.\n\n## The threats people forget\n\n- The admin interface, which usually has weaker controls than the user-facing one.\n- The background job, which often runs with elevated privilege and no authorization check.\n- The error path, which frequently leaks internal detail.\n- The third-party integration, which is trusted implicitly.\n- The recovery flow, which is frequently weaker than the login it can bypass.\n\n## Verification\n\n- A diagram exists with boundaries marked.\n- Every boundary has been walked through all six categories.\n- Every mitigation names a mechanism and has a test.\n"
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
"testing": {
|
|
462
|
+
"end-to-end-test-stability": {
|
|
463
|
+
"name": "end-to-end-test-stability",
|
|
464
|
+
"description": "Write or fix browser or device end-to-end tests. Use when tests fail intermittently, when they rely on sleeps, or when a suite is disabled because it is unreliable.",
|
|
465
|
+
"content": "---\nname: end-to-end-test-stability\ndescription: Write or fix browser or device end-to-end tests. Use when tests fail intermittently, when they rely on sleeps, or when a suite is disabled because it is unreliable.\n---\n\n# End-to-End Test Stability\n\n## Where flakiness comes from\n\nAlmost always timing: the test acts before the application is ready. Fixed sleeps do not fix this — they make it rarer and slower, which is the worst of both.\n\n## Workflow\n\n1. Replace every sleep with a condition.\n - Wait for the element to be visible and enabled, for the request to complete, for the loading state to disappear. Modern frameworks auto-wait on assertions; use them rather than hand-rolled polling.\n - **Done when:** the suite contains no fixed-duration sleep.\n\n2. Select by what the user perceives.\n - Role and accessible name, then visible text, then a dedicated test identifier. Never a CSS class or an XPath through the DOM structure — those break on every styling change and test nothing meaningful.\n - **Done when:** no selector depends on styling or DOM nesting.\n\n3. Isolate each test's data.\n - Every test creates what it needs with unique values and cleans up. Shared fixtures mean tests interfere and cannot run in parallel — and the failure appears in whichever test ran second.\n - **Done when:** the suite passes with tests run in a random order and in parallel.\n\n4. Control the network.\n - For anything but a deliberate integration test, intercept and stub external calls. A third party's rate limit or outage should not fail your build.\n - **Done when:** the suite passes with external network access blocked.\n\n5. Capture evidence on failure.\n - Screenshot, DOM snapshot, video, and network log. A CI failure with no artefact costs an hour of local reproduction, and some failures do not reproduce locally at all.\n - **Done when:** a failing run leaves artefacts sufficient to diagnose without re-running.\n\n6. Keep the suite small and critical.\n - Cover the handful of journeys whose breakage would be an emergency. An end-to-end test for every form is a suite nobody waits for.\n - **Done when:** every end-to-end test covers a journey that would be an incident if broken.\n\n## Verification\n\n- Twenty consecutive runs pass without a retry.\n- The suite passes in a random order and in parallel.\n- A deliberate failure leaves a screenshot and a network log.\n"
|
|
466
|
+
},
|
|
467
|
+
"property-based-testing": {
|
|
468
|
+
"name": "property-based-testing",
|
|
469
|
+
"description": "Test a function with many valid inputs. Use when example tests keep missing edge cases, when testing a parser, serialiser, or algorithm, or when input space is large.",
|
|
470
|
+
"content": "---\nname: property-based-testing\ndescription: Test a function with many valid inputs. Use when example tests keep missing edge cases, when testing a parser, serialiser, or algorithm, or when input space is large.\n---\n\n# Property-Based Testing\n\n## The shift\n\nInstead of \"for this input, expect this output\", state a property that must hold for EVERY input, and let the framework generate hundreds of them — including the pathological ones nobody thinks to write: empty, one element, duplicates, maximum integers, unusual Unicode.\n\n## The properties worth reaching for\n\n- **Round trip** — `decode(encode(x)) == x`. The single most valuable property, and it applies to every serialiser, parser and codec.\n- **Invariant** — sorting preserves length and multiset; a transfer preserves the total balance.\n- **Idempotence** — `f(f(x)) == f(x)`. Normalisers, formatters, deduplicators.\n- **Oracle** — the fast implementation agrees with the obviously correct slow one.\n- **Commutativity or ordering** — order of independent operations does not change the result.\n\n## Workflow\n\n1. Pick the property before the generator.\n - Write the invariant as a sentence first. A generator without a property is just random input.\n - **Done when:** the property is stated in one sentence.\n\n2. Generate the real input domain.\n - Constrain the generator to what the function actually accepts, but no further. Over-constraining hides the bugs; the framework's default string generator producing surrogate pairs and combining characters is a feature, not a nuisance.\n - **Done when:** the generator covers the documented domain and nothing outside it.\n\n3. Use the shrunk counterexample.\n - On failure the framework reduces the input to a minimal failing case. That minimal case is usually the whole diagnosis.\n - **Done when:** the reported failure is minimal and reproducible from its seed.\n\n4. Keep the failing case as a regression test.\n - Add the specific counterexample as an ordinary example test. Property tests are randomised; a pinned example guarantees this exact bug stays fixed.\n - **Done when:** each discovered failure has a pinned example test.\n\n5. Combine, do not replace.\n - Properties catch classes of bugs; examples document intent and pin known cases. A suite needs both.\n - **Done when:** the module has both property and example tests.\n\n## Verification\n\n- Each property runs several hundred cases in CI.\n- A deliberately introduced off-by-one is caught.\n- Every past failure has a pinned regression test.\n"
|
|
471
|
+
},
|
|
472
|
+
"regression-test-from-a-bug": {
|
|
473
|
+
"name": "regression-test-from-a-bug",
|
|
474
|
+
"description": "Fix a reported bug. Use whenever a defect is being fixed, before writing the fix itself.",
|
|
475
|
+
"content": "---\nname: regression-test-from-a-bug\ndescription: Fix a reported bug. Use whenever a defect is being fixed, before writing the fix itself.\n---\n\n# Regression Test from a Bug\n\n## The order\n\nTest first, then fix. A test written after the fix passes immediately, and you never learn whether it would have caught the bug. This is the single highest-value testing discipline and the most commonly skipped.\n\n## Workflow\n\n1. Reproduce it before you understand it.\n - Get the exact inputs, state and steps. A bug you cannot reproduce cannot be verified as fixed, and \"I could not reproduce it\" is not a resolution.\n - **Done when:** you can trigger the bug on demand.\n\n2. Write a failing test at the lowest level that captures it.\n - Reduce the reproduction to the smallest unit that still fails. A bug reproduced through the whole UI is usually a unit-level bug once narrowed.\n - **Done when:** the test fails, and it fails with the same symptom the user reported.\n\n3. Understand the cause before changing anything.\n - The first plausible explanation is often wrong. Confirm it by predicting a second observable consequence and checking for it.\n - **Done when:** you can explain why the code produced this exact wrong result.\n\n4. Fix the cause, not the symptom.\n - A null check that suppresses a crash leaves the wrong value flowing through the system. Ask where the null came from.\n - **Done when:** the fix addresses where the wrong value originated.\n\n5. Look for siblings.\n - The same mistake is usually made in more than one place. Search for the pattern before closing.\n - **Done when:** the codebase has been searched for the same shape and the results addressed.\n\n6. Confirm the test now passes and the rest still does.\n - **Done when:** the new test is green, the suite is green, and reverting the fix turns the new test red.\n\n## Verification\n\n- The test failed before the fix and passes after.\n- Reverting the fix makes it fail again.\n- A search for the same pattern elsewhere has been done and reported.\n"
|
|
476
|
+
},
|
|
477
|
+
"snapshot-and-visual-regression": {
|
|
478
|
+
"name": "snapshot-and-visual-regression",
|
|
479
|
+
"description": "Use snapshot or visual regression tests. Use when UI changes break silently, when snapshots are updated without review, or when a large snapshot file has appeared.",
|
|
480
|
+
"content": "---\nname: snapshot-and-visual-regression\ndescription: Use snapshot or visual regression tests. Use when UI changes break silently, when snapshots are updated without review, or when a large snapshot file has appeared.\n---\n\n# Snapshot and Visual Regression Testing\n\n## The failure mode of snapshots\n\nThey are trivial to update. When a snapshot fails, the fastest action is to accept the new one, and that becomes reflexive — at which point the snapshot records what the code does rather than what it should do, and catches nothing.\n\n## Workflow\n\n1. Snapshot small, meaningful units.\n - A component's rendered output, a serialised payload, a formatted error. Never a whole page — a large snapshot fails on every unrelated change and is never actually read.\n - **Done when:** every snapshot is small enough to review in a diff.\n\n2. Review every snapshot change deliberately.\n - The diff is the test result. Accepting it without reading is the same as deleting the test.\n - **Done when:** each snapshot change in a review has a stated reason.\n\n3. Remove non-determinism before snapshotting.\n - Timestamps, identifiers, ordering, and floating-point formatting. Replace them with stable placeholders — otherwise the snapshot fails randomly and gets updated reflexively.\n - **Done when:** ten consecutive runs produce identical snapshots.\n\n4. Render visual tests in a controlled environment.\n - A container with pinned fonts and a fixed device pixel ratio. Font rendering differs between operating systems, so screenshots taken on a laptop will never match CI.\n - **Done when:** the same commit produces identical screenshots locally and in CI.\n\n5. Set a sensible pixel tolerance.\n - Zero tolerance flags anti-aliasing noise. Too high hides real regressions. Tune it against a known-good pair.\n - **Done when:** the threshold catches a one-pixel layout shift but ignores anti-aliasing.\n\n6. Prefer explicit assertions where they are clearer.\n - \"The submit button is disabled\" is a better test than a snapshot containing a disabled attribute somewhere. Reach for snapshots when the output is large and the shape is the point.\n - **Done when:** behavioural rules are asserted explicitly, not implied by a snapshot.\n\n## Verification\n\n- Ten runs produce no spurious diff.\n- Local and CI screenshots match on the same commit.\n- Each snapshot is small enough to review.\n"
|
|
481
|
+
},
|
|
482
|
+
"test-doubles-and-mocking": {
|
|
483
|
+
"name": "test-doubles-and-mocking",
|
|
484
|
+
"description": "Use mocks, stubs, or fakes in tests. Use when a test needs an external dependency, when mocks make tests brittle, or when a suite mocks its own internals.",
|
|
485
|
+
"content": "---\nname: test-doubles-and-mocking\ndescription: Use mocks, stubs, or fakes in tests. Use when a test needs an external dependency, when mocks make tests brittle, or when a suite mocks its own internals.\n---\n\n# Test Doubles\n\n## The kinds, and when each is right\n\n- **Stub** — returns canned answers. For queries: \"this returns three users.\"\n- **Fake** — a working lightweight implementation. An in-memory repository. Usually the best of the four.\n- **Mock** — asserts it was called in a particular way. For commands with no observable result: \"an email was sent.\"\n- **Spy** — records calls for later inspection.\n\nPrefer a fake. It exercises real behaviour and does not encode the caller's implementation into the test.\n\n## Workflow\n\n1. Mock only at real boundaries.\n - Network, filesystem, clock, randomness, and third-party services. Mocking your own modules couples the test to the current internal structure — the thing you most want to be free to change.\n - **Done when:** every double replaces something outside the unit under test.\n\n2. Never mock what you can control cheaply.\n - A containerised database is faster to set up and catches far more than a mocked repository. Same for a local HTTP server versus a mocked client.\n - **Done when:** slow-and-external is the only justification for each double.\n\n3. Make time and randomness injectable.\n - Pass a clock and a source of randomness rather than calling the global. This removes the need to mock a global, which is the most fragile kind of double.\n - **Done when:** no test needs to patch a global time or random function.\n\n4. Keep the double honest.\n - A stub returning a shape the real dependency never returns produces a green test and a broken system. Where the language allows, type the double against the real interface, and add one contract test against the real dependency.\n - **Done when:** at least one test exercises the real dependency for each mocked boundary.\n\n5. Assert interactions sparingly.\n - Verify a call only when the call IS the outcome. Verifying every internal call turns the test into a transcript of the implementation.\n - **Done when:** interaction assertions exist only for side effects with no observable result.\n\n## Verification\n\n- Each double replaces a genuine external boundary.\n- A contract test covers each mocked dependency against the real thing.\n- Refactoring internals does not require changing doubles.\n"
|
|
486
|
+
},
|
|
487
|
+
"test-strategy-and-pyramid": {
|
|
488
|
+
"name": "test-strategy-and-pyramid",
|
|
489
|
+
"description": "Decide what to test and at what level. Use when a suite is slow and flaky, when coverage is high but bugs still ship, or when starting testing on an untested codebase.",
|
|
490
|
+
"content": "---\nname: test-strategy-and-pyramid\ndescription: Decide what to test and at what level. Use when a suite is slow and flaky, when coverage is high but bugs still ship, or when starting testing on an untested codebase.\n---\n\n# Test Strategy\n\n## The proportions\n\nMany fast unit tests, fewer integration tests, very few end-to-end tests. Inverting this gives a suite that is slow, flaky, and gives its feedback long after the mistake was made. But note the real goal: CONFIDENCE per second of runtime. A pure unit suite that mocks every boundary can pass while the system is entirely broken.\n\n## Workflow\n\n1. Test behaviour at the boundary the user cares about.\n - For a library, that is its public API. For a service, its HTTP surface. For a UI, what a user can see and do. Testing internals locks the implementation and makes refactoring a test rewrite.\n - **Done when:** the tests still pass after an internal refactor.\n\n2. Put each test at the lowest level that gives real confidence.\n - Pure logic: unit. A query, a serialiser, an auth check: integration against a real dependency. A critical user journey: end-to-end, and only a handful of them.\n - **Done when:** every test's level is justified by what it would catch.\n\n3. Use real dependencies where they are cheap.\n - A containerised database in an integration test catches what a mocked repository never will — wrong SQL, a missing index, a constraint violation. Mock only what is slow, external, or non-deterministic.\n - **Done when:** database and serialisation code is tested against the real thing.\n\n4. Treat coverage as a diagnostic, not a target.\n - Coverage finds untested code. It cannot tell whether the tests assert anything meaningful. A coverage target produces tests written to satisfy the target.\n - **Done when:** coverage is reported and used to find gaps, not enforced as a gate on its own.\n\n5. Delete flaky tests or fix them the same day.\n - A tolerated flaky test teaches the team to re-run rather than to read. That habit is what lets a real failure ship.\n - **Done when:** the suite has no known intermittent failures.\n\n## Verification\n\n- The unit suite runs in seconds.\n- The full suite runs in minutes.\n- Ten consecutive runs give identical results.\n- An internal refactor requires no test changes.\n"
|
|
491
|
+
},
|
|
492
|
+
"writing-good-assertions": {
|
|
493
|
+
"name": "writing-good-assertions",
|
|
494
|
+
"description": "Write or improve test assertions. Use when a failure message does not say what broke, when tests assert on implementation details, or when a test passes with any input.",
|
|
495
|
+
"content": "---\nname: writing-good-assertions\ndescription: Write or improve test assertions. Use when a failure message does not say what broke, when tests assert on implementation details, or when a test passes with any input.\n---\n\n# Writing Good Assertions\n\n## What a failing test owes you\n\nThe failure message alone should tell you what broke and why, without opening the test. `expected true to be false` fails that; it costs a debugging session to learn what the test was even checking.\n\n## Workflow\n\n1. Assert on the outcome, not on the steps.\n - Assert what the function returned and what changed in the world. Asserting that an internal helper was called with particular arguments tests the implementation, and breaks on every refactor while catching no bugs.\n - **Done when:** no assertion mentions a private function or an internal call count.\n\n2. Compare whole values, not field by field.\n - One deep equality on the expected object gives a diff. Ten separate field assertions stop at the first and hide the rest.\n - **Done when:** structured results are asserted as a whole.\n\n3. Make the test name the specification.\n - `rejects a token that expired one second ago`, not `test auth 3`. The name plus the failure should be enough to act on.\n - **Done when:** every test name is a sentence about behaviour.\n\n4. Assert the negative space too.\n - What must NOT happen: no email sent, no second write, nothing logged at error level, no other field changed. Most regressions are unintended extra effects, and only a negative assertion catches them.\n - **Done when:** effect-producing code has at least one assertion about what did not happen.\n\n5. Prove the test can fail.\n - Break the implementation deliberately and confirm the test goes red. A test that passes against broken code is worse than no test, because it is believed.\n - **Done when:** each new test has been seen to fail for the right reason.\n\n6. Avoid conditionals in tests.\n - An `if` in a test means it asserts different things in different runs, and it can silently assert nothing. Split it into separate tests or parametrise it.\n - **Done when:** no test branches on runtime values.\n\n## Verification\n\n- Every new test has been observed failing against a deliberate break.\n- No assertion references a private implementation detail.\n- Failure output identifies the problem without opening the test file.\n"
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
"web": {
|
|
499
|
+
"accessible-components": {
|
|
500
|
+
"name": "accessible-components",
|
|
501
|
+
"description": "Build or audit an interactive UI component — modal, dropdown, tabs, menu, form. Use when a component is built from divs, when it cannot be operated by keyboard, or when accessibility is being reviewed.",
|
|
502
|
+
"content": "---\nname: accessible-components\ndescription: Build or audit an interactive UI component — modal, dropdown, tabs, menu, form. Use when a component is built from divs, when it cannot be operated by keyboard, or when accessibility is being reviewed.\n---\n\n# Accessible Components\n\n## The first rule\n\nUse the native element. `<button>`, `<a href>`, `<details>`, `<dialog>`, `<input type=\"checkbox\">`. Every one of them brings focus handling, keyboard behaviour, and screen-reader semantics that a div plus ARIA has to reimplement, usually incompletely. Reach for ARIA only when no native element exists.\n\n## Workflow\n\n1. Audit by keyboard alone.\n - Unplug the mouse. Tab through the component. Can you reach everything, operate everything, and see where focus is?\n - **Done when:** every interactive element is reachable and operable, and the focus ring is always visible.\n\n2. Fix semantics before appearance.\n - A clickable div needs `role`, `tabindex`, and key handlers for Enter and Space. A button needs none of them. Convert rather than patch.\n - **Done when:** the accessibility tree in DevTools shows the right role for every control.\n\n3. Manage focus at boundaries.\n - Opening a modal moves focus into it and traps it there. Closing returns focus to the trigger. Without the return, keyboard users land at the top of the document.\n - **Done when:** opening and closing leaves focus exactly where a mouse user's attention would be.\n\n4. Name everything.\n - Every control has an accessible name: visible text, `aria-label`, or a `<label for>`. An icon-only button with no name is unusable by a screen reader.\n - **Done when:** no control in the accessibility tree has an empty name.\n\n5. Announce what changes.\n - Content that appears without a page change (validation errors, toasts, results counts) needs a live region, or it is silent.\n - **Done when:** a screen reader announces the change without the user hunting for it.\n\n## Per-component keyboard contracts\n\n- **Modal:** Escape closes, focus trapped, `role=\"dialog\"` `aria-modal=\"true\"`, labelled by its title.\n- **Tabs:** arrow keys move between tabs, Tab moves into the panel, `aria-selected` and `aria-controls` wired.\n- **Menu:** arrows move, Escape closes and returns focus, Home and End jump to the ends.\n- **Combobox:** arrows move through options, Enter selects, Escape reverts, `aria-activedescendant` tracks.\n\n## Verification\n\n- The whole flow is completable with the keyboard only.\n- `axe` or the browser's accessibility audit reports no violations.\n- Every control has a non-empty accessible name.\n"
|
|
503
|
+
},
|
|
504
|
+
"api-client-resilience": {
|
|
505
|
+
"name": "api-client-resilience",
|
|
506
|
+
"description": "Write client-side code that calls an API. Use when fetch calls have no error handling, no timeout, or no cancellation, or when a component fetches in an effect without cleanup.",
|
|
507
|
+
"content": "---\nname: api-client-resilience\ndescription: Write client-side code that calls an API. Use when fetch calls have no error handling, no timeout, or no cancellation, or when a component fetches in an effect without cleanup.\n---\n\n# API Client Resilience\n\n## What every network call needs\n\nA timeout, a cancellation path, an error branch, and a decision about retrying. A `fetch` with none of them will eventually hang a UI on a flaky network, and that failure is invisible in testing on a fast connection.\n\n## Workflow\n\n1. Give every request a timeout.\n - `fetch` has no default timeout — a request can hang indefinitely. Use `AbortSignal.timeout(ms)`, and combine it with a cancellation signal via `AbortSignal.any()`.\n - **Done when:** no request can outlive a stated deadline.\n\n2. Cancel on unmount and on supersession.\n - A request whose component has unmounted must be aborted, or it resolves into a dead component. A search-as-you-type must abort the previous request, or responses arrive out of order and the UI shows the wrong result.\n - **Done when:** rapid input produces one final result matching the last query.\n\n3. Treat a non-2xx response as an error explicitly.\n - `fetch` does not reject on 404 or 500. Check `response.ok` — code that omits this parses an error page as data.\n - **Done when:** every call checks `response.ok` before parsing.\n\n4. Retry only what is safe.\n - Idempotent requests (GET, PUT, DELETE) may retry with exponential backoff and jitter. A POST may not, unless it carries an idempotency key.\n - Never retry a 4xx: the request was wrong and will be wrong again.\n - **Done when:** the retry policy is stated per method, and 4xx is excluded.\n\n5. Prefer a server-cache library over hand-rolled effects.\n - TanStack Query or SWR already implement deduplication, caching, revalidation, retry and cancellation correctly. Hand-rolling them in `useEffect` reimplements the same four bugs each time.\n - **Done when:** either a cache library is used, or the four behaviours above are each implemented and tested.\n\n## Verification\n\n- A request against an unreachable host fails within the timeout rather than hanging.\n- Typing quickly into a search box leaves the result matching the final query.\n- A 500 response surfaces as an error state, not as parsed data.\n"
|
|
508
|
+
},
|
|
509
|
+
"bundle-size-reduction": {
|
|
510
|
+
"name": "bundle-size-reduction",
|
|
511
|
+
"description": "Reduce JavaScript or CSS bundle size. Use when a build warns about chunk size, when a page ships more than a few hundred kilobytes of script, or when load time is dominated by download and parse.",
|
|
512
|
+
"content": "---\nname: bundle-size-reduction\ndescription: Reduce JavaScript or CSS bundle size. Use when a build warns about chunk size, when a page ships more than a few hundred kilobytes of script, or when load time is dominated by download and parse.\n---\n\n# Bundle Size Reduction\n\n## Workflow\n\n1. Look at the bundle before guessing.\n - `rollup-plugin-visualizer`, `webpack-bundle-analyzer`, or `vite build --mode production` with a visualiser. One dependency is usually most of the weight.\n - **Done when:** you can name the three largest contributors and their sizes.\n\n2. Attack the largest one first.\n - Common offenders and their fixes:\n - A date library — use `Intl.DateTimeFormat` or a modular alternative.\n - A full icon set — import individual icons, never the barrel file.\n - `lodash` — import the single function path, or use the platform equivalent.\n - A charting or editor library — load it dynamically at the point of use.\n - **Done when:** the largest contributor has dropped measurably or is justified in writing.\n\n3. Split by route.\n - Dynamic `import()` at route boundaries. The landing page should not carry the admin dashboard's code.\n - **Done when:** navigating between routes loads separate chunks.\n\n4. Check tree-shaking is actually working.\n - It requires ES modules, `\"sideEffects\": false` in the dependency's manifest, and no barrel file re-exporting everything. A single CommonJS dependency in the chain can defeat it silently.\n - **Done when:** an unused export from a first-party module is absent from the output.\n\n5. Do not forget CSS and fonts.\n - Unused CSS from a utility framework needs the purge step configured against every template path. Subset fonts to the characters actually used.\n - **Done when:** the CSS bundle contains no selector the app cannot produce.\n\n## Verification\n\n- Report the before and after transferred size for the entry chunk.\n- The largest chunk is below the project's stated budget.\n- The app still works: run the test suite and load every route.\n"
|
|
513
|
+
},
|
|
514
|
+
"core-web-vitals": {
|
|
515
|
+
"name": "core-web-vitals",
|
|
516
|
+
"description": "Improve page load or interaction performance on the web. Use when a page feels slow, when Lighthouse scores are low, or before shipping a public-facing page.",
|
|
517
|
+
"content": "---\nname: core-web-vitals\ndescription: Improve page load or interaction performance on the web. Use when a page feels slow, when Lighthouse scores are low, or before shipping a public-facing page.\n---\n\n# Core Web Vitals\n\n## The three metrics, and what actually moves them\n\n- **LCP** (largest contentful paint) — how long until the main thing appears. Dominated by server response time and by whether the hero image or font is discoverable early.\n- **INP** (interaction to next paint) — how long the page takes to respond to input. Dominated by long tasks on the main thread.\n- **CLS** (cumulative layout shift) — how much things move. Dominated by images and ads without reserved space, and by fonts swapping.\n\n## Workflow\n\n1. Measure before changing anything.\n - Lab first (Lighthouse, DevTools performance panel), then field data if available. Lab numbers on a fast laptop are optimistic; throttle to a mid-tier device.\n - **Done when:** you have a baseline number for each of the three metrics.\n\n2. Fix LCP at the source.\n - Identify the LCP element in DevTools. It is almost always an image or a heading blocked by a font.\n - Preload it, set `fetchpriority=\"high\"`, serve modern formats, and size it correctly. Never lazy-load the LCP image — that is the single most common self-inflicted LCP regression.\n - **Done when:** the LCP element starts loading in the first network round trip.\n\n3. Fix CLS by reserving space.\n - Every image and video gets explicit `width` and `height` (or `aspect-ratio`). Every font uses `font-display: optional` or `swap` with a metric-matched fallback.\n - **Done when:** CLS is under 0.1 and the page does not visibly jump on reload.\n\n4. Fix INP by breaking up long tasks.\n - Find tasks over 50ms in the performance panel. Split them, defer them, or move them off the main thread.\n - Ship less JavaScript: code-split by route, drop polyfills for browsers you do not support, and audit the bundle for a single dependency dominating it.\n - **Done when:** no task on the interaction path exceeds 50ms.\n\n5. Re-measure.\n - **Done when:** each metric has improved against the recorded baseline, with the numbers stated.\n\n## Verification\n\n- Lighthouse on a throttled mid-tier profile shows LCP under 2.5s, INP under 200ms, CLS under 0.1.\n- The before and after numbers are reported side by side.\n"
|
|
518
|
+
},
|
|
519
|
+
"css-layout-systems": {
|
|
520
|
+
"name": "css-layout-systems",
|
|
521
|
+
"description": "Build a page or component layout in CSS. Use when a layout uses absolute positioning or floats, when it breaks at a viewport size, or when a container will not centre.",
|
|
522
|
+
"content": "---\nname: css-layout-systems\ndescription: Build a page or component layout in CSS. Use when a layout uses absolute positioning or floats, when it breaks at a viewport size, or when a container will not centre.\n---\n\n# CSS Layout Systems\n\n## Choosing between the two\n\n- **Flexbox** — one dimension. A row of items, a column of stacked sections, anything where content size should drive the layout.\n- **Grid** — two dimensions, or any layout where the CONTAINER should define the structure regardless of content. Page shells, card grids, forms with aligned labels.\n\nAbsolute positioning is for overlays only. Floats are for wrapping text around an image and nothing else.\n\n## Workflow\n\n1. Name the layout in one sentence before writing CSS.\n - \"A sidebar of fixed width and a main area that fills the rest\" is a grid. \"A row of buttons pushed to the right\" is flex. The sentence picks the tool.\n - **Done when:** the layout is described in words and the tool follows from it.\n\n2. Let the intrinsic sizing work.\n - `minmax()`, `auto-fit`, `clamp()` and `min-content` remove most media queries. A card grid that reflows with `repeat(auto-fit, minmax(16rem, 1fr))` needs no breakpoints at all.\n - **Done when:** the layout adapts across widths with as few explicit breakpoints as possible.\n\n3. Fix overflow at the source.\n - A grid or flex item defaults to `min-width: auto`, which refuses to shrink below its content. That is why a long word or a wide table breaks the page. Set `min-width: 0` on the item.\n - **Done when:** no horizontal scrollbar appears on the page body at any width.\n\n4. Use logical properties.\n - `margin-inline`, `padding-block`, `inset-inline-start`. They work in right-to-left languages without a second stylesheet.\n - **Done when:** the layout is correct with `dir=\"rtl\"` set on the document.\n\n5. Contain what scrolls.\n - Wide content — tables, code blocks, diagrams — scrolls inside its own `overflow-x: auto` container. The page body never scrolls sideways.\n - **Done when:** every wide element scrolls within itself.\n\n## Verification\n\n- No horizontal page scroll from 320px to 2560px.\n- `dir=\"rtl\"` produces a mirrored, correct layout.\n- Zooming text to 200% does not clip or overlap content.\n"
|
|
523
|
+
},
|
|
524
|
+
"form-validation-ux": {
|
|
525
|
+
"name": "form-validation-ux",
|
|
526
|
+
"description": "Build or fix a form. Use when validation fires on every keystroke, when errors are not associated with fields, or when a submit can be double-fired.",
|
|
527
|
+
"content": "---\nname: form-validation-ux\ndescription: Build or fix a form. Use when validation fires on every keystroke, when errors are not associated with fields, or when a submit can be double-fired.\n---\n\n# Form Validation UX\n\n## Timing is the whole design\n\nValidate a field when the user LEAVES it, not while they are typing. Validating on every keystroke tells someone their email is invalid after the first character, which is both true and useless. Once a field has errored, re-validate on change so the error clears as soon as it is fixed.\n\n## Workflow\n\n1. Validate on the server regardless.\n - Client validation is a convenience. Anything that matters is enforced server-side, because the client is under the user's control.\n - **Done when:** every rule exists on the server, whether or not it also exists on the client.\n\n2. Share one schema.\n - Define the rules once (Zod, Valibot, a JSON schema) and use them on both sides. Two hand-written copies drift, and the drift shows up as a form that submits and then fails.\n - **Done when:** changing a rule in one place changes both sides.\n\n3. Wire errors to fields accessibly.\n - `aria-invalid` on the input, `aria-describedby` pointing at the message, and the message in a live region. An error rendered near the field but not associated with it is invisible to a screen reader.\n - **Done when:** a screen reader announces the error when focus enters the field.\n\n4. Make submission safe.\n - Disable the submit while in flight and guard on the server with an idempotency key. A double-click on a slow network creates two orders.\n - **Done when:** double-submitting produces one result.\n\n5. Never lose what was typed.\n - On a failed submit, keep every value and focus the first invalid field. Clearing a form on error is the fastest way to lose a user.\n - **Done when:** a failed submit preserves all input and moves focus to the first problem.\n\n## Verification\n\n- Submitting empty shows all errors at once, with focus on the first.\n- Fixing a field clears its error immediately.\n- Double-clicking submit produces one server-side effect.\n- The whole form is completable by keyboard.\n"
|
|
528
|
+
},
|
|
529
|
+
"react-state-architecture": {
|
|
530
|
+
"name": "react-state-architecture",
|
|
531
|
+
"description": "Choose where state lives in a React application. Use when props are threaded through many layers, when a context re-renders the whole tree, or when a global store is being added.",
|
|
532
|
+
"content": "---\nname: react-state-architecture\ndescription: Choose where state lives in a React application. Use when props are threaded through many layers, when a context re-renders the whole tree, or when a global store is being added.\n---\n\n# React State Architecture\n\n## The ordering rule\n\nState belongs at the lowest level that can own it. Every level it is lifted above that is a re-render nobody asked for. Reach for the next tier only when the current one provably fails.\n\n1. Local `useState` — owned by one component.\n2. Lifted to the nearest common parent — shared by siblings.\n3. URL — anything the user should be able to link to, refresh into, or navigate back to. Filters, tabs, pagination, selected item.\n4. Server cache (TanStack Query, SWR, RSC) — anything that came from the network. This is not application state; it is a cache with staleness, and treating it as state is the single most common source of duplicate fetching and stale UI.\n5. Context — genuinely global and rarely changing: theme, locale, the authenticated user.\n6. A store (Zustand, Redux) — high-frequency global state with complex updates. Rare.\n\n## Workflow\n\n1. Classify each piece of state against the tiers above.\n - The question that resolves most cases: does this come from the server? If yes, it belongs in the server cache, not in `useState` with a `useEffect`.\n - **Done when:** every piece of state is assigned to a tier with a stated reason.\n\n2. Move URL-shaped state to the URL.\n - Search params via the router. This gets back-button and shareability for free, both of which are otherwise bugs users will report.\n - **Done when:** reloading the page restores the same view.\n\n3. Split contexts by update frequency.\n - One context holding both the theme and a live cursor position re-renders every consumer on every mouse move. Separate them.\n - **Done when:** no context holds values with different change rates.\n\n4. Derive instead of storing.\n - A value computable from other state must not be stored. Two sources of truth drift, and the drift is a bug class you cannot test away.\n - **Done when:** no `useEffect` exists whose only job is to sync one state value from another.\n\n## Verification\n\n- The React DevTools profiler shows no component re-rendering on an update it does not display.\n- Reloading any view restores it exactly.\n- Searching for `useEffect` finds none that only call `setState` from other state.\n"
|
|
533
|
+
},
|
|
534
|
+
"ssr-hydration-correctness": {
|
|
535
|
+
"name": "ssr-hydration-correctness",
|
|
536
|
+
"description": "Debug or prevent a server-rendering hydration mismatch. Use when the console warns about hydration, when content flashes and changes on load, or when a component behaves differently on first render.",
|
|
537
|
+
"content": "---\nname: ssr-hydration-correctness\ndescription: Debug or prevent a server-rendering hydration mismatch. Use when the console warns about hydration, when content flashes and changes on load, or when a component behaves differently on first render.\n---\n\n# SSR Hydration Correctness\n\n## What a mismatch means\n\nThe server produced one tree and the client produced a different one from the same props. The framework has to discard its work and re-render, which costs the performance the server render was supposed to buy — and in the worst case leaves the DOM and the component state disagreeing.\n\n## The four causes, in order of frequency\n\n1. **Non-deterministic values.** `Date.now()`, `Math.random()`, `crypto.randomUUID()` produce different output on each side.\n2. **Browser-only APIs read during render.** `window`, `document`, `localStorage`, `matchMedia`. These are undefined on the server, so the branch differs.\n3. **Locale or timezone formatting.** The server formats in UTC and the client in the user's zone.\n4. **Invalid HTML nesting.** A `<div>` inside a `<p>`, or a `<td>` outside a row. The browser repairs the DOM, so the client tree no longer matches what was sent.\n\n## Workflow\n\n1. Read the mismatch message for the specific element.\n - Modern frameworks name the node and show both trees. That usually identifies the cause outright.\n - **Done when:** you can point at the exact element and value that differ.\n\n2. Move non-deterministic work out of render.\n - Compute it on the server and pass it as a prop, or compute it in an effect after mount and accept one frame of the fallback.\n - **Done when:** render is a pure function of props on both sides.\n\n3. Gate browser-only reads behind mount.\n - Read the value in an effect and store it in state, rather than reading it during render. The first render then matches the server by construction.\n - **Done when:** no browser global is read during render.\n\n4. Fix formatting by fixing the inputs.\n - Send an ISO timestamp and an explicit timezone; format identically on both sides. Do not paper over it with a suppression attribute — that hides the mismatch without removing it.\n - **Done when:** the same input formats to the same string on both sides.\n\n5. Validate the HTML.\n - Run the server output through a validator. Nesting errors produce mismatches that look inexplicable at the component level.\n - **Done when:** the server HTML validates.\n\n## Verification\n\n- No hydration warning in the console on any route.\n- No visible flash of changed content on load.\n- Disabling JavaScript still shows correct server-rendered content.\n"
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
};
|