Ultra-Sharp Agent Skills: research-first authoring system + 72 skills
Research synthesis (15 principles, Sharp Skill Checklist, SKILL.md template), 2 validated example skills, 7 collections (documents, frontend design, databases, backend, writing, US/CA tax & accounting), a stdlib-only linter enforcing the checklist repo-wide, and a validation report with real execution evidence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
161 changed files +14,370 −0
added
.gitignore
+2 −0
@@ -0,0 +1,2 @@ | |||||
| 1 | +.claude/ | ||||
| 2 | +.DS_Store | ||||
added
CLAUDE.md
+103 −0
@@ -0,0 +1,103 @@ | |||||
| 1 | +<!-- | ||||
| 2 | +Author: Simon-Pierre Boucher | ||||
| 3 | +Contact: contact@spboucher.ai | ||||
| 4 | +--> | ||||
| 5 | + | ||||
| 6 | +# CLAUDE.md — Skill Authoring Protocol (Research-First, Ultra-Sharp Skills) | ||||
| 7 | + | ||||
| 8 | +**Author:** Simon-Pierre Boucher | ||||
| 9 | +**Contact:** contact@spboucher.ai | ||||
| 10 | + | ||||
| 11 | +--- | ||||
| 12 | + | ||||
| 13 | +## Mission | ||||
| 14 | + | ||||
| 15 | +You (Claude) will become an expert in writing **skills for AI agents** — skills that are *ultra sharp* and *fine-pointed*: minimal surface, maximal precision, zero ambiguity, perfect triggering. You will do this in two mandatory phases: **(1) Ultra-intensive web research**, then **(2) Build 2 example skills in English to validate what you learned.** | ||||
| 16 | + | ||||
| 17 | +Do NOT skip Phase 1. Do NOT write any skill before the research is complete and synthesized. | ||||
| 18 | + | ||||
| 19 | +--- | ||||
| 20 | + | ||||
| 21 | +## Phase 1 — Ultra-Intensive Web Research (MANDATORY FIRST) | ||||
| 22 | + | ||||
| 23 | +Perform a deep, exhaustive web research campaign (minimum 10–15 distinct searches + page fetches) on how to write high-quality skills for AI agents. Cover ALL of the following angles: | ||||
| 24 | + | ||||
| 25 | +1. **Official Anthropic documentation on Agent Skills** — SKILL.md format, YAML frontmatter (`name`, `description`), progressive disclosure (metadata → body → bundled resources), folder anatomy (`scripts/`, `references/`, `assets/`). | ||||
| 26 | +2. **Skill triggering mechanics** — how descriptions drive activation, why skills under-trigger, how to write "pushy" but precise descriptions that fire on the right user phrases and never on the wrong ones. | ||||
| 27 | +3. **Best practices & anti-patterns** — ideal SKILL.md length (<500 lines), when to split into reference files, table of contents for large references, "principle of least surprise," deterministic scripts vs. prose instructions. | ||||
| 28 | +4. **Prompt engineering research applied to skills** — instruction clarity, positive/negative examples, output-format specification, step ordering, failure-mode handling. | ||||
| 29 | +5. **Community & ecosystem knowledge** — GitHub repos of real skills (e.g., anthropics/skills), blog posts, engineering write-ups, comparisons with OpenAI/LLM tool-use instructions, MCP-adjacent patterns. | ||||
| 30 | +6. **Evaluation & iteration** — how to test a skill (trigger evals, task evals), how to measure trigger rate, how to iterate on descriptions without overfitting. | ||||
| 31 | + | ||||
| 32 | +### Research rules | ||||
| 33 | +- Use `web_search` broadly first, then `web_fetch` the highest-quality primary sources (Anthropic docs, official repos, engineering blogs). | ||||
| 34 | +- Take structured notes as you go. Prefer primary sources over aggregators. | ||||
| 35 | +- At the end of Phase 1, produce a written synthesis file: **`RESEARCH-SYNTHESIS.md`** containing: | ||||
| 36 | + - The 10–15 core principles of an ultra-sharp skill (each in 1–2 sentences). | ||||
| 37 | + - A checklist ("Sharp Skill Checklist") you will apply to every skill you write. | ||||
| 38 | + - A template of the ideal SKILL.md structure. | ||||
| 39 | +- Only when `RESEARCH-SYNTHESIS.md` is complete may you proceed to Phase 2. | ||||
| 40 | + | ||||
| 41 | +--- | ||||
| 42 | + | ||||
| 43 | +## Phase 2 — Build 2 Example Skills (in English) to Test the Method | ||||
| 44 | + | ||||
| 45 | +Using ONLY the principles from your synthesis, create **two complete example skills, written entirely in English**, each in its own folder with a proper `SKILL.md`: | ||||
| 46 | + | ||||
| 47 | +### Skill 1 — Deterministic / verifiable domain | ||||
| 48 | +A skill with objectively checkable output (e.g., data extraction, file transformation, structured report generation). It must include: | ||||
| 49 | +- YAML frontmatter with a sharp, trigger-optimized `description`. | ||||
| 50 | +- A step-by-step workflow with explicit output format. | ||||
| 51 | +- At least one bundled resource (`references/` or `scripts/`) demonstrating progressive disclosure. | ||||
| 52 | + | ||||
| 53 | +### Skill 2 — Stylistic / subjective domain | ||||
| 54 | +A skill governing style or judgment (e.g., a house writing style, a code-review playbook). It must include: | ||||
| 55 | +- Positive AND negative examples (do this / never do this). | ||||
| 56 | +- Clear boundary conditions: when the skill applies and when it must NOT. | ||||
| 57 | + | ||||
| 58 | +### Validation step | ||||
| 59 | +For each skill, after writing it: | ||||
| 60 | +1. Write 3 realistic test prompts (some that SHOULD trigger it, at least one that should NOT). | ||||
| 61 | +2. Simulate/run the skill against the triggering prompts and show the outputs. | ||||
| 62 | +3. Apply your "Sharp Skill Checklist" line by line and report pass/fail. | ||||
| 63 | +4. Fix anything that fails, then re-check. | ||||
| 64 | + | ||||
| 65 | +--- | ||||
| 66 | + | ||||
| 67 | +## Non-Negotiable File Header Rule | ||||
| 68 | + | ||||
| 69 | +**EVERY file you create in this project** — `CLAUDE.md`, `RESEARCH-SYNTHESIS.md`, every `SKILL.md`, every reference file, every script — MUST begin with this header (adapted to the file's comment syntax): | ||||
| 70 | + | ||||
| 71 | +``` | ||||
| 72 | +Author: Simon-Pierre Boucher | ||||
| 73 | +Contact: contact@spboucher.ai | ||||
| 74 | +``` | ||||
| 75 | + | ||||
| 76 | +- Markdown files: use an HTML comment block or visible header lines at the very top. | ||||
| 77 | +- Python/shell scripts: use `#` comment lines at the very top (after any shebang). | ||||
| 78 | +- YAML frontmatter files (SKILL.md): place the header comment immediately after the frontmatter block, or as `#` comments before it if the format allows. | ||||
| 79 | + | ||||
| 80 | +No file ships without this header. Verify it before delivering anything. | ||||
| 81 | + | ||||
| 82 | +--- | ||||
| 83 | + | ||||
| 84 | +## Definition of "Ultra Sharp / Fine Point" | ||||
| 85 | + | ||||
| 86 | +A skill qualifies as ultra sharp only if: | ||||
| 87 | +- Its description triggers on the exact intended intents and nothing else. | ||||
| 88 | +- Every instruction is actionable — no vague verbs ("handle," "deal with") without a concrete procedure. | ||||
| 89 | +- The SKILL.md is as short as possible but no shorter; anything long lives in `references/`. | ||||
| 90 | +- Output format is fully specified (structure, naming, location). | ||||
| 91 | +- Failure modes and edge cases are addressed explicitly. | ||||
| 92 | +- It passed the validation step above. | ||||
| 93 | + | ||||
| 94 | +--- | ||||
| 95 | + | ||||
| 96 | +## Deliverables Summary | ||||
| 97 | + | ||||
| 98 | +1. `RESEARCH-SYNTHESIS.md` (with header) | ||||
| 99 | +2. `skill-1-<name>/SKILL.md` + resources (all with headers) | ||||
| 100 | +3. `skill-2-<name>/SKILL.md` + resources (all with headers) | ||||
| 101 | +4. Validation report (test prompts, outputs, checklist results) | ||||
| 102 | + | ||||
| 103 | +Work in this exact order. Research first. Sharp skills second. Headers everywhere. | ||||
added
README.md
+213 −0
@@ -0,0 +1,213 @@ | |||||
| 1 | +<!-- | ||||
| 2 | +Author: Simon-Pierre Boucher | ||||
| 3 | +Contact: contact@spboucher.ai | ||||
| 4 | +--> | ||||
| 5 | + | ||||
| 6 | +<div align="center"> | ||||
| 7 | + | ||||
| 8 | +# ⚡ Ultra-Sharp Agent Skills | ||||
| 9 | + | ||||
| 10 | +### A research-first skill-authoring system + 72 production-ready skills for AI agents | ||||
| 11 | + | ||||
| 12 | +[](#-the-collections) | ||||
| 13 | +[](#-the-collections) | ||||
| 14 | +[](#-quality-gate) | ||||
| 15 | +[](https://agentskills.io) | ||||
| 16 | + | ||||
| 17 | +[](RESEARCH-SYNTHESIS.md) | ||||
| 18 | +[](VALIDATION-REPORT.md) | ||||
| 19 | +[](tools/validate_skills.py) | ||||
| 20 | +[](https://code.claude.com) | ||||
| 21 | +[](mailto:contact@spboucher.ai) | ||||
| 22 | + | ||||
| 23 | +*Every skill: a trigger-optimized description, one default per decision, a validation-loop workflow,* | ||||
| 24 | +*and pairwise-exclusive boundaries so no request ever fires two skills.* | ||||
| 25 | + | ||||
| 26 | +</div> | ||||
| 27 | + | ||||
| 28 | +--- | ||||
| 29 | + | ||||
| 30 | +## 🧭 What is this? | ||||
| 31 | + | ||||
| 32 | +**Skills** are folders of instructions (`SKILL.md` + resources) that AI agents load on demand. | ||||
| 33 | +This repository was built in two phases, per [CLAUDE.md](CLAUDE.md): | ||||
| 34 | + | ||||
| 35 | +1. 🔬 **Research** — 20+ primary-source searches & fetches (Anthropic docs, `anthropics/skills`, engineering blogs, eval guides) distilled into **[RESEARCH-SYNTHESIS.md](RESEARCH-SYNTHESIS.md)**: 15 core principles, the **Sharp Skill Checklist**, and the ideal SKILL.md template. | ||||
| 36 | +2. 🗡️ **Build** — 72 skills across 7 collections, each mechanically verified by a custom linter and validated with positive **and negative** trigger tests ([VALIDATION-REPORT.md](VALIDATION-REPORT.md)). | ||||
| 37 | + | ||||
| 38 | +## 🚀 Quickstart | ||||
| 39 | + | ||||
| 40 | +```bash | ||||
| 41 | +# install any skill into Claude Code (personal scope) | ||||
| 42 | +cp -r doc-skills/processing-pdf ~/.claude/skills/ | ||||
| 43 | + | ||||
| 44 | +# or project scope | ||||
| 45 | +cp -r backend-skills/designing-rest-apis .claude/skills/ | ||||
| 46 | + | ||||
| 47 | +# verify the whole repo (stdlib only, no dependencies) | ||||
| 48 | +python3 tools/validate_skills.py . | ||||
| 49 | +# checked 72 skill(s) | ||||
| 50 | +# all checks passed | ||||
| 51 | +``` | ||||
| 52 | + | ||||
| 53 | +--- | ||||
| 54 | + | ||||
| 55 | +## 📚 The Collections | ||||
| 56 | + | ||||
| 57 | +### 📄 doc-skills — Document Processing  | ||||
| 58 | + | ||||
| 59 | +Create / read / modify every major document type. One default library per operation, re-parse validation after every write. | ||||
| 60 | + | ||||
| 61 | +| Skill | What it does | | ||||
| 62 | +|---|---| | ||||
| 63 | +| 📊 [`processing-xlsx`](doc-skills/processing-xlsx/) | Excel workbooks with openpyxl — values, formulas, formatting, multi-sheet | | ||||
| 64 | +| 📝 [`processing-docx`](doc-skills/processing-docx/) | Word documents with python-docx — paragraphs, tables, styles + pandoc reading | | ||||
| 65 | +| 🎞️ [`processing-pptx`](doc-skills/processing-pptx/) | PowerPoint decks with python-pptx — slides, layouts, tables, charts | | ||||
| 66 | +| 📕 [`processing-pdf`](doc-skills/processing-pdf/) | Extract text/tables, create, merge, split, watermark, encrypt, fill forms | | ||||
| 67 | +| 🧾 [`processing-json`](doc-skills/processing-json/) | Create, modify, validate, and query JSON / JSON Lines | | ||||
| 68 | +| 🗂️ [`processing-csv`](doc-skills/processing-csv/) | CSV/TSV create, edit, convert to/from JSON | | ||||
| 69 | +| 🏷️ [`processing-xml`](doc-skills/processing-xml/) | XML create, edit, validate, XPath queries — namespace-safe | | ||||
| 70 | +| ⚙️ [`processing-yaml`](doc-skills/processing-yaml/) | YAML configs — safe_load discipline, round-trip edits, Norway-problem proof | | ||||
| 71 | +| ✍️ [`processing-markdown`](doc-skills/processing-markdown/) | Markdown create, restructure, convert via pandoc | | ||||
| 72 | +| 🌐 [`processing-html`](doc-skills/processing-html/) | Local HTML — extract text/tables/links, edit with BeautifulSoup | | ||||
| 73 | + | ||||
| 74 | +### 🎨 frontend-skills — Frontend Design  | ||||
| 75 | + | ||||
| 76 | +Grounded in Anthropic's `frontend-design` skill + 2026 standards (WCAG 2.2 AA · LCP ≤2.5s · INP ≤200ms · CLS ≤0.1). | ||||
| 77 | + | ||||
| 78 | +| Skill | What it does | | ||||
| 79 | +|---|---| | ||||
| 80 | +| 🏗️ [`structuring-semantic-html`](frontend-skills/structuring-semantic-html/) | Landmarks, heading hierarchy, meaning-bearing markup, SEO/OG meta | | ||||
| 81 | +| 📐 [`designing-responsive-layouts`](frontend-skills/designing-responsive-layouts/) | Mobile-first flexbox/grid, container queries, clamp() fluid sizing | | ||||
| 82 | +| 🎨 [`theming-design-tokens`](frontend-skills/theming-design-tokens/) | Token tiers, dark mode, spacing scales, contrast-safe palettes | | ||||
| 83 | +| 🔤 [`choosing-typography`](frontend-skills/choosing-typography/) | Typeface pairing per brief, modular scales, font loading | | ||||
| 84 | +| ♿ [`ensuring-accessibility`](frontend-skills/ensuring-accessibility/) | WCAG 2.2 AA audits — keyboard, focus, ARIA, contrast, targets | | ||||
| 85 | +| ✨ [`crafting-ui-animations`](frontend-skills/crafting-ui-animations/) | Purposeful motion, transform/opacity only, reduced-motion always | | ||||
| 86 | +| 🧾 [`designing-forms`](frontend-skills/designing-forms/) | Labels, validation timing, error states, multi-step patterns | | ||||
| 87 | +| ⚛️ [`building-react-components`](frontend-skills/building-react-components/) | Props APIs, composition, state placement, hooks | | ||||
| 88 | +| 🚀 [`optimizing-web-performance`](frontend-skills/optimizing-web-performance/) | Core Web Vitals budgets — images, fonts, splitting, measurement | | ||||
| 89 | +| 🎯 [`creating-landing-pages`](frontend-skills/creating-landing-pages/) | Hero thesis, CTA rhythm, one signature element, benefit-first copy | | ||||
| 90 | + | ||||
| 91 | +### 🗄️ db-skills — Database Management  | ||||
| 92 | + | ||||
| 93 | +PostgreSQL-first with MySQL/SQLite deviations noted. Measure before changing anything. | ||||
| 94 | + | ||||
| 95 | +| Skill | What it does | | ||||
| 96 | +|---|---| | ||||
| 97 | +| 🔍 [`writing-sql-queries`](db-skills/writing-sql-queries/) | Correct, readable, injection-safe SQL — CTEs, window functions, NULL traps | | ||||
| 98 | +| 📋 [`designing-database-schemas`](db-skills/designing-database-schemas/) | 3NF-first modeling, keys, types, constraints, naming | | ||||
| 99 | +| ⚡ [`optimizing-sql-performance`](db-skills/optimizing-sql-performance/) | EXPLAIN ANALYZE, indexing strategy, N+1, keyset pagination | | ||||
| 100 | +| 🔀 [`managing-database-migrations`](db-skills/managing-database-migrations/) | Versioned, immutable, reversible; zero-downtime expand→contract | | ||||
| 101 | +| 💾 [`backing-up-databases`](db-skills/backing-up-databases/) | Scheduled backups, PITR, 3-2-1 rule, restore drills | | ||||
| 102 | +| 🔐 [`securing-databases`](db-skills/securing-databases/) | Least-privilege roles, parameterized-only queries, TLS, RLS, auditing | | ||||
| 103 | +| 🐘 [`administering-postgresql`](db-skills/administering-postgresql/) | Roles, config knobs with starting values, autovacuum, monitoring | | ||||
| 104 | +| 🪶 [`managing-sqlite`](db-skills/managing-sqlite/) | WAL mode, pragmas, bulk-insert transactions, safe backups | | ||||
| 105 | +| 📦 [`modeling-nosql-data`](db-skills/modeling-nosql-data/) | Access-pattern-first design, embed vs reference, Redis key design | | ||||
| 106 | +| 🚨 [`troubleshooting-databases`](db-skills/troubleshooting-databases/) | 5-stage incident triage runbook with the exact query per stage | | ||||
| 107 | + | ||||
| 108 | +### 🔧 backend-skills — Backend Development  | ||||
| 109 | + | ||||
| 110 | +| Skill | What it does | | ||||
| 111 | +|---|---| | ||||
| 112 | +| 🛣️ [`designing-rest-apis`](backend-skills/designing-rest-apis/) | Resources, status codes, pagination, versioning, problem+json, OpenAPI | | ||||
| 113 | +| 🕸️ [`designing-graphql-apis`](backend-skills/designing-graphql-apis/) | Schema-first, DataLoader vs N+1, cursor connections, complexity limits | | ||||
| 114 | +| 📡 [`designing-webhooks`](backend-skills/designing-webhooks/) | HMAC signing, backoff retries, idempotent delivery, DLQ | | ||||
| 115 | +| 📤 [`handling-file-uploads`](backend-skills/handling-file-uploads/) | Presigned URLs, magic-byte validation, streaming, resumable uploads | | ||||
| 116 | +| 🔑 [`implementing-authentication`](backend-skills/implementing-authentication/) | argon2id, sessions vs JWT, OAuth2/OIDC + PKCE, MFA, safe resets | | ||||
| 117 | +| 🛂 [`implementing-authorization`](backend-skills/implementing-authorization/) | RBAC/ABAC, deny-by-default, IDOR prevention, tenant isolation | | ||||
| 118 | +| 🧰 [`validating-input`](backend-skills/validating-input/) | Boundary schemas, allowlists, canonicalization, structured 422s | | ||||
| 119 | +| 🛡️ [`securing-backend-services`](backend-skills/securing-backend-services/) | Security headers, TLS, secrets, SSRF/CSRF defenses, safe errors | | ||||
| 120 | +| 💥 [`handling-errors`](backend-skills/handling-errors/) | Error taxonomy, problem+json, retries with jitter, circuit breakers | | ||||
| 121 | +| 🔭 [`instrumenting-observability`](backend-skills/instrumenting-observability/) | Structured logs, correlation IDs, RED metrics, OpenTelemetry | | ||||
| 122 | +| 🧪 [`testing-backend-services`](backend-skills/testing-backend-services/) | Test pyramid, testcontainers, contract tests, zero flake | | ||||
| 123 | +| 🎛️ [`managing-configuration`](backend-skills/managing-configuration/) | 12-factor env config, typed startup validation, secret separation | | ||||
| 124 | +| ⏳ [`writing-background-jobs`](backend-skills/writing-background-jobs/) | Idempotent jobs, backoff, DLQ, timeouts, queue observability | | ||||
| 125 | +| 📨 [`handling-async-messaging`](backend-skills/handling-async-messaging/) | Transactional outbox, idempotent consumers, schema versioning | | ||||
| 126 | +| 🗃️ [`caching-strategies`](backend-skills/caching-strategies/) | Cache-aside, TTL discipline, invalidation, stampede protection | | ||||
| 127 | +| 🚦 [`limiting-request-rates`](backend-skills/limiting-request-rates/) | Token bucket, 429 + Retry-After, tiered limits, load shedding | | ||||
| 128 | +| 🐳 [`containerizing-services`](backend-skills/containerizing-services/) | Multi-stage builds, non-root, pinned images, healthchecks | | ||||
| 129 | +| 🚢 [`shipping-with-ci-cd`](backend-skills/shipping-with-ci-cd/) | Build-once promotion, canary/blue-green, automated rollback | | ||||
| 130 | +| 🧩 [`architecting-service-boundaries`](backend-skills/architecting-service-boundaries/) | Modular monolith default, data ownership, sagas, strangler | | ||||
| 131 | +| 📈 [`scaling-backend-services`](backend-skills/scaling-backend-services/) | Statelessness, pooling, load balancing, autoscaling on the right metric | | ||||
| 132 | + | ||||
| 133 | +### ✒️ writing-skills — Writing  | ||||
| 134 | + | ||||
| 135 | +Stylistic skills: every rule ships with a ✅/❌ pair; every workflow ends in a self-review pass. | ||||
| 136 | + | ||||
| 137 | +| Skill | What it does | | ||||
| 138 | +|---|---| | ||||
| 139 | +| 📖 [`writing-technical-documentation`](writing-skills/writing-technical-documentation/) | READMEs, architecture docs, runbooks — quickstart first, scannable | | ||||
| 140 | +| 🔌 [`writing-api-documentation`](writing-skills/writing-api-documentation/) | Endpoint reference with runnable examples, errors documented like successes | | ||||
| 141 | +| 🧑🏫 [`writing-tutorials`](writing-skills/writing-tutorials/) | Step-by-step guides — every step has a verifiable checkpoint | | ||||
| 142 | +| 📰 [`writing-blog-posts`](writing-skills/writing-blog-posts/) | One specific claim, a real hook, real code and numbers | | ||||
| 143 | +| 🤖 [`writing-agent-skills`](writing-skills/writing-agent-skills/) | **The meta-skill** — author SKILL.md files with the Sharp Skill method | | ||||
| 144 | +| 📧 [`writing-professional-emails`](writing-skills/writing-professional-emails/) | Ask-first structure, decision-ready subject lines | | ||||
| 145 | +| 📊 [`writing-executive-summaries`](writing-skills/writing-executive-summaries/) | BLUF, one page, numbers over adjectives, a clear pick | | ||||
| 146 | +| 🤝 [`writing-proposals`](writing-skills/writing-proposals/) | Their problem first, scope exclusions, tiered pricing | | ||||
| 147 | +| 🗒️ [`writing-meeting-notes`](writing-skills/writing-meeting-notes/) | Decisions & actions first, every action has owner + deadline | | ||||
| 148 | +| 🪄 [`editing-and-proofreading`](writing-skills/editing-and-proofreading/) | Four fixed passes: structure → paragraphs → sentences → mechanics | | ||||
| 149 | + | ||||
| 150 | +### 💰 finance-skills — US/Canada Tax & Accounting  | ||||
| 151 | + | ||||
| 152 | +🇺🇸 🇨🇦 Tax-year-2026 figures with official verification sources. **Educational, legal planning only** — every skill refuses evasion and refers complex cases to a CPA. | ||||
| 153 | + | ||||
| 154 | +| Skill | What it does | | ||||
| 155 | +|---|---| | ||||
| 156 | +| 🧾 [`preparing-us-personal-tax-returns`](finance-skills/preparing-us-personal-tax-returns/) | Form 1040 — documents, schedules, deadlines, safe harbors | | ||||
| 157 | +| 🍁 [`preparing-canadian-personal-tax-returns`](finance-skills/preparing-canadian-personal-tax-returns/) | T1 — slips, deductions vs credits, NETFILE, instalments | | ||||
| 158 | +| 💸 [`optimizing-us-personal-taxes`](finance-skills/optimizing-us-personal-taxes/) | 401(k)/IRA/HSA priority, Roth decisions, loss harvesting, bunching | | ||||
| 159 | +| 🇨🇦 [`optimizing-canadian-personal-taxes`](finance-skills/optimizing-canadian-personal-taxes/) | RRSP vs TFSA vs FHSA, legal splitting, superficial-loss rule | | ||||
| 160 | +| 🌉 [`handling-cross-border-taxation`](finance-skills/handling-cross-border-taxation/) | Residency tests, treaty tie-breakers, FBAR/8938, TFSA-for-US-persons trap | | ||||
| 161 | +| 📒 [`bookkeeping-for-small-businesses`](finance-skills/bookkeeping-for-small-businesses/) | Double-entry, chart of accounts, monthly close, reconciliation | | ||||
| 162 | +| 📑 [`preparing-financial-statements`](finance-skills/preparing-financial-statements/) | Income statement, balance sheet, cash flow — with cross-statement ties | | ||||
| 163 | +| 🏢 [`filing-us-business-taxes`](finance-skills/filing-us-business-taxes/) | Schedule C / 1065 / 1120-S / 1120, estimated taxes, payroll, 1099s | | ||||
| 164 | +| 🏬 [`filing-canadian-business-taxes`](finance-skills/filing-canadian-business-taxes/) | T2125, T2, GST/HST, payroll remittances, T4/T5 | | ||||
| 165 | +| 🧮 [`optimizing-business-taxes`](finance-skills/optimizing-business-taxes/) | S-corp analysis (US) · SBD protection, salary vs dividends (Canada) | | ||||
| 166 | + | ||||
| 167 | +### 🏅 The founding examples  | ||||
| 168 | + | ||||
| 169 | +| Skill | What it does | | ||||
| 170 | +|---|---| | ||||
| 171 | +| 🔬 [`skill-1-profiling-csv-data`](skill-1-profiling-csv-data/) | Deterministic domain — CSV data-quality reports via a bundled stdlib script (executed & verified) | | ||||
| 172 | +| 📣 [`skill-2-writing-release-notes`](skill-2-writing-release-notes/) | Stylistic domain — benefit-first release notes with do/never examples | | ||||
| 173 | + | ||||
| 174 | +--- | ||||
| 175 | + | ||||
| 176 | +## ✅ Quality Gate | ||||
| 177 | + | ||||
| 178 | + | ||||
| 179 | + | ||||
| 180 | + | ||||
| 181 | + | ||||
| 182 | + | ||||
| 183 | + | ||||
| 184 | +[`tools/validate_skills.py`](tools/validate_skills.py) (stdlib-only) mechanically enforces the Sharp Skill Checklist on every skill: valid frontmatter · name rules (≤64 chars, charset, reserved words, folder match) · description rules (≤1024 chars, literal "Use when …" trigger clause, "Do not use …" boundary, third person, no XML tags) · author header in every file · body ≤500 lines · zero broken reference links · forward-slash paths only. | ||||
| 185 | + | ||||
| 186 | +## 🧬 Design invariants (all 72 skills) | ||||
| 187 | + | ||||
| 188 | +1. 🎯 **Description = trigger** — WHAT + literal "Use when …" phrases + "Do not use …" boundary | ||||
| 189 | +2. 🧱 **Pairwise-exclusive boundaries** per collection — no request can plausibly fire two skills | ||||
| 190 | +3. 1️⃣ **One default per decision**, escape hatch named with its install command | ||||
| 191 | +4. 🔁 **Every workflow ends with a validation step** — malformed input is reported, never guessed around | ||||
| 192 | +5. 🪜 **Progressive disclosure** — lean SKILL.md, depth one level deep in `references/` with a TOC | ||||
| 193 | + | ||||
| 194 | +## 📁 Repository map | ||||
| 195 | + | ||||
| 196 | +```text | ||||
| 197 | +. | ||||
| 198 | +├── RESEARCH-SYNTHESIS.md # the method: 15 principles + Sharp Skill Checklist + template | ||||
| 199 | +├── VALIDATION-REPORT.md # trigger tests, execution evidence, checklist results | ||||
| 200 | +├── skill-1-profiling-csv-data/ skill-2-writing-release-notes/ | ||||
| 201 | +├── doc-skills/ (10) ├── frontend-skills/ (10) ├── db-skills/ (10) | ||||
| 202 | +├── backend-skills/ (20) ├── writing-skills/ (10) ├── finance-skills/ (10) | ||||
| 203 | +├── tools/validate_skills.py # the quality gate | ||||
| 204 | +└── validation/ # fixtures + real outputs | ||||
| 205 | +``` | ||||
| 206 | + | ||||
| 207 | +<div align="center"> | ||||
| 208 | + | ||||
| 209 | +**Author:** Simon-Pierre Boucher · **Contact:** [contact@spboucher.ai](mailto:contact@spboucher.ai) | ||||
| 210 | + | ||||
| 211 | +*Built research-first with [Claude Code](https://claude.com/claude-code) — every figure sourced, every skill linted, every trigger tested.* | ||||
| 212 | + | ||||
| 213 | +</div> | ||||
added
RESEARCH-SYNTHESIS.md
+167 −0
@@ -0,0 +1,167 @@ | |||||
| 1 | +<!-- | ||||
| 2 | +Author: Simon-Pierre Boucher | ||||
| 3 | +Contact: contact@spboucher.ai | ||||
| 4 | +--> | ||||
| 5 | + | ||||
| 6 | +# RESEARCH-SYNTHESIS.md — How to Write Ultra-Sharp Skills for AI Agents | ||||
| 7 | + | ||||
| 8 | +**Author:** Simon-Pierre Boucher | ||||
| 9 | +**Contact:** contact@spboucher.ai | ||||
| 10 | +**Date:** 2026-08-05 | ||||
| 11 | +**Basis:** 16 distinct web searches + page fetches across official Anthropic documentation, the anthropics/skills repository, Anthropic engineering/blog posts, and community engineering write-ups. Sources listed at the end. | ||||
| 12 | + | ||||
| 13 | +--- | ||||
| 14 | + | ||||
| 15 | +## Part 1 — The 15 Core Principles of an Ultra-Sharp Skill | ||||
| 16 | + | ||||
| 17 | +1. **The description IS the trigger.** At startup only `name` + `description` are loaded (~100 tokens/skill); Claude matches the user's request against the description to decide whether to fire. A vague description is the #1 cause of skill failure. | ||||
| 18 | + | ||||
| 19 | +2. **A description states WHAT + WHEN, with literal user phrases.** Include both what the skill does and the concrete trigger contexts — the exact words a user would type ("PDFs, forms, document extraction") — because an abstract description won't match a concrete request. | ||||
| 20 | + | ||||
| 21 | +3. **Write descriptions in third person, directive voice.** Descriptions are injected into the system prompt; "I can help you…" or "You can use this…" causes discovery problems. Directive phrasing ("Use when…") measurably raises activation rates versus purely descriptive phrasing. | ||||
| 22 | + | ||||
| 23 | +4. **Descriptions live in a shared, finite budget.** In Claude Code all skill descriptions share a character budget (~15,000 chars default) and overflow is silently dropped — so every description must be dense and short, and each skill you add taxes all the others. | ||||
| 24 | + | ||||
| 25 | +5. **Progressive disclosure is the architecture.** Three levels: metadata (always loaded, ~100 tokens), SKILL.md body (loaded on trigger, keep <500 lines / <5k tokens), bundled resources (zero context cost until read/executed). Design every skill around these levels. | ||||
| 26 | + | ||||
| 27 | +6. **Concise is key — the context window is a public good.** Assume Claude is already smart: cut anything Claude already knows, and challenge every paragraph with "does this justify its token cost?" | ||||
| 28 | + | ||||
| 29 | +7. **Match degrees of freedom to task fragility.** Fragile, order-dependent operations get exact scripts and "do not modify" guardrails (low freedom); judgment tasks get heuristics and trust (high freedom). Narrow bridge → rails; open field → direction. | ||||
| 30 | + | ||||
| 31 | +8. **One default, not a menu.** Give a single recommended tool/approach with an explicit escape hatch for the exception ("Use pdfplumber; for scanned PDFs use OCR instead") — offering many options confuses execution. | ||||
| 32 | + | ||||
| 33 | +9. **Prefer deterministic scripts over prose for deterministic work.** Bundled scripts are more reliable than generated code, cost zero context (only their output enters context), and must solve errors themselves rather than defer to Claude; no unexplained "voodoo constants." | ||||
| 34 | + | ||||
| 35 | +10. **Make execution intent explicit.** Say "Run `scripts/x.py`" (execute) vs "See `scripts/x.py` for the algorithm" (read) — ambiguity here wastes tokens or produces re-implementation. | ||||
| 36 | + | ||||
| 37 | +11. **Keep references one level deep, with a table of contents past ~100 lines.** Nested reference chains get partially read (`head -100`); every reference file should link directly from SKILL.md, and long ones need a TOC so partial reads still reveal scope. | ||||
| 38 | + | ||||
| 39 | +12. **Fully specify the output.** Templates, exact file naming, destination paths, and input→output example pairs (3–5, covering edge cases) convey format and style better than any amount of description — this is standard prompt-engineering practice applied to skills. | ||||
| 40 | + | ||||
| 41 | +13. **Use workflows with checklists and validation loops.** Break complex tasks into numbered steps Claude can check off, and close the loop: run validator → fix → re-validate → only then proceed. | ||||
| 42 | + | ||||
| 43 | +14. **Build evals BEFORE writing extensive documentation.** Establish a baseline without the skill, write ≥3 test scenarios including **negative prompts that must NOT trigger the skill**, run multiple trials (behavior is nondeterministic), and grade outcomes, not paths. | ||||
| 44 | + | ||||
| 45 | +15. **Iterate from observed behavior, not assumptions.** Use the two-Claude loop (Claude A authors, Claude B executes real tasks), watch how the agent actually navigates the files (ignored files, missed links, over-read sections), and refine the description first whenever triggering misfires. | ||||
| 46 | + | ||||
| 47 | +**Hygiene constants (from the spec):** `name` ≤64 chars, lowercase/numbers/hyphens only, no reserved words ("anthropic", "claude"), gerund form preferred; `description` ≤1,024 chars, non-empty, no XML tags; consistent terminology throughout; no time-sensitive facts; forward-slash paths only; never assume packages are installed. | ||||
| 48 | + | ||||
| 49 | +--- | ||||
| 50 | + | ||||
| 51 | +## Part 2 — The Sharp Skill Checklist | ||||
| 52 | + | ||||
| 53 | +Apply line by line to EVERY skill before delivery. | ||||
| 54 | + | ||||
| 55 | +### A. Triggering (the point of the spear) | ||||
| 56 | +- [ ] A1. Description states WHAT the skill does AND WHEN to use it | ||||
| 57 | +- [ ] A2. Description contains the literal key terms/phrases a user would type | ||||
| 58 | +- [ ] A3. Description is third person, directive ("Use when…"), no XML tags, ≤1,024 chars | ||||
| 59 | +- [ ] A4. Description would NOT match plausible neighboring requests (no false positives) | ||||
| 60 | +- [ ] A5. Name is gerund-form (or clear noun phrase), lowercase/hyphens, ≤64 chars, not vague (`helper`, `utils`) | ||||
| 61 | + | ||||
| 62 | +### B. Body sharpness | ||||
| 63 | +- [ ] B1. SKILL.md body <500 lines; anything long lives in `references/` | ||||
| 64 | +- [ ] B2. No content Claude already knows; every paragraph earns its tokens | ||||
| 65 | +- [ ] B3. Exactly one recommended default per operation, with explicit escape hatch | ||||
| 66 | +- [ ] B4. Workflow is numbered steps (checklist if >3 steps); validation loop for quality-critical output | ||||
| 67 | +- [ ] B5. Output format fully specified: structure, file naming, destination | ||||
| 68 | +- [ ] B6. Concrete input→output examples where style/format matters | ||||
| 69 | +- [ ] B7. Consistent terminology; no time-sensitive info; no vague verbs without a procedure | ||||
| 70 | +- [ ] B8. Failure modes and edge cases explicitly addressed | ||||
| 71 | + | ||||
| 72 | +### C. Resources & structure | ||||
| 73 | +- [ ] C1. All references link one level deep from SKILL.md; descriptive file names | ||||
| 74 | +- [ ] C2. Reference files >100 lines start with a table of contents | ||||
| 75 | +- [ ] C3. Scripts: execution vs read-as-reference intent is explicit | ||||
| 76 | +- [ ] C4. Scripts handle their own errors ("solve, don't defer"); all constants justified | ||||
| 77 | +- [ ] C5. Dependencies stated explicitly (or stdlib-only); forward-slash paths only | ||||
| 78 | + | ||||
| 79 | +### D. Validation | ||||
| 80 | +- [ ] D1. ≥3 test prompts written: triggering AND at least one non-triggering | ||||
| 81 | +- [ ] D2. Skill executed/simulated against triggering prompts; outputs verified | ||||
| 82 | +- [ ] D3. Negative prompt confirmed NOT to activate the skill | ||||
| 83 | +- [ ] D4. Checklist re-run after any fix | ||||
| 84 | + | ||||
| 85 | +### E. Project rule | ||||
| 86 | +- [ ] E1. Every file carries the author header (Author: Simon-Pierre Boucher / Contact: contact@spboucher.ai) | ||||
| 87 | + | ||||
| 88 | +--- | ||||
| 89 | + | ||||
| 90 | +## Part 3 — Template of the Ideal SKILL.md | ||||
| 91 | + | ||||
| 92 | +```markdown | ||||
| 93 | +--- | ||||
| 94 | +name: doing-the-thing # gerund, lowercase-hyphens, ≤64 chars | ||||
| 95 | +description: <What it does in one clause with key nouns>. Use when the user asks to <literal trigger phrases>, mentions <key terms>, or <concrete context>. Do not use for <nearest non-target intent>. | ||||
| 96 | +--- | ||||
| 97 | + | ||||
| 98 | +<!-- | ||||
| 99 | +Author: Simon-Pierre Boucher | ||||
| 100 | +Contact: contact@spboucher.ai | ||||
| 101 | +--> | ||||
| 102 | + | ||||
| 103 | +# Doing the Thing | ||||
| 104 | + | ||||
| 105 | +## When to use / when NOT to use | ||||
| 106 | +- Use for: <precise intents> | ||||
| 107 | +- Do NOT use for: <neighboring intents that belong to other skills or plain answers> | ||||
| 108 | + | ||||
| 109 | +## Workflow | ||||
| 110 | +Copy this checklist and check off items as you complete them: | ||||
| 111 | + | ||||
| 112 | +- [ ] Step 1: <action> (run `scripts/tool.py <args>` — execute, do not read) | ||||
| 113 | +- [ ] Step 2: <action> | ||||
| 114 | +- [ ] Step 3: Validate: <check>. If it fails, fix and repeat Step 2. | ||||
| 115 | +- [ ] Step 4: Produce output exactly per "Output format" below. | ||||
| 116 | + | ||||
| 117 | +## Output format | ||||
| 118 | +Save to `<naming-rule>`. Use this exact structure: | ||||
| 119 | +<template block> | ||||
| 120 | + | ||||
| 121 | +## Examples | ||||
| 122 | +**Input:** <realistic input> → **Output:** <exact desired output> | ||||
| 123 | +**Input:** <edge case> → **Output:** <exact desired output> | ||||
| 124 | + | ||||
| 125 | +## Edge cases & failure modes | ||||
| 126 | +- <case> → <exact behavior> | ||||
| 127 | +- <case> → <exact behavior> | ||||
| 128 | + | ||||
| 129 | +## References (one level deep) | ||||
| 130 | +- Advanced details: see [references/advanced.md](references/advanced.md) | ||||
| 131 | +``` | ||||
| 132 | + | ||||
| 133 | +Directory anatomy: | ||||
| 134 | + | ||||
| 135 | +```text | ||||
| 136 | +doing-the-thing/ | ||||
| 137 | +├── SKILL.md # <500 lines, loaded on trigger | ||||
| 138 | +├── references/ # loaded only when needed; TOC if >100 lines | ||||
| 139 | +│ └── advanced.md | ||||
| 140 | +├── scripts/ # executed via bash; only output enters context | ||||
| 141 | +│ └── tool.py | ||||
| 142 | +└── assets/ # templates, images, data (optional) | ||||
| 143 | +``` | ||||
| 144 | + | ||||
| 145 | +--- | ||||
| 146 | + | ||||
| 147 | +## Sources | ||||
| 148 | + | ||||
| 149 | +Primary (fetched in full): | ||||
| 150 | +- [Skill authoring best practices — Claude Platform Docs](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices.md) | ||||
| 151 | +- [Agent Skills overview — Claude Platform Docs](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview) | ||||
| 152 | +- [Equipping agents for the real world with Agent Skills — Anthropic Engineering](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills) | ||||
| 153 | +- [skill-creator SKILL.md — anthropics/skills (GitHub)](https://github.com/anthropics/skills/blob/main/skills/skill-creator/SKILL.md) | ||||
| 154 | +- [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) | ||||
| 155 | +- [Improving skill-creator: test, measure, and refine Agent Skills — Anthropic blog](https://claude.com/blog/improving-skill-creator-test-measure-and-refine-agent-skills) | ||||
| 156 | +- [Practical Guide to Evaluating and Testing Agent Skills — Philipp Schmid](https://www.philschmid.de/testing-skills) | ||||
| 157 | +- [Claude Code skills not triggering? It might not see them — blog.fsck.com](https://blog.fsck.com/2025/12/17/claude-code-skills-not-triggering/) | ||||
| 158 | + | ||||
| 159 | +Secondary (search-level): | ||||
| 160 | +- [anthropics/skills repository](https://github.com/anthropics/skills) | ||||
| 161 | +- [Why Claude Code Skills Don't Trigger (2026) — DEV Community](https://dev.to/lizechengnet/why-claude-code-skills-dont-trigger-and-how-to-fix-them-in-2026-o7h) | ||||
| 162 | +- [How to Activate Claude Skills Automatically — DEV Community](https://dev.to/oluwawunmiadesewa/claude-code-skills-not-triggering-2-fixes-for-100-activation-3b57) | ||||
| 163 | +- [Prompt engineering best practices for 2026 — Claude blog](https://claude.com/blog/best-practices-for-prompt-engineering) | ||||
| 164 | +- [Testing Agent Skills Systematically with Evals — OpenAI Developers](https://developers.openai.com/blog/eval-skills) | ||||
| 165 | +- [Function calling — OpenAI API docs](https://developers.openai.com/api/docs/guides/function-calling) | ||||
| 166 | +- [Agent Skills: Progressive Disclosure as a System Design Pattern — SwirlAI](https://www.newsletter.swirlai.com/p/agent-skills-progressive-disclosure) | ||||
| 167 | +- [Introducing Task Evals — Tessl](https://tessl.io/blog/introducing-task-evals-measure-whether-your-skills-actually-work/) | ||||
added
VALIDATION-REPORT.md
+166 −0
@@ -0,0 +1,166 @@ | |||||
| 1 | +<!-- | ||||
| 2 | +Author: Simon-Pierre Boucher | ||||
| 3 | +Contact: contact@spboucher.ai | ||||
| 4 | +--> | ||||
| 5 | + | ||||
| 6 | +# VALIDATION-REPORT.md — Test Prompts, Outputs, and Checklist Results | ||||
| 7 | + | ||||
| 8 | +**Author:** Simon-Pierre Boucher | ||||
| 9 | +**Contact:** contact@spboucher.ai | ||||
| 10 | +**Date:** 2026-08-05 | ||||
| 11 | + | ||||
| 12 | +Validation of the two Phase-2 example skills per the CLAUDE.md protocol, plus | ||||
| 13 | +mechanical verification of the whole repository with `tools/validate_skills.py` | ||||
| 14 | +(the deterministic linter that enforces the Sharp Skill Checklist). | ||||
| 15 | + | ||||
| 16 | +--- | ||||
| 17 | + | ||||
| 18 | +## Skill 1 — `profiling-csv-data` (deterministic / verifiable) | ||||
| 19 | + | ||||
| 20 | +### Test prompts | ||||
| 21 | + | ||||
| 22 | +| # | Prompt | Expected | Result | | ||||
| 23 | +|---|---|---|---| | ||||
| 24 | +| 1 | "Profile customers.csv and tell me about its data quality" | TRIGGER | ✅ matches "profile", "data quality", "csv" in description | | ||||
| 25 | +| 2 | "What columns and missing values are in validation/fixtures/customers.csv?" | TRIGGER | ✅ matches "columns", "missing/null values", "CSV" | | ||||
| 26 | +| 3 | "Convert customers.csv to JSON" | NO TRIGGER | ✅ description explicitly excludes "converting, transforming" — falls through to processing-csv / direct answer | | ||||
| 27 | + | ||||
| 28 | +### Execution evidence (real runs, not simulated) | ||||
| 29 | + | ||||
| 30 | +- `python3 skill-1-profiling-csv-data/scripts/profile_csv.py validation/fixtures/customers.csv` | ||||
| 31 | + → valid JSON: 8 rows, 6 columns, types inferred correctly (integer/string/date/float), | ||||
| 32 | + 1 null in `age`, 1 null in `city`, 1 duplicate row detected. ✅ | ||||
| 33 | +- Rendered report per `references/report-format.md`: see | ||||
| 34 | + [validation/fixtures/customers-profile.md](validation/fixtures/customers-profile.md) | ||||
| 35 | + — verdict rule 2 applied (🟡, 3 issues), naming rule respected | ||||
| 36 | + (`customers.csv` → `customers-profile.md`). ✅ | ||||
| 37 | +- Edge cases: | ||||
| 38 | + - `ragged.csv` → `ragged_rows: 2` detected ✅ | ||||
| 39 | + - `headeronly.csv` → `rows: 0`, exit 0 (→ ⚠️ verdict path) ✅ | ||||
| 40 | + - `missing.csv` → clear error on stderr, exit code 2 ✅ | ||||
| 41 | + | ||||
| 42 | +### Sharp Skill Checklist | ||||
| 43 | + | ||||
| 44 | +| Item | Result | Note | | ||||
| 45 | +|---|---|---| | ||||
| 46 | +| A1 what + when in description | ✅ | | | ||||
| 47 | +| A2 literal user phrases | ✅ | "profile", "what's in this CSV", "duplicate rows" | | ||||
| 48 | +| A3 third person, directive, ≤1024 chars | ✅ | 441 chars | | ||||
| 49 | +| A4 no false-positive neighbors | ✅ | conversion/editing/plotting explicitly excluded | | ||||
| 50 | +| A5 name gerund, lowercase-hyphens | ✅ | `profiling-csv-data` | | ||||
| 51 | +| B1 body <500 lines | ✅ | 44 lines | | ||||
| 52 | +| B2–B3 no known-content, one default | ✅ | script is the single source of truth | | ||||
| 53 | +| B4 checklist workflow + validation loop | ✅ | Step 5 validates rendering | | ||||
| 54 | +| B5 output fully specified | ✅ | template + `<stem>-profile.md` naming + destination | | ||||
| 55 | +| B6 examples where format matters | ✅ | exact template in reference file | | ||||
| 56 | +| B7 consistent terms, no time-sensitive info | ✅ | | | ||||
| 57 | +| B8 failure modes addressed | ✅ | missing/empty/ragged/encoding | | ||||
| 58 | +| C1 references one level deep | ✅ | one reference file | | ||||
| 59 | +| C2 TOC for >100-line references | ✅ (N/A) | reference is 62 lines | | ||||
| 60 | +| C3 execute-vs-read explicit | ✅ | "execute, do not read" | | ||||
| 61 | +| C4 scripts solve-don't-defer, constants justified | ✅ | all 4 constants commented; errors handled | | ||||
| 62 | +| C5 dependencies stated | ✅ | stdlib-only | | ||||
| 63 | +| D1–D4 tests incl. negative | ✅ | above | | ||||
| 64 | +| E1 author headers | ✅ | linter-verified | | ||||
| 65 | + | ||||
| 66 | +**Initial failures found and fixed during validation:** | ||||
| 67 | +1. Linter flagged `name != folder` because CLAUDE.md mandates `skill-1-` folder | ||||
| 68 | + prefixes — resolved by encoding the project convention into the linter | ||||
| 69 | + (frontmatter name must equal folder minus `skill-N-` prefix). Re-checked: pass. | ||||
| 70 | + | ||||
| 71 | +--- | ||||
| 72 | + | ||||
| 73 | +## Skill 2 — `writing-release-notes` (stylistic / subjective) | ||||
| 74 | + | ||||
| 75 | +### Test prompts | ||||
| 76 | + | ||||
| 77 | +| # | Prompt | Expected | Result | | ||||
| 78 | +|---|---|---|---| | ||||
| 79 | +| 1 | "Write release notes for v2.8 from these commits: [list]" | TRIGGER | ✅ matches "write release notes" | | ||||
| 80 | +| 2 | "Draft the what's new section for our app update" | TRIGGER | ✅ matches "'what's new' section", "version announcement" | | ||||
| 81 | +| 3 | "Write a commit message for this diff" | NO TRIGGER | ✅ description explicitly excludes git commit messages | | ||||
| 82 | + | ||||
| 83 | +### Simulated run (prompt 1, commit list from references/examples.md) | ||||
| 84 | + | ||||
| 85 | +Input: 6 commits (SAML SSO, query-planner perf, avatar NPE fix, CI bump, | ||||
| 86 | +billing refactor, breaking `/v1/auth` removal). | ||||
| 87 | +Output produced by following the skill: | ||||
| 88 | + | ||||
| 89 | +```markdown | ||||
| 90 | +## Acme 2.8.0 — 2026-08-05 | ||||
| 91 | + | ||||
| 92 | +> **Breaking:** The legacy `/v1/auth` endpoint is removed. Switch integrations to `/v2/auth` before upgrading. | ||||
| 93 | + | ||||
| 94 | +Faster searches and single sign-on headline this release. | ||||
| 95 | + | ||||
| 96 | +### Added | ||||
| 97 | +- Sign in with your company account: SAML single sign-on is now available on all Team plans. | ||||
| 98 | + | ||||
| 99 | +### Improved | ||||
| 100 | +- Search is dramatically faster — most queries now return in about 0.2 seconds instead of nearly a second. | ||||
| 101 | + | ||||
| 102 | +### Fixed | ||||
| 103 | +- Fixed a crash when opening a profile that has no avatar. | ||||
| 104 | +``` | ||||
| 105 | + | ||||
| 106 | +Verified against the five house rules: benefit-first ✅, plain language ✅ | ||||
| 107 | +(no "leverage/robust/seamless", no ticket IDs), exact three-heading grouping ✅, | ||||
| 108 | +breaking change at top as blockquote ✅, noise cut ✅ (CI bump and refactor | ||||
| 109 | +correctly absent). | ||||
| 110 | + | ||||
| 111 | +### Sharp Skill Checklist | ||||
| 112 | + | ||||
| 113 | +| Item | Result | Note | | ||||
| 114 | +|---|---|---| | ||||
| 115 | +| A1–A3 description what+when, literal phrases, 3rd person, ≤1024 | ✅ | 418 chars | | ||||
| 116 | +| A4 no false positives | ✅ | commit messages / PR descriptions / API docs excluded | | ||||
| 117 | +| A5 name | ✅ | `writing-release-notes` (gerund) | | ||||
| 118 | +| B1 body <500 lines | ✅ | 68 lines | | ||||
| 119 | +| B3 one default | ✅ | single format, single grouping scheme | | ||||
| 120 | +| B4 workflow + self-review loop | ✅ | step 5 re-checks the five rules | | ||||
| 121 | +| B5 output fully specified | ✅ | template + delivery rule (chat vs CHANGELOG.md) | | ||||
| 122 | +| B6 positive AND negative examples | ✅ | in body + references/examples.md | | ||||
| 123 | +| B8 edge cases | ✅ | no user-visible changes / ambiguity / unknown version | | ||||
| 124 | +| C1–C2 references one level deep, TOC | ✅ | examples.md has TOC | | ||||
| 125 | +| C3–C4 scripts | ✅ (N/A) | prose-only skill by design (subjective domain) | | ||||
| 126 | +| D1–D4 tests incl. negative | ✅ | above | | ||||
| 127 | +| E1 headers | ✅ | linter-verified | | ||||
| 128 | + | ||||
| 129 | +**Boundary-condition check (mandated for the stylistic skill):** the skill | ||||
| 130 | +defines both an exclusion list AND a tie-breaker procedure (ask "end users or | ||||
| 131 | +engineers?") for ambiguous audience — pass. | ||||
| 132 | + | ||||
| 133 | +--- | ||||
| 134 | + | ||||
| 135 | +## Repository-wide mechanical validation | ||||
| 136 | + | ||||
| 137 | +`python3 tools/validate_skills.py .` enforces: frontmatter parses; name rules | ||||
| 138 | +(≤64 chars, charset, reserved words, folder match); description rules (≤1024, | ||||
| 139 | +"Use when" trigger clause, "Do not use" boundary, third person, no XML tags); | ||||
| 140 | +author header in every .md/.py/.sh file; body ≤500 lines; no broken reference | ||||
| 141 | +links; no backslash paths. | ||||
| 142 | + | ||||
| 143 | +### Final run (after all six collections were built) | ||||
| 144 | + | ||||
| 145 | +``` | ||||
| 146 | +$ python3 tools/validate_skills.py . | ||||
| 147 | +checked 72 skill(s) | ||||
| 148 | +all checks passed | ||||
| 149 | +``` | ||||
| 150 | + | ||||
| 151 | +Coverage: 2 example skills (Phase 2), 10 doc-skills, 10 frontend-skills, | ||||
| 152 | +10 db-skills, 20 backend-skills, 10 writing-skills, 10 finance-skills — | ||||
| 153 | +72 skills, 157 | ||||
| 154 | +markdown/Python files, every one carrying the author header and every | ||||
| 155 | +description carrying both a "Use when …" trigger clause and a | ||||
| 156 | +"Do not use …" boundary. | ||||
| 157 | + | ||||
| 158 | +**Failures caught by the linter during the build (all fixed):** | ||||
| 159 | +1. `name` vs mandated `skill-N-` folder prefix conflict → linter updated to | ||||
| 160 | + encode the project convention (see Skill 1 section). | ||||
| 161 | +2. Several transient broken-reference failures while build agents were | ||||
| 162 | + mid-write → re-checked after completion; all resolved. | ||||
| 163 | + | ||||
| 164 | +**Trigger de-confliction:** each collection README documents a pairwise | ||||
| 165 | +boundary table; every skill description names its nearest neighboring intent | ||||
| 166 | +and explicitly excludes it, so no request should plausibly fire two skills. | ||||
added
backend-skills/README.md
+63 −0
@@ -0,0 +1,63 @@ | |||||
| 1 | +<!-- | ||||
| 2 | +Author: Simon-Pierre Boucher | ||||
| 3 | +Contact: contact@spboucher.ai | ||||
| 4 | +--> | ||||
| 5 | + | ||||
| 6 | +# backend-skills — Backend Development Skill Collection | ||||
| 7 | + | ||||
| 8 | +**Author:** Simon-Pierre Boucher | ||||
| 9 | +**Contact:** contact@spboucher.ai | ||||
| 10 | + | ||||
| 11 | +Twenty ultra-sharp backend skills following the method in | ||||
| 12 | +[../RESEARCH-SYNTHESIS.md](../RESEARCH-SYNTHESIS.md). Database-specific work | ||||
| 13 | +lives in [../db-skills/](../db-skills/) — this collection deliberately | ||||
| 14 | +excludes it. All pass `python3 ../tools/validate_skills.py`. | ||||
| 15 | + | ||||
| 16 | +## The collection and its boundaries | ||||
| 17 | + | ||||
| 18 | +**API design** | ||||
| 19 | +| Skill | Handles | Explicitly does NOT handle | | ||||
| 20 | +|---|---|---| | ||||
| 21 | +| `designing-rest-apis` | resources, methods, status codes, pagination, versioning, problem+json, OpenAPI | GraphQL; webhooks; auth mechanics | | ||||
| 22 | +| `designing-graphql-apis` | schema design, DataLoader/N+1, cursor pagination, complexity limits | REST; subscriptions infra | | ||||
| 23 | +| `designing-webhooks` | payloads, HMAC signing, retries, idempotent delivery | consuming webhooks' business logic; internal messaging | | ||||
| 24 | +| `handling-file-uploads` | presigned URLs, validation, safe naming, streaming, resumable | serving files; image processing; CDNs | | ||||
| 25 | + | ||||
| 26 | +**Security (defensive)** | ||||
| 27 | +| Skill | Handles | Explicitly does NOT handle | | ||||
| 28 | +|---|---|---| | ||||
| 29 | +| `implementing-authentication` | password storage, sessions vs JWT, OAuth2/OIDC + PKCE, MFA, resets | permissions → `implementing-authorization`; DB creds → db-skills | | ||||
| 30 | +| `implementing-authorization` | RBAC/ABAC, deny-by-default, IDOR, tenant isolation, policy centralization | login/identity → `implementing-authentication` | | ||||
| 31 | +| `validating-input` | boundary schemas, allowlists, canonicalization, 422 responses | authn/authz; deep business rules | | ||||
| 32 | +| `securing-backend-services` | headers, TLS, dependencies, secrets, SSRF/CSRF, least privilege | login, permissions, input validation, DB security (see above) | | ||||
| 33 | + | ||||
| 34 | +**Reliability & operations** | ||||
| 35 | +| Skill | Handles | Explicitly does NOT handle | | ||||
| 36 | +|---|---|---| | ||||
| 37 | +| `handling-errors` | error taxonomy, problem+json mapping, retries, circuit breakers | validation responses; observability pipelines | | ||||
| 38 | +| `instrumenting-observability` | structured logs, correlation IDs, RED metrics, OpenTelemetry, alerting | error-handling code; incident process | | ||||
| 39 | +| `testing-backend-services` | test pyramid, testcontainers, contract tests, determinism | UI testing; load testing | | ||||
| 40 | +| `managing-configuration` | 12-factor env config, startup validation, secrets separation | infra provisioning; CI/CD config | | ||||
| 41 | + | ||||
| 42 | +**Async & performance** | ||||
| 43 | +| Skill | Handles | Explicitly does NOT handle | | ||||
| 44 | +|---|---|---| | ||||
| 45 | +| `writing-background-jobs` | idempotent jobs, retries/backoff, DLQ, timeouts | inter-service messaging; system cron | | ||||
| 46 | +| `handling-async-messaging` | outbox pattern, event versioning, ordering, poison messages | in-process job queues; customer webhooks | | ||||
| 47 | +| `caching-strategies` | cache-aside, TTL discipline, invalidation, stampede protection | CDN/edge config; SQL tuning → db-skills | | ||||
| 48 | +| `limiting-request-rates` | token bucket, 429/Retry-After, tiered limits, load shedding | authorization; capacity planning | | ||||
| 49 | + | ||||
| 50 | +**Infrastructure & architecture** | ||||
| 51 | +| Skill | Handles | Explicitly does NOT handle | | ||||
| 52 | +|---|---|---| | ||||
| 53 | +| `containerizing-services` | multi-stage builds, non-root, layer caching, health checks | Kubernetes; pipeline design | | ||||
| 54 | +| `shipping-with-ci-cd` | stages, build-once-promote, deploy strategies, rollback | writing tests; Dockerfiles | | ||||
| 55 | +| `architecting-service-boundaries` | modular monolith default, data ownership, sagas, strangler | messaging mechanics; endpoint design | | ||||
| 56 | +| `scaling-backend-services` | statelessness, pooling, load balancing, autoscaling, replicas | query tuning; caching; rate limiting | | ||||
| 57 | + | ||||
| 58 | +## Shared conventions | ||||
| 59 | + | ||||
| 60 | +- Description = WHAT + "Use when …" (literal phrases) + "Do not use for …" | ||||
| 61 | +- One default per decision with an escape hatch; concrete values over adjectives | ||||
| 62 | +- Every workflow ends with a validation step (replay a job twice, boot with a missing var, kill an instance mid-deploy) | ||||
| 63 | +- `SKILL.md` <150 lines; depth in `references/patterns.md` (with TOC) | ||||
added
backend-skills/architecting-service-boundaries/SKILL.md
+46 −0
@@ -0,0 +1,46 @@ | |||||
| 1 | +--- | ||||
| 2 | +name: architecting-service-boundaries | ||||
| 3 | +description: Decides how to split (or not split) a backend into services — modular monolith by default, boundaries along business capabilities, data ownership per service, sagas over distributed transactions, strangler-pattern extraction. Use when the user asks whether to adopt microservices, how to split a monolith, where service boundaries belong, how services should share data, or how to handle transactions across services. Do not use for message-broker mechanics (handling-async-messaging) or REST endpoint design (designing-rest-apis). | ||||
| 4 | +--- | ||||
| 5 | + | ||||
| 6 | +<!-- | ||||
| 7 | +Author: Simon-Pierre Boucher | ||||
| 8 | +Contact: contact@spboucher.ai | ||||
| 9 | +--> | ||||
| 10 | + | ||||
| 11 | +# Architecting Service Boundaries | ||||
| 12 | + | ||||
| 13 | +## When to use / when NOT to use | ||||
| 14 | +- **Use for:** monolith-vs-services decisions, drawing/reviewing service boundaries, cross-service data and transaction design, extraction plans. | ||||
| 15 | +- **Do NOT use for:** broker/queue mechanics (handling-async-messaging), endpoint design (designing-rest-apis), or infra scaling (scaling-backend-services). | ||||
| 16 | + | ||||
| 17 | +## Core rules | ||||
| 18 | + | ||||
| 19 | +1. **Default to a modular monolith.** One deployable with enforced internal module boundaries. Split a module out only on a proven trigger: independent scaling need, independent deploy cadence, or team-ownership friction — never "because microservices." | ||||
| 20 | +2. **Boundaries follow business capabilities, not technical layers.** | ||||
| 21 | + - ✅ `billing`, `catalog`, `fulfillment` | ||||
| 22 | + - ❌ `api-service`, `business-logic-service`, `database-service` | ||||
| 23 | +3. **Each service owns its data.** No other service reads its tables; integration happens through its API or published events. A shared database is one service wearing several trench coats. | ||||
| 24 | +4. **Keep synchronous call chains shallow — ~2 hops max.** Request → A → B is acceptable; A → B → C → D couples four uptimes and multiplies latency. Deeper flows go asynchronous via events. | ||||
| 25 | +5. **Distributed transactions are sagas.** A sequence of local transactions with explicit compensating actions for each step. Never reach for two-phase commit; design the compensation before the happy path. | ||||
| 26 | +6. **Contracts are versioned and backward compatible.** Additive changes only within a version; breaking changes get a new version with a deprecation window. A consumer must never be forced to deploy in lockstep with a provider. | ||||
| 27 | +7. **Extract with the strangler pattern.** Route a slice of traffic through the new service while the monolith still serves the rest; retire monolith code only after parity is proven. Big-bang rewrites forfeit the rollback path. | ||||
| 28 | +8. **Watch for the distributed monolith.** Services that must deploy together, share a schema, or break when one is down have all of the costs of microservices and none of the benefits — re-merge or re-draw the boundary. | ||||
| 29 | + | ||||
| 30 | +## Workflow | ||||
| 31 | + | ||||
| 32 | +1. List business capabilities and the teams that own them; sketch candidate boundaries there. | ||||
| 33 | +2. For each candidate split, name the concrete trigger from rule 1. No trigger → stays a module. | ||||
| 34 | +3. For each boundary: define the owned data, the exposed contract, and the events published. | ||||
| 35 | +4. Map every cross-boundary write flow as a saga with compensations (rule 5). | ||||
| 36 | +5. Plan extraction via strangler routing with a rollback switch (rule 7). | ||||
| 37 | +6. Validate the design: for each service, confirm it can deploy alone with every other service frozen, and survive (degraded, not down) any single dependency being offline. Any "no" is a rule-8 smell — redraw before building. | ||||
| 38 | + | ||||
| 39 | +## Edge cases & failure modes | ||||
| 40 | +- **Two services keep changing in the same PRs** → boundary is wrong; merge them or move the shared concept to one owner. | ||||
| 41 | +- **A service needs another's data constantly** → replicate via events into a local read model instead of chatty sync calls. | ||||
| 42 | +- **Reporting needs to join across services** → dedicated analytics store fed by events; never grant cross-service table access. | ||||
| 43 | +- **Saga compensation impossible** (e.g., email already sent) → make the step last, or design it to be semantically reversible ("correction" message). | ||||
| 44 | + | ||||
| 45 | +## References | ||||
| 46 | +Decision tables, saga and strangler sketches: see [references/patterns.md](references/patterns.md). | ||||
added
backend-skills/architecting-service-boundaries/references/patterns.md
+115 −0
@@ -0,0 +1,115 @@ | |||||
| 1 | +<!-- | ||||
| 2 | +Author: Simon-Pierre Boucher | ||||
| 3 | +Contact: contact@spboucher.ai | ||||
| 4 | +--> | ||||
| 5 | + | ||||
| 6 | +# Patterns — Architecting Service Boundaries | ||||
| 7 | + | ||||
| 8 | +## Contents | ||||
| 9 | +- Split/don't-split decision table | ||||
| 10 | +- Modular monolith enforcement | ||||
| 11 | +- Saga with compensations (order placement) | ||||
| 12 | +- Local read model via events | ||||
| 13 | +- Strangler extraction plan | ||||
| 14 | +- Distributed-monolith smell test | ||||
| 15 | +- Gotchas | ||||
| 16 | + | ||||
| 17 | +## Split/don't-split decision table | ||||
| 18 | + | ||||
| 19 | +| Signal | Verdict | | ||||
| 20 | +|---|---| | ||||
| 21 | +| Module needs 10× the replicas of the rest | Split (scaling trigger) | | ||||
| 22 | +| Module ships weekly while the rest ships daily and blocks it | Split (cadence trigger) | | ||||
| 23 | +| Two teams keep merge-conflicting in one module | Split (ownership trigger) | | ||||
| 24 | +| "It would be cleaner as a service" | Don't split | | ||||
| 25 | +| Junior team, no platform/on-call maturity | Don't split yet | | ||||
| 26 | +| Module is called by everything, in-process, latency-sensitive | Don't split | | ||||
| 27 | + | ||||
| 28 | +## Modular monolith enforcement | ||||
| 29 | + | ||||
| 30 | +Enforce boundaries inside one deployable so extraction stays cheap: | ||||
| 31 | + | ||||
| 32 | +``` | ||||
| 33 | +app/ | ||||
| 34 | +├── billing/ # public API: billing/api.py only | ||||
| 35 | +├── catalog/ | ||||
| 36 | +├── fulfillment/ | ||||
| 37 | +└── shared/ # pure utilities only — no business logic | ||||
| 38 | +``` | ||||
| 39 | + | ||||
| 40 | +- Each module exposes one façade (`billing/api.py`); cross-module imports of internals fail CI (import-linter / ArchUnit / eslint-boundaries). | ||||
| 41 | +- Each module gets its own schema/namespace in the database from day one — table ownership is then already settled when a split comes. | ||||
| 42 | + | ||||
| 43 | +## Saga with compensations (order placement) | ||||
| 44 | + | ||||
| 45 | +``` | ||||
| 46 | +Step Compensation | ||||
| 47 | +1. reserve inventory → release reservation | ||||
| 48 | +2. charge payment → refund payment | ||||
| 49 | +3. create shipment → cancel shipment | ||||
| 50 | +4. send confirmation → (terminal; deliberately last — cannot be unsent) | ||||
| 51 | +``` | ||||
| 52 | + | ||||
| 53 | +```python | ||||
| 54 | +SAGA = [ | ||||
| 55 | + (reserve_inventory, release_reservation), | ||||
| 56 | + (charge_payment, refund_payment), | ||||
| 57 | + (create_shipment, cancel_shipment), | ||||
| 58 | + (send_confirmation, None), # irreversible step goes last | ||||
| 59 | +] | ||||
| 60 | + | ||||
| 61 | +def run_saga(order): | ||||
| 62 | + done = [] | ||||
| 63 | + for step, compensate in SAGA: | ||||
| 64 | + try: | ||||
| 65 | + step(order) | ||||
| 66 | + done.append(compensate) | ||||
| 67 | + except StepFailed: | ||||
| 68 | + for comp in reversed(done): # unwind in reverse order | ||||
| 69 | + if comp: | ||||
| 70 | + comp(order) | ||||
| 71 | + raise | ||||
| 72 | +``` | ||||
| 73 | + | ||||
| 74 | +Orchestration (one coordinator, above) is the default — easier to trace and test. | ||||
| 75 | +Choreography (each service reacts to events) is the escape hatch when the | ||||
| 76 | +coordinator itself becomes a coupling point. | ||||
| 77 | + | ||||
| 78 | +## Local read model via events | ||||
| 79 | + | ||||
| 80 | +Instead of `fulfillment` calling `catalog` on every shipment: | ||||
| 81 | + | ||||
| 82 | +``` | ||||
| 83 | +catalog --publishes--> product.updated {id, weight, dims} | ||||
| 84 | +fulfillment --consumes--> upserts into its own product_dimensions table | ||||
| 85 | +``` | ||||
| 86 | + | ||||
| 87 | +Fulfillment reads locally (fast, survives catalog downtime); staleness is | ||||
| 88 | +bounded by event lag and must be acceptable for the use case — if it isn't, | ||||
| 89 | +the data wasn't yours to cache and the boundary may be wrong. | ||||
| 90 | + | ||||
| 91 | +## Strangler extraction plan | ||||
| 92 | + | ||||
| 93 | +1. Freeze feature work in the monolith module being extracted. | ||||
| 94 | +2. Stand up the new service; dual-write or replay events to fill its store. | ||||
| 95 | +3. Route N% of read traffic via a routing layer flag; compare responses (shadow diff). | ||||
| 96 | +4. Ramp N → 100 with the rollback switch live at every step. | ||||
| 97 | +5. Cut writes over behind the same flag; verify; delete monolith code path within a sprint (a dormant duplicate path rots into rule-8 territory). | ||||
| 98 | + | ||||
| 99 | +## Distributed-monolith smell test | ||||
| 100 | + | ||||
| 101 | +Answer for each service; any "yes" means redraw: | ||||
| 102 | + | ||||
| 103 | +- Must it deploy in the same release train as another service? | ||||
| 104 | +- Does another service read or write its tables? | ||||
| 105 | +- Does one service's outage take it fully down (not degraded)? | ||||
| 106 | +- Do contract changes require synchronized PRs across repos? | ||||
| 107 | + | ||||
| 108 | +## Gotchas | ||||
| 109 | + | ||||
| 110 | +- **Entity-shaped services** (`user-service` that everyone calls for everything) recreate the shared database over HTTP; split by capability (identity vs profile vs preferences). | ||||
| 111 | +- **Shared libraries with business logic** are a hidden shared schema — a change forces lockstep upgrades. Keep shared libs to pure utilities. | ||||
| 112 | +- **Sagas are not ACID**: intermediate states are visible. Model them explicitly (`PENDING_PAYMENT`), don't pretend isolation. | ||||
| 113 | +- **Compensation ≠ undo**: refund is a new transaction, not an erasure — books must show both. | ||||
| 114 | +- **Event schemas are contracts too** — version them like APIs (rule 6), with consumers tolerant of unknown fields. | ||||
| 115 | +- **The routing flag is load-bearing** during strangler cuts; it needs tests and an owner like any production code. | ||||
added
backend-skills/caching-strategies/SKILL.md
+50 −0
@@ -0,0 +1,50 @@ | |||||
| 1 | +--- | ||||
| 2 | +name: caching-strategies | ||||
| 3 | +description: Designs application-level caching — cache-aside pattern, key naming, TTL policy, invalidation strategy, and stampede protection with Redis or in-process caches. Use when the user asks to add a cache, cache API responses or query results, speed up repeated reads, fix stale-cache or cache-invalidation bugs, or prevent cache stampedes. Do not use for HTTP/CDN edge caching configuration or for database query and index tuning. | ||||
| 4 | +--- | ||||
| 5 | + | ||||
| 6 | +<!-- | ||||
| 7 | +Author: Simon-Pierre Boucher | ||||
| 8 | +Contact: contact@spboucher.ai | ||||
| 9 | +--> | ||||
| 10 | + | ||||
| 11 | +# Caching Strategies | ||||
| 12 | + | ||||
| 13 | +## When to use / when NOT to use | ||||
| 14 | +- **Use for:** adding or reviewing application caches (Redis, Memcached, in-process): what to cache, key design, TTLs, invalidation, failure behavior. | ||||
| 15 | +- **Do NOT use for:** CDN/`Cache-Control` edge configuration, database tuning (→ db-skills/optimizing-sql-performance), or memoizing pure functions in code. | ||||
| 16 | + | ||||
| 17 | +## Core rules | ||||
| 18 | + | ||||
| 19 | +1. **Measure before caching.** Cache only reads that are demonstrably hot AND expensive; record the baseline latency and expected hit rate first. A cache below ~80% hit rate usually adds complexity for nothing. | ||||
| 20 | +2. **Cache-aside is the default pattern:** read cache → miss → read origin → write cache with TTL. Write-through/write-behind only when a measured write-path need exists. | ||||
| 21 | +3. **Every key has a TTL — no immortal keys.** TTL is the invalidation of last resort; without it, every bug becomes permanent. | ||||
| 22 | + - ✅ `SETEX product:v1:42 300 …` | ||||
| 23 | + - ❌ `SET product:42 …` (lives until someone remembers it exists) | ||||
| 24 | +4. **Choose the invalidation strategy per data class, at design time:** | ||||
| 25 | + - tolerates staleness → **TTL-only** (pick the tolerance as the TTL) | ||||
| 26 | + - must reflect writes → **purge/update on write** (delete the key in the write path) | ||||
| 27 | + - broad derived data → **versioned keys** (bump `v` in the key; old entries age out) | ||||
| 28 | + Mixing strategies ad hoc is how stale-forever bugs are born. | ||||
| 29 | +5. **Key schema is part of the design:** `entity:version:id[:variant]`, e.g. `product:v2:42:fr`. Every dimension that changes the value appears in the key — locale, currency, role. | ||||
| 30 | +6. **Never serve one user's data from a shared key.** Per-user data gets the user ID in the key; better, don't cache authorization decisions at all. | ||||
| 31 | + - ❌ `GET profile:current` — whoever primed it wins | ||||
| 32 | +7. **Protect against stampedes:** jitter TTLs (±10%) so keys don't expire in sync, and use a single-flight lock so one process rebuilds a hot key while others serve slightly-stale or wait. | ||||
| 33 | +8. **Cache down ≠ site down.** Wrap cache calls with a short timeout (~50 ms) and fall through to origin on any cache error; a cache outage becomes a latency event, not an availability event. | ||||
| 34 | + | ||||
| 35 | +## Workflow | ||||
| 36 | + | ||||
| 37 | +1. Identify the hot, expensive read; record baseline latency and expected hit rate. | ||||
| 38 | +2. Classify its data (staleness tolerance) and pick the invalidation strategy from rule 4. | ||||
| 39 | +3. Define the key schema and TTL (+ jitter); implement cache-aside with origin fallback on cache errors. | ||||
| 40 | +4. Add hit/miss metrics per key family. | ||||
| 41 | +5. **Validate:** write to the origin and confirm the read path reflects it within the chosen tolerance; kill the cache and confirm requests still succeed from origin; check hit rate after a warm-up period against the target. | ||||
| 42 | + | ||||
| 43 | +## Edge cases & failure modes | ||||
| 44 | +- **Caching negative results** (not-found) — allowed with a SHORT TTL (~30 s) to absorb miss storms, but must be purged on create. | ||||
| 45 | +- **Large values** (>100 KB in Redis) — compress or split; big values evict everything else. | ||||
| 46 | +- **Cold start after deploy/flush** — expect an origin load spike; single-flight (rule 7) is what keeps it survivable. | ||||
| 47 | +- **Two caches for one datum** (in-process + Redis) — layered TTLs multiply staleness; keep the in-process layer very short (~1–5 s). | ||||
| 48 | + | ||||
| 49 | +## References | ||||
| 50 | +Snippets for single-flight, jitter, and key-schema helpers: see [references/patterns.md](references/patterns.md). | ||||
added
backend-skills/caching-strategies/references/patterns.md
+124 −0
@@ -0,0 +1,124 @@ | |||||
| 1 | +<!-- | ||||
| 2 | +Author: Simon-Pierre Boucher | ||||
| 3 | +Contact: contact@spboucher.ai | ||||
| 4 | +--> | ||||
| 5 | + | ||||
| 6 | +# Patterns — Caching | ||||
| 7 | + | ||||
| 8 | +## Contents | ||||
| 9 | +- Cache-aside with fallback | ||||
| 10 | +- TTL jitter | ||||
| 11 | +- Single-flight rebuild | ||||
| 12 | +- Versioned keys | ||||
| 13 | +- Purge-on-write | ||||
| 14 | +- Negative caching | ||||
| 15 | +- Metrics | ||||
| 16 | +- Gotchas | ||||
| 17 | + | ||||
| 18 | +## Cache-aside with fallback | ||||
| 19 | + | ||||
| 20 | +```python | ||||
| 21 | +import json, random | ||||
| 22 | + | ||||
| 23 | +CACHE_TIMEOUT_S = 0.05 # cache slower than 50 ms is worse than origin | ||||
| 24 | +TTL_S = 300 # staleness tolerance for this data class | ||||
| 25 | + | ||||
| 26 | +def get_product(pid: int): | ||||
| 27 | + key = f"product:v2:{pid}" | ||||
| 28 | + try: | ||||
| 29 | + raw = redis.get(key) # timeout=CACHE_TIMEOUT_S on the client | ||||
| 30 | + if raw is not None: | ||||
| 31 | + return json.loads(raw) | ||||
| 32 | + except RedisError: | ||||
| 33 | + pass # cache down → serve from origin | ||||
| 34 | + value = db.fetch_product(pid) | ||||
| 35 | + try: | ||||
| 36 | + redis.setex(key, jittered(TTL_S), json.dumps(value)) | ||||
| 37 | + except RedisError: | ||||
| 38 | + pass # failing to cache is not an error | ||||
| 39 | + return value | ||||
| 40 | +``` | ||||
| 41 | + | ||||
| 42 | +## TTL jitter | ||||
| 43 | + | ||||
| 44 | +```python | ||||
| 45 | +def jittered(ttl: int) -> int: | ||||
| 46 | + return int(ttl * random.uniform(0.9, 1.1)) # ±10% desynchronizes expiry | ||||
| 47 | +``` | ||||
| 48 | + | ||||
| 49 | +## Single-flight rebuild | ||||
| 50 | + | ||||
| 51 | +```python | ||||
| 52 | +LOCK_TTL_S = 10 # > rebuild p99 so a crashed builder's lock self-clears | ||||
| 53 | + | ||||
| 54 | +def get_report(rid: str): | ||||
| 55 | + key = f"report:v1:{rid}" | ||||
| 56 | + raw = redis.get(key) | ||||
| 57 | + if raw is not None: | ||||
| 58 | + return json.loads(raw) | ||||
| 59 | + if redis.set(f"lock:{key}", "1", nx=True, ex=LOCK_TTL_S): | ||||
| 60 | + value = build_report(rid) # only this process rebuilds | ||||
| 61 | + redis.setex(key, jittered(600), json.dumps(value)) | ||||
| 62 | + redis.delete(f"lock:{key}") | ||||
| 63 | + return value | ||||
| 64 | + time.sleep(0.1) # others: brief wait then retry once | ||||
| 65 | + raw = redis.get(key) | ||||
| 66 | + return json.loads(raw) if raw else build_report(rid) # last resort: origin | ||||
| 67 | +``` | ||||
| 68 | + | ||||
| 69 | +## Versioned keys | ||||
| 70 | + | ||||
| 71 | +```python | ||||
| 72 | +# Invalidate a whole family by bumping the version constant in code — | ||||
| 73 | +# no scan-and-delete, old entries simply age out via TTL. | ||||
| 74 | +PRODUCT_CACHE_V = 3 | ||||
| 75 | +key = f"product:v{PRODUCT_CACHE_V}:{pid}:{locale}" | ||||
| 76 | +``` | ||||
| 77 | + | ||||
| 78 | +## Purge-on-write | ||||
| 79 | + | ||||
| 80 | +```python | ||||
| 81 | +def update_product(pid: int, fields: dict): | ||||
| 82 | + db.update_product(pid, fields) | ||||
| 83 | + try: | ||||
| 84 | + redis.delete(f"product:v2:{pid}") # delete, don't rewrite: the next | ||||
| 85 | + except RedisError: # read repopulates from fresh origin | ||||
| 86 | + log.warning("purge failed for %s — TTL is the backstop", pid) | ||||
| 87 | +``` | ||||
| 88 | + | ||||
| 89 | +Delete (not set) after write: writing the new value here races concurrent | ||||
| 90 | +readers repopulating from a stale read replica. | ||||
| 91 | + | ||||
| 92 | +## Negative caching | ||||
| 93 | + | ||||
| 94 | +```python | ||||
| 95 | +NEG_TTL_S = 30 # short: absorbs miss storms without delaying creates for long | ||||
| 96 | + | ||||
| 97 | +if value is None: | ||||
| 98 | + redis.setex(key, NEG_TTL_S, "__miss__") | ||||
| 99 | +# On create: redis.delete(key) so the sentinel dies immediately. | ||||
| 100 | +``` | ||||
| 101 | + | ||||
| 102 | +## Metrics | ||||
| 103 | + | ||||
| 104 | +```python | ||||
| 105 | +metrics.incr(f"cache.{family}.{'hit' if raw is not None else 'miss'}") | ||||
| 106 | +``` | ||||
| 107 | + | ||||
| 108 | +Alert when hit rate for a family drops below its target (~80%) — usually a | ||||
| 109 | +key-schema change or an invalidation bug, not traffic. | ||||
| 110 | + | ||||
| 111 | +## Gotchas | ||||
| 112 | + | ||||
| 113 | +- **Caching the serialized response of another cache-user** stacks TTLs; | ||||
| 114 | + staleness = sum of layers, not max. | ||||
| 115 | +- **`KEYS pattern*` for invalidation** blocks Redis; that need is the signal | ||||
| 116 | + to switch to versioned keys. | ||||
| 117 | +- **Thundering read-repair after purge-on-write**: hot keys need | ||||
| 118 | + single-flight even with purge-on-write. | ||||
| 119 | +- **Objects that serialize differently across app versions** poison shared | ||||
| 120 | + caches during deploys — version the key (rule 5) on format changes. | ||||
| 121 | +- **Read replicas + purge-on-write**: purge, then repopulate-on-read may | ||||
| 122 | + read a stale replica and resurrect old data; short TTL bounds the damage. | ||||
| 123 | +- **In-process caches in autoscaled fleets** are N independent staleness | ||||
| 124 | + bubbles; keep them ≤5 s or accept per-instance divergence. | ||||
added
backend-skills/containerizing-services/SKILL.md
+50 −0
@@ -0,0 +1,50 @@ | |||||
| 1 | +--- | ||||
| 2 | +name: containerizing-services | ||||
| 3 | +description: Containerizes backend services with production-grade Dockerfiles — multi-stage builds, pinned base images, non-root users, healthchecks, and small images. Use when the user asks to write or review a Dockerfile, dockerize or containerize a service or app, shrink a Docker image, or fix container build or security issues. Do not use for Kubernetes manifests or orchestration, docker-compose service topology, or CI pipeline design (shipping-with-ci-cd). | ||||
| 4 | +--- | ||||
| 5 | + | ||||
| 6 | +<!-- | ||||
| 7 | +Author: Simon-Pierre Boucher | ||||
| 8 | +Contact: contact@spboucher.ai | ||||
| 9 | +--> | ||||
| 10 | + | ||||
| 11 | +# Containerizing Services | ||||
| 12 | + | ||||
| 13 | +## When to use / when NOT to use | ||||
| 14 | +- **Use for:** writing or reviewing Dockerfiles, image size/security hardening, build-cache problems. | ||||
| 15 | +- **Do NOT use for:** Kubernetes/orchestration, compose topology, CI pipelines (shipping-with-ci-cd), or app code changes. | ||||
| 16 | + | ||||
| 17 | +## Core rules | ||||
| 18 | + | ||||
| 19 | +1. **Multi-stage: build heavy, run slim.** Compile/install in a build stage; copy only artifacts into the runtime stage. | ||||
| 20 | + - ✅ `FROM node:22.4-slim AS build` … `FROM node:22.4-slim` + `COPY --from=build /app/dist ./dist` | ||||
| 21 | + - ❌ One stage that ships compilers, dev deps, and source history. | ||||
| 22 | +2. **Pin base images.** Exact tag minimum; digest for production. | ||||
| 23 | + - ✅ `FROM python:3.12.4-slim@sha256:…` | ||||
| 24 | + - ❌ `FROM python:latest` | ||||
| 25 | +3. **Run as non-root.** Create a user and switch: `USER app`. Root-only containers fail most cluster policies and widen every exploit. | ||||
| 26 | +4. **Order layers for cache.** Copy dependency manifests and install BEFORE copying source, so code edits don't bust the dependency layer. | ||||
| 27 | + - ✅ `COPY package*.json ./` → `RUN npm ci` → `COPY . .` | ||||
| 28 | + - ❌ `COPY . .` first (every commit reinstalls everything). | ||||
| 29 | +5. **One process per container.** No supervisord bundles; sidecars belong to the orchestrator. Logs go to stdout/stderr only — never files inside the container. | ||||
| 30 | +6. **Ship a `.dockerignore`.** At minimum: `.git`, `node_modules`/venvs, `.env*`, secrets, test fixtures. Never `COPY` a secret; pass at runtime. | ||||
| 31 | +7. **Define `HEALTHCHECK`** (or document the orchestrator probe) hitting a real readiness endpoint, not `/`. | ||||
| 32 | +8. **Default slim/distroless; alpine only knowingly.** musl breaks some native wheels/binaries — use alpine only after the app is verified on it. | ||||
| 33 | + | ||||
| 34 | +## Workflow | ||||
| 35 | + | ||||
| 36 | +1. Pick runtime base (`<lang>:<exact-version>-slim` default; distroless escape hatch for static binaries). | ||||
| 37 | +2. Write `.dockerignore` before the Dockerfile. | ||||
| 38 | +3. Write multi-stage Dockerfile per rules 1–8. | ||||
| 39 | +4. Build: `docker build -t svc:dev .` — rebuild after touching one source file and confirm the dependency layer is cached (`CACHED` in output). | ||||
| 40 | +5. Validate: `docker run --rm svc:dev id -u` returns non-zero UID; `docker inspect --format='{{.Config.Healthcheck}}' svc:dev` is set; `docker run --rm --read-only svc:dev` starts (add tmpfs mounts if the app needs scratch dirs). | ||||
| 41 | +6. Check size: `docker images svc:dev` — if the runtime image exceeds ~2× the artifact size, find what leaked in (`docker history svc:dev`). | ||||
| 42 | + | ||||
| 43 | +## Edge cases & failure modes | ||||
| 44 | +- **Native deps fail on slim** → install build tools in the build stage only (`apt-get install -y --no-install-recommends build-essential`), never in runtime. | ||||
| 45 | +- **Secrets needed at build time** → `RUN --mount=type=secret,id=npm_token …` (BuildKit); never `ARG` a secret (it persists in history). | ||||
| 46 | +- **Image works locally, fails in cluster as non-root** → files owned by root; `COPY --chown=app:app`. | ||||
| 47 | +- **Timezone/CA errors in distroless** → use the `:debug` or cc variant, or copy `ca-certificates` from the build stage. | ||||
| 48 | + | ||||
| 49 | +## References | ||||
| 50 | +Deeper recipes and gotchas: see [references/patterns.md](references/patterns.md). | ||||
added
backend-skills/containerizing-services/references/patterns.md
+135 −0
@@ -0,0 +1,135 @@ | |||||
| 1 | +<!-- | ||||
| 2 | +Author: Simon-Pierre Boucher | ||||
| 3 | +Contact: contact@spboucher.ai | ||||
| 4 | +--> | ||||
| 5 | + | ||||
| 6 | +# Patterns — Containerizing Services | ||||
| 7 | + | ||||
| 8 | +## Contents | ||||
| 9 | +- Node.js multi-stage Dockerfile | ||||
| 10 | +- Python multi-stage Dockerfile | ||||
| 11 | +- Go static binary + distroless | ||||
| 12 | +- .dockerignore baseline | ||||
| 13 | +- Build-time secrets (BuildKit) | ||||
| 14 | +- Runtime hardening flags | ||||
| 15 | +- Gotchas | ||||
| 16 | + | ||||
| 17 | +## Node.js multi-stage Dockerfile | ||||
| 18 | + | ||||
| 19 | +```dockerfile | ||||
| 20 | +# syntax=docker/dockerfile:1 | ||||
| 21 | +FROM node:22.4-slim AS build | ||||
| 22 | +WORKDIR /app | ||||
| 23 | +COPY package*.json ./ | ||||
| 24 | +RUN npm ci | ||||
| 25 | +COPY . . | ||||
| 26 | +RUN npm run build && npm prune --omit=dev | ||||
| 27 | + | ||||
| 28 | +FROM node:22.4-slim | ||||
| 29 | +ENV NODE_ENV=production | ||||
| 30 | +WORKDIR /app | ||||
| 31 | +RUN useradd --uid 10001 --create-home app | ||||
| 32 | +COPY --from=build --chown=app:app /app/node_modules ./node_modules | ||||
| 33 | +COPY --from=build --chown=app:app /app/dist ./dist | ||||
| 34 | +USER app | ||||
| 35 | +EXPOSE 3000 | ||||
| 36 | +HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ | ||||
| 37 | + CMD node -e "fetch('http://127.0.0.1:3000/healthz').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))" | ||||
| 38 | +CMD ["node", "dist/server.js"] | ||||
| 39 | +``` | ||||
| 40 | + | ||||
| 41 | +## Python multi-stage Dockerfile | ||||
| 42 | + | ||||
| 43 | +```dockerfile | ||||
| 44 | +# syntax=docker/dockerfile:1 | ||||
| 45 | +FROM python:3.12.4-slim AS build | ||||
| 46 | +WORKDIR /app | ||||
| 47 | +RUN apt-get update && apt-get install -y --no-install-recommends build-essential \ | ||||
| 48 | + && rm -rf /var/lib/apt/lists/* | ||||
| 49 | +COPY requirements.txt . | ||||
| 50 | +RUN pip install --no-cache-dir --prefix=/install -r requirements.txt | ||||
| 51 | + | ||||
| 52 | +FROM python:3.12.4-slim | ||||
| 53 | +WORKDIR /app | ||||
| 54 | +RUN useradd --uid 10001 --create-home app | ||||
| 55 | +COPY --from=build /install /usr/local | ||||
| 56 | +COPY --chown=app:app . . | ||||
| 57 | +USER app | ||||
| 58 | +EXPOSE 8000 | ||||
| 59 | +HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ | ||||
| 60 | + CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/healthz').status==200 else 1)" | ||||
| 61 | +CMD ["gunicorn", "-b", "0.0.0.0:8000", "app.wsgi:application"] | ||||
| 62 | +``` | ||||
| 63 | + | ||||
| 64 | +## Go static binary + distroless | ||||
| 65 | + | ||||
| 66 | +```dockerfile | ||||
| 67 | +# syntax=docker/dockerfile:1 | ||||
| 68 | +FROM golang:1.23.1 AS build | ||||
| 69 | +WORKDIR /src | ||||
| 70 | +COPY go.mod go.sum ./ | ||||
| 71 | +RUN go mod download | ||||
| 72 | +COPY . . | ||||
| 73 | +RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /out/svc ./cmd/svc | ||||
| 74 | + | ||||
| 75 | +FROM gcr.io/distroless/static-debian12:nonroot | ||||
| 76 | +COPY --from=build /out/svc /svc | ||||
| 77 | +EXPOSE 8080 | ||||
| 78 | +ENTRYPOINT ["/svc"] | ||||
| 79 | +``` | ||||
| 80 | + | ||||
| 81 | +Distroless has no shell — the orchestrator's HTTP probe replaces `HEALTHCHECK`. | ||||
| 82 | + | ||||
| 83 | +## .dockerignore baseline | ||||
| 84 | + | ||||
| 85 | +``` | ||||
| 86 | +.git | ||||
| 87 | +.gitignore | ||||
| 88 | +.env* | ||||
| 89 | +*.md | ||||
| 90 | +node_modules | ||||
| 91 | +__pycache__ | ||||
| 92 | +*.pyc | ||||
| 93 | +.venv | ||||
| 94 | +dist | ||||
| 95 | +coverage | ||||
| 96 | +tests/fixtures | ||||
| 97 | +Dockerfile | ||||
| 98 | +``` | ||||
| 99 | + | ||||
| 100 | +Remove `dist` from the ignore list if you COPY prebuilt artifacts instead of building in-image. | ||||
| 101 | + | ||||
| 102 | +## Build-time secrets (BuildKit) | ||||
| 103 | + | ||||
| 104 | +```dockerfile | ||||
| 105 | +RUN --mount=type=secret,id=pip_index \ | ||||
| 106 | + PIP_INDEX_URL=$(cat /run/secrets/pip_index) pip install -r requirements.txt | ||||
| 107 | +``` | ||||
| 108 | + | ||||
| 109 | +```bash | ||||
| 110 | +docker build --secret id=pip_index,src=.pip_index_url . | ||||
| 111 | +``` | ||||
| 112 | + | ||||
| 113 | +Never `ARG TOKEN` — args are recoverable via `docker history`. | ||||
| 114 | + | ||||
| 115 | +## Runtime hardening flags | ||||
| 116 | + | ||||
| 117 | +```bash | ||||
| 118 | +docker run --rm \ | ||||
| 119 | + --read-only --tmpfs /tmp \ | ||||
| 120 | + --cap-drop ALL \ | ||||
| 121 | + --security-opt no-new-privileges \ | ||||
| 122 | + -p 8000:8000 svc:prod | ||||
| 123 | +``` | ||||
| 124 | + | ||||
| 125 | +Start from all-dropped and add back only what breaks. | ||||
| 126 | + | ||||
| 127 | +## Gotchas | ||||
| 128 | + | ||||
| 129 | +- **`COPY --from` keeps root ownership** unless `--chown` is given — the classic "works as root, crashes as USER app" cause. | ||||
| 130 | +- **alpine + Python wheels**: musl forces source builds of numpy/psycopg2 etc.; use `-slim` (glibc) unless you've verified alpine. | ||||
| 131 | +- **`EXPOSE` documents, it does not publish** — publishing is `-p`/orchestrator config. | ||||
| 132 | +- **apt cache bloat**: always `rm -rf /var/lib/apt/lists/*` in the same `RUN` as the install, or the cache lands in the layer anyway. | ||||
| 133 | +- **CMD shell form (`CMD node server.js`) wraps in `/bin/sh`** → PID 1 is sh, signals (SIGTERM) never reach the app → 10s kill delay on every deploy. Use exec form (JSON array). | ||||
| 134 | +- **`HEALTHCHECK` in Dockerfile is ignored by Kubernetes** — it uses its own probes; keep both consistent. | ||||
| 135 | +- **Bind-mounting over image content in dev** hides image bugs; test the real image before shipping. | ||||
added
backend-skills/designing-graphql-apis/SKILL.md
+48 −0
@@ -0,0 +1,48 @@ | |||||
| 1 | +--- | ||||
| 2 | +name: designing-graphql-apis | ||||
| 3 | +description: Designs GraphQL APIs with schema-first typing, Relay-style cursor connections, DataLoader batching against N+1 queries, union result types for expected errors, and depth/complexity limits. Use when the user asks to design, review, or refactor a GraphQL schema or API, write type definitions or resolvers, add pagination to a GraphQL query, fix N+1 resolver performance, or structure mutations. Do not use for REST APIs (designing-rest-apis) or for real-time subscription transport infrastructure. | ||||
| 4 | +--- | ||||
| 5 | + | ||||
| 6 | +<!-- | ||||
| 7 | +Author: Simon-Pierre Boucher | ||||
| 8 | +Contact: contact@spboucher.ai | ||||
| 9 | +--> | ||||
| 10 | + | ||||
| 11 | +# Designing GraphQL APIs | ||||
| 12 | + | ||||
| 13 | +## When to use / when NOT to use | ||||
| 14 | +- **Use for:** GraphQL schema design, type/resolver structure, connections, mutations, error modeling, query cost control. | ||||
| 15 | +- **Do NOT use for:** REST endpoint design → `designing-rest-apis`; WebSocket/subscription transport setup; client-side query writing. | ||||
| 16 | + | ||||
| 17 | +## Core rules | ||||
| 18 | + | ||||
| 19 | +1. **Schema first, domain-shaped.** Design the SDL from the domain and client needs, then implement resolvers. Never mirror database tables 1:1 — expose what clients consume. | ||||
| 20 | + - ✅ `type Order { total: Money!, placedAt: DateTime! }` | ||||
| 21 | + - ❌ `type OrdersTbl { amt_cents: Int, created_ts: Int }` | ||||
| 22 | +2. **Nullable by default; non-null (`!`) only when guaranteed.** A non-null field that fails nulls out its whole parent chain. Reserve `!` for IDs and fields the resolver can never fail to produce. | ||||
| 23 | +3. **Paginate lists with Relay connections.** Any list that can grow gets `Connection`/`Edge`/`PageInfo` with `first`/`after` cursor args — never a bare unbounded `[Order!]!`. | ||||
| 24 | +4. **DataLoader is the default N+1 fix.** Every resolver that fetches by ID goes through a per-request DataLoader that batches and caches. One query per collection of parents, not one per parent. | ||||
| 25 | +5. **Mutations: verb-object names, one input, one payload.** `createOrder(input: CreateOrderInput!): CreateOrderPayload!`. Payload contains the changed object plus expected-error fields — never return the bare object. | ||||
| 26 | +6. **Expected errors are union result types; the `errors` array is for exceptions only.** | ||||
| 27 | + - ✅ `union CreateOrderResult = Order | ValidationError | OutOfStockError` | ||||
| 28 | + - ❌ throwing for "email already taken" so clients parse `errors[0].message` | ||||
| 29 | +7. **Cap query cost.** Enforce a depth limit (default 10) and a complexity budget (points per field × list multipliers); reject over-budget queries before execution. | ||||
| 30 | +8. **Additive evolution only.** Add fields freely; never remove or change a type in place — `@deprecated(reason: "...")` first, remove in a coordinated major cycle. | ||||
| 31 | + | ||||
| 32 | +## Workflow | ||||
| 33 | + | ||||
| 34 | +1. List client use cases; sketch queries clients should be able to write. | ||||
| 35 | +2. Write the SDL: types, connections (rule 3), mutations (rule 5), error unions (rule 6). | ||||
| 36 | +3. Apply nullability discipline (rule 2) type by type. | ||||
| 37 | +4. Plan resolvers: mark every by-ID fetch as a DataLoader (rule 4). | ||||
| 38 | +5. Set depth/complexity limits (rule 7). | ||||
| 39 | +6. **Validate:** run the schema through a linter (`npx graphql-schema-linter schema.graphql` — install with `npm i -g graphql-schema-linter` if missing), execute the sketched queries from step 1 against a stub server, and log SQL for one nested query to confirm no N+1 (query count must be O(depth), not O(rows)). Fix and repeat. | ||||
| 40 | + | ||||
| 41 | +## Edge cases & failure modes | ||||
| 42 | +- **A field that is expensive for some parents** → split it (`Order.summary` cheap, `Order.analytics` separate type) so cheap queries don't pay. | ||||
| 43 | +- **Polymorphic lists** → interfaces when types share fields, unions when they don't; always include `__typename` handling in examples. | ||||
| 44 | +- **File uploads** → do not tunnel through GraphQL; issue a presigned URL via mutation and upload out-of-band. | ||||
| 45 | +- **Global object identity** → give every fetchable type a globally unique `id: ID!` (base64 `Type:dbId`) so caches and `node(id:)` refetching work. | ||||
| 46 | + | ||||
| 47 | +## References | ||||
| 48 | +Deeper patterns (connection SDL, DataLoader implementation, complexity scoring): see [references/patterns.md](references/patterns.md). | ||||
added
backend-skills/designing-graphql-apis/references/patterns.md
+127 −0
@@ -0,0 +1,127 @@ | |||||
| 1 | +<!-- | ||||
| 2 | +Author: Simon-Pierre Boucher | ||||
| 3 | +Contact: contact@spboucher.ai | ||||
| 4 | +--> | ||||
| 5 | + | ||||
| 6 | +# Patterns — Designing GraphQL APIs | ||||
| 7 | + | ||||
| 8 | +## Contents | ||||
| 9 | +- Connection SDL (Relay style) | ||||
| 10 | +- Mutation with union result | ||||
| 11 | +- DataLoader implementation | ||||
| 12 | +- Depth and complexity limits | ||||
| 13 | +- Deprecation flow | ||||
| 14 | +- Gotchas | ||||
| 15 | + | ||||
| 16 | +## Connection SDL (Relay style) | ||||
| 17 | + | ||||
| 18 | +```graphql | ||||
| 19 | +type Query { | ||||
| 20 | + orders(first: Int = 20, after: String, status: OrderStatus): OrderConnection! | ||||
| 21 | +} | ||||
| 22 | + | ||||
| 23 | +type OrderConnection { | ||||
| 24 | + edges: [OrderEdge!]! | ||||
| 25 | + pageInfo: PageInfo! | ||||
| 26 | + totalCount: Int # nullable: expensive, resolve only when asked | ||||
| 27 | +} | ||||
| 28 | + | ||||
| 29 | +type OrderEdge { | ||||
| 30 | + node: Order! | ||||
| 31 | + cursor: String! | ||||
| 32 | +} | ||||
| 33 | + | ||||
| 34 | +type PageInfo { | ||||
| 35 | + hasNextPage: Boolean! | ||||
| 36 | + endCursor: String | ||||
| 37 | +} | ||||
| 38 | +``` | ||||
| 39 | +Clamp `first` to a max of 100 in the resolver (same reasoning as REST page | ||||
| 40 | +limits); reject negative values with a validation error. | ||||
| 41 | + | ||||
| 42 | +## Mutation with union result | ||||
| 43 | + | ||||
| 44 | +```graphql | ||||
| 45 | +input CreateOrderInput { | ||||
| 46 | + customerId: ID! | ||||
| 47 | + items: [OrderItemInput!]! | ||||
| 48 | +} | ||||
| 49 | + | ||||
| 50 | +union CreateOrderResult = CreateOrderSuccess | ValidationError | OutOfStockError | ||||
| 51 | + | ||||
| 52 | +type CreateOrderSuccess { order: Order! } | ||||
| 53 | +type ValidationError { fields: [FieldError!]! } | ||||
| 54 | +type OutOfStockError { itemIds: [ID!]!, message: String! } | ||||
| 55 | + | ||||
| 56 | +type Mutation { | ||||
| 57 | + createOrder(input: CreateOrderInput!): CreateOrderResult! | ||||
| 58 | +} | ||||
| 59 | +``` | ||||
| 60 | + | ||||
| 61 | +Client handles each case via `... on` fragments; the top-level `errors` array | ||||
| 62 | +stays reserved for genuine faults (auth failure, internal error). | ||||
| 63 | + | ||||
| 64 | +## DataLoader implementation | ||||
| 65 | + | ||||
| 66 | +```python | ||||
| 67 | +# Python (aiodataloader); same shape in JS's dataloader package. | ||||
| 68 | +from aiodataloader import DataLoader | ||||
| 69 | + | ||||
| 70 | +class CustomerLoader(DataLoader): | ||||
| 71 | + async def batch_load_fn(self, ids): | ||||
| 72 | + rows = await db.fetch( | ||||
| 73 | + "SELECT * FROM customers WHERE id = ANY($1)", ids) | ||||
| 74 | + by_id = {r["id"]: r for r in rows} | ||||
| 75 | + # Must return results in the SAME ORDER as ids, None for misses. | ||||
| 76 | + return [by_id.get(i) for i in ids] | ||||
| 77 | + | ||||
| 78 | +# Create ONE loader per request (in context), never a module-level singleton — | ||||
| 79 | +# its cache would leak data across users. | ||||
| 80 | +async def resolve_customer(order, info): | ||||
| 81 | + return await info.context["customer_loader"].load(order["customer_id"]) | ||||
| 82 | +``` | ||||
| 83 | + | ||||
| 84 | +## Depth and complexity limits | ||||
| 85 | + | ||||
| 86 | +```javascript | ||||
| 87 | +// graphql-depth-limit + graphql-query-complexity (npm) | ||||
| 88 | +validationRules: [ | ||||
| 89 | + depthLimit(10), // pathological nesting stops here | ||||
| 90 | + createComplexityRule({ | ||||
| 91 | + maximumComplexity: 1000, // ~1 point per field | ||||
| 92 | + listFactor: 10, // lists multiply child cost | ||||
| 93 | + onComplete: (c) => log.info({ complexity: c }), | ||||
| 94 | + }), | ||||
| 95 | +] | ||||
| 96 | +``` | ||||
| 97 | +Return the budget in the rejection message so clients can adapt: | ||||
| 98 | +`"Query complexity 2140 exceeds maximum 1000"`. | ||||
| 99 | + | ||||
| 100 | +## Deprecation flow | ||||
| 101 | + | ||||
| 102 | +```graphql | ||||
| 103 | +type Order { | ||||
| 104 | + total: Money! | ||||
| 105 | + amount: Int @deprecated(reason: "Use total; amount is cents-only and removed after 2027-01-01.") | ||||
| 106 | +} | ||||
| 107 | +``` | ||||
| 108 | +1. Add the replacement field. 2. Deprecate with a reason that names the | ||||
| 109 | +replacement and a date. 3. Monitor field usage (most gateways report it). | ||||
| 110 | +4. Remove only when usage is zero or the date passes. | ||||
| 111 | + | ||||
| 112 | +## Gotchas | ||||
| 113 | + | ||||
| 114 | +- **Non-null cascade:** an error in `Order.customer: Customer!` nulls the | ||||
| 115 | + entire `order` — with `Customer` (nullable) only the field nulls. This is | ||||
| 116 | + why rule 2 defaults to nullable. | ||||
| 117 | +- **DataLoader order contract:** `batch_load_fn` must return exactly | ||||
| 118 | + len(ids) results in input order; returning a dict or short list corrupts | ||||
| 119 | + unrelated resolvers silently. | ||||
| 120 | +- **Enums over booleans:** `status: OrderStatus` beats `isActive/isArchived` | ||||
| 121 | + boolean pairs that can contradict each other. | ||||
| 122 | +- **Introspection in production:** disable for public APIs unless the API is | ||||
| 123 | + deliberately open; it enumerates your entire attack surface. | ||||
| 124 | +- **`totalCount` on large tables** is a full COUNT(*) per query — make it | ||||
| 125 | + nullable and resolve lazily, or return an estimate and say so. | ||||
| 126 | +- **Cursor stability:** cursors must encode the ORDER BY key, not row | ||||
| 127 | + position, or pagination skips/duplicates under concurrent writes. | ||||
added
backend-skills/designing-rest-apis/SKILL.md
+48 −0
@@ -0,0 +1,48 @@ | |||||
| 1 | +--- | ||||
| 2 | +name: designing-rest-apis | ||||
| 3 | +description: Designs REST APIs with correct resource naming, HTTP method and status-code semantics, mandatory pagination, day-one versioning, and RFC 9457 problem+json errors, with OpenAPI as the source of truth. Use when the user asks to design, review, or refactor a REST API, define endpoints or routes, choose status codes, add pagination or versioning, or write an OpenAPI/Swagger spec. Do not use for GraphQL APIs (designing-graphql-apis), webhook delivery (designing-webhooks), or authentication mechanics (implementing-authentication). | ||||
| 4 | +--- | ||||
| 5 | + | ||||
| 6 | +<!-- | ||||
| 7 | +Author: Simon-Pierre Boucher | ||||
| 8 | +Contact: contact@spboucher.ai | ||||
| 9 | +--> | ||||
| 10 | + | ||||
| 11 | +# Designing REST APIs | ||||
| 12 | + | ||||
| 13 | +## When to use / when NOT to use | ||||
| 14 | +- **Use for:** designing or reviewing REST endpoints, URL structure, status codes, pagination, versioning, error bodies, OpenAPI specs. | ||||
| 15 | +- **Do NOT use for:** GraphQL schemas → `designing-graphql-apis`; webhook delivery → `designing-webhooks`; login/tokens → `implementing-authentication`; rate-limit policy → `limiting-request-rates`. | ||||
| 16 | + | ||||
| 17 | +## Core rules | ||||
| 18 | + | ||||
| 19 | +1. **Plural nouns, never verbs, in paths.** Actions come from HTTP methods; non-CRUD actions become sub-resources. | ||||
| 20 | + - ✅ `POST /orders`, `POST /orders/42/cancellation` | ||||
| 21 | + - ❌ `POST /createOrder`, `GET /getOrders` | ||||
| 22 | +2. **Method semantics are non-negotiable.** GET is safe and cacheable; PUT replaces and is idempotent; PATCH partially updates; DELETE is idempotent (second call → 404 or 204, never 500). Never mutate on GET. | ||||
| 23 | +3. **Status codes carry meaning — use the right one.** 200 read/update, 201 + `Location` header on create, 204 no body, 400 malformed syntax, 401 unauthenticated, 403 unauthorized, 404 absent (also for hiding resources), 409 state conflict, 422 valid syntax but failed business validation, 429 rate limited, 500 only for genuine server faults. | ||||
| 24 | + - ✅ `422` for "email already registered" | ||||
| 25 | + - ❌ `200 {"success": false}` | ||||
| 26 | +4. **Every collection paginates from day one.** Default `limit=20`, hard max `limit=100`; return cursor pagination (`next_cursor`) by default, offset only for small, static datasets. | ||||
| 27 | +5. **Version from the first commit.** Path prefix `/v1/` is the default. A breaking change (removed/renamed field, changed type or semantics) requires `/v2/`; additive changes do not. | ||||
| 28 | +6. **Errors are structured, uniform, and safe.** Use RFC 9457 problem+json: `type`, `title`, `status`, `detail`, `instance` (+ per-field `errors` array for 422). Never leak stack traces, SQL, or internal class names. | ||||
| 29 | +7. **The OpenAPI spec is the source of truth.** Write or update the spec with every endpoint change; generated docs and clients follow the spec, not the code. | ||||
| 30 | +8. **Filtering and sorting are query parameters with one convention.** `?status=active&sort=-created_at` (leading `-` = descending). Unknown parameters → 400, don't ignore silently. | ||||
| 31 | + | ||||
| 32 | +## Workflow | ||||
| 33 | + | ||||
| 34 | +1. List the resources (nouns) and their relationships before any URL exists. | ||||
| 35 | +2. Map each operation to method + path per rules 1–2; define request/response bodies. | ||||
| 36 | +3. Assign status codes per rule 3, including every failure path. | ||||
| 37 | +4. Add pagination, filtering, versioning per rules 4–5, 8. | ||||
| 38 | +5. Write the OpenAPI spec (or update it) and define the problem+json error schema once, referenced everywhere. | ||||
| 39 | +6. **Validate:** lint the spec (`npx @redocly/cli lint openapi.yaml` — install with `npm i -g @redocly/cli` if missing) and walk one full CRUD cycle checking each response code against rule 3. Fix and re-lint until clean. | ||||
| 40 | + | ||||
| 41 | +## Edge cases & failure modes | ||||
| 42 | +- **Long-running operations** → `202 Accepted` + status resource (`GET /operations/{id}`), never a 30 s blocking request. | ||||
| 43 | +- **Bulk operations** → dedicated resource (`POST /orders/batch`) returning per-item results with individual statuses (207-style body), not first-error-aborts. | ||||
| 44 | +- **Retries on create** → accept an `Idempotency-Key` header on POST; same key + same body → same response, no duplicate. | ||||
| 45 | +- **Deprecating a field** → mark `deprecated: true` in the spec and announce a sunset date; removal only in the next major version. | ||||
| 46 | + | ||||
| 47 | +## References | ||||
| 48 | +Deeper patterns (pagination envelopes, problem+json schemas, OpenAPI skeleton): see [references/patterns.md](references/patterns.md). | ||||
added
backend-skills/designing-rest-apis/references/patterns.md
+147 −0
@@ -0,0 +1,147 @@ | |||||
| 1 | +<!-- | ||||
| 2 | +Author: Simon-Pierre Boucher | ||||
| 3 | +Contact: contact@spboucher.ai | ||||
| 4 | +--> | ||||
| 5 | + | ||||
| 6 | +# Patterns — Designing REST APIs | ||||
| 7 | + | ||||
| 8 | +## Contents | ||||
| 9 | +- URL and method matrix | ||||
| 10 | +- Pagination envelope (cursor) | ||||
| 11 | +- problem+json error schemas | ||||
| 12 | +- Idempotency-Key handling | ||||
| 13 | +- OpenAPI 3.1 skeleton | ||||
| 14 | +- Gotchas | ||||
| 15 | + | ||||
| 16 | +## URL and method matrix | ||||
| 17 | + | ||||
| 18 | +``` | ||||
| 19 | +GET /v1/orders list (paginated) 200 | ||||
| 20 | +POST /v1/orders create 201 + Location | ||||
| 21 | +GET /v1/orders/{id} read 200 / 404 | ||||
| 22 | +PUT /v1/orders/{id} full replace (idempotent) 200 / 404 | ||||
| 23 | +PATCH /v1/orders/{id} partial update 200 / 404 / 409 | ||||
| 24 | +DELETE /v1/orders/{id} delete (idempotent) 204 / 404 | ||||
| 25 | +POST /v1/orders/{id}/refund non-CRUD action 201 / 409 | ||||
| 26 | +GET /v1/customers/{id}/orders nested read-only view 200 | ||||
| 27 | +``` | ||||
| 28 | +Nest at most one level; deeper relations get top-level resources with filters | ||||
| 29 | +(`/v1/orders?customer_id=7`), not `/customers/7/orders/3/items/9`. | ||||
| 30 | + | ||||
| 31 | +## Pagination envelope (cursor) | ||||
| 32 | + | ||||
| 33 | +```json | ||||
| 34 | +{ | ||||
| 35 | + "data": [{ "id": "ord_01J8", "status": "active" }], | ||||
| 36 | + "pagination": { | ||||
| 37 | + "next_cursor": "eyJpZCI6Im9yZF8wMUo4In0", | ||||
| 38 | + "has_more": true, | ||||
| 39 | + "limit": 20 | ||||
| 40 | + } | ||||
| 41 | +} | ||||
| 42 | +``` | ||||
| 43 | +Request: `GET /v1/orders?limit=20&cursor=eyJpZCI6...`. The cursor is opaque | ||||
| 44 | +(base64 of the last sort key) — clients must not parse it. `limit` above the | ||||
| 45 | +max of 100 → clamp to 100, do not error. | ||||
| 46 | + | ||||
| 47 | +## problem+json error schemas | ||||
| 48 | + | ||||
| 49 | +Header: `Content-Type: application/problem+json` | ||||
| 50 | + | ||||
| 51 | +```json | ||||
| 52 | +{ | ||||
| 53 | + "type": "https://api.example.com/errors/validation", | ||||
| 54 | + "title": "Validation failed", | ||||
| 55 | + "status": 422, | ||||
| 56 | + "detail": "2 fields are invalid.", | ||||
| 57 | + "instance": "/v1/orders", | ||||
| 58 | + "errors": [ | ||||
| 59 | + { "field": "email", "message": "must be a valid email address" }, | ||||
| 60 | + { "field": "quantity", "message": "must be between 1 and 99" } | ||||
| 61 | + ] | ||||
| 62 | +} | ||||
| 63 | +``` | ||||
| 64 | + | ||||
| 65 | +Conflict example: | ||||
| 66 | + | ||||
| 67 | +```json | ||||
| 68 | +{ | ||||
| 69 | + "type": "https://api.example.com/errors/state-conflict", | ||||
| 70 | + "title": "Order already shipped", | ||||
| 71 | + "status": 409, | ||||
| 72 | + "detail": "Order ord_01J8 cannot be cancelled after shipment.", | ||||
| 73 | + "instance": "/v1/orders/ord_01J8/cancellation" | ||||
| 74 | +} | ||||
| 75 | +``` | ||||
| 76 | + | ||||
| 77 | +Define the schema once under `components/schemas/Problem` and `$ref` it from | ||||
| 78 | +every 4xx/5xx response. | ||||
| 79 | + | ||||
| 80 | +## Idempotency-Key handling | ||||
| 81 | + | ||||
| 82 | +```python | ||||
| 83 | +# Pseudocode for POST endpoints that create resources. | ||||
| 84 | +# Keys expire after 24h — long enough for client retry storms, short enough | ||||
| 85 | +# to bound storage. | ||||
| 86 | +def create_order(request): | ||||
| 87 | + key = request.headers.get("Idempotency-Key") | ||||
| 88 | + if key: | ||||
| 89 | + cached = idempotency_store.get(key) | ||||
| 90 | + if cached: | ||||
| 91 | + if cached.request_hash != hash(request.body): | ||||
| 92 | + return problem(422, "Idempotency-Key reused with different body") | ||||
| 93 | + return cached.response # replay, no side effect | ||||
| 94 | + response = do_create(request.body) # single side effect | ||||
| 95 | + if key: | ||||
| 96 | + idempotency_store.put(key, hash(request.body), response, ttl_hours=24) | ||||
| 97 | + return response | ||||
| 98 | +``` | ||||
| 99 | + | ||||
| 100 | +## OpenAPI 3.1 skeleton | ||||
| 101 | + | ||||
| 102 | +```yaml | ||||
| 103 | +openapi: 3.1.0 | ||||
| 104 | +info: { title: Orders API, version: 1.0.0 } | ||||
| 105 | +servers: [{ url: https://api.example.com/v1 }] | ||||
| 106 | +paths: | ||||
| 107 | + /orders: | ||||
| 108 | + get: | ||||
| 109 | + parameters: | ||||
| 110 | + - { name: limit, in: query, schema: { type: integer, maximum: 100, default: 20 } } | ||||
| 111 | + - { name: cursor, in: query, schema: { type: string } } | ||||
| 112 | + responses: | ||||
| 113 | + "200": { $ref: "#/components/responses/OrderList" } | ||||
| 114 | + "429": { $ref: "#/components/responses/Problem" } | ||||
| 115 | + post: | ||||
| 116 | + parameters: | ||||
| 117 | + - { name: Idempotency-Key, in: header, schema: { type: string } } | ||||
| 118 | + responses: | ||||
| 119 | + "201": { $ref: "#/components/responses/Order" } | ||||
| 120 | + "422": { $ref: "#/components/responses/Problem" } | ||||
| 121 | +components: | ||||
| 122 | + schemas: | ||||
| 123 | + Problem: | ||||
| 124 | + type: object | ||||
| 125 | + properties: | ||||
| 126 | + type: { type: string } | ||||
| 127 | + title: { type: string } | ||||
| 128 | + status: { type: integer } | ||||
| 129 | + detail: { type: string } | ||||
| 130 | + instance: { type: string } | ||||
| 131 | +``` | ||||
| 132 | + | ||||
| 133 | +## Gotchas | ||||
| 134 | + | ||||
| 135 | +- **404 vs 403 leaks existence.** If callers must not learn a resource exists, | ||||
| 136 | + return 404 for both missing and forbidden. | ||||
| 137 | +- **PUT with partial body silently nulls fields** — that is correct replace | ||||
| 138 | + semantics; if clients expect merge, they need PATCH. Document which you offer. | ||||
| 139 | +- **Offset pagination drifts** when rows are inserted mid-scan; deep offsets | ||||
| 140 | + also get slow. Cursors fix both — default to them. | ||||
| 141 | +- **Trailing slashes**: `/orders` and `/orders/` must not be two resources. | ||||
| 142 | + Redirect or normalize one to the other. | ||||
| 143 | +- **Enum widening is breaking for clients that switch exhaustively.** Adding | ||||
| 144 | + an enum value is only additive if the spec documents "unknown values may | ||||
| 145 | + appear" from v1. | ||||
| 146 | +- **Date-times**: always RFC 3339 UTC (`2026-08-05T14:30:00Z`); epoch | ||||
| 147 | + integers and local times cause silent client bugs. | ||||
added
backend-skills/designing-webhooks/SKILL.md
+47 −0
Large diff (47 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/designing-webhooks/references/patterns.md
+114 −0
Large diff (114 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/handling-async-messaging/SKILL.md
+45 −0
Large diff (45 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/handling-async-messaging/references/patterns.md
+140 −0
Large diff (140 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/handling-errors/SKILL.md
+47 −0
Large diff (47 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/handling-errors/references/patterns.md
+116 −0
Large diff (116 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/handling-file-uploads/SKILL.md
+45 −0
Large diff (45 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/handling-file-uploads/references/patterns.md
+129 −0
Large diff (129 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/implementing-authentication/SKILL.md
+48 −0
Large diff (48 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/implementing-authentication/references/patterns.md
+133 −0
Large diff (133 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/implementing-authorization/SKILL.md
+46 −0
Large diff (46 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/implementing-authorization/references/patterns.md
+109 −0
Large diff (109 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/instrumenting-observability/SKILL.md
+45 −0
Large diff (45 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/instrumenting-observability/references/patterns.md
+122 −0
Large diff (122 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/limiting-request-rates/SKILL.md
+45 −0
Large diff (45 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/limiting-request-rates/references/patterns.md
+124 −0
Large diff (124 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/managing-configuration/SKILL.md
+47 −0
Large diff (47 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/managing-configuration/references/patterns.md
+107 −0
Large diff (107 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/scaling-backend-services/SKILL.md
+46 −0
Large diff (46 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/scaling-backend-services/references/patterns.md
+139 −0
Large diff (139 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/securing-backend-services/SKILL.md
+46 −0
Large diff (46 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/securing-backend-services/references/patterns.md
+118 −0
Large diff (118 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/shipping-with-ci-cd/SKILL.md
+46 −0
Large diff (46 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/shipping-with-ci-cd/references/patterns.md
+134 −0
Large diff (134 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/testing-backend-services/SKILL.md
+47 −0
Large diff (47 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/testing-backend-services/references/patterns.md
+120 −0
Large diff (120 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/validating-input/SKILL.md
+45 −0
Large diff (45 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/validating-input/references/patterns.md
+114 −0
Large diff (114 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/writing-background-jobs/SKILL.md
+47 −0
Large diff (47 lines) is not loaded by default to keep the page fast.Load diff
added
backend-skills/writing-background-jobs/references/patterns.md
+135 −0
Large diff (135 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/README.md
+40 −0
Large diff (40 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/administering-postgresql/SKILL.md
+48 −0
Large diff (48 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/administering-postgresql/references/patterns.md
+144 −0
Large diff (144 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/backing-up-databases/SKILL.md
+46 −0
Large diff (46 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/backing-up-databases/references/patterns.md
+139 −0
Large diff (139 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/designing-database-schemas/SKILL.md
+58 −0
Large diff (58 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/designing-database-schemas/references/patterns.md
+126 −0
Large diff (126 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/managing-database-migrations/SKILL.md
+48 −0
Large diff (48 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/managing-database-migrations/references/patterns.md
+124 −0
Large diff (124 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/managing-sqlite/SKILL.md
+49 −0
Large diff (49 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/managing-sqlite/references/patterns.md
+125 −0
Large diff (125 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/modeling-nosql-data/SKILL.md
+49 −0
Large diff (49 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/modeling-nosql-data/references/patterns.md
+128 −0
Large diff (128 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/optimizing-sql-performance/SKILL.md
+61 −0
Large diff (61 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/optimizing-sql-performance/references/patterns.md
+145 −0
Large diff (145 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/securing-databases/SKILL.md
+48 −0
Large diff (48 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/securing-databases/references/patterns.md
+156 −0
Large diff (156 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/troubleshooting-databases/SKILL.md
+57 −0
Large diff (57 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/troubleshooting-databases/references/patterns.md
+159 −0
Large diff (159 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/writing-sql-queries/SKILL.md
+65 −0
Large diff (65 lines) is not loaded by default to keep the page fast.Load diff
added
db-skills/writing-sql-queries/references/patterns.md
+144 −0
Large diff (144 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/README.md
+42 −0
Large diff (42 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-csv/SKILL.md
+72 −0
Large diff (72 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-csv/references/recipes.md
+157 −0
Large diff (157 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-docx/SKILL.md
+70 −0
Large diff (70 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-docx/references/recipes.md
+161 −0
Large diff (161 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-html/SKILL.md
+76 −0
Large diff (76 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-html/references/recipes.md
+136 −0
Large diff (136 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-json/SKILL.md
+74 −0
Large diff (74 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-json/references/recipes.md
+154 −0
Large diff (154 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-markdown/SKILL.md
+59 −0
Large diff (59 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-markdown/references/recipes.md
+134 −0
Large diff (134 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-pdf/SKILL.md
+63 −0
Large diff (63 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-pdf/references/recipes.md
+164 −0
Large diff (164 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-pptx/SKILL.md
+81 −0
Large diff (81 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-pptx/references/recipes.md
+173 −0
Large diff (173 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-xlsx/SKILL.md
+63 −0
Large diff (63 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-xlsx/references/recipes.md
+163 −0
Large diff (163 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-xml/SKILL.md
+69 −0
Large diff (69 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-xml/references/recipes.md
+139 −0
Large diff (139 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-yaml/SKILL.md
+68 −0
Large diff (68 lines) is not loaded by default to keep the page fast.Load diff
added
doc-skills/processing-yaml/references/recipes.md
+135 −0
Large diff (135 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/README.md
+52 −0
Large diff (52 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/bookkeeping-for-small-businesses/SKILL.md
+54 −0
Large diff (54 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/bookkeeping-for-small-businesses/references/reference.md
+119 −0
Large diff (119 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/filing-canadian-business-taxes/SKILL.md
+66 −0
Large diff (66 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/filing-canadian-business-taxes/references/reference.md
+82 −0
Large diff (82 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/filing-us-business-taxes/SKILL.md
+66 −0
Large diff (66 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/filing-us-business-taxes/references/reference.md
+73 −0
Large diff (73 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/handling-cross-border-taxation/SKILL.md
+56 −0
Large diff (56 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/handling-cross-border-taxation/references/reference.md
+92 −0
Large diff (92 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/optimizing-business-taxes/SKILL.md
+68 −0
Large diff (68 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/optimizing-business-taxes/references/reference.md
+105 −0
Large diff (105 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/optimizing-canadian-personal-taxes/SKILL.md
+67 −0
Large diff (67 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/optimizing-canadian-personal-taxes/references/reference.md
+103 −0
Large diff (103 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/optimizing-us-personal-taxes/SKILL.md
+75 −0
Large diff (75 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/optimizing-us-personal-taxes/references/reference.md
+109 −0
Large diff (109 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/preparing-canadian-personal-tax-returns/SKILL.md
+51 −0
Large diff (51 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/preparing-canadian-personal-tax-returns/references/reference.md
+90 −0
Large diff (90 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/preparing-financial-statements/SKILL.md
+52 −0
Large diff (52 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/preparing-financial-statements/references/reference.md
+82 −0
Large diff (82 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/preparing-us-personal-tax-returns/SKILL.md
+51 −0
Large diff (51 lines) is not loaded by default to keep the page fast.Load diff
added
finance-skills/preparing-us-personal-tax-returns/references/reference.md
+103 −0
Large diff (103 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/README.md
+38 −0
Large diff (38 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/building-react-components/SKILL.md
+62 −0
Large diff (62 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/building-react-components/references/patterns.md
+133 −0
Large diff (133 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/choosing-typography/SKILL.md
+54 −0
Large diff (54 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/choosing-typography/references/patterns.md
+125 −0
Large diff (125 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/crafting-ui-animations/SKILL.md
+55 −0
Large diff (55 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/crafting-ui-animations/references/patterns.md
+157 −0
Large diff (157 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/creating-landing-pages/SKILL.md
+58 −0
Large diff (58 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/creating-landing-pages/references/patterns.md
+119 −0
Large diff (119 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/designing-forms/SKILL.md
+57 −0
Large diff (57 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/designing-forms/references/patterns.md
+147 −0
Large diff (147 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/designing-responsive-layouts/SKILL.md
+59 −0
Large diff (59 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/designing-responsive-layouts/references/patterns.md
+116 −0
Large diff (116 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/ensuring-accessibility/SKILL.md
+61 −0
Large diff (61 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/ensuring-accessibility/references/patterns.md
+141 −0
Large diff (141 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/optimizing-web-performance/SKILL.md
+65 −0
Large diff (65 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/optimizing-web-performance/references/patterns.md
+153 −0
Large diff (153 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/structuring-semantic-html/SKILL.md
+61 −0
Large diff (61 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/structuring-semantic-html/references/patterns.md
+151 −0
Large diff (151 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/theming-design-tokens/SKILL.md
+55 −0
Large diff (55 lines) is not loaded by default to keep the page fast.Load diff
added
frontend-skills/theming-design-tokens/references/patterns.md
+153 −0
Large diff (153 lines) is not loaded by default to keep the page fast.Load diff
added
skill-1-profiling-csv-data/SKILL.md
+36 −0
Large diff (36 lines) is not loaded by default to keep the page fast.Load diff
added
skill-1-profiling-csv-data/references/report-format.md
+52 −0
Large diff (52 lines) is not loaded by default to keep the page fast.Load diff
added
skill-1-profiling-csv-data/scripts/profile_csv.py
+167 −0
Large diff (167 lines) is not loaded by default to keep the page fast.Load diff
added
skill-2-writing-release-notes/SKILL.md
+70 −0
Large diff (70 lines) is not loaded by default to keep the page fast.Load diff
added
skill-2-writing-release-notes/references/examples.md
+97 −0
Large diff (97 lines) is not loaded by default to keep the page fast.Load diff
added
tools/validate_skills.py
+144 −0
Large diff (144 lines) is not loaded by default to keep the page fast.Load diff
added
validation/fixtures/customers-profile.md
+31 −0
Large diff (31 lines) is not loaded by default to keep the page fast.Load diff
added
validation/fixtures/customers.csv
+9 −0
Large diff (9 lines) is not loaded by default to keep the page fast.Load diff
added
validation/fixtures/headeronly.csv
+1 −0
Large diff (1 lines) is not loaded by default to keep the page fast.Load diff
added
validation/fixtures/ragged.csv
+3 −0
Large diff (3 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/README.md
+43 −0
Large diff (43 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/editing-and-proofreading/SKILL.md
+64 −0
Large diff (64 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/editing-and-proofreading/references/examples.md
+101 −0
Large diff (101 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-agent-skills/SKILL.md
+59 −0
Large diff (59 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-agent-skills/references/examples.md
+114 −0
Large diff (114 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-api-documentation/SKILL.md
+63 −0
Large diff (63 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-api-documentation/references/examples.md
+124 −0
Large diff (124 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-blog-posts/SKILL.md
+61 −0
Large diff (61 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-blog-posts/references/examples.md
+102 −0
Large diff (102 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-executive-summaries/SKILL.md
+61 −0
Large diff (61 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-executive-summaries/references/examples.md
+95 −0
Large diff (95 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-meeting-notes/SKILL.md
+60 −0
Large diff (60 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-meeting-notes/references/examples.md
+106 −0
Large diff (106 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-professional-emails/SKILL.md
+65 −0
Large diff (65 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-professional-emails/references/examples.md
+110 −0
Large diff (110 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-proposals/SKILL.md
+62 −0
Large diff (62 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-proposals/references/examples.md
+109 −0
Large diff (109 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-technical-documentation/SKILL.md
+61 −0
Large diff (61 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-technical-documentation/references/examples.md
+132 −0
Large diff (132 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-tutorials/SKILL.md
+59 −0
Large diff (59 lines) is not loaded by default to keep the page fast.Load diff
added
writing-skills/writing-tutorials/references/examples.md
+111 −0
Large diff (112 lines) is not loaded by default to keep the page fast.Load diff