refactor: documentation for workflows: update Planning & Building, Reviewing & Shipping, and Testing & Debugging sections to enhance clarity and structure.

This commit is contained in:
duthaho
2026-05-07 16:57:35 +07:00
parent 44a3a2835d
commit 52e2cd6b4b
147 changed files with 4269 additions and 20215 deletions
+174
View File
@@ -0,0 +1,174 @@
---
name: audit-dependencies
user-invocable: true
description: >
Use when investigating dependency bloat, security advisories, supply-chain risk,
upgrade planning, or before adding a new third-party package. Activate for
keywords like "deps", "dependencies", "package.json", "requirements.txt",
"Cargo.toml", "audit", "CVE", "stale package", "do we use", "what depends on",
"transitive dep". Produces a written audit with import-graph evidence — never
trust scanner output without verifying call sites.
---
# Audit Dependencies
## Overview
A four-step dependency audit that goes past `npm audit` / `pip-audit` / `cargo audit`
output into the actual import graph. The skill enforces that every claim
("we don't use that import path", "this dep is dead", "this CVE doesn't apply")
is backed by evidence from the code, not from a tool's verdict alone. The audit
produces a list of dependencies with three columns: declared, transitively pulled,
actually called. Anything in column 1 or 2 but not column 3 is a candidate for
removal. Anything called but unpinned, deprecated, or vulnerable is an action item.
Senior ICs use it before adding a new dep, before a major version bump, or after
a CVE lands.
## When to Use
- After a CVE alert from `npm audit`, `pip-audit`, GitHub Dependabot, Snyk, or similar
- Before adding a new third-party package to the project
- Before a major-version upgrade of a framework, ORM, or runtime
- When `node_modules` / `site-packages` / `target` size feels disproportionate
- When evaluating whether a package can be removed
- During quarterly or release-cycle hygiene
## When NOT to Use
- A patch-version bump on a dep you actively use, with no behavioral changes in the
changelog. Just bump it.
- A dependency you added in this same PR. You know what it does.
- An audit on a deploy artifact you don't own (audit upstream, not the binary).
## Process
### Step 1: Snapshot
**Goal:** Capture the current declared dependency state in a form you can diff later.
**Inputs:** The project's manifest file(s) — `package.json`, `requirements.txt`,
`pyproject.toml`, `Cargo.toml`, `go.mod`, `Gemfile`, etc.
**Actions:**
1. Run the ecosystem's lockfile-respecting list command:
- `npm ls --all` (or `pnpm ls --depth=Infinity`)
- `pip list --format=json`
- `cargo tree`
- `go list -m all`
2. Pipe to a file. Date-stamp it. This is your before-state.
3. Note the count of direct deps and total deps (direct + transitive).
**Output:** A snapshot file at a known path. A two-line note: `<N> direct,
<M> transitive`.
### Step 2: Build the call graph
**Goal:** Determine which declared and transitively-pulled dependencies are
actually imported by your code.
**Inputs:** The snapshot from Step 1 + access to the source tree.
**Actions:**
1. For each direct dependency, search the source tree for imports of it. Use the
ecosystem's import syntax:
- JS/TS: `import .* from ['"]<name>['"]` and `require\(['"]<name>['"]\)`
- Python: `^(from|import) <name>(\.|$| )`
- Rust: `use <crate>::` and `extern crate <crate>;`
- Go: literal package path matches
2. Record the count of import sites per dep.
3. **Zero-import direct deps** are candidates for removal. Mark them.
4. For transitive deps that look load-bearing (security-related: jsonwebtoken,
cryptography, openssl, lodash, requests), check if your code imports them
directly. If yes, promote to a direct dep so you control its version.
**Output:** A table per dep: `<name> | <declared version> | <import sites>
| <verdict: keep | remove | promote>`.
### Step 3: Cross-check the scanner
**Goal:** Reconcile your import-graph evidence with what `npm audit` /
`pip-audit` / `cargo audit` reports, and decide whether each advisory applies.
**Inputs:** The Step 2 table, plus the output of the ecosystem's audit tool.
**Actions:**
1. Run the audit tool. Capture the full report.
2. For each advisory, look up the affected package in your Step 2 table.
3. **Crucial check:** does your code call the vulnerable function? An advisory on
a package you import does *not* automatically apply if the vulnerable code path
is in a sub-module you never reach. Read the advisory; locate the affected
function; grep your code for it.
4. Classify each advisory:
- **APPLIES — patch:** vulnerable code path is reachable; upgrade available.
- **APPLIES — workaround:** vulnerable code path is reachable; no patch yet,
mitigate at call site.
- **DOES NOT APPLY:** the vulnerable code path is not reachable from your code.
Document the proof in the audit artifact.
**Output:** Each advisory annotated with a verdict and a one-line proof
(`<file:line>` showing reach or absence of the vulnerable function).
### Step 4: Write the audit
**Goal:** Produce an artifact with actions, not opinions.
**Inputs:** The Step 2 table and Step 3 advisory verdicts.
**Actions:**
1. Write a Markdown artifact at `docs/audits/deps-<YYYY-MM-DD>.md` with sections:
- **Snapshot** (Step 1 counts)
- **Removals** (zero-import direct deps; estimated diff in transitive count)
- **Promotions** (transitive → direct, with version pin)
- **Advisory verdicts** (each with proof line)
- **Action items** (single bulleted list of changes to apply, in order)
2. The action items list is the deliverable. Each item is a concrete change
("Remove `lodash` from package.json — 0 import sites in src/. Re-run
`pnpm install` to verify transitive count drops by N.").
3. Open a PR for the action items. Each PR change links back to the audit.
**Output:** The audit artifact at the dated path, plus a PR (or sequence of PRs)
applying the action items.
## Rationalizations
| Excuse | Why it sounds reasonable | Why it's wrong | What to do instead |
|---|---|---|---|
| "`npm audit` says it's fine, that's enough." | The scanner is the standard tool, it's automated, it sees more than I do. | Scanners report on declared package versions against a CVE database. They do not tell you whether the vulnerable code path is reachable from your code, nor whether a high-severity advisory in a sub-package matters at all. A clean audit can hide real exposure; a noisy audit can list advisories that don't apply. | Run the scanner, but treat its output as input to Step 3, not the conclusion. Each advisory needs a reachability check before you ignore or patch it. |
| "It's just a patch bump, ship it." | SemVer says patch is bug-fix only, no breaking changes. | SemVer is a publishing convention, not a behavioral guarantee. Patch bumps regularly include behavior shifts (changed defaults, tightened validation, dropped Node/Python versions). Skipping a read of the changelog because "it's just a patch" is the line where the regression you'll spend tomorrow debugging gets shipped today. | Read the changelog or release notes for every bump, even patch. 30 seconds of reading saves 3 hours of bisect later. |
| "We don't use that import path." | It's true that not every advisory applies to every consumer. | "We don't use that import path" said *without* the grep that proves it is folklore. The function may be called transitively by another dep you do use. Or it may be called by a code path triggered only in production. The claim is testable; test it. | Step 3, Action 3: find the affected function in the package source, grep your code (and the code of the deps that use it) for the function name. Cite the file:line where you proved absence — or where you found a call. |
| "snyk/dependabot already filed a PR — just merge it." | Automated remediation is a real win. | The bot's PR upgrades the package; it doesn't verify your code still works at the new version, nor that the upgrade actually closes the advisory in your call path. Merging blind means you trust the bot's reachability analysis (it has none) and your CI's coverage (it may not exercise the affected code). | Treat the bot's PR as a draft of Step 4's action item. Run the test suite. Read the changelog. If the changelog mentions a behavior change in code you call, exercise that path manually before merging. |
| "Removing deps is risky — we might need them later." | True for some deps; the cost of removing a useful dep is non-trivial. | "Might need later" without evidence is hoarding. Unused deps still pull transitive deps, still expand the CVE attack surface, still slow installs and CI. The cost of removal is reversible (re-add when actually needed); the cost of leaving them is paid every install. | If the dep has zero import sites in Step 2 and no roadmap item committed to using it within one release cycle, remove it. Note the version in the audit artifact so re-adding the same version is easy. |
## Evidence Requirements
| Checkpoint | Required artifact | What "no evidence" looks like |
|---|---|---|
| End of Step 1 | Snapshot file with direct/transitive counts | "We have a lot of dependencies." |
| End of Step 2 | Per-dep table with import-site counts and a `keep/remove/promote` verdict | "Most of these look unused, I think." |
| End of Step 3 | Per-advisory verdict with file:line proof of reach/absence | "The high-severity ones are the urgent ones." |
| End of Step 4 | Audit artifact at `docs/audits/deps-<date>.md` plus an action-items PR | "I'll get to the cleanup in the next sprint." |
## Red Flags
- A dep you marked `remove` is removed by the PR but tests still pass and bundle
size doesn't change. You may have searched for the wrong import name (alias?
re-export?). Re-grep before merging.
- The audit tool reports a CVE on a dep you marked `remove`. The CVE may be moot,
but verify removal closes it before declaring done.
- You found a vulnerable function reachable from your code but the package has no
patch yet. Don't just file an issue — apply a workaround at your call site
(validation, sandboxing, or wrapping) and document it in the audit.
- More than 30% of direct deps have zero import sites. The project is using a
dependency manifest as a wishlist. Coordinate with the team before mass removal.
- A scanner says "high severity" on a dep that doesn't appear in your Step 2
table. The lockfile and the manifest are out of sync. Rebuild the lockfile.
## References
- *Software Engineering at Google*, Wright et al. (O'Reilly, 2020), Chapter 21
"Dependency Management" — the "diamond dependency problem" and the case for
reading import graphs over manifest declarations.
-129
View File
@@ -1,129 +0,0 @@
---
name: autoplan
argument-hint: "[plan-path]"
user-invocable: true
description: >
Use when the user wants a full multi-angle review of a written implementation plan — strategy, architecture, UX, and developer experience all at once. Activate for keywords like "autoplan", "auto review", "review everything", "full review", "run all reviews", "auto review this plan", "review from every angle", "run the review gauntlet". Dispatches all 4 reviewer agents (ceo-reviewer, eng-reviewer, design-reviewer, devex-reviewer) in parallel, merges scorecards, and gates all recommended fixes through a single multi-select AskUserQuestion prompt. Applies selected fixes to the plan and saves a consolidated review artifact.
---
# Autoplan (Parallel Plan Review)
## When to Use
- Plan is complex enough to warrant reviews from multiple angles
- User has a plan and wants "the full gauntlet" before implementation
- Before merging a plan to main or handing off to execution
## When NOT to Use
- Plan doesn't exist yet — use `writing-plans` first
- You only need one dimension reviewed — use the individual `plan-*-review` skill
- Plan has been implemented — use `requesting-code-review` or `review` on the code
---
## Workflow
### Step 1: Resolve the plan path
- If `[plan-path]` argument provided, use it
- Else scan (in order): `docs/claudekit/plans/*.md`, `docs/plans/*.md` (generic fallback), `plan.md` in cwd
- Multiple matches → pick newest by mtime
- None found → stop and tell user to run `/claudekit:writing-plans` first
### Step 2: Parallel fan-out
Emit a single assistant message containing four `Agent` tool calls — one per reviewer. They must be in ONE message so they run concurrently. Do NOT emit them sequentially.
For each Agent call, use `subagent_type` matching the reviewer name (`ceo-reviewer`, `eng-reviewer`, `design-reviewer`, `devex-reviewer`). Prompt each with:
- The absolute plan path
- Its dimension rubric (5 dimensions)
- The required output format
### Step 3: Merge the four scorecards
Produce a consolidated report:
```markdown
# Autoplan Review: <plan-basename>
**Date**: YYYY-MM-DD
## Overall Scores
| Reviewer | Overall | Lowest dimension |
|---|---|---|
| CEO | N.N/10 | <dim>: N/10 |
| ENG | N.N/10 | <dim>: N/10 |
| DESIGN | N.N/10 | <dim>: N/10 |
| DEVEX | N.N/10 | <dim>: N/10 |
## Critical Issues (sorted by score ascending — worst first)
| Reviewer | Dimension | Score | Issue | Fix (preview) |
|---|---|---|---|---|
...
## All Strengths
- [CEO] ...
- [ENG] ...
...
## Consolidated Fix Checklist (dedup across reviewers)
- [ ] autoplan-fix-1 — [CEO, DEVEX] "Onboarding not thought through" — In section "Onboarding", add: ...
- [ ] autoplan-fix-2 — [ENG] "No rollback for Phase 2" — In section "Phase 2", add: ...
...
```
**Dedup rule**: if two reviewers flag semantically similar issues (heuristic: same section cited + overlapping fix text), merge into one checklist row with both reviewer tags. Otherwise keep separate.
### Step 4: Single consolidation gate
If the consolidated fix checklist is empty (no dimension across any reviewer scored <6), skip this step entirely. Tell the user: "Plan scores well across all 4 dimensions — no fixes recommended." Still proceed to Step 6 to write the artifact (recording a clean review is useful).
Otherwise, use `AskUserQuestion` with all `autoplan-fix-*` items as multi-select options. One prompt. Include an "Apply none" option.
### Step 5: Apply selected fixes
For each selected fix, use `Edit` on the plan file. Each fix is either:
- `Replace "<old>" with "<new>"``Edit` with `old_string=<old>`, `new_string=<new>`
- `In section "<heading>", add: <text>``Read` the file, locate the heading, `Edit` to append `<text>` under it
If a fix is too vague to apply deterministically (fails the concreteness contract), skip it and report to the user as `Unapplied: <reason>`.
### Step 6: Write the consolidated artifact
Write the consolidated report (including `Applied fixes` + `Skipped fixes` sections) to `docs/claudekit/reviews/<plan-basename>-autoplan-YYYY-MM-DD.md`. Create the `docs/claudekit/reviews/` directory if it does not exist.
### Step 7: Error handling
- If one of the four agent dispatches fails, proceed with the remaining three and note `[dimension] review unavailable: <reason>` in the merged report.
- If the plan file is empty or unparseable, each reviewer will return `Overall: 0/10` with a single fix "Plan is empty". Surface to user without a fix-selection gate.
- If `Edit` fails on a fix (stale match after concurrent modifications), report as skipped with reason `stale_match`.
---
## Output Format (what the user sees)
```
# Autoplan Review: <plan-basename>
[overall scores table]
[critical issues table]
[strengths]
[consolidated fix checklist]
> Which fixes to apply?
> [AskUserQuestion multi-select + "Apply none" option]
Applied N fixes across <K> dimensions to <plan-path>.
Skipped M fixes (reason: too vague / stale match / agent unavailable).
Artifact: docs/claudekit/reviews/<plan-basename>-autoplan-YYYY-MM-DD.md
```
---
## Related Skills
- `writing-plans` — Produces the plan this reviews
- `plan-ceo-review`, `plan-eng-review`, `plan-design-review`, `plan-devex-review` — Individual dimensions (autoplan runs them in parallel)
- `dispatching-parallel-agents` — The parallel-dispatch pattern this skill uses
- `feature-workflow` — In a full feature workflow, run autoplan between Planning and Implementation phases
-298
View File
@@ -1,298 +0,0 @@
---
name: brainstorming
argument-hint: "[topic]"
description: >
Use when the user wants to design, explore, or ideate on ANY new feature, architecture decision, or unclear requirement. Activate for keywords like "brainstorm", "design", "explore", "what if", "how should we", "options for", "trade-offs", or any open-ended question about implementation approach. Also trigger when requirements are vague, ambiguous, or when multiple valid solutions exist -- err on the side of brainstorming before jumping into code.
---
# Brainstorming
## When to Use
- Designing new features with unclear requirements
- Exploring architecture decisions
- Refining user requirements
- Breaking down complex problems
- When multiple valid approaches exist
## When NOT to Use
- Executing already-approved plans -- use `executing-plans` instead
- Simple bug fixes with obvious solutions -- jump straight to fixing
- Mechanical refactoring where the approach is already clear
---
## Startup Mode (for new product / standalone ideas)
**Activation**: user's topic is a new product or standalone initiative, not a feature inside an existing codebase.
**Detection signals**:
- Keywords: "is this worth building", "should I build", "startup idea", "product idea", "I have an idea for"
- No existing codebase context; user is describing a concept pre-code
**Gate question** (first clarifier, always):
> Is this (a) a feature inside an existing codebase, or (b) a new product / standalone idea?
> - (b) → Startup Mode replaces Phase 1 (Understanding)
> - (a) → normal Phase 1
**Six forcing questions** (asked one at a time, per existing conventions):
1. **Demand reality** — "How do you *know* people want this? Give me evidence, not intuition."
2. **Status quo** — "What do people do today to solve this? Why isn't that enough?"
3. **Desperate specificity** — "Who is your very first user? Name, role, where you find them — be concrete."
4. **Narrowest wedge** — "What's the smallest thing you could ship this week that delivers real value to that one user?"
5. **Observation** — "Have you watched someone struggle with this problem? What did you see?"
6. **Future-fit** — "If this works, what does v3 look like in two years? Does that excite you enough to commit?"
**Output gate** (after Q6) — produce a traffic-light assessment per question (🟢/🟡/🔴) plus a recommendation:
- 5-6 green → proceed to Phase 2 (Exploration)
- 2-4 green → proceed but flag red/yellow items as design-time risks
- 0-1 green → pause; suggest more user-discovery work before designing
**After Startup Mode**: continue with the existing Phase 2 (Exploration) and Phase 3 (Design Presentation). YAGNI, multiple-choice questioning, and design-doc output are unchanged.
---
## Three-Phase Process
### Phase 1: Understanding
**Goal**: Clarify requirements through sequential questioning.
**Rules**:
- Ask only ONE question per message
- If a topic needs more exploration, break it into multiple questions
- Prefer multiple-choice questions over open-ended when possible
- Wait for user response before next question
**Example**:
```
BAD: "What authentication method do you want, and should we support SSO,
and what about password requirements?"
GOOD: "Which authentication method should we use?
a) Username/password only
b) OAuth (Google, GitHub)
c) Both options"
```
### Phase 2: Exploration
**Goal**: Present alternatives with clear trade-offs.
**Process**:
1. Present 2-3 different approaches
2. Lead with the recommended option
3. Explain trade-offs for each
4. Let user choose direction
**Format**:
```markdown
## Approach 1: [Name] (Recommended)
[Description]
- Pros: [Benefits]
- Cons: [Drawbacks]
## Approach 2: [Name]
[Description]
- Pros: [Benefits]
- Cons: [Drawbacks]
Which approach aligns better with your goals?
```
### Phase 3: Design Presentation
**Goal**: Present validated design in digestible chunks.
**Rules**:
- Break design into 200-300 word sections
- Validate incrementally after each section
- Cover: architecture, components, data flow, error handling, testing
- Be flexible - allow user to request clarification or changes
**Sections to Cover**:
1. Architecture overview
2. Component breakdown
3. Data flow
4. Error handling
5. Testing considerations
---
## Core Principles
### YAGNI Ruthlessly
Remove unnecessary features aggressively:
- Question every "nice to have"
- Start with minimal viable design
- Add complexity only when justified
- "We might need this later" = remove it
### One Question at a Time
Sequential questioning produces better results:
- Gives user time to think deeply
- Prevents overwhelming with choices
- Creates natural conversation flow
- Allows follow-up on unclear points
### Multiple-Choice Preference
When possible, provide structured options:
- Reduces cognitive load
- Surfaces your understanding
- Makes decisions concrete
- Still allow "Other" option
---
## Output Format
**Save location**: After design validation, write the design document to:
```
docs/claudekit/specs/YYYY-MM-DD-<topic>-design.md
```
Create the `docs/claudekit/specs/` directory if it does not exist. Use today's date (YYYY-MM-DD) and a short, kebab-case topic slug.
Document to timestamped markdown:
```markdown
# Design: [Feature Name]
Date: [YYYY-MM-DD]
## Summary
[2-3 sentences]
## Architecture
[Architecture decisions]
## Components
[Component breakdown]
## Data Flow
[How data moves through system]
## Error Handling
[Error scenarios and handling]
## Testing Strategy
[Testing approach]
## Open Questions
[Any remaining unknowns]
```
---
## Post-Design Workflow
After design is validated:
1. Commit design document to version control
2. Optionally proceed to implementation
3. Use `writing-plans` skill for detailed task breakdown
4. Use `executing-plans` skill for implementation
---
## MCP Integration
This skill leverages MCP servers for enhanced brainstorming:
### Sequential Thinking (Primary)
```
Use Sequential Thinking for structured exploration:
- Track design options as thought sequences
- Build confidence in recommendations incrementally
- Allow for revisions as user provides feedback
- Document reasoning chain for design decisions
```
### Memory
```
Persist design decisions across sessions:
- Store design concepts as entities
- Create relations between components
- Recall user preferences from previous sessions
- Build project design knowledge over time
```
### Context7
```
For informed technology choices:
- Fetch current library documentation
- Compare capabilities accurately
- Ground recommendations in real data
```
---
## Stack-Specific Brainstorming Examples
These show what Phase 2 (Exploration) output looks like for different domains:
### FastAPI endpoint design
```markdown
## Approach 1: REST + JWT Bearer Auth (Recommended)
POST /api/orders with Pydantic v2 validation, async SQLAlchemy.
- Pros: Simple, cacheable, great OpenAPI docs via FastAPI
- Cons: Multiple round-trips for nested resources
## Approach 2: GraphQL + API Key Auth
Single /graphql endpoint with Strawberry, API key in header.
- Pros: Flexible queries, single round-trip for nested data
- Cons: Caching harder, team unfamiliar with Strawberry
**Decision**: REST — team knows it, OpenAPI auto-docs save time,
nested resources not needed for this feature.
```
### React data table component
```markdown
## Approach 1: TanStack Table + URL Params (Recommended)
Server component fetches data, client component for interactions.
Sort/filter state in URL search params (shareable links).
- Pros: Bookmarkable state, SSR-friendly, no global store needed
- Cons: URL parsing boilerplate
## Approach 2: Zustand Store + SWR
Client-only with SWR for fetching, Zustand for table state.
- Pros: Simple state management, familiar pattern
- Cons: Not SSR-friendly, state lost on refresh
**Decision**: TanStack Table + URL params — users need to share
filtered views, and it works with Next.js App Router.
```
### Database multi-tenancy
```markdown
## Approach 1: Shared Table + tenant_id + RLS (Recommended)
Single `orders` table with `tenant_id` column, PostgreSQL RLS policies.
- Pros: Simple migrations, single connection pool, no schema sprawl
- Cons: Must never forget WHERE tenant_id = ? (RLS prevents this)
## Approach 2: Schema-per-tenant
Each tenant gets own PostgreSQL schema, selected via search_path.
- Pros: Strong isolation, easy per-tenant backup/restore
- Cons: Migration complexity grows linearly with tenants
**Decision**: Shared table + RLS — we have <100 tenants, RLS gives
isolation guarantees without migration pain.
```
---
## Related Skills
- `writing-plans` -- After brainstorming produces a validated design, use writing-plans to create a detailed implementation plan
- `sequential-thinking` -- For complex problems that benefit from structured step-by-step reasoning during the brainstorming process
@@ -1,88 +0,0 @@
# Brainstorming Question Patterns
Quick-reference catalog of effective question types for brainstorming sessions. Use these to systematically explore a problem space before jumping to solutions.
---
## Clarifying Questions
**Purpose:** Ensure you understand the actual problem before solving it. Most failed implementations stem from unclear requirements.
**When to use:** At the start of every brainstorming session, and whenever the request contains ambiguous terms.
| # | Question | Context |
|---|----------|---------|
| 1 | What exactly should happen when a user does X? | Use when the described behavior has multiple valid interpretations. Forces concrete scenario thinking. |
| 2 | Who is the primary user of this feature, and what's their current workflow? | Use when the requester assumes you know the audience. Different users need different solutions. |
| 3 | What does success look like? How will you know this is working? | Use to surface acceptance criteria early. Prevents building the wrong thing correctly. |
| 4 | Can you walk me through a specific example from start to finish? | Use when the description is abstract. Concrete examples reveal hidden requirements. |
---
## Constraint Questions
**Purpose:** Identify boundaries that shape the solution space. Constraints eliminate options early and prevent wasted effort.
**When to use:** After clarifying the goal, before exploring solutions. Especially important when the requester says "just build X."
| # | Question | Context |
|---|----------|---------|
| 1 | What's the timeline? Is there a hard deadline or a target? | Use always. A 2-day solution looks nothing like a 2-month solution. |
| 2 | What can't change? Are there existing systems, APIs, or schemas we must preserve? | Use when modifying an existing system. Reveals integration constraints. |
| 3 | What's the performance budget? Expected load, response time, data volume? | Use for any feature touching data pipelines, APIs, or user-facing flows. |
| 4 | Are there compliance, security, or accessibility requirements? | Use for anything involving user data, payments, or public-facing UI. Easy to forget, expensive to retrofit. |
---
## Alternative Questions
**Purpose:** Expand the solution space. The first idea is rarely the best idea.
**When to use:** After constraints are clear but before committing to an approach. Especially when the requester has already proposed a specific solution.
| # | Question | Context |
|---|----------|---------|
| 1 | What if we solved this without building anything new? Could an existing tool or configuration handle it? | Use to challenge the assumption that code is needed. Sometimes a config change or third-party tool is enough. |
| 2 | What's the simplest version that still delivers value? | Use to find the MVP. Strips away nice-to-haves and focuses on the core need. |
| 3 | Have you considered [opposite approach]? What would that look like? | Use to break anchoring bias. If they propose a push model, ask about pull. If sync, ask about async. |
| 4 | What would we do if we had to ship this today? | Use to identify which parts are truly essential vs. which are aspirational. |
---
## Prioritization Questions
**Purpose:** Sequence work effectively when there's more to do than time allows.
**When to use:** When the feature has multiple components, when scope is growing, or when the team is debating what to build first.
| # | Question | Context |
|---|----------|---------|
| 1 | Which of these capabilities is most important to the first user? | Use to rank features by user impact rather than technical convenience. |
| 2 | What's the MVP — the smallest thing we can ship and learn from? | Use when scope is expanding. Forces a shippable first increment. |
| 3 | What can wait for v2 without blocking the core experience? | Use to defer non-essential work explicitly rather than letting it creep in. |
| 4 | If we could only ship one of these this week, which one? | Use when the team can't agree on priority. Forces a direct comparison. |
---
## Technical Questions
**Purpose:** Ground the discussion in implementation reality. Surface architecture decisions that affect the solution.
**When to use:** Once the goal and constraints are clear, before writing a plan. Essential for features that touch multiple systems.
| # | Question | Context |
|---|----------|---------|
| 1 | What's the data model? What entities exist, and how do they relate? | Use for any feature involving persistent state. Data model drives everything. |
| 2 | How does authentication and authorization work for this? Who can see/do what? | Use for any feature with access control. Auth is often assumed but rarely specified. |
| 3 | What's the expected scale — users, requests/sec, data size? | Use to choose between simple and scalable approaches. Over-engineering is as wasteful as under-engineering. |
| 4 | What existing code or patterns should this follow? Are there conventions to match? | Use to maintain consistency. New code that ignores existing patterns creates maintenance burden. |
---
## Using This Reference
1. **Don't ask all questions** — pick the 3-5 most relevant for the situation
2. **Start with clarifying** — always ensure you understand the problem
3. **Adapt the phrasing** — these are templates, not scripts
4. **Listen for gaps** — the questions the requester struggles to answer reveal the areas that need more thought
5. **Document answers** — capture decisions as they're made so you don't re-ask later
+211
View File
@@ -0,0 +1,211 @@
---
name: code-review-loop
user-invocable: true
description: >
Use when opening a PR for review or when receiving review feedback. Activate
for keywords like "code review", "PR review", "request review", "review
feedback", "address comments", "reviewer said". Covers both ends of the loop:
preparing a reviewable PR and acting on feedback rigorously. Always engage with
every comment -- never dismiss feedback by silently ignoring it.
---
# Code Review Loop
## Overview
End-to-end code review etiquette. Covers the requesting side (preparing a PR
that's reviewable) and the receiving side (acting on feedback). The skill exists
because most code review failures aren't disagreement — they're noise. Reviewers
get PRs they can't reasonably review (1500 lines, mixed concerns, no
description) and authors get feedback they don't engage with seriously
(silent dismissals, "fixed" without explanation, defensive replies). The skill
enforces structure on both ends and dispatches `claudekit:code-reviewer` /
`claudekit:security-auditor` agents on the diff. Used after `verification-gate`,
before merge.
## When to Use
- Opening a PR for review
- Responding to review comments on a PR you authored
- Reviewing a PR another engineer authored (the skill applies symmetrically)
- Re-requesting review after addressing feedback
## When NOT to Use
- Quick fixes via direct push to a branch nobody else uses (no review needed)
- A PR is already merged and you have post-merge feedback (file a follow-up
issue, don't re-litigate)
- Reviewing infra/config that the project's policy explicitly auto-approves
## Process
### Step 1: Prepare the PR (requesting side)
**Goal:** A reviewable PR.
**Inputs:** A branch with verified changes (you've run `verification-gate`).
**Actions:**
1. The PR title is one line, describing what changed. Not "Updates" or "Fix
stuff." Verb-led: "Add idempotency key to charge endpoint."
2. The PR description has these sections:
- **What:** 1-3 sentences naming the change.
- **Why:** the spec link, the ticket, the bug being fixed.
- **How:** the design choice, especially if non-obvious. Cite the plan if
one exists.
- **Verification:** the output from `verification-gate` (paste or link).
- **Risk + rollback:** if the change has any risk, name it and the rollback
procedure.
3. Check the diff size. If >400 lines (excluding tests, generated files,
lockfiles), consider splitting the PR. Reviewers won't read the whole thing
carefully; they'll skim, miss issues, and approve.
4. Tag the right reviewers. For sensitive paths (auth, payments, data), tag
the security-savvy reviewer too.
**Output:** A PR open for review with the description filled out.
### Step 2: Dispatch the reviewer agents
**Goal:** A first pass before human reviewers spend their time.
**Inputs:** The open PR.
**Actions:**
1. Dispatch `claudekit:code-reviewer` on the diff. Returns: structural findings
(data flow, error handling, edge cases), style findings, complexity findings.
2. If the diff touches `auth/`, `payments/`, `crypto/`, `users/`, `sessions/`,
`tokens/`, or any path with sensitive-data semantics, also dispatch
`claudekit:security-auditor`. Returns: input-validation findings, OWASP-aligned
findings, secret-handling findings.
3. Read both findings lists. Address obvious issues (typos, missing error
handling, easily-fixed structural notes) yourself before human reviewers see
the PR.
4. Push the changes. Note in the PR description that automated reviewer agents
ran, plus any findings you intentionally deferred.
**Output:** A PR that has been pre-reviewed by agents; obvious findings already
addressed.
### Step 3: Receive feedback (receiving side)
**Goal:** Engage with every comment.
**Inputs:** Reviewer comments on the PR.
**Actions:**
1. Read every comment before responding to any. Get the full picture; don't
start replying piecemeal.
2. For each comment, choose one of three responses:
- **Agree + apply:** make the change. Reply with the commit hash that
applied it. Don't reply "fixed" without the hash.
- **Disagree + explain:** explain why you disagree. Cite evidence (a test, a
constraint, a decision in the spec). Ask the reviewer if your reasoning
resolves their concern.
- **Need more context:** ask the reviewer for clarification. Don't guess at
what they meant.
3. Never silently dismiss a comment. If you didn't apply it and didn't reply,
the reviewer assumes you missed it.
**Output:** Every comment has a response thread.
### Step 4: Apply changes in coherent commits
**Goal:** Make the diff history easy to re-review.
**Inputs:** The agreed changes from Step 3.
**Actions:**
1. Group changes by topic. One commit per topic, even if multiple comments
contributed to it.
2. Each commit message names what changed and references the comment thread
("Address review: extract validation to <module>; thread #N").
3. Don't squash before re-review unless the project's policy demands it.
Reviewers want to see what changed since their last pass.
**Output:** New commits on the branch addressing the agreed feedback.
### Step 5: Re-request review
**Goal:** Hand back to the reviewer with a clear next step.
**Inputs:** The branch with applied changes.
**Actions:**
1. Add a single comment on the PR summarizing what you addressed and what you
pushed back on:
- "Addressed: comments #1, #3, #5 (commits a1b2c, c3d4e)"
- "Pushed back: comments #2, #4 — see threads"
2. Re-request review through the platform's mechanism (re-assign, request
re-review, etc.).
3. Don't ping by Slack/IM unless the PR is blocking and reviewers are unaware.
**Output:** Reviewers re-engaged with a summary of what changed.
### Step 6: Close the loop
**Goal:** Merge cleanly.
**Inputs:** Approval from required reviewers.
**Actions:**
1. Confirm CI is green at the most recent commit (not the branch tip from when
review was requested).
2. Resolve all comment threads. If a thread has unresolved disagreement, the PR
shouldn't merge yet — escalate or compromise.
3. Merge using the project's standard method (squash, merge commit, rebase).
4. If the PR introduced anything not yet rolled out (feature flag off, config
not flipped), the PR is *merged* but not *delivered* — track delivery
separately.
**Output:** PR merged. Any pending delivery steps tracked.
## Rationalizations
| Excuse | Why it sounds reasonable | Why it's wrong | What to do instead |
|---|---|---|---|
| "I'll write a quick PR description and the reviewer can read the diff for context." | The diff is the source of truth; the description is metadata. | The diff shows *what* changed, not *why*. A reviewer reading the diff cold has to reconstruct the intent, the constraints, the alternatives considered. They will reconstruct partially, miss something, ask questions you've already answered in the spec, and slow the review by hours. | Write the description. The What/Why/How structure is short — 4-8 sentences — and saves the reviewer reconstruction time. The PR description is a contract: this is what I'm asking you to look for. |
| "The PR is large but the changes are mechanical — easy to review." | Mechanical changes are real. A rename across 800 lines is genuinely simple. | "Mechanical" is the line said before someone discovers a non-mechanical change buried in the mass: a slightly different signature, an off-by-one, a behavior tweak the rename quietly altered. Reviewers don't read 800-line "mechanical" PRs line-by-line; they spot-check and approve. The buried bug ships. | Split the PR. Mechanical-only commit goes first, behavior changes (if any) go in a separate small PR after. If the PR is genuinely 100% mechanical, you can call that out explicitly and the reviewer can approve confidently — but don't ask them to take "mechanical" on faith. |
| "I'll reply 'fixed' to the comments — the reviewer can see the new commits." | The reviewer can navigate the PR; making them re-derive the linkage feels like courtesy theater. | The reviewer is reviewing many PRs that day; they don't remember which comment maps to which commit, and the PR UI doesn't always make it obvious. "Fixed" without a hash forces them to scan the diff hunting for your change, find it, verify it, and *then* react. The hash saves the search. | Reply with the commit hash: "Fixed in a1b2c3d." Or, if it was multi-commit: "Fixed in a1b2c3d (extracted) and c3d4e5f (renamed param)." 10 seconds for you, 90 seconds saved per comment for the reviewer. |
| "The reviewer's comment is wrong — I'll just leave it and merge." | Sometimes reviewers really are wrong. Defending against bad feedback is a real skill. | Silently dismissing the comment doesn't tell the reviewer they're wrong; it tells them they were ignored. Next PR, they'll either escalate the same comment more aggressively or stop reviewing your PRs carefully. The disagreement is the data; suppressing it loses the data and the relationship. | If you disagree, reply with your reasoning. Cite evidence. Ask if your reasoning resolves their concern. They may have context you don't, or vice versa — the comment thread is where that gets surfaced. |
| "Security review is overkill for this — the file is just a refactor." | Refactors really don't usually change security posture. | "Just a refactor" can move a sensitive call across a boundary, change which path a request takes, alter the ordering of validation and side effects. The security-auditor agent is automated and cheap; running it on a refactor that touches sensitive paths takes 30 seconds and catches the cases where "just a refactor" wasn't. | If the diff touches any sensitive path (auth, payments, crypto, users, sessions, tokens), dispatch the security-auditor regardless of how mechanical the change feels. The cost is automated; the risk is asymmetric. |
| "CI ran when I opened the PR — that's still the source of truth." | CI results don't usually change between open and merge. | The branch typically has new commits between PR-open and merge (review feedback, conflict resolution, the dependency upgrade that sneaked into main). The CI run from PR-open is testing a state that no longer exists. Merging on stale green is how flaky-vs-broken slips through. | Confirm CI is green on the *current* commit before merging. Most platforms show this; if yours doesn't, push a no-op or re-run CI to confirm. |
## Evidence Requirements
| Checkpoint | Required artifact | What "no evidence" looks like |
|---|---|---|
| End of Step 1 | PR description with What/Why/How/Verification/Risk sections; diff <400 lines (or split rationale) | An empty PR description; "see ticket." |
| End of Step 2 | Reviewer agent findings addressed or noted as deferred | "I think the code is fine; let humans look." |
| End of Step 3 | Every comment has a response | Some comments left unanswered. |
| End of Step 4 | New commits each named with topic + comment-thread reference | One huge "address review" commit. |
| End of Step 5 | A summary comment listing what was addressed and what was pushed back on | Re-request without summary. |
| End of Step 6 | CI green on the most recent commit; all threads resolved | "Merged it; CI was green earlier." |
## Red Flags
- The PR description is one sentence. The reviewer is reconstructing your work
from the diff alone.
- The diff exceeds 400 non-trivial lines and isn't split. Reviewers will skim.
- A comment thread has more than 5 back-and-forth replies. The disagreement
needs to be moved to a synchronous conversation.
- Multiple comments left without any reply from the author. The PR was abandoned
mid-review.
- The PR was merged with unresolved comment threads. The disagreement is now
hidden in the history.
- The PR has 20 commits each titled "fix review." Squash before merge or commit
with topical messages.
- The "Verification" section is missing. The PR jumped from work to merge
without the gate.
## References
- *Software Engineering at Google*, Wright et al. (O'Reilly, 2020), Chapter 9
"Code Review" — "Small CL" principle and the case that review effectiveness
inversely correlates with diff size. Step 1's 400-line guideline derives
from Google's internal observations on review-found-defects vs CL size.
-209
View File
@@ -1,209 +0,0 @@
---
name: condition-based-waiting
user-invocable: false
description: >
Use when waiting on external conditions like CI pipeline runs, deployments, long builds, database migrations, or test suites. Trigger for keywords like "wait for", "check status", "poll", "monitor", "is it done", "build running", "deploy in progress", or when a background process needs to complete before the next step. Also activate when using run_in_background or Monitor tools in Claude Code.
---
# Condition-Based Waiting
## When to Use
- CI/CD pipeline is running and you need results before proceeding
- Deployment is in progress and you need to verify it succeeded
- Long-running build (Next.js, Docker) is executing
- Database migration is applying
- Test suite takes more than 30 seconds
## When NOT to Use
- Commands that complete in under 10 seconds (just run them normally)
- Checking static state that won't change (read the file instead)
- Polling for human action (ask the user instead)
---
## Claude Code Patterns
### Background execution for long commands
Use `run_in_background` when a command takes more than ~30 seconds:
```bash
# Long test suite — run in background, get notified when done
pytest -v --cov=src # run_in_background: true
# Docker build
docker build -t myapp . # run_in_background: true
# Next.js production build
next build # run_in_background: true
# NestJS build + test
npm run build && npm test # run_in_background: true
```
You'll be notified automatically when the command completes — **do not poll or sleep**.
### Monitor tool for streaming output
Use Monitor when you need to watch for specific output patterns:
```bash
# Watch for build completion
until curl -sf http://localhost:3000/health; do sleep 2; done
# Watch for migration completion
until alembic check 2>&1 | grep -q "No new upgrade"; do sleep 5; done
```
---
## Checking CI/CD Status
### GitHub Actions
```bash
# Watch a running workflow (blocks until complete)
gh run watch
# Check status of the latest run
gh run view --json status,conclusion
# Check specific workflow
gh run list --workflow=ci.yml --limit=1 --json status,conclusion
# Wait for all checks on a PR
gh pr checks --watch
```
### After CI completes
```bash
# Get detailed results
gh run view <run-id> --log-failed
# Re-run failed jobs only
gh run rerun <run-id> --failed
```
---
## Checking Deployments
### Health check polling
```bash
# Wait for deployment to be healthy
until curl -sf https://staging.example.com/health | grep -q '"status":"ok"'; do
sleep 5
done
echo "Deployment is healthy"
```
### Vercel / Cloudflare
```bash
# Vercel — check latest deployment status
npx vercel ls --limit=1
# Cloudflare Pages — check deployment
npx wrangler pages deployment list --project-name=myapp
```
---
## Checking Build Output
### Framework-specific patterns
```bash
# Next.js — watch for "Compiled successfully"
# (use run_in_background for `next build`, read output when notified)
# Python — watch for test results
pytest -v --tb=short # run_in_background: true
# Docker — watch for "Successfully built"
docker build -t myapp . # run_in_background: true
```
### Database migrations
```bash
# Alembic (Python)
alembic upgrade head # run_in_background: true for large migrations
# Prisma (TypeScript)
npx prisma migrate deploy # run_in_background: true
# Verify migration status
alembic check # Python
npx prisma migrate status # TypeScript
```
---
## Anti-Patterns
### Don't: Sleep loops
```bash
# BAD — burns cache, wastes tokens
sleep 60 && check_status
sleep 60 && check_status
sleep 60 && check_status
# GOOD — use run_in_background or until-loop with Monitor
```
### Don't: Poll too frequently
```bash
# BAD — checking every second
while true; do curl localhost:3000/health; sleep 1; done
# GOOD — reasonable interval based on expected duration
until curl -sf localhost:3000/health; do sleep 5; done
```
### Don't: Wait without timeouts
```bash
# BAD — waits forever
until curl -sf localhost:3000/health; do sleep 5; done
# GOOD — timeout after 5 minutes
timeout 300 bash -c 'until curl -sf localhost:3000/health; do sleep 5; done'
```
### Don't: Guess completion
```markdown
BAD: "The build probably finished by now, let's proceed"
GOOD: "Let me check the build status before proceeding"
```
---
## Timing Guide
| Operation | Expected Duration | Check Interval | Approach |
|-----------|------------------|----------------|----------|
| Unit tests (small) | 5-30s | N/A | Run inline |
| Unit tests (large) | 30s-5m | N/A | `run_in_background` |
| `next build` | 30s-3m | N/A | `run_in_background` |
| Docker build | 1-10m | N/A | `run_in_background` |
| CI pipeline | 2-15m | 30s | `gh run watch` |
| Deployment | 1-10m | 5s | Health check poll |
| DB migration (small) | 5-30s | N/A | Run inline |
| DB migration (large) | 1-30m | N/A | `run_in_background` |
---
## Related Skills
- `verification-before-completion` — After waiting, verify the result before claiming success
- `github-actions` — CI/CD workflow patterns
- `docker` — Container build patterns
- `systematic-debugging` — When the thing you're waiting for fails
-300
View File
@@ -1,300 +0,0 @@
---
name: defense-in-depth
user-invocable: false
description: >
Use when fixing any data-related bug, when building validation for critical data paths, or when a single validation point has already failed in production. Also activate whenever you hear "it slipped through," "the check was bypassed," or "it worked in tests but not production." Apply aggressively to any scenario involving data integrity, input validation across layers, or preventing bug recurrence through structural guarantees rather than single-point fixes.
---
# Defense-in-Depth
## When to Use
- After fixing any data-related bug
- Protecting critical data paths
- Preventing bug recurrence
- Building robust systems
- When single validation points have failed
## When NOT to Use
- Greenfield prototyping where speed matters more than robustness and requirements are still fluid
- Non-data-related bugs such as logic errors, race conditions, or algorithmic mistakes
- UI styling issues where visual correctness is the concern, not data integrity
---
## Core Concept
**"Validate at EVERY layer data passes through. Make the bug structurally impossible."**
Single validation points can be bypassed:
- Alternative code paths skip validation
- Refactoring accidentally removes checks
- Tests mock away the validation
Multiple layers create redundancy:
- Different layers catch different cases
- If one check fails, another catches it
- Bug becomes impossible, not just unlikely
---
## The Four-Layer Approach
### Layer 1: Entry Point Validation
Reject invalid input at API/system boundaries:
```typescript
// API endpoint - first line of defense
app.post('/orders', (req, res) => {
// Type check
if (typeof req.body.userId !== 'string') {
return res.status(400).json({ error: 'userId must be a string' });
}
// Existence check
if (!req.body.userId) {
return res.status(400).json({ error: 'userId is required' });
}
// Format validation
if (!isValidUUID(req.body.userId)) {
return res.status(400).json({ error: 'userId must be a valid UUID' });
}
// Proceed with valid data
orderService.createOrder(req.body);
});
```
### Layer 2: Business Logic Validation
Ensure data semantically makes sense for the operation:
```typescript
// Service layer - business rules
class OrderService {
async createOrder(data: OrderData) {
// Business validation
const user = await this.userRepo.findById(data.userId);
if (!user) {
throw new BusinessError('User does not exist');
}
if (!user.canPlaceOrders) {
throw new BusinessError('User is not allowed to place orders');
}
if (data.items.length === 0) {
throw new BusinessError('Order must have at least one item');
}
// Proceed with valid business state
return this.orderRepo.create(data);
}
}
```
### Layer 3: Environment Guards
Add context-specific safeguards:
```typescript
// Repository layer - environment guards
class OrderRepository {
async create(order: Order) {
// Test environment guard
if (process.env.NODE_ENV === 'test' && !process.env.ALLOW_DB_WRITES) {
throw new Error('Database writes disabled in test environment');
}
// Production safety guard
if (order.total > 100000 && !order.managerApproval) {
throw new Error('Large orders require manager approval');
}
// Dangerous operation guard
if (order.userId === SYSTEM_USER_ID) {
throw new Error('Cannot create orders for system user');
}
return this.db.insert('orders', order);
}
}
```
### Layer 4: Debug Instrumentation
Capture execution context for forensic analysis:
```typescript
// Logging layer - forensic evidence
class OrderRepository {
async create(order: Order) {
// Log entry for debugging
this.logger.debug('Creating order', {
orderId: order.id,
userId: order.userId,
itemCount: order.items.length,
total: order.total,
timestamp: new Date().toISOString(),
requestId: context.requestId
});
try {
const result = await this.db.insert('orders', order);
this.logger.info('Order created successfully', {
orderId: result.id,
duration: Date.now() - start
});
return result;
} catch (error) {
this.logger.error('Order creation failed', {
orderId: order.id,
error: error.message,
stack: error.stack,
order: JSON.stringify(order)
});
throw error;
}
}
}
```
---
## Why Multiple Layers?
### Single Point Failure
```typescript
// Only one check - easily bypassed
function createOrder(data) {
if (!data.userId) throw new Error('userId required'); // Single check
// ...
}
// Direct repository call bypasses validation
orderRepository.create({ items: [] }); // No userId check!
```
### Multi-Layer Protection
```typescript
// Multiple checks - defense in depth
// Layer 1: API validates
// Layer 2: Service validates
// Layer 3: Repository validates
// Even if one is bypassed, others catch it
orderRepository.create({ items: [] });
// Repository throws: "userId is required"
```
---
## Implementation Strategy
When debugging, use this approach:
### 1. Trace the Data Flow
```markdown
User Input → API → Service → Repository → Database
```
### 2. Identify Checkpoints
```markdown
Where does this data pass through?
- API endpoint (Layer 1)
- Service method (Layer 2)
- Repository method (Layer 3)
- Database constraints (Layer 4)
```
### 3. Add Validation at Each
```markdown
For each checkpoint:
- What could be wrong at this point?
- What validation makes sense here?
- What error message helps debug?
```
### 4. Test Layer Independence
```markdown
Remove each layer one at a time:
- Does the bug still get caught?
- Which layer catches it?
- Is there a gap in coverage?
```
---
## Validation by Layer Type
| Layer | What to Validate | Example |
|-------|------------------|---------|
| Entry Point | Type, format, presence | `userId` is string, not empty |
| Business Logic | Semantic correctness | User exists, can place orders |
| Environment | Context-specific rules | Test mode restrictions |
| Data Access | Integrity constraints | Foreign keys, not null |
---
## Anti-Patterns
### Single Checkpoint Fallacy
```typescript
// BAD: One validation point
if (isValid(data)) {
// Assume valid everywhere else
}
```
### Validation in Tests Only
```typescript
// BAD: Tests validate, production doesn't
beforeEach(() => {
validateTestData(data); // This doesn't help production
});
```
### Trust After First Check
```typescript
// BAD: Validated once, trusted forever
const validatedData = validate(input);
// ... many lines later ...
process(validatedData); // Is it still valid?
```
---
## Checklist
After fixing any bug:
- [ ] Root cause identified
- [ ] Fix applied at source
- [ ] Layer 1 validation added (entry point)
- [ ] Layer 2 validation added (business logic)
- [ ] Layer 3 guards added (environment)
- [ ] Layer 4 logging added (instrumentation)
- [ ] Tested: removing any single layer still catches bug
- [ ] Bug is structurally impossible, not just fixed
---
## Related Skills
- `root-cause-tracing` - Use before defense-in-depth to find the actual source of the bug before adding multi-layer validation
- `systematic-debugging` - General debugging methodology that pairs with defense-in-depth for comprehensive bug resolution
- `owasp` - Security-specific validation patterns that complement defense-in-depth for security-sensitive code paths
@@ -1,197 +0,0 @@
# Validation Layers Reference
Multi-layer validation strategy ensuring no single point of failure.
## Overview
```
Request -> [Layer 1: Input] -> [Layer 2: Business] -> [Layer 3: Persistence] -> [Layer 4: Output] -> Response
```
Each layer validates independently. A failure at any layer should produce a clear, actionable error. Never rely on a single layer.
## Layer 1: Input Boundary
**Purpose**: Reject malformed, oversized, or obviously invalid data at the edge.
### What to Validate
- Data types and shapes (string, number, object structure)
- Required vs optional fields
- String length, numeric ranges, allowed values
- Format patterns (email, URL, UUID, date)
- Content-Type headers, encoding
- File upload size and MIME type
- Request rate and authentication tokens
### Python (FastAPI + Pydantic)
```python
from pydantic import BaseModel, Field, EmailStr
from fastapi import FastAPI, Query
class CreateUserRequest(BaseModel):
email: EmailStr
name: str = Field(min_length=1, max_length=200)
age: int = Field(ge=0, le=150)
role: Literal["admin", "user", "viewer"]
@app.post("/users")
async def create_user(req: CreateUserRequest):
# req is already validated by Pydantic
...
```
### TypeScript (Zod + Express)
```typescript
import { z } from "zod";
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(200),
age: z.number().int().min(0).max(150),
role: z.enum(["admin", "user", "viewer"]),
});
app.post("/users", (req, res) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.issues });
}
// result.data is typed and validated
});
```
### Tools
| Language | Library | Purpose |
|---|---|---|
| Python | Pydantic, marshmallow, cerberus | Schema validation |
| TypeScript | Zod, Yup, io-ts, Ajv | Schema validation |
| Any | JSON Schema | Language-agnostic schema |
## Layer 2: Business Logic
**Purpose**: Enforce domain rules, state transitions, and authorization.
### What to Validate
- Business rules (e.g., "cannot cancel a shipped order")
- State machine transitions (e.g., draft -> published, not draft -> archived)
- Cross-field dependencies (e.g., "end_date must be after start_date")
- Authorization (e.g., "only the owner can modify this resource")
- Resource existence (e.g., "referenced entity must exist")
- Idempotency and duplicate detection
### Python
```python
class OrderService:
def cancel_order(self, order_id: str, user_id: str) -> Order:
order = self.repo.get(order_id)
if order is None:
raise NotFoundError(f"Order {order_id} not found")
if order.owner_id != user_id:
raise ForbiddenError("Only the order owner can cancel")
if order.status not in ("pending", "confirmed"):
raise BusinessRuleError(
f"Cannot cancel order in '{order.status}' status"
)
order.status = "cancelled"
return self.repo.save(order)
```
### TypeScript
```typescript
class OrderService {
cancelOrder(orderId: string, userId: string): Order {
const order = this.repo.get(orderId);
if (!order) throw new NotFoundError(`Order ${orderId} not found`);
if (order.ownerId !== userId) throw new ForbiddenError("Only the order owner can cancel");
const cancellableStatuses = ["pending", "confirmed"] as const;
if (!cancellableStatuses.includes(order.status)) {
throw new BusinessRuleError(`Cannot cancel order in '${order.status}' status`);
}
order.status = "cancelled";
return this.repo.save(order);
}
}
```
### Guidelines
- Keep validation logic in the service/domain layer, not in controllers
- Use custom exception types that map to HTTP status codes
- Business rules should be testable independently of HTTP/DB
## Layer 3: Data Persistence
**Purpose**: Enforce data integrity at the database level as the last line of defense.
### What to Validate
- NOT NULL constraints
- UNIQUE constraints (email, username)
- FOREIGN KEY constraints (referential integrity)
- CHECK constraints (value ranges, enums)
- Data types and precision
- Default values
### PostgreSQL Examples
```sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL CHECK (char_length(name) > 0),
age INTEGER CHECK (age >= 0 AND age <= 150),
role VARCHAR(20) NOT NULL CHECK (role IN ('admin', 'user', 'viewer')),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
status VARCHAR(20) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'confirmed', 'shipped', 'cancelled')),
total_cents INTEGER NOT NULL CHECK (total_cents >= 0)
);
```
### Guidelines
- Mirror constraints in your ORM (SQLAlchemy `CheckConstraint`, Prisma `@unique`, etc.)
- Database constraints are the safety net; they catch bugs in application code
- Always handle constraint violation errors gracefully (unique violation -> 409 Conflict)
- Use migrations to manage schema changes
## Layer 4: Output Boundary
**Purpose**: Ensure responses are safe, well-formed, and contain only intended data.
### What to Validate
- Strip sensitive fields (passwords, internal IDs, tokens)
- HTML-encode user-generated content to prevent XSS
- Validate response schema (catch accidental data leaks)
- Set security headers (Content-Type, X-Content-Type-Options)
- Limit response size
### Techniques
- **Python**: Use Pydantic `response_model` to exclude fields not in the response schema
- **TypeScript**: Create explicit mapper functions (`toUserResponse()`) that pick only safe fields
- **Headers**: Set `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Content-Security-Policy`
- **Encoding**: HTML-encode user-generated content before rendering
## Layer Interaction Summary
| Layer | Catches | If Missing |
|---|---|---|
| Input | Malformed data, injection attempts | Bad data flows into business logic |
| Business | Invalid operations, auth bypass | Violated business rules, data corruption |
| Persistence | Constraint violations, duplicates | Inconsistent data in database |
| Output | Data leaks, XSS | Sensitive data exposed to clients |
-66
View File
@@ -1,66 +0,0 @@
---
name: devops
description: >
Use when containerizing applications, configuring CI/CD pipelines, deploying to environments, or deploying to edge — including Docker, Dockerfile, docker-compose, multi-stage builds, GitHub Actions, workflow YAML, matrix builds, workflow_dispatch, Cloudflare Workers, Pages, R2, D1, KV, wrangler, container registries, or deployment workflows (staging, production, health checks, smoke tests).
---
# DevOps
## When to Use
- Containerizing applications with Docker or Docker Compose
- Setting up CI/CD pipelines with GitHub Actions
- Deploying to Cloudflare Workers, Pages, R2, D1, or KV
- Deploying applications to staging or production environments
- Running pre-deploy checks (build, tests, security audit)
- Optimizing container images, build caching, or deployment workflows
- Configuring wrangler.toml, Durable Objects, or Cloudflare Queues
## When NOT to Use
- Application code without infrastructure concerns — use framework-specific skills
- Database schema changes — use `databases`
- Security auditing — use `owasp`
---
## Quick Reference
| Topic | Reference | Key features |
|-------|-----------|-------------|
| Docker | `references/docker.md` | Dockerfiles, multi-stage builds, Compose, .dockerignore, healthchecks |
| GitHub Actions | `references/github-actions.md` | Workflow YAML, matrix builds, caching, secrets, reusable workflows |
| Cloudflare Workers | `references/cloudflare-workers.md` | Workers, Pages, R2, D1, KV, Durable Objects, wrangler |
---
## Best Practices
1. **Use multi-stage builds** to keep production images small (Docker).
2. **Pin image tags and action versions** — use digests or major version tags, never `latest`.
3. **Order instructions for cache efficiency** — copy dependency manifests before application code (Docker).
4. **Run as non-root** in containers (Docker).
5. **Use caching aggressively** in CI — cache package manager stores and Docker layers (GitHub Actions).
6. **Set minimal permissions** — add a top-level `permissions` block (GitHub Actions).
7. **Extract reusable workflows and composite actions** for shared CI logic (GitHub Actions).
8. **Keep secrets out of logs** — never `echo` a secret (GitHub Actions).
## Common Pitfalls
1. **Bloated images** — using full base images instead of slim/alpine variants (Docker).
2. **Cache invalidation by COPY order** — placing `COPY . .` before `RUN pip install` (Docker).
3. **Secrets baked into layers** (Docker).
4. **Unpinned action versions** (GitHub Actions).
5. **Overly broad triggers** — triggering on every push to every branch (GitHub Actions).
6. **Secret exposure in pull requests from forks** (GitHub Actions).
7. **Using Node.js APIs without `nodejs_compat`** (Cloudflare Workers).
8. **Blocking the event loop** — Workers have strict CPU time limits (Cloudflare Workers).
9. **Using KV for frequently updated data** — eventually consistent with ~60s propagation (Cloudflare Workers).
---
## Related Skills
- `owasp` — Security hardening for containers and CI
- `git-workflows` — Commits and PRs feeding CI/CD pipelines
- `performance-optimization` — Deploy-time benchmarks and regression checks
@@ -1,543 +0,0 @@
# DevOps — Cloudflare Workers Patterns
# Cloudflare Workers & Pages
## Overview
Edge-first deployment patterns for Cloudflare's platform. Covers Workers (compute), Pages (static + SSR), R2 (object storage), D1 (SQLite at edge), KV (key-value), Durable Objects (stateful), and Queues (async processing). Focused on the Python/TypeScript stack this kit targets.
## When to Use
- Deploying APIs or full-stack apps to Cloudflare's edge network
- Building serverless functions with Workers
- Deploying Next.js or static sites via Cloudflare Pages
- Using D1 (edge SQLite), R2 (S3-compatible storage), or KV (low-latency reads)
- Implementing real-time coordination with Durable Objects
- Background job processing with Cloudflare Queues
## When NOT to Use
- **Long-running compute** (> 30s CPU) — use traditional servers or containers
- **Heavy database workloads** — D1 is SQLite; use Postgres/Mongo for complex queries
- **GPU/ML inference** (unless using Workers AI) — use dedicated compute
- **Local-only development** — Workers run on V8 isolates, not Node.js
---
## Quick Reference
| I need... | Go to |
|-----------|-------|
| Worker project structure | § Project Structure below |
| Hono framework on Workers | § Hono Framework below |
| D1 database patterns | § D1 (Edge SQLite) below |
| R2 object storage | § R2 (Object Storage) below |
| KV key-value store | § KV below |
| Durable Objects | § Durable Objects below |
| Pages deployment (Next.js) | § Cloudflare Pages below |
| CI/CD with GitHub Actions | § CI/CD below |
| Wrangler config reference | See `wrangler-patterns.md` in this skill's directory |
---
## Project Structure
```
my-worker/
├── wrangler.toml # Wrangler config (bindings, routes, env)
├── src/
│ ├── index.ts # Entry point (fetch handler)
│ ├── routes/ # Route handlers
│ ├── middleware/ # Auth, CORS, logging
│ ├── services/ # Business logic
│ └── types.ts # Env bindings type
├── migrations/ # D1 migrations
├── test/ # Vitest tests
└── package.json
```
### Entry point
```typescript
// src/index.ts
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/health') {
return Response.json({ status: 'ok' });
}
// Route to handlers...
return new Response('Not found', { status: 404 });
},
} satisfies ExportedHandler<Env>;
```
### Type-safe bindings
```typescript
// src/types.ts
export interface Env {
DB: D1Database;
BUCKET: R2Bucket;
CACHE: KVNamespace;
API_KEY: string;
ENVIRONMENT: 'development' | 'staging' | 'production';
}
```
---
## Hono Framework (Recommended)
Hono is the de facto framework for Workers — ultralight (~14KB), type-safe, and built for edge runtimes.
```typescript
// src/index.ts
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { HTTPException } from 'hono/http-exception';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
type Bindings = {
DB: D1Database;
BUCKET: R2Bucket;
API_KEY: string;
};
const app = new Hono<{ Bindings: Bindings }>();
app.use('*', logger());
app.use('*', cors({ origin: ['https://app.example.com'], credentials: true }));
// Health check
app.get('/health', (c) => c.json({ status: 'ok' }));
// Validated endpoint
const createUserSchema = z.object({
email: z.string().email().max(254),
name: z.string().min(1).max(100),
});
app.post('/v1/users', zValidator('json', createUserSchema), async (c) => {
const { email, name } = c.req.valid('json');
const result = await c.env.DB
.prepare('INSERT INTO users (id, email, name) VALUES (?, ?, ?) RETURNING *')
.bind(crypto.randomUUID(), email, name)
.first();
return c.json(result, 201);
});
// Error handling — RFC 9457 Problem Details
app.onError((err, c) => {
if (err instanceof HTTPException) {
return c.json({
type: `https://api.example.com/problems/${err.status}`,
title: err.message,
status: err.status,
}, err.status);
}
console.error(err);
return c.json({
type: 'https://api.example.com/problems/internal-error',
title: 'Internal server error',
status: 500,
}, 500);
});
export default app;
```
---
## D1 (Edge SQLite)
Cloudflare's serverless SQL database. SQLite at the edge with automatic replication.
### Migrations
```bash
# Create migration
npx wrangler d1 migrations create my-db create-users
# Apply locally
npx wrangler d1 migrations apply my-db --local
# Apply to production
npx wrangler d1 migrations apply my-db --remote
```
```sql
-- migrations/0001_create-users.sql
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
role TEXT DEFAULT 'member' CHECK(role IN ('admin', 'member', 'viewer')),
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX idx_users_email ON users(email);
```
### Querying with prepared statements
```typescript
// Always use prepared statements — never concatenate SQL
async function getUser(db: D1Database, id: string) {
return db.prepare('SELECT * FROM users WHERE id = ?').bind(id).first();
}
async function listUsers(db: D1Database, cursor?: string, limit = 20) {
const stmt = cursor
? db.prepare('SELECT * FROM users WHERE id > ? ORDER BY id LIMIT ?').bind(cursor, limit)
: db.prepare('SELECT * FROM users ORDER BY id LIMIT ?').bind(limit);
return stmt.all();
}
// Batch multiple statements in a transaction
async function transferCredits(db: D1Database, from: string, to: string, amount: number) {
const results = await db.batch([
db.prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?').bind(amount, from),
db.prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?').bind(amount, to),
]);
return results;
}
```
### D1 limitations to know
- **No JOINs across databases** — one D1 database per binding
- **5MB max row size**, 10GB max database
- **Read replicas are automatic** but writes go to a single leader
- **No stored procedures / triggers** — SQLite subset
- **Prepared statements are mandatory** — `db.exec()` with raw SQL is for migrations only
---
## R2 (Object Storage)
S3-compatible object storage without egress fees.
```typescript
// Upload
app.put('/v1/files/:key', async (c) => {
const key = c.req.param('key');
const body = await c.req.arrayBuffer();
const contentType = c.req.header('Content-Type') ?? 'application/octet-stream';
await c.env.BUCKET.put(key, body, {
httpMetadata: { contentType },
customMetadata: { uploadedBy: c.get('userId') },
});
return c.json({ key, size: body.byteLength }, 201);
});
// Download
app.get('/v1/files/:key', async (c) => {
const obj = await c.env.BUCKET.get(c.req.param('key'));
if (!obj) return c.json({ error: 'Not found' }, 404);
return new Response(obj.body, {
headers: {
'Content-Type': obj.httpMetadata?.contentType ?? 'application/octet-stream',
'ETag': obj.etag,
},
});
});
// List with prefix
app.get('/v1/files', async (c) => {
const prefix = c.req.query('prefix') ?? '';
const listed = await c.env.BUCKET.list({ prefix, limit: 100 });
return c.json({ objects: listed.objects.map((o) => ({ key: o.key, size: o.size })) });
});
```
### Presigned URLs for direct upload
```typescript
// Generate a presigned URL so clients upload directly to R2
app.post('/v1/upload-url', async (c) => {
const key = `uploads/${crypto.randomUUID()}`;
// Use the S3-compatible API for presigned URLs
// Requires R2 API token with write access
return c.json({ key, uploadUrl: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com/${BUCKET_NAME}/${key}` });
});
```
---
## KV (Key-Value Store)
Global low-latency reads (~10ms worldwide), eventually consistent writes.
```typescript
// Set with TTL
await c.env.CACHE.put('session:abc123', JSON.stringify(sessionData), {
expirationTtl: 3600, // 1 hour
});
// Get with type safety
const raw = await c.env.CACHE.get('session:abc123');
const session = raw ? JSON.parse(raw) as SessionData : null;
// List keys by prefix
const keys = await c.env.CACHE.list({ prefix: 'session:' });
// Delete
await c.env.CACHE.delete('session:abc123');
```
**Use KV for:** session tokens, feature flags, cached API responses, configuration. **Not for:** frequently updated counters, multi-key transactions (use Durable Objects).
---
## Durable Objects
Stateful, single-instance coordination. Each Durable Object has a unique ID and runs in exactly one location.
```typescript
// src/counter.ts
export class Counter implements DurableObject {
private count = 0;
constructor(private state: DurableObjectState, private env: Env) {}
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/increment') {
this.count++;
await this.state.storage.put('count', this.count);
return Response.json({ count: this.count });
}
this.count = (await this.state.storage.get<number>('count')) ?? 0;
return Response.json({ count: this.count });
}
}
// In the Worker, route to the Durable Object:
app.post('/v1/counters/:name/increment', async (c) => {
const id = c.env.COUNTER.idFromName(c.req.param('name'));
const stub = c.env.COUNTER.get(id);
const res = await stub.fetch(new Request('https://dummy/increment'));
return c.json(await res.json());
});
```
**Use Durable Objects for:** rate limiting, WebSocket rooms, collaborative editing, distributed locks, shopping carts. **Not for:** read-heavy caching (use KV).
---
## Cloudflare Pages
### Next.js on Pages
```bash
# Deploy Next.js to Cloudflare Pages
npx wrangler pages deploy .next --project-name=my-app
```
Use `@cloudflare/next-on-pages` for full App Router + Server Components support:
```bash
pnpm add @cloudflare/next-on-pages
```
```typescript
// next.config.ts
import { setupDevPlatform } from '@cloudflare/next-on-pages/next-dev';
if (process.env.NODE_ENV === 'development') {
await setupDevPlatform();
}
const nextConfig = { /* ... */ };
export default nextConfig;
```
### Static site on Pages
```bash
# Build and deploy
pnpm build
npx wrangler pages deploy dist/ --project-name=my-site
```
Pages auto-deploys from GitHub: connect your repo in the Cloudflare dashboard, set the build command and output directory. Preview deploys on every PR.
---
## Wrangler Config
```toml
# wrangler.toml
name = "my-api"
main = "src/index.ts"
compatibility_date = "2026-01-01"
compatibility_flags = ["nodejs_compat"]
[vars]
ENVIRONMENT = "production"
# D1 database
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# R2 bucket
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket"
# KV namespace
[[kv_namespaces]]
binding = "CACHE"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# Durable Object
[[durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"
[[migrations]]
tag = "v1"
new_classes = ["Counter"]
# Environment overrides
[env.staging]
vars = { ENVIRONMENT = "staging" }
[env.staging.d1_databases]
binding = "DB"
database_name = "my-db-staging"
database_id = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
```
**`compatibility_date`** pins your Worker to a specific runtime version. Always set it to a recent date and update periodically. **`nodejs_compat`** enables Node.js built-in APIs (Buffer, crypto, streams) — required for most npm packages.
---
## CI/CD
### GitHub Actions deploy
```yaml
# .github/workflows/deploy.yml
name: Deploy Worker
on:
push:
branches: [main]
pull_request:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: pnpm install
- name: Run tests
run: pnpm test
- name: Apply D1 migrations (production)
if: github.ref == 'refs/heads/main'
run: npx wrangler d1 migrations apply my-db --remote
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
- name: Deploy to staging (PR)
if: github.event_name == 'pull_request'
run: npx wrangler deploy --env staging
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
- name: Deploy to production
if: github.ref == 'refs/heads/main'
run: npx wrangler deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
```
### Local development
```bash
# Start local dev server with all bindings (D1, R2, KV, DO)
npx wrangler dev
# With local D1 persistence
npx wrangler dev --persist-to .wrangler/state
```
`wrangler dev` uses Miniflare under the hood — a local simulator for all Cloudflare primitives. Test against real bindings locally before deploying.
---
## Testing
Use **Vitest + Miniflare** (via `@cloudflare/vitest-pool-workers`):
```typescript
// vitest.config.ts
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.toml' },
},
},
},
});
```
```typescript
// test/index.spec.ts
import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test';
import { describe, it, expect } from 'vitest';
import worker from '../src/index';
describe('Worker', () => {
it('returns health check', async () => {
const request = new Request('http://localhost/health');
const ctx = createExecutionContext();
const response = await worker.fetch(request, env, ctx);
await waitOnExecutionContext(ctx);
expect(response.status).toBe(200);
const body = await response.json();
expect(body).toEqual({ status: 'ok' });
});
});
```
---
## Common Pitfalls
1. **Using Node.js APIs without `nodejs_compat`.** Workers run on V8, not Node.js. Without the flag, `Buffer`, `crypto`, `process` are undefined.
2. **Blocking the event loop.** Workers have strict CPU time limits (10ms free, 30s paid). Heavy computation blocks all concurrent requests. Use `ctx.waitUntil()` for background work.
3. **Ignoring D1's eventually consistent reads.** Writes go to the leader; reads from replicas may lag by seconds. Design for eventual consistency.
4. **Using KV for frequently updated data.** KV is eventually consistent with ~60s propagation. Use Durable Objects for strong consistency.
5. **Not setting `compatibility_date`.** Without it, you get the oldest runtime behavior. Always pin to a recent date.
6. **Forgetting `ctx.waitUntil()`.** Background work (logging, analytics) must be wrapped in `waitUntil()` or it gets killed when the response is sent.
7. **Large Worker bundles.** Workers have a 10MB compressed limit (free: 1MB). Tree-shake aggressively; avoid heavy npm packages.
8. **Not testing locally with Miniflare.** `wrangler dev` simulates all bindings locally. Deploying untested changes to edge = debugging in production.
---
## Related Skills
- `docker` — alternative deployment model (containers vs edge)
- `github-actions` — CI/CD pipeline for deploying Workers
- `vitest` — testing Workers with Miniflare pool
-655
View File
@@ -1,655 +0,0 @@
# DevOps — Docker Patterns
# Docker
## When to Use
- Containerizing applications
- Local development environments
- CI/CD pipelines
## When NOT to Use
- Serverless-only deployments where containers are not part of the architecture (e.g., pure AWS Lambda, Cloudflare Workers)
- Local development without containers where native tooling is preferred
- Simple scripts or utilities that do not need isolation or reproducible environments
---
## Core Patterns
### 1. Multi-Stage Builds
Multi-stage builds separate build-time dependencies from the runtime image, producing
smaller, more secure containers.
#### Python (builder + slim runtime)
```dockerfile
# ---- Build stage ----
FROM python:3.12-slim AS builder
WORKDIR /build
# Install build-only dependencies (gcc, etc.) needed by some wheels
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc libpq-dev && \
rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# ---- Runtime stage ----
FROM python:3.12-slim
WORKDIR /app
# Copy only the installed packages from the builder
COPY --from=builder /install /usr/local
# Copy application code
COPY src/ ./src/
COPY main.py .
# Run as non-root
RUN addgroup --system app && adduser --system --ingroup app app
USER app
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```
#### Node.js (build + nginx/alpine)
```dockerfile
# ---- Build stage ----
FROM node:20-alpine AS builder
WORKDIR /app
# Install dependencies first for layer caching
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
# Copy source and build
COPY tsconfig.json ./
COPY src/ ./src/
COPY public/ ./public/
RUN pnpm build
# ---- Runtime stage (static site served by nginx) ----
FROM nginx:1.27-alpine
# Copy custom nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy built assets from builder
COPY --from=builder /app/dist /usr/share/nginx/html
# Run as non-root
RUN chown -R nginx:nginx /usr/share/nginx/html && \
chown -R nginx:nginx /var/cache/nginx && \
chown -R nginx:nginx /var/log/nginx && \
touch /var/run/nginx.pid && \
chown -R nginx:nginx /var/run/nginx.pid
USER nginx
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/ || exit 1
CMD ["nginx", "-g", "daemon off;"]
```
#### Node.js (API server with alpine runtime)
```dockerfile
# ---- Build stage ----
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY tsconfig.json ./
COPY src/ ./src/
RUN pnpm build
# Prune dev dependencies for a lighter production node_modules
RUN pnpm prune --prod
# ---- Runtime stage ----
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
RUN addgroup -S app && adduser -S app -G app
USER app
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]
```
#### Go (build + scratch)
```dockerfile
# ---- Build stage ----
FROM golang:1.22-alpine AS builder
WORKDIR /build
# Download dependencies first for caching
COPY go.mod go.sum ./
RUN go mod download
# Copy source and build a static binary
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server
# ---- Runtime stage (scratch = empty image) ----
FROM scratch
# Copy CA certificates for HTTPS calls
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Copy the static binary
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
```
---
### 2. Docker Compose for Development
A full-featured Compose file with services, volumes, networks, healthchecks, and
environment variable management.
```yaml
services:
app:
build:
context: .
dockerfile: Dockerfile
target: builder # Use builder stage for dev with hot-reload
ports:
- "3000:3000"
environment:
NODE_ENV: development
DATABASE_URL: postgresql://user:pass@db:5432/app
REDIS_URL: redis://redis:6379
env_file:
- .env.local # Local overrides (gitignored)
volumes:
- .:/app # Bind-mount source for hot-reload
- /app/node_modules # Anonymous volume to preserve node_modules
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
networks:
- backend
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d app"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- backend
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
networks:
- backend
worker:
build:
context: .
dockerfile: Dockerfile.worker
environment:
DATABASE_URL: postgresql://user:pass@db:5432/app
REDIS_URL: redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
networks:
- backend
restart: unless-stopped
volumes:
postgres_data:
redis_data:
networks:
backend:
driver: bridge
```
---
### 3. Layer Caching
Docker caches each layer. If a layer has not changed, every layer after it is also
cached. Order instructions from least-frequently-changed to most-frequently-changed.
#### Optimal instruction order
```dockerfile
FROM python:3.12-slim
WORKDIR /app
# 1. System dependencies (rarely change)
RUN apt-get update && apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
# 2. Dependency manifests (change when adding packages)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 3. Application code (changes most often)
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]
```
#### .dockerignore patterns
Always include a `.dockerignore` to keep the build context small and avoid leaking
secrets into layers.
```
# Version control
.git
.gitignore
# Dependencies (rebuilt inside container)
node_modules
__pycache__
*.pyc
.venv
venv
# Build output
dist
build
*.egg-info
# IDE and editor files
.vscode
.idea
*.swp
*.swo
# Environment and secrets
.env
.env.*
*.pem
*.key
# Docker files (not needed in context)
Dockerfile*
docker-compose*
.dockerignore
# Documentation and misc
README.md
CHANGELOG.md
LICENSE
docs/
```
---
### 4. Health Checks
Health checks let Docker (and orchestrators like Compose/Swarm/K8s) know when a
container is actually ready to serve traffic.
#### HTTP health check with curl
```dockerfile
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
```
#### HTTP health check with wget (alpine images without curl)
```dockerfile
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
```
#### TCP port check (for non-HTTP services)
```dockerfile
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD nc -z localhost 5432 || exit 1
```
#### Python-native check (no extra binaries needed)
```dockerfile
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
```
**Parameter reference:**
| Parameter | Description | Default |
|------------------|--------------------------------------------------|---------|
| `--interval` | Time between checks | 30s |
| `--timeout` | Max time for a single check | 30s |
| `--start-period` | Grace period before checks count as failures | 0s |
| `--retries` | Consecutive failures before marking unhealthy | 3 |
---
### 5. Security Hardening
#### Run as non-root user
```dockerfile
# Debian/Ubuntu based images
RUN addgroup --system app && adduser --system --ingroup app app
USER app
# Alpine based images
RUN addgroup -S app && adduser -S app -G app
USER app
```
#### Use minimal base images
| Base Image | Size | Use Case |
|--------------------|---------|---------------------------------------|
| `alpine` | ~5 MB | General minimal base |
| `*-slim` | ~50 MB | Debian-based with fewer packages |
| `distroless` | ~20 MB | Google's no-shell, no-package-manager |
| `scratch` | 0 MB | Static binaries only (Go, Rust) |
```dockerfile
# Distroless for Python
FROM gcr.io/distroless/python3-debian12
COPY --from=builder /app /app
CMD ["main.py"]
```
#### Never put secrets in image layers
```dockerfile
# BAD - secret is baked into image history
COPY .env /app/.env
RUN echo "API_KEY=secret123" >> /app/.env
# GOOD - pass secrets at runtime
CMD ["python", "main.py"]
# docker run -e API_KEY=secret123 myapp
# or docker run --env-file .env myapp
```
#### Multi-stage to exclude build tools
Build tools (compilers, package managers, source code) stay in the builder stage
and never reach the runtime image. This reduces attack surface and image size.
```dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build && pnpm prune --prod
FROM node:20-alpine
WORKDIR /app
# Only the built output and production deps are copied
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/index.js"]
```
---
### 6. Environment Configuration
#### ARG vs ENV
| Directive | Available at | Persists in image | Use for |
|-----------|-------------|-------------------|-----------------------------|
| `ARG` | Build time | No | Build-time variables |
| `ENV` | Build + run | Yes | Runtime configuration |
```dockerfile
# ARG - only available during build
ARG NODE_ENV=production
ARG BUILD_VERSION=unknown
# ENV - available at build and runtime
ENV NODE_ENV=${NODE_ENV}
ENV APP_VERSION=${BUILD_VERSION}
# Build with: docker build --build-arg BUILD_VERSION=1.2.3 .
```
#### .env files with Compose
```yaml
services:
app:
build: .
# Single .env file
env_file:
- .env
# Multiple files (later files override earlier ones)
env_file:
- .env.defaults
- .env.local
# Inline environment variables (override env_file)
environment:
LOG_LEVEL: debug
DEBUG: "true"
```
#### Secrets management with Docker Compose
```yaml
services:
app:
build: .
secrets:
- db_password
- api_key
environment:
DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
environment: API_KEY # Read from host environment
```
Inside the container, secrets are mounted at `/run/secrets/<name>` as files.
---
### 7. Networking
#### Bridge networks for service isolation
```yaml
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
networks:
- frontend-net
- backend-net # Can reach the API
api:
build: ./api
ports:
- "8000:8000"
networks:
- backend-net # Reachable by frontend and workers
db:
image: postgres:16-alpine
networks:
- backend-net # Only reachable by api and workers
# No ports exposed to host
worker:
build: ./worker
networks:
- backend-net
networks:
frontend-net:
driver: bridge
backend-net:
driver: bridge
```
#### Service discovery
Within a Docker Compose network, services reach each other by **service name**
as the hostname.
```python
# In the api service, connect to db using its service name
DATABASE_URL = "postgresql://user:pass@db:5432/app"
# In the frontend service, call the api by service name
API_URL = "http://api:8000"
```
#### Exposing ports
```yaml
services:
app:
ports:
- "3000:3000" # host:container, binds to 0.0.0.0
- "127.0.0.1:3000:3000" # bind to localhost only (more secure)
expose:
- "3000" # expose to other containers only, not host
```
---
## Best Practices
1. **Use multi-stage builds** -- Separate build dependencies from the runtime
image. The final image should contain only what is needed to run the
application.
2. **Pin image tags** -- Use `node:20.11-alpine` or a digest instead of
`node:latest` or `node:20`. Floating tags lead to unpredictable builds.
3. **Order instructions for cache efficiency** -- Copy dependency manifests and
install dependencies before copying application code. This ensures that code
changes do not invalidate the dependency layer cache.
4. **Use .dockerignore** -- Exclude `.git`, `node_modules`, `__pycache__`, `.env`
files, and anything not needed inside the container to keep the build context
small and avoid leaking secrets.
5. **Run as non-root** -- Add a `USER` instruction to run the process as an
unprivileged user. Never run production containers as root.
6. **Combine RUN commands** -- Merge related `RUN` instructions with `&&` to
reduce layers and always clean up apt/apk caches in the same layer that
installs packages.
7. **Use COPY instead of ADD** -- `COPY` is explicit and predictable. `ADD` has
implicit behaviors (tar extraction, URL fetching) that can surprise you.
8. **Set explicit HEALTHCHECK** -- Define health checks in the Dockerfile so
orchestrators know when the container is ready. This prevents routing traffic
to containers that are still starting up.
---
## Common Pitfalls
1. **Bloated images** -- Using full base images like `python:3.12` instead of
`python:3.12-slim` adds hundreds of megabytes. Always prefer slim or alpine
variants. Use multi-stage builds to exclude build tools.
2. **Cache invalidation by COPY order** -- Placing `COPY . .` before
`RUN pip install` means every code change reinstalls all dependencies. Always
copy the dependency manifest first, install, then copy the rest of the code.
3. **Running as root** -- Forgetting the `USER` instruction means the container
process runs as root. If the application is compromised, the attacker has full
control of the container filesystem.
4. **Secrets baked into layers** -- Using `COPY .env .` or `ARG` for secrets
embeds them in the image layer history. Anyone with access to the image can
extract them with `docker history`. Pass secrets at runtime via environment
variables or Docker secrets.
5. **Missing .dockerignore** -- Without a `.dockerignore`, the entire directory
(including `.git`, `node_modules`, `.env` files) is sent as build context.
This slows builds, increases image size, and risks leaking credentials.
6. **Ignoring healthchecks in Compose** -- Using `depends_on` without
`condition: service_healthy` means the dependent service starts as soon as
the database container starts, not when the database is actually ready to
accept connections. Always pair `depends_on` with healthchecks.
---
## Related Skills
- `github-actions` - CI/CD workflows for building and deploying Docker containers
- `owasp` - Security best practices for container hardening and vulnerability scanning
-801
View File
@@ -1,801 +0,0 @@
# DevOps — GitHub Actions Patterns
# GitHub Actions
## When to Use
- Setting up CI/CD pipelines
- Automating tests and builds
- Deployment automation
## When NOT to Use
- GitLab CI projects using `.gitlab-ci.yml` configuration
- Jenkins pipelines using Jenkinsfile or Groovy-based configuration
- CircleCI, Travis CI, or other non-GitHub CI/CD systems
---
## Core Patterns
### 1. CI Pipeline
Complete CI workflow covering checkout, setup, install, lint, test, and build for
both Python and Node.js projects.
#### Node.js CI Pipeline
```yaml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
- run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm typecheck
test:
name: Test
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
- run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm test -- --coverage
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
retention-days: 7
build:
name: Build
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
- run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
retention-days: 5
```
#### Python CI Pipeline
```yaml
name: CI - Python
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
- run: pip install -r requirements-dev.txt
- run: ruff check .
- run: ruff format --check .
- run: mypy src/
test:
name: Test
runs-on: ubuntu-latest
needs: lint
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U test"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
- run: pip install -r requirements.txt -r requirements-dev.txt
- name: Run tests
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
run: pytest -v --cov=src --cov-report=xml
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage-xml
path: coverage.xml
retention-days: 7
```
---
### 2. Matrix Strategy
Matrix builds run the same job across multiple combinations of OS, language
version, or other variables.
#### OS and version matrix
```yaml
jobs:
test:
name: Test (${{ matrix.os }}, Node ${{ matrix.node }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [18, 20, 22]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: "npm"
- run: npm ci
- run: npm test
```
#### Include and exclude
```yaml
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python: ["3.11", "3.12"]
exclude:
# Skip Python 3.11 on Windows
- os: windows-latest
python: "3.11"
include:
# Add a specific combination with extra env
- os: ubuntu-latest
python: "3.13"
experimental: true
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental || false }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- run: pip install -r requirements.txt
- run: pytest
```
---
### 3. Caching
Caching avoids re-downloading dependencies on every run. Use `hashFiles` to
generate cache keys from lockfiles so the cache invalidates when dependencies
change.
#### npm cache
```yaml
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-
```
#### pnpm cache
```yaml
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: echo "store=$(pnpm store path)" >> "$GITHUB_OUTPUT"
- uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.store }}
key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
pnpm-${{ runner.os }}-
```
#### pip cache
```yaml
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('**/requirements*.txt') }}
restore-keys: |
pip-${{ runner.os }}-
```
#### Docker layer cache
```yaml
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: myapp:latest
cache-from: type=gha
cache-to: type=gha,mode=max
```
---
### 4. Reusable Workflows
Reusable workflows let you define a workflow once and call it from other
workflows, reducing duplication across repositories.
#### Defining a reusable workflow (`.github/workflows/reusable-test.yml`)
```yaml
name: Reusable Test Workflow
on:
workflow_call:
inputs:
node-version:
description: "Node.js version to use"
required: false
type: string
default: "20"
working-directory:
description: "Directory to run commands in"
required: false
type: string
default: "."
secrets:
NPM_TOKEN:
required: false
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: "npm"
registry-url: "https://registry.npmjs.org"
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: npm test
```
#### Calling a reusable workflow
```yaml
name: CI
on:
push:
branches: [main]
jobs:
test-app:
uses: ./.github/workflows/reusable-test.yml
with:
node-version: "20"
working-directory: "packages/app"
secrets: inherit # Pass all secrets to the called workflow
test-lib:
uses: ./.github/workflows/reusable-test.yml
with:
node-version: "20"
working-directory: "packages/lib"
secrets: inherit
```
---
### 5. Composite Actions
Composite actions package multiple steps into a single reusable action. Unlike
reusable workflows, they run inline within the calling job.
#### Action definition (`.github/actions/setup-project/action.yml`)
```yaml
name: "Setup Project"
description: "Install Node.js, enable corepack, and install dependencies"
inputs:
node-version:
description: "Node.js version"
required: false
default: "20"
install-command:
description: "Command to install dependencies"
required: false
default: "pnpm install --frozen-lockfile"
runs:
using: "composite"
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Enable corepack
shell: bash
run: corepack enable
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: echo "store=$(pnpm store path)" >> "$GITHUB_OUTPUT"
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.store }}
key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
pnpm-${{ runner.os }}-
- name: Install dependencies
shell: bash
run: ${{ inputs.install-command }}
```
#### Using the composite action
```yaml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
with:
node-version: "20"
- run: pnpm build
```
---
### 6. Deployment
Deployment workflows with environment protection rules, manual approval gates,
and multi-stage promotion.
```yaml
name: Deploy
on:
push:
branches: [main]
workflow_dispatch:
inputs:
environment:
description: "Target environment"
required: true
type: choice
options:
- staging
- production
permissions:
contents: read
deployments: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
- run: corepack enable && pnpm install --frozen-lockfile
- run: pnpm build
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: build
environment:
name: staging
url: https://staging.example.com
steps:
- uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
- name: Deploy to staging
env:
DEPLOY_TOKEN: ${{ secrets.STAGING_DEPLOY_TOKEN }}
run: |
echo "Deploying to staging..."
# Replace with your actual deploy command
# e.g., aws s3 sync, rsync, wrangler publish, etc.
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: deploy-staging
if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'production'
environment:
name: production
url: https://example.com
# Production environment should have required reviewers configured
# in GitHub Settings > Environments > production > Protection rules
steps:
- uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
- name: Deploy to production
env:
DEPLOY_TOKEN: ${{ secrets.PRODUCTION_DEPLOY_TOKEN }}
run: |
echo "Deploying to production..."
```
---
### 7. Artifacts
Artifacts let you share data between jobs in the same workflow or persist build
outputs for later download.
#### Upload artifact
```yaml
- name: Upload test results
uses: actions/upload-artifact@v4
if: always() # Upload even if tests fail
with:
name: test-results-${{ matrix.os }}-${{ matrix.node }}
path: |
test-results/
coverage/
retention-days: 14
if-no-files-found: warn # warn, error, or ignore
```
#### Download artifact in another job
```yaml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
deploy:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- run: ls -la dist/
```
#### Download all artifacts
```yaml
- uses: actions/download-artifact@v4
with:
path: all-artifacts/
# Each artifact is placed in a subdirectory named after the artifact
```
---
### 8. Conditional Execution
Control when jobs and steps run using `if` expressions, job dependencies, and
path filters.
#### Path filters on triggers
```yaml
on:
push:
branches: [main]
paths:
- "src/**"
- "package.json"
- "pnpm-lock.yaml"
paths-ignore:
- "docs/**"
- "*.md"
```
#### Conditional jobs
```yaml
jobs:
changes:
runs-on: ubuntu-latest
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
backend:
- 'src/api/**'
- 'requirements*.txt'
frontend:
- 'src/web/**'
- 'package.json'
test-backend:
needs: changes
if: needs.changes.outputs.backend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install -r requirements.txt && pytest
test-frontend:
needs: changes
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
```
#### Conditional steps with if expressions
```yaml
steps:
- name: Run only on main branch
if: github.ref == 'refs/heads/main'
run: echo "On main"
- name: Run only on pull requests
if: github.event_name == 'pull_request'
run: echo "PR event"
- name: Run only when previous step failed
if: failure()
run: echo "Something failed"
- name: Always run (cleanup)
if: always()
run: echo "Cleanup"
- name: Run only when a label is present
if: contains(github.event.pull_request.labels.*.name, 'deploy')
run: echo "Deploy label found"
- name: Skip for dependabot
if: github.actor != 'dependabot[bot]'
run: npm test
```
#### Job dependencies
```yaml
jobs:
lint:
runs-on: ubuntu-latest
steps:
- run: echo "Linting..."
test:
runs-on: ubuntu-latest
steps:
- run: echo "Testing..."
# Runs after both lint and test succeed
deploy:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- run: echo "Deploying..."
# Runs even if test fails, but only after it completes
notify:
runs-on: ubuntu-latest
needs: [test]
if: always()
steps:
- run: echo "Test job status: ${{ needs.test.result }}"
```
---
## Best Practices
1. **Pin action versions with SHA** -- Use the full commit SHA instead of a
mutable tag: `actions/checkout@b4ffde65f...` (or at minimum a major version
tag like `@v4`). This prevents supply-chain attacks where a tag is moved.
2. **Use caching aggressively** -- Cache package manager stores (`~/.npm`,
pnpm store, `~/.cache/pip`) and Docker layers. A well-cached pipeline can
cut run times by 50-80%.
3. **Set minimal permissions** -- Add a top-level `permissions` block and grant
only what is needed. Default permissions are overly broad and pose a security
risk, especially for pull requests from forks.
4. **Run jobs in parallel** -- Structure independent jobs (lint, test, typecheck)
to run concurrently. Use `needs` only when there is a real dependency between
jobs.
5. **Use `fail-fast: false` in matrix builds** -- By default a failing matrix
combination cancels all others. Setting `fail-fast: false` lets all
combinations complete so you get the full picture of what is broken.
6. **Use environment protection rules** -- Configure required reviewers and wait
timers on production environments in GitHub Settings. This adds a human gate
before production deploys.
7. **Extract reusable workflows and composite actions** -- If the same steps
appear in multiple workflows, factor them into a reusable workflow
(`workflow_call`) or composite action to keep things DRY.
8. **Keep secrets out of logs** -- Never `echo` a secret. GitHub masks known
secrets, but dynamically constructed values may leak. Use `::add-mask::` for
runtime values that should be hidden.
---
## Common Pitfalls
1. **Unpinned action versions** -- Using `actions/checkout@main` means your
workflow pulls whatever is on main today. A bad push to that action
repository could break or compromise your builds. Pin to a tag (`@v4`) or
SHA.
2. **Missing caching** -- Running `npm ci` or `pip install` from scratch on
every run wastes minutes. Always configure caching for your package manager,
or use the built-in `cache` option in setup actions (e.g.,
`actions/setup-node` has a `cache` input).
3. **Overly broad triggers** -- Triggering on every push to every branch floods
the queue. Restrict triggers to `main` and pull requests. Use `paths` or
`paths-ignore` to skip runs when only docs or unrelated files change.
4. **Secret exposure in pull requests from forks** -- Secrets are NOT available
in workflows triggered by `pull_request` from forks (by design). If your
workflow needs secrets for fork PRs, use `pull_request_target` carefully and
never check out untrusted code in that context.
5. **Large artifacts without retention limits** -- Uploading artifacts without
setting `retention-days` uses the repository default (90 days), consuming
storage quota. Set short retention for transient artifacts like test results
and coverage reports.
6. **Ignoring `if: always()` for cleanup** -- Steps after a failure are skipped
by default. If you need to upload test results, send notifications, or run
cleanup regardless of prior step results, use `if: always()` or
`if: failure()`.
---
## Related Skills
- `docker` - Container patterns for building and deploying Dockerized applications in workflows
- `pytest` - Python test configuration for CI pipeline integration
- `vitest` - TypeScript/JavaScript test configuration for CI pipeline integration
-329
View File
@@ -1,329 +0,0 @@
---
name: dispatching-parallel-agents
description: >
Use when facing 3 or more independent failures across different domains, when multiple subsystems are broken with no shared state, or when test failures span unrelated modules. Also activate whenever you see independent bugs in auth, cart, user, or other separate domains that can be fixed concurrently. Use for launching parallel background tasks like research, analysis, or code review across independent areas. Activate aggressively for any scenario where parallel work would reduce total resolution time without creating merge conflicts.
---
# Dispatching Parallel Agents
## When to Use
- Multiple subsystems broken independently
- No shared state between failures
- Each fix is self-contained
- Parallel work won't create conflicts
## When NOT to Use
- Tasks with shared state or sequential dependencies where one fix affects another
- Single-file changes that don't benefit from parallelization overhead
- Sequential workflows where each step depends on the output of the previous step
---
## Core Principle
**"Dispatch one agent per independent problem domain. Let them work concurrently."**
### Why Parallel?
- Faster resolution (3 problems in time of 1)
- Focused context per agent
- No context pollution between fixes
- Easy to integrate results
### Why Not Always Parallel?
- Related problems need shared context
- Exploration requires system-wide view
- Conflicting changes cause merge issues
- Some fixes depend on others
---
## Identification Pattern
### Step 1: Group Failures by Domain
```markdown
Test failures:
- src/auth/login.test.ts (3 failures) → Auth domain
- src/cart/checkout.test.ts (2 failures) → Cart domain
- src/user/profile.test.ts (1 failure) → User domain
Each is independent - fixing one doesn't affect others.
```
### Step 2: Verify Independence
```markdown
Ask for each group:
- Does it share state with other groups? NO
- Does fixing it require changes to other groups? NO
- Could fixes conflict with each other? NO
If all NO → Parallel is safe
If any YES → Sequential or combined approach
```
---
## Task Creation Pattern
Each agent receives:
### 1. Specific Scope
```markdown
BAD: "Fix all the tests"
GOOD: "Fix auth/login.test.ts - 3 failing tests"
```
### 2. Clear Goal
```markdown
BAD: "Make it work"
GOOD: "Make all tests in auth/login.test.ts pass"
```
### 3. Constraints
```markdown
- Only modify files in src/auth/
- Don't change the test expectations
- Don't modify shared utilities
```
### 4. Expected Output
```markdown
Return:
- Files modified
- Tests now passing
- Summary of changes
- Any concerns
```
---
## Execution Pattern
### Dispatch Agents Concurrently
```markdown
Agent 1: Fix auth/login.test.ts
Agent 2: Fix cart/checkout.test.ts
Agent 3: Fix user/profile.test.ts
All three run simultaneously.
```
### Monitor Progress
```markdown
While agents working:
- Check for early failures
- Watch for scope violations
- Ready to pause if conflicts detected
```
---
## Integration Pattern
### Step 1: Collect Results
```markdown
Agent 1 returned:
- Modified: src/auth/login-service.ts
- Tests: 3/3 passing
- Summary: Fixed token validation edge case
Agent 2 returned:
- Modified: src/cart/checkout-service.ts
- Tests: 2/2 passing
- Summary: Fixed price calculation rounding
Agent 3 returned:
- Modified: src/user/profile-service.ts
- Tests: 1/1 passing
- Summary: Fixed null handling in profile update
```
### Step 2: Verify No Conflicts
```markdown
Check:
- No overlapping file modifications
- No conflicting changes to shared types
- No incompatible API changes
```
### Step 3: Run Full Test Suite
```bash
npm test
# All tests should pass including:
# - The 6 originally failing tests
# - All other tests (no regressions)
```
### Step 4: Integrate Changes
```bash
# If all agents used branches
git merge agent-1-auth-fixes
git merge agent-2-cart-fixes
git merge agent-3-user-fixes
```
---
## Example Prompts
### Agent Task Prompt Template
```markdown
## Task: Fix [specific test file]
**Scope**: Only modify files in [directory]
**Failing tests**:
1. [test name 1]
2. [test name 2]
**Constraints**:
- Do not modify test expectations
- Do not change shared utilities in src/utils/
- Do not modify types in src/types/
**Goal**: Make all tests in [file] pass
**Return**:
- List of files modified
- Summary of changes made
- Number of tests now passing
- Any concerns about the changes
```
### Result Collection Prompt
```markdown
## Parallel Agent Results
**Agent 1 (Auth)**:
[Paste agent 1 results]
**Agent 2 (Cart)**:
[Paste agent 2 results]
**Agent 3 (User)**:
[Paste agent 3 results]
## Integration Checklist
- [ ] No file conflicts
- [ ] Full test suite passes
- [ ] Changes are isolated to domains
- [ ] Ready to merge
```
---
## Example: Full-Stack Feature Dispatch
A real-world example dispatching 3 agents for a new "orders" feature:
### Independence check
| | Agent 1 (Backend) | Agent 2 (Frontend) | Agent 3 (Database) |
|---|---|---|---|
| **Files** | `src/api/orders.py`, `tests/test_orders.py` | `src/components/order-form.tsx`, `*.test.tsx` | `migrations/003_orders.sql`, `tests/test_migration.py` |
| **Test suite** | `pytest tests/test_orders.py` | `npm test -- order-form` | `pytest tests/test_migration.py` |
| **Shared state?** | No | No | No |
All three touch different files and different test suites — safe to parallelize.
### Agent 1 — Backend (FastAPI)
```markdown
## Task: Implement POST /api/orders with validation
**Context**: FastAPI + SQLAlchemy async + Pydantic v2
**Files**: src/api/orders.py, src/schemas/order.py, tests/test_orders.py
**Constraints**: Depends(get_db), return 201, RFC 9457 errors
**Verify**: pytest tests/test_orders.py -v
```
### Agent 2 — Frontend (React/Next.js)
```markdown
## Task: Build OrderForm component with validation
**Context**: Next.js App Router + react-hook-form + Zod + shadcn/ui
**Files**: src/components/order-form.tsx, src/components/order-form.test.tsx
**Constraints**: 'use client', Zod schema, accessible form fields
**Verify**: npx vitest run src/components/order-form.test.tsx
```
### Agent 3 — Database (PostgreSQL)
```markdown
## Task: Create orders table migration
**Context**: Alembic migrations, PostgreSQL
**Files**: migrations/003_create_orders.sql, tests/test_orders_migration.py
**Constraints**: Include indexes on user_id and created_at, add foreign key to users
**Verify**: pytest tests/test_orders_migration.py -v
```
### Integration after all 3 complete
```bash
# 1. Run each agent's test suite to confirm
pytest tests/test_orders.py tests/test_orders_migration.py -v
npx vitest run src/components/order-form.test.tsx
# 2. Run full test suite for regressions
pytest -v && npm test
# 3. Verify no file conflicts
git diff --name-only # should show no overlapping files between agents
```
---
## Conflict Resolution
If conflicts detected:
```markdown
1. STOP parallel execution
2. Identify conflicting changes
3. Decide which takes priority
4. Continue sequentially from conflict point
5. Learn: Update domain boundaries
```
---
## Checklist
Before parallel dispatch:
- [ ] 3+ independent failures identified
- [ ] Failures grouped by domain
- [ ] Independence verified (no shared state)
- [ ] Scope boundaries clear
- [ ] Conflict potential assessed
After parallel completion:
- [ ] All agent results collected
- [ ] No file conflicts detected
- [ ] Full test suite passes
- [ ] Changes integrated successfully
---
## Related Skills
- `executing-plans` - Use executing-plans when tasks are sequential; use dispatching-parallel-agents when tasks are independent and can run concurrently
- `writing-plans` - Write a plan first to identify which tasks are independent before dispatching parallel agents
@@ -1,196 +0,0 @@
# Parallelization Patterns Reference
How to decide what to parallelize and which pattern to use.
## Core Principle
Parallelize when tasks are **independent**: no shared mutable state, no ordering dependency, and results can be combined without conflict.
## Pattern 1: Independent Tasks
**When**: Two or more tasks share no state and have no ordering dependency.
**Always parallel.** This is the simplest and most common case.
### Examples
- Linting + type checking + unit tests (different tools, same codebase, read-only)
- Researching two unrelated libraries
- Generating tests for unrelated modules
- Reviewing separate files
### Structure
```
[Dispatcher]
|--- Agent A: lint src/
|--- Agent B: typecheck src/
|--- Agent C: run tests
\--- Agent D: security scan
[Collect all results]
```
### Decision Criteria
- Do they read/write the same files? No -> parallel
- Does one need output from another? No -> parallel
- Can they run in any order? Yes -> parallel
## Pattern 2: Fan-Out / Fan-In
**When**: A single task can be split into N identical subtasks, then results are merged.
### Examples
- Process each file in a directory independently
- Run the same analysis on multiple services
- Test multiple configurations
- Investigate multiple potential causes of a bug
### Structure
```
[Dispatcher: split work into N chunks]
|--- Agent 1: process chunk 1
|--- Agent 2: process chunk 2
|--- Agent 3: process chunk 3
\--- Agent N: process chunk N
[Collector: merge results from all agents]
```
### Implementation
Split items across agents (round-robin, by directory, or by type), dispatch all simultaneously, collect results, handle failures by retrying individually, then merge into unified output.
## Pattern 3: Pipeline (Sequential)
**When**: Output of step N is input to step N+1.
**Must be sequential.** Cannot parallelize.
### Examples
- Parse code -> analyze AST -> generate report
- Fetch data -> transform -> validate -> persist
- Write code -> run tests -> fix failures
### Structure
```
[Step 1: parse] --> [Step 2: analyze] --> [Step 3: report]
```
### When Pipelines Contain Parallelizable Steps
A pipeline stage itself might fan out:
```
[Step 1: identify files]
--> [Step 2: analyze each file in parallel (fan-out/fan-in)]
--> [Step 3: merge analysis into report]
```
## Pattern 4: Pipeline with Parallel Stages
**When**: Some pipeline stages can run in parallel, others must be sequential.
### Example: Feature Implementation
```
[Sequential: write plan]
--> [Parallel: implement module A, implement module B, implement module C]
--> [Sequential: integration test]
--> [Parallel: write docs, update changelog]
--> [Sequential: final review]
```
## Decision Matrix
| Task Characteristic | Pattern | Parallelizable? |
|---|---|---|
| No shared state, no ordering | Independent | Yes |
| Same operation on many items | Fan-out/fan-in | Yes |
| Output feeds next step | Pipeline | No |
| Mixed dependencies | Pipeline + parallel stages | Partially |
| Shared mutable state | Sequential or lock-based | No (usually) |
| Non-deterministic ordering matters | Sequential | No |
## Common Parallel Task Patterns
### File-Per-Agent
Split work by file or directory. Each agent owns its files exclusively.
```
Agent 1: src/auth/**
Agent 2: src/orders/**
Agent 3: src/users/**
```
**Best for**: code review, refactoring, test generation, documentation.
**Watch out for**: shared utilities, cross-module imports. Assign shared code to one agent or make it read-only for all.
### Test Suite Splitting
Split tests by module, type, or estimated runtime.
```
Agent 1: unit tests (fast)
Agent 2: integration tests (medium)
Agent 3: e2e tests (slow)
```
**Best for**: CI acceleration, pre-merge validation.
### Multi-Service Investigation
When debugging spans multiple services, assign one agent per service.
```
Agent 1: investigate auth service logs
Agent 2: investigate order service logs
Agent 3: investigate payment service logs
```
**Best for**: distributed system debugging, incident response.
### Research Branches
Explore multiple hypotheses or approaches simultaneously.
```
Agent 1: research approach A (Redis caching)
Agent 2: research approach B (CDN edge caching)
Agent 3: research approach C (application-level memoization)
```
**Best for**: technology evaluation, design exploration, root cause hypotheses.
## Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
| Parallelizing dependent tasks | Race conditions, wrong results | Identify dependencies first, use pipeline |
| Too many agents | Overhead exceeds benefit | 2-5 agents is typical sweet spot |
| No merge strategy | Results conflict or duplicate | Define merge/dedup logic before dispatching |
| Shared file writes | Corruption, lost changes | Assign file ownership to one agent |
| No failure handling | One failure blocks everything | Collect partial results, retry individually |
## Checklist Before Parallelizing
1. **List all tasks** that need to happen
2. **Draw dependencies** between them (which needs output from which?)
3. **Group independent tasks** into parallel batches
4. **Define the merge strategy** for collecting results
5. **Assign ownership** so no two agents write the same file
6. **Plan for failure** of individual agents
7. **Estimate whether parallelism helps** (overhead vs time saved)
## Quick Reference: Dispatch Decision
- Single atomic operation -> just do it, no parallelism
- Splittable into independent chunks -> fan-out/fan-in
- Each step depends on previous output -> pipeline (sequential)
- Mix of independent and dependent steps -> pipeline with parallel stages
- Everything independent -> run all in parallel
+183
View File
@@ -0,0 +1,183 @@
---
name: evidence-driven-debugging
user-invocable: true
description: >
Use during active debugging when you have a hypothesis to test or need to
instrument a running system. Activate for keywords like "debug", "instrument",
"log", "trace", "breakpoint", "what's happening at runtime", "production
behavior". Pair to investigate-root-cause for the doing-it phase. Always
record what you observed -- never debug entirely "in your head" without leaving
evidence behind.
---
# Evidence-Driven Debugging
## Overview
The active-debugging companion to `investigate-root-cause`. Where investigate
produces a written hypothesis, evidence-driven-debugging is the workflow for
*testing* that hypothesis with real instrumentation: logs, breakpoints, prints,
debugger sessions, runtime probes. The skill exists because the most common
debugging-phase failure is the engineer who runs through three or four mental
hypotheses without writing anything down, ends up where they started, and can't
reconstruct what they tried. Evidence-driven debugging keeps a paper trail.
Used inside Phase 3 of `investigate-root-cause`, but invocable directly when an
existing hypothesis just needs runtime testing.
## When to Use
- You have a hypothesis from `investigate-root-cause` and need to test it
- You're debugging in a system that's hard to step through (async, distributed,
multi-process)
- You've added logs/prints to test a theory and need to organize what you learn
- A bug only reproduces in a deployed environment, not locally
- You're about to do "let me just add some console.logs" — pause and use this
skill to keep them organized
## When NOT to Use
- You don't have a hypothesis yet — go to `investigate-root-cause` Phase 2 first
- The bug is in code you can step through with a debugger and the path is short
- The fix is one line and obvious from reading; debugging instrumentation is
overkill
## Process
### Step 1: State the hypothesis to test
**Goal:** Be explicit about what runtime evidence will confirm or refute.
**Inputs:** A hypothesis (from `investigate-root-cause` Phase 2 or your own
prior thinking).
**Actions:**
1. Write the hypothesis as one sentence: `The bug occurs because [X] causes [Y]
when [Z].`
2. Decide what runtime evidence would confirm it: a value at a specific line, a
sequence of events, an absence of an expected log line.
3. Decide what would refute it: the value isn't what you predicted, the
sequence is different, the expected event happens but the bug still occurs.
**Output:** A test design: `If I see <evidence>, hypothesis is confirmed; if I
see <other>, hypothesis is refuted; ambiguous = collect more.`
### Step 2: Place instrumentation
**Goal:** Add the minimum runtime probes to capture the evidence.
**Inputs:** The test design.
**Actions:**
1. Choose the instrumentation method that fits the system:
- Synchronous code with a debugger available: breakpoint at the predicted line.
- Async or distributed code: structured log lines with a tag (e.g.,
`[bug-1234]`).
- Production-only repro: a feature flag that turns on extra logging for one
tenant or one user.
2. Add probes at the boundaries: input, decision points, output. Three probes
beats one super-probe — boundaries catch where the value changes.
3. Tag every probe with the same identifier so you can filter logs later.
4. Commit the instrumentation in a separate commit with a `debug:` prefix so
it's easy to revert.
**Output:** Instrumentation in code (or in a debugger config), tagged.
### Step 3: Reproduce and capture
**Goal:** Run the bug with the instrumentation in place and capture output.
**Inputs:** Instrumented code + the reproducer from `investigate-root-cause`
Phase 1.
**Actions:**
1. Run the reproducer. Capture every probe's output.
2. Save the captured output to a scratch file or PR comment. Don't rely on
terminal scrollback.
3. If the bug is intermittent, run the reproducer multiple times. Capture each
run separately so you can spot the variance.
**Output:** Captured probe output, saved.
### Step 4: Compare against the test design
**Goal:** Decide confirm/refute/ambiguous.
**Inputs:** Captured output + Step 1's test design.
**Actions:**
1. Read the output line by line. Match each line to the design's expected
evidence.
2. Verdict:
- **Confirmed:** the predicted evidence matched. Move to fix.
- **Refuted:** the prediction was wrong. The hypothesis is wrong; return to
`investigate-root-cause` Phase 2 with the new evidence.
- **Ambiguous:** the output didn't clearly match either case. Add more
instrumentation (Step 2 again) or run more reproducers (Step 3 again).
3. Write down the verdict and the evidence supporting it.
**Output:** A one-line verdict: `Hypothesis confirmed | Refuted (return to
hypothesis) | Ambiguous (add probes at <location>)`.
### Step 5: Clean up the instrumentation
**Goal:** Remove debug probes when the work is done.
**Inputs:** A confirmed hypothesis (or a refuted one that led you elsewhere).
**Actions:**
1. Revert the `debug:` commits, OR
2. Convert any probes worth keeping into proper structured logs (with the
project's standard logger, with the right log level, no `[bug-1234]` tag).
These become permanent observability.
3. Confirm no debug `print` / `console.log` / `dbg!` lines remain in the
committed code.
**Output:** Clean working tree. Either the debug commits are reverted or
formalized.
## Rationalizations
| Excuse | Why it sounds reasonable | Why it's wrong | What to do instead |
|---|---|---|---|
| "I'll just add some console.logs and figure it out as I go." | Fast, low-overhead, the standard move. | The "figure it out as you go" version usually doesn't write down the hypothesis you're testing or what would refute it. You add prints, see some output, decide it "looks suspicious," add more prints, follow the suspicion, and lose track of which hypothesis you started with. By the time you find the bug (or get stuck), you can't reconstruct what you tried. | Spend 60 seconds on Step 1's test design before adding probes. Even one sentence is enough. The structure forces you to know what you're looking for, which is what makes the probes interpretable when they fire. |
| "The probes don't need tags — there aren't that many logs." | A handful of probes in a quiet system don't need filtering. | "Not that many logs" is true on your dev box. In a production-grade reproducer, the probe lines are intermixed with framework logs, request logs, third-party noise. Untagged probes are findable only by remembering which file you put them in, which is the same memory problem the skill exists to fix. | Tag every probe with the same identifier (`[bug-1234]`, `[debug-jane]`, whatever's unique). Filtering on the tag isolates your evidence in seconds. |
| "I don't need to save the output — I just looked at it." | Looked-at-and-understood is real. | Looked-at-and-understood doesn't survive a context switch. If you finish a debugging session at 6 PM and resume at 9 AM, the captured output is gone, you're reconstructing from the bug-fix patch you didn't write down, and you re-instrument in a slightly different way and lose comparability. | Save the output. Even if it's `tail -f log.out > /tmp/bug-1234-run-1.log`. The save is the deliverable for Step 3. |
| "Refuted hypothesis means I should fix it anyway — I have a workaround in mind." | Sometimes the workaround is faster than continuing to investigate. | The workaround that ships against a refuted hypothesis is the workaround that doesn't fix the actual bug. The bug recurs in a different shape because the fix addressed a hypothesis the evidence already disagreed with. The workaround is at best a shim and at worst a compounding error. | If refuted, return to `investigate-root-cause` Phase 2. The evidence you just gathered is input to the next hypothesis; don't waste it by patching against a hypothesis it disproved. |
| "I'll leave the debug logs in — observability is good." | Adding logs is one of the cheapest improvements to a service's observability. | Debug logs left in are not observability. They lack the structure (level, key-value, sampling) of proper logs; they pollute the log stream with bug-1234 tags forever; they confuse the next person who searches for "the right way" to log this thing and finds your debug prints. | If a probe is genuinely useful as long-term observability, *convert* it: re-write as a structured log with proper level, no tag, in the right place. Otherwise revert. The middle option (leave the debug logs in unchanged) is the worst of both. |
## Evidence Requirements
| Checkpoint | Required artifact | What "no evidence" looks like |
|---|---|---|
| End of Step 1 | A test design naming what confirms, refutes, and what's ambiguous | "I'll see what the logs say." |
| End of Step 2 | Instrumented code committed with `debug:` prefix and a shared tag | "I dropped some console.logs in." |
| End of Step 3 | Captured output saved to a file or PR comment | "I saw the output in my terminal." |
| End of Step 4 | A one-line verdict and the evidence supporting it | "It seems like the cache is the problem." |
| End of Step 5 | Reverted or formalized probes; clean working tree confirmed | "I'll clean up the debug logs later." |
## Red Flags
- More than 5 probes added in one round. You're guessing where to look; tighten
the hypothesis first.
- Probes are clustered in one file even though the system is multi-component.
You're debugging only the part you're comfortable with, not the system.
- The output file is empty after Step 3. Either the reproducer didn't actually
run or the probe is on a code path that wasn't hit. Check before you draw
conclusions.
- The verdict is "ambiguous" three times in a row. Either the hypothesis is too
vague (return to Phase 2) or the system is genuinely too hard to instrument
through (escalate to someone who knows the runtime).
- The cleanup step is "I'll do it after the PR merges." Debug commits live in
the merged history forever; clean up before merge.
## References
- John Allspaw, "Resilience Engineering: Where Do I Start?"
(adaptivecapacitylabs.com, 2019) — the principle that observability is
designed before the incident, not retrofitted during. Step 5's "convert vs
revert" decision operationalizes this for per-bug debugging instrumentation.
-334
View File
@@ -1,334 +0,0 @@
---
name: executing-plans
description: >
Use when there is a written implementation plan ready to execute, or when the user says "execute", "run the plan", "implement the plan", "start building", or references a plan file. Also activate when using subagent-driven development with independent tasks, when the user wants automated execution with quality gates, or when picking up a previously written plan. If a plan document exists and no one is executing it yet, this is the skill to use.
---
# Executing Plans
## When to Use
- Executing plans created with `writing-plans` skill
- Staying in current session with independent tasks
- Wanting quality gates without human delays
- Systematic implementation with verification
## When NOT to Use
- No plan exists yet -- use `writing-plans` first to create one
- Single-task work that does not need sequential execution or review gates
- Research or exploration where the goal is learning, not building
---
## Core Pattern
**"Fresh subagent per task + review between tasks = high quality, fast iteration"**
### Why Fresh Agents?
- Prevents context pollution between tasks
- Each task gets focused attention
- Failures don't cascade
- Easier to retry individual tasks
### Why Code Review Between Tasks?
- Catches issues early
- Ensures code matches intent
- Prevents technical debt accumulation
- Creates natural checkpoints
---
## Execution Workflow
### Step 1: Load Plan
```markdown
1. Read the plan file
2. Verify plan is complete and approved
3. Create TodoWrite with all tasks from plan
4. Set first task to in_progress
```
### Step 2: Execute Task
For each task:
```markdown
1. Dispatch fresh subagent with task details
2. Subagent implements following TDD cycle:
- Write failing test
- Verify test fails
- Implement minimally
- Verify test passes
- Commit
3. Subagent returns completion summary
```
### Step 3: Code Review
After each task:
```markdown
1. Dispatch code-reviewer subagent
2. Review scope: only changes from current task
3. Reviewer returns findings:
- Critical: Must fix before proceeding
- Important: Should fix before proceeding
- Minor: Can fix later
```
### Step 4: Handle Review Findings
```markdown
IF Critical or Important issues found:
1. Dispatch fix subagent for each issue
2. Re-request code review
3. Repeat until no Critical/Important issues
IF only Minor issues:
1. Note for later cleanup
2. Proceed to next task
```
### Step 5: Mark Complete
```markdown
1. Update TodoWrite - mark task completed
2. Move to next task
3. Repeat from Step 2
```
### Step 6: Final Review
After all tasks complete:
```markdown
1. Dispatch comprehensive code review
2. Review entire implementation against plan
3. Verify all success criteria met
4. Run full test suite
5. Use `finishing-a-development-branch` skill
```
---
## Critical Rules
### Never Skip Code Reviews
Every task must be reviewed before proceeding. No exceptions.
### Never Proceed with Critical Issues
Critical issues must be fixed. The pattern is:
```
implement → review → fix critical → re-review → proceed
```
### Never Run Parallel Implementation
Tasks run sequentially:
```
WRONG: Run Task 1, 2, 3 simultaneously
RIGHT: Run Task 1 → Review → Task 2 → Review → Task 3 → Review
```
### Always Read Plan Before Implementing
```
WRONG: Start coding based on memory of plan
RIGHT: Read plan file, extract task details, then implement
```
---
## Subagent Communication
### Implementation Subagent Prompt
```markdown
## Task: [Task Name]
**Context**: Executing plan for [Feature Name]
**Files to modify**:
- [File paths from plan]
**Steps**:
[Exact steps from plan]
**Requirements**:
- Follow TDD: test first, then implement
- Commit after completion
- Return summary of what was done
**Output expected**:
- Files modified
- Tests added
- Commit hash
- Any issues encountered
```
### Stack-Specific Task Prompt Examples
**Python/FastAPI:**
```markdown
## Task: Implement GET /api/users endpoint
**Context**: FastAPI + SQLAlchemy async + Pydantic v2
**Files**: src/api/users.py, tests/test_users.py
**Pattern**: Follow src/api/health.py for router setup
**Steps**:
1. Write test: GET /api/users returns 200 with list
2. Verify test fails (404 — route doesn't exist)
3. Implement: APIRouter, async def, Depends(get_db)
4. Verify test passes
5. Add edge case: GET /api/users/999 returns 404 ProblemDetails
**Verify**: pytest tests/test_users.py -v (all green)
```
**TypeScript/NestJS:**
```markdown
## Task: Implement UsersController with CRUD
**Context**: NestJS + Prisma + class-validator DTOs
**Files**: src/users/users.controller.ts, src/users/users.controller.spec.ts
**Pattern**: Follow src/health/ module structure
**Steps**:
1. Write spec: POST /users returns 201 with user
2. Verify spec fails (404 — no route)
3. Implement: Controller, Service, CreateUserDto with @IsEmail()
4. Verify spec passes
5. Add: GET /users/:id returns 404 for missing user
**Verify**: npm test -- --testPathPattern=users.controller (all green)
```
**React/Next.js:**
```markdown
## Task: Build UserTable with sorting and pagination
**Context**: Next.js App Router + TanStack Table + shadcn/ui
**Files**: src/components/user-table.tsx, src/components/user-table.test.tsx
**Pattern**: Follow src/components/data-table.tsx for column defs
**Steps**:
1. Write test: renders table with user data
2. Verify test fails (component doesn't exist)
3. Implement: columns, DataTable wrapper, sort handlers
4. Verify test passes
5. Add test: clicking column header sorts data
**Verify**: npx vitest run src/components/user-table.test.tsx (all green)
```
### Stack-Specific Verification Commands
| Stack | Test Command | Full Verify |
|-------|-------------|-------------|
| Python/FastAPI | `pytest tests/test_<module>.py -v` | `pytest -v && ruff check . && mypy src/` |
| TypeScript/NestJS | `npm test -- --testPathPattern=<module>` | `npm test && npm run lint && npm run build` |
| Next.js | `npx vitest run <file>` | `npm test && next lint && next build` |
### Code Review Subagent Prompt
```markdown
## Code Review Request
**Scope**: Changes from Task [N]
**Files changed**:
- [List of files]
**Review against**:
- Plan requirements for this task
- Code quality standards
- Security best practices
- Test coverage
**Return**:
- Critical issues (must fix)
- Important issues (should fix)
- Minor issues (can defer)
- Approval status
```
---
## TodoWrite Integration
Maintain task status throughout:
```markdown
| Task | Status |
|------|--------|
| Task 1: Create model | completed |
| Task 2: Add validation | completed |
| Task 3: Create endpoint | in_progress |
| Task 4: Add tests | pending |
| Task 5: Documentation | pending |
```
Update status in real-time:
- `pending``in_progress` when starting
- `in_progress``completed` when reviewed and approved
---
## Error Handling
### Task Fails
```markdown
1. Capture error details
2. Attempt fix (max 2 retries)
3. If still failing, pause execution
4. Report to user with:
- Which task failed
- Error details
- Suggested resolution
5. Wait for user decision
```
### Review Finds Major Issues
```markdown
1. List all Critical/Important issues
2. Dispatch fix subagent for each
3. Re-run code review
4. If issues persist after 2 cycles:
- Pause execution
- Report to user
- May need plan revision
```
---
## Completion Checklist
Before declaring plan execution complete:
- [ ] All tasks marked completed
- [ ] All code reviews passed
- [ ] Full test suite passes
- [ ] No Critical issues outstanding
- [ ] No Important issues outstanding
- [ ] Final comprehensive review done
- [ ] Ready for `finishing-a-development-branch`
---
## Related Skills
- `writing-plans` -- Use to create the plan before executing it
- `dispatching-parallel-agents` -- For coordinating multiple independent agents when plan tasks allow parallelism
- `verification-before-completion` -- Ensures each task and the final result are properly verified before claiming completion
@@ -1,110 +0,0 @@
# Plan Execution Checklist
Step-by-step checklist for executing implementation plans. Follow this sequence for each plan to ensure consistent, high-quality delivery.
---
## Phase 1: Pre-Execution
Complete all items before writing any code.
- [ ] **Read the full plan end-to-end** — Understand the complete scope before starting any task. Do not start task 1 without knowing what task N requires.
- [ ] **Identify the dependency graph** — Which tasks depend on others? Which can run in parallel? Mark the critical path.
- [ ] **Check external dependencies** — API keys available? Services running? Permissions granted? Third-party accounts set up?
- [ ] **Verify the environment**
- [ ] Correct branch checked out (or worktree created)
- [ ] Dependencies installed and up to date
- [ ] Existing tests pass before any changes
- [ ] Build succeeds from clean state
- [ ] **Clarify ambiguities** — If any task description is unclear, resolve it now. Do not guess during implementation.
- [ ] **Estimate total effort** — Does the sum of task estimates feel realistic given what you know? Flag concerns early.
---
## Phase 2: Per-Task Execution
Repeat for each task in plan order (respecting dependencies).
### Before Starting the Task
- [ ] **Read the task spec completely** — Including files to modify, changes, tests, and verification steps
- [ ] **Confirm dependencies are met** — All prerequisite tasks marked complete and verified
- [ ] **Check current state** — Run tests, confirm the codebase is in a good state before making changes
### During the Task
- [ ] **Write tests first** — If the plan includes tests for this task, write them before the implementation. They should fail initially.
- [ ] **Implement the changes** — Follow the spec. If you need to deviate, document why.
- [ ] **Run the task's specific tests** — All tests for this task must pass
- [ ] **Run the full test suite** — Ensure no regressions from your changes
- [ ] **Complete the task's verification steps** — Every verification item in the plan must be checked
### After Completing the Task
- [ ] **Mark the task complete** — Update the plan document
- [ ] **Check for side effects** — Did anything unexpected break? Are there warnings?
- [ ] **Commit the work** — One commit per task with a clear message referencing the plan
```
feat(scope): task description
Plan: [plan-name], Task N
```
- [ ] **Update the plan if needed** — If you discovered something that affects later tasks, note it now
---
## Phase 3: Post-Execution
Complete after all tasks are done.
### Verification
- [ ] **Run the full test suite** — All tests pass, not just the ones you added
```bash
# Python
pytest -v --cov=src
# TypeScript
pnpm test
```
- [ ] **Run the build** — Confirm the project builds without errors
```bash
pnpm build # or equivalent
```
- [ ] **Run linters and type checks** — No new warnings or errors
- [ ] **Manual verification** — Walk through the acceptance criteria in the plan's Verification Plan section
- [ ] **Check for leftover artifacts**
- [ ] No TODO comments left unresolved
- [ ] No commented-out code
- [ ] No debug logging left in place
- [ ] No temporary files committed
### Review
- [ ] **Self-review the diff** — Read your own changes as if reviewing someone else's PR
```bash
git diff main...HEAD
```
- [ ] **Check test quality** — Do tests verify behavior, not implementation? Are edge cases covered?
- [ ] **Check documentation** — If the plan required doc updates, are they done?
- [ ] **Verify acceptance criteria** — Every criterion in the plan marked as met
### Completion
- [ ] **Update plan status** — Mark as "Complete"
- [ ] **Summarize deviations** — Document any changes from the original plan and why
- [ ] **Create PR or merge** — Follow the project's git workflow
- [ ] **Clean up** — Remove worktree if used, close related issues
---
## Quick Reference: Common Failure Points
| Failure | Prevention |
|---------|-----------|
| Skipping plan review, then discovering blockers mid-task | Always complete Phase 1 fully |
| Tests pass in isolation but fail together | Run full suite after every task |
| Deviation from plan without updating it | Document changes as you make them |
| "It works on my machine" | Verify in clean environment |
| Forgetting to commit per-task | Commit immediately after verification |
| Side effects in later tasks | Check for regressions after each task |
-137
View File
@@ -1,137 +0,0 @@
---
name: feature-workflow
argument-hint: "[feature description or issue]"
user-invocable: true
description: >
Use when implementing a complete feature end-to-end — from requirements analysis through planning, implementation, testing, and review. Trigger for keywords like "feature", "implement", "build", "add functionality", "end-to-end", or any task that spans planning through delivery. Also activate when the user provides a feature description, issue reference, or requirement spec that needs a structured development workflow.
---
# Feature Workflow
## When to Use
- Implementing a complete feature from requirements to delivery
- When given a feature description, issue number, or requirement spec
- Multi-phase work that needs planning, implementation, testing, and review
- Any task that benefits from a structured development workflow
## When NOT to Use
- Simple bug fixes — use `systematic-debugging`
- Pure refactoring — use `refactoring`
- Writing tests for existing code — use `testing`
- Already have a plan to execute — use `executing-plans`
---
## Workflow Phases
### Phase 1: Understanding
1. Parse the feature request thoroughly
2. Identify acceptance criteria
3. List assumptions that need validation
4. Clarify ambiguous requirements with the user
### Phase 2: Planning
1. Explore codebase for related implementations and patterns
2. Identify integration points and dependencies
3. Decompose into atomic, verifiable tasks
4. Order tasks by dependencies
5. Track all tasks with TodoWrite
6. (Optional, recommended for non-trivial features) Run `autoplan` on the resulting plan to pressure-test strategy, architecture, design, and DX before Phase 4 (Implementation)
### Phase 3: Research (if needed)
If the feature involves unfamiliar technology:
1. Research best practices and patterns
2. Find examples in the codebase or documentation
3. Identify potential pitfalls
### Phase 4: Implementation
For each task:
1. Write failing test first (TDD)
2. Implement minimally to pass the test
3. Refactor if needed
4. Mark task complete immediately
### Phase 5: Testing
1. Run full test suite — no regressions
2. Verify coverage — should not decrease
3. Test edge cases and error scenarios
```bash
# Python
pytest -v --cov=src
# TypeScript
pnpm test
```
### Phase 6: Review
Self-review checklist:
- [ ] Code follows project conventions
- [ ] No security vulnerabilities
- [ ] Error handling is complete
- [ ] Tests are passing
- [ ] No debug statements or TODOs
### Phase 7: Completion
1. Verify all tasks complete
2. Stage appropriate files
3. Generate commit message
4. Create PR if requested
---
## Output Format
```markdown
## Feature Implementation Complete
### Feature
[Feature description]
### Changes Made
- `path/to/file.ts` — [What was added/modified]
- `path/to/file.test.ts` — [Tests added]
### Tests
- [x] Unit tests passing
- [x] Integration tests passing
- [x] Coverage: XX%
### Ready for Review
```
---
## Best Practices
1. **Break down aggressively** — smaller tasks are easier to verify and commit.
2. **Test first** — every task starts with a failing test.
3. **Commit incrementally** — commit after each task, not at the end.
4. **Clarify before building** — ambiguous requirements lead to rework.
5. **Check existing patterns** — follow conventions already in the codebase.
## Common Pitfalls
1. **Starting without understanding** — jumping to code before clarifying requirements.
2. **Monolithic implementation** — implementing everything in one pass without incremental verification.
3. **Ignoring existing patterns** — building something inconsistent with the rest of the codebase.
4. **Skipping tests** — "I'll add tests later" means no tests.
---
## Related Skills
- `brainstorming` — Use before this skill when requirements are unclear or need exploration
- `writing-plans` — Use for detailed task breakdown when the feature is complex
- `test-driven-development` — The TDD discipline applied during Phase 4
- `git-workflows` — Committing and shipping the completed feature
- `requesting-code-review` — Getting feedback before merging
@@ -1,338 +0,0 @@
---
name: finishing-a-development-branch
description: >
Use when implementation is complete and all tests pass, when ready to merge a feature branch, create a PR, or clean up after development. Use whenever you hear "ship it," "ready to merge," "branch is done," or "create a PR." Activate at the end of any feature, bugfix, or chore branch lifecycle to ensure proper verification, option presentation, and worktree cleanup.
---
# Finishing a Development Branch
## When to Use
- After implementing a feature
- After all tests pass
- Ready to merge or create PR
- Cleaning up after development
## When NOT to Use
- Work is still in progress and not all planned changes have been implemented
- Tests are failing and need to be fixed before the branch can be finalized
- Uncommitted changes remain that have not been staged or committed yet
---
## The 5-Step Workflow
### Step 1: Verify Tests
Run the project's test suite:
```bash
npm test
# or
pytest
# or
go test ./...
```
**Decision point**:
- Tests PASS → Continue to Step 2
- Tests FAIL → STOP. Cannot proceed with failing tests.
```markdown
⚠️ STOP: Tests failing
Cannot proceed with merge/PR until tests pass.
Fix failing tests first, then restart this workflow.
```
### Step 2: Determine Base Branch
Identify which branch this feature branch originated from:
```bash
# Check tracking branch
git branch -vv
# Or check common bases
git merge-base main feature-branch
git merge-base develop feature-branch
```
Common base branches:
- `main` or `master` - Production
- `develop` - Development
- `release/*` - Release branches
### Step 3: Present Options
Offer exactly four choices:
```markdown
## Branch Completion Options
Your feature branch `feature/email-verification` is ready.
All tests pass (42/42).
Choose how to proceed:
1. **Merge locally** - Merge into [base] on your machine
2. **Create Pull Request** - Push and open PR for review
3. **Keep as-is** - Leave branch for later work
4. **Discard** - Delete this branch and all changes
Enter your choice (1-4):
```
### Step 4: Execute Choice
#### Option 1: Merge Locally
```bash
# Switch to base branch
git checkout main
# Pull latest
git pull origin main
# Merge feature branch
git merge feature/email-verification
# Verify tests still pass
npm test
# Delete feature branch
git branch -d feature/email-verification
```
#### Option 2: Create Pull Request
```bash
# Push branch to remote
git push -u origin feature/email-verification
# Create PR (using gh CLI)
gh pr create \
--title "Add email verification" \
--body "## Summary
- Implements email verification flow
- Adds verification token generation
- Includes tests for all scenarios
## Test Plan
- [x] Unit tests pass
- [x] Integration tests pass
- [x] Manual testing complete"
```
#### Option 3: Keep As-Is
```markdown
Branch preserved: feature/email-verification
Note: Remember to return to this branch later.
Current state: All tests passing, ready for merge.
```
#### Option 4: Discard
```markdown
⚠️ WARNING: This will delete all work on this branch.
Type "discard" to confirm: _______
```
If confirmed:
```bash
# Switch away from branch
git checkout main
# Force delete branch
git branch -D feature/email-verification
# If pushed to remote, delete there too
git push origin --delete feature/email-verification
```
### Step 5: Cleanup Worktree (if applicable)
For options 1, 2, and 4, cleanup the worktree environment:
```bash
# Remove worktree
git worktree remove ../feature-email-verification
# Or if worktree is in special location
git worktree remove /path/to/worktree
```
For option 3 (keep), preserve the worktree.
---
## Decision Flow
```
┌─────────────────────────┐
│ Tests Passing? │
└───────────┬─────────────┘
┌────┴────┐
│ NO │──────► STOP: Fix tests first
└─────────┘
YES
┌─────────────────────────┐
│ Present 4 Options │
└───────────┬─────────────┘
┌───────┼───────┬───────┐
│ │ │ │
▼ ▼ ▼ ▼
Merge PR Keep Discard
│ │ │ │
▼ ▼ │ ▼
Cleanup Cleanup │ Confirm
│ │ │ │
▼ ▼ │ ▼
Done Done Done Cleanup
Done
```
---
## Pull Request Template
When choosing Option 2:
```markdown
## Summary
[Brief description of changes]
## Changes
- [Change 1]
- [Change 2]
- [Change 3]
## Test Plan
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manual testing scenarios:
- [ ] Scenario 1
- [ ] Scenario 2
## Screenshots (if applicable)
[Add screenshots here]
## Related Issues
Closes #[issue number]
```
---
## Verification Before Each Option
### Before Merge
```markdown
- [ ] Tests pass on feature branch
- [ ] Base branch is up to date
- [ ] No merge conflicts
- [ ] Tests pass after merge
```
### Before PR
```markdown
- [ ] Tests pass
- [ ] Branch pushed to remote
- [ ] PR description complete
- [ ] Reviewers assigned (if required)
```
### Before Discard
```markdown
- [ ] Confirmed with user (typed "discard")
- [ ] No valuable uncommitted changes
- [ ] Branch deleted locally
- [ ] Branch deleted from remote (if pushed)
```
---
## Stack-Specific Pre-Merge Checklist
### Python/FastAPI
```bash
pytest -v --cov=src # Full test suite
ruff check . && ruff format --check . # Lint + format
mypy src/ --strict # Type check
pip-audit # Security audit
alembic upgrade head && alembic check # Verify migrations
```
### TypeScript/NestJS
```bash
npm test # Full test suite
npm run lint # Lint
npm run build # Build (catches type errors)
npm audit --production # Security audit
npx prisma migrate status # Verify migrations
```
### Next.js
```bash
npm test # Tests
next lint # Lint
next build # Build (catches SSR/RSC issues)
```
### Stack-Specific PR Description Extras
**Python/FastAPI PRs** — include:
- Migration included? (alembic revision)
- New dependencies? (requirements.txt changes)
- Async patterns verified? (no blocking calls in async)
**NestJS PRs** — include:
- New modules registered in AppModule?
- DTOs have class-validator decorators?
- Prisma schema changed? (migration included)
**Next.js PRs** — include:
- Server vs Client components correct?
- Bundle size impact?
- `'use client'` directives where needed?
---
## Core Principle
**"Verify tests → Present options → Execute choice → Clean up"**
Never:
- Merge with failing tests
- Delete work without confirmation
- Skip the verification step
- Leave orphaned worktrees
---
## Related Skills
- `requesting-code-review` - Use before finishing the branch to get review feedback, especially for Option 2 (Create PR)
- `verification-before-completion` - Run verification checks before claiming the branch is ready to finish
- `executing-plans` - If the branch was created from an execution plan, return to the plan to mark tasks complete
@@ -1,197 +0,0 @@
# Branch Completion Checklist
Checklist and reference for completing a development branch and integrating work.
## Pre-Merge Checklist
### Code Quality
- [ ] All tests pass on the branch (`pytest -v` / `pnpm test`)
- [ ] No linting errors (`ruff check` / `eslint .`)
- [ ] Type checking passes (`mypy` / `tsc --noEmit`)
- [ ] No TODO/FIXME without a ticket reference
- [ ] No debugging artifacts (print statements, console.log, commented-out code)
- [ ] No hardcoded secrets, API keys, or credentials
### Review
- [ ] Code review requested and approved
- [ ] All review comments addressed (fixed, deferred with ticket, or discussed)
- [ ] No unresolved conversations in the PR
### Testing
- [ ] Unit tests added for new behavior
- [ ] Integration tests added for new endpoints/services
- [ ] Edge cases covered (empty input, max size, unauthorized, concurrent)
- [ ] Test coverage meets minimum threshold (80% overall, 95% critical paths)
- [ ] Manual testing completed for UI/UX changes
### Documentation
- [ ] Public API documentation updated (docstrings, OpenAPI spec)
- [ ] README updated (if setup steps changed)
- [ ] CHANGELOG entry added (if applicable)
- [ ] Migration guide written (if breaking changes)
- [ ] Architecture/design docs updated (if structural changes)
### Branch Hygiene
- [ ] Branch is up to date with main (rebase or merge)
- [ ] No merge conflicts
- [ ] Commit history is clean and meaningful
- [ ] Branch name follows convention (`feature/`, `fix/`, `hotfix/`, `chore/`)
### CI/CD
- [ ] CI pipeline is green (all checks pass)
- [ ] Build succeeds
- [ ] No new warnings introduced
- [ ] Performance benchmarks pass (if applicable)
- [ ] Security scan passes (if applicable)
### Database/Infrastructure
- [ ] Migrations are reversible
- [ ] Migrations have been tested (up and down)
- [ ] No destructive schema changes without a migration plan
- [ ] Environment variables documented (if new ones added)
- [ ] Feature flags configured (if using progressive rollout)
## Merge Strategy Decision
### Merge Commit (`git merge --no-ff`)
**When to use:**
- Feature branch with multiple meaningful commits
- You want to preserve the full development history
- Team convention requires merge commits
**Result:** Preserves all commits plus a merge commit. Creates a clear merge point in history.
```bash
git checkout main
git merge --no-ff feature/TICKET-123-description
```
### Squash Merge (`git merge --squash`)
**When to use:**
- Feature branch has messy/WIP commits
- The feature is a single logical unit
- You want a clean linear history on main
**Result:** All commits become one commit on main.
```bash
git checkout main
git merge --squash feature/TICKET-123-description
git commit -m "feat(orders): add bulk order cancellation (#123)"
```
### Rebase (`git rebase main` + fast-forward merge)
**When to use:**
- Small number of clean, atomic commits
- You want linear history without merge commits
- Each commit builds on the previous logically
**Result:** Commits are replayed on top of main. No merge commit.
```bash
git checkout feature/TICKET-123-description
git rebase main
git checkout main
git merge --ff-only feature/TICKET-123-description
```
### Decision Matrix
| Situation | Strategy |
|---|---|
| Feature with messy WIP commits | Squash |
| Feature with clean, meaningful commits | Merge commit or rebase |
| Single commit fix | Fast-forward (rebase) |
| Long-lived branch, multiple contributors | Merge commit |
| Team prefers linear history | Squash or rebase |
| Need to bisect individual changes later | Merge commit or rebase (not squash) |
## Update Branch Before Merging
### Option A: Rebase onto main
```bash
git checkout feature/TICKET-123-description
git fetch origin
git rebase origin/main
# Resolve conflicts if any
git push --force-with-lease # update remote branch
```
**Pros:** Clean linear history.
**Cons:** Rewrites history (don't use if others are working on the branch).
### Option B: Merge main into branch
```bash
git checkout feature/TICKET-123-description
git fetch origin
git merge origin/main
# Resolve conflicts if any
git push
```
**Pros:** Safe, preserves history, works with shared branches.
**Cons:** Adds merge commits to the feature branch.
## Post-Merge Steps
### Immediate
- [ ] Delete the feature branch (local and remote)
```bash
git branch -d feature/TICKET-123-description
git push origin --delete feature/TICKET-123-description
```
- [ ] Verify main branch builds and tests pass
- [ ] Verify deployment to staging/preview environment succeeds
### Follow-Up
- [ ] Close the associated ticket/issue
- [ ] Notify the team (if significant change)
- [ ] Monitor logs and error rates after deployment
- [ ] Verify the feature works in the deployed environment
- [ ] Update project board/tracker
### If Something Goes Wrong
| Problem | Action |
|---|---|
| Tests fail on main after merge | Revert the merge commit immediately, investigate on a new branch |
| Deployment fails | Roll back deployment, investigate, do not push fixes to main under pressure |
| Bug found in production | Create a hotfix branch from main, fix, test, deploy |
| Need to undo a squash merge | `git revert <squash-commit-sha>` |
| Need to undo a merge commit | `git revert -m 1 <merge-commit-sha>` |
## Quick Reference: Common Commands
```bash
# Check if branch is up to date with main
git fetch origin && git log HEAD..origin/main --oneline
# See what will be merged
git log main..HEAD --oneline
# See the full diff against main
git diff main...HEAD
# Check CI status (GitHub CLI)
gh pr checks
# Merge via GitHub CLI
gh pr merge --squash # or --merge, --rebase
# Delete branch after merge
gh pr merge --squash --delete-branch
```
-119
View File
@@ -1,119 +0,0 @@
---
name: git-workflows
argument-hint: "[commit/ship/pr/changelog]"
description: >
Use when committing code, creating pull requests, shipping changes, or generating changelogs. Trigger for keywords like "commit", "push", "PR", "pull request", "ship", "merge", "changelog", "release notes", "conventional commits", or any git workflow beyond basic status/diff. Also activate when preparing code for review or automating the commit-to-PR pipeline.
---
# Git Workflows
## When to Use
- Creating commits with conventional commit messages
- Shipping code (commit + review + push + PR)
- Creating pull requests with proper descriptions
- Generating changelogs from commit history
- Preparing code for review or merge
## When NOT to Use
- Basic git operations (status, diff, log) — just run them directly
- Branch management strategy — use `using-git-worktrees`
- Code review content — use `requesting-code-review`
---
## Quick Reference
| Workflow | Reference | Key content |
|----------|-----------|-------------|
| Committing | `references/committing.md` | Conventional commits, message format, pre-commit checks |
| Shipping | `references/shipping.md` | Full ship workflow: review → test → commit → push → PR |
| Pull Requests | `references/pull-requests.md` | PR creation, description templates, gh CLI patterns |
| Changelogs | `references/changelogs.md` | Changelog generation from commits, Keep a Changelog format |
---
## Conventional Commit Format
```
type(scope): subject
body (optional)
footer (optional)
```
| Type | When |
|------|------|
| `feat` | New feature |
| `fix` | Bug fix |
| `docs` | Documentation only |
| `refactor` | Code restructuring, no behavior change |
| `test` | Adding or fixing tests |
| `chore` | Maintenance, dependencies, CI |
| `style` | Formatting, whitespace |
### Subject Line Rules
- Max 50 characters, imperative mood ("Add" not "Added"), no trailing period
---
## Ship Workflow
```
1. Pre-ship checks (secrets, debug statements)
2. Self-review (code quality, style)
3. Run tests (full suite, coverage check)
4. Create commit (conventional format)
5. Push to remote
6. Create PR (summary, test plan, checklist)
```
---
## PR Description Template
```markdown
## Summary
- [Change 1]
- [Change 2]
## Test Plan
- [ ] Unit tests pass
- [ ] Manual testing done
## Checklist
- [ ] No breaking changes
- [ ] Tests added/updated
- [ ] Documentation updated
```
---
## Best Practices
1. **Atomic commits** — one logical change per commit, not one file per commit.
2. **Explain why, not what** — the diff shows what changed; the message explains why.
3. **Stage specific files** — prefer `git add <file>` over `git add -A` to avoid committing secrets or unrelated changes.
4. **Reference issues** — include `Closes #123` or `Fixes #456` in footers.
5. **Pre-commit checks** — verify no secrets, debug statements, or commented-out code before committing.
6. **PR descriptions matter** — reviewers read the description before the diff; make it count.
## Common Pitfalls
1. **Committing secrets**`.env` files, API keys, tokens in staged changes.
2. **Vague commit messages** — "fix stuff", "updates", "WIP" provide no context.
3. **Giant PRs** — 500+ line PRs get rubber-stamped; split into focused chunks.
4. **Amending published commits** — rewriting history others have pulled causes conflicts.
5. **Skipping pre-commit hooks**`--no-verify` hides real issues.
6. **Force pushing to shared branches** — can destroy teammates' work.
---
## Related Skills
- `requesting-code-review` — Preparing changes for reviewer feedback
- `finishing-a-development-branch` — End-of-branch workflow decisions
- `using-git-worktrees` — Isolated branch management
@@ -1,59 +0,0 @@
# Changelog Generation
## Keep a Changelog Format
Based on [keepachangelog.com](https://keepachangelog.com):
```markdown
## [1.2.0] - 2026-04-19
### Added
- Password reset functionality (#123)
- Email verification for new accounts
### Changed
- Improved error messages for validation failures
- Updated dependencies to latest versions
### Fixed
- Race condition in session handling (#456)
- Incorrect timezone in date displays
### Removed
- Legacy v1 API endpoints (deprecated since 1.0)
```
## Generating from Commits
```bash
# Get commits since last tag
git log --oneline $(git describe --tags --abbrev=0)..HEAD
# Group by type
git log --oneline --grep="^feat" $(git describe --tags --abbrev=0)..HEAD
git log --oneline --grep="^fix" $(git describe --tags --abbrev=0)..HEAD
```
## Category Mapping
| Commit Type | Changelog Category |
|-------------|-------------------|
| `feat` | Added |
| `fix` | Fixed |
| `refactor`, `perf` | Changed |
| removal commits | Removed |
| `docs` | Usually omitted |
| `chore`, `test`, `style` | Usually omitted |
## User-Friendly Descriptions
Transform commit messages into user-facing descriptions:
```
BAD: feat(auth): add pwd reset (#123)
GOOD: Password reset functionality — users can now reset their password via email (#123)
```
- Write for users, not developers
- Include PR/issue references
- Explain the user-visible impact
@@ -1,90 +0,0 @@
# Committing Patterns
## Pre-Commit Checklist
Before staging:
- [ ] No secrets (`.env`, API keys, tokens)
- [ ] No debug statements (`console.log`, `print()`, `debugger`)
- [ ] No commented-out code blocks
- [ ] Code is formatted (prettier/ruff)
## Conventional Commit Format
```
type(scope): subject
body (optional - explain why, not what)
footer (optional - references, breaking changes)
```
### Types
| Type | When | Example |
|------|------|---------|
| `feat` | New feature | `feat(auth): add OAuth2 login` |
| `fix` | Bug fix | `fix(api): handle null user in profile` |
| `docs` | Documentation | `docs(readme): update install steps` |
| `refactor` | Restructure, no behavior change | `refactor(db): extract query builders` |
| `test` | Add/fix tests | `test(auth): add login edge cases` |
| `chore` | Maintenance | `chore(deps): update React to 19` |
| `style` | Formatting | `style: apply prettier` |
| `perf` | Performance | `perf(query): add index on user_id` |
### Subject Line Rules
- Max 50 characters
- Imperative mood: "Add" not "Added" or "Adds"
- No trailing period
- Capitalize first letter
### Body Rules
- Wrap at 72 characters
- Explain **why**, not what (the diff shows what)
- Use bullet points for multiple changes
### Footer Patterns
```
Closes #123
Fixes #456
BREAKING CHANGE: removed legacy auth endpoint
Co-Authored-By: Claude <noreply@anthropic.com>
```
## Staging Best Practices
```bash
# Prefer specific files over blanket add
git add src/auth/login.ts src/auth/login.test.ts
# Review what you're committing
git diff --staged
# Never commit these
# .env, credentials.json, *.pem, *.key
```
## Commit Command Pattern
```bash
git commit -m "$(cat <<'EOF'
feat(auth): add password reset flow
- Add reset token generation with 1h expiry
- Implement email sending via SendGrid
- Add rate limiting (3 requests/hour)
Closes #123
Co-Authored-By: Claude <noreply@anthropic.com>
EOF
)"
```
## Amending vs New Commit
- **Amend**: Only for unpushed commits, only when fixing the same logical change
- **New commit**: Always for pushed commits, or when adding distinct changes
- **Never amend after pre-commit hook failure** — the commit didn't happen, so amend would modify the previous commit
@@ -1,77 +0,0 @@
# Pull Request Patterns
## Pre-PR Checklist
- [ ] All tests passing
- [ ] Code self-reviewed
- [ ] No merge conflicts with base branch
- [ ] Branch pushed to remote
- [ ] Commit history is clean (no "WIP" or "fix typo" noise)
## Creating a PR
```bash
# Check current state
git status
git diff main...HEAD
git log --oneline main..HEAD
# Push if needed
git push -u origin $(git branch --show-current)
# Create PR
gh pr create --title "feat(scope): description" --body "$(cat <<'EOF'
## Summary
- [Change 1]
- [Change 2]
## Test Plan
- [ ] Unit tests added
- [ ] Manual testing done
- [ ] Edge cases covered
## Checklist
- [ ] No breaking changes
- [ ] Tests added/updated
- [ ] Documentation updated
🤖 Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
```
## PR Title Format
Follow conventional commits: `type(scope): description`
- Max 70 characters
- Use description/body for details, not the title
## PR Size Guidelines
| Size | Lines Changed | Review Time |
|------|--------------|-------------|
| Small | < 100 | Quick review |
| Medium | 100-300 | Thorough review |
| Large | 300-500 | Split if possible |
| Too Large | > 500 | Must split |
## Viewing PR Comments
```bash
# View PR comments
gh api repos/owner/repo/pulls/123/comments
# View PR review comments
gh pr view 123 --comments
```
## Draft PRs
```bash
# Create as draft for early feedback
gh pr create --draft --title "WIP: feature" --body "Early draft for feedback"
# Mark ready when done
gh pr ready 123
```
-101
View File
@@ -1,101 +0,0 @@
# Ship Workflow
Complete workflow: review → test → commit → push → PR.
## Phase 1: Pre-Ship Checks
```bash
git status
git diff --staged
```
Verify:
- [ ] No secrets in staged files
- [ ] No debug statements
- [ ] No commented-out code
- [ ] No unintended files
## Phase 2: Self-Review
- Check code quality and style compliance
- Verify security (no hardcoded secrets, proper input validation)
- Address critical issues before proceeding
## Phase 3: Run Tests
```bash
# Python
pytest -v
# TypeScript
pnpm test
```
- All tests must pass
- Coverage should not decrease
- No new warnings
## Phase 4: Create Commit
```bash
# Stage specific files
git add src/feature.ts src/feature.test.ts
# Commit with conventional format
git commit -m "$(cat <<'EOF'
feat(scope): description
- Change 1
- Change 2
Co-Authored-By: Claude <noreply@anthropic.com>
EOF
)"
```
## Phase 5: Push and Create PR
```bash
# Push with upstream tracking
git push -u origin feature/my-feature
# Create PR
gh pr create --title "feat(scope): description" --body "$(cat <<'EOF'
## Summary
- Change 1
- Change 2
## Test Plan
- [ ] Unit tests pass
- [ ] Manual testing done
Co-Authored-By: Claude <noreply@anthropic.com>
EOF
)"
```
## Quick Ship Mode
For small, low-risk changes:
1. Skip detailed self-review
2. Auto-generate commit message from diff
3. Minimal PR description
## Ship Report Format
```markdown
## Ship Complete
### Commit
**Hash**: `abc1234`
**Message**: `feat(auth): add password reset`
### Checks
- [x] Tests passing (42 tests)
- [x] Coverage: 85% (+3%)
- [x] No security issues
### Pull Request
**URL**: https://github.com/org/repo/pull/123
**Status**: Ready for review
```
+205
View File
@@ -0,0 +1,205 @@
---
name: incremental-shipping
user-invocable: true
description: >
Use when implementing a non-trivial feature, migration, or refactor that would
otherwise be a single large change. Activate for keywords like "feature flag",
"incremental", "vertical slice", "migration", "rollout", "behind a flag", "ship
small". Enforces vertical slicing, feature-flagged rollout, and refactor-with-
evidence (behavior-preserving changes proved by test/perf deltas). Always ship
the smallest reversible change -- never bundle unrelated improvements.
---
# Incremental Shipping
## Overview
A workflow for landing large changes as small, reversible increments. The skill
exists because the most common shipping failure isn't a missing test or a bad
deploy — it's a 1500-line PR that bundles a feature, a refactor, and a config
change, takes three days to review, and lands with a regression nobody isolated.
Incremental shipping splits that into thin vertical slices behind feature flags,
plus a refactor-with-evidence section for behavior-preserving changes that need
their own discipline (test deltas, perf measurements). Used after `write-plan`
and `test-first`, before `code-review-loop`.
## When to Use
- A feature plan has 5+ tasks and would otherwise ship as one PR
- A migration must run alongside existing code for a transition period
- A refactor changes structure but should preserve behavior; you need to prove it
- A change is risky enough that you want a kill switch in production
## When NOT to Use
- The change is single-file and trivially reversible (`git revert` is enough)
- The change has no observable surface (internal-only refactor of a single
function called by tests)
- An emergency hotfix where the cost of incrementality exceeds the cost of risk
## Process
### Step 1: Identify the vertical slice
**Goal:** Define the smallest change that delivers user-observable value (or
preserves behavior, for refactors) and can ship on its own.
**Inputs:** A task or set of tasks from your plan.
**Actions:**
1. Ask: what's the smallest version of this change that a user could see, an
API consumer could call, or a test could exercise? Not "the smallest piece of
code" — the smallest *value-delivering* slice.
2. List what would be excluded from this slice: features, edges, polish.
Excluded items become later slices.
3. The slice should be implementable in 1-3 PRs of <300 lines each.
**Output:** A slice definition: `Slice 1: <what's included>; out of slice:
<what's deferred>`.
### Step 2: Add the feature flag
**Goal:** A kill switch that lets the slice ship dark.
**Inputs:** The slice definition.
**Actions:**
1. Choose a flag name. Convention: `<feature>_enabled` for booleans,
`<feature>_rollout` for percentage rollouts.
2. Wire the flag to a config source (env var, feature-flag service, config file).
3. Default the flag to **off**. The slice ships off, gets verified in production
off, then turned on.
4. Write a comment at the flag's read site naming the deletion plan: `// Remove
this flag and the off branch after rollout completes — see ticket <link>`.
**Output:** Flag is committed (off-by-default), readable from production.
### Step 3: Implement the slice
**Goal:** Code that delivers the slice, gated by the flag.
**Inputs:** The slice definition + the flag.
**Actions:**
1. Implement following `test-first`. Each test runs both flag-on and flag-off
paths if behavior diverges.
2. Branch on the flag at one well-named location, not scattered. The off branch
reproduces existing behavior; the on branch implements the slice.
3. Avoid bundling: if you spot an unrelated cleanup (typo, lint, dead code),
write it down for a follow-up PR. Don't include it now.
**Output:** Slice implementation behind the flag, all tests pass.
### Step 4: Refactor with evidence (when applicable)
**Goal:** Structural changes that preserve behavior, proved by deltas.
**Inputs:** A refactor opportunity revealed during Step 3 OR a separate refactor
task in the plan.
**Actions:**
1. Before refactoring: run the test suite and capture the green output. This is
the **before-state**.
2. If perf-sensitive: run the relevant benchmark. Capture the number. (Bench tool
varies; the project's standard.)
3. Make the structural change. One change at a time — don't bundle multiple
refactors.
4. After refactoring: run the test suite. Confirm green. This is the **after-state**.
5. If perf-sensitive: re-run the benchmark. The delta must be within the project's
tolerance. If perf regresses, revert and rethink.
6. Paste before/after test output and (if applicable) perf numbers in the PR.
"Refactor with evidence" means the evidence is in the PR, not in your head.
**Output:** Refactored code + before/after evidence in the PR.
### Step 5: Ship the slice
**Goal:** Land the slice in production with the flag off, then turn it on.
**Inputs:** Slice implementation + tests.
**Actions:**
1. Land the PR with the flag off. The merge is dark — production behavior is
unchanged because the off branch reproduces existing behavior.
2. Verify in production with flag off (regression check — did anything break that
we didn't gate properly?).
3. Turn the flag on. Start with internal users / a small percentage / a single
tenant.
4. Monitor: error rates, p95 latency, business metrics relevant to the slice.
If anomalies appear, flip the flag off — that's the kill switch's job.
5. Ramp up. 1% → 10% → 50% → 100% over hours or days, depending on risk.
**Output:** Slice fully rolled out OR rolled back via flag with a learning.
### Step 6: Plan the next slice or remove the flag
**Goal:** Close the loop on this slice.
**Inputs:** A 100% rollout that's been stable for the project's bake-time
(typically 1 release cycle).
**Actions:**
1. If more slices remain, return to Step 1 with the next slice.
2. If this was the last slice, delete the flag and the off branch. Open a
"delete flag" PR. The flag's lifetime should be measurable in days/weeks, not
months/years.
3. If the slice was reverted, write a one-paragraph learning: what assumption
was wrong, what evidence revealed it, what would have caught it earlier.
**Output:** Either a new slice in flight or a flag-removal PR or a learning
note.
## Rationalizations
| Excuse | Why it sounds reasonable | Why it's wrong | What to do instead |
|---|---|---|---|
| "Feature flags add complexity — let's just ship it." | Flags do add code paths and require maintenance. | "Just ship it" without a flag is fine for trivial changes; for the cases this skill applies to, the flag is the difference between a 30-second rollback and a 2-hour incident. The complexity of one well-placed flag is fixed and small; the complexity of fixing prod with no kill switch is unbounded. | Add the flag. The cost of one branch and one config read is the cheapest insurance you'll buy. Delete the flag after rollout (Step 6) so the complexity is temporary. |
| "I'll bundle this small cleanup with the feature — saves a PR." | Reducing PR count feels efficient. | The bundled cleanup is the change that breaks the PR review. The reviewer can't tell which lines are feature and which are cleanup; they ask questions about both, you answer for both, the review takes 2x as long. If the cleanup introduces a regression, bisect points to a commit that mixes feature and cleanup, doubling the debugging time. | Open a separate PR for the cleanup. The two PRs together review faster than one mixed PR. The reviewer can approve the cleanup with a glance and focus attention on the feature. |
| "Refactor first, then add the feature." | Clean code makes adding features easier. | Refactor-then-feature lands a refactor with no feature-driven verification. The "behavior-preserving" claim is unverified at the only test that matters — the feature exercising the refactored area. The refactor ships, looks fine, and the feature later reveals that the refactor changed behavior in a path tests didn't cover. | Make the change you need (the feature), then refactor afterward if needed, with the feature's tests as your safety net. Or: refactor and pass Step 4's evidence check (before/after deltas) explicitly. Don't refactor without evidence. |
| "I'll roll out to 100% directly — no point in 1%." | Gradual rollout has overhead and most slices are fine at 100%. | The cost of "no point in 1%" is a 100% rollout when the slice happens to have a regression. The 1% step would have surfaced the issue with 1% of the blast radius. Skipping the gradual ramp on the 95% of safe changes is fine; the discipline is needed for the 5% where it's not. | Default to a gradual ramp. If the change is small enough that 100% is genuinely safe, you can shorten the ramp (1% for 5 minutes, then 100%) but don't skip the verification step. |
| "I'll keep the off branch in code as a fallback even after rollout." | Fallback paths feel like safety. | Long-lived dual-path code becomes the ambiguity nobody understands six months later. The off branch is dead in production but alive in tests, in code review, in mental load. Every modification has to consider both paths. The "safety" you preserved is paid for forever. | Set a deletion deadline at the flag's introduction (Step 2 comment). When 100% rollout has baked, delete the flag and the off branch. If the change ever needs to be undone, `git revert` does the work — that's why version control exists. |
| "The refactor's behavior preservation is obvious — no need for the perf benchmark." | Many refactors really don't change perf. | "Obvious" without measurement is the line said before someone discovers the refactor changed an O(n) loop into an O(n²) one because of a hidden re-evaluation. Perf regressions from refactors are surprisingly common because the refactor optimized for readability, not for the compiler's hot path. | If the code is in a perf-sensitive area (request handler, hot loop, batch job), run the benchmark before and after. The delta is the receipt. If it's truly cold path, you can skip — but say so explicitly in the PR ("perf not measured; cold path"). |
## Evidence Requirements
| Checkpoint | Required artifact | What "no evidence" looks like |
|---|---|---|
| End of Step 1 | A slice definition naming what's included and what's deferred | "I'll start coding and see how big it gets." |
| End of Step 2 | A feature flag committed off-by-default with a deletion-plan comment | "We can add the flag later if needed." |
| End of Step 3 | Tests pass; flag-on and flag-off paths both exercised by tests | "It works behind the flag." |
| End of Step 4 (refactor) | Before/after test runner output + (if applicable) perf benchmark numbers | "Refactor preserves behavior — trust me." |
| End of Step 5 | Rollout sequence with monitoring observations at each ramp step | "It's at 100%, looks fine." |
| End of Step 6 | Either a flag-removal PR or a written learning from a revert | "We'll get to flag cleanup eventually." |
## Red Flags
- The slice is more than 500 lines of diff. It's not a slice; it's a feature.
Split it.
- The feature flag has no deletion plan. The flag will outlive the feature.
- Step 4's "after" benchmark is missing because "perf isn't a concern here." If
the code runs in production, perf is always a concern; document the cold-path
decision explicitly.
- The rollout went directly from 0% to 100%. Either the slice was trivial (was
the flag needed?) or the discipline was skipped.
- The PR contains both a feature gate and a "while I was here" cleanup. Split
before review.
- Multiple flags in flight for related slices and you can't remember which is
which. Slow down; the flag-cycle is supposed to be short.
## References
- Martin Fowler, *Refactoring* (Addison-Wesley, 2nd ed. 2018), Chapter 1
"Refactoring: A First Example" — the principle "make the change easy, then
make the easy change" applied to vertical slicing. Step 4's
refactor-with-evidence operationalizes Fowler's "test before, test after"
rule with explicit artifact capture.
- Pete Hodgson, "Feature Toggles" (martinfowler.com, 2017) — the categorization
of release toggles vs. permission toggles, plus the discipline that release
toggles should have a short lifetime. Step 6's deletion requirement
operationalizes that discipline.
+13 -37
View File
@@ -1,9 +1,9 @@
---
name: init
description: >
Interactive setup wizard for claudekit. Scaffolds rules, modes, hooks, and MCP
server configs into the user's project. Run /claudekit:init to configure.
Use when setting up a new project with claudekit or reconfiguring an existing one.
Interactive setup wizard for claudekit. Scaffolds rules, hooks, and MCP server
configs into the user's project. Run /claudekit:init to configure. Use when
setting up a new project with claudekit or reconfiguring an existing one.
user-invocable: true
argument-hint: "[--all] to skip prompts and install everything"
---
@@ -12,12 +12,13 @@ argument-hint: "[--all] to skip prompts and install everything"
Interactive setup wizard that scaffolds project-level configuration files into the user's `.claude/` directory.
Output styles ship with the plugin and are auto-discovered by Claude Code (no init step needed for them — see `output-styles/` at the plugin root).
## What It Generates
| Category | Files | Location |
|----------|-------|----------|
| Rules | api.md, frontend.md, migrations.md, security.md, testing.md | `.claude/rules/` |
| Modes | brainstorm.md, deep-research.md, default.md, implementation.md, orchestration.md, review.md, token-efficient.md | `.claude/modes/` |
| Hooks | auto-format, block-dangerous-commands, notify | `.claude/hooks/` + `settings.local.json` |
| MCP Servers | context7, sequential, playwright, memory, filesystem | `.mcp.json` |
@@ -43,25 +44,7 @@ If (b), list each rule with a one-line description and let user select:
For each selected rule, read the template from `${CLAUDE_PLUGIN_ROOT}/skills/init/templates/rules/<name>.md` and write it to `.claude/rules/<name>.md`.
### Step 2: Modes
"Which behavioral modes do you want to install?"
- a) All modes (brainstorm, deep-research, default, implementation, orchestration, review, token-efficient)
- b) Let me pick individually
- c) Skip modes
If (b), list each mode with a one-line description:
- **brainstorm.md** — Creative exploration, divergent thinking, pro/con comparisons
- **deep-research.md** — Thorough analysis with citations and evidence
- **default.md** — Balanced standard behavior
- **implementation.md** — Code-focused, minimal prose, maximum productivity
- **orchestration.md** — Multi-task coordination and parallel work
- **review.md** — Critical analysis, finding issues, security focus
- **token-efficient.md** — Compressed output for cost savings (30-70%)
For each selected mode, read the template from `${CLAUDE_PLUGIN_ROOT}/skills/init/templates/modes/<name>.md` and write it to `.claude/modes/<name>.md`.
### Step 3: Hooks
### Step 2: Hooks
"Which hooks do you want to install?"
- a) Auto-format (runs linter after Write/Edit)
@@ -97,7 +80,7 @@ Hook entry format for `settings.local.json`:
If `settings.local.json` already has a `hooks` key, merge new entries into the existing structure — do not overwrite.
### Step 4: MCP Servers
### Step 3: MCP Servers
"Which MCP servers do you want to configure?"
- a) Context7 (library documentation lookup)
@@ -115,7 +98,7 @@ For each selected server:
3. Select the correct config (`win32` or `posix` key)
4. Merge into the project's `.mcp.json` (create with `{"mcpServers": {}}` if it doesn't exist)
### Step 5: Summary
### Step 4: Summary
Print a summary table of everything installed:
@@ -123,14 +106,14 @@ Print a summary table of everything installed:
Claudekit setup complete!
Rules: 5 installed → .claude/rules/
Modes: 7 installed → .claude/modes/
Hooks: 3 installed → .claude/hooks/ + settings.local.json
MCP: 5 configured → .mcp.json
Next steps:
- Skills are available as /claudekit:<name> (13 user-invocable spine + 22 auto-trigger supporting = 35 total)
- Agents are available as claudekit:<name> (24 agents)
- Switch modes: "switch to brainstorm mode"
- Skills available as /claudekit:<name> (15 total)
- Agents available as claudekit:<name> (8 specialists)
- Output styles available via /config (5 shipped: Brainstorm, Deep Research,
Implementation, Review, Token Efficient)
```
---
@@ -139,7 +122,6 @@ Next steps:
If `$ARGUMENTS` contains `--all`, skip all prompts and install everything:
- All 5 rules
- All 7 modes
- All 3 hooks
- All 5 MCP servers
@@ -152,10 +134,4 @@ If `$ARGUMENTS` contains `--all`, skip all prompts and install everything:
- **For hooks, always use `settings.local.json`** (not `settings.json`) — local is gitignored so hook config stays personal.
- **Use `${CLAUDE_PLUGIN_ROOT}`** to reference template files within the plugin.
- **Platform detection for MCP**: Windows uses `cmd /c npx`, macOS/Linux uses `npx` directly.
---
## Related Skills
- `writing-skills` — for creating custom skills after init
- `mode-switching` — for using the installed modes
- **Output styles are NOT scaffolded by init.** They ship with the plugin at `output-styles/` and are auto-discovered. Users switch them via `/config` or by setting `outputStyle` in `.claude/settings.local.json`.
-112
View File
@@ -1,112 +0,0 @@
# Brainstorm Mode
## Description
Creative exploration mode optimized for ideation, design discussions, and exploring alternatives. Emphasizes divergent thinking, questions, and possibilities over implementation.
## When to Use
- Initial feature exploration
- Architecture decisions
- Problem definition
- Design sessions
- When stuck on approach
---
## Behavior
### Communication
- Ask more questions before concluding
- Present multiple alternatives
- Explore edge cases verbally
- Use "what if" scenarios
### Problem Solving
- Divergent thinking first
- Delay convergence on solutions
- Consider unconventional approaches
- Map trade-offs explicitly
### Output Format
- Structured comparisons
- Pro/con lists
- Decision matrices
- Visual diagrams (ASCII/Mermaid)
---
## Activation
Use natural language:
```
"switch to brainstorm mode"
"let's brainstorm [topic]"
"explore options for [feature]"
```
---
## Example Behaviors
### Before Implementing
```
Before we implement, let me explore some approaches:
Option A: [approach]
- Pros: ...
- Cons: ...
Option B: [approach]
- Pros: ...
- Cons: ...
Which direction interests you? Or should we explore more options?
```
### Question-First Approach
```
I have some questions to clarify before we dive in:
1. [Clarifying question about scope]
2. [Question about constraints]
3. [Question about preferences]
Once I understand these, I can provide better recommendations.
```
---
## MCP Integration
This mode leverages MCP servers for enhanced brainstorming:
### Sequential Thinking (Primary)
```
ALWAYS use Sequential Thinking in brainstorm mode:
- Explore design options systematically
- Track trade-offs for each approach
- Build confidence in recommendations incrementally
- Allow for revisions and backtracking
```
### Memory
```
Persist design decisions:
- Store design concepts and rationale
- Remember user preferences from previous sessions
- Build project design knowledge over time
```
### Context7
```
For informed technology choices:
- Fetch docs to compare library options
- Ground recommendations in real capabilities
```
## Combines Well With
- `brainstorming` skill (auto-triggered for creative exploration)
- `writing-plans` skill (transition from exploration to planning)
- Deep research mode (for informed exploration)
@@ -1,158 +0,0 @@
# Deep Research Mode
## Description
Thorough analysis mode for comprehensive investigation. Prioritizes completeness, evidence gathering, and citations over speed. Use when accuracy and depth matter more than efficiency.
## When to Use
- Technology evaluation
- Architecture research
- Security audits
- Performance analysis
- Complex debugging
- Due diligence tasks
---
## Behavior
### Communication
- Cite sources and evidence
- Acknowledge uncertainty explicitly
- Present confidence levels
- Include caveats and limitations
### Problem Solving
- Exhaustive exploration
- Multiple verification passes
- Cross-reference findings
- Document assumptions
### Output Format
- Structured reports
- Evidence sections
- Source citations
- Confidence indicators
---
## Research Process
### Phase 1: Scope Definition
- Clarify research questions
- Define success criteria
- Identify constraints
### Phase 2: Information Gathering
- Search codebase thoroughly
- Consult documentation
- Web research if needed
- Gather all relevant data
### Phase 3: Analysis
- Cross-reference findings
- Identify patterns
- Note contradictions
- Assess reliability
### Phase 4: Synthesis
- Draw conclusions
- Present evidence
- State confidence levels
- Acknowledge gaps
---
## Output Format
```markdown
## Research: [Topic]
### Question
[What we're investigating]
### Methodology
[How we researched]
### Findings
#### Finding 1: [Title]
- Evidence: [source/location]
- Confidence: [High/Medium/Low]
- Details: [explanation]
#### Finding 2: [Title]
...
### Conclusions
- [Conclusion 1] (Confidence: X/10)
- [Conclusion 2] (Confidence: X/10)
### Gaps & Limitations
- [What we couldn't determine]
- [Areas needing more investigation]
### Sources
- [Source 1]
- [Source 2]
```
---
## Activation
Use natural language:
```
"switch to deep-research mode"
"research [topic] thoroughly"
"do a deep investigation of [area]"
```
### Depth Levels
| Level | Behavior |
|-------|----------|
| 1 | Quick scan, surface findings |
| 2 | Standard analysis |
| 3 | Thorough investigation |
| 4 | Comprehensive with cross-references |
| 5 | Exhaustive, leave no stone unturned |
---
## MCP Integration
This mode leverages MCP servers for comprehensive research:
### Sequential Thinking (Primary)
```
ALWAYS use Sequential Thinking in deep-research mode:
- Structure analysis into logical thought sequences
- Track confidence scores for each finding
- Revise conclusions as evidence emerges
- Document reasoning chain for transparency
```
### Context7
```
For library/technology research:
- Fetch current documentation with get-library-docs
- Use mode='info' for conceptual understanding
- Verify findings against authoritative sources
```
### Memory
```
Build persistent research knowledge:
- Store research findings as entities
- Create relations between discovered concepts
- Recall previous research in future sessions
```
## Combines Well With
- `sequential-thinking` skill (structured step-by-step analysis)
- `researcher` agent (comprehensive technology research)
- Security audits
- Performance optimization
-47
View File
@@ -1,47 +0,0 @@
# Default Mode
## Description
Standard balanced mode for general development tasks. This is the baseline behavior that provides a good mix of thoroughness and efficiency.
## When Active
This mode is active by default unless another mode is explicitly specified.
---
## Behavior
### Communication
- Clear, concise responses
- Balance between explanation and action
- Standard code comments where helpful
### Problem Solving
- Balanced analysis depth
- Standard verification steps
- Normal iteration cycles
### Output Format
- Full code blocks with context
- Explanations where helpful
- Standard documentation level
---
## Activation
This mode is active by default. No activation needed.
To switch to another mode, use natural language:
```
"switch to brainstorm mode"
"use implementation mode"
"switch to token-efficient mode"
```
---
## Compatible With
All skills and workflows. This mode provides baseline behavior that other modes modify.
@@ -1,139 +0,0 @@
# Implementation Mode
## Description
Code-focused execution mode that minimizes discussion and maximizes code output. For when the plan is clear and it's time to build.
## When to Use
- Executing approved plans
- Clear, well-defined tasks
- Repetitive code generation
- When design is already decided
- Batch file operations
---
## Behavior
### Communication
- Minimal prose
- Action-oriented updates
- Progress indicators only
- Skip explanations unless asked
### Problem Solving
- Execute, don't deliberate
- Follow established patterns
- Make reasonable defaults
- Flag blockers immediately
### Output Format
- Code blocks primarily
- File paths clearly marked
- Minimal inline comments
- Progress checkmarks
---
## Output Pattern
```markdown
Creating `src/services/user-service.ts`:
```typescript
[code]
```
Creating `src/services/user-service.test.ts`:
```typescript
[code]
```
Running tests...
✓ 5 passing
Committing: `feat(user): add user service`
```
---
## Execution Flow
### Standard Pattern
1. Read task requirements
2. Identify files to create/modify
3. Generate code
4. Run verification
5. Report completion
### Progress Updates
```
[1/5] Creating model...
[2/5] Creating service...
[3/5] Creating tests...
[4/5] Running tests... ✓
[5/5] Committing...
Done. Created 3 files, all tests passing.
```
---
## Activation
Use natural language:
```
"switch to implementation mode"
"just code it"
"execute the plan"
```
---
## Decision Making
When encountering choices during implementation:
| Situation | Behavior |
|-----------|----------|
| Style choice | Follow existing patterns |
| Missing detail | Use reasonable default |
| Ambiguity | Flag and continue with assumption |
| Blocker | Stop and report immediately |
---
## Tool Usage
### Built-in Tools (Primary)
```
Use Claude Code built-in tools for file operations:
- Read to check existing code
- Write to create new files
- Edit for modifications
- Grep/Glob to find patterns to follow
```
### MCP Integration
#### Context7
```
For accurate library usage:
- Fetch current API documentation
- Get correct patterns and examples
```
#### Memory
```
Recall implementation context:
- Remember established patterns
- Recall user preferences
- Store decisions for consistency
```
## Combines Well With
- `executing-plans` skill (structured plan execution)
- `test-driven-development` skill (TDD workflow)
- Token-efficient mode (for maximum efficiency)
- After brainstorm/planning phases
@@ -1,163 +0,0 @@
# Orchestration Mode
## Description
Multi-agent coordination mode for managing complex tasks that benefit from parallel execution, task delegation, and result aggregation. Optimized for efficiency through parallelization.
## When to Use
- Large-scale refactoring
- Multi-file changes
- Complex feature implementation
- When tasks are parallelizable
- Coordinating multiple concerns
---
## Behavior
### Communication
- Task delegation clarity
- Progress aggregation
- Coordination updates
- Final synthesis
### Problem Solving
- Identify parallelizable work
- Delegate to specialized agents
- Aggregate results
- Resolve conflicts
### Output Format
- Task breakdown
- Agent assignments
- Progress tracking
- Consolidated results
---
## Orchestration Pattern
### Phase 1: Analysis
```markdown
## Task Decomposition
Total work: [description]
### Parallelizable Tasks
1. [Task A] - Can run independently
2. [Task B] - Can run independently
3. [Task C] - Can run independently
### Sequential Tasks
4. [Task D] - Depends on A, B
5. [Task E] - Final integration
```
### Phase 2: Delegation
```markdown
## Agent Assignments
| Task | Agent Type | Status |
|------|------------|--------|
| Task A | researcher | Running |
| Task B | tester | Running |
| Task C | code-reviewer | Running |
```
### Phase 3: Aggregation
```markdown
## Results
### Task A: Complete
- Findings: [summary]
### Task B: Complete
- Results: [summary]
### Task C: Complete
- Findings: [summary]
### Synthesis
[Combined conclusions and next steps]
```
---
## Agent Dispatch Pattern
For launching parallel background tasks using the Agent tool:
```markdown
Dispatching parallel agents:
1. Agent(researcher, "Research authentication patterns") -> Background #1
2. Agent(security-auditor, "Analyze current security") -> Background #2
3. Agent(scout-external, "Review competitor approaches") -> Background #3
Monitoring progress...
Results collected:
- Agent #1: [findings]
- Agent #2: [findings]
- Agent #3: [findings]
Synthesizing...
```
---
## Activation
Use natural language:
```
"switch to orchestration mode"
"coordinate these tasks in parallel"
"use parallel agents for this"
```
---
## Task Parallelization Rules
### Good Candidates for Parallel
- Independent file modifications
- Research tasks across different areas
- Test generation for different modules
- Documentation for separate components
### Must Be Sequential
- Tasks with dependencies
- Database migrations
- Changes to shared state
- Integration after parallel work
### Decision Matrix
| Condition | Parallelize? |
|-----------|--------------|
| No shared files | Yes |
| Independent modules | Yes |
| Shared dependencies | No |
| Order matters | No |
| Can merge results | Yes |
---
## Quality Gates
Between parallel phases:
1. Verify all agents completed
2. Check for conflicts
3. Review combined results
4. Run integration tests
5. Proceed to next phase
---
## Combines Well With
- `dispatching-parallel-agents` skill (structured parallel task dispatch)
- `executing-plans` skill (plan execution with quality gates)
- `subagent-driven-development` skill (automated agent coordination)
- Complex feature development
-141
View File
@@ -1,141 +0,0 @@
# Review Mode
## Description
Critical analysis mode optimized for code review, auditing, and quality assessment. Emphasizes finding issues, suggesting improvements, and thorough examination.
## When to Use
- Code reviews
- Security audits
- Performance reviews
- Pre-merge checks
- Quality assessments
- Architecture reviews
---
## Behavior
### Communication
- Direct feedback
- Prioritized findings
- Constructive criticism
- Specific, actionable suggestions
### Problem Solving
- Look for issues first
- Question assumptions
- Check edge cases
- Verify against standards
### Output Format
- Categorized findings
- Severity levels
- Line-specific comments
- Improvement suggestions
---
## Review Categories
### Severity Levels
| Level | Description | Action |
|-------|-------------|--------|
| Critical | Bugs, security issues | Must fix before merge |
| Important | Code smells, performance | Should fix |
| Minor | Style, naming | Consider fixing |
| Nitpick | Preferences | Optional |
### Review Areas
| Area | Focus |
|------|-------|
| Correctness | Does it work? Edge cases? |
| Security | Vulnerabilities, data exposure |
| Performance | Efficiency, scalability |
| Maintainability | Readability, complexity |
| Testing | Coverage, quality of tests |
| Standards | Convention compliance |
---
## Output Format
```markdown
## Code Review: [file/PR]
### Summary
[1-2 sentence overview]
### Critical Issues
1. **[Issue]** (line X)
- Problem: [description]
- Fix: [suggestion]
### Important Issues
1. **[Issue]** (line X)
- Problem: [description]
- Suggestion: [improvement]
### Minor Issues
- Line X: [issue and suggestion]
- Line Y: [issue and suggestion]
### Positive Notes
- [What was done well]
### Verdict
[ ] Ready to merge
[x] Needs changes (N critical, M important issues)
```
---
## Activation
Use natural language:
```
"switch to review mode"
"review this code critically"
"do a security-focused review"
```
---
## MCP Integration
This mode leverages MCP servers for thorough review:
### Playwright
```
For UI/frontend reviews:
- Render and verify visual changes
- Test responsive behavior
- Check accessibility
- Capture screenshots for comparison
```
### Sequential Thinking
```
For systematic code analysis:
- Step through logic methodically
- Track multiple concerns
- Build comprehensive issue list
```
### Memory
```
Apply consistent review standards:
- Recall past review decisions
- Remember approved patterns
- Track recurring issues
```
## Combines Well With
- `review` skill (user-invocable PR review)
- `security-review` skill (user-invocable security audit)
- Deep research mode (for thorough audits)
- `security-auditor` agent, `code-reviewer` agent
@@ -1,113 +0,0 @@
# Token-Efficient Mode
## Description
Cost optimization mode that produces compressed, concise outputs while maintaining accuracy. Reduces token usage by 30-70% depending on task type.
## When to Use
- High-volume sessions
- Simple tasks
- When cost is a concern
- Repeated similar operations
- Quick iterations
---
## Behavior
### Communication
- Minimal explanations
- No conversational filler
- Direct answers only
- Skip obvious context
### Problem Solving
- Jump to solutions
- Assume competence
- Skip basic explanations
- Reference docs instead of explaining
### Output Format
- Code without surrounding prose
- Abbreviated comments
- Terse commit messages
- Bullet points over paragraphs
---
## Output Patterns
### Standard vs Token-Efficient
**Standard:**
```
I'll help you fix this bug. First, let me explain what's happening.
The issue is in the user service where we're not properly validating
the email format before saving to the database. Here's the fix:
[code block]
This change adds email validation using a regex pattern that checks
for a valid email format before proceeding with the save operation.
```
**Token-Efficient:**
```
Fix: Add email validation
[code block]
```
### Compression Techniques
| Technique | Savings |
|-----------|---------|
| Skip preambles | 20-30% |
| Code-only responses | 40-50% |
| Abbreviated comments | 10-15% |
| Reference over explain | 30-40% |
---
## Activation
Use natural language:
```
"switch to token-efficient mode"
"be concise"
"code only"
```
### Verbosity Levels
| Level | Trigger | Savings |
|-------|---------|---------|
| Concise | "be concise" | 30-40% |
| Ultra | "code only" | 60-70% |
---
## When NOT to Use
- Complex architectural decisions
- Code reviews (need thorough analysis)
- Documentation tasks
- Teaching/explanation requests
- Debugging complex issues
---
## Example Output
**Request:** Fix the null pointer in user.ts
**Token-Efficient Response:**
```typescript
// user.ts:42
if (!user) return null;
// Before: user.name (crashes when null)
// After: user?.name ?? 'Unknown'
```
Done. Test: `npm test -- --grep "null user"`
+194
View File
@@ -0,0 +1,194 @@
---
name: investigate-root-cause
user-invocable: true
description: >
Use when encountering ANY bug, error, test failure, or unexpected behavior. Activate
for keywords like "bug", "error", "failing", "broken", "doesn't work", "unexpected",
"crash", "exception", "TypeError", "undefined", stack traces, or any error message.
Also trigger when tests fail unexpectedly, when behavior differs from expectations,
when investigating production incidents, or when flaky/intermittent issues appear.
Investigation produces evidence and a written hypothesis before any fix is attempted.
Always investigate root cause before proposing fixes -- never guess at solutions.
---
# Investigate Root Cause
## Overview
A four-phase debugging workflow that forces an engineer to gather evidence and write
down a hypothesis *before* changing any code. The skill exists because the most
common debugging failure isn't a missing technique — it's the engineer skipping past
the error message, forming a vague mental theory, and patching the symptom. Every
phase here produces an artifact you could paste into a code review. If you can't
produce the artifact, you haven't done the phase. The skill is for senior ICs and
tech leads who already know how to debug; what it adds is the discipline to refuse
to fix what you don't yet understand.
## When to Use
- A test is failing and you don't already know why
- An error message appeared that you cannot immediately point to a line of code for
- A reproduction is intermittent (sometimes passes, sometimes fails)
- A previously passing system started failing after no obvious cause
- Production is misbehaving and the cause isn't in the most recent commit
- You catch yourself about to write a fix while still uncertain why the bug happens
## When NOT to Use
- The error message names a missing import, typo, or syntax error and the fix is one
character. Just fix it.
- The runbook for this exact failure exists and the documented fix has been applied
before. Follow the runbook.
- The "bug" is a config value that needs flipping in an environment variable. Flip it.
## Process
Four phases. Each phase has a gate. You do not advance until the gate's evidence
artifact exists.
### Phase 1: Gather
**Goal:** Surface every fact that already exists about this bug, before forming any
theory.
**Inputs:** A bug report, a failing test, an error message, or a complaint
("it doesn't work").
**Actions:**
1. **Capture the literal error.** Copy the full text of the error message and the
complete stack trace. Do not paraphrase. If there is no error message, write down
the exact observed-vs-expected behavior in one sentence each.
2. **Find the reproduction.** Run the failing scenario yourself. Record the exact
command, environment, and inputs. If you cannot reproduce it, that is the bug to
investigate first — go to Step 3 and Step 4 and stay in Phase 1 until you can.
3. **Read recent history.** Run `git log --oneline -30` and read the last 30 commits.
Note which commits touch files in the stack trace.
4. **Collect logs.** Pull logs around the failure window. If structured logs exist,
filter to the request or session that hit the bug. If not, raise the verbosity
and re-run the reproduction.
5. **Look at the data.** If the bug involves a record, fetch the record. If it
involves a query, run the query. If it involves a request body, capture the body.
**Output:** A short text block titled `Phase 1: Gather` containing the literal error
text, the exact reproducer command, the relevant commit hashes, log excerpts, and
data values. Pasted into a scratch file or PR description.
### Phase 2: Hypothesize
**Goal:** Convert evidence into a single specific written hypothesis. One.
**Inputs:** The Phase 1 artifact.
**Actions:**
1. **Find a working comparison.** Locate the closest equivalent code path that
succeeds. Read it. Note the differences.
2. **Identify the smallest difference that matters.** Configuration, data shape,
environment, timing, or contract. Name it.
3. **Write the hypothesis as one sentence in this exact form:**
`The bug occurs because [X] causes [Y] when [Z].`
No "I think." No "maybe." If you can't fill all three slots, return to Phase 1.
**Output:** A one-sentence hypothesis added under `Phase 2: Hypothesize`. Plus the
file:line citation of the working comparison code.
### Phase 3: Test
**Goal:** Prove or disprove the hypothesis with a single deliberate change.
**Inputs:** The hypothesis from Phase 2.
**Actions:**
1. **Design the smallest test of the hypothesis.** Often this is a one-line
`print` / `console.error` / breakpoint at the line where you predicted the
anomaly happens, NOT a fix.
2. **Run it. Capture the output.** Record what you saw with the same rigor as
Phase 1's literal error capture.
3. **Decide:** does the output confirm or refute the hypothesis?
- **Confirm:** advance to Phase 4.
- **Refute:** return to Phase 2 with the new evidence. Update the hypothesis.
Do not start patching.
**Output:** Under `Phase 3: Test`, the exact instrumentation used, the output
captured, and a one-line verdict: `Hypothesis confirmed | Hypothesis refuted →
return to Phase 2`.
### Phase 4: Prove
**Goal:** A fix that addresses the cause, with a regression test that pins it.
**Inputs:** A confirmed hypothesis.
**Actions:**
1. **Write a failing test that captures the bug.** The test fails on `main` and
passes after the fix. It exercises the cause, not the symptom.
2. **Make the smallest change that makes the test pass.** Single targeted fix at
the cause. Do not bundle other improvements.
3. **Run the failing test. Confirm it passes.**
4. **Run the full test suite. Confirm green.**
5. **Run the original reproduction from Phase 1. Confirm fixed.**
**Output:** Under `Phase 4: Prove`, paste:
- Failing test name and location
- Test runner output before fix (red)
- Test runner output after fix (green)
- Full-suite output (green)
- Original Phase 1 reproducer output (now passing)
## Rationalizations
| Excuse | Why it sounds reasonable | Why it's wrong | What to do instead |
|---|---|---|---|
| "I think I see the problem — let me just patch it." | The fix often is small once you understand it. The instinct that you "see it" feels like signal. | If you were right, you wouldn't need a hypothesis. The "I see it" feeling is pattern-matching on similar bugs you've seen before, and pattern-matching has a high false-positive rate on real systems. The patches that ship from this state usually fix the *symptom* one observation downstream of the cause. | Phase 2 anyway. Write the hypothesis sentence. If it really is obvious, this takes 60 seconds. If you can't write the sentence, you didn't actually see it. |
| "Can't reproduce locally — must be a flake." | Flakes do exist, and chasing a non-reproducer wastes time. | "Flake" is what we call a bug whose trigger condition we haven't found yet. Closing a ticket as "flaky" hands the bug to the next person who hits it, plus accumulated mystery. The trigger is real; you just don't know it yet. | Treat "can't reproduce" as the bug. Phase 1, Step 2: list every difference between your environment and the failing one (timezone, locale, clock skew, parallelism, container vs host, data size, prior test state). Bisect on differences. |
| "It worked before the last deploy — it's the deploy." | Recent deploys do cause regressions, and `git bisect` is real evidence. | "It's the deploy" without bisect is folklore. The deploy may have shifted timing, exposed a latent bug, or changed something orthogonal. Skipping bisect means the fix may also be folklore. | Run `git bisect` between the last known good and the first known bad. Cite the actual offending commit hash in the hypothesis. |
| "It's probably a race condition." / "Must be caching." | These categories explain a lot of intermittent bugs. | Naming a category is not a hypothesis. "Race condition" doesn't tell you which two operations race or what the interleaving is. Until you can write `[X] causes [Y] when [Z]` with the actual operations and ordering, you're labeling, not investigating. | Phase 2 with concrete operations: which thread/request reads, which writes, what happens when the write lands during the read. Same shape for caching: which key, which TTL, what stale value, who serves it. |
| "Let me wrap it in a try/catch and move on." | Defensive coding is a real practice, and silencing exceptions does keep the surface stable. | Catching the exception that resulted from the bug doesn't fix the bug — it hides the evidence the next investigator needs. The system continues to be wrong, just quieter. The next failure will be downstream and harder to trace. | If a try/catch is appropriate for *known* failure modes, fine — but only after the cause is understood. The catch goes in Phase 4 *with* a hypothesis-confirmed reason for tolerating that failure mode. Otherwise you are masking. |
| "I'll add some logs and check it tomorrow." | Adding logging is a real Phase 1 action. | The trap is the "tomorrow" part — logs that get added without a written hypothesis, drift in the codebase as cruft, and never get analyzed because by tomorrow the urgent thing has shifted. The investigation gets put down without a marker. | Add logs, but inside Phase 1 with a written reason: "logging X to test whether Y occurs before Z." Set a calendar reminder for the analysis. If you won't analyze tomorrow, don't add the logs. |
| "The error message is misleading — the real bug is somewhere else." | Sometimes errors do surface far from their cause. | "The error is misleading" said *before* Phase 1's literal capture is the engineer dismissing evidence they haven't read carefully yet. The error message is data; "misleading" is a story you tell about data. Read the data first. | Paste the literal error in Phase 1. If the message names a file:line, look at that file:line before declaring the message is wrong. Most "misleading" errors are accurate; the engineer was holding a wrong mental model of which code runs first. |
## Evidence Requirements
Every phase has a gate. If the gate's artifact does not exist, that phase has not
been completed.
| Checkpoint | Required artifact | What "no evidence" looks like |
|---|---|---|
| End of Phase 1 | Literal error text + reproducer command + relevant commit hashes pasted in a `Phase 1: Gather` block | "I read through the code and I'm pretty sure it's in the auth module." |
| End of Phase 2 | One sentence in form `The bug occurs because [X] causes [Y] when [Z]` | "It's probably a race condition somewhere in the request lifecycle." |
| End of Phase 3 | Captured output from a deliberate test of the hypothesis (instrumentation OR experiment), plus a confirm/refute verdict | "Yeah I tried a thing and it seemed to work." |
| End of Phase 4 | Failing test (red), passing test after fix (green), full suite (green), original reproducer (fixed) — all four pasted | "Tests pass on my machine." |
If you can't paste it, you haven't done it. Stop.
## Red Flags
Concrete observations that mean STOP and reassess.
- You've changed the same line three or more different ways in the last hour. You
don't have a working hypothesis; you're guessing.
- You added a `try/catch`, `if err == nil`, or test-skip whose justification is
"to make the test pass." That's masking, not fixing.
- The hypothesis sentence is missing the `when [Z]` clause. You don't know the
trigger condition. The fix will be partial.
- Three consecutive fix attempts have failed. The bug is architectural, not local.
Escalate or rescope.
- You're about to ship a fix you cannot explain to the next reviewer in one
sentence. The reviewer won't accept it; you shouldn't either.
- The failing test you wrote in Phase 4 doesn't actually fail on `main` without
the fix. It tests something tangential. Rewrite it.
## References
- John Allspaw & Richard Cook, *How Complex Systems Fail* (Cognitive Technologies
Laboratory, 1998) — point #5 ("Complex systems run in degraded mode") and point
#14 ("Change introduces new forms of failure"). Use these to resist the "it
worked before the deploy" reflex; the post-deploy failure is often a latent
problem made visible, not the deploy itself.
- *Site Reliability Engineering*, Beyer et al. (Google, O'Reilly 2016), Chapter 12
"Effective Troubleshooting" — defines the diagnose-test-fix loop this skill's
Phases 2-4 implement, and explicitly warns against the "I know what's wrong"
pattern handled in the Rationalizations table.
+154
View File
@@ -0,0 +1,154 @@
---
name: map-codebase
user-invocable: true
description: >
Use when entering an unfamiliar codebase or area, before making non-trivial changes,
when onboarding to a new system, or when planning a refactor that touches multiple
modules. Activate for keywords like "explore", "map", "find where", "trace", "how
does X work", "what calls Y", "scope of change". Produces an evidence-cited map of
the relevant area with file:line references for every claim. Always cite the file
and line you read -- never assert behavior you have not verified by reading.
---
# Map Codebase
## Overview
A methodical exploration workflow that produces an evidence-cited map of a codebase
area. Replaces ad-hoc grep with a disciplined four-step pass: scope, list, read,
diagram. The output is a short artifact you can attach to a plan or design doc —
file paths, line numbers, call directions, and the questions you couldn't answer
from reading. The skill's value is enforcing that every claim about the code is
backed by a specific file:line citation, not a memory or pattern-match. Senior ICs
and tech leads use it to bound the blast radius of a change before they propose it.
## When to Use
- Before writing a plan that touches more than one module
- When inheriting a codebase area you didn't author
- When a teammate asks "how does X work" and you don't have a confident answer with citations
- Before a refactor, to enumerate everything that calls the code you're about to change
- When debugging crosses a boundary you don't fully understand (auth, ORM, framework internals)
## When NOT to Use
- The change is single-file and you've already read the file
- You're modifying code you wrote yourself within the last week
- The "exploration" is really a one-line lookup that `Grep` answers in 5 seconds
## Process
### Step 1: Scope
**Goal:** Pin down what you are mapping and what you explicitly are not.
**Inputs:** A task, plan, or question that triggered the need to explore.
**Actions:**
1. Write one sentence: `I am mapping <X> in order to <Y>.` X is concrete (a feature,
a module, a request path). Y is the decision the map will support.
2. Write one sentence naming what is **out of scope**: `I am not mapping <Z>.`
This prevents the exploration from sprawling.
3. Set a time box. 30 minutes for a single feature, 90 minutes for a subsystem.
**Output:** A two-sentence scope statement at the top of your scratch artifact.
### Step 2: List entry points
**Goal:** Enumerate every place execution can enter the area being mapped.
**Inputs:** The scope statement.
**Actions:**
1. Find route handlers, controllers, CLI commands, queue consumers, scheduled jobs,
or event listeners that touch the area. `Grep` for the framework's routing
primitives.
2. List each entry point as `<file:line> — <what triggers it>`.
3. If the count exceeds 10, return to Step 1. Your scope is too wide.
**Output:** A bullet list of entry points with file:line citations.
### Step 3: Trace and read
**Goal:** Read the actual code at each entry point and the immediate calls outward,
collecting facts.
**Inputs:** The entry-points list.
**Actions:**
1. For each entry point, read the function body. No skimming — line by line.
2. Note every call out of that function: which module, which function, which
file:line.
3. Follow each call one level deep. Then stop and decide if you need a second
level. Most maps don't.
4. Record surprises. Lines that don't do what their name suggests, defensive code
that hints at a past bug, configuration that controls behavior implicitly.
5. Record questions. Things you couldn't answer from reading — these become the
"Open" section of the output.
**Output:** A flat list of facts, each in form `<file:line> — <what this code does>`,
plus a short list of open questions.
### Step 4: Diagram and write up
**Goal:** Compress the trace into a single artifact a teammate can read in 3 minutes.
**Inputs:** The trace from Step 3.
**Actions:**
1. Write the artifact in Markdown with these sections:
- **Scope** (the Step 1 sentences)
- **Entry points** (the Step 2 list)
- **Call graph** (a small ASCII diagram or nested bullet list with file:line)
- **Surprises** (each in form `<file:line> — <what surprised me>`)
- **Open questions** (each in form `<question> — <where you'd need to look>`)
2. Save it. Even if it's a scratch file in `/tmp`. The artifact is the deliverable.
3. If the map is for a plan or design doc, link it; do not paraphrase it.
**Output:** A Markdown artifact at a known path. Maximum 300 lines.
## Rationalizations
| Excuse | Why it sounds reasonable | Why it's wrong | What to do instead |
|---|---|---|---|
| "I already know how this works." | You may have read this code before. Re-reading feels like wasted time. | Memory drift is real and unsensed. The function you remember was three commits ago; the current version has a different signature, a new branch, or a defensive check that changes behavior. The bugs that hit hardest in unfamiliar areas are usually in the code the engineer was sure they knew. | Read the file at the actual current commit before you cite it. If your memory matches what's there, the read takes 60 seconds. If it doesn't, you just avoided a confident wrong answer in your plan. |
| "Grep is enough — I don't need to read the function." | Grep does locate code. For a one-line lookup, that's the whole job. | Grep tells you *where* something appears, not *what it does*. A function that grep matches on `cache.get` may also delete on cache miss, may wrap a remote call, may log to a different sink. Citing the file:line without reading it is asserting behavior you haven't verified. | After Grep finds the call site, open the file and read the function body. Cite file:line in your map only after reading. |
| "Two levels deep is enough — I don't need to follow further." | Going arbitrarily deep is how exploration sprawls. Time-boxing is correct. | The trap is stopping deep enough to feel productive but not deep enough to answer the actual scope question. If your scope was "what does this endpoint do," and the second level is a generic ORM call, the answer is still incomplete. | Re-read your Step 1 scope sentence. If your current trace doesn't answer the `in order to <Y>` clause, you haven't gone deep enough on the calls that matter. Don't go deeper on calls that don't. |
| "I'll write it up later — let me just keep exploring." | Writing while exploring breaks flow. | "Later" usually means after the next task arrives, by which point the trace is gone from working memory. The map ends up reconstructed from a fuzzy recollection, with citations the engineer "thinks are right." That's the same failure mode as not mapping at all. | Open the artifact file at Step 1 and append as you trace. The artifact is grown, not written at the end. If you finish the trace and the artifact is empty, you're going to write it from memory, badly. |
| "ASCII diagrams are silly — text is fine." | Some maps genuinely don't need a diagram. Pure prose can carry. | A diagram-free writeup of a multi-entry-point system is hard to scan and hard to verify. The reader has to mentally reconstruct the call graph from prose. They won't. They'll skim, miss something, and your map becomes a thing nobody actually used. | If there are 3+ entry points or 2+ modules in the scope, draw the call graph. ASCII is fine. Half the value of mapping is the *picture* in someone else's head, not the prose in yours. |
## Evidence Requirements
| Checkpoint | Required artifact | What "no evidence" looks like |
|---|---|---|
| End of Step 1 | Two-sentence scope statement at top of artifact | "I'm exploring the auth module." |
| End of Step 2 | Bulleted entry-points list with file:line on every row | "There are a bunch of routes that hit this." |
| End of Step 3 | Flat trace with file:line on every fact | "It looks like the cache is checked first, then the DB." |
| End of Step 4 | Markdown artifact saved at a known path with all 5 sections, ≤300 lines | "I have a good mental model now." |
If the artifact does not exist as a file you could send to a teammate, you have not
mapped the codebase. You have read some code.
## Red Flags
- Your map exceeds 300 lines. Your scope was too wide; return to Step 1.
- More than half the entries in your trace cite the same file. You are reading one
file, not mapping a system.
- Your "Open questions" section is empty. You either understand everything (rare,
suspicious) or you stopped recording uncertainty.
- You wrote the artifact in past tense ("I explored…") instead of present tense
("This module routes…"). The first version is a journal entry; the second is a
map a teammate can use.
- A claim in the artifact has no file:line citation. The reader has to take it on
faith.
## References
- Michael Feathers, *Working Effectively with Legacy Code* (Prentice Hall, 2004),
Chapter 16 "I Don't Understand the Code Well Enough to Change It" — the
scratch-refactoring and effect-sketch techniques are the source of the
diagram-as-deliverable principle in Step 4.
-87
View File
@@ -1,87 +0,0 @@
---
name: mode-switching
argument-hint: "[mode name]"
user-invocable: true
description: >
Use when the user wants to switch behavioral modes for the session — adjusting communication style, output format, and problem-solving approach. Trigger for keywords like "mode", "switch mode", "brainstorm mode", "token-efficient", "deep-research mode", "implementation mode", "review mode", "orchestration mode", or any request to change how Claude responds for the remainder of the session.
---
# Mode Switching
## When to Use
- User wants to change response style for the session
- Switching between exploration and execution phases
- Optimizing for token efficiency during high-volume work
- Entering focused review or deep-research mode
## When NOT to Use
- One-off format requests ("give me a shorter answer") — just comply directly
- Switching tools or skills — modes affect style, not capabilities
---
## Available Modes
| Mode | Description | Best For |
|------|-------------|----------|
| `default` | Balanced responses, mix of explanation and code | General tasks |
| `brainstorm` | More questions, multiple alternatives, explore trade-offs | Design, ideation |
| `token-efficient` | Minimal explanations, code-only where possible | High-volume, cost savings |
| `deep-research` | Thorough analysis, citations, confidence levels | Investigation, audits |
| `implementation` | Jump straight to code, progress indicators | Executing plans |
| `review` | Look for issues first, severity levels, actionable feedback | Code review, QA |
| `orchestration` | Task breakdown, parallel execution, result aggregation | Complex parallel work |
## Mode Activation
Use natural language to switch modes for the session:
```
"switch to brainstorm mode" # Creative exploration
"use implementation mode" # Code-focused execution
"switch to token-efficient mode" # Compressed output
"back to default mode" # Reset
```
## Recommended Workflows
### Feature Development
```
brainstorm → implementation → review → default
```
### Bug Investigation
```
deep-research → implementation → default
```
### Cost-Conscious Session
```
token-efficient → [work on tasks] → default
```
---
## Mode Files
Mode definitions: `.claude/modes/`
Customize modes by editing these files. Each mode adjusts:
- Communication style and verbosity
- Output format preferences
- Problem-solving approach
- When to ask questions vs proceed
---
## Related Skills
- `writing-concisely` — The token-efficient mode activates this skill's patterns
- `brainstorming` — The brainstorm mode uses this skill's questioning approach
- `executing-plans` — Implementation mode pairs with plan execution
- `sequential-thinking` — Deep research mode leverages structured reasoning
-66
View File
@@ -1,66 +0,0 @@
---
name: owasp
description: >
Use when reviewing code for security vulnerabilities, implementing authentication or authorization flows, handling user input validation, or building web endpoints exposed to untrusted data. Trigger on keywords like XSS, SQL injection, CSRF, input sanitization, password hashing, security headers, "security scan", "vulnerability scan", "npm audit", or "pip-audit". Also apply when auditing existing code for OWASP Top 10 compliance, scanning dependencies for known vulnerabilities, detecting hardcoded secrets, or conducting security-focused code reviews.
---
# OWASP Security Patterns
## When to Use
- Reviewing code for OWASP Top 10 vulnerabilities
- Implementing input validation on user-facing endpoints
- Adding security headers (CSP, HSTS, X-Frame-Options)
- Preventing XSS, SQL injection, CSRF, or SSRF
- Auditing authentication or authorization flows
- Building endpoints that handle untrusted data
- Scanning dependencies for known vulnerabilities (`npm audit`, `pip-audit`)
- Detecting hardcoded secrets, API keys, or tokens in code
## When NOT to Use
- Infrastructure security (network, firewall, cloud IAM) — use platform-specific tools
- Cryptographic algorithm selection — consult cryptography experts
- Compliance frameworks (SOC 2, HIPAA) — security patterns help but don't cover audit requirements
---
## Quick Reference
| Topic | Reference | Key content |
|-------|-----------|-------------|
| All security patterns | `references/patterns.md` | Input validation, SQL injection, XSS, CSRF, auth, headers |
| OWASP Top 10 cheatsheet | `references/owasp-top10-cheatsheet.md` | Quick reference for each vulnerability category |
| Security headers | `references/security-headers.md` | CSP, HSTS, X-Frame-Options, Referrer-Policy |
| Security checklist | `references/security-checklist.md` | Pre-deploy security review checklist |
| Security audit script | `references/security-audit.py` | Automated security scanning utility |
---
## Best Practices
1. **Validate all input at the boundary.** Use Pydantic (Python) or Zod (TypeScript) for schema validation. Never trust client-side validation alone.
2. **Use parameterized queries exclusively.** Never concatenate user input into SQL strings. Use ORM query builders or prepared statements.
3. **Encode output based on context.** HTML-encode for HTML, URL-encode for URLs, JSON-encode for JSON. No single encoding fits all contexts.
4. **Set security headers on every response.** CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy.
5. **Use CSRF tokens for state-changing requests.** Every POST/PUT/DELETE from a browser form needs a CSRF token.
6. **Apply rate limiting to all public endpoints.** Especially authentication, registration, and password reset.
7. **Never expose stack traces or internal errors to clients.** Return generic error messages; log details server-side.
8. **Audit dependencies regularly.** Run `npm audit` / `pip-audit` / `safety check` in CI.
## Common Pitfalls
1. **Relying on client-side validation only** — easily bypassed with curl or browser devtools.
2. **Using `dangerouslySetInnerHTML` or `| safe` without sanitization** — XSS vector.
3. **SQL string concatenation** — even "just for this one query" is a SQL injection risk.
4. **Missing CSRF protection on API routes** — if cookies are used for auth, CSRF applies.
5. **Overly permissive CORS**`Access-Control-Allow-Origin: *` with credentials is a security hole.
6. **Logging sensitive data** — passwords, tokens, and PII in logs persist in storage and backups.
---
## Related Skills
- `defense-in-depth` — Multi-layer validation so a single-point failure can't cause data corruption
- `testing` — Security test patterns (input validation, authz boundaries)
- `devops` — Container and CI hardening
@@ -1,193 +0,0 @@
# OWASP Top 10 (2021) Cheat Sheet
Quick reference for the OWASP Top 10 web application security risks.
---
## A01: Broken Access Control
**Risk**: Users act outside intended permissions (view other users' data, modify access).
**Prevention**: Deny by default. Enforce ownership. Disable directory listing. Log failures.
```python
# Enforce ownership check
def get_order(order_id, current_user):
order = db.query(Order).get(order_id)
if order.user_id != current_user.id:
raise PermissionError("Access denied")
return order
```
## A02: Cryptographic Failures
**Risk**: Exposure of sensitive data due to weak or missing encryption.
**Prevention**: Encrypt data at rest and in transit. Use strong algorithms (AES-256, bcrypt). Never store plaintext passwords.
```python
from passlib.hash import bcrypt
hashed = bcrypt.hash(password)
assert bcrypt.verify(password, hashed)
```
## A03: Injection
**Risk**: Untrusted data sent to an interpreter as part of a command or query.
**Prevention**: Use parameterized queries. Validate and sanitize all input. Use ORMs.
```python
# WRONG: cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
# RIGHT:
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
```
```typescript
// WRONG: db.query(`SELECT * FROM users WHERE id = ${id}`)
// RIGHT:
db.query("SELECT * FROM users WHERE id = $1", [id]);
```
## A04: Insecure Design
**Risk**: Missing or ineffective security controls due to flawed architecture.
**Prevention**: Use threat modeling. Apply secure design patterns. Establish reference architectures. Write abuse-case tests.
```python
# Rate-limit sensitive operations
from functools import lru_cache
from datetime import datetime, timedelta
LOGIN_ATTEMPTS = {} # Use Redis in production
def check_rate_limit(ip: str, max_attempts=5, window=300):
now = datetime.now().timestamp()
attempts = [t for t in LOGIN_ATTEMPTS.get(ip, []) if now - t < window]
if len(attempts) >= max_attempts:
raise RateLimitExceeded()
attempts.append(now)
LOGIN_ATTEMPTS[ip] = attempts
```
## A05: Security Misconfiguration
**Risk**: Default configs, incomplete setups, open cloud storage, verbose errors.
**Prevention**: Repeatable hardening process. Minimal platform. Remove unused features. Review cloud permissions.
```yaml
# Docker: don't run as root
FROM python:3.12-slim
RUN useradd -m appuser
USER appuser
```
## A06: Vulnerable and Outdated Components
**Risk**: Using components with known vulnerabilities.
**Prevention**: Remove unused dependencies. Monitor CVEs. Use `pip audit`, `npm audit`. Pin versions.
```bash
pip audit # Python
npm audit # Node.js
npx depcheck # Find unused deps
```
## A07: Identification and Authentication Failures
**Risk**: Weak authentication, credential stuffing, session fixation.
**Prevention**: MFA. Strong password policies. Secure session management. Throttle failed logins.
```python
# Secure session config (Flask)
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE="Lax",
PERMANENT_SESSION_LIFETIME=timedelta(hours=1),
)
```
## A08: Software and Data Integrity Failures
**Risk**: Code and infrastructure that does not protect against integrity violations (CI/CD, unsigned updates).
**Prevention**: Verify signatures. Use lock files. Review CI/CD pipelines. Use Subresource Integrity.
```html
<!-- Subresource Integrity -->
<script src="https://cdn.example.com/lib.js"
integrity="sha384-abc123..."
crossorigin="anonymous"></script>
```
## A09: Security Logging and Monitoring Failures
**Risk**: Insufficient logging makes breaches undetectable.
**Prevention**: Log auth events, access control failures, input validation failures. Set up alerts.
```python
import logging
logger = logging.getLogger("security")
def login(username, password):
user = authenticate(username, password)
if not user:
logger.warning("Failed login attempt", extra={
"username": username,
"ip": request.remote_addr,
"timestamp": datetime.utcnow().isoformat(),
})
raise AuthenticationError()
logger.info("Successful login", extra={"user_id": user.id})
```
## A10: Server-Side Request Forgery (SSRF)
**Risk**: Application fetches remote resources without validating user-supplied URLs.
**Prevention**: Allowlist URLs/domains. Block private IP ranges. Disable redirects.
```python
from urllib.parse import urlparse
import ipaddress
ALLOWED_HOSTS = {"api.example.com", "cdn.example.com"}
def validate_url(url: str) -> bool:
parsed = urlparse(url)
if parsed.hostname not in ALLOWED_HOSTS:
return False
try:
ip = ipaddress.ip_address(parsed.hostname)
if ip.is_private or ip.is_loopback:
return False
except ValueError:
pass # hostname, not IP — already checked against allowlist
return True
```
---
## Quick Reference Table
| ID | Name | Key Control |
|-----|-------------------------------|--------------------------------|
| A01 | Broken Access Control | Deny by default, enforce ownership |
| A02 | Cryptographic Failures | Encrypt in transit + at rest |
| A03 | Injection | Parameterized queries |
| A04 | Insecure Design | Threat modeling, abuse cases |
| A05 | Security Misconfiguration | Hardened defaults, minimal surface |
| A06 | Vulnerable Components | Audit deps, pin versions |
| A07 | Auth Failures | MFA, session security |
| A08 | Integrity Failures | Verify signatures, lock files |
| A09 | Logging Failures | Log security events, alert |
| A10 | SSRF | Allowlist URLs, block private IPs |
*Source: [OWASP Top 10 (2021)](https://owasp.org/Top10/)*
-551
View File
@@ -1,551 +0,0 @@
# Owasp — Patterns
# OWASP Web Application Security
## When to Use
- Security code reviews
- Implementing authentication or authorization
- Handling user input from untrusted sources
- Building or auditing web API endpoints
- Configuring CORS, CSP, or other security headers
- Managing secrets, tokens, or credentials in code
- Setting up rate limiting or brute force protection
## When NOT to Use
- General code style or formatting reviews with no security implications
- Non-web applications such as CLI tools, batch scripts, or desktop utilities
- Performance optimization tasks where security is not the concern
- Infrastructure-level security (firewall rules, network segmentation)
---
## Core Patterns
### 1. Input Validation & Sanitization
Always validate input at the boundary. Use allowlists over denylists.
**Python (Pydantic)**
```python
# BAD - no validation, accepts anything
@app.post("/users")
async def create_user(request: Request):
data = await request.json()
name = data["name"] # no length check, no type check
email = data["email"] # no format validation
role = data["role"] # user controls their own role
db.execute(f"INSERT INTO users VALUES ('{name}', '{email}', '{role}')")
# GOOD - strict schema validation with Pydantic
from pydantic import BaseModel, EmailStr, Field
from enum import Enum
class UserRole(str, Enum):
viewer = "viewer"
editor = "editor"
class CreateUserRequest(BaseModel):
name: str = Field(min_length=1, max_length=100, pattern=r"^[a-zA-Z\s\-]+$")
email: EmailStr
role: UserRole = UserRole.viewer # default to least privilege
@app.post("/users")
async def create_user(payload: CreateUserRequest):
# Pydantic rejects invalid data before this code runs
db.add(User(name=payload.name, email=payload.email, role=payload.role))
```
**TypeScript (Zod)**
```typescript
// BAD - trusting req.body directly
app.post("/users", (req, res) => {
const { name, email, role } = req.body; // no validation
db.query(`INSERT INTO users VALUES ('${name}', '${email}', '${role}')`);
});
// GOOD - validate with Zod at the boundary
import { z } from "zod";
const CreateUserSchema = z.object({
name: z.string().min(1).max(100).regex(/^[a-zA-Z\s\-]+$/),
email: z.string().email(),
role: z.enum(["viewer", "editor"]).default("viewer"),
});
app.post("/users", (req, res) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.flatten() });
}
// result.data is typed and validated
await prisma.user.create({ data: result.data });
});
```
**File Upload Validation**
```python
# GOOD - validate MIME type (not just extension), size, and sanitize filename
import magic
ALLOWED_TYPES = {"image/jpeg", "image/png", "application/pdf"}
MAX_SIZE = 5 * 1024 * 1024 # 5 MB
def validate_upload(file_bytes: bytes, filename: str) -> bool:
if len(file_bytes) > MAX_SIZE:
raise ValueError("File too large")
if magic.from_buffer(file_bytes, mime=True) not in ALLOWED_TYPES:
raise ValueError("Disallowed file type")
if ".." in filename or filename.startswith("."):
raise ValueError("Invalid filename")
return True
```
### 2. SQL Injection Prevention
Never concatenate user input into SQL strings. Always use parameterized queries or ORM methods.
**Raw SQL (Python)**
```python
# BAD - string interpolation creates injection vector
def get_user(user_id: str):
query = f"SELECT * FROM users WHERE id = '{user_id}'"
# Input: "'; DROP TABLE users; --" destroys the table
cursor.execute(query)
# GOOD - parameterized query
def get_user(user_id: str):
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
return cursor.fetchone()
```
**SQLAlchemy (Python)**
```python
# BAD - text() with f-string
from sqlalchemy import text
result = session.execute(text(f"SELECT * FROM users WHERE name = '{name}'"))
# GOOD - bound parameters with text()
result = session.execute(text("SELECT * FROM users WHERE name = :name"), {"name": name})
# GOOD - ORM query (automatically parameterized)
user = session.query(User).filter(User.name == name).first()
```
**Prisma (TypeScript)**
```typescript
// BAD - raw query with interpolation
const user = await prisma.$queryRawUnsafe(`SELECT * FROM users WHERE id = '${id}'`);
// GOOD - tagged template (auto-parameterized)
const user = await prisma.$queryRaw`SELECT * FROM users WHERE id = ${id}`;
// GOOD - Prisma client methods (always safe)
const user = await prisma.user.findUnique({ where: { id } });
```
### 3. XSS Prevention
Prevent cross-site scripting by encoding output, setting CSP headers, and sanitizing HTML.
**Output Encoding**
```typescript
// BAD - renders raw user content as HTML
element.innerHTML = userComment;
// GOOD - use textContent for plain text
element.textContent = userComment;
// GOOD - React auto-escapes by default (don't bypass it)
return <div>{userComment}</div>;
// BAD - dangerouslySetInnerHTML defeats React's protection
return <div dangerouslySetInnerHTML={{ __html: userComment }} />;
```
**Sanitizing HTML When You Must Render It**
```typescript
// GOOD - sanitize with DOMPurify when HTML rendering is required
import DOMPurify from "dompurify";
const cleanHtml = DOMPurify.sanitize(userHtml, {
ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "p", "br"],
ALLOWED_ATTR: ["href", "title"],
});
return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />;
```
### 4. Authentication Patterns
**Password Hashing**
```python
# BAD - plain text or weak hashing
hashed = hashlib.md5(password.encode()).hexdigest() # trivially crackable
# GOOD - use argon2 (preferred) or bcrypt with proper cost
from passlib.hash import argon2
hashed = argon2.hash(password)
is_valid = argon2.verify(password, hashed)
```
```typescript
// GOOD - bcrypt in Node.js
import bcrypt from "bcrypt";
const SALT_ROUNDS = 12;
const hashed = await bcrypt.hash(password, SALT_ROUNDS);
const isValid = await bcrypt.compare(password, hashed);
```
**JWT Best Practices**
```python
# BAD - long-lived token, weak secret
token = jwt.encode({"user_id": 1, "exp": datetime.utcnow() + timedelta(days=365)},
"secret123", algorithm="HS256")
# GOOD - short expiry, strong secret, httpOnly cookie delivery
ACCESS_TOKEN_EXPIRY = timedelta(minutes=15)
def create_access_token(user_id: int) -> str:
return jwt.encode(
{"sub": user_id, "exp": datetime.now(timezone.utc) + ACCESS_TOKEN_EXPIRY},
os.environ["JWT_SECRET_KEY"], algorithm="HS256",
)
def set_token_cookie(response: Response, token: str):
response.set_cookie(
key="access_token", value=token,
httponly=True, secure=True, samesite="lax", # not accessible via JS, HTTPS only
max_age=int(ACCESS_TOKEN_EXPIRY.total_seconds()),
)
```
**Session Management Rules**
- Set session timeouts (30 minutes idle, 8 hours absolute)
- Regenerate session ID after login to prevent session fixation
- Store sessions server-side (Redis, database), not in cookies
- Clear sessions on logout (`request.session.clear()`)
- Use `httponly`, `secure`, and `samesite=lax` on session cookies
### 5. Authorization & Access Control
**RBAC Pattern**
```python
# GOOD - role-based access control with decorator
from enum import Enum
class Role(str, Enum):
admin = "admin"
editor = "editor"
viewer = "viewer"
ROLE_HIERARCHY = {Role.admin: 3, Role.editor: 2, Role.viewer: 1}
def require_role(minimum_role: Role):
def decorator(func):
async def wrapper(request: Request, *args, **kwargs):
user = request.state.user
if ROLE_HIERARCHY.get(user.role, 0) < ROLE_HIERARCHY[minimum_role]:
raise HTTPException(status_code=403)
return await func(request, *args, **kwargs)
return wrapper
return decorator
@app.delete("/posts/{post_id}")
@require_role(Role.editor)
async def delete_post(request: Request, post_id: int): ...
```
**Middleware-Based Authorization (Express)**
```typescript
// GOOD - authorization middleware
function requireRole(...allowedRoles: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user || !allowedRoles.includes(req.user.role)) {
return res.status(403).json({ error: "Forbidden" });
}
next();
};
}
app.delete("/posts/:id", requireRole("admin", "editor"), deletePostHandler);
```
**Object-Level Permissions**
```python
# BAD - checks auth but not ownership (any user can edit any document)
@app.put("/documents/{doc_id}")
async def update_document(doc_id: int, payload: UpdateDoc, user=Depends(get_current_user)):
doc = await db.get(Document, doc_id)
doc.content = payload.content
# GOOD - verify ownership or admin role on every mutation
@app.put("/documents/{doc_id}")
async def update_document(doc_id: int, payload: UpdateDoc, user=Depends(get_current_user)):
doc = await db.get(Document, doc_id)
if not doc:
raise HTTPException(status_code=404)
if doc.owner_id != user.id and user.role != Role.admin:
raise HTTPException(status_code=403)
doc.content = payload.content
```
### 6. CORS Configuration
**FastAPI**
```python
# BAD - allows everything
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True,
allow_methods=["*"], allow_headers=["*"])
# GOOD - restrictive CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com", "https://staging.example.com"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
```
**Express**
```typescript
// BAD
app.use(cors({ origin: true, credentials: true }));
// GOOD - explicit allowlist with callback
const ALLOWED_ORIGINS = ["https://app.example.com"];
app.use(cors({
origin: (origin, cb) => {
if (!origin || ALLOWED_ORIGINS.includes(origin)) cb(null, true);
else cb(new Error("Not allowed by CORS"));
},
credentials: true,
methods: ["GET", "POST", "PUT", "DELETE"],
}));
```
### 7. Security Headers
**Express with Helmet**
```typescript
// GOOD - Helmet sets secure defaults for all critical headers
import helmet from "helmet";
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:"],
frameAncestors: ["'none'"],
},
},
hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
}));
```
**FastAPI**
```python
# GOOD - security headers middleware
@app.middleware("http")
async def security_headers(request, call_next):
response = await call_next(request)
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains; preload"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Permissions-Policy"] = "camera=(), microphone=(), geolocation=()"
response.headers["Content-Security-Policy"] = "default-src 'self'; frame-ancestors 'none';"
return response
```
### 8. Secret Management
```python
# BAD - hardcoded secrets
DATABASE_URL = "postgresql://admin:p@ssw0rd@localhost/mydb"
API_KEY = "sk-1234567890abcdef"
JWT_SECRET = "mysecret"
# GOOD - environment variables with validation
import os
def get_required_env(key: str) -> str:
value = os.environ.get(key)
if not value:
raise RuntimeError(f"Required environment variable {key} is not set")
return value
DATABASE_URL = get_required_env("DATABASE_URL")
API_KEY = get_required_env("API_KEY")
JWT_SECRET = get_required_env("JWT_SECRET")
```
**.env and .gitignore**
```bash
# .env (NEVER commit this file)
DATABASE_URL=postgresql://admin:securepass@localhost/mydb
JWT_SECRET=a-very-long-random-string-from-openssl-rand
API_KEY=sk-prod-xxxxxxxxxxxx
```
```gitignore
# .gitignore - always include these
.env
.env.*
!.env.example
*.pem
*.key
credentials.json
```
Commit a `.env.example` with empty values to document required variables without exposing secrets.
### 9. Rate Limiting
**Python (FastAPI with slowapi)**
```python
# GOOD - rate limiting on sensitive endpoints
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
@app.post("/login")
@limiter.limit("5/minute") # brute force protection
async def login(request: Request, credentials: LoginRequest):
...
@app.post("/api/data")
@limiter.limit("100/minute") # general API rate limit
async def get_data(request: Request):
...
```
**Express (express-rate-limit)**
```typescript
// GOOD - tiered rate limiting
import rateLimit from "express-rate-limit";
const generalLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 });
const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5 });
app.use("/api/", generalLimiter);
app.use("/auth/login", authLimiter);
app.use("/auth/register", authLimiter);
```
### 10. Dependency Security
```bash
# Python - audit dependencies
pip install pip-audit
pip-audit # scan for known vulnerabilities
pip-audit --fix # auto-fix where possible
# Node.js - audit dependencies
npm audit # list vulnerabilities
npm audit fix # auto-fix compatible updates
pnpm audit # pnpm equivalent
# Always commit lock files to ensure reproducible builds
# Python: requirements.txt or poetry.lock
# Node.js: package-lock.json, pnpm-lock.yaml, or yarn.lock
```
Run `npm audit --audit-level=high` and `pip-audit --strict` in CI (e.g., GitHub Actions on every PR and weekly schedule). Treat high-severity findings as build failures.
---
## Best Practices
1. **Validate at the boundary, trust nothing inside.** Every piece of user input -- query params, headers, request bodies, file uploads -- must be validated before processing. Use Pydantic or Zod schemas, not manual checks.
2. **Apply the principle of least privilege everywhere.** Default to the most restrictive access. Grant permissions explicitly. Use role-based access control and verify object-level ownership on every mutation.
3. **Never store or log secrets in plain text.** Use environment variables, a secret manager, or encrypted storage. Ensure secrets never appear in logs, error messages, or version control.
4. **Use strong, adaptive password hashing.** Always use argon2 or bcrypt with a sufficient work factor. Never use MD5, SHA-1, or SHA-256 alone for password storage.
5. **Set security headers on every response.** Enable HSTS, CSP, X-Content-Type-Options, X-Frame-Options, and Referrer-Policy. Use Helmet for Express and middleware for FastAPI.
6. **Fail closed, not open.** When authentication or authorization checks encounter errors, deny access by default. Never fall through to an unprotected code path on exception.
7. **Keep dependencies updated and audited.** Run `npm audit` and `pip-audit` in CI pipelines. Pin dependency versions with lock files. Review changelogs before major upgrades.
8. **Enforce rate limiting on all public-facing endpoints.** Apply stricter limits on authentication and password reset endpoints. Use IP-based and account-based limiting together for defense in depth.
---
## Common Pitfalls
1. **Trusting client-side validation alone.** Attackers bypass browser validation trivially. Always re-validate on the server.
2. **Using wildcard CORS with credentials.** `allow_origins=["*"]` with credentials is insecure and browsers reject it. Specify exact origins.
3. **Storing JWTs in localStorage.** Any XSS can steal them. Use httpOnly, secure, sameSite cookies instead.
4. **Returning detailed error messages in production.** Stack traces help attackers. Return generic messages to clients, log details server-side.
5. **Using ORM raw query methods unsafely.** `$queryRawUnsafe` and `text()` with f-strings bypass ORM protections. Audit every raw SQL call.
6. **Checking authentication but not authorization.** "Logged in" does not mean "authorized." Check object-level permissions on every write.
7. **Disabling security in dev and shipping it.** CSP, CORS, HTTPS disabled for convenience can reach production. Use environment-aware config.
8. **Ignoring dependency vulnerabilities.** Known CVEs in transitive deps are a top attack vector. Automate auditing in CI.
---
## Security Review Checklist
- [ ] All user input validated with schema (Pydantic / Zod) before processing
- [ ] No string concatenation or interpolation in SQL queries
- [ ] Passwords hashed with argon2 or bcrypt (never MD5/SHA)
- [ ] JWTs have short expiry, use httpOnly cookies, strong secret from env
- [ ] Authorization checked at object level, not just authentication
- [ ] CORS configured with explicit origin allowlist (no wildcards with credentials)
- [ ] Security headers set: CSP, HSTS, X-Content-Type-Options, X-Frame-Options
- [ ] No secrets hardcoded in source -- all from environment variables
- [ ] .env files listed in .gitignore, .env.example committed
- [ ] Rate limiting applied to login, registration, and password reset endpoints
- [ ] File uploads validated by MIME type, size, and sanitized filename
- [ ] Error responses do not leak stack traces or internal details
- [ ] Dependencies audited with npm audit / pip-audit (no high-severity CVEs)
- [ ] HTTPS enforced in production with HSTS preload
- [ ] No use of eval(), dangerouslySetInnerHTML (without DOMPurify), or innerHTML
---
## Related Skills
- `docker` — Container security hardening
- `defense-in-depth` — Multi-layer security validation
-217
View File
@@ -1,217 +0,0 @@
# Security Headers Reference
Comprehensive reference for HTTP security headers with recommended values and implementation examples.
---
## Header Reference Table
| Header | Purpose | Recommended Value |
|--------|---------|-------------------|
| `Content-Security-Policy` | Prevent XSS, data injection | See detailed section below |
| `Strict-Transport-Security` | Force HTTPS | `max-age=63072000; includeSubDomains; preload` |
| `X-Frame-Options` | Prevent clickjacking | `DENY` or `SAMEORIGIN` |
| `X-Content-Type-Options` | Prevent MIME sniffing | `nosniff` |
| `Referrer-Policy` | Control referer leakage | `strict-origin-when-cross-origin` |
| `Permissions-Policy` | Restrict browser features | See detailed section below |
---
## Content-Security-Policy (CSP)
Controls which resources the browser is allowed to load.
**Starter policy (strict):**
```
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
```
**Key directives:**
| Directive | Controls | Example |
|-----------|----------|---------|
| `default-src` | Fallback for all resource types | `'self'` |
| `script-src` | JavaScript sources | `'self' https://cdn.example.com` |
| `style-src` | CSS sources | `'self' 'unsafe-inline'` |
| `img-src` | Image sources | `'self' data: https:` |
| `connect-src` | Fetch, XHR, WebSocket targets | `'self' https://api.example.com` |
| `frame-ancestors` | Who can embed this page | `'none'` |
| `form-action` | Form submission targets | `'self'` |
## Strict-Transport-Security (HSTS)
Forces browsers to use HTTPS for all future requests to this domain.
```
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
```
- `max-age=63072000` — 2 years (minimum for preload list)
- `includeSubDomains` — apply to all subdomains
- `preload` — opt into browser preload lists
## X-Frame-Options
Prevents the page from being embedded in iframes (clickjacking protection).
```
X-Frame-Options: DENY
```
| Value | Behavior |
|-------|----------|
| `DENY` | Never allow framing |
| `SAMEORIGIN` | Allow framing by same origin only |
Note: `frame-ancestors` in CSP is the modern replacement but set both for backward compatibility.
## X-Content-Type-Options
Prevents browsers from MIME-sniffing the response content type.
```
X-Content-Type-Options: nosniff
```
Always pair with correct `Content-Type` headers on responses.
## Referrer-Policy
Controls how much referrer information is sent with requests.
```
Referrer-Policy: strict-origin-when-cross-origin
```
| Value | Cross-Origin Sends | Same-Origin Sends |
|-------|-------------------|-------------------|
| `no-referrer` | Nothing | Nothing |
| `origin` | Origin only | Origin only |
| `strict-origin-when-cross-origin` | Origin (HTTPS only) | Full URL |
| `same-origin` | Nothing | Full URL |
## Permissions-Policy
Restricts which browser features the page can use.
```
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
```
| Feature | Recommended | Description |
|---------|-------------|-------------|
| `camera` | `()` | Disable camera access |
| `microphone` | `()` | Disable microphone |
| `geolocation` | `()` | Disable location |
| `payment` | `()` | Disable Payment API |
| `usb` | `()` | Disable USB access |
| `fullscreen` | `(self)` | Allow fullscreen for same origin |
---
## Implementation: Python (FastAPI)
```python
from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
app = FastAPI()
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next) -> Response:
response = await call_next(request)
response.headers["Content-Security-Policy"] = (
"default-src 'self'; script-src 'self'; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data: https:; "
"frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
)
response.headers["Strict-Transport-Security"] = (
"max-age=63072000; includeSubDomains; preload"
)
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Permissions-Policy"] = (
"camera=(), microphone=(), geolocation=(), payment=()"
)
return response
app.add_middleware(SecurityHeadersMiddleware)
```
## Implementation: Node.js (Express)
```typescript
import helmet from "helmet";
import express from "express";
const app = express();
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
frameAncestors: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
},
},
strictTransportSecurity: {
maxAge: 63072000,
includeSubDomains: true,
preload: true,
},
frameguard: { action: "deny" },
referrerPolicy: { policy: "strict-origin-when-cross-origin" },
permissionsPolicy: {
features: {
camera: [],
microphone: [],
geolocation: [],
payment: [],
},
},
})
);
```
## Implementation: Next.js
```typescript
// next.config.ts
const securityHeaders = [
{ key: "Content-Security-Policy", value: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none'" },
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=(), payment=()" },
];
export default {
async headers() {
return [{ source: "/(.*)", headers: securityHeaders }];
},
};
```
---
## Verification
```bash
# Check headers on a live site
curl -I https://example.com
# Use securityheaders.com for a grade
# https://securityheaders.com/?q=https://example.com
```
*Source: [MDN HTTP Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers), [OWASP Secure Headers](https://owasp.org/www-project-secure-headers/)*
-200
View File
@@ -1,200 +0,0 @@
#!/usr/bin/env python3
"""Security audit scanner for common vulnerabilities.
Scans source files for hardcoded secrets, eval() usage, SQL string
concatenation, and sensitive data in console output. Outputs JSON.
Usage:
python security-audit.py ./src
python security-audit.py ./src --severity high --format pretty
"""
import argparse
import json
import os
import re
import sys
from dataclasses import asdict, dataclass, field
from pathlib import Path
SCAN_EXTENSIONS = {
".py", ".js", ".ts", ".jsx", ".tsx", ".java", ".go",
".rb", ".php", ".env", ".yaml", ".yml", ".toml", ".json",
}
SKIP_DIRS = {
"node_modules", ".git", "__pycache__", ".venv", "venv",
"dist", "build", ".next", ".nuxt", "vendor",
}
@dataclass
class Finding:
file: str
line: int
rule: str
severity: str
message: str
snippet: str
@dataclass
class AuditReport:
scanned_files: int = 0
findings: list = field(default_factory=list)
summary: dict = field(default_factory=dict)
# --- Detection Rules ---
SECRET_PATTERNS = [
(r'(?i)(api[_-]?key|apikey)\s*[=:]\s*["\'][A-Za-z0-9_\-]{16,}["\']', "Possible API key"),
(r'(?i)(secret|password|passwd|pwd)\s*[=:]\s*["\'][^"\']{8,}["\']', "Possible hardcoded secret"),
(r'(?i)(aws_access_key_id|aws_secret_access_key)\s*[=:]\s*["\'][^"\']+["\']', "AWS credential"),
(r'(?i)bearer\s+[A-Za-z0-9_\-\.]{20,}', "Possible bearer token"),
(r'(?i)(ghp_|gho_|github_pat_)[A-Za-z0-9_]{20,}', "GitHub token"),
(r'(?i)(sk-|pk_live_|pk_test_|sk_live_|sk_test_)[A-Za-z0-9]{20,}', "API secret key"),
(r'-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----', "Private key in source"),
]
EVAL_PATTERNS = [
(r'\beval\s*\(', "eval() usage detected"),
(r'\bexec\s*\(', "exec() usage detected (Python)"),
(r'new\s+Function\s*\(', "new Function() usage (dynamic code)"),
(r'\bchild_process\.exec\s*\(', "child_process.exec (command injection risk)"),
(r'subprocess\.call\s*\([^)]*shell\s*=\s*True', "subprocess with shell=True"),
(r'os\.system\s*\(', "os.system() usage (command injection risk)"),
]
SQL_PATTERNS = [
(r'(?i)(SELECT|INSERT|UPDATE|DELETE|DROP)\s+.*([\+]|\.format\(|f["\']|%\s)', "SQL string concatenation"),
(r'(?i)execute\s*\(\s*f["\']', "SQL f-string in execute()"),
(r'(?i)\.query\s*\(\s*`[^`]*\$\{', "SQL template literal injection"),
(r'(?i)\.raw\s*\(\s*f["\']', "Raw SQL with f-string"),
]
SENSITIVE_LOG_PATTERNS = [
(r'console\.log\s*\(.*(?i)(password|secret|token|key|credential)', "Sensitive data in console.log"),
(r'print\s*\(.*(?i)(password|secret|token|key|credential)', "Sensitive data in print()"),
(r'logger?\.(info|debug|warn)\s*\(.*(?i)(password|secret|token)', "Sensitive data in logger"),
]
RULES = [
("hardcoded-secret", "high", SECRET_PATTERNS),
("dangerous-eval", "high", EVAL_PATTERNS),
("sql-injection", "high", SQL_PATTERNS),
("sensitive-logging", "medium", SENSITIVE_LOG_PATTERNS),
]
def should_scan(path: Path) -> bool:
if path.suffix not in SCAN_EXTENSIONS:
return False
for part in path.parts:
if part in SKIP_DIRS:
return False
return True
def scan_file(filepath: Path) -> list[Finding]:
findings = []
try:
content = filepath.read_text(encoding="utf-8", errors="ignore")
except (OSError, PermissionError):
return findings
lines = content.splitlines()
for line_num, line in enumerate(lines, start=1):
stripped = line.strip()
if stripped.startswith(("#", "//", "*", "/*")):
continue
for rule_name, severity, patterns in RULES:
for pattern, message in patterns:
if re.search(pattern, line):
findings.append(Finding(
file=str(filepath),
line=line_num,
rule=rule_name,
severity=severity,
message=message,
snippet=line.strip()[:120],
))
return findings
def scan_directory(target: Path, severity_filter: str | None = None) -> AuditReport:
report = AuditReport()
severity_order = {"high": 3, "medium": 2, "low": 1}
min_severity = severity_order.get(severity_filter, 0) if severity_filter else 0
for root, dirs, files in os.walk(target):
dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
for fname in files:
fpath = Path(root) / fname
if not should_scan(fpath):
continue
report.scanned_files += 1
for finding in scan_file(fpath):
if severity_order.get(finding.severity, 0) >= min_severity:
report.findings.append(finding)
report.summary = {
"total": len(report.findings),
"high": sum(1 for f in report.findings if f.severity == "high"),
"medium": sum(1 for f in report.findings if f.severity == "medium"),
"low": sum(1 for f in report.findings if f.severity == "low"),
"by_rule": {},
}
for f in report.findings:
report.summary["by_rule"][f.rule] = report.summary["by_rule"].get(f.rule, 0) + 1
return report
def main():
parser = argparse.ArgumentParser(
description="Scan source files for common security issues.",
epilog="Example: python security-audit.py ./src --severity high",
)
parser.add_argument("target", help="Directory or file to scan")
parser.add_argument(
"--severity", choices=["low", "medium", "high"],
help="Minimum severity to report (default: all)",
)
parser.add_argument(
"--format", choices=["json", "pretty"], default="json",
help="Output format (default: json)",
)
args = parser.parse_args()
target = Path(args.target)
if not target.exists():
print(f"Error: {target} does not exist", file=sys.stderr)
sys.exit(1)
report = scan_directory(target, args.severity)
output = {
"scanned_files": report.scanned_files,
"summary": report.summary,
"findings": [asdict(f) for f in report.findings],
}
if args.format == "pretty":
print(f"\nScanned {report.scanned_files} files\n")
print(f"Findings: {report.summary['total']} total "
f"({report.summary['high']} high, {report.summary['medium']} medium)")
print("-" * 60)
for f in report.findings:
print(f"[{f.severity.upper()}] {f.file}:{f.line}")
print(f" Rule: {f.rule}")
print(f" {f.message}")
print(f" > {f.snippet}")
print()
else:
print(json.dumps(output, indent=2))
sys.exit(1 if report.summary.get("high", 0) > 0 else 0)
if __name__ == "__main__":
main()
@@ -1,120 +0,0 @@
# Security Code Review Checklist
**Project**: _______________
**Reviewer**: _______________
**Date**: _______________
**Scope**: _______________
---
## Authentication and Session Management
- [ ] Passwords hashed with bcrypt/argon2 (not MD5/SHA1)
- [ ] Session tokens are cryptographically random
- [ ] Session cookies use `Secure`, `HttpOnly`, `SameSite` flags
- [ ] Session timeout is enforced (idle and absolute)
- [ ] Failed login attempts are rate-limited
- [ ] MFA is available for sensitive accounts
- [ ] Password reset tokens expire and are single-use
## Authorization and Access Control
- [ ] Access denied by default (allowlist approach)
- [ ] Server-side authorization on every request
- [ ] Resource ownership verified before access
- [ ] Role/permission checks cannot be bypassed via direct URL
- [ ] Admin endpoints have separate authentication
- [ ] CORS policy restricts allowed origins
## Input Validation
- [ ] All user input validated server-side
- [ ] Parameterized queries used for all database access
- [ ] No string concatenation in SQL/commands
- [ ] File uploads validated (type, size, content)
- [ ] Path traversal prevented on file operations
- [ ] JSON/XML parsers configured against XXE
## Output Encoding
- [ ] HTML output properly escaped (XSS prevention)
- [ ] Content-Type headers set correctly on all responses
- [ ] API responses do not leak stack traces in production
- [ ] Error messages do not reveal system internals
- [ ] Sensitive data excluded from logs
## Cryptography
- [ ] TLS 1.2+ enforced for all connections
- [ ] Sensitive data encrypted at rest
- [ ] No hardcoded secrets, keys, or passwords in source
- [ ] Secrets loaded from environment variables or vault
- [ ] Strong algorithms used (AES-256, RSA-2048+, SHA-256+)
- [ ] No custom cryptographic implementations
## Security Headers
- [ ] Content-Security-Policy configured
- [ ] Strict-Transport-Security enabled
- [ ] X-Frame-Options set to DENY
- [ ] X-Content-Type-Options set to nosniff
- [ ] Referrer-Policy configured
- [ ] Permissions-Policy restricts unused features
## Dependencies
- [ ] No known vulnerabilities (`npm audit` / `pip audit` clean)
- [ ] Unused dependencies removed
- [ ] Dependencies pinned to specific versions
- [ ] Lock file committed and up to date
## Logging and Monitoring
- [ ] Authentication events logged (success and failure)
- [ ] Authorization failures logged
- [ ] Sensitive data not written to logs
- [ ] Log injection prevented (user input sanitized in logs)
- [ ] Alerts configured for suspicious patterns
## API Security
- [ ] Rate limiting on all public endpoints
- [ ] Request size limits configured
- [ ] API keys/tokens not exposed in URLs
- [ ] Pagination enforced on list endpoints
- [ ] HTTPS required (HTTP redirects or blocks)
## Infrastructure
- [ ] Debug mode disabled in production
- [ ] Default credentials changed
- [ ] Unnecessary ports/services disabled
- [ ] Container runs as non-root user
- [ ] Environment variables not logged at startup
---
## Summary
| Category | Pass | Fail | N/A |
|----------|------|------|-----|
| Authentication | | | |
| Authorization | | | |
| Input Validation | | | |
| Output Encoding | | | |
| Cryptography | | | |
| Security Headers | | | |
| Dependencies | | | |
| Logging | | | |
| API Security | | | |
| Infrastructure | | | |
**Overall Assessment**: [ ] Pass / [ ] Conditional Pass / [ ] Fail
**Notes**:
**Follow-up Actions**:
-116
View File
@@ -1,116 +0,0 @@
---
name: performance-optimization
argument-hint: "[file or function]"
description: >
Use when analyzing or optimizing code performance — including profiling, benchmarking, fixing N+1 queries, reducing bundle size, eliminating memory leaks, or improving algorithm complexity. Trigger for keywords like "slow", "performance", "optimize", "profiling", "memory leak", "bundle size", "N+1", "re-render", "benchmark", "latency", "throughput", or any request to make code faster. Also activate when investigating production performance issues or when code review flags performance concerns.
---
# Performance Optimization
## When to Use
- Profiling slow code to find bottlenecks
- Fixing N+1 query problems
- Reducing JavaScript bundle size
- Eliminating memory leaks
- Improving algorithm complexity
- Benchmarking before/after optimization
- Investigating production latency issues
## When NOT to Use
- Premature optimization — profile first, optimize second
- Caching strategy design — use `caching`
- Database schema/index design — use `databases`
- Code structure improvement — use `refactoring`
---
## Quick Reference
| Topic | Reference | Key content |
|-------|-----------|-------------|
| Profiling tools | `references/profiling.md` | Python (cProfile, py-spy, Scalene) and JS/TS (DevTools, Lighthouse, clinic.js) |
| Anti-patterns | `references/anti-patterns.md` | N+1 queries, unnecessary re-renders, event loop blocking, memory leaks |
---
## Optimization Workflow
1. **Measure first** — profile to find the actual bottleneck
2. **Set a target** — "reduce p95 latency from 500ms to 100ms"
3. **Optimize the hot path** — fix the #1 bottleneck, not everything
4. **Benchmark before/after** — prove the improvement with numbers
5. **Check for regressions** — ensure correctness wasn't sacrificed
---
## Profiling Quick Start
### Python
```bash
# CPU profiling
python -m cProfile -o output.prof script.py
# Visualize: pip install snakeviz && snakeviz output.prof
# Live profiling (attach to running process)
py-spy top --pid 12345
# Line-by-line profiling
kernprof -lv script.py # requires @profile decorator
```
### JavaScript/TypeScript
```bash
# Bundle analysis
npx webpack-bundle-analyzer stats.json
# or: ANALYZE=true next build
# Node.js profiling
node --prof app.js
clinic doctor -- node app.js
# Benchmarking
npx vitest bench
```
---
## Common Anti-Patterns
| Anti-Pattern | Detection | Fix |
|-------------|-----------|-----|
| N+1 queries | `django-debug-toolbar`, `prisma.$on('query')` | `select_related`/`joinedload`/`include` |
| Unnecessary re-renders | React DevTools Profiler | `useMemo`, `useCallback`, `React.memo` |
| Blocking event loop | `clinic doctor`, high event loop lag | `worker_threads`, async variants |
| Memory leaks | Heap snapshots, growing `process.memoryUsage()` | Remove listeners, clear refs, bound caches |
| Unbounded lists | No pagination, full table scans | Cursor pagination, `LIMIT` |
| Heavy imports | Bundle analyzer showing large deps | Tree-shaking, `import { x }`, code splitting |
---
## Best Practices
1. **Profile before optimizing** — intuition about bottlenecks is often wrong.
2. **Optimize the hot path** — 80% of time is spent in 20% of code.
3. **Measure, don't guess** — use benchmarks with statistical significance.
4. **Set clear targets** — "faster" is not measurable; "p95 < 100ms" is.
5. **Avoid premature optimization** — correctness and readability come first.
## Common Pitfalls
1. **Optimizing cold paths** — spending time on code that runs once.
2. **Micro-benchmarking without context** — 10ns vs 20ns doesn't matter if the DB call takes 50ms.
3. **Sacrificing readability** — an unreadable optimization is a future bug.
4. **Caching without invalidation** — stale data is worse than slow data.
5. **Ignoring algorithmic complexity** — no amount of micro-optimization fixes O(n^2) on large inputs.
---
## Related Skills
- `systematic-debugging` — Investigating slow paths with root-cause rigor
- `testing` — Benchmarking and perf regression tests
- `devops` — Deploy-time perf checks
@@ -1,115 +0,0 @@
# Performance Anti-Patterns
## N+1 Queries
**Signal**: Many small queries instead of one batch query.
### SQLAlchemy (Python)
```python
# BAD: N+1 — each user triggers a query for posts
users = session.query(User).all()
for user in users:
print(user.posts) # lazy load, 1 query per user
# GOOD: eager loading
from sqlalchemy.orm import joinedload, selectinload
users = session.query(User).options(selectinload(User.posts)).all()
```
### Prisma (TypeScript)
```typescript
// BAD: N+1
const users = await prisma.user.findMany();
for (const user of users) {
const posts = await prisma.post.findMany({ where: { authorId: user.id } });
}
// GOOD: include
const users = await prisma.user.findMany({ include: { posts: true } });
```
### Django
```python
# BAD
for order in Order.objects.all():
print(order.customer.name) # N+1
# GOOD
for order in Order.objects.select_related('customer').all():
print(order.customer.name) # 1 query with JOIN
```
## Unnecessary Re-renders (React)
**Signal**: Components re-rendering when their data hasn't changed.
```typescript
// BAD: new object created every render
<Child style={{ color: 'red' }} />
// GOOD: stable reference
const style = useMemo(() => ({ color: 'red' }), []);
<Child style={style} />
// BAD: new function every render
<Button onClick={() => handleClick(id)} />
// GOOD: stable callback
const handleClick = useCallback(() => doSomething(id), [id]);
<Button onClick={handleClick} />
```
Detect with: React DevTools Profiler → "Highlight updates when components render"
## Blocking the Event Loop (Node.js)
**Signal**: High event loop lag, slow response times.
```typescript
// BAD: synchronous file read blocks everything
const data = fs.readFileSync('large-file.json');
// GOOD: async
const data = await fs.promises.readFile('large-file.json');
// BAD: CPU-heavy in main thread
const hash = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512');
// GOOD: async or worker_threads
const hash = await new Promise((resolve, reject) => {
crypto.pbkdf2(password, salt, 100000, 64, 'sha512', (err, key) => {
err ? reject(err) : resolve(key);
});
});
```
## Memory Leaks
### Python
- Circular references with `__del__`
- Unclosed file handles / DB connections
- Growing global caches without TTL
- Detect: `objgraph`, `tracemalloc`
### JavaScript
- Detached DOM nodes
- Forgotten event listeners (`addEventListener` without `removeEventListener`)
- Closures capturing large scopes
- Unbounded `Map`/`Set` growth
- Detect: Chrome Heap Snapshots, `process.memoryUsage()`
## Heavy Imports / Bundle Bloat
```typescript
// BAD: imports entire library
import _ from 'lodash';
// GOOD: tree-shakeable import
import { debounce } from 'lodash-es';
// GOOD: native alternative
const debounce = (fn, ms) => { /* 5 lines */ };
```
Replace heavy deps: moment → dayjs, lodash → lodash-es or native, date-fns (tree-shakeable).
Use `React.lazy()` + `Suspense` for route-based code splitting.
@@ -1,109 +0,0 @@
# Profiling Tools Reference
## Python
### cProfile (built-in, function-level)
```bash
python -m cProfile -o output.prof script.py
# Visualize
pip install snakeviz && snakeviz output.prof
```
### py-spy (sampling, production-safe)
```bash
# Top-like view of running process
py-spy top --pid 12345
# Generate flame graph
py-spy record -o profile.svg --pid 12345
```
### line_profiler (line-by-line)
```bash
# Add @profile decorator to target function
kernprof -lv script.py
```
### memory_profiler (memory usage)
```bash
# Add @profile decorator
python -m memory_profiler script.py
# Or use stdlib tracemalloc for snapshot comparison
```
### Scalene (CPU + memory + GPU)
```bash
scalene script.py
# Modern alternative, AI-suggested optimizations
```
## JavaScript / TypeScript
### Chrome DevTools Performance
- Performance tab → Record → interact → Stop
- Flame chart shows main thread activity
- Look for long tasks (>50ms), layout thrashing
### Lighthouse (web vitals)
```bash
npx lighthouse https://localhost:3000 --output=json
# CI integration
npx @lhci/cli autorun
```
### Bundle Analysis
```bash
# Webpack
npx webpack-bundle-analyzer stats.json
# Next.js
ANALYZE=true next build
# Source map explorer
npx source-map-explorer dist/**/*.js
```
### clinic.js (Node.js)
```bash
# Event loop health
clinic doctor -- node app.js
# CPU flame graph
clinic flame -- node app.js
# Async bottlenecks
clinic bubbleprof -- node app.js
```
### Node.js built-in
```bash
node --prof app.js
node --prof-process isolate-*.log > profile.txt
```
## Benchmarking
### Python
```bash
# pytest-benchmark
pytest --benchmark-only
# timeit
python -m timeit -s "setup" "expression"
```
### JavaScript/TypeScript
```typescript
// Vitest bench (built-in)
// my-func.bench.ts
import { bench } from 'vitest';
bench('my function', () => {
myFunction(testData);
});
```
```bash
npx vitest bench
```
-92
View File
@@ -1,92 +0,0 @@
---
name: plan-ceo-review
argument-hint: "[plan-path]"
user-invocable: true
description: >
Use when the user wants strategic/scope review of a written implementation plan. Activate for keywords like "review my plan", "think bigger", "is this ambitious enough", "scope review", "strategy review", "expand scope", "10-star product", "what should we build", "is this worth building at this scope". Reviews a plan doc on 5 dimensions (ambition, problem clarity, wedge focus, demand reality, future-fit), scores 0-10 each, proposes concrete fixes, and applies user-selected fixes to the plan. Dispatches the ceo-reviewer agent for scoring.
---
# Plan CEO Review
## When to Use
- After a plan has been written (e.g., by `writing-plans` or `planner` agent)
- Before implementation begins — to pressure-test scope and ambition
- When the user says the plan "feels small" or "might be too narrow"
- When deciding whether to expand, hold, or reduce scope
## When NOT to Use
- No plan file exists yet — use `writing-plans` first
- Plan has already been implemented — use `requesting-code-review` on the code
- You want architecture review — use `plan-eng-review` instead
---
## Workflow
### Step 1: Resolve the plan path
- If `[plan-path]` argument provided, use it
- Else scan (in order): `docs/claudekit/plans/*.md`, `docs/plans/*.md` (generic fallback), `plan.md` in cwd
- If multiple matches, pick the newest by mtime
- If none found, stop and tell the user to run `/claudekit:writing-plans` first
### Step 2: Dispatch the `ceo-reviewer` agent
Invoke the Agent tool with `subagent_type: "ceo-reviewer"`. Pass a prompt containing:
- The absolute plan path
- The 5 dimensions (the agent already knows them, but re-state for grounding)
- The required output format (the markdown block from the agent's spec)
### Step 3: Present the scorecard
Show the returned CEO Review markdown to the user verbatim.
### Step 4: Single consolidation gate
Use `AskUserQuestion` with the `Recommended fixes` checklist from the scorecard. Multi-select. If the list is empty (no dimension scored <6), skip this step and tell the user "Plan scores well on strategy — no fixes recommended."
### Step 5: Apply selected fixes
For each selected fix, use `Edit` on the plan file. Each fix is either:
- `Replace "<old>" with "<new>"``Edit` with `old_string=<old>`, `new_string=<new>`
- `In section "<heading>", add: <text>``Read` the file, locate the heading, use `Edit` to append `<text>` under it
If a fix is too vague to apply deterministically (fails the concreteness contract), skip it and report it to the user as `Unapplied: <reason>`.
### Step 6: Write the review artifact
Write a copy of the CEO Review to `docs/claudekit/reviews/<plan-basename>-ceo-YYYY-MM-DD.md`. Create the directory if needed. Include an `Applied fixes` and `Skipped fixes` section at the bottom.
---
## Output Format (what the user sees)
```
# CEO Review: <plan-basename>
Overall: N.N/10
[scorecard table]
[critical issues]
[strengths]
> Please select which fixes to apply:
> [AskUserQuestion multi-select]
Applied N fixes to <plan-path>.
Skipped M fixes (reason: too vague / no match).
Review artifact saved: docs/claudekit/reviews/...
```
---
## Related Skills
- `writing-plans` — Produces the plan doc this skill reviews
- `plan-eng-review` — Architecture review (complementary dimension)
- `plan-design-review` — UX/visual review (complementary)
- `plan-devex-review` — DX review (complementary)
- `autoplan` — Runs this skill + the other three plan-reviews in parallel
-63
View File
@@ -1,63 +0,0 @@
---
name: plan-design-review
argument-hint: "[plan-path]"
user-invocable: true
description: >
Use when the user wants a UX/visual design review of a written implementation plan with UI components. Activate for keywords like "review the design plan", "design critique", "is the UX right", "check hierarchy", "visual review of the plan", "does this look generic", "avoid AI slop". Reviews a plan doc on 5 dimensions (information hierarchy, visual consistency, state coverage, accessibility, polish vs AI slop), scores 0-10 each, proposes concrete fixes, and applies user-selected fixes. Dispatches the design-reviewer agent.
---
# Plan DESIGN Review
## When to Use
- Plan includes UI components or user-facing screens
- User wants a designer's-eye critique before implementation
- To catch AI-slop patterns and missing states
## When NOT to Use
- Plan has no UI surface
- You want a live visual audit of shipped UI — (future `design-review` skill in Bundle B will cover that)
- You want architecture review — use `plan-eng-review`
---
## Workflow
### Step 1: Resolve the plan path
Same as other plan-reviews: arg > `docs/claudekit/plans/*` > `docs/plans/*` (generic fallback) > `plan.md`. Newest by mtime.
### Step 2: Dispatch the `design-reviewer` agent
Invoke Agent tool with `subagent_type: "design-reviewer"`. Pass plan path + 5 dimensions (information hierarchy, visual consistency, state coverage, accessibility, polish vs AI slop) + output format.
### Step 3: Present the scorecard
Show the returned DESIGN Review markdown verbatim.
### Step 4: Single consolidation gate
`AskUserQuestion` with `Recommended fixes`. Skip if empty.
### Step 5: Apply selected fixes
For each selected fix, use `Edit` on the plan file. Each fix is either:
- `Replace "<old>" with "<new>"``Edit` with `old_string=<old>`, `new_string=<new>`
- `In section "<heading>", add: <text>``Read` the file, locate the heading, use `Edit` to append `<text>` under it
If a fix is too vague to apply deterministically (fails the concreteness contract), skip it and report to the user as `Unapplied: <reason>`.
### Step 6: Write the review artifact
`docs/claudekit/reviews/<plan-basename>-design-YYYY-MM-DD.md` with Applied/Skipped sections.
---
## Related Skills
- `writing-plans` — Produces the plan
- `plan-ceo-review`, `plan-eng-review`, `plan-devex-review` — Complementary dimensions
- `autoplan` — Runs all four in parallel
- `ui-ux-designer` agent — Generates UI designs (complementary: designer creates, reviewer critiques)
-63
View File
@@ -1,63 +0,0 @@
---
name: plan-devex-review
argument-hint: "[plan-path]"
user-invocable: true
description: >
Use when the user wants a developer-experience review of a written implementation plan for APIs, CLIs, SDKs, libraries, or docs. Activate for keywords like "review the DX", "is this SDK ergonomic", "devex review", "API design review", "time to hello world", "how's the CLI". Reviews a plan doc on 5 dimensions (Time to Hello World, API/CLI ergonomics, error copy, docs structure, magical moments), scores 0-10 each, proposes concrete fixes, and applies user-selected fixes. Dispatches the devex-reviewer agent.
---
# Plan DEVEX Review
## When to Use
- Plan ships a developer-facing surface (API, CLI, SDK, library, docs)
- User wants a DX audit before shipping
- To catch ergonomics regressions, unhelpful error messages, or "reads like generated docs"
## When NOT to Use
- Plan has no developer-facing surface (pure internal backend, consumer UI only)
- You want strategic review — use `plan-ceo-review`
- The product is already shipped — (future `devex-review` in Bundle B will cover live DX audit)
---
## Workflow
### Step 1: Resolve the plan path
Same convention: arg > `docs/claudekit/plans/*` > `docs/plans/*` (generic fallback) > `plan.md`. Newest by mtime.
### Step 2: Dispatch the `devex-reviewer` agent
Invoke Agent tool with `subagent_type: "devex-reviewer"`. Pass plan path + 5 dimensions (Time to Hello World, API/CLI ergonomics, error copy, docs structure, magical moments) + output format.
### Step 3: Present the scorecard
Show returned DEVEX Review markdown verbatim.
### Step 4: Single consolidation gate
`AskUserQuestion` with `Recommended fixes`. Skip if empty.
### Step 5: Apply selected fixes
For each selected fix, use `Edit` on the plan file. Each fix is either:
- `Replace "<old>" with "<new>"``Edit` with `old_string=<old>`, `new_string=<new>`
- `In section "<heading>", add: <text>``Read` the file, locate the heading, use `Edit` to append `<text>` under it
If a fix is too vague to apply deterministically (fails the concreteness contract), skip it and report to the user as `Unapplied: <reason>`.
### Step 6: Write the review artifact
`docs/claudekit/reviews/<plan-basename>-devex-YYYY-MM-DD.md` with Applied/Skipped sections.
---
## Related Skills
- `writing-plans` — Produces the plan
- `plan-ceo-review`, `plan-eng-review`, `plan-design-review` — Complementary
- `autoplan` — Parallel fan-out
- `api-designer` agent — Generates API designs (complementary: designer creates, reviewer critiques)
-78
View File
@@ -1,78 +0,0 @@
---
name: plan-eng-review
argument-hint: "[plan-path]"
user-invocable: true
description: >
Use when the user wants an architecture/execution review of a written implementation plan. Activate for keywords like "review the architecture", "does this design make sense", "lock in the plan", "engineering review", "architecture review", "audit this plan", "pre-implementation review". Reviews a plan doc on 5 dimensions (data flow, failure modes, edge cases & invariants, test matrix, rollback & migration), scores 0-10 each, proposes concrete fixes, and applies user-selected fixes. Dispatches the eng-reviewer agent for scoring.
---
# Plan ENG Review
## When to Use
- After a plan has been written and before coding starts
- When the user wants a tech-lead-style architecture audit
- When the plan may be missing failure modes, edge cases, or rollback strategy
## When NOT to Use
- No plan file exists — use `writing-plans` first
- You want strategic review — use `plan-ceo-review`
- The code exists and you need diff review — use `requesting-code-review`
---
## Workflow
### Step 1: Resolve the plan path
- If `[plan-path]` argument provided, use it
- Else scan: `docs/claudekit/plans/*.md`, `docs/plans/*.md` (generic fallback), `plan.md` in cwd
- Newest by mtime wins
- None found → stop and tell user to run `/claudekit:writing-plans` first
### Step 2: Dispatch the `eng-reviewer` agent
Invoke the Agent tool with `subagent_type: "eng-reviewer"`. Pass:
- The absolute plan path
- The 5 dimensions (data flow, failure modes, edge cases & invariants, test matrix, rollback & migration)
- The required output format
### Step 3: Present the scorecard
Show the returned ENG Review markdown verbatim.
### Step 4: Single consolidation gate
`AskUserQuestion` with the `Recommended fixes` checklist. Skip if empty.
### Step 5: Apply selected fixes
For each selected fix, use `Edit` on the plan file. Each fix is either:
- `Replace "<old>" with "<new>"``Edit` with `old_string=<old>`, `new_string=<new>`
- `In section "<heading>", add: <text>``Read` the file, locate the heading, use `Edit` to append `<text>` under it
If a fix is too vague to apply deterministically (fails the concreteness contract), skip it and report to the user as `Unapplied: <reason>`.
### Step 6: Write the review artifact
Save to `docs/claudekit/reviews/<plan-basename>-eng-YYYY-MM-DD.md` with `Applied fixes` and `Skipped fixes` sections.
---
## Output Format
Identical structure to `plan-ceo-review` but with ENG rubric.
---
## Related Skills
- `writing-plans` — Produces the plan this reviews
- `plan-ceo-review` — Strategic review (complementary)
- `plan-design-review` — UX review (complementary)
- `plan-devex-review` — DX review (complementary)
- `autoplan` — Fan-out all four reviews in parallel
- `planner` agent — Often produces the plan this reviews
+198
View File
@@ -0,0 +1,198 @@
---
name: plan-review-architecture
user-invocable: true
description: >
Architecture-dimension reviewer for written plans. Use when running plan-review
or directly when an architectural review is wanted. Activate for keywords like
"architecture review", "data flow", "failure modes", "rollback", "edge cases",
"test matrix". Scores 5 sub-dimensions 0-10, produces ranked fixes. Always cite
file paths or task numbers from the plan -- never write generic architectural
advice.
---
# Plan Review — Architecture Dimension
## Overview
The architecture-dimension reviewer for `plan-review`. Reads a plan and scores
five concrete sub-dimensions on 0-10: data flow, failure modes, edge cases, test
matrix, and rollback safety. Every score must be paired with a finding citing the
plan task number or section that caused the score. The skill produces a ranked
fix list aligned with `plan-review`'s consolidation step. Used by `plan-review`'s
orchestrator, but invocable directly when only an architectural review is needed.
## When to Use
- Invoked by `plan-review` as one of its two parallel reviewers
- The user wants an architectural pass on a plan without the experience review
- A plan has been edited substantially in architectural areas and needs re-scoring
## When NOT to Use
- The plan is single-task or single-file (architecture review is overkill)
- You haven't read the underlying spec; architecture findings without spec
context produce noise
## Process
### Step 1: Pre-read
**Goal:** Build context before scoring.
**Inputs:** The plan file. Optionally: the spec it's derived from, the relevant
codebase area.
**Actions:**
1. Read the spec (if available) for goals, non-goals, constraints, acceptance
criteria.
2. Read the plan end to end.
3. Run `map-codebase` mentally on the affected area: which files, which entry
points, which downstream services or queues.
**Output:** A short pre-read note: `Plan touches <areas>; primary risks I'll watch
for: <list>`.
### Step 2: Score the five sub-dimensions
**Goal:** Produce 5 scores with cited findings.
**Inputs:** The plan file plus pre-read notes.
**Actions:** For each sub-dimension below, score 0-10 and write at least one
finding. Findings must cite the plan task number or section.
1. **Data flow (0-10)**
- Is the plan explicit about who owns the data at each step?
- Are reads and writes ordered correctly across services?
- Are eventual-consistency boundaries marked?
- Score 10 = data flow is unambiguous from the plan alone.
- Score 5 = a reader has to guess at one or more transitions.
- Score 0 = data flow contradicts itself or the spec.
2. **Failure modes (0-10)**
- For each external call (DB, queue, API), does the plan say what happens on
failure?
- Timeouts named?
- Retry policy specified, including backoff and idempotency?
- Circuit-breaker, fallback, or fail-closed behavior named?
- Score 10 = every external interaction has a named failure path.
- Score 5 = some failure modes addressed, others left to "we'll handle errors."
- Score 0 = the plan assumes the happy path and stops.
3. **Edge cases (0-10)**
- Empty inputs, max-size inputs, unicode, boundary values?
- Concurrent access (race conditions, optimistic locking)?
- Partial failure (one of N writes succeeds)?
- Replays (idempotency on duplicate requests)?
- Score 10 = edge cases enumerated and acceptance criteria cover them.
- Score 5 = some named, others assumed-handled.
- Score 0 = no edge case considered.
4. **Test matrix (0-10)**
- Does each task have a named test command?
- Are unit, integration, and contract tests differentiated where appropriate?
- Are tests authored before or alongside the code (per the project's TDD posture)?
- Are negative tests (invalid input, failure paths) included?
- Score 10 = test coverage maps onto failure modes and edge cases line for line.
- Score 5 = happy-path tests only.
- Score 0 = "tests pass" without naming what tests.
5. **Rollback safety (0-10)**
- For each high-risk task (schema changes, deploy ordering, config flips), is
a rollback procedure named?
- For destructive migrations, is the procedure flagged as `NOT POSSIBLE` and
gated behind a feature flag, dual-write, or backfill?
- Score 10 = every high-risk task has a one-line rollback.
- Score 5 = some rollbacks named, others assumed.
- Score 0 = no rollback considered; destructive change with no kill switch.
### Step 3: Rank findings as fixes
**Goal:** Convert each finding into a concrete fix proposal.
**Inputs:** The findings from Step 2.
**Actions:**
1. For each finding, write a fix in the form: `<task or section> — change
<X> to <Y>` or `Add <Z> to <task or section>`.
2. Rank each fix by impact:
- **Blocker** — without this, the plan is structurally unsafe to execute.
- **Important** — without this, the plan will produce a regrettable result.
- **Nice-to-have** — improves clarity but isn't load-bearing.
3. If a sub-dimension scores ≤4, the gap is almost always a blocker.
**Output:** A ranked list of fixes with cited targets in the plan.
### Step 4: Write the architecture report
**Goal:** Hand `plan-review` a clean, paste-ready report.
**Inputs:** Scores and ranked fixes.
**Actions:**
1. Produce a Markdown block with this structure:
```markdown
## Architecture review
- Data flow: X/10 — <one-line justification>
- Failure modes: X/10 — <one-line justification>
- Edge cases: X/10 — <one-line justification>
- Test matrix: X/10 — <one-line justification>
- Rollback safety: X/10 — <one-line justification>
### Findings
- [Blocker] <finding>; fix: <fix>; cite: <task #>
- [Important] <finding>; fix: <fix>; cite: <task #>
- [Nice-to-have] <finding>; fix: <fix>; cite: <task #>
```
2. Hand back to `plan-review` for consolidation with the experience reviewer.
**Output:** The Markdown block.
## Rationalizations
| Excuse | Why it sounds reasonable | Why it's wrong | What to do instead |
|---|---|---|---|
| "I'll score by gut feel — calibration is a waste of time." | Experienced reviewers do have calibrated guts. | Gut-feel scoring without rubric anchors produces "everything's a 7" output the user cannot act on. The rubric anchors exist so the score communicates *which* gap is open, not just that something feels off. | Use the 0/5/10 anchors above. If a sub-dimension feels like a 7, that's actually a "5 with one gap closed" — name the open gap, score 6 or 7, and write the finding for it. |
| "I'll skip the citations — the user can find the relevant tasks." | Plans are short; finding the cited task is fast. | Findings without citations leave the user to do the matching, and they will skip findings that take work to verify. The citation is the cheapest part of the review for the reviewer and the most expensive part to reconstruct for the consumer. | Cite the task number or plan section in every finding. `Task 4 — failure mode for the cache miss is undefined` not `Cache failure modes are missing`. |
| "Rollback for this is obviously the deploy team's problem." | Some rollbacks are operational, owned by SRE. | "Obviously theirs" is the line you say when you don't know what the rollback is. The author of the change knows what would need to be undone; the deploy team knows how to undo it. The plan needs the *what*, not the *how*. | Even if SRE owns execution, the plan author writes one line: "Rollback: revert <commit>; re-run migration `down`; truncate <table>." If you can't write that line, escalate during review, don't skip during review. |
| "Edge cases score is low because edge cases are uncommon — that's fine." | Some edges genuinely never trigger in production. | "Uncommon" without measurement is a guess. Even uncommon edges hit at production scale (1-in-a-million × 1M req/day = 1/day). Scoring edge cases low because "they're uncommon" is the reviewer flinching from a real gap. | Score the edge case sub-dimension on whether the plan *names* the edges, not on whether you predict they'll trigger. The plan is responsible for surfacing the cases; ops decides which to handle. |
| "Test matrix is the tester's problem, not architecture's." | Test design and architecture are different specialties. | The test matrix is architectural in plans because the tests double as a check that the architecture's failure modes were considered. A plan with rich failure modes and thin tests is internally inconsistent — the tests don't exercise what the architecture promises. | Score the test matrix here. Cite the failure modes from sub-dimension 2 and confirm the test list (sub-dimension 4) covers them. The two scores should track each other; if they don't, that's a finding. |
## Evidence Requirements
| Checkpoint | Required artifact | What "no evidence" looks like |
|---|---|---|
| End of Step 1 | Pre-read note naming areas touched and risks to watch | "I read the plan, looks like a backend change." |
| End of Step 2 | Five scores 0-10 each paired with at least one cited finding | "Looks mostly OK; some gaps." |
| End of Step 3 | Ranked fix list with `[Blocker/Important/Nice]` tags | "There are some things to improve." |
| End of Step 4 | The Markdown block exactly in the format above | A free-form summary the orchestrator has to re-format. |
## Red Flags
- Every score is 8-10. Either the plan is unusually strong (rare) or you're
pattern-matching. Pick the weakest sub-dimension and find at least one finding
worth flagging.
- A finding cites no task number. The reviewer is generating advice, not review.
- Test matrix score is much higher than failure modes score. Tests cover what
isn't an architectural concern, or the architecture has gaps the tests don't
exercise.
- All blockers come from the same sub-dimension. The plan has a concentrated
weakness; consider whether the plan author needs help in that area before more
fixes pile on.
- Rollback safety is 10/10 on a plan with destructive migrations. Verify by
reading the actual rollback lines; "10/10" without specific procedures cited is
a false positive.
## References
- Heroku, *Twelve-Factor App* (12factor.net) — the principles around config,
backing services, and disposability inform sub-dimensions 1 (data flow) and 5
(rollback safety). Cited at the rubric level, not skill level — when reviewing
a plan that violates twelve-factor principles, name which factor.
+186
View File
@@ -0,0 +1,186 @@
---
name: plan-review-experience
user-invocable: true
description: >
Experience-dimension reviewer for written plans (UX + DX). Use when running
plan-review or directly when an experience review is wanted. Activate for
keywords like "UX review", "DX review", "experience review", "error states",
"API ergonomics", "developer experience", "user states". Scores 5 sub-dimensions
0-10 covering both end-user experience (information hierarchy, state coverage,
accessibility) and developer experience (error copy, API/CLI ergonomics, AI-slop
avoidance). Always cite plan task numbers -- never write generic UX/DX advice.
---
# Plan Review — Experience Dimension
## Overview
The experience-dimension reviewer for `plan-review`. Scores five sub-dimensions:
information hierarchy, state coverage, accessibility, DX ergonomics, and AI-slop
avoidance. UX and DX in one pass reflects that "user" and "developer" are both
human consumers of an interface — what differs is the surface (a screen vs an
API/CLI), not the rigor required. The skill produces scored findings paired
with concrete fixes the plan author can apply. Used by `plan-review`'s orchestrator
in parallel with `plan-review-architecture`.
## When to Use
- Invoked by `plan-review` as one of its two parallel reviewers
- The user wants an experience pass on a plan without the architecture review
- A plan has been edited substantially in user-facing or API-facing areas
## When NOT to Use
- The plan has no user-facing or developer-facing surface (pure internal job;
experience review will produce noise)
- The change is single-task and the experience implications are obvious
## Process
### Step 1: Pre-read
**Goal:** Identify the surfaces the plan touches.
**Inputs:** The plan file. Optionally: the spec, existing UI mockups, or API specs.
**Actions:**
1. Read the spec and plan.
2. Identify each user-facing or developer-facing surface in the plan: screens,
modals, error states, API endpoints, CLI flags, config keys, log lines, error
messages, docs.
3. For each, note: who consumes this, in what context, with what level of
familiarity.
**Output:** A surfaces inventory: `<surface> — <consumer> — <context>`.
### Step 2: Score the five sub-dimensions
**Goal:** 5 scores with cited findings.
**Inputs:** The plan and the surfaces inventory.
**Actions:** For each sub-dimension below, score 0-10 and write at least one
finding citing a plan task or section.
1. **Information hierarchy (0-10)**
- For each user-facing surface: does the plan name what's primary, secondary,
tertiary?
- Does the plan say what the user sees first?
- Score 10 = hierarchy is unambiguous from the plan.
- Score 5 = the plan describes what's *on* a screen but not what's emphasized.
- Score 0 = the plan lists features without ordering.
2. **State coverage (0-10)**
- For each surface: does the plan address loading, empty, error, partial,
and success states?
- Are state transitions named (what happens after submit, after timeout)?
- Score 10 = all five state types named per surface.
- Score 5 = success and error covered; loading/empty/partial assumed.
- Score 0 = only the success state is described.
3. **Accessibility (0-10)**
- Keyboard navigation paths named?
- Screen reader semantics specified (ARIA labels, headings)?
- Color/contrast not the only carrier of meaning?
- Localization/RTL support flagged where applicable?
- For non-UI surfaces: is the API/CLI usable by an automation that doesn't
have human eyes (parseable output, exit codes)?
- Score 10 = accessibility is named per surface, not assumed.
- Score 5 = some surfaces named, others assumed-accessible.
- Score 0 = accessibility is unmentioned and the plan visibly precludes it.
4. **DX ergonomics (0-10)**
- Error messages for developers: do they say what went wrong AND what to do?
- API/CLI: are arguments named in the convention of the project?
- Defaults: does the plan name them?
- Time-to-hello-world (TTHW): can a new developer get a working call with one
copy-paste?
- Score 10 = a developer hitting an error knows the next step from the message.
- Score 5 = errors are named but copy is generic ("Internal error").
- Score 0 = errors are uncategorized; debugging requires reading source.
5. **AI-slop avoidance (0-10)**
- Plan or surface copy doesn't use AI-cliché vocabulary (delve, crucial, robust,
comprehensive, multifaceted, leverage, harness, unlock, journey, magical,
seamless, world-class, 10x, pivotal).
- No emoji bullet decoration.
- No "Here's the kicker" or "let me break this down" phrasing in user-facing
text.
- Headings name the thing, not advertise the experience.
- Score 10 = copy reads as if a careful engineer wrote it.
- Score 5 = some slop in user-facing strings, otherwise OK.
- Score 0 = the plan reads like marketing.
### Step 3: Rank findings as fixes
Same procedure as `plan-review-architecture`'s Step 3. Tag each fix as
`[Blocker]`, `[Important]`, or `[Nice-to-have]`. Cite plan tasks.
A blocker in this dimension is typically: a state type entirely missing for a
user surface (e.g., no error state defined for a submit flow), or an accessibility
gap that would fail a basic audit.
### Step 4: Write the experience report
**Goal:** Hand `plan-review` a clean, paste-ready report.
**Actions:** Produce a Markdown block:
```markdown
## Experience review
- Information hierarchy: X/10 — <one-line justification>
- State coverage: X/10 — <one-line justification>
- Accessibility: X/10 — <one-line justification>
- DX ergonomics: X/10 — <one-line justification>
- AI-slop avoidance: X/10 — <one-line justification>
### Findings
- [Blocker] <finding>; fix: <fix>; cite: <task #>
- [Important] <finding>; fix: <fix>; cite: <task #>
- [Nice-to-have] <finding>; fix: <fix>; cite: <task #>
```
**Output:** The Markdown block.
## Rationalizations
| Excuse | Why it sounds reasonable | Why it's wrong | What to do instead |
|---|---|---|---|
| "Loading and empty states aren't worth flagging — they're obvious." | Most components have default loading spinners and empty-message components, so the assumption is "the framework will handle it." | "The framework will handle it" is what produces a UI where the empty state shows "No items found" with no explanation, no call to action, and no path forward. Defaults are not defaults of *quality*; they're defaults of *existence*. The plan needs to name what the empty state says, not just that one will appear. | Score state coverage on whether the plan *says* what each state shows. If the plan is silent, score it 5 or below and write the finding. |
| "Accessibility is something we'll add later." | Some accessibility work genuinely is post-MVP polish. | "Later" almost never happens because by the time the feature ships, the structure that should have been keyboard-navigable, screen-reader-labeled, and color-independent has hardened. Retrofitting accessibility costs 5-10x more than building it right. | Score accessibility on whether the plan *names* it per surface. "Form is keyboard-navigable; submit on Enter; errors announced via aria-live" takes one line in the plan. If the plan is silent, the implementation will be silent too. |
| "AI-slop is just style — it doesn't affect correctness." | Word choice doesn't change whether code works. | Slop in user-facing copy ("our magical, AI-powered…") signals to the user that the team didn't care enough to write the words a careful engineer would. It also signals to the next maintainer that the bar here is low. The bar set by copy carries through to the bar set by everything else. | Flag every slop instance in the plan. The fix is one-word substitutions ("magical" → drop or replace with a concrete verb). The discipline is uniform across the codebase. |
| "DX error messages: 'Internal error' is fine for now." | Internal errors do happen, and exposing internals is a security concern. | "Internal error" in a developer-facing surface is the line that produces support tickets and Stack Overflow questions. The dev needs to know whether to retry, fix their input, contact support, or give up. "Internal error" answers none of those. | Score DX ergonomics on whether each error tells the dev what to do next. Generic copy is a finding. Fix: write the action ("Retry in 30s" / "Check the input format at <doc-link>" / "Contact support@…"). |
| "Information hierarchy is a designer concern, not the plan's." | The plan describes the work; the designer chooses the layout. | This was true when designers and engineers worked sequentially with specs in between. It's no longer true at the speed plans are written and shipped. The plan that doesn't name what's primary on a surface delegates the call to whoever implements first — and they will pick what's easiest, not what's best. | Score hierarchy on whether the plan says what the user sees first per surface. If the plan names "modal with three tabs" without saying which tab is the default, that's a finding. |
## Evidence Requirements
| Checkpoint | Required artifact | What "no evidence" looks like |
|---|---|---|
| End of Step 1 | A surfaces inventory: `<surface> — <consumer> — <context>` | "It's a UI plan." |
| End of Step 2 | Five scores 0-10 each paired with at least one cited finding | "UX is good; DX has some gaps." |
| End of Step 3 | Ranked fix list with `[Blocker/Important/Nice]` tags | "Some things to improve." |
| End of Step 4 | The Markdown block in the exact format above | A free-form summary. |
## Red Flags
- Sub-dimension 5 (AI-slop) scores 10 but the plan contains words like "leverage,"
"seamless," or "delightful." You missed instances; re-read.
- Information hierarchy scores 10 on a plan with no UI mockup, no wireframe, and
no copy specified. You're guessing.
- DX score is 10 on a plan with no API surface. The dimension doesn't apply; mark
it `n/a` rather than scoring 10.
- All findings are AI-slop. The reviewer is fixated on copy and missed the
structural issues.
- The plan has zero error states named and the score is above 5. Re-score.
## References
- Steve Krug, *Don't Make Me Think* (New Riders, 3rd ed. 2014), Chapter 1
"Don't make me think!" — the principle of obviousness operationalizes into the
information-hierarchy and state-coverage sub-dimensions.
- *Web Content Accessibility Guidelines (WCAG) 2.1* (W3C, 2018) — the citation
standard for sub-dimension 3 (accessibility). Use AA as the default conformance
level when scoring.
+183
View File
@@ -0,0 +1,183 @@
---
name: plan-review
user-invocable: true
description: >
Use after a plan exists and before any implementation begins. Activate for
keywords like "review the plan", "check this plan", "is the plan ready",
"plan-review", "pressure-test the plan". Orchestrates two parallel reviewers —
architecture and experience — consolidates their findings into one fix gate, and
applies user-selected fixes to the plan. Always run before non-trivial
implementation -- a plan that survives review costs less to implement than one
that doesn't.
---
# Plan Review
## Overview
The plan-review orchestrator. Dispatches `plan-review-architecture` and
`plan-review-experience` in parallel, collects scored findings from each (0-10
on five sub-dimensions), consolidates them into a single ranked fix list, asks
the user to approve fixes, and applies the approved ones to the plan file. The
skill exists because plans fail in two distinct directions — architectural
soundness (data flow, failure modes, edge cases) and human factors (UX hierarchy,
DX touchpoints, error states) — and a single reviewer rarely covers both well.
Splitting the review into two specialist passes catches more, faster. Used
between `write-plan` and implementation.
## When to Use
- A plan exists at `docs/claudekit/plans/<basename>-plan.md` (or equivalent) and
implementation hasn't started
- A plan has been substantially edited and you want a re-review before merge
- Implementation has started and reviewers have flagged structural issues — back
up to plan-review before continuing
## When NOT to Use
- The plan is for a single-file, single-author change (use code review instead)
- A previous plan-review already passed and the plan hasn't changed since
- You don't have a written plan yet (use `write-plan` first)
## Process
### Step 1: Locate and read the plan
**Goal:** Confirm the plan file exists and meets the minimum bar to be reviewed.
**Inputs:** A path or filename for the plan.
**Actions:**
1. Find the plan file. Default location: `docs/claudekit/plans/`.
2. Read it end to end.
3. Check minimum bar: numbered task list, file paths cited, test commands named,
`Acceptance:` lines present, `## Risks` section present.
4. If the plan fails the minimum bar, return to `write-plan`. Do not run review
on an underdeveloped plan — the reviewers will flag the same things in two
different voices and waste cycles.
**Output:** Confirmation that the plan is review-ready, or a list of return-to-plan
items.
### Step 2: Dispatch the two reviewers in parallel
**Goal:** Get two independent reviews, each scored on 5 sub-dimensions.
**Inputs:** The plan file.
**Actions:**
1. Dispatch `claudekit:architect` agent with the plan file. Sub-dimensions to
score: data flow, failure modes, edge cases, test matrix, rollback safety.
2. Dispatch `claudekit:experience-reviewer` agent with the plan file.
Sub-dimensions to score: information hierarchy, state coverage (loading/empty/
error), accessibility, DX (error copy, API/CLI ergonomics), AI-slop avoidance.
3. Both run in parallel. Wait for both.
4. Each reviewer returns: a 0-10 score per sub-dimension, a list of findings,
and a list of suggested fixes ranked by impact.
**Output:** Two reviewer reports.
### Step 3: Consolidate findings
**Goal:** Merge the two reports into one ranked fix list.
**Inputs:** Both reviewer reports.
**Actions:**
1. Combine the findings into a single list. Tag each finding with its source
(`[arch]` or `[exp]`).
2. De-duplicate. Findings that both reviewers caught get a `[both]` tag and
higher priority — two independent passes flagging the same thing is signal.
3. Rank by impact-on-implementation:
- **Blocker** — the plan cannot be executed without this fix
- **Important** — the plan can execute but will produce a regrettable result
- **Nice-to-have** — improves clarity but isn't load-bearing
4. Write a consolidated review artifact at
`docs/claudekit/reviews/<plan-basename>-review-<YYYY-MM-DD>.md` with sections:
`## Architecture` (with sub-dim scores), `## Experience` (with sub-dim scores),
`## Consolidated Fixes` (the ranked list).
**Output:** A single review artifact with a ranked fix list.
### Step 4: User decision gate
**Goal:** Get the user's call on which fixes to apply.
**Inputs:** The consolidated fix list.
**Actions:**
1. Present the consolidated list to the user via AskUserQuestion. For each
blocker, the option is `Apply` or `Acknowledge and skip with rationale`.
For important and nice-to-have, the option is `Apply` or `Skip`.
2. Skipped blockers must be paired with a one-line rationale that goes into
the review artifact. Skipped important/nice-to-have items don't need
rationale but get logged.
3. The user's choices form the apply-list.
**Output:** A list of fixes to apply, with skip rationales for any skipped
blockers.
### Step 5: Apply fixes to the plan
**Goal:** Edit the plan file to reflect the approved fixes.
**Inputs:** The apply-list.
**Actions:**
1. For each fix, edit the plan file. Use the Edit tool, not by rewriting the
plan from scratch.
2. After each edit, append to the review artifact: `Applied: <fix description>
→ <plan section affected>`.
3. After all fixes are applied, re-read the plan and confirm it's still
internally consistent. Plans can drift during fix application; re-read catches
that.
4. Bump the plan's version stamp at the top: `Reviewed and updated YYYY-MM-DD
via /claudekit:plan-review`.
**Output:** Updated plan file plus updated review artifact. Plan ready to execute.
## Rationalizations
| Excuse | Why it sounds reasonable | Why it's wrong | What to do instead |
|---|---|---|---|
| "Plan-review is overhead — let's just start coding." | Some plans really are simple. Adding ceremony for trivial work is bad. | "Just start coding" is fine for one-file changes; plan-review exists for the cases that aren't. The cost of a 20-minute review against a 4-day implementation is the cheapest insurance you'll buy that week. The cases that *feel* trivial enough to skip review are also the cases where the buried gotcha hits hardest in the third PR. | If your plan has more than 5 tasks or touches more than one module, run plan-review. The 20 minutes saves a round trip later. |
| "I only need one reviewer — architect is enough." | Architectural review is the one most engineers think of when they think "review." | One reviewer covers half the failure modes. The architecture reviewer won't notice that your error copy says "Internal error" instead of telling the user what to do; the experience reviewer won't notice that your DB migration has no rollback. Two independent passes catch ~2x the issues. | Run both reviewers. They're parallel; the wall-clock cost is the slower of the two, not the sum. |
| "I'll skip the blockers I disagree with — they don't apply here." | Sometimes reviewers really are wrong, and an author's domain knowledge can override review. | Skipping a blocker silently is how plan reviews become advisory. The discipline is: skip is fine, but the rationale gets written down in the review artifact. If you can't write a one-line rationale, you don't disagree, you're rationalizing. | Apply Step 4's rule: every skipped blocker gets a one-line rationale. The rationale is the receipt for your choice. Reviewers reading the plan downstream will see the skip and the reason, not just the absence. |
| "I'll fix the plan in my head and not bother editing the file." | Mental updates feel faster than file edits. | The plan you implement against is the plan in the file, not the one in your head. The mental version drifts during the days between review and implementation. The teammate who picks up a task sees the unfixed version and implements the unfixed plan. | Edit the file. Use the Edit tool, not "I'll rewrite it cleanly." Each change is small; the cumulative edit takes minutes. |
| "I'll re-read the plan after applying fixes — but I'm sure it's consistent." | After 5 surgical edits, "I'm sure it's still consistent" is a comfortable belief. | Surgical edits drift. A fix that retitles task 4 may leave a `Blocked by: Task 4` reference dangling somewhere. A fix that splits a task into two may leave the numbering inconsistent. The drift is invisible to the author but obvious on a fresh read. | After Step 5's edits are applied, re-read the plan top to bottom. Catch the dangling references before the implementer does. |
## Evidence Requirements
| Checkpoint | Required artifact | What "no evidence" looks like |
|---|---|---|
| End of Step 1 | Confirmation note or list of return-to-plan items | "Plan looks fine to me." |
| End of Step 2 | Two reviewer reports, each with 0-10 scores per sub-dim | "Reviewers said it's mostly OK." |
| End of Step 3 | Review artifact at `docs/claudekit/reviews/<plan>-<date>.md` with consolidated ranked fixes | "I'll keep the findings in my head." |
| End of Step 4 | A list of `Apply` / `Skip` decisions; skipped blockers each have a rationale | "I picked the ones I felt good about." |
| End of Step 5 | Plan file updated with each approved fix; review artifact appended with `Applied:` lines | "I made the changes; should be good." |
## Red Flags
- Both reviewers score every sub-dimension 9-10. Either the plan is unusually
good (rare) or the reviewers are pattern-matching (common). Re-dispatch with
more pressure.
- One reviewer scores everything 9-10 and the other scores everything 4-5. The
reviewers diverge wildly; read both reports yourself before consolidating.
- More than 10 blockers. The plan needs to be rewritten, not patched.
- A blocker's "fix" is a sentence-level edit. Fixes that small often mean the
reviewer was nitpicking. Demote to "important" or "nice-to-have."
- The user skips every blocker with rationale. Either the plan was reviewed by
the wrong reviewers (mismatch in expertise) or the user is skipping discipline.
Stop and check.
## References
- *Software Engineering at Google*, Wright et al. (O'Reilly, 2020), Chapter 9
"Code Review" — the case that review is most effective when reviewers cover
distinct dimensions, not duplicated coverage. The two-reviewer split (architecture
vs experience) operationalizes that principle for plan review.
-422
View File
@@ -1,422 +0,0 @@
---
name: playwright
description: Use when writing, debugging, or configuring E2E tests with Playwright. Trigger for any mention of end-to-end testing, browser automation, page objects, visual regression, storageState auth, playwright.config, or cross-browser testing. Also use when setting up E2E in CI, testing critical user flows, or debugging flaky browser tests.
---
# Playwright E2E Testing
## Overview
The definitive E2E testing reference for web apps built with Next.js, FastAPI, Django, NestJS, Express, and React. Covers test structure, locator strategy, authentication reuse, API mocking, visual regression, accessibility, CI sharding, and framework-specific setup.
## When to Use
- Testing critical user flows end-to-end (login, checkout, onboarding)
- Cross-browser testing (Chromium, Firefox, WebKit)
- Visual regression testing with `toHaveScreenshot()`
- Accessibility auditing with `@axe-core/playwright`
- Testing Server Components, SSR pages, or full-stack flows
- Mobile/responsive testing via device emulation
## When NOT to Use
- **Unit testing** isolated functions — use `pytest` or `vitest`
- **Component testing** React components in isolation — use `vitest` + Testing Library (faster feedback loop)
- **API-only testing** with no browser interaction — use `httpx` / `supertest` directly
- **Load/performance testing** — use k6, Artillery, or Locust
---
## Quick Reference
| I need... | Go to |
|-----------|-------|
| Production-grade config to copy | [templates/playwright.config.ts](templates/playwright.config.ts) |
| Page Object, auth, mocking patterns | [references/e2e-patterns.md](references/e2e-patterns.md) |
| Locator strategy | § Locators below |
| Auth reuse with storageState | § Authentication below |
| CI setup (GitHub Actions + sharding) | § CI Integration below |
| Framework-specific webServer | § Framework Integration below |
---
## Core Patterns
### Test Structure
```typescript
import { test, expect } from '@playwright/test';
test.describe('Checkout flow', () => {
test('guest can complete purchase', async ({ page }) => {
await page.goto('/products/widget-pro');
await page.getByRole('button', { name: 'Add to cart' }).click();
await page.getByRole('link', { name: 'Cart' }).click();
await page.getByRole('button', { name: 'Checkout' }).click();
await page.getByLabel('Email').fill('guest@example.com');
await page.getByRole('button', { name: 'Place order' }).click();
await expect(page.getByText('Order confirmed')).toBeVisible();
});
});
```
### Locators — the priority order
Always prefer **role-based and user-visible locators**. They survive refactors and match how users interact with the page.
| Priority | Locator | When |
|----------|---------|------|
| 1 | `getByRole('button', { name: '...' })` | Interactive elements with accessible names |
| 2 | `getByLabel('...')` | Form fields with `<label>` |
| 3 | `getByText('...')` | Static visible text |
| 4 | `getByPlaceholder('...')` | Inputs without labels (fix the label instead) |
| 5 | `getByTestId('...')` | Last resort — when no semantic locator works |
**Never use:** `page.locator('.css-class')`, `page.locator('#id')`, XPath. These break on every styling change.
### Assertions
```typescript
// Visibility
await expect(page.getByText('Welcome')).toBeVisible();
await expect(page.getByRole('alert')).not.toBeVisible();
// Content
await expect(page.getByRole('heading')).toHaveText('Dashboard');
await expect(page.getByRole('table')).toContainText('usr_abc123');
// Navigation
await expect(page).toHaveURL('/dashboard');
await expect(page).toHaveTitle('Dashboard | Acme');
// Count
await expect(page.getByRole('listitem')).toHaveCount(5);
// Attribute / state
await expect(page.getByRole('button', { name: 'Submit' })).toBeEnabled();
await expect(page.getByRole('checkbox')).toBeChecked();
```
All `expect()` calls **auto-retry** until the timeout (default 5s). No `waitForSelector` needed.
### Fixtures
Extend `test` to share setup logic without inheritance chains.
```typescript
// fixtures.ts
import { test as base, expect } from '@playwright/test';
type Fixtures = {
adminPage: Page;
};
export const test = base.extend<Fixtures>({
adminPage: async ({ browser }, use) => {
const context = await browser.newContext({
storageState: 'e2e/.auth/admin.json',
});
const page = await context.newPage();
await use(page);
await context.close();
},
});
export { expect };
```
```typescript
// admin.spec.ts
import { test, expect } from './fixtures';
test('admin can view users', async ({ adminPage }) => {
await adminPage.goto('/admin/users');
await expect(adminPage.getByRole('table')).toBeVisible();
});
```
---
## Authentication
Use **`storageState`** to log in once in `globalSetup` and reuse across all tests. Eliminates login page interaction from every test.
```typescript
// e2e/global-setup.ts
import { chromium, FullConfig } from '@playwright/test';
async function globalSetup(config: FullConfig) {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('http://localhost:3000/login');
await page.getByLabel('Email').fill('admin@example.com');
await page.getByLabel('Password').fill('test-password');
await page.getByRole('button', { name: 'Sign in' }).click();
await page.waitForURL('/dashboard');
await page.context().storageState({ path: 'e2e/.auth/admin.json' });
await browser.close();
}
export default globalSetup;
```
```typescript
// playwright.config.ts
export default defineConfig({
globalSetup: './e2e/global-setup.ts',
projects: [
{ name: 'authenticated', use: { storageState: 'e2e/.auth/admin.json' } },
{ name: 'guest', use: { storageState: undefined } },
],
});
```
**Multiple roles:** create separate storage state files per role (`admin.json`, `member.json`, `guest`) and use Playwright projects or fixtures to select which role each test suite uses.
---
## API Mocking
Use `page.route()` to intercept network requests. Prefer this over MSW for E2E — it runs at the browser level and doesn't require service worker setup.
```typescript
test('shows error on API failure', async ({ page }) => {
await page.route('**/api/v1/users', (route) =>
route.fulfill({
status: 500,
contentType: 'application/problem+json',
body: JSON.stringify({
type: 'https://api.example.com/problems/internal-error',
title: 'Internal server error',
status: 500,
}),
}),
);
await page.goto('/users');
await expect(page.getByRole('alert')).toContainText('Something went wrong');
});
```
**When to mock vs use real backend:**
- **Mock:** error paths, edge cases, third-party integrations, rate-limit scenarios
- **Real backend:** happy-path smoke tests, data integrity flows, auth flows
---
## Framework Integration
### Next.js
```typescript
// playwright.config.ts
export default defineConfig({
webServer: {
command: 'pnpm dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
use: { baseURL: 'http://localhost:3000' },
});
```
For App Router with Server Components — test the rendered output, not the server component directly. Playwright sees the final HTML the browser receives.
### FastAPI / Django (Python backends)
```typescript
// playwright.config.ts
export default defineConfig({
webServer: [
{
command: 'uvicorn app.main:app --port 8000',
url: 'http://localhost:8000/health',
reuseExistingServer: !process.env.CI,
timeout: 30_000,
},
{
command: 'pnpm dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
],
use: { baseURL: 'http://localhost:3000' },
});
```
`webServer` accepts an array — spin up both backend and frontend in one config.
### NestJS / Express
Same pattern as FastAPI — use `webServer` with the backend's start command (`nest start --watch` or `node dist/main.js`). Point the health check URL at the backend's `/health` endpoint.
---
## CI Integration (GitHub Actions)
```yaml
# .github/workflows/e2e.yml
name: E2E Tests
on:
pull_request:
push:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1/4, 2/4, 3/4, 4/4]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: pnpm install
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm exec playwright test --shard=${{ matrix.shard }}
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report-${{ strategy.job-index }}
path: playwright-report/
retention-days: 7
- uses: actions/upload-artifact@v4
if: failure()
with:
name: test-traces-${{ strategy.job-index }}
path: test-results/
retention-days: 3
```
**Sharding** splits tests across `N` parallel runners. Use `fail-fast: false` so one shard failure doesn't kill the others.
**Artifacts:** always upload `playwright-report/` (HTML report) and `test-results/` on failure (traces for debugging).
---
## Accessibility Testing
```typescript
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('homepage has no a11y violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
.analyze();
expect(results.violations).toEqual([]);
});
```
Run accessibility audits on every critical page. Integrate into the main E2E suite — don't create a separate "a11y suite" that gets ignored. Use `.withTags()` to target specific WCAG levels.
---
## Visual Regression
```typescript
test('dashboard matches screenshot', async ({ page }) => {
await page.goto('/dashboard');
// Wait for dynamic content to settle
await expect(page.getByRole('table')).toBeVisible();
await expect(page).toHaveScreenshot('dashboard.png', {
maxDiffPixelRatio: 0.01,
animations: 'disabled',
mask: [page.getByTestId('timestamp')],
});
});
```
- **`animations: 'disabled'`** — prevents CSS/JS animation flicker from causing false diffs
- **`mask`** — hides dynamic content (timestamps, avatars, random IDs) that changes between runs
- **`maxDiffPixelRatio`** — allows minor anti-aliasing differences across environments
Update baselines: `pnpm exec playwright test --update-snapshots`
For team-scale visual regression with review UIs, pair with **Argos**, **Percy**, or **Chromatic**.
---
## Debugging
| Situation | Tool |
|-----------|------|
| Writing tests | `npx playwright test --ui` (interactive test explorer) |
| Test just failed in CI | Download `test-results/` artifact → `npx playwright show-trace trace.zip` |
| Flaky test | `npx playwright test --repeat-each=10` to reproduce |
| Step-by-step inspection | `await page.pause()` in code → debugger opens |
| Generate test from actions | `npx playwright codegen http://localhost:3000` |
**Trace-on-first-retry** — the most cost-effective trace strategy for CI:
```typescript
// playwright.config.ts
use: {
trace: 'on-first-retry',
}
```
Records a trace only when a test fails and retries. You get debugging info without the storage cost of tracing every test.
---
## File Organization
```
e2e/
├── playwright.config.ts
├── global-setup.ts
├── fixtures.ts # Shared custom fixtures
├── .auth/ # storageState files (gitignored)
│ ├── admin.json
│ └── member.json
├── pages/ # Page objects (if used)
│ ├── login.page.ts
│ └── dashboard.page.ts
├── specs/ # Test files
│ ├── auth.spec.ts
│ ├── checkout.spec.ts
│ └── dashboard.spec.ts
└── helpers/ # Shared utilities
└── api.ts # API helpers for seeding data
```
Keep E2E tests in a top-level `e2e/` directory, separate from unit/integration tests. This keeps `vitest` and `playwright` from interfering with each other's config/discovery.
---
## Common Pitfalls
1. **`page.waitForTimeout()`** — never use hard waits. Use `expect()` auto-retry or `page.waitForResponse()` instead. Hard waits are the #1 source of flaky tests.
2. **CSS/XPath selectors** — break on every refactor. Use role/label/text locators. If you can't find a semantic locator, add a `data-testid` attribute (and fix the accessibility).
3. **Test interdependence** — tests that share state or must run in order. Every test should work in isolation. Use `storageState` + API calls to seed data, not prior tests.
4. **Testing implementation details** — checking CSS classes, DOM structure, or internal state. Test what the user sees and does.
5. **Running all browsers in CI** — run Chromium-only in CI by default (covers ~95% of bugs). Run multi-browser on a nightly schedule, not on every PR.
6. **Forgetting `--with-deps` in CI**`playwright install` without `--with-deps` skips system dependencies (fonts, libs) and causes cryptic failures.
7. **No trace on failure** — without `trace: 'on-first-retry'` and artifact upload, CI failures are impossible to debug remotely.
8. **Giant spec files** — split by feature, not by page. `checkout.spec.ts`, `auth.spec.ts`, `search.spec.ts` — each focused on one flow.
9. **Mocking everything** — E2E tests that mock the entire backend aren't E2E tests. Mock only third-party services and error scenarios; let happy paths hit the real stack.
10. **No visual regression baseline management** — screenshots checked into git without review. Use `--update-snapshots` deliberately, review diffs in PRs.
---
## Related Skills
- `vitest` — unit/integration testing for TypeScript/JavaScript (complement to E2E)
- `pytest` — unit/integration testing for Python
- `testing-anti-patterns` — patterns that make tests unreliable (applies to E2E too)
- `test-driven-development` — TDD methodology (use Playwright for the "integration test" step)
- `github-actions` — CI/CD pipeline configuration for running E2E
@@ -1,364 +0,0 @@
# E2E Testing Patterns
Deep-dive patterns for Playwright E2E tests. The main SKILL.md covers the essentials; this reference covers scaling patterns, data management, and anti-flake strategies.
---
## Page Object Model (Scaling Pattern)
Use Page Objects when a suite grows beyond ~20 tests and multiple specs interact with the same pages. Keep them thin — locators and actions only, no assertions.
```typescript
// e2e/pages/login.page.ts
import { type Page, type Locator } from '@playwright/test';
export class LoginPage {
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorAlert: Locator;
constructor(private readonly page: Page) {
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: 'Sign in' });
this.errorAlert = page.getByRole('alert');
}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
}
```
```typescript
// e2e/specs/auth.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
test('valid credentials redirect to dashboard', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin@example.com', 'test-password');
await expect(page).toHaveURL('/dashboard');
});
test('invalid credentials show error', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin@example.com', 'wrong');
await expect(loginPage.errorAlert).toContainText('Invalid credentials');
});
```
**When to use Page Objects vs inline locators:**
- **< 20 tests:** inline locators in each spec (simpler, less indirection)
- **20-50 tests:** locator helper functions or fixtures
- **50+ tests:** full Page Object Model with fixtures for injection
---
## Test Data Management
### API-based seeding (recommended)
Seed data via API calls in fixtures or `beforeAll`, not through the UI.
```typescript
// e2e/helpers/api.ts
export async function createTestUser(request: APIRequestContext) {
const response = await request.post('/api/v1/users', {
data: {
email: `test-${Date.now()}@example.com`,
name: 'Test User',
role: 'member',
},
headers: { Authorization: `Bearer ${process.env.TEST_API_TOKEN}` },
});
return response.json();
}
export async function deleteTestUser(request: APIRequestContext, userId: string) {
await request.delete(`/api/v1/users/${userId}`, {
headers: { Authorization: `Bearer ${process.env.TEST_API_TOKEN}` },
});
}
```
```typescript
// e2e/specs/user-management.spec.ts
import { test, expect } from '@playwright/test';
import { createTestUser, deleteTestUser } from '../helpers/api';
test.describe('User management', () => {
let testUser: { id: string; email: string };
test.beforeAll(async ({ request }) => {
testUser = await createTestUser(request);
});
test.afterAll(async ({ request }) => {
await deleteTestUser(request, testUser.id);
});
test('user appears in list', async ({ page }) => {
await page.goto('/admin/users');
await expect(page.getByText(testUser.email)).toBeVisible();
});
});
```
### Database seeding (alternative)
For complex data, seed directly via a test database. Use `globalSetup` to reset the DB and `beforeAll` per suite for specific records.
```typescript
// e2e/global-setup.ts (addition)
import { execSync } from 'child_process';
async function globalSetup() {
// Reset test database
execSync('pnpm db:reset --force', { env: { ...process.env, DATABASE_URL: process.env.TEST_DATABASE_URL } });
execSync('pnpm db:seed', { env: { ...process.env, DATABASE_URL: process.env.TEST_DATABASE_URL } });
// ... auth setup ...
}
```
---
## Anti-Flake Strategies
### Disable animations globally
```typescript
// e2e/fixtures.ts
import { test as base } from '@playwright/test';
export const test = base.extend({
page: async ({ page }, use) => {
await page.addStyleTag({
content: `
*, *::before, *::after {
animation-duration: 0s !important;
animation-delay: 0s !important;
transition-duration: 0s !important;
transition-delay: 0s !important;
}
`,
});
await use(page);
},
});
```
### Wait for network idle after navigation
```typescript
test('dashboard loads fully', async ({ page }) => {
await page.goto('/dashboard');
// Wait for the specific content, not generic network idle
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page.getByRole('table')).toBeVisible();
});
```
**Never use `page.waitForLoadState('networkidle')`** for SPAs — it fires prematurely when the initial HTML loads but React hasn't rendered yet. Wait for the specific element you care about.
### Retry flaky assertions with custom timeout
```typescript
// For a known-slow operation
await expect(page.getByText('Report generated')).toBeVisible({ timeout: 30_000 });
```
### Isolate test state with fresh contexts
```typescript
test.describe('shopping cart', () => {
test.use({ storageState: undefined }); // Fresh guest for each test
test('add item to cart', async ({ page }) => {
// This test starts with an empty cart every time
});
});
```
---
## Multi-Role Testing
Test different user roles in separate projects or fixtures.
```typescript
// playwright.config.ts
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'admin',
use: { storageState: 'e2e/.auth/admin.json' },
dependencies: ['setup'],
testMatch: /.*\.admin\.spec\.ts/,
},
{
name: 'member',
use: { storageState: 'e2e/.auth/member.json' },
dependencies: ['setup'],
testMatch: /.*\.member\.spec\.ts/,
},
{
name: 'guest',
testMatch: /.*\.guest\.spec\.ts/,
},
],
```
Or use fixtures for per-test role selection:
```typescript
// e2e/fixtures.ts
type Accounts = {
adminPage: Page;
memberPage: Page;
};
export const test = base.extend<Accounts>({
adminPage: async ({ browser }, use) => {
const ctx = await browser.newContext({ storageState: 'e2e/.auth/admin.json' });
await use(await ctx.newPage());
await ctx.close();
},
memberPage: async ({ browser }, use) => {
const ctx = await browser.newContext({ storageState: 'e2e/.auth/member.json' });
await use(await ctx.newPage());
await ctx.close();
},
});
```
---
## Network Interception Patterns
### Wait for a specific API response before asserting
```typescript
test('submitting form shows success', async ({ page }) => {
await page.goto('/settings');
const responsePromise = page.waitForResponse(
(resp) => resp.url().includes('/api/v1/settings') && resp.status() === 200,
);
await page.getByRole('button', { name: 'Save' }).click();
await responsePromise;
await expect(page.getByText('Settings saved')).toBeVisible();
});
```
### Mock a third-party service
```typescript
test('shows map with mocked geocoding', async ({ page }) => {
await page.route('**/maps.googleapis.com/**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
results: [{ geometry: { location: { lat: 37.7749, lng: -122.4194 } } }],
}),
}),
);
await page.goto('/locations/new');
await page.getByLabel('Address').fill('123 Main St');
await page.getByRole('button', { name: 'Lookup' }).click();
await expect(page.getByTestId('map-marker')).toBeVisible();
});
```
### Simulate slow network
```typescript
test('shows loading state on slow network', async ({ page, context }) => {
await context.route('**/api/**', async (route) => {
await new Promise((resolve) => setTimeout(resolve, 3000));
await route.continue();
});
await page.goto('/dashboard');
await expect(page.getByRole('progressbar')).toBeVisible();
});
```
---
## Accessibility Patterns
### Scan all critical pages in a single test file
```typescript
// e2e/specs/a11y.spec.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
const pages = ['/', '/login', '/dashboard', '/settings', '/users'];
for (const path of pages) {
test(`${path} has no critical a11y violations`, async ({ page }) => {
await page.goto(path);
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.exclude('.third-party-widget')
.analyze();
expect(results.violations.filter((v) => v.impact === 'critical')).toEqual([]);
});
}
```
### Assert specific a11y rules
```typescript
test('form has proper labels', async ({ page }) => {
await page.goto('/signup');
const results = await new AxeBuilder({ page })
.include('form')
.withRules(['label', 'input-button-name'])
.analyze();
expect(results.violations).toEqual([]);
});
```
---
## Debugging Checklist
When a test fails in CI:
1. **Download the trace artifact** from GitHub Actions
2. **Open with:** `npx playwright show-trace trace.zip`
3. **Check the timeline:** click through each action to see DOM snapshots
4. **Check the console tab:** look for JS errors or failed requests
5. **Check the network tab:** did an API call fail or return unexpected data?
6. **If flaky:** run locally with `npx playwright test path/to/test --repeat-each=20`
7. **If environment-specific:** compare screenshots from CI vs local
8. **If timing-related:** replace `waitForTimeout` with `expect().toBeVisible()` or `waitForResponse()`
---
## Related
- [templates/playwright.config.ts](../templates/playwright.config.ts) — starter config
- [Playwright official docs](https://playwright.dev/docs/intro)
- [Playwright best practices](https://playwright.dev/docs/best-practices)
@@ -1,102 +0,0 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Production-grade Playwright config.
*
* Includes: multi-browser projects, mobile emulation, auth via storageState,
* trace-on-first-retry, CI-aware retries, webServer auto-start, and sharding.
*
* Copy to your project root and customize baseURL, webServer command, and
* storageState paths.
*/
export default defineConfig({
testDir: './e2e/specs',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI
? [['html'], ['github'], ['json', { outputFile: 'e2e/results.json' }]]
: [['html']],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
// --- Auth setup (runs first) ---
{
name: 'setup',
testMatch: /.*\.setup\.ts/,
},
// --- Desktop browsers ---
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'e2e/.auth/user.json',
},
dependencies: ['setup'],
},
// Uncomment for multi-browser (nightly or pre-release, not every PR):
// {
// name: 'firefox',
// use: {
// ...devices['Desktop Firefox'],
// storageState: 'e2e/.auth/user.json',
// },
// dependencies: ['setup'],
// },
// {
// name: 'webkit',
// use: {
// ...devices['Desktop Safari'],
// storageState: 'e2e/.auth/user.json',
// },
// dependencies: ['setup'],
// },
// --- Mobile emulation ---
// {
// name: 'mobile-chrome',
// use: {
// ...devices['Pixel 7'],
// storageState: 'e2e/.auth/user.json',
// },
// dependencies: ['setup'],
// },
// {
// name: 'mobile-safari',
// use: {
// ...devices['iPhone 14'],
// storageState: 'e2e/.auth/user.json',
// },
// dependencies: ['setup'],
// },
// --- Guest (unauthenticated) tests ---
{
name: 'guest',
use: {
...devices['Desktop Chrome'],
},
testMatch: /.*\.guest\.spec\.ts/,
},
],
// --- Auto-start the dev server ---
webServer: {
command: 'pnpm dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
// --- Global setup for auth ---
globalSetup: './e2e/global-setup.ts',
});
-331
View File
@@ -1,331 +0,0 @@
---
name: receiving-code-review
description: >
Use when code review feedback is received, whether from human reviewers, automated tools, or PR comments. Use when processing review comments, handling review rejections, iterating on feedback cycles, or deciding how to prioritize critical vs minor issues. Activate aggressively any time review feedback arrives -- categorize, prioritize, fix critical issues first, and re-request review with a clear summary of changes made.
---
# Receiving Code Review
## When to Use
- After receiving review feedback
- Processing automated review results
- Handling reviewer comments on PRs
- Iterating after code review rejection
## When NOT to Use
- Self-review of your own code where an independent perspective is what you actually need
- Initial implementation before any review has been requested or received
- Design or brainstorming phase where feedback is about ideas, not code
---
## Feedback Categories
### Critical Issues
**Definition**: Must fix before proceeding. Security vulnerabilities, data loss risks, broken functionality.
```markdown
Examples:
- SQL injection vulnerability
- Unhandled null pointer
- Data corruption possibility
- Authentication bypass
```
**Response**: Fix immediately. Do not proceed until resolved.
### Important Issues
**Definition**: Should fix before proceeding. Code quality, maintainability, potential bugs.
```markdown
Examples:
- Missing error handling
- Inefficient algorithm
- Poor naming
- Missing tests for edge cases
```
**Response**: Fix before merging. May defer to follow-up if blocking.
### Minor Issues
**Definition**: Can fix later. Style preferences, optional improvements.
```markdown
Examples:
- Variable naming suggestions
- Comment improvements
- Minor refactoring opportunities
- Documentation polish
```
**Response**: Note for later. Can merge without addressing.
---
## Processing Workflow
### Step 1: Categorize All Feedback
```markdown
## Review Feedback
### Critical (Must Fix)
1. Line 45: SQL query vulnerable to injection
2. Line 89: User data exposed in logs
### Important (Should Fix)
1. Line 23: Missing null check
2. Line 67: Test doesn't cover error path
### Minor (Can Defer)
1. Line 12: Consider renaming 'x' to 'count'
2. Line 34: Could extract to helper function
```
### Step 2: Fix Critical Issues First
```markdown
Addressing critical issue 1:
- File: src/db/queries.ts:45
- Issue: SQL injection vulnerability
- Fix: Use parameterized query
- Verification: Tested with malicious input
```
### Step 3: Fix Important Issues
```markdown
Addressing important issue 1:
- File: src/services/user.ts:23
- Issue: Missing null check
- Fix: Added guard clause
- Verification: Test added for null case
```
### Step 4: Note Minor Issues
```markdown
Deferred for follow-up:
- Line 12: Variable rename (tracked in TODO)
- Line 34: Extract helper (low priority)
```
### Step 5: Request Re-Review
After fixes applied, request re-review with:
```markdown
## Re-Review Request
### Fixed Issues
- [x] SQL injection (line 45) - Now uses parameterized query
- [x] Data exposure (line 89) - Removed user data from logs
- [x] Null check (line 23) - Added guard clause
- [x] Test coverage (line 67) - Added error path test
### Deferred (Minor)
- Variable rename (line 12) - Will address in cleanup PR
### Changes Since Last Review
- 4 files modified
- 2 tests added
- All previous feedback addressed
```
---
## Handling Disagreements
### When You Disagree with Feedback
```markdown
1. Don't dismiss immediately
2. Consider the reviewer's perspective
3. Explain your reasoning
4. Provide evidence (code, tests, docs)
5. Be open to being wrong
6. Escalate if needed (tech lead, team discussion)
```
### Disagreement Response Template
```markdown
## Re: [Feedback item]
I considered this feedback carefully. Here's my perspective:
**Reviewer's concern**: [Their point]
**My reasoning**: [Why I did it this way]
**Evidence**: [Tests, benchmarks, docs supporting approach]
**Proposed resolution**: [Accept, discuss, or defer]
```
---
## Common Feedback Types
### Security Issues
Always fix immediately:
```typescript
// Before (vulnerable)
const query = `SELECT * FROM users WHERE id = '${userId}'`;
// After (secure)
const query = 'SELECT * FROM users WHERE id = $1';
const result = await db.query(query, [userId]);
```
```python
# Python equivalent
# Before (vulnerable)
query = f"SELECT * FROM users WHERE email = '{email}'"
result = await db.execute(text(query))
# After (secure — use ORM)
result = await db.execute(select(User).where(User.email == email))
```
### Error Handling
Add comprehensive handling:
```typescript
// Before
const user = await getUser(id);
return user.name;
// After
const user = await getUser(id);
if (!user) {
throw new NotFoundError(`User ${id} not found`);
}
return user.name;
```
```python
# Python equivalent
# Before
try:
user = await get_user(user_id)
except:
return None
# After
try:
user = await get_user(user_id)
except UserNotFoundError:
raise HTTPException(status_code=404, detail=f"User {user_id} not found")
```
### Test Coverage
Add missing tests:
```typescript
// Before: Only happy path tested
it('should return user', async () => {
const user = await getUser('valid-id');
expect(user).toBeDefined();
});
// After: Edge cases covered
it('should return user', async () => { /* ... */ });
it('should throw NotFoundError for missing user', async () => { /* ... */ });
it('should throw ValidationError for invalid id', async () => { /* ... */ });
```
```python
# Python equivalent
# Before: Only happy path
async def test_get_user(client):
response = await client.get("/api/users/1")
assert response.status_code == 200
# After: Edge cases covered
async def test_get_user_returns_user(client):
response = await client.get("/api/users/1")
assert response.status_code == 200
async def test_get_user_not_found(client):
response = await client.get("/api/users/999")
assert response.status_code == 404
async def test_get_user_invalid_id(client):
response = await client.get("/api/users/not-a-number")
assert response.status_code == 422
```
### Performance
Address efficiency concerns:
```typescript
// Before (N+1 query)
const users = await getUsers();
for (const user of users) {
user.orders = await getOrders(user.id);
}
// After (batch query)
const users = await getUsers();
const userIds = users.map(u => u.id);
const ordersByUser = await getOrdersForUsers(userIds);
users.forEach(u => u.orders = ordersByUser[u.id]);
```
```python
# Python equivalent (SQLAlchemy)
# Before (N+1)
users = (await db.execute(select(User))).scalars().all()
for user in users:
orders = (await db.execute(select(Order).where(Order.user_id == user.id))).scalars().all()
# After (eager loading)
users = (await db.execute(
select(User).options(selectinload(User.orders))
)).scalars().all()
```
---
## Re-Review Checklist
Before requesting re-review:
- [ ] All Critical issues fixed
- [ ] All Important issues fixed (or explicitly deferred with reason)
- [ ] Minor issues noted for follow-up
- [ ] Tests added/updated for fixes
- [ ] Full test suite passes
- [ ] Changes summarized for reviewer
---
## Iteration Limits
```markdown
If review requires 3+ cycles:
1. STOP
2. Schedule discussion with reviewer
3. Identify root cause of misalignment
4. May need design discussion
5. Don't keep iterating endlessly
```
---
## Related Skills
- `requesting-code-review` - Companion skill for initiating reviews with proper context before feedback is received
- `systematic-debugging` - Use systematic debugging techniques when review feedback reveals bugs that need investigation
- `verification-before-completion` - After addressing review feedback, verify all fixes before claiming completion
@@ -1,190 +0,0 @@
# Feedback Categories Reference
How to categorize, prioritize, and respond to code review feedback.
## Category Definitions
### Critical -- Must Fix Before Merge
**Impact**: Security vulnerability, data loss, crash, or correctness failure.
**Examples**:
- SQL injection or XSS vulnerability
- Missing authentication/authorization check
- Data corruption or silent data loss
- Unhandled exception that crashes the service
- Race condition that causes incorrect results
- Breaking change to public API without migration path
**Response**: Fix immediately. No merge until resolved. Thank the reviewer.
**Time**: Address within hours, not days.
### Important -- Should Fix
**Impact**: Logic error, missing edge case, performance issue, or maintainability concern.
**Examples**:
- Missing null/undefined check on a code path that can be reached
- N+1 query that will degrade with data growth
- Missing error handling for a plausible failure mode
- Incorrect business logic for an edge case
- Missing test for a significant code path
- Resource leak (connection, file handle, memory)
**Response**: Fix before merge unless there is a strong reason to defer (document with a ticket if deferring).
**Time**: Address before the next review round.
### Minor -- Fix If Easy
**Impact**: Code style, naming, comments, minor readability.
**Examples**:
- Variable name could be clearer
- Comment is slightly inaccurate
- Could extract a helper function for readability
- Import ordering
- Unnecessary intermediate variable
- Slightly verbose code that could be simplified
**Response**: Fix if the change is quick and low-risk. If fixing would require significant refactoring, note it for a follow-up.
**Time**: Address in the current PR or create a follow-up ticket.
### Subjective -- Discuss and Decide
**Impact**: Architectural preference, design philosophy, style choice where both options are valid.
**Examples**:
- "I would have used a class here instead of functions"
- "I prefer early returns over nested if-else"
- "Consider using pattern X instead of pattern Y"
- "This could also be modeled as an event-driven system"
- Disagreement on level of abstraction
**Response**: Engage in discussion. Consider the merits. Agree on a direction or escalate to team lead. Neither side is necessarily wrong.
**Time**: Resolve within one discussion round if possible.
## Prioritization Matrix
| Category | Merge Blocker? | Default Action | Can Defer? |
|---|---|---|---|
| Critical | Yes | Fix now | No |
| Important | Usually | Fix now or create ticket | With justification |
| Minor | No | Fix if quick | Yes, with follow-up |
| Subjective | No | Discuss | Yes, team decision |
## How to Handle Each Category
### Receiving Critical Feedback
1. Acknowledge the issue immediately
2. Do not be defensive -- this is protecting users
3. Fix and push the update
4. Add a test that would catch the issue
5. Consider if similar issues exist elsewhere
```
> Reviewer: This SQL query uses string interpolation, which is vulnerable to injection.
>
> You: Good catch -- fixed in abc1234. Added parameterized query and a test
> that verifies injection attempts are escaped. Also checked the other
> queries in this module; they all use parameterized queries already.
```
### Receiving Important Feedback
1. Evaluate whether the feedback is correct (verify, don't assume)
2. If correct, fix it
3. If you disagree, explain your reasoning with evidence
4. If deferring, create a ticket and reference it
```
> Reviewer: This will N+1 query when loading orders with items.
>
> You: You're right. Added eager loading with joinedload() in commit def5678.
> Added a test that asserts query count stays constant regardless of item count.
```
### Receiving Minor Feedback
1. Fix quickly if possible
2. If it requires significant refactoring, note it
```
> Reviewer: Consider renaming `data` to `order_summary` for clarity.
>
> You: Renamed in abc9012. Agreed it's clearer.
```
or
```
> Reviewer: This function could be extracted into a utility.
>
> You: Agree, but it's only used here for now. Created PROJ-789 to extract
> it if we need it elsewhere. Keeping it inline for this PR.
```
### Receiving Subjective Feedback
1. Consider the suggestion genuinely
2. Present your reasoning if you disagree
3. Look for objective criteria to decide (performance, testability, consistency with codebase)
4. If no clear winner, defer to existing codebase conventions
5. If still no consensus, the code author decides (or escalate)
```
> Reviewer: I'd prefer a class-based approach here.
>
> You: I considered that. Went with functions because: (1) no shared state
> between operations, (2) matches the pattern in src/services/auth.py,
> (3) easier to test in isolation. Happy to discuss further if you see
> benefits I'm missing.
```
## Handling Disagreements
### Step-by-Step Process
1. **Verify the claim**: Run the test, check the docs, reproduce the scenario. Do not argue from assumption.
2. **Propose an alternative**: If you disagree, suggest what you would do instead and explain why.
3. **Look for objective evidence**: Benchmarks, test results, documentation, or existing patterns in the codebase.
4. **Find common ground**: Often both approaches have merit. Look for a synthesis.
5. **Escalate if stuck**: Bring in a third opinion (tech lead, team discussion). Do not let PRs stall.
### What NOT to Do
- Do not dismiss feedback without investigation
- Do not agree with everything to avoid conflict (performative agreement hides bugs)
- Do not take feedback personally
- Do not let disagreements block merges for days -- timebox the discussion
- Do not relitigate decisions that were already agreed upon by the team
## Feedback Response Checklist
For each piece of feedback received:
- [ ] Read and understand the feedback fully
- [ ] Categorize it (critical / important / minor / subjective)
- [ ] If technical claim: verify it independently (run the code, check docs)
- [ ] Respond with what you did (fixed, deferred with ticket, or discussed)
- [ ] If fixed: reference the commit
- [ ] If deferred: reference the ticket
- [ ] If disagreeing: provide reasoning with evidence
## Quick Reference: Response Templates
**Agreeing and fixing:**
> Fixed in [commit]. Added test to prevent regression.
**Agreeing and deferring:**
> Agreed. Created [TICKET] to address this. Out of scope for this PR.
**Disagreeing with reasoning:**
> Considered this. Went with [approach] because [reason 1], [reason 2]. Here's [evidence]. Open to discussion.
**Asking for clarification:**
> Can you clarify what you mean by [X]? I want to make sure I address the right concern.
-112
View File
@@ -1,112 +0,0 @@
---
name: refactoring
argument-hint: "[file or function]"
description: >
Use when improving code structure, readability, or maintainability without changing behavior. Trigger for keywords like "refactor", "clean up", "extract", "simplify", "rename", "restructure", "code smell", "technical debt", "DRY", or any request to improve code quality without adding features. Also activate when code reviews identify structural issues, when functions are too long, or when duplication needs elimination.
---
# Refactoring
## When to Use
- Improving code structure without changing behavior
- Extracting reusable functions or components
- Eliminating code duplication
- Reducing complexity (long functions, deep nesting)
- Renaming for clarity
- Addressing code review feedback about structure
## When NOT to Use
- Adding new features — use `feature-workflow`
- Fixing bugs — use `systematic-debugging` (behavior change, not refactoring)
- Performance optimization — use `performance-optimization`
---
## Quick Reference
| Topic | Reference | Key content |
|-------|-----------|-------------|
| Refactoring patterns | `references/patterns.md` | Extract, inline, rename, move, decompose, introduce parameter object |
| Code smells | `references/code-smells.md` | Detection signals and recommended refactorings |
---
## Safe Refactoring Workflow
1. **Ensure tests pass** before any change
2. **Make one small, behavior-preserving change** at a time
3. **Run tests after each change**
4. **Commit each successful step** independently
5. **Use type checkers** (mypy/tsc) as a secondary safety net
6. **Never mix refactoring with feature/bug changes** in the same commit
---
## Core Patterns
| Pattern | When | Example |
|---------|------|---------|
| Extract function | Long function, repeated logic | Pull 10-line block into named function |
| Inline function | Trivial wrapper adding no clarity | Remove `getAge()` that just returns `this.age` |
| Rename symbol | Name doesn't reveal intent | `x``userCount` |
| Introduce parameter object | 4+ related parameters | `(name, email, age)``UserInput` |
| Replace conditional with polymorphism | Long if/else or switch chains | Strategy pattern or subclass dispatch |
| Decompose conditional | Complex boolean expression | `isEligible()` instead of `age > 18 && !banned && verified` |
| Extract variable | Complex expression | `const isOverBudget = total > limit * 1.1` |
---
## Code Smell Signals
- **Long function** (>20-30 lines)
- **Long parameter list** (>3-4 params)
- **Duplicated logic** across multiple locations
- **Deep nesting** (>3 levels)
- **Feature envy** — function uses another class's data more than its own
- **Shotgun surgery** — one change requires edits in many files
- **Primitive obsession** — raw strings/dicts instead of typed objects
- **Dead code** — unreachable or unused functions/imports
---
## Python-Specific
- Convert `dict` bags to **dataclasses** or **TypedDict**
- Add **type hints** progressively
- Replace loops with **comprehensions** where clearer
- Use **`@property`** instead of get/set methods
- Use **`Enum`** instead of string constants
## TypeScript-Specific
- Use **discriminated unions** instead of class hierarchies
- Replace `any` with **generics** or **`unknown`** + narrowing
- Replace enums with **`as const`** objects for tree-shaking
- Extract **utility types** (`Pick`, `Omit`, `Partial`)
---
## Best Practices
1. **Rule of three** — extract on the third duplication, not the first.
2. **Tests are the safety net** — never refactor without them.
3. **Small steps** — one rename is better than a big-bang rewrite.
4. **Preserve interfaces** — change internals, not public APIs (unless that's the goal).
5. **Use IDE tooling** — automated rename/move updates all references.
## Common Pitfalls
1. **Refactoring without tests** — no safety net to catch regressions.
2. **Mixing refactoring with features** — makes it impossible to identify behavior changes.
3. **Premature abstraction** — extracting patterns before duplication exists.
4. **Too-large refactors** — big-bang rewrites instead of incremental steps.
5. **Breaking public interfaces** — changing signatures without updating callers.
---
## Related Skills
- `testing` — Ensure test coverage before refactoring
- `writing-concisely` — Refactoring responses can be terse (show before/after)
@@ -1,32 +0,0 @@
# Code Smells Detection Guide
## Smell → Refactoring Map
| Smell | Signal | Refactoring |
|-------|--------|-------------|
| Long function | >20-30 lines | Extract function |
| Long parameter list | >3-4 params | Introduce parameter object |
| Duplicated logic | Same code in 3+ places | Extract function, DRY |
| Deep nesting | >3 levels of indentation | Early return, extract function |
| Feature envy | Uses another class's data more than its own | Move method to the class with the data |
| Shotgun surgery | One change → edits in many files | Move related code together |
| Primitive obsession | Raw strings/dicts instead of types | Introduce dataclass/interface |
| Dead code | Unreachable or unused | Delete it (git has history) |
| God class | Class does too many things | Extract class by responsibility |
| Comments as deodorant | Comments explaining messy code | Refactor the code to be clear |
## Python-Specific Smells
- `dict` used as a struct → use `@dataclass` or `TypedDict`
- Missing type hints on public functions
- Manual `__init__` boilerplate → `@dataclass`
- String constants → `Enum`
- Getter/setter methods → `@property`
## TypeScript-Specific Smells
- `any` type → `unknown` + narrowing or generics
- Enum → `as const` object (better tree-shaking)
- Class hierarchy for variants → discriminated union
- Interface duplication → utility types (`Pick`, `Omit`, `Partial`)
- Index as key in lists → stable unique ID
-93
View File
@@ -1,93 +0,0 @@
# Refactoring Patterns
## Extract Function
Pull cohesive logic into a named function.
```python
# Before
def process_order(order):
# validate
if not order.items:
raise ValueError("Empty order")
if order.total < 0:
raise ValueError("Negative total")
# ... 50 more lines
# After
def validate_order(order):
if not order.items:
raise ValueError("Empty order")
if order.total < 0:
raise ValueError("Negative total")
def process_order(order):
validate_order(order)
# ... rest of processing
```
## Introduce Parameter Object
Group 4+ related parameters into a single object.
```typescript
// Before
function createUser(name: string, email: string, age: number, role: string) { ... }
// After
interface CreateUserInput {
name: string;
email: string;
age: number;
role: string;
}
function createUser(input: CreateUserInput) { ... }
```
## Replace Conditional with Polymorphism
```typescript
// Before
function getPrice(type: string, base: number): number {
if (type === 'premium') return base * 0.8;
if (type === 'bulk') return base * 0.7;
return base;
}
// After
const pricingStrategies: Record<string, (base: number) => number> = {
premium: (base) => base * 0.8,
bulk: (base) => base * 0.7,
standard: (base) => base,
};
function getPrice(type: string, base: number): number {
return (pricingStrategies[type] ?? pricingStrategies.standard)(base);
}
```
## Decompose Conditional
```python
# Before
if age > 18 and not banned and verified and subscription_active:
grant_access()
# After
def is_eligible(user):
return user.age > 18 and not user.banned and user.verified and user.subscription_active
if is_eligible(user):
grant_access()
```
## Extract Variable
```typescript
// Before
if (order.total > 100 && order.items.length > 5 && !order.hasDiscount) { ... }
// After
const isLargeOrder = order.total > 100 && order.items.length > 5;
const qualifiesForDiscount = isLargeOrder && !order.hasDiscount;
if (qualifiesForDiscount) { ... }
```
+219
View File
@@ -0,0 +1,219 @@
---
name: release-and-changelog
user-invocable: true
description: >
Use when cutting a release, bumping a version, or writing release notes.
Activate for keywords like "release", "version bump", "changelog", "release
notes", "tag", "publish", "ship a release", "v1.x", "v2.x". Enforces version
hygiene: SemVer respect, changelog discipline, atomic commits, tagged release.
Always reflect the actual diff in the changelog -- never write notes from
memory or marketing copy.
---
# Release and Changelog
## Overview
A workflow for cutting a clean release: bump the version, write changelog
entries that reflect the actual diff, tag, publish. The skill exists because
the most common release-time failure isn't the publishing mechanism — it's the
changelog that says "various improvements" or "performance enhancements"
without naming what changed. Users reading the notes can't decide whether to
upgrade; engineers debugging six months later can't bisect on the release.
This skill enforces that the changelog is built from the diff, not from a
remembered list of features. Used after `code-review-loop` and before
publishing/tagging.
## When to Use
- Cutting a numbered release (`v1.2.0`, `v2.0.0-rc1`, etc.)
- Updating a `CHANGELOG.md` after a feature merge in projects with a
rolling-changelog policy
- Bumping a package version in a published library
- Writing release notes for a deploy that crosses a version boundary
## When NOT to Use
- Continuous-deployment projects with no version concept (every merge is a
deploy; there's no release event)
- Internal services where deploys don't carry version semantics for consumers
- A trivial doc-only or test-only change (changelog entry optional per
project policy)
## Process
### Step 1: Determine the version bump
**Goal:** Pick the correct SemVer level.
**Inputs:** The set of changes since the last release.
**Actions:**
1. List every change since the last tag: `git log <last-tag>..HEAD --oneline`.
2. Classify each change:
- **Breaking** (incompatible API change, removed feature, changed behavior
that callers depend on) → MAJOR bump
- **New feature** (additive, backward-compatible) → MINOR bump
- **Bug fix or internal improvement** (no behavioral change for callers) →
PATCH bump
3. The bump is the **highest** classification across all changes. One breaking
change in a release of 50 fixes is still a MAJOR bump.
4. If the project is pre-1.0 (`0.x.y`), treat MINOR as breaking-allowed and
PATCH as the conservative bump. The 0.x.y SemVer license to break is real
but should still be exercised consciously.
**Output:** The new version number, with the rationale: `v1.2.0 → v1.3.0
(MINOR: added X feature, no breaking changes)`.
### Step 2: Build the changelog from the diff
**Goal:** A `CHANGELOG.md` entry built from actual changes, not memory.
**Inputs:** The change list from Step 1.
**Actions:**
1. Open `CHANGELOG.md`. If it doesn't exist, create one following Keep a
Changelog (keepachangelog.com) format.
2. Add a section at the top: `## [<version>] - <YYYY-MM-DD>`.
3. Below it, add subheadings as needed:
- `### Added` (new features)
- `### Changed` (changes to existing functionality)
- `### Deprecated` (features marked for removal)
- `### Removed` (deleted features)
- `### Fixed` (bug fixes)
- `### Security` (vulnerability fixes)
4. For each change in your Step 1 list, write one entry under the right
subheading. Each entry:
- Names what changed in user-observable terms (not implementation terms).
- Cites the PR or commit hash.
- Names the consumer impact if non-trivial (migration step, removed feature,
etc.).
5. **Reflect the actual diff.** If you wrote "Improved performance" without
naming what was improved, return to the diff and find the specific
improvement.
**Output:** A `CHANGELOG.md` entry that reads like the diff, not like marketing
copy.
### Step 3: Update the manifest
**Goal:** Bump the version where the package's tools look for it.
**Inputs:** The new version number from Step 1.
**Actions:**
1. Update the version in every manifest the project uses:
- `package.json` (Node)
- `pyproject.toml` / `setup.py` (Python)
- `Cargo.toml` (Rust)
- `plugin.json` / `marketplace.json` (Claude Code plugin)
- `VERSION` file (where applicable)
2. If the project has a generated build artifact embedding the version
(`__version__` constant, build banner), regenerate it.
3. Confirm all manifests show the same version. Drift here is a common bug.
**Output:** All version manifests aligned to the new version.
### Step 4: Atomic release commit
**Goal:** One commit that captures the release.
**Inputs:** Updated manifests + updated CHANGELOG.
**Actions:**
1. Stage the manifest changes and the CHANGELOG entry.
2. Commit with a message that names the version and the level:
`Release v1.3.0 (MINOR)` or follow project convention.
3. The commit should contain *only* the version bump and the changelog. No
feature changes, no fixes, no "while I was here" cleanups. Atomic.
**Output:** A single release commit on the release branch (or main, depending
on the project's branching model).
### Step 5: Tag and publish
**Goal:** Make the release discoverable to consumers.
**Inputs:** The release commit.
**Actions:**
1. Tag the commit: `git tag -a v1.3.0 -m "v1.3.0 (MINOR): added X feature"`.
2. Push the tag: `git push origin v1.3.0`.
3. If the project publishes to a registry (npm, PyPI, crates.io, marketplace),
run the publish command. Verify the published artifact matches the tag.
4. If a release notes mechanism exists (GitHub Releases, etc.), copy the
CHANGELOG entry to it. Don't paraphrase; the changelog and the release notes
should match.
5. If there's a deploy associated with the release, trigger it now (or follow
the project's deploy procedure).
**Output:** Tagged, published release. Tag matches the version; published
artifact matches the tag.
### Step 6: Post-release verification
**Goal:** Confirm consumers can actually consume the release.
**Inputs:** A published release.
**Actions:**
1. Install the released artifact in a clean environment (a fresh container,
a separate venv, a sandboxed install). Don't test from your dev box.
2. Run a smoke check: import the package, run a hello-world, hit the new
feature.
3. If the install fails or the smoke check breaks, the release is wrong even
though it's tagged. Yank/unpublish if the registry supports it; otherwise
ship a patch release.
**Output:** A confirmation that the release works for a fresh consumer.
## Rationalizations
| Excuse | Why it sounds reasonable | Why it's wrong | What to do instead |
|---|---|---|---|
| "It's just a patch — I don't need to write changelog entries for every fix." | Patch releases are routine. Per-fix entries can feel like ceremony. | The changelog is a contract with consumers. A patch with no entries reads as "no notable changes" — but the consumer who runs `npm update` and gets a regression has no way to bisect on the release notes because the notes are empty. The 60 seconds of writing the entry buys hours of debuggability later. | Write one entry per fix. Even one line ("Fixed off-by-one in pagination — #234"). The cost is small; the value is durable. |
| "The diff is small — I can write the changelog from memory." | A small diff really is reconstructable from memory. | Memory drifts in even short timescales. The PR you wrote yesterday already has details (the exact behavior change, the constraint you handled) that aren't in your head today. The changelog written from memory says "improved X" instead of "X now respects Y under condition Z," which is the actual content the consumer needs. | Build the changelog from `git log <last-tag>..HEAD`. Even for small diffs. The 30 seconds of running the command and reading the commits is the discipline. |
| "Nobody reads changelogs anyway." | Some consumers really don't read changelogs. Auto-update bots upgrade silently. | "Nobody reads them" is true until someone debugs a regression and bisects on releases. The changelog is the bisect index. The empty changelog turns "which release introduced this?" into a manual diff comparison; the populated changelog turns it into a 30-second read. | Write the changelog for the future debugger, not for the casual reader. The audience is the engineer six months from now who needs to know what changed in v1.3.0. |
| "I'll bump the version after I publish — the registry will tell me what to use." | Some registries do auto-increment. Letting the tool decide feels efficient. | Auto-increment doesn't know your SemVer intent. A breaking change auto-bumped as PATCH ships under a version consumers will pick up by default — they get the breaking change without warning. The version is your communication; only you know what the changes mean. | Bump the version *before* publishing. Step 1 → Step 3 in this skill. The version reflects intent, not just sequence. |
| "I'll skip the post-release smoke check — CI tested everything." | CI does run the test suite. | CI tests the source tree, not the published artifact. A package that builds and tests fine in CI may publish broken because of a missing file in the package manifest, an unset environment variable in the publish step, or a registry-specific transformation that broke something. The smoke check on a fresh install catches the published-vs-source gap. | Run the smoke check (Step 6). Fresh container, install from registry, run the basic flow. 5 minutes; it catches the class of bugs CI cannot. |
| "I'll batch multiple unrelated fixes into one release commit." | Fewer commits is cleaner. | The release commit is the bisect target; a clean release commit (only the bump and changelog) is bisect-friendly. Mixing fixes into the release commit ties the release to the unrelated fixes — `git revert` of the release commit reverts the fixes too. | Land fixes in their own commits before the release. The release commit only contains the version bump and changelog. Atomic in Step 4 means atomic. |
## Evidence Requirements
| Checkpoint | Required artifact | What "no evidence" looks like |
|---|---|---|
| End of Step 1 | Version bump rationale: `<old> → <new> (<level>: <reason>)` | "Bumping the version." |
| End of Step 2 | Changelog entries built from `git log` output, not memory | "Various improvements." |
| End of Step 3 | All manifests show the same version | "Updated package.json." |
| End of Step 4 | An atomic release commit with only manifest + changelog changes | A release commit that also includes feature fixes. |
| End of Step 5 | Tag pushed; published artifact verified to match tag | "Tagged it." |
| End of Step 6 | Smoke check output from a fresh-install environment | "I'll trust it." |
## Red Flags
- The changelog entry for a release is "Various improvements and bug fixes."
Build it from the diff.
- A MAJOR-level change (breaking) is in a MINOR release. Either the change
isn't actually breaking or the release is mis-leveled.
- The release commit contains code changes other than version bump + changelog.
Re-do as atomic.
- Manifests disagree on the version. Pick one and align them all.
- The git tag doesn't match the published artifact's version. Yank or correct.
- The smoke check was skipped. The release is unverified.
- The CHANGELOG file was force-edited to remove an entry. Releases shouldn't be
rewritten retroactively.
## References
- Tom Preston-Werner, *Semantic Versioning 2.0.0* (semver.org, 2013) — the
canonical reference for MAJOR/MINOR/PATCH semantics. Step 1 operationalizes
the SemVer rules with explicit classification.
- Olivier Lacan & contributors, *Keep a Changelog 1.1.0* (keepachangelog.com) —
the format used in Step 2's subheading structure (Added, Changed, Deprecated,
Removed, Fixed, Security).
-283
View File
@@ -1,283 +0,0 @@
---
name: requesting-code-review
description: >
Use when completing any task, implementing a feature, fixing a critical bug, or before merging to a main branch. Use whenever code is ready for feedback, when unsure about an implementation approach, or when changes touch security, authentication, or data handling. Activate before any PR creation or branch merge to ensure reviewers have complete context, clear scope, and focused areas of concern.
---
# Requesting Code Review
## When to Use
- After completing a task (before proceeding to next)
- After implementing a feature
- Before merging to main branch
- When unsure about implementation approach
- After fixing critical bugs
## When NOT to Use
- Mid-implementation work where the code is still incomplete and likely to change significantly
- Research or exploration tasks where you are prototyping and not producing production code
- Trivial one-line fixes like typo corrections or version bumps that carry no risk
---
## Review Request Components
### 1. Scope Definition
Clearly state what should be reviewed:
```markdown
## Review Scope
**Files changed**:
- src/services/user-service.ts (modified)
- src/services/user-service.test.ts (added)
- src/types/user.ts (modified)
**Lines changed**: ~150 additions, ~20 deletions
**Not in scope** (don't review):
- package.json changes (unrelated dependency update)
- Generated files in dist/
```
### 2. Context
Explain why these changes were made:
```markdown
## Context
**Task**: Implement user email verification
**Requirements**:
- Users must verify email before accessing features
- Verification link expires after 24 hours
- Users can request new verification email
**Design decisions**:
- Used JWT for verification token (stateless)
- Stored verification status in existing User table
```
### 3. Areas of Concern
Highlight where you want focused attention:
```markdown
## Areas of Concern
1. **Security**: Is the token generation secure enough?
2. **Error handling**: Are all edge cases covered?
3. **Performance**: Will the verification lookup be efficient?
```
### 4. Test Coverage
Show what's tested:
```markdown
## Test Coverage
- Unit tests: 8 new tests in user-service.test.ts
- Integration: Manual testing of full flow
- Edge cases: Expired token, invalid token, already verified
**Not tested** (known gaps):
- Load testing with many concurrent verifications
```
---
## Review Request Template
```markdown
## Code Review Request
### Summary
[1-2 sentence description of changes]
### Files Changed
- `path/to/file1.ts` - [Brief description]
- `path/to/file2.ts` - [Brief description]
### Context
[Why these changes were needed]
### Implementation Notes
[Key decisions made and why]
### Areas for Focus
1. [Specific concern 1]
2. [Specific concern 2]
### Testing
- [x] Unit tests added/updated
- [x] Integration tests pass
- [ ] E2E tests (not applicable)
### Checklist
- [x] Code follows project conventions
- [x] No security vulnerabilities introduced
- [x] Documentation updated if needed
```
---
## What to Include
### Always Include
- List of changed files
- Summary of what changed
- Why the change was needed
- Test status
### Include When Relevant
- Design alternatives considered
- Performance implications
- Security considerations
- Breaking changes
### Never Include
- Unrelated changes
- Formatting-only commits
- Debug code
- TODO comments (resolve first)
---
## Review Types
### Quick Review
For small, low-risk changes:
```markdown
## Quick Review: Fix typo in error message
**File**: src/errors.ts
**Change**: Fixed "recieved" → "received" in error message
**Risk**: None
```
### Standard Review
For typical feature work:
```markdown
## Review: Add user preferences
**Files**: 3 files, ~200 lines
**Context**: Users can now save display preferences
**Focus**: Data validation, storage approach
```
### Critical Review
For high-risk changes:
```markdown
## CRITICAL REVIEW: Authentication refactor
**Files**: 12 files, ~800 lines
**Risk**: HIGH - Authentication system changes
**Required reviewers**: Security team
**Focus**: Token handling, session management, encryption
```
---
## Best Practices
### Keep Reviews Focused
```markdown
BAD: "Review my last week of work"
GOOD: "Review the user verification feature (3 files)"
```
### Provide Runnable Context
```markdown
## To test locally
1. git checkout feature/email-verification
2. npm install
3. npm test -- --grep "email verification"
```
### Be Specific About Concerns
```markdown
BAD: "Let me know if anything looks wrong"
GOOD: "I'm unsure about the error handling in lines 45-60"
```
### Include Relevant Links
```markdown
Related:
- Ticket: PROJ-123
- Design doc: [link]
- Previous discussion: [link]
```
---
## After Submitting
### What to Expect
```markdown
Reviewer will return:
- Critical issues (must fix)
- Important issues (should fix)
- Minor issues (optional)
- Approval/rejection status
```
### How to Handle Feedback
See `receiving-code-review` skill for detailed guidance.
---
## Stack-Specific Review Context
What reviewers need to know, by stack:
### Python/FastAPI
- Pydantic models changed? (schema compatibility with existing clients)
- SQLAlchemy models changed? (migration included?)
- New dependencies in `requirements.txt`?
- Async patterns correct? (no blocking calls in async functions)
- Type hints complete? (`mypy --strict` passes?)
### TypeScript/NestJS
- DTOs changed? (`class-validator` decorators correct?)
- New modules registered in `AppModule`?
- Guards/interceptors applied correctly?
- Prisma schema changed? (migration included?)
- `whitelist: true` on `ValidationPipe`?
### React/Next.js
- Server vs Client components correct?
- `'use client'` directive where needed?
- State management approach (local vs global)?
- Bundle size impact? (check with `next build`)
- Accessibility (aria labels, keyboard nav)?
---
## Related Skills
- `receiving-code-review` - Companion skill for processing and acting on review feedback after it is received
- `verification-before-completion` - Run verification checks before requesting review to ensure code is actually ready
- `finishing-a-development-branch` - Use after review approval to complete the branch merge/PR workflow
@@ -1,143 +0,0 @@
# Review Request Template
Use this template when requesting code review. Copy the structure below and fill in each section. Remove sections that are not applicable, but err on the side of including more context.
---
## Review Request
### Summary
_One to three sentences describing the change at a high level. What does this change do and why?_
**Type**: `feature` | `bugfix` | `refactor` | `performance` | `security` | `chore`
**Ticket/Issue**: [Link or ID]
**Branch**: `feature/TICKET-123-description` -> `main`
---
### Changes Made
_List the key changes. Group by area if touching multiple parts of the codebase._
**Core changes:**
- [ ] Changed X in `src/path/to/file.py` to support Y
- [ ] Added new endpoint `POST /api/resource` in `src/api/routes.py`
- [ ] Updated database schema: added `column_name` to `table_name`
**Supporting changes:**
- [ ] Added migration `migrations/0042_add_column.py`
- [ ] Updated config for new feature flag `ENABLE_FEATURE_X`
**Files changed:** _N files, +X/-Y lines_ (or let the PR tool calculate)
---
### Testing Done
_Describe what testing was performed. Be specific._
- [ ] Unit tests added/updated: `tests/test_feature.py`
- [ ] Integration tests added/updated: `tests/integration/test_api.py`
- [ ] Manual testing steps:
1. Step one
2. Step two
3. Expected result
- [ ] Edge cases tested:
- Empty input
- Maximum size input
- Unauthorized user
- Concurrent requests
- [ ] All existing tests pass: `pytest -v` / `pnpm test`
---
### Areas of Concern
_Be honest about parts you are unsure about. This helps reviewers focus._
- [ ] The caching logic in `src/services/cache.py` lines 42-67 may have race conditions under high concurrency
- [ ] Not sure if the error handling in `handleTimeout()` covers all edge cases
- [ ] Performance impact of the new query has not been benchmarked
- [ ] _None -- I am confident in this change_
---
### Reviewer Focus Areas
_Tell the reviewer where to spend their time. Rank by priority._
1. **Security**: Authentication logic in `src/auth/middleware.py` -- does the token validation cover all cases?
2. **Correctness**: State machine transitions in `src/services/order.py` -- are all transitions valid?
3. **Performance**: New database query in `src/repos/order_repo.py` -- is it using the right index?
4. **Design**: Is the service layer abstraction appropriate, or should this be split?
---
### How to Test Locally
_Step-by-step instructions so the reviewer can verify the change._
```bash
# 1. Set up environment
git checkout feature/TICKET-123-description
pip install -r requirements.txt # or: pnpm install
# 2. Run migrations (if applicable)
python manage.py migrate # or: pnpm db:migrate
# 3. Set required environment variables (if applicable)
export FEATURE_X_ENABLED=true
# 4. Run the application
python -m uvicorn main:app --reload # or: pnpm dev
# 5. Test the change
curl -X POST http://localhost:8000/api/resource \
-H "Content-Type: application/json" \
-d '{"key": "value"}'
# Expected: 201 Created with response body { "id": "...", "key": "value" }
# 6. Run tests
pytest tests/ -v # or: pnpm test
```
---
### Additional Context
_Optional. Screenshots, diagrams, links to design docs, related PRs, or anything else that helps the reviewer._
- Design doc: [link]
- Related PR: #42
- Screenshot of UI change: [attached]
- Before/after performance metrics: [data]
---
## Quick Version (For Small Changes)
For small, low-risk changes, use this abbreviated format:
```
## Review Request
**Summary**: Fix off-by-one in pagination (returns N+1 results instead of N)
**Ticket**: PROJ-456
**Changes**: `src/api/pagination.py` line 23: `< limit` changed to `<= limit`
**Tests**: Updated `tests/test_pagination.py`, all pass
**Risk**: Low -- single line change, well-covered by tests
```
---
## Checklist Before Submitting
- [ ] Self-reviewed the diff (read your own PR as if you were the reviewer)
- [ ] Tests added for new behavior
- [ ] No TODO/FIXME/HACK comments left without a ticket reference
- [ ] No debugging artifacts (print statements, console.log, commented-out code)
- [ ] Documentation updated (if user-facing behavior changed)
- [ ] Migration is reversible (if schema changed)
- [ ] No secrets in the diff
-245
View File
@@ -1,245 +0,0 @@
---
name: root-cause-tracing
user-invocable: false
description: >
Use when a bug manifests far from its origin, when stack traces show multiple layers of indirection, or when data corruption appears with no obvious source. Use for any scenario involving "it was already wrong by the time it got here," deep execution stack errors, constraint violations caused by upstream failures, or mysterious data state issues. Always prefer this over surface-level fixes when the error location differs from the bug location.
---
# Root Cause Tracing
## When to Use
- Errors occur far from entry points
- Data corruption with unclear source
- Need to identify which code path triggers failures
- Stack traces show multiple levels of indirection
- "It was already wrong by the time it got here"
## When NOT to Use
- Surface-level UI bugs where the cause and effect are co-located
- Known issues with documented fixes already available in the codebase or issue tracker
- Performance optimization work where profiling tools are more appropriate than tracing
---
## Core Principle
**"Trace backward through the call chain until you find the original trigger, then fix at the source."**
The error location is rarely the bug location:
```
User Input → Validation → Service → Repository → Database
^ ^
| |
Bug HERE Error appears HERE
(bad input allowed) (constraint violation)
```
Fixing at the database layer treats the symptom. Fixing at validation prevents the bug.
---
## The Tracing Methodology
### Step 1: Identify Observable Error
Document exactly what you see:
```markdown
Error: "Cannot insert NULL into column 'user_id'"
Location: database-repository.ts:156
Stack trace: [full trace]
```
### Step 2: Locate Immediate Cause
Find the code directly responsible:
```typescript
// database-repository.ts:156
async function insertOrder(order: Order) {
await db.insert('orders', {
user_id: order.userId, // <- This is NULL
// ...
});
}
```
### Step 3: Trace One Level Up
Who called this function? What did they pass?
```typescript
// order-service.ts:89
async function createOrder(orderData: OrderData) {
const order = new Order(orderData);
await repository.insertOrder(order); // <- Called from here
}
```
### Step 4: Continue Tracing
Keep going up the call chain:
```typescript
// order-controller.ts:45
async function handleCreateOrder(req: Request) {
const orderData = req.body; // <- userId might be missing here
await orderService.createOrder(orderData);
}
```
### Step 5: Find Original Source
Reach the entry point where the problem originated:
```typescript
// The real bug: No validation at entry point
// req.body.userId was never validated
```
---
## Instrumentation Techniques
When manual analysis fails, add diagnostic logging:
### Strategic Console.error
```typescript
// Add before suspicious operations
console.error('[TRACE] order-service.createOrder input:', {
orderData,
hasUserId: !!orderData.userId,
stack: new Error().stack
});
```
### Stack Trace Capture
```typescript
// Capture where a value came from
function setUserId(id: string | null) {
if (id === null) {
console.error('[TRACE] userId set to null from:', new Error().stack);
}
this.userId = id;
}
```
### Boundary Logging
```typescript
// Log at every system boundary
async function callExternalApi(params) {
console.error('[TRACE] API request:', params);
const response = await fetch(url, params);
console.error('[TRACE] API response:', response.status, await response.text());
return response;
}
```
### Environment/Context Logging
```typescript
console.error('[TRACE] Context:', {
env: process.env.NODE_ENV,
timestamp: new Date().toISOString(),
requestId: context.requestId,
userId: context.user?.id
});
```
---
## Finding the Instrumentation Output
After adding logging:
```bash
# Run tests and grep for traces
npm test 2>&1 | grep "\[TRACE\]"
# Or run specific test
npm test -- --grep "failing test" 2>&1 | grep "\[TRACE\]"
```
---
## Common Root Cause Locations
| Where Error Appears | Where Bug Often Is |
|--------------------|--------------------|
| Database constraint | Input validation |
| Type error in service | Data transformation |
| Null reference | Optional field handling |
| API timeout | Connection pool config |
| Memory error | Resource cleanup |
---
## Defense-in-Depth Integration
After finding root cause, add validation at multiple layers:
```typescript
// Layer 1: Entry point
function handleRequest(req) {
if (!req.body.userId) {
throw new ValidationError('userId required');
}
}
// Layer 2: Service
function createOrder(data) {
assert(data.userId, 'userId must be provided to createOrder');
}
// Layer 3: Repository
function insertOrder(order) {
assert(order.userId, 'Cannot insert order without userId');
}
```
See `defense-in-depth` skill for comprehensive approach.
---
## Critical Warning
**"NEVER fix just where the error appears."**
Fixing at the error location:
- Treats symptom, not cause
- Leaves bug available to trigger from other paths
- Creates false confidence
- Guarantees the bug will return
Fixing at the source:
- Prevents the bug entirely
- Protects all code paths
- Creates robust system
- Actually solves the problem
---
## Tracing Checklist
- [ ] Error message and location documented
- [ ] Immediate cause identified
- [ ] Call chain traced backward
- [ ] Original source found
- [ ] Instrumentation added if needed
- [ ] Fix applied at source (not symptom)
- [ ] Defense-in-depth validation added
- [ ] Test proves fix works
---
## Related Skills
- `systematic-debugging` - General debugging methodology; use root-cause-tracing when the bug location differs from the error location
- `defense-in-depth` - After tracing the root cause, apply multi-layer validation to make the bug structurally impossible
- `sequential-thinking` - Use sequential thinking to systematically document evidence and hypotheses during complex tracing sessions
@@ -1,168 +0,0 @@
# Tracing Techniques Reference
Backward-tracing techniques for systematic root cause analysis.
## Stack Trace Analysis
### Reading a Stack Trace
1. Start at the **bottom** (most recent call) to find the immediate failure
2. Scan **upward** to find the first frame in **your code** (not library code)
3. That frame is usually the symptom location, not the cause
4. Continue upward to find where bad data or state originated
### Symptom vs Cause
| What You See | Likely Actual Cause |
|---|---|
| `NullPointerException` / `TypeError: cannot read property of undefined` | Value not set upstream, missing null check at origin |
| `IndexOutOfBoundsException` | Off-by-one in loop logic or empty collection not guarded |
| `ConnectionRefusedError` | Service down, wrong port, firewall rule, DNS resolution |
| `TimeoutError` | Deadlock, resource exhaustion, slow query, network partition |
| `ValidationError` | Caller passing wrong shape, schema mismatch, migration gap |
### Tips
- Filter out framework frames to reduce noise
- In async code, the stack may be split; look for `caused by` or `previous` sections
- In Python, read `__cause__` and `__context__` on chained exceptions
- In TypeScript/Node, check `error.cause` (ES2022+)
## Binary Search / Git Bisect
### When to Use
- Bug exists now but worked at some known-good point
- Reproducer is automatable (script, test command)
### Process
```bash
git bisect start
git bisect bad # current commit is broken
git bisect good <known-good-sha> # last known working commit
# Git checks out a midpoint; run your test
git bisect good # or bad, based on result
# Repeat until Git identifies the first bad commit
git bisect reset # return to original branch
```
### Automated Bisect
```bash
git bisect start HEAD <good-sha>
git bisect run ./test-script.sh
# Exit 0 = good, exit 1 = bad, exit 125 = skip
```
## Log Correlation
### Technique
1. Identify the **exact timestamp** of the error
2. Search all related service logs within a window (e.g., +/- 30 seconds)
3. Filter by **correlation ID**, **request ID**, or **user ID** across services
4. Build a timeline of events across services
### Correlation Fields to Look For
- `request_id` or `trace_id` (distributed tracing)
- `user_id` or `session_id`
- Source IP or client identifier
- Timestamps (normalize to UTC)
### Tools
- `grep` / `rg` with timestamp ranges
- Structured logging with JSON output + `jq`
- Distributed tracing (OpenTelemetry, Jaeger, Zipkin)
## Dependency Analysis (Backward Data Flow)
### Process
1. Start at the error location
2. Identify the **variable or value** that is wrong
3. Trace backward: where was this value set?
4. At each step, ask: is this value correct here? If yes, move forward. If no, keep going back.
5. The root cause is where correct data first becomes incorrect.
### Common Data Flow Points
```
User Input -> Validation -> Transform -> Business Logic -> Persistence -> Query -> Response
```
Trace backward through this chain from wherever the error manifests.
### Dependency Categories
| Dependency | What to Check |
|---|---|
| Function arguments | Caller passing wrong values |
| Config / env vars | Wrong environment, stale config |
| Database state | Missing migration, corrupt data |
| External API | Changed response format, auth expiry |
| Shared state | Race condition, stale cache |
## Instrumentation Points
### Where to Add Temporary Logging
1. **Entry/exit of suspected function** — log arguments and return value
2. **Before/after external calls** — log request and response
3. **Branch points** — log which path was taken and why
4. **Data transformation steps** — log before and after
5. **Error handlers** — log the full error with context
### Guidelines
- Use a distinct prefix (e.g., `[DEBUG-TRACE]`) so logs are easy to find and remove
- Log the **type** as well as the **value** (catches `"null"` vs `null`)
- In production, use feature flags or debug log levels, not code changes
- Remove all temporary logging before committing
### Python Example
```python
import logging
logger = logging.getLogger(__name__)
def process_order(order_id: str) -> Order:
logger.debug("[DEBUG-TRACE] process_order called with: %s (type: %s)", order_id, type(order_id))
order = db.get_order(order_id)
logger.debug("[DEBUG-TRACE] db.get_order returned: %s", order)
# ... rest of logic
```
### TypeScript Example
```typescript
function processOrder(orderId: string): Order {
console.debug(`[DEBUG-TRACE] processOrder called with: ${orderId} (type: ${typeof orderId})`);
const order = db.getOrder(orderId);
console.debug(`[DEBUG-TRACE] db.getOrder returned:`, order);
// ... rest of logic
}
```
## Common Root Cause Categories
| Category | Symptoms | Investigation Approach |
|---|---|---|
| **Data issues** | Wrong output, validation errors, corrupt state | Trace the bad value backward through the data flow |
| **Race conditions** | Intermittent failures, works-on-retry, order-dependent | Look for shared mutable state, add timing logs, test with delays |
| **Config drift** | Works locally but not in staging/prod | Diff environment configs, check env vars, verify secrets |
| **Dependency changes** | Broke after deploy with no code changes | Check lock file diffs, dependency changelogs, API version headers |
| **Resource exhaustion** | Timeouts, OOM, connection pool errors | Monitor metrics (memory, CPU, connections, disk), check for leaks |
| **Schema mismatch** | Serialization errors, missing fields | Compare expected vs actual schema, check migration status |
## Quick Decision: Which Technique to Use
| Situation | Start With |
|---|---|
| Have a stack trace | Stack trace analysis |
| "It used to work" | Git bisect |
| Multi-service issue | Log correlation |
| Wrong data in output | Backward data flow |
| No idea where to start | Add instrumentation at boundaries |
-249
View File
@@ -1,249 +0,0 @@
---
name: sequential-thinking
description: >
Use when facing any complex problem requiring careful step-by-step reasoning, evidence collection, and confidence tracking. Use when debugging has multiple possible causes, when making architecture decisions with trade-offs, during security analysis or audits, for performance investigations, or whenever decisions need explicit documentation. Activate aggressively for any scenario where jumping to conclusions would be risky or where the reasoning chain matters as much as the answer.
---
# Sequential Thinking
## When to Use
- Complex debugging
- Architecture decisions
- Security analysis
- Performance investigation
- Any problem with multiple possible causes
- When decisions need documentation
## When NOT to Use
- Simple straightforward tasks where the answer is obvious and well-known
- Mechanical code changes like renames, formatting, or boilerplate generation
- When the MCP sequential-thinking server is unavailable and structured tool support is needed
---
## The Sequential Process
### Step 1: Define the Question
Clearly state what you're trying to determine.
```markdown
## Question
What is causing the authentication timeout for users with special characters in passwords?
```
### Step 2: Gather Evidence
Collect all relevant information systematically.
```markdown
## Evidence Collection
### Evidence 1: Error Logs
- Source: `logs/auth-service.log`
- Finding: Timeout occurs at password encoding step
- Confidence: High (direct observation)
### Evidence 2: Code Review
- Source: `src/auth/password.ts:42`
- Finding: URL encoding applied to password
- Confidence: High (code inspection)
### Evidence 3: Test Results
- Source: Manual testing
- Finding: Works with alphanumeric, fails with `@#$`
- Confidence: High (reproducible)
```
### Step 3: Form Hypotheses
Generate possible explanations.
```markdown
## Hypotheses
### Hypothesis A: URL Encoding Issue
- Evidence supporting: E1, E2, E3
- Evidence against: None
- Probability: 80%
### Hypothesis B: Character Set Mismatch
- Evidence supporting: E3
- Evidence against: E2 (UTF-8 used)
- Probability: 15%
### Hypothesis C: Database Encoding
- Evidence supporting: None directly
- Evidence against: E1 (fails before DB)
- Probability: 5%
```
### Step 4: Test Hypotheses
Verify the most likely explanation.
```markdown
## Testing
### Test for Hypothesis A
Action: Remove URL encoding, use base64 instead
Result: Password `test@123` now works
Conclusion: Hypothesis A confirmed
```
### Step 5: Document Conclusion
State the final answer with confidence.
```markdown
## Conclusion
**Root Cause**: URL encoding in password.ts:42 mangles special characters
**Confidence**: 9/10
**Evidence Chain**:
1. Timeout at encoding step (logs)
2. URL encoding in code (review)
3. Special char passwords fail (testing)
4. Removing encoding fixes issue (verification)
**Fix**: Replace URL encoding with base64 at line 42
```
---
## Output Template
```markdown
# Sequential Analysis: [Problem Description]
## Question
[Clear statement of what we're investigating]
## Evidence
### Evidence 1: [Title]
- Source: [where found]
- Finding: [what it shows]
- Confidence: [High/Medium/Low]
### Evidence 2: [Title]
...
## Hypotheses
### Hypothesis A: [Name]
- Supporting evidence: [list]
- Contradicting evidence: [list]
- Probability: [X%]
### Hypothesis B: [Name]
...
## Testing
### Test 1: [What tested]
- Action: [what was done]
- Expected: [what should happen if hypothesis true]
- Actual: [what happened]
- Result: [confirms/refutes hypothesis]
## Conclusion
**Answer**: [clear statement]
**Confidence**: [X/10]
**Key Evidence**: [most important findings]
**Recommended Action**: [what to do next]
```
---
## Confidence Scoring
| Score | Meaning | Evidence Required |
|-------|---------|-------------------|
| 9-10 | Certain | Multiple independent confirmations |
| 7-8 | High | Strong evidence, tested hypothesis |
| 5-6 | Medium | Good evidence, some uncertainty |
| 3-4 | Low | Limited evidence, multiple possibilities |
| 1-2 | Guess | Insufficient evidence |
---
## Anti-Patterns
### Jumping to Conclusions
```markdown
❌ "The bug is probably in the database"
✅ "Let me gather evidence before hypothesizing"
```
### Confirmation Bias
```markdown
❌ Only looking for evidence supporting first guess
✅ Actively seeking contradicting evidence
```
### Skipping Documentation
```markdown
❌ Fixing without recording reasoning
✅ Document even simple analysis for future reference
```
---
## Activation
### Via Mode
```
Use mode: deep-research
```
### Via Command
```
Apply sequential thinking to analyze [problem]
```
### Via Skill Reference
```
Use skill: sequential-thinking
```
---
## MCP Integration
This skill is powered by the Sequential Thinking MCP server:
### Using the MCP Tool
```
The Sequential Thinking MCP server provides the `sequentialthinking` tool.
Use it for:
- Breaking complex problems into thought sequences
- Tracking confidence and revising conclusions
- Building evidence chains with explicit reasoning
- Maintaining state across multiple reasoning steps
```
### Tool Parameters
```
thought: Your current thinking step
thoughtNumber: Current step number
totalThoughts: Estimated total steps needed
nextThoughtNeeded: Whether more steps are needed
isRevision: If revising previous thinking
needsMoreThoughts: If more analysis needed
```
### Integration Pattern
```
1. Start with initial thought defining the question
2. Gather evidence in subsequent thoughts
3. Form hypotheses with probability estimates
4. Test and verify in later thoughts
5. Conclude with confidence score
```
## Related Skills
- `brainstorming` -- Use brainstorming for open-ended creative exploration; use sequential thinking when you need structured evidence-based analysis
- `root-cause-tracing` -- Complements sequential thinking by providing the tracing methodology to follow during evidence gathering steps
- `systematic-debugging` -- Use systematic debugging for the overall debugging framework; sequential thinking adds rigorous documentation and confidence tracking
-123
View File
@@ -1,123 +0,0 @@
---
name: session-management
argument-hint: "[save/list/restore/index/load/status]"
description: >
Use when managing session state — including saving/restoring checkpoints, generating project structure indexes, loading project components into context, or checking project status. Trigger for keywords like "checkpoint", "save state", "restore", "index", "project structure", "load context", "status", "what's the state", or any request to manage the working session. Also activate when resuming work from a previous session or when needing to understand the current project layout.
---
# Session Management
## When to Use
- Saving or restoring session state (checkpoints)
- Generating project structure indexes
- Loading specific project components into context
- Checking current project status (git, tasks, PRs)
- Resuming work from a previous session
## When NOT to Use
- Git operations (commit, push, PR) — use `git-workflows`
- Branch management — use `using-git-worktrees`
- Launching parallel background work — use `dispatching-parallel-agents`
---
## Quick Reference
| Topic | Reference | Key content |
|-------|-----------|-------------|
| Checkpoints | `references/checkpoints.md` | Save/restore/list/delete session state |
| Project indexing | `references/indexing.md` | Generate PROJECT_INDEX.md, scan structure |
| Context loading | `references/loading.md` | Load components by category or path |
| Status checking | `references/status.md` | Git state, tasks, recent activity |
---
## Checkpoints
Save and restore conversation context using git-based state:
```bash
# Save current state
# → creates git stash + metadata in .claude/checkpoints/
/checkpoint save feature-auth
# List available checkpoints
/checkpoint list
# Restore a checkpoint
/checkpoint restore feature-auth
# Delete old checkpoint
/checkpoint delete old-checkpoint
```
Auto-checkpoint is suggested before major refactoring, context switches, and risky operations.
---
## Project Indexing
Generate a comprehensive project structure index:
```bash
# Generate PROJECT_INDEX.md
/index
# Shallow index (3 levels deep)
/index --depth=3
```
The index categorizes files by type: entry points, API routes, models, services, utilities, tests, and configuration.
---
## Context Loading
Load specific components into context for focused work:
| Category | What It Loads |
|----------|---------------|
| `api` | API routes and endpoints |
| `models` | Data models and types |
| `services` | Business logic services |
| `auth` | Authentication related |
| `db` | Database related |
| `tests` | Test files |
| `config` | Configuration files |
```bash
/load api # Load all API routes
/load src/services/user.ts # Load specific file
/load auth --related # Load auth + related files
/load --all --shallow # Quick overview of everything
```
---
## Status
Get current project status:
```bash
/status
```
Shows: git branch and status, in-progress/pending/completed tasks, recent commits, open PRs.
---
## Best Practices
1. **Checkpoint before context switches** — save state when switching tasks.
2. **Index periodically** — regenerate when project structure changes significantly.
3. **Load narrow, expand as needed** — start with specific components, add related files.
4. **Name checkpoints descriptively**`auth-progress` beats `checkpoint-1`.
---
## Related Skills
- `using-git-worktrees` — Isolated branch management for parallel work
- `dispatching-parallel-agents` — Launching parallel background tasks
@@ -1,48 +0,0 @@
# Checkpoints
## Save Checkpoint
```bash
/checkpoint save [name]
```
Creates a git stash with metadata in `.claude/checkpoints/[name].json`:
```json
{
"name": "feature-auth",
"created": "2026-04-19T14:30:00Z",
"git_stash": "stash@{0}",
"files_in_context": ["src/auth/login.ts", "src/auth/token.ts"],
"current_task": "Implementing JWT refresh",
"notes": "User-provided notes"
}
```
## List Checkpoints
```bash
/checkpoint list
```
## Restore Checkpoint
```bash
/checkpoint restore [name]
```
Applies git stash, loads metadata, summarizes restored context.
## Delete Checkpoint
```bash
/checkpoint delete [name]
```
## Auto-Checkpoint Triggers
Suggest checkpoints before:
- Major refactoring
- Context switches
- Risky operations
- Natural breakpoints in complex work
@@ -1,45 +0,0 @@
# Project Indexing
## Generate Index
Scan the project and create `PROJECT_INDEX.md`:
### Excluded Directories
`node_modules/`, `.git/`, `__pycache__/`, `dist/`, `build/`, `.next/`, `venv/`, `.venv/`, coverage, cache
### File Categories
- **Entry Points**: Main files, index files, app entry
- **API/Routes**: Endpoint definitions
- **Models/Types**: Data structures, schemas
- **Services**: Business logic
- **Utilities**: Helper functions
- **Tests**: Test files
- **Configuration**: Config files, env templates
### Output Format
```markdown
# Project Index: [Name]
Generated: [timestamp]
## Quick Navigation
| Category | Key Files |
|----------|-----------|
| Entry Points | [list] |
| API Routes | [list] |
## Directory Structure
[tree view]
## Key Files
### Entry Points
- `[path]` - [description]
## Dependencies
### External
- [package]: [purpose]
## Architecture Notes
[patterns observed]
```
@@ -1,49 +0,0 @@
# Context Loading
## Load Components
Load specific parts of the project into context for focused work.
### By Category
| Category | What It Loads |
|----------|---------------|
| `api` | API routes and endpoints |
| `models` | Data models and types |
| `services` | Business logic services |
| `utils` | Utility functions |
| `tests` | Test files |
| `config` | Configuration files |
| `auth` | Authentication related |
| `db` | Database related |
### By Path
```bash
/load src/services/user.ts # Specific file
/load src/auth/ # Directory
```
### Flags
| Flag | Description |
|------|-------------|
| `--all` | Load all key components |
| `--shallow` | Load only file summaries |
| `--deep` | Load full file contents |
| `--related` | Include related files |
### Output
```markdown
## Loaded Context
### Files Loaded (N)
- `path/to/file.ts` - [purpose]
### Key Components
- [Component]: [description]
### Ready For
- [suggested actions based on loaded context]
```
@@ -1,34 +0,0 @@
# Status Checking
## Project Status
Show current project state:
```bash
git status
git log --oneline -5
```
### Output Format
```markdown
## Project Status
### Git
- Branch: `feature/xyz`
- Status: Clean / X modified files
### Tasks
- In Progress: X
- Pending: Y
- Completed: Z
### Recent Commits
1. [commit message]
2. [commit message]
### Open PRs
- #123: [title]
```
Combines git state, TodoWrite tasks, and recent activity into a single snapshot.
+172
View File
@@ -0,0 +1,172 @@
---
name: shape-spec
user-invocable: true
description: >
Use when starting a non-trivial feature, change, or refactor before any plan or
code is written. Activate for keywords like "spec", "shape", "what should we
build", "requirements", "design this", "we need to", "let's add". Produces a
written spec covering goals, non-goals, constraints, acceptance criteria, and
open questions. Engineering-flavored — does not chase founder framings like
demand reality, wedge focus, or 10x outcomes.
---
# Shape Spec
## Overview
A short structured workflow that turns a vague request ("we need to add X") into a
written spec that can be reviewed and planned against. The skill exists because the
most expensive engineering bug is the wrong feature shipped well — and the second
most expensive is the right feature with a missing constraint nobody wrote down.
A spec is not a plan; it does not answer "how." It answers what, why, and what's
out of bounds. The deliverable is a one-to-three page Markdown document a teammate
can read in 5 minutes and sign off on, or push back against. Used before any plan
is written.
## When to Use
- A feature has been discussed informally and someone needs to write it down
- Multiple stakeholders disagree on scope and you need a shared text to argue against
- The change touches more than one module or service (multi-team coordination)
- A previous attempt at this work was abandoned or shipped wrong, and you're not
sure why
- You're about to start `/claudekit:write-plan` and realize you can't define
acceptance criteria yet
## When NOT to Use
- The change is one-line, single-file, single-author
- A spec already exists; you should be reviewing it, not rewriting it
- You're in the middle of debugging — debugging produces a fix, not a spec.
Use `/claudekit:investigate-root-cause`.
## Process
### Step 1: One-line summary
**Goal:** Force the spec into a single sentence before any further work.
**Inputs:** A feature request, ticket, conversation, or vague need.
**Actions:**
1. Write the spec's title and a single sentence below it: `This spec proposes
<X> so that <Y>.` X is concrete (a behavior, an artifact). Y is the engineering
outcome (not the business outcome — leave that to product docs).
2. If you cannot write the sentence in one try, the request is too vague. Ask the
requester one clarifying question. Don't fill in the X and Y with assumptions.
**Output:** The title and the one-sentence summary at the top of the spec file.
### Step 2: Goals and non-goals
**Goal:** Bound the work explicitly.
**Inputs:** The Step 1 summary.
**Actions:**
1. Write a `## Goals` section. 3-7 bullets. Each goal is a concrete, observable
outcome — something you could write a test for, even if you won't.
2. Write a `## Non-Goals` section. 3-5 bullets. Each non-goal is a thing a
reasonable reader might assume is in scope but is not.
3. The non-goals list is more important than the goals list. Goals expand naturally
in conversation; non-goals only get pinned down when you write them.
**Output:** Two bulleted sections.
### Step 3: Constraints
**Goal:** List every external requirement the implementation must respect.
**Inputs:** The goals from Step 2 plus your knowledge of the existing system.
**Actions:**
1. Write a `## Constraints` section grouped under sub-headings:
- **Compatibility** — APIs, schemas, protocols that must not break
- **Performance** — latency budgets, throughput floors, payload sizes
- **Security/Compliance** — auth, data residency, audit logging requirements
- **Operational** — supported environments, runtime versions, infra dependencies
2. Each constraint is one line, concrete, falsifiable. "Must be performant" is
not a constraint. "p95 latency under 200ms at 1k RPS" is.
3. If you can't answer a constraint, mark it `OPEN` and put it in Step 5's
open questions instead of guessing.
**Output:** A constraints section with at least one entry under each subheading
(or `None` explicitly stated).
### Step 4: Acceptance criteria
**Goal:** Define "done" in terms a tester could check.
**Inputs:** The goals and constraints.
**Actions:**
1. Write `## Acceptance Criteria` as a numbered list.
2. Each criterion is in the form `Given <state>, when <action>, then <expected>`
OR `<observable behavior> is <verifiable measurement>`. No "system should be
robust"; instead "system handles 10k concurrent connections without dropping
below p99 < 500ms."
3. At least one criterion per goal. More if a goal has multiple observable
facets.
**Output:** A numbered list of falsifiable criteria.
### Step 5: Open questions
**Goal:** Surface what you don't know before someone discovers it mid-implementation.
**Inputs:** Honest reflection on the spec.
**Actions:**
1. Write `## Open Questions`. Each question:
- is concrete enough that someone could answer it,
- names who is likely to know,
- states the impact of getting it wrong.
2. If the spec has no open questions, you're not paying enough attention. There
is always at least one.
**Output:** A list of questions, each with a `who knows` and `impact if wrong`
note.
## Rationalizations
| Excuse | Why it sounds reasonable | Why it's wrong | What to do instead |
|---|---|---|---|
| "We can figure this out as we go." | Some specs really are just-in-time. Over-specifying upfront is a known failure mode. | "Figure it out as we go" is the line said before the implementation reveals a constraint nobody wrote down, scope creeps to cover that constraint, and the work doubles. The cost of writing a 1-page spec is 30 minutes; the cost of discovering the missing constraint at code-review time is the round trip plus a partial rewrite. | If the change touches one file and one author, skip the spec. If it touches more than one of either, the 30 minutes is cheaper than the round trip. |
| "The non-goals are obvious — I don't need to write them down." | Stating the obvious feels condescending in writing. | The non-goals only feel obvious to the spec author. The reviewer who pushes back on "why didn't you also handle X" hasn't read your mind, only your spec. Unwritten non-goals get implemented anyway, doubling the work, or get cut at the end, leaving someone disappointed. | Write the 3-5 non-goals even if they feel obvious. They're not for you; they're for the reviewer who hasn't been in your head. |
| "We'll add tests later — acceptance criteria can be vague for now." | Acceptance criteria do mature during implementation. Premature specificity can lock in the wrong thing. | Vague acceptance criteria are how "done" becomes negotiable. Without falsifiable criteria, the engineer who finishes and the reviewer who signs off are negotiating on vibes. The work merges, then someone discovers the missing case in production. | Write at least one falsifiable criterion per goal. If you can't, the goal isn't concrete enough — fix the goal first. |
| "There are no open questions, this one's clear." | Sometimes a spec really is well-understood. Forcing questions for show is performative. | "No open questions" almost always means "I haven't looked hard enough." Every spec interacts with infra, data, or upstream/downstream systems, each of which has assumptions you haven't audited. The questions are real; you just haven't asked them. | List at least one open question. If you can't find a real one, write down the assumption you're least sure of as a question — "Are we sure component X behaves this way under load?" That assumption is your weakest link. |
| "We don't need a constraints section for an internal tool." | Internal tools are real, and the formality of constraint-writing fits external APIs better. | Internal tools have constraints too — runtime version, deploy environment, who can call them, data sensitivity. Skipping the section because "internal" is how the internal tool ends up using a deprecated runtime, a soon-to-be-removed library, or storing data the org isn't allowed to log. | Write the constraints section. For internal tools, "Compatibility: must run on the org's standard Python 3.11 runtime" and "Security: must not log PII" are short and they matter. |
## Evidence Requirements
| Checkpoint | Required artifact | What "no evidence" looks like |
|---|---|---|
| End of Step 1 | One-sentence summary in `<X> so that <Y>` form | "It's a feature for the dashboard." |
| End of Step 2 | Goals + Non-Goals sections, both populated | "Goals are obvious from context." |
| End of Step 3 | Constraints section with all four subheadings present | "I'll add constraints if the reviewer asks." |
| End of Step 4 | Acceptance criteria, one falsifiable item per goal | "It should work well." |
| End of Step 5 | At least one open question with who/impact annotations | "Nothing open at this time." |
## Red Flags
- The non-goals list is empty or shorter than 2 items. You haven't bounded the scope.
- A goal cannot map to any acceptance criterion. The goal is too abstract to ship.
- The spec exceeds 3 pages. You are writing a design doc, not a spec. Stop and
refactor — most of this content belongs in a plan or design doc downstream.
- An acceptance criterion contains words like "should", "performant", "robust",
"user-friendly". None of these are testable. Replace with measurements.
- The spec is written entirely in passive voice. You are hiding the actor and the
decision-maker. Rewrite in the voice of the team that will own the work.
## References
- *A Philosophy of Software Design*, John Ousterhout (Yaknyam Press, 2nd ed.
2021), Chapter 14 "Choosing Names" and the principle "deep modules over wide
modules" — useful when defining what your spec is and is not. The non-goals
section operationalizes Ousterhout's "draw the box around the module" advice
before code is written.
-237
View File
@@ -1,237 +0,0 @@
---
name: subagent-driven-development
description: >
Use when executing implementation plans with independent tasks in the current session. Trigger when 3+ independent tasks exist, when a plan is ready to execute with the Agent tool, or when the user says "use subagents", "dispatch agents", "parallel implementation". Also activate when tasks touch different files/modules with no shared state, making them safe to parallelize via Claude Code's Agent tool.
---
# Subagent-Driven Development
## When to Use
- A written plan exists with 3+ independent tasks
- Tasks touch different files/modules with no shared state
- Each task has a clear verification command (test suite, build)
- You want faster execution through parallelism
## When NOT to Use
- Tasks have sequential dependencies (task B needs task A's output)
- Tasks modify the same files (will cause merge conflicts)
- The codebase is unfamiliar and you need to explore first
- Fewer than 3 tasks (overhead of dispatch isn't worth it)
---
## Task Decomposition
### Identifying independent units
Tasks are independent when they answer **NO** to all three questions:
| Question | If YES → Sequential |
|----------|---------------------|
| Does task B read files that task A writes? | Shared state |
| Does task B import modules that task A creates? | Dependency chain |
| Do both tasks modify the same file? | Merge conflict |
### Good decomposition example
```markdown
## Plan: User Order Feature
### Task 1 — Backend API (independent)
- Files: src/api/orders.py, tests/test_orders.py
- Verify: pytest tests/test_orders.py -v
### Task 2 — Frontend Component (independent)
- Files: src/components/order-form.tsx, src/components/order-form.test.tsx
- Verify: npm test -- --testPathPattern=order-form
### Task 3 — Database Migration (independent)
- Files: migrations/003_create_orders.sql, tests/test_orders_migration.py
- Verify: pytest tests/test_orders_migration.py -v
```
All three tasks touch different files, different test suites, no shared imports.
---
## Subagent Prompt Template
Each subagent prompt must be **self-contained** — the agent has no context from your conversation.
```markdown
## Task
[One-sentence goal]
## Context
- Project: [framework, language, key conventions]
- Architecture: [relevant module structure]
- Related code: [existing patterns to follow — file paths]
## Files to Create/Modify
- [exact file paths]
## Constraints
- [validation rules, error format, naming conventions]
- [test-first: write failing test, then implement]
## Verification
- Run: [exact command]
- Expected: [what success looks like]
```
### Python/FastAPI example prompt
```markdown
## Task
Implement POST /api/orders endpoint with Pydantic validation.
## Context
- Project: FastAPI + SQLAlchemy async + Pydantic v2
- Architecture: src/api/ for routes, src/models/ for SQLAlchemy, src/schemas/ for Pydantic
- Follow pattern in: src/api/users.py (dependency injection, error handling)
## Files to Create/Modify
- src/schemas/order.py (CreateOrderRequest, OrderResponse)
- src/api/orders.py (POST endpoint)
- tests/test_orders.py (test with httpx.AsyncClient)
## Constraints
- Use Depends(get_db) for database session injection
- Return 201 on success, RFC 9457 ProblemDetails on error
- Test-first: write failing test, verify red, implement, verify green
## Verification
- Run: pytest tests/test_orders.py -v
- Expected: all tests pass, no warnings
```
### TypeScript/NestJS example prompt
```markdown
## Task
Implement OrdersModule with CRUD controller and Prisma service.
## Context
- Project: NestJS + Prisma + class-validator
- Architecture: src/<feature>/ modules with controller, service, dto/, entities/
- Follow pattern in: src/users/ (module structure, DTO validation, Prisma injection)
## Files to Create/Modify
- src/orders/orders.module.ts
- src/orders/orders.controller.ts
- src/orders/orders.service.ts
- src/orders/dto/create-order.dto.ts
- src/orders/orders.controller.spec.ts
## Constraints
- Use ValidationPipe with whitelist: true
- PartialType for UpdateOrderDto
- ProblemDetails error format via global exception filter
## Verification
- Run: npm test -- --testPathPattern=orders
- Expected: all tests pass
```
### React/Next.js example prompt
```markdown
## Task
Build OrderForm component with validation and submission.
## Context
- Project: Next.js App Router + shadcn/ui + react-hook-form + Zod
- Architecture: src/components/ for shared, src/app/(routes)/ for pages
- Follow pattern in: src/components/user-form.tsx
## Files to Create/Modify
- src/components/order-form.tsx
- src/components/order-form.test.tsx
## Constraints
- Client component ('use client')
- Zod schema for validation, react-hook-form for state
- shadcn/ui Form, Input, Button components
- Test with Testing Library + vitest
## Verification
- Run: npx vitest run src/components/order-form.test.tsx
- Expected: all tests pass
```
---
## Execution Pattern
### 1. Dispatch all independent tasks
```markdown
Launch Agent 1: Backend task (background)
Launch Agent 2: Frontend task (background)
Launch Agent 3: Database task (background)
```
### 2. Collect and verify results
As each agent completes:
- Read the files it created/modified
- Run its verification command
- Check for quality issues
### 3. Review between tasks
After all agents complete, run a review pass:
- Do the pieces integrate correctly?
- Are there any naming inconsistencies?
- Run the full test suite (not just individual task tests)
### 4. Integration verification
```bash
# Python
pytest -v --cov=src
# TypeScript
npm test && npm run build
# Full stack
pytest -v && npm test && npm run build
```
---
## Error Handling
| Scenario | Action |
|----------|--------|
| Agent task fails verification | Retry once with error context in prompt |
| Agent produces wrong pattern | Fix manually, don't retry |
| 2+ failures on same task | Stop, investigate root cause |
| Merge conflict between agents | Tasks weren't truly independent — fix decomposition |
### Retry prompt template
```markdown
## Task (retry — previous attempt failed)
[Same task as before]
## Previous Error
[Exact error message/test failure output]
## What Went Wrong
[Your analysis of why it failed]
## Additional Context
[Any clarification the agent needs]
```
---
## Related Skills
- `dispatching-parallel-agents` — When to parallelize and how to manage concurrent work
- `executing-plans` — Sequential plan execution with review gates
- `using-git-worktrees` — Give each subagent an isolated workspace
- `writing-plans` — Create plans with proper task decomposition for subagent execution
-356
View File
@@ -1,356 +0,0 @@
---
name: systematic-debugging
user-invocable: true
description: >
Use when encountering ANY bug, error, test failure, or unexpected behavior. Activate for keywords like "bug", "error", "failing", "broken", "doesn't work", "unexpected", "crash", "exception", "TypeError", "undefined", stack traces, or any error message. Also trigger when tests fail unexpectedly, when behavior differs from expectations, when investigating production incidents, or when flaky/intermittent issues appear. ALWAYS investigate root cause before proposing fixes -- never guess at solutions.
---
# Systematic Debugging
## When to Use
- Bug reports with unclear cause
- Errors appearing in production
- Tests failing unexpectedly
- Intermittent/flaky issues
- Complex multi-component failures
## When NOT to Use
- Known issues with documented fixes already available in the codebase or runbook
- Simple typo or syntax errors that are immediately obvious from the error message
- Configuration issues where the fix is simply updating an environment variable or config value
---
## The Four Phases
### Phase 1: Root Cause Investigation
**Goal**: Understand what's happening before attempting to fix.
**Steps**:
1. **Read error messages carefully**
```markdown
- What is the exact error message?
- What is the stack trace?
- What line numbers are mentioned?
- What values are shown?
```
2. **Reproduce consistently**
```markdown
- Can you trigger the bug reliably?
- What exact steps reproduce it?
- What environment is required?
- Document the reproduction steps
```
3. **Track recent changes**
```markdown
- What changed recently?
- git log --oneline -20
- When did it last work?
- What was deployed?
```
4. **Gather evidence**
```markdown
- Collect logs
- Check monitoring/metrics
- Review related code
- Note any patterns
```
5. **Add instrumentation** (for multi-component systems)
```typescript
// Add diagnostic logging at each boundary
console.error('[DEBUG] Input received:', JSON.stringify(input));
console.error('[DEBUG] After validation:', JSON.stringify(validated));
console.error('[DEBUG] Before database call:', JSON.stringify(query));
console.error('[DEBUG] Database result:', JSON.stringify(result));
```
```python
# Python equivalent — add diagnostic logging at boundaries
import logging
logger = logging.getLogger(__name__)
async def get_user(user_id: str, db: AsyncSession) -> User:
logger.error(f"get_user called with user_id={user_id!r}, type={type(user_id)}")
user = await db.get(User, user_id)
logger.error(f"get_user result: {user!r}")
if not user:
raise HTTPException(status_code=404, detail=f"User {user_id} not found")
return user
```
---
### Phase 2: Pattern Analysis
**Goal**: Find comparable working code to identify differences.
**Steps**:
1. **Find working code**
```markdown
- Is there similar functionality that works?
- What did this code look like before?
- Are there reference implementations?
```
2. **Study reference thoroughly**
```markdown
- How does the working version handle this case?
- What dependencies does it use?
- What assumptions does it make?
```
3. **Identify differences**
```markdown
- What's different between working and broken?
- Configuration differences?
- Data differences?
- Environment differences?
```
4. **Understand dependencies**
```markdown
- What does this code depend on?
- What depends on this code?
- Are dependencies behaving correctly?
```
---
### Phase 3: Hypothesis and Testing
**Goal**: Form and test a specific theory about the cause.
**Steps**:
1. **Form specific hypothesis**
```markdown
Write it down explicitly:
"The bug occurs because [X] causes [Y] when [Z]"
Example:
"The bug occurs because the cache returns stale data
when the user's session expires during an active request"
```
2. **Test with minimal changes**
```markdown
- Change ONE variable at a time
- Don't combine multiple fixes
- Verify results after each change
```
3. **Validate hypothesis**
```markdown
- Does the fix address the hypothesis?
- Can you explain WHY it works?
- Does it make the bug impossible, not just unlikely?
```
---
### Phase 4: Implementation
**Goal**: Fix properly with verification.
**Steps**:
1. **Write failing test first**
```typescript
it('should handle expired session during request', () => {
const session = createExpiredSession();
const result = processRequest(session);
expect(result.error).toBe('SESSION_EXPIRED');
});
```
```python
# Python equivalent
async def test_expired_session_returns_401(client, expired_token):
response = await client.get(
"/api/me",
headers={"Authorization": f"Bearer {expired_token}"},
)
assert response.status_code == 401
```
2. **Implement single targeted fix**
```typescript
// Fix addresses root cause, not symptom
function processRequest(session: Session) {
if (session.isExpired()) {
return { error: 'SESSION_EXPIRED' };
}
// ... rest of logic
}
```
```python
# Python equivalent — add expiry check in dependency
async def get_current_user(token: str = Depends(oauth2_scheme)):
payload = decode_token(token)
if payload.exp < datetime.utcnow().timestamp():
raise HTTPException(status_code=401, detail="Token expired")
return await get_user(payload.sub)
```
3. **Verify fix works**
```bash
# TypeScript
npm test -- --grep "expired session"
# Python
pytest tests/test_auth.py -v -k "expired_session"
```
4. **Verify no regressions**
```bash
# TypeScript
npm test
# Python
pytest -v
```
---
## The Three-Fix Rule
**If three or more fixes fail consecutively, STOP.**
This signals an architectural problem, not a simple bug:
```markdown
Fix attempt 1: Failed
Fix attempt 2: Failed
Fix attempt 3: Failed
STOP: This is not a bug - this is a design problem.
Action: Discuss with user/team before proceeding
- Explain what's been tried
- Explain why it's not working
- Propose architectural changes
```
---
## Key Principles
### Never Skip Error Details
```markdown
BAD: "There's an error somewhere"
GOOD: "TypeError: Cannot read property 'id' of undefined
at UserService.getUser (user-service.ts:42)"
```
### Reproduce Before Investigating
```markdown
BAD: "I think I know what's wrong" (starts coding)
GOOD: "Let me reproduce this first" (writes repro steps)
```
### Trace Backward to Origin
```markdown
BAD: Fix where error appears
GOOD: Trace data backward to find where it became invalid
```
### One Change Per Test
```markdown
BAD: "I changed A, B, and C - now it works!"
(Which one fixed it? Are the others safe?)
GOOD: "I changed A - still broken.
I reverted A and changed B - now it works.
B was the fix."
```
---
## Debugging Checklist
Before attempting any fix:
- [ ] Error message fully read and understood
- [ ] Bug reproduced consistently
- [ ] Recent changes reviewed
- [ ] Evidence gathered (logs, traces)
- [ ] Hypothesis written down
- [ ] Similar working code identified
- [ ] Root cause identified (not just symptom)
Before declaring fixed:
- [ ] Failing test written
- [ ] Fix implemented
- [ ] Test passes
- [ ] No regressions (full test suite passes)
- [ ] Fix explained (can articulate why it works)
---
## Stack-Specific Debugging Tools
| Stack | Log Inspection | REPL Debug | Test Isolation |
|-------|---------------|------------|----------------|
| Python/FastAPI | `logging` + `structlog` | `breakpoint()` / `pdb` | `pytest -x -k test_name` |
| TypeScript/NestJS | NestJS `Logger` | `debugger` + `--inspect` | `jest --testNamePattern` |
| Next.js | `console.error` + React DevTools | Browser DevTools | `vitest run file.test.ts` |
| React | React DevTools + `useDebugValue` | Browser DevTools | `vitest run --reporter=verbose` |
| Django | `django.utils.log` + `DEBUG=True` | `breakpoint()` / `pdb` | `python manage.py test app.tests.TestCase.test_name` |
### Python-specific debugging tips
```python
# Quick pdb breakpoint (Python 3.7+)
breakpoint() # drops into pdb at this line
# Conditional breakpoint
if user_id == "problematic_id":
breakpoint()
# SQLAlchemy query logging — see actual SQL
import logging
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
# FastAPI request/response logging middleware
@app.middleware("http")
async def log_requests(request: Request, call_next):
logger.info(f"{request.method} {request.url}")
response = await call_next(request)
logger.info(f"Status: {response.status_code}")
return response
```
### TypeScript-specific debugging tips
```typescript
// NestJS — enable verbose logging
const app = await NestFactory.create(AppModule, { logger: ['verbose'] });
// Prisma — log queries
const prisma = new PrismaClient({ log: ['query', 'info', 'warn', 'error'] });
// Next.js — debug server components
// Add to next.config.js
module.exports = { logging: { fetches: { fullUrl: true } } };
```
---
## Related Skills
- `root-cause-tracing` -- Deep-dive technique for tracing issues back through complex dependency chains
- `defense-in-depth` -- Add defensive layers to prevent similar bugs from recurring
- `verification-before-completion` -- Ensures the fix is actually verified with evidence before claiming the bug is resolved
@@ -1,155 +0,0 @@
# Systematic Debugging Checklist
Step-by-step process for debugging any issue. Follow the steps in order — skipping ahead is the most common cause of wasted debugging time.
---
## Step 1: Reproduce
**Goal:** Confirm you can trigger the bug on demand.
- [ ] **Get the exact steps** — What did the user do? What input? What sequence?
- [ ] **Reproduce it yourself** — If you can't reproduce it, you can't verify a fix
- [ ] **Find the minimal reproduction** — Strip away everything that isn't necessary to trigger the bug
- [ ] **Document the environment**
- OS and version
- Language/runtime version
- Dependency versions
- Environment variables or config that matters
- [ ] **Note what you expect vs. what actually happens**
**If you can't reproduce:**
- Check if it's environment-specific (OS, browser, node version)
- Check if it's state-dependent (specific data, race condition, cache)
- Add logging and wait for it to happen again
---
## Step 2: Gather Evidence
**Goal:** Collect all available information before forming theories.
- [ ] **Read the error message carefully** — The answer is often in the message. Read the full text, not just the first line.
- [ ] **Read the full stack trace** — Identify which line in YOUR code is the entry point (ignore framework internals at first)
- [ ] **Check logs** — Application logs, server logs, browser console
- [ ] **Check timestamps** — When did it start? Does it correlate with a deployment, config change, or data change?
- [ ] **Check recent changes**
```bash
git log --oneline -20
git diff HEAD~5..HEAD -- path/to/suspect/area/
```
- [ ] **Check monitoring/metrics** — Error rates, latency, resource usage
- [ ] **Search for the error** — Has someone on the team seen this before? Check issues, Slack, docs.
---
## Step 3: Form Hypotheses
**Goal:** Generate candidate explanations ranked by likelihood.
- [ ] **What changed recently?** — The most common cause of new bugs is new code
- [ ] **What assumptions might be wrong?** — About input format, data state, timing, permissions
- [ ] **List 2-3 hypotheses** — Write them down explicitly:
1. [Most likely] ...
2. [Possible] ...
3. [Less likely but worth checking] ...
- [ ] **For each hypothesis, define what evidence would confirm or refute it**
**Common root causes to consider:**
- Null/undefined where a value was expected
- Off-by-one or boundary condition
- Race condition or timing issue
- Stale cache or state
- Environment difference (local vs. prod)
- Dependency version mismatch
- Incorrect assumption about API contract
---
## Step 4: Test Hypotheses
**Goal:** Confirm or eliminate each hypothesis with evidence.
- [ ] **Start with the most likely hypothesis**
- [ ] **Add targeted logging** — Log the specific values your hypothesis predicts will be wrong
```python
# Python
import logging
logger = logging.getLogger(__name__)
logger.debug(f"Value at suspect point: {value!r}, type: {type(value)}")
```
```javascript
// JavaScript
console.log('Value at suspect point:', JSON.stringify(value), typeof value);
```
- [ ] **Use git bisect for regressions** — Find the exact commit that introduced the bug
```bash
git bisect start
git bisect bad # Current commit is broken
git bisect good v1.2.0 # This version was working
# Test each commit bisect offers, mark good/bad
```
- [ ] **Isolate components** — Test each component in isolation to narrow the scope
- [ ] **Use a debugger for complex state issues**
### Debugger Quick Reference
| Language | Tool | Start Command |
|----------|------|--------------|
| Python | pdb | `import pdb; pdb.set_trace()` or `breakpoint()` |
| Python | logging | `logging.basicConfig(level=logging.DEBUG)` |
| Python | traceback | `import traceback; traceback.print_exc()` |
| JavaScript | debugger | `debugger;` statement in code |
| JavaScript | console | `console.log()`, `console.trace()`, `console.table()` |
| JavaScript | Chrome DevTools | Open DevTools > Sources > set breakpoint |
| TypeScript | Node inspect | `node --inspect -r ts-node/register script.ts` |
---
## Step 5: Fix and Verify
**Goal:** Apply the minimal correct fix and prove it works.
- [ ] **Make the smallest fix possible** — Fix the bug, not the whole file. One concern per commit.
- [ ] **Write a regression test** — A test that fails without your fix and passes with it
```python
def test_handles_empty_input_without_crash():
"""Regression test for bug #123 — empty input caused TypeError."""
result = process(input_data="")
assert result == expected_default
```
- [ ] **Verify the fix resolves the original reproduction**
- [ ] **Run the full test suite** — Confirm no side effects
- [ ] **Check related code paths** — Is the same bug pattern present elsewhere?
```bash
# Search for similar patterns
grep -rn "similar_pattern" src/
```
- [ ] **Test edge cases around the fix** — Boundary values, null inputs, concurrent access
---
## Step 6: Document and Prevent
**Goal:** Prevent this class of bug from recurring.
- [ ] **Write a clear commit message** explaining what was wrong and why the fix works
- [ ] **Update documentation** if the bug revealed a misunderstanding
- [ ] **Consider systemic fixes:**
- Could a type system catch this? (Add types)
- Could a linter rule catch this? (Add rule)
- Could input validation catch this? (Add validation)
- Could monitoring catch this sooner? (Add alert)
---
## Quick Reference: Debugging Anti-Patterns
| Anti-Pattern | What to Do Instead |
|-------------|-------------------|
| Changing random things until it works | Form a hypothesis, test it, iterate |
| Debugging in production | Reproduce locally first |
| Reading code for hours without running it | Add a log statement and run it |
| Fixing the symptom, not the cause | Ask "why?" until you reach the root |
| Not writing a regression test | Always write one before closing the bug |
| Debugging alone for too long | Ask for help after 30 minutes of no progress |
-392
View File
@@ -1,392 +0,0 @@
---
name: test-driven-development
user-invocable: true
description: >
Use when writing new features, fixing bugs, or changing any behavior in production code. Activate for keywords like "implement", "add feature", "fix bug", "write code", "build", "create endpoint", "add functionality", or any task that will result in production code changes. Also trigger when the user asks to refactor existing code, when tests need to be written, or when someone says "TDD". This skill should be the default for ALL implementation work -- no production code without a failing test first.
---
# Test-Driven Development (TDD)
## When to Use
- New feature development
- Bug fixes (write test that reproduces bug first)
- Refactoring (ensure tests exist before changing)
- Any behavior change
## When NOT to Use
- Prototyping or throwaway code with explicit user approval to skip tests
- Configuration-only changes (e.g., environment variables, CI config, linter rules)
- Documentation updates that do not affect runtime behavior
---
## The Red-Green-Refactor Cycle
### 1. RED: Write Failing Test
Write a minimal test demonstrating the desired behavior:
```typescript
describe('calculateTotal', () => {
it('should sum item prices', () => {
const items = [{ price: 10 }, { price: 20 }];
expect(calculateTotal(items)).toBe(30);
});
});
```
**Python equivalent:**
```python
# tests/test_cart.py
def test_calculate_total_sums_item_prices():
items = [{"price": 10}, {"price": 20}]
assert calculate_total(items) == 30
```
### 2. VERIFY RED: Confirm Test Fails
Run the test and confirm it fails **for the right reason**:
```bash
# TypeScript
npm test -- --grep "sum item prices"
# Expected: FAIL — calculateTotal is not defined
# Python
pytest tests/test_cart.py -v
# Expected: FAIL — NameError: name 'calculate_total' is not defined
```
**Critical**: The failure should be because the feature doesn't exist, not because of typos or syntax errors.
### 3. GREEN: Write Minimal Code
Write the simplest code that makes the test pass:
```typescript
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
```
```python
# src/services/cart.py
def calculate_total(items: list[dict]) -> int:
return sum(item["price"] for item in items)
```
**Don't over-engineer**. If the test passes with simple code, stop.
### 4. VERIFY GREEN: Confirm Test Passes
Run the test and confirm it passes:
```bash
# TypeScript
npm test -- --grep "sum item prices"
# Expected: PASS
# Python
pytest tests/test_cart.py -v
# Expected: PASS
```
### 5. REFACTOR: Clean Up
With green tests, refactor safely:
- Extract functions
- Rename variables
- Remove duplication
- Run tests after each change
---
## The Non-Negotiable Rule
**NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST**
This is not a guideline. It's a rule.
### What If I Already Wrote Code?
Delete it. Completely.
```
WRONG: "I'll keep this code as reference while writing tests"
RIGHT: Delete the code, write test, rewrite implementation
```
### Why So Strict?
- Code written before tests wasn't driven by tests
- Keeping it as reference leads to rationalization
- Tests written after code often just verify what was written
- True TDD produces different (usually better) designs
---
## Test Quality Standards
### One Behavior Per Test
```typescript
// BAD: Multiple behaviors
it('should validate and save user', () => {
expect(validateUser(user)).toBe(true);
expect(saveUser(user)).toBe(1);
});
// GOOD: Single behavior
it('should validate user email format', () => {
expect(validateUser({ email: 'test@example.com' })).toBe(true);
});
it('should save valid user', () => {
const user = createValidUser();
expect(saveUser(user)).toBe(1);
});
```
### Clear Naming
Test names should describe the behavior:
```typescript
// BAD
it('test1', () => {});
it('calculateTotal', () => {});
// GOOD
it('should return 0 for empty cart', () => {});
it('should apply discount when coupon is valid', () => {});
```
### Real Code Over Mocks
Use real implementations when possible:
```typescript
// PREFER: Real database (test container)
const db = await startTestDatabase();
const result = await userRepo.save(user);
// AVOID: Excessive mocking
const mockDb = { save: jest.fn().mockResolvedValue(1) };
```
```python
# PREFER: Real database (test fixture)
@pytest.fixture
async def db_session(async_engine):
async with AsyncSession(async_engine) as session:
yield session
async def test_save_user(db_session):
user = User(email="test@example.com", name="Test")
db_session.add(user)
await db_session.commit()
assert user.id is not None
# AVOID: Excessive mocking
def test_save_user_mocked():
mock_db = MagicMock()
mock_db.add.return_value = None # proves nothing
```
### Test Observable Behavior
Test what the code does, not how it does it:
```typescript
// BAD: Testing implementation
it('should call helper function', () => {
calculateTotal(items);
expect(helperFn).toHaveBeenCalled();
});
// GOOD: Testing behavior
it('should return correct total', () => {
expect(calculateTotal(items)).toBe(30);
});
```
---
## Common Rationalizations (Reject These)
### "I'll write tests after"
Tests written after code verify what was written, not what should happen. The test can't prove the code is correct if it was shaped to match existing code.
### "Manual testing is enough"
Ad-hoc testing is not systematic. It misses edge cases, isn't repeatable, and doesn't prevent regressions.
### "This code is too simple to test"
Simple code breaks too. A test takes seconds and provides permanent verification.
### "I don't have time"
TDD is faster in the medium term. Debugging time saved far exceeds test-writing time.
### "I already wrote it, might as well keep it"
Sunk cost fallacy. Delete and rewrite properly.
---
## Edge Cases to Test
Always include tests for:
- Empty inputs
- Null/undefined values
- Boundary conditions
- Error scenarios
- Large inputs
- Invalid inputs
```typescript
describe('calculateTotal', () => {
it('should return 0 for empty array', () => {
expect(calculateTotal([])).toBe(0);
});
it('should handle null items array', () => {
expect(() => calculateTotal(null)).toThrow();
});
it('should handle negative prices', () => {
const items = [{ price: -10 }, { price: 20 }];
expect(calculateTotal(items)).toBe(10);
});
});
```
```python
def test_calculate_total_empty_list():
assert calculate_total([]) == 0
def test_calculate_total_none_raises():
with pytest.raises(TypeError):
calculate_total(None)
def test_calculate_total_negative_prices():
items = [{"price": -10}, {"price": 20}]
assert calculate_total(items) == 10
```
---
## Framework-Specific TDD Patterns
### FastAPI endpoint TDD
Write the test with `httpx.AsyncClient` first, then implement the route:
```python
# 1. RED — test first
import pytest
from httpx import AsyncClient
@pytest.mark.anyio
async def test_create_order_returns_201(client: AsyncClient):
response = await client.post("/api/orders", json={"item": "widget", "quantity": 2})
assert response.status_code == 201
assert response.json()["item"] == "widget"
# 2. GREEN — implement route
from fastapi import APIRouter, status
from pydantic import BaseModel
router = APIRouter(prefix="/api/orders")
class CreateOrderRequest(BaseModel):
item: str
quantity: int
@router.post("", status_code=status.HTTP_201_CREATED)
async def create_order(body: CreateOrderRequest):
return {"id": "ord_1", "item": body.item, "quantity": body.quantity}
```
### NestJS endpoint TDD
Write the test with `supertest` first, then implement the controller:
```typescript
// 1. RED — test first
it('POST /orders — creates order', () =>
request(app.getHttpServer())
.post('/orders')
.send({ item: 'widget', quantity: 2 })
.expect(201)
.expect((res) => {
expect(res.body.item).toBe('widget');
}));
// 2. GREEN — implement controller
@Post()
@HttpCode(HttpStatus.CREATED)
create(@Body() dto: CreateOrderDto) {
return this.ordersService.create(dto);
}
```
### React component TDD
Write the test with Testing Library first, then implement the component:
```typescript
// 1. RED — test first
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
it('should call onSubmit with form data', async () => {
const onSubmit = vi.fn();
render(<OrderForm onSubmit={onSubmit} />);
await userEvent.type(screen.getByLabelText('Item'), 'widget');
await userEvent.click(screen.getByRole('button', { name: /submit/i }));
expect(onSubmit).toHaveBeenCalledWith(expect.objectContaining({ item: 'widget' }));
});
// 2. GREEN — implement component
export function OrderForm({ onSubmit }: { onSubmit: (data: OrderData) => void }) {
// minimal implementation to pass the test
}
```
---
## TDD Catches Bugs
The methodology catches bugs before commit:
- Writing test first forces you to think about edge cases
- Seeing test fail proves it can catch failures
- Green bar confirms the fix works
- Test prevents regression forever
This is faster than:
1. Write code
2. Manual test (miss edge case)
3. Ship
4. Bug reported
5. Debug
6. Fix
7. Ship again
---
## Related Skills
- `verification-before-completion` -- Ensures tests are actually run and passing before claiming work is done
- `testing-anti-patterns` -- Avoid common testing mistakes that undermine TDD effectiveness
- `pytest` -- Python-specific testing patterns and best practices for TDD
- `vitest` -- TypeScript/JavaScript-specific testing patterns and best practices for TDD
- `writing-plans` — Planning implementation tasks for TDD workflow
@@ -1,150 +0,0 @@
# TDD Decision Tree
Quick reference for deciding when and how to apply Test-Driven Development.
---
## Decision: Should I Use TDD Here?
```
Is this code...
├─ Business logic or data transformation?
│ └─ YES: Always TDD. No exceptions.
├─ An API endpoint (REST, GraphQL, RPC)?
│ └─ YES: Always TDD. Write request/response tests first.
├─ A bug fix?
│ └─ YES: Always TDD. Write a failing test that reproduces the bug first.
├─ A utility function or helper?
│ └─ YES: Always TDD. These are the easiest to TDD — pure input/output.
├─ A database query or repository method?
│ └─ YES: Always TDD. Test the query behavior, not the SQL syntax.
├─ A state machine or workflow?
│ └─ YES: Always TDD. Test each transition.
├─ UI layout or styling (CSS, Tailwind, visual positioning)?
│ └─ TDD optional. Visual output is hard to assert meaningfully.
│ Use snapshot tests or visual regression tools instead.
├─ Configuration or environment setup?
│ └─ TDD optional. Test that config loads correctly, but don't
│ TDD every config value. Integration tests are more useful.
├─ A database migration?
│ └─ TDD optional. Test that migration runs forward and backward.
│ Don't TDD the migration SQL itself.
├─ A prototype or spike?
│ └─ TDD optional. Spikes are throwaway. But if the spike becomes
│ real code, stop and add tests before continuing.
├─ Third-party integration glue code?
│ └─ TDD the contract, not the integration. Write tests against
│ the interface you expect, mock the external service.
└─ Generated code (scaffolding, boilerplate)?
└─ TDD optional. Test the generator if you wrote it.
Don't TDD the generated output.
```
---
## Decision Factors
When the tree above doesn't give a clear answer, weigh these factors:
| Factor | Favors TDD | Favors Test-After |
|--------|-----------|-------------------|
| **Testability** | Clear inputs/outputs, deterministic | Heavy side effects, UI rendering |
| **Complexity** | Multiple branches, edge cases | Straightforward single-path logic |
| **Risk** | Failure is costly (data loss, security) | Failure is cosmetic or low-impact |
| **Stability** | Requirements are clear and stable | Requirements are still changing |
| **Team convention** | Team expects TDD | Team doesn't practice TDD |
| **Confidence** | You're unsure how to implement it | You've built this exact thing before |
**Rule of thumb:** If you're unsure, use TDD. The cost of writing a test first is low. The cost of a bug in untested code is high.
---
## The TDD Cycle
```
1. RED — Write a failing test that defines the desired behavior
2. GREEN — Write the minimum code to make the test pass
3. REFACTOR — Clean up without changing behavior (tests still pass)
4. REPEAT — Next behavior
```
### Common Mistakes
- **Writing too much test at once** — Test one behavior per cycle
- **Writing implementation before the test fails** — The failing test is the spec
- **Skipping refactor** — Technical debt accumulates in GREEN if you don't clean up
- **Testing implementation details** — Test what it does, not how it does it
---
## Handling Legacy Code Without Tests
Legacy code (code without tests) requires a different entry point into TDD.
### Step 1: Characterization Tests
Before changing anything, write tests that capture current behavior:
```python
# Characterization test — documents what the code DOES, not what it SHOULD do
def test_calculate_total_current_behavior():
result = calculate_total(items=[{"price": 10, "qty": 2}])
assert result == 20 # Observed behavior, may or may not be correct
```
### Step 2: Identify the Change Boundary
What's the smallest piece of code you need to change? Draw a boundary around it.
### Step 3: Add Seams
If the code is untestable (hard dependencies, global state), add seams:
- Extract method
- Inject dependencies
- Wrap external calls
### Step 4: TDD the Change
Now that you have characterization tests protecting existing behavior and seams allowing isolation, use the normal RED-GREEN-REFACTOR cycle for your change.
### Step 5: Decide What to Keep
After the change, decide which characterization tests to keep:
- **Keep** tests that document important behavior
- **Replace** tests that covered the code you changed (your TDD tests are better)
- **Remove** tests that only existed to enable your refactoring
---
## TDD by Test Type
| Test Type | TDD Approach |
|-----------|-------------|
| **Unit tests** | Standard RED-GREEN-REFACTOR. One behavior per cycle. |
| **Integration tests** | Write the test against the integration boundary first. May need stubs for external services during RED phase. |
| **API tests** | Define the request and expected response first. Implement handler to make it pass. |
| **E2E tests** | Not typically TDD'd per-cycle. Write E2E tests for critical paths after unit/integration TDD. |
---
## Quick Checklist
Before claiming a task is done with TDD:
- [ ] Every production function has at least one test that was written before the function
- [ ] No test was written after the code it tests (except characterization tests for legacy code)
- [ ] All tests pass
- [ ] Code has been refactored after going GREEN
- [ ] Tests verify behavior, not implementation
+181
View File
@@ -0,0 +1,181 @@
---
name: test-first
user-invocable: true
description: >
Use when implementing any feature, bugfix, or refactor that has a testable outcome.
Activate for keywords like "TDD", "test-first", "red-green", "write the test
first", "implement <feature>", "fix <bug>". Enforces the red-green-refactor
discipline -- write a failing test, make it pass with the smallest change, refactor
with tests as a safety net. Always paste the red and green test runner output --
never claim "tests pass" without showing them pass.
---
# Test First
## Overview
Red-green-refactor TDD with strict evidence requirements. The skill exists because
the most common testing failure isn't missing tests — it's tests written *after*
the code, designed to pass against the implementation rather than to specify it.
Test-first inverts the order: a failing test asserts the desired behavior, the
smallest implementation makes it pass, and refactoring runs with the test as a
safety net. Each step produces test runner output that goes into the PR. The
skill is for engineers shipping production code — not a TDD evangelism doc.
## When to Use
- Implementing a new feature with a testable surface (function, endpoint, CLI
command, UI behavior with a test harness)
- Fixing a bug — the regression test is the test you write first
- Refactoring code that has incomplete test coverage; tests come before the
refactor
- Onboarding to legacy code where you need to characterize behavior before
changing it
## When NOT to Use
- Pure UX/visual work with no behavioral assertion (use visual review instead)
- Exploratory spike work where the goal is learning, not shipping (mark spike
branches and write tests when promoting to mainline)
- Writing a one-off script that runs once and is discarded
## Process
### Step 1: Pick the smallest testable behavior
**Goal:** Identify one observable behavior to assert, smaller than the task.
**Inputs:** A task from your plan with an `Acceptance:` line.
**Actions:**
1. Read the acceptance criterion. Extract one specific input/output pair you
could write as a test.
2. If the criterion is too broad ("handles user signup correctly"), narrow it
to one case: "user signup with a duplicate email returns 409."
3. Name the test in a sentence form: `it <verb>s <subject> when <condition>`.
**Output:** A test name and a one-line description of the input/output pair.
### Step 2: Write the failing test (RED)
**Goal:** A test that currently fails for the right reason.
**Inputs:** The test name from Step 1.
**Actions:**
1. Open the test file. Create one if it doesn't exist; place it next to the
code-under-test or in the project's standard test location.
2. Write the test. Arrange-Act-Assert structure. No setup beyond what this
specific case needs.
3. Run the test. Confirm it fails.
4. **Read the failure message.** It must fail because the behavior is missing,
not because of a typo, missing import, or wrong file path. If it fails for
the wrong reason, fix the test before continuing.
5. Paste the red output into your scratch space or PR description. This is your
Step 2 evidence.
**Output:** Test code committed in a `test:` commit (or staged), red runner
output captured.
### Step 3: Make it pass with the smallest change (GREEN)
**Goal:** The test passes after a minimal implementation.
**Inputs:** The failing test.
**Actions:**
1. Implement the simplest code that could make the test pass. Hardcoded values
are acceptable here if no second test exists yet.
2. Run the test. Confirm it passes.
3. Run the full suite (or at least the file's test group). Confirm no regressions.
4. Paste the green output and the suite output. This is your Step 3 evidence.
**Output:** Implementation committed (or staged) in a separate commit from the
test. Green runner output captured.
### Step 4: Refactor (REFACTOR)
**Goal:** Improve the code's structure with the test as a safety net.
**Inputs:** Passing test and implementation.
**Actions:**
1. Look at the implementation. Identify duplication, unclear names, awkward
structure.
2. Make one structural improvement at a time. Run the test after each.
3. If the test fails after a refactor, the refactor changed behavior — back it
out, don't push through.
4. Stop refactoring when the cost of further changes exceeds the benefit. This
step is finite; don't gold-plate.
5. Paste the post-refactor green output. This is your Step 4 evidence.
**Output:** Refactored implementation, all tests still green.
### Step 5: Add the next test
**Goal:** Cycle back to Step 1 with a new case.
**Inputs:** Acceptance criteria not yet covered.
**Actions:**
1. Pick the next-smallest behavior from the acceptance criterion.
2. Loop Steps 1-4.
3. Continue until all acceptance criteria are covered.
**Output:** A complete test suite for the task, with red→green evidence for each
step.
## Rationalizations
| Excuse | Why it sounds reasonable | Why it's wrong | What to do instead |
|---|---|---|---|
| "I'll write the tests after the implementation — same outcome." | The tests do exist either way; order seems like ceremony. | Tests written after implementation are designed against the code that exists, not against the behavior the code should have. They confirm what was built; they don't catch what should have been. The "same outcome" claim only holds if you'd write the same tests both ways, and the literature (and most engineers' experience) shows you don't. | Write the test first. Take the 5 minutes to capture the failing output. The discipline cost is low and the test you write is meaningfully different. |
| "This is too simple to test — it's just a getter." | Trivial code is genuinely common, and writing tests for it does feel like make-work. | "Too simple" is the line said before someone changes the getter to compute something derived, and the absence of a test means the change ships without a check. The test is documentation: when the next person modifies the function, they see what callers expect. | If the code has any behavior — even returning a stored value with a computed default — write the test. Three lines of test code are not the cost they feel like. If it's truly inert (a constant), skip the test and don't lose sleep. |
| "Tests slow me down — I'll add them at the end." | Writing tests during a feature does add minutes to the cycle. | "At the end" usually means after the PR is open, after the reviewer is waiting, after the feature is "done in your head." At that point tests get written under time pressure, against the implementation that exists, with the corners-cut they always have under that condition. The minutes saved by deferring are paid back at 3-5x in the PR cycle. | Write the test first. Each red→green cycle is 10-15 minutes. By the end of the task, the tests are real and the PR is short, not a 200-line rewrite of a hand-tested feature. |
| "The test is hard to write — must be the wrong abstraction." | Test difficulty is genuinely a signal of design problems. | True at the limit, but "hard to write" sometimes just means the case is genuinely complex and the test deserves the work. Treating every hard-to-test case as an architectural smell becomes an excuse to skip tests on actual complexity. | Distinguish: does the test require setting up 10 mocks (architectural smell) vs is the assertion logic complex (legitimate complexity)? Skip the test only in the first case, and only after writing down the architectural concern in the spec or a follow-up. |
| "I'll write one big integration test instead of 10 unit tests." | Integration tests cover more in fewer lines. | One big test that covers 10 cases is one big test that fails opaquely when any of the 10 break. The failure message says "the integration test is red"; you spend 30 minutes finding which case. Ten unit tests that each cover one case fail with the case name in the report. | Write one unit test per case. Use integration tests for cross-component behavior, not as a substitute for unit-level coverage. |
| "I'll mock everything — it'll be fast." | Speed of test runs matters; mocks are how you get there. | Over-mocking produces tests that pass while the integration is broken. The test exercised your mock; the production code exercises a real database, a real HTTP client, a real clock — and they don't match the mock. The fast-but-wrong test is worse than no test because it provides false confidence. | Mock external services (HTTP, DB, third-party APIs) with named contract assertions. Don't mock language primitives, your own modules, or anything within the unit you're testing. Time-skew tests deserve a fake clock, not a real one. |
## Evidence Requirements
| Checkpoint | Required artifact | What "no evidence" looks like |
|---|---|---|
| End of Step 1 | A test name in `it <verb>s <subject> when <condition>` form | "I'll figure out what to test as I write." |
| End of Step 2 | Failing test code + red runner output (paste) | "I wrote a test; it should fail." |
| End of Step 3 | Passing test + green runner output (paste) + full-suite output | "Tests pass on my machine." |
| End of Step 4 | Post-refactor green output (paste) | "I cleaned things up." |
| End of Step 5 | All acceptance criteria covered by named tests | "Coverage is good." |
If the runner output isn't pasted somewhere in the PR or scratch artifact, you have
not satisfied this skill.
## Red Flags
- The red test failed because of an import error, missing file, or syntax issue.
You wrote a test that doesn't run — fix it before claiming red.
- The implementation is more than 30 lines for a single red→green cycle. The
test was too broad; split it.
- The test passes on the first run, before you write any implementation. It's
not testing what you think.
- A "passing" green test contains the literal string the implementation outputs
(`expect(x).toBe('hello world')` against `return 'hello world'`). It's a
tautology, not a behavioral check.
- The test mocks the function under test. You're asserting against the mock, not
the code.
- The PR has 5 commits and zero `test:` commits. The tests were retrofitted.
## References
- Kent Beck, *Test-Driven Development by Example* (Addison-Wesley, 2002),
Chapter 1 "Multi-Currency Money" — the canonical red-green-refactor example.
Steps 2-4 of this skill operationalize Beck's loop with strict evidence
requirements added.
- *Software Engineering at Google*, Wright et al. (O'Reilly, 2020), Chapter 11
"Testing Overview" and Chapter 12 "Unit Testing" — the test-pyramid framing
and the case for unit-level coverage as the foundation, used in
Rationalization rows 5 and 6.
-273
View File
@@ -1,273 +0,0 @@
---
name: testing-anti-patterns
user-invocable: false
description: >
Use when writing, reviewing, or debugging tests. Activate for keywords like "mock", "stub", "test helper", "flaky test", "test passes but bug ships", "false positive", "test coverage", or when tests seem unreliable. Also trigger when reviewing test code in PRs, when tests pass but production breaks, when someone proposes heavy mocking, or when test failures are intermittent. If any test smells wrong or feels like it is not actually verifying real behavior, this skill applies.
---
# Testing Anti-Patterns
## When to Use
- Writing new tests
- Reviewing test code
- Debugging flaky or unreliable tests
- When tests pass but bugs still ship
## When NOT to Use
- Writing production code that is not test-related
- Test framework configuration or setup (e.g., jest.config, vitest.config)
- Performance testing or load testing scenarios
---
## The Five Anti-Patterns
### 1. Testing Mock Behavior Instead of Real Code
**The Problem**:
Tests verify mocks work, not that actual code works.
```typescript
// BAD: Testing the mock
it('should call the database', () => {
const mockDb = { save: jest.fn().mockResolvedValue({ id: 1 }) };
const service = new UserService(mockDb);
await service.createUser({ name: 'Test' });
expect(mockDb.save).toHaveBeenCalled(); // Only proves mock was called
});
```
**The Solution**:
Test actual behavior with real (or realistic) dependencies.
```typescript
// GOOD: Testing real behavior
it('should persist user to database', async () => {
const db = await createTestDatabase();
const service = new UserService(db);
const result = await service.createUser({ name: 'Test' });
const saved = await db.findById(result.id);
expect(saved.name).toBe('Test'); // Proves data was actually saved
});
```
**Key Principle**: "Test what the code does, not what the mocks do."
---
### 2. Polluting Production with Test-Only Methods
**The Problem**:
Adding methods to production code solely for test cleanup or access.
```typescript
// BAD: Production class with test-only method
class ConnectionPool {
private connections: Connection[] = [];
// This method exists only for tests
destroy(): void { // DON'T DO THIS
this.connections.forEach(c => c.close());
this.connections = [];
}
}
```
**The Solution**:
Handle cleanup in test utilities, not production code.
```typescript
// GOOD: Test utility handles cleanup
// test-utils/connection-pool.ts
export async function withTestPool(fn: (pool: ConnectionPool) => Promise<void>) {
const pool = new ConnectionPool();
try {
await fn(pool);
} finally {
// Cleanup handled by test infrastructure
await closeAllConnections(pool);
}
}
```
**Key Principle**: Production code should never know it's being tested.
---
### 3. Mocking Without Understanding Dependencies
**The Problem**:
Over-mocking to "be safe" removes behavior the test actually depends on.
```typescript
// BAD: Mocking everything blindly
it('should process order', () => {
jest.mock('./inventory'); // What does this mock?
jest.mock('./payment'); // Did we need to mock this?
jest.mock('./shipping'); // This might break the test logic
const result = processOrder(order);
expect(result.status).toBe('complete');
});
```
**The Solution**:
Understand what each dependency does before mocking it.
```typescript
// GOOD: Selective, understood mocking
it('should process order when payment succeeds', () => {
// Only mock external service (payment gateway)
// Keep inventory and shipping real for integration test
const paymentGateway = createMockPaymentGateway({
chargeResult: { success: true, transactionId: 'txn-123' }
});
const result = processOrder(order, { paymentGateway });
expect(result.status).toBe('complete');
expect(result.transactionId).toBe('txn-123');
});
```
**Key Principle**: Mock at boundaries, not internally.
---
### 4. Creating Incomplete Mocks
**The Problem**:
Partial mocks that only include known fields, hiding structural assumptions.
```typescript
// BAD: Incomplete mock
const mockApiResponse = {
data: { users: [] }
// Missing: status, headers, pagination, errors
};
it('should handle API response', () => {
fetchMock.mockResolvedValue(mockApiResponse);
const result = await getUsers();
expect(result).toEqual([]);
});
// Test passes, but production fails when accessing response.pagination
```
**The Solution**:
Create complete mocks that match real API responses.
```typescript
// GOOD: Complete mock matching real response structure
const mockApiResponse = {
status: 200,
headers: { 'content-type': 'application/json' },
data: {
users: [],
pagination: { page: 1, total: 0, hasMore: false },
errors: null
}
};
it('should handle empty API response', () => {
fetchMock.mockResolvedValue(mockApiResponse);
const result = await getUsers();
expect(result.users).toEqual([]);
expect(result.hasMore).toBe(false);
});
```
**Key Principle**: Mocks should be indistinguishable from real responses.
---
### 5. Writing Tests as Afterthoughts
**The Problem**:
Treating testing as optional follow-up work rather than integral to development.
```typescript
// BAD: Tests written after code, just verifying what exists
it('should do what the function does', () => {
// This test was written by looking at the implementation
// It tests the current behavior, not the intended behavior
const result = processData(input);
expect(result).toMatchSnapshot(); // "Whatever it does is correct"
});
```
**The Solution**:
Use TDD - tests define requirements before implementation.
```typescript
// GOOD: Test written first, defines requirement
it('should filter inactive users from report', () => {
const users = [
{ id: 1, name: 'Alice', active: true },
{ id: 2, name: 'Bob', active: false }
];
const report = generateReport(users);
expect(report.users).toHaveLength(1);
expect(report.users[0].name).toBe('Alice');
});
// Now implement generateReport to make this pass
```
**Key Principle**: TDD prevents all these anti-patterns naturally.
---
## Recognition Guide
| Symptom | Likely Anti-Pattern |
|---------|---------------------|
| Tests pass but bugs ship | #1 Testing mocks |
| `destroy()` or `reset()` in production | #2 Test pollution |
| "I mocked that to be safe" | #3 Blind mocking |
| TypeError in production, not tests | #4 Incomplete mocks |
| Tests feel like documentation | #5 Afterthought tests |
---
## Prevention Checklist
Before committing tests, verify:
- [ ] Tests use real dependencies where possible
- [ ] Mocks are for external boundaries only
- [ ] No production code exists solely for tests
- [ ] Mock structures match real API responses
- [ ] Tests were written before implementation (TDD)
- [ ] Tests verify behavior, not implementation details
---
## Core Principle
**"Mocks are tools to isolate, not things to test."**
Mocks help you:
- Isolate unit under test
- Control external dependencies
- Speed up slow operations (network, disk)
Mocks should never:
- Be the thing you're verifying
- Hide bugs in dependencies
- Create false confidence
---
## Related Skills
- `test-driven-development` -- TDD naturally prevents most testing anti-patterns by requiring tests before implementation
- `pytest` -- Python-specific testing best practices that complement anti-pattern awareness
- `vitest` -- TypeScript/JavaScript-specific testing best practices that complement anti-pattern awareness
@@ -1,183 +0,0 @@
# Testing Anti-Pattern Catalog
Quick reference of common testing anti-patterns. Each entry includes: what it looks like, why it's a problem, and how to fix it.
---
## 1. The Ice Cream Cone
**Symptom:** Most tests are E2E or integration tests. Few or no unit tests. The test pyramid is inverted.
**Root cause:** Tests were written after the feature, following user flows instead of testing isolated logic. Or the code is tightly coupled, making unit tests hard to write.
**Impact:** Test suite is slow, brittle, and expensive to maintain. Failures are hard to diagnose because tests cover too much at once.
**Fix:** Refactor toward the test pyramid. Extract business logic into pure functions and unit test them. Reserve E2E tests for critical user flows only. Target ratio: 70% unit, 20% integration, 10% E2E.
---
## 2. The Mockery
**Symptom:** Tests mock so aggressively that they're testing the mocks, not the actual code. The thing under test has all its dependencies replaced.
**Root cause:** Code has too many dependencies, or the developer equates "isolated" with "mock everything."
**Impact:** Tests pass even when the real code is broken. Refactoring breaks every test because mocks are coupled to implementation details.
**Fix:** Only mock external boundaries (network, database, filesystem, clock). Use real implementations for internal collaborators. If you need too many mocks, the code has too many dependencies — refactor first.
---
## 3. The Slow Suite
**Symptom:** Test suite takes more than a few minutes to run. Developers skip tests locally and only run them in CI.
**Root cause:** Too many integration/E2E tests, tests that hit real databases or network, no test parallelization, expensive setup/teardown.
**Impact:** Developers stop running tests, bugs slip through, CI becomes a bottleneck.
**Fix:**
- Profile the suite to find the slowest tests
- Replace slow integration tests with fast unit tests where possible
- Use in-memory databases for integration tests
- Parallelize test execution
- Target: unit suite under 30 seconds, full suite under 5 minutes
---
## 4. The Flaky Test
**Symptom:** Test passes most of the time but fails unpredictably. Re-running usually makes it pass.
**Root cause:** Race conditions, time-dependent logic, shared mutable state between tests, reliance on external services, non-deterministic ordering.
**Impact:** Team loses trust in tests. "Oh that one's flaky" becomes an excuse to ignore real failures. CI results become meaningless.
**Fix:**
- Isolate the flaky test and run it 100 times to confirm flakiness
- Check for: shared state, date/time usage, async timing, test ordering
- Fix the root cause (don't just add retries)
- Quarantine truly unfixable flaky tests while investigating
---
## 5. The Assertion-Free Test
**Symptom:** Test runs code but doesn't assert anything meaningful. It only checks that no exception was thrown.
```python
# Bad — this tests nothing useful
def test_process_data():
process_data(sample_input) # No assertion
```
**Root cause:** Test was written to hit a coverage target rather than verify behavior.
**Impact:** False sense of security. Code "has tests" but bugs go undetected.
**Fix:** Every test must assert on the outcome. Ask: "What behavior am I verifying?" If you can't answer, the test isn't testing anything.
```python
# Good — asserts the actual behavior
def test_process_data_calculates_total():
result = process_data(sample_input)
assert result.total == 42.0
```
---
## 6. The Copy-Paste Test
**Symptom:** Test file has blocks of nearly identical code repeated with minor variations. Tests are long and look like each other.
**Root cause:** Developer tested a new case by copying an existing test and tweaking values instead of extracting a pattern.
**Impact:** Maintenance nightmare. A change to the interface requires updating dozens of near-identical tests. Easy to introduce subtle bugs in copies.
**Fix:** Use parameterized tests for variations on the same behavior:
```python
# Python — pytest.mark.parametrize
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("", ""),
("123", "123"),
])
def test_to_upper(input, expected):
assert to_upper(input) == expected
```
```typescript
// TypeScript — test.each (vitest/jest)
test.each([
["hello", "HELLO"],
["", ""],
["123", "123"],
])("to_upper(%s) returns %s", (input, expected) => {
expect(toUpper(input)).toBe(expected);
});
```
---
## 7. The Time Bomb
**Symptom:** Test passes today but will fail on a future date, or fails on certain days/times (new year, month boundary, DST change, leap year).
**Root cause:** Test uses `Date.now()`, `new Date()`, or similar without controlling the clock. Assertions are hardcoded to specific dates.
**Impact:** Sudden failures on specific dates. CI breaks on January 1, or during DST transitions.
**Fix:** Always inject or mock the clock:
```python
# Python — freeze time
from freezegun import freeze_time
@freeze_time("2025-06-15T12:00:00Z")
def test_expiry_check():
assert is_expired(created_at="2025-06-14T12:00:00Z", ttl_hours=23)
```
```typescript
// TypeScript — vitest fake timers
vi.useFakeTimers();
vi.setSystemTime(new Date("2025-06-15T12:00:00Z"));
expect(isExpired(createdAt, 23)).toBe(true);
vi.useRealTimers();
```
---
## 8. The Hidden Dependency
**Symptom:** Test passes locally but fails in CI, or fails when run in isolation but passes as part of the full suite.
**Root cause:** Test depends on external state that isn't set up by the test itself: a running database, a file on disk, an environment variable, output from a previous test, or global state modified by another test.
**Impact:** Tests are order-dependent, environment-dependent, and unreliable. Debugging failures requires understanding the entire test suite's execution order.
**Fix:**
- Each test must set up and tear down its own state
- Use fixtures (pytest fixtures, beforeEach/afterEach) for shared setup
- Run tests in random order to catch hidden dependencies
```bash
pytest -p randomly # Python
vitest --sequence.shuffle # vitest
```
- Never rely on test execution order
---
## Quick Decision Table
| Symptom | Likely Anti-Pattern | First Action |
|---------|-------------------|--------------|
| Tests are slow | Ice Cream Cone or Slow Suite | Profile, find the slowest tests |
| Tests break on refactor | The Mockery | Reduce mocks, test behavior not implementation |
| Tests fail randomly | Flaky Test | Isolate and run 100x |
| High coverage but bugs slip through | Assertion-Free Test | Audit assertions in coverage-targeted tests |
| Tests are hard to maintain | Copy-Paste Test | Extract parameterized tests |
| Tests fail on certain dates | Time Bomb | Inject/mock the clock |
| Tests fail in CI only | Hidden Dependency | Run locally in random order |
| Tests pass but code is clearly broken | The Mockery or Assertion-Free | Check what's actually being asserted |
-63
View File
@@ -1,63 +0,0 @@
---
name: testing
description: >
Use when writing, debugging, or configuring unit or integration tests with pytest, Vitest, or Jest. Also activate for fixtures, mocking, coverage, parametrization, jest.mock, vi.mock, jest.fn, vi.fn, conftest.py, vitest.config.ts, jest.config, Testing Library, @jest/globals, or any test configuration.
---
# Testing
## When to Use
- Writing Python tests with pytest (fixtures, parametrize, markers, coverage)
- Testing JavaScript/TypeScript with Vitest (React components, mocking, workspace)
- NestJS or existing projects using Jest
- Debugging test configuration, ESM issues, or flaky tests
- Setting up coverage, CI integration, or test infrastructure
## When NOT to Use
- E2E browser testing — use `playwright`
- Testing anti-patterns and methodology — use `testing-anti-patterns`
- TDD workflow — use `test-driven-development`
---
## Quick Reference
| Framework | Reference | Key features |
|-----------|-----------|-------------|
| pytest | `references/pytest.md` | Fixtures, parametrize, conftest, markers, coverage, async tests |
| Vitest | `references/vitest.md` | vi.mock, vi.fn, Testing Library, MSW, workspace, coverage |
| Jest | `references/jest.md` | jest.mock, jest.fn, @jest/globals, NestJS testing, migration to Vitest |
---
## Best Practices
1. **Name tests descriptively.** `test_[function]_[scenario]_[expected]` (Python) or `it('should [behavior]')` (JS/TS).
2. **Keep tests independent.** Never rely on execution order. Each test sets up its own state.
3. **One assertion focus per test.** Multiple asserts OK if verifying the same behavior.
4. **Mock at the boundary, not in the middle.** Mock external services, databases, and network calls. Don't mock internal functions.
5. **Clear/restore mocks between tests.** `vi.clearAllMocks()` in `beforeEach` or `jest.restoreAllMocks()` in `afterEach`.
6. **Use `userEvent` over `fireEvent`** for React component testing (simulates real user behavior).
7. **Query by role and label, not test IDs** (`getByRole`, `getByLabelText` over `getByTestId`).
8. **Run the full suite in CI with branch coverage.** Local development can use `-x` for fast feedback.
## Common Pitfalls
1. **Forgetting to `await` in async tests.** Omitting `await` makes tests pass vacuously.
2. **Mock hoisting confusion.** `vi.mock()`/`jest.mock()` calls are hoisted — variables referenced in mock implementations may be undefined.
3. **Shared mutable fixtures.** A module-scoped fixture returning a mutable object gets modified by one test and breaks another.
4. **Patching the wrong import path.** Patch where the import is looked up, not where it's defined.
5. **Snapshot overuse.** Developers update snapshots without reviewing diffs. Prefer explicit assertions.
6. **Not cleaning up fake timers.** Forgetting `vi.useRealTimers()` in `afterEach` breaks subsequent tests.
7. **Testing implementation, not behavior.** Assert on outcomes, not internal method calls.
8. **Running Jest where Vitest fits.** For new Vite/React/Next.js projects, Vitest is strictly better.
---
## Related Skills
- `testing-anti-patterns` — Common testing mistakes to avoid
- `test-driven-development` — TDD workflow
- `playwright` — End-to-end browser testing
-409
View File
@@ -1,409 +0,0 @@
# Testing — Jest Patterns
# Jest
## Overview
Testing patterns for projects that use Jest as their test runner — primarily NestJS backends and legacy React projects. For new TypeScript/React projects, prefer `vitest` (faster, native ESM, Vite-aligned). This skill focuses on Jest-specific patterns, NestJS integration, and the Jest-to-Vitest migration path.
## When to Use
- NestJS projects (Jest is the default test runner)
- Existing projects that already use Jest
- React component testing with Jest + Testing Library
- Debugging Jest configuration issues (ESM, TypeScript transforms, module resolution)
## When NOT to Use
- **New Vite/React/Next.js projects** — use `vitest` (better ESM support, faster)
- **Python testing** — use `pytest`
- **E2E browser testing** — use `playwright`
- **Cloudflare Workers** — use `vitest` with `@cloudflare/vitest-pool-workers`
---
## Quick Reference
| I need... | Go to |
|-----------|-------|
| NestJS testing patterns | § NestJS Testing below |
| Mock patterns | § Mocking below |
| TypeScript config | § Configuration below |
| ESM troubleshooting | § ESM Gotchas below |
| Migration to Vitest | § Jest → Vitest Migration below |
---
## Core Patterns
### Test structure
```typescript
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
service = new UserService();
});
it('should create a user with default role', () => {
const user = service.create({ email: 'test@example.com', name: 'Test' });
expect(user.role).toBe('member');
});
it('should throw on duplicate email', () => {
service.create({ email: 'test@example.com', name: 'A' });
expect(() => service.create({ email: 'test@example.com', name: 'B' }))
.toThrow('Email already exists');
});
});
```
### Assertions
```typescript
// Equality
expect(result).toBe(42); // strict ===
expect(result).toEqual({ id: '1' }); // deep equality
expect(result).toStrictEqual(obj); // deep + type equality
// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
// Numbers
expect(count).toBeGreaterThan(0);
expect(price).toBeCloseTo(9.99, 2);
// Strings
expect(message).toMatch(/error/i);
expect(message).toContain('failed');
// Arrays / objects
expect(arr).toContain('item');
expect(arr).toHaveLength(3);
expect(obj).toHaveProperty('email', 'test@example.com');
// Exceptions
expect(() => parse('{bad}')).toThrow(SyntaxError);
expect(() => validate({})).toThrow('Required');
// Async
await expect(fetchUser('missing')).rejects.toThrow('Not found');
await expect(fetchUser('exists')).resolves.toHaveProperty('id');
```
---
## Mocking
### `jest.fn()` — standalone mock function
```typescript
const callback = jest.fn();
callback('arg1');
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith('arg1');
```
### `jest.spyOn()` — spy on existing methods
```typescript
const spy = jest.spyOn(service, 'findOne').mockResolvedValue(mockUser);
await controller.getUser('123');
expect(spy).toHaveBeenCalledWith('123');
spy.mockRestore(); // Restore original
```
### `jest.mock()` — module mocking
```typescript
// Auto-mock entire module
jest.mock('./email.service');
// Manual mock with implementation
jest.mock('./email.service', () => ({
EmailService: jest.fn().mockImplementation(() => ({
send: jest.fn().mockResolvedValue({ messageId: 'msg_123' }),
})),
}));
```
### Mock return values
```typescript
const mock = jest.fn();
mock.mockReturnValue(42); // Sync
mock.mockReturnValueOnce(1); // First call only
mock.mockResolvedValue({ ok: true }); // Async
mock.mockRejectedValue(new Error()); // Async throw
mock.mockImplementation((x) => x * 2); // Custom logic
```
### Clear vs Reset vs Restore
| Method | Clears calls | Resets implementation | Restores original |
|--------|-------------|----------------------|-------------------|
| `mockClear()` | yes | no | no |
| `mockReset()` | yes | yes (returns undefined) | no |
| `mockRestore()` | yes | yes | yes (spyOn only) |
Use `jest.restoreAllMocks()` in `afterEach` to avoid mock leaks.
---
## NestJS Testing
### Unit test a service
```typescript
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { PrismaService } from '../prisma/prisma.service';
import { NotFoundException } from '@nestjs/common';
describe('UsersService', () => {
let service: UsersService;
let prisma: jest.Mocked<PrismaService>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: PrismaService,
useValue: {
user: {
findUnique: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
},
},
],
}).compile();
service = module.get(UsersService);
prisma = module.get(PrismaService);
});
it('throws NotFoundException for missing user', async () => {
prisma.user.findUnique.mockResolvedValue(null);
await expect(service.findOne('missing')).rejects.toThrow(NotFoundException);
});
it('returns user when found', async () => {
const mockUser = { id: '1', email: 'test@example.com', name: 'Test' };
prisma.user.findUnique.mockResolvedValue(mockUser);
const result = await service.findOne('1');
expect(result).toEqual(mockUser);
expect(prisma.user.findUnique).toHaveBeenCalledWith({ where: { id: '1' } });
});
});
```
### E2E test a controller
```typescript
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('Users (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }));
await app.init();
});
afterAll(() => app.close());
it('POST /users creates user', () =>
request(app.getHttpServer())
.post('/users')
.send({ email: 'test@example.com', name: 'Test' })
.expect(201)
.expect((res) => expect(res.body).toHaveProperty('id')));
it('POST /users rejects invalid payload', () =>
request(app.getHttpServer())
.post('/users')
.send({ email: 'bad' })
.expect(400));
});
```
### Test a guard
```typescript
import { ExecutionContext } from '@nestjs/common';
import { JwtAuthGuard } from './jwt-auth.guard';
import { JwtService } from '@nestjs/jwt';
describe('JwtAuthGuard', () => {
let guard: JwtAuthGuard;
let jwtService: jest.Mocked<JwtService>;
beforeEach(() => {
jwtService = { verifyAsync: jest.fn() } as any;
guard = new JwtAuthGuard(jwtService);
});
const mockContext = (authHeader?: string): ExecutionContext => ({
switchToHttp: () => ({
getRequest: () => ({
headers: { authorization: authHeader },
}),
}),
}) as any;
it('rejects missing token', async () => {
await expect(guard.canActivate(mockContext())).rejects.toThrow('Missing bearer token');
});
it('accepts valid token', async () => {
jwtService.verifyAsync.mockResolvedValue({ sub: 'user_1', role: 'admin' });
await expect(guard.canActivate(mockContext('Bearer valid.jwt.token'))).resolves.toBe(true);
});
});
```
---
## Configuration
### TypeScript with `ts-jest`
```typescript
// jest.config.ts
import type { Config } from 'jest';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/*.spec.ts', '**/*.test.ts'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.module.ts',
'!src/main.ts',
'!src/**/*.dto.ts',
'!src/**/*.entity.ts',
],
coverageThreshold: {
global: { branches: 80, functions: 80, lines: 80, statements: 80 },
},
};
export default config;
```
### SWC transform (faster)
Replace `ts-jest` with `@swc/jest` for 5-10x faster transforms:
```typescript
// jest.config.ts
const config: Config = {
transform: {
'^.+\\.tsx?$': ['@swc/jest'],
},
// ... rest same
};
```
### React + Testing Library
```typescript
// jest.config.ts
const config: Config = {
testEnvironment: 'jsdom',
setupFilesAfterSetup: ['<rootDir>/jest.setup.ts'],
transform: { '^.+\\.tsx?$': ['@swc/jest'] },
moduleNameMapper: {
'\\.(css|less|scss)$': 'identity-obj-proxy',
'\\.(jpg|png|svg)$': '<rootDir>/__mocks__/fileMock.ts',
},
};
// jest.setup.ts
import '@testing-library/jest-dom';
```
---
## ESM Gotchas
Jest's ESM support is still experimental. Common issues and fixes:
| Problem | Fix |
|---------|-----|
| `SyntaxError: Cannot use import` | Add `transform` with `ts-jest` or `@swc/jest` |
| Module not found for `.js` imports | Set `moduleNameMapper` or use `ts-jest` with `useESM: true` |
| `jest.mock()` doesn't work with ESM | Use `jest.unstable_mockModule()` (experimental) |
| Dynamic `import()` in tests | Set `transform` to handle the syntax |
| `__dirname` undefined | ESM doesn't have `__dirname`; use `import.meta.url` + `fileURLToPath` |
**If fighting ESM issues takes more than 30 minutes, migrate to Vitest.** Vitest handles ESM natively and is a near-drop-in replacement.
---
## Jest → Vitest Migration
For projects outgrowing Jest's ESM limitations or wanting faster transforms:
| Jest | Vitest |
|------|--------|
| `jest.fn()` | `vi.fn()` |
| `jest.mock('./mod')` | `vi.mock('./mod')` |
| `jest.spyOn(obj, 'method')` | `vi.spyOn(obj, 'method')` |
| `jest.useFakeTimers()` | `vi.useFakeTimers()` |
| `jest.config.ts` | `vitest.config.ts` |
| `@jest/globals` | `vitest` |
| `ts-jest` / `@swc/jest` | Not needed (native TS) |
| `jest.setup.ts``setupFilesAfterSetup` | `vitest.config.ts``setupFiles` |
Most tests migrate with a find-replace of `jest``vi` and `@jest/globals``vitest`. Run `npx vitest --reporter=verbose` to catch edge cases.
---
## Common Pitfalls
1. **Mock leaks between tests.** Always call `jest.restoreAllMocks()` in `afterEach`. Without it, one test's mock infects the next.
2. **Forgetting `await` on async assertions.** `expect(fn()).rejects.toThrow()` without `await` silently passes even if the promise resolves.
3. **Using `jest.mock()` with ESM.** Module-level `jest.mock()` doesn't work reliably with ESM. Use `jest.unstable_mockModule()` or switch to Vitest.
4. **Testing implementation, not behavior.** Asserting `mock.toHaveBeenCalledTimes(3)` tests internal calls, not outcomes. Assert on the return value or side effect instead.
5. **Slow transforms.** Default `ts-jest` is slow. Switch to `@swc/jest` for 5-10x speedup with zero config change.
6. **Not closing NestJS app in E2E tests.** Missing `afterAll(() => app.close())` leaks connections and causes "open handle" warnings.
7. **Snapshot overuse.** `toMatchSnapshot()` on large objects makes tests pass everything — any change auto-updates. Use targeted assertions instead.
8. **Running Jest where Vitest fits.** For new Vite/React/Next.js projects, Vitest is strictly better (native ESM, faster, same API). Only use Jest when the framework mandates it (NestJS) or the project already depends on it.
---
## Related Skills
- `vitest` — preferred runner for new TypeScript/React projects
- `nestjs` — NestJS framework (Jest is the default runner)
- `react` — React component patterns
- `testing-anti-patterns` — test quality pitfalls (applies to Jest too)
- `test-driven-development` — TDD methodology
-686
View File
@@ -1,686 +0,0 @@
# Testing — pytest Patterns
# pytest
## When to Use
- Writing Python tests
- Test fixtures and setup
- Mocking dependencies
## When NOT to Use
- JavaScript or TypeScript testing -- use the `vitest` skill instead
- Projects that explicitly mandate unittest-only by convention with no pytest dependency
- Non-Python test files or environments
---
## Core Patterns
### 1. Fixtures
Fixtures provide reusable setup and teardown logic. They are requested by name as test function parameters.
#### Function-Scoped Fixtures (default)
A new instance is created for every test that requests it.
```python
import pytest
from myapp.models import User
from myapp.db import Session
@pytest.fixture
def user():
"""Fresh user instance per test."""
return User(id=1, name="Alice", email="alice@example.com")
def test_user_display_name(user):
assert user.display_name() == "Alice"
def test_user_email_domain(user):
assert user.email_domain() == "example.com"
```
#### Class and Module Scope
Use broader scopes for expensive resources that are safe to share.
```python
@pytest.fixture(scope="class")
def api_client():
"""Shared across all tests in a test class."""
client = APIClient(base_url="http://testserver")
client.authenticate(token="test-token")
return client
@pytest.fixture(scope="module")
def database_schema():
"""Created once per test module, shared across all tests in the file."""
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
yield engine
engine.dispose()
@pytest.fixture(scope="session")
def redis_connection():
"""Created once for the entire test session."""
conn = Redis(host="localhost", port=6379, db=15)
conn.flushdb()
yield conn
conn.flushdb()
conn.close()
```
#### Yield Fixtures for Teardown
`yield` separates setup from teardown. Code after `yield` runs after the test completes, even if the test fails.
```python
@pytest.fixture
def db_session():
session = Session()
session.begin()
yield session
session.rollback()
session.close()
@pytest.fixture
def temp_config(tmp_path):
config_file = tmp_path / "config.yaml"
config_file.write_text("debug: true\nlog_level: INFO\n")
yield config_file
# tmp_path is automatically cleaned up by pytest
```
#### Autouse Fixtures
Apply a fixture to every test automatically without requesting it by name.
```python
@pytest.fixture(autouse=True)
def reset_environment(monkeypatch):
"""Ensure each test starts with clean environment variables."""
monkeypatch.delenv("API_KEY", raising=False)
monkeypatch.delenv("DATABASE_URL", raising=False)
@pytest.fixture(autouse=True)
def freeze_time():
"""Pin time for deterministic tests."""
with freeze_time("2025-06-15T12:00:00Z"):
yield
```
#### Factory Fixtures
Return a factory function when tests need multiple instances with varying parameters.
```python
@pytest.fixture
def make_user():
"""Factory that creates users with sensible defaults."""
created = []
def _make_user(name="Test User", role="viewer", active=True):
user = User(name=name, role=role, active=active)
created.append(user)
return user
yield _make_user
# Teardown: clean up all created users
for u in created:
u.delete()
def test_admin_permissions(make_user):
admin = make_user(name="Admin", role="admin")
viewer = make_user(name="Viewer", role="viewer")
assert admin.can_delete_users() is True
assert viewer.can_delete_users() is False
```
#### Parametrized Fixtures with request.param
Run the same test against multiple fixture variants.
```python
@pytest.fixture(params=["sqlite", "postgresql"])
def db_engine(request):
"""Test against multiple database backends."""
if request.param == "sqlite":
engine = create_engine("sqlite:///:memory:")
elif request.param == "postgresql":
engine = create_engine("postgresql://test:test@localhost/testdb")
yield engine
engine.dispose()
def test_insert_and_query(db_engine):
# This test runs twice: once with sqlite, once with postgresql
with db_engine.connect() as conn:
conn.execute(text("CREATE TABLE t (id INT)"))
conn.execute(text("INSERT INTO t VALUES (1)"))
result = conn.execute(text("SELECT * FROM t")).fetchall()
assert len(result) == 1
```
---
### 2. Parametrize
#### Single Parameter
```python
@pytest.mark.parametrize("email", [
"user@example.com",
"admin@test.org",
"name+tag@domain.co.uk",
])
def test_valid_email_accepted(email):
assert is_valid_email(email) is True
```
#### Multiple Parameters
```python
@pytest.mark.parametrize("input_text, expected", [
("hello", "HELLO"),
("world", "WORLD"),
("", ""),
("already UPPER", "ALREADY UPPER"),
])
def test_uppercase(input_text, expected):
assert input_text.upper() == expected
```
#### Custom IDs for Readable Output
```python
@pytest.mark.parametrize("status_code, should_retry", [
pytest.param(200, False, id="success-no-retry"),
pytest.param(429, True, id="rate-limited-retry"),
pytest.param(500, True, id="server-error-retry"),
pytest.param(404, False, id="not-found-no-retry"),
])
def test_retry_logic(status_code, should_retry):
response = MockResponse(status_code=status_code)
assert should_retry_request(response) is should_retry
```
#### Indirect Parametrize
Pass parameters through a fixture rather than directly to the test.
```python
@pytest.fixture
def user_role(request):
"""Create a user with the given role."""
return User(name="Test", role=request.param)
@pytest.mark.parametrize("user_role", ["admin", "editor", "viewer"], indirect=True)
def test_dashboard_access(user_role):
if user_role.role == "admin":
assert user_role.can_access("/admin/dashboard") is True
else:
assert user_role.can_access("/admin/dashboard") is False
```
#### Stacking Parametrize Decorators
Creates the cartesian product of all parameter sets.
```python
@pytest.mark.parametrize("method", ["GET", "POST", "PUT", "DELETE"])
@pytest.mark.parametrize("auth", ["token", "session", "none"])
def test_endpoint_auth(method, auth):
# Runs 4 x 3 = 12 test cases
response = make_request(method=method, auth_type=auth)
if auth == "none":
assert response.status_code == 401
else:
assert response.status_code in (200, 201, 204)
```
---
### 3. Mocking
#### monkeypatch -- Environment Variables and Attributes
```python
def test_reads_api_key_from_env(monkeypatch):
monkeypatch.setenv("API_KEY", "test-key-12345")
config = load_config()
assert config.api_key == "test-key-12345"
def test_missing_api_key_raises(monkeypatch):
monkeypatch.delenv("API_KEY", raising=False)
with pytest.raises(ConfigError, match="API_KEY is required"):
load_config()
def test_override_attribute(monkeypatch):
monkeypatch.setattr("myapp.settings.MAX_RETRIES", 0)
assert retry_request(failing_url) is None # No retries attempted
def test_override_dict_item(monkeypatch):
monkeypatch.setitem(app_config, "timeout", 1)
assert app_config["timeout"] == 1
```
#### unittest.mock.patch
```python
from unittest.mock import patch, Mock, AsyncMock
@patch("myapp.services.payment.stripe.Charge.create")
def test_charge_customer(mock_charge):
mock_charge.return_value = Mock(id="ch_123", status="succeeded")
result = process_payment(amount=1000, currency="usd", token="tok_visa")
mock_charge.assert_called_once_with(
amount=1000, currency="usd", source="tok_visa"
)
assert result.charge_id == "ch_123"
@patch("myapp.services.email.send_email")
@patch("myapp.services.user.UserRepository.find_by_id")
def test_send_welcome_email(mock_find, mock_send):
mock_find.return_value = User(id=1, email="new@example.com")
mock_send.return_value = True
send_welcome(user_id=1)
mock_send.assert_called_once_with(
to="new@example.com", template="welcome"
)
```
#### responses Library for HTTP Mocking
```python
import responses
import requests
@responses.activate
def test_fetch_user_from_api():
responses.add(
responses.GET,
"https://api.example.com/users/1",
json={"id": 1, "name": "Alice"},
status=200,
)
result = fetch_user(user_id=1)
assert result["name"] == "Alice"
assert len(responses.calls) == 1
assert responses.calls[0].request.url == "https://api.example.com/users/1"
@responses.activate
def test_api_timeout_handling():
responses.add(
responses.GET,
"https://api.example.com/users/1",
body=requests.exceptions.ConnectionError("Connection timed out"),
)
with pytest.raises(ServiceUnavailableError):
fetch_user(user_id=1)
```
#### pytest-mock's mocker Fixture
```python
def test_with_mocker(mocker):
mock_repo = mocker.patch("myapp.services.OrderRepository")
mock_repo.return_value.get_by_id.return_value = Order(
id=1, status="pending"
)
service = OrderService()
order = service.get_order(1)
assert order.status == "pending"
mock_repo.return_value.get_by_id.assert_called_once_with(1)
def test_spy_on_method(mocker):
spy = mocker.spy(UserService, "validate_email")
service = UserService()
service.register("alice@example.com")
spy.assert_called_once_with(service, "alice@example.com")
```
---
### 4. Async Testing
#### pytest-asyncio Basics
```python
import pytest
import httpx
@pytest.mark.asyncio
async def test_async_fetch():
async with httpx.AsyncClient() as client:
response = await client.get("https://httpbin.org/get")
assert response.status_code == 200
@pytest.mark.asyncio
async def test_async_exception():
with pytest.raises(ValueError, match="invalid"):
await validate_async_input("")
```
#### Async Fixtures
```python
@pytest.fixture
async def async_db_session():
session = AsyncSession(bind=async_engine)
await session.begin()
yield session
await session.rollback()
await session.close()
@pytest.mark.asyncio
async def test_async_query(async_db_session):
result = await async_db_session.execute(
select(User).where(User.active == True)
)
users = result.scalars().all()
assert len(users) >= 0
```
#### Configuring asyncio Mode
In `pyproject.toml` or `pytest.ini`, set the default mode to avoid repeating the marker:
```toml
# pyproject.toml
[tool.pytest.ini_options]
asyncio_mode = "auto"
```
With `asyncio_mode = "auto"`, any `async def test_*` function is automatically treated as async -- no `@pytest.mark.asyncio` needed.
---
### 5. Test Organization
#### conftest.py Hierarchy
```
tests/
├── conftest.py # Session/global fixtures (db connection, app client)
├── unit/
│ ├── conftest.py # Unit-specific fixtures (mocked services)
│ ├── test_models.py
│ └── test_utils.py
├── integration/
│ ├── conftest.py # Integration fixtures (real db session, test server)
│ ├── test_api.py
│ └── test_repositories.py
└── e2e/
├── conftest.py # E2E fixtures (browser, full app)
└── test_workflows.py
```
Fixtures in a `conftest.py` are available to all tests in the same directory and below. No imports needed.
#### Test Discovery
pytest discovers tests by default based on these rules:
- Files matching `test_*.py` or `*_test.py`
- Classes prefixed with `Test` (no `__init__` method)
- Functions prefixed with `test_`
Configure custom discovery in `pyproject.toml`:
```toml
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
```
#### Markers
```python
import pytest
import sys
# Built-in markers
@pytest.mark.skip(reason="Not implemented yet")
def test_future_feature():
pass
@pytest.mark.skipif(
sys.platform == "win32", reason="Unix-only functionality"
)
def test_unix_permissions():
pass
@pytest.mark.xfail(reason="Known bug #1234, fix pending")
def test_known_broken():
result = buggy_function()
assert result == "expected"
```
#### Custom Markers
Register markers in `pyproject.toml` to avoid warnings:
```toml
[tool.pytest.ini_options]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"integration: marks integration tests requiring external services",
"smoke: critical path tests for quick validation",
]
```
```python
@pytest.mark.slow
def test_full_data_migration():
migrate_all_records() # Takes 30+ seconds
assert count_records() == EXPECTED_TOTAL
@pytest.mark.smoke
def test_health_endpoint(client):
response = client.get("/health")
assert response.status_code == 200
```
Run selectively:
```bash
pytest -m "smoke" # Only smoke tests
pytest -m "not slow" # Skip slow tests
pytest -m "integration and not slow" # Integration but not slow
```
---
### 6. Coverage
#### Basic Usage
```bash
pytest --cov=src --cov-report=term-missing
pytest --cov=src --cov-report=html # Generates htmlcov/
pytest --cov=src --cov-branch # Enable branch coverage
```
#### Configuration in pyproject.toml
```toml
[tool.pytest.ini_options]
addopts = "--cov=src --cov-report=term-missing --cov-fail-under=80"
[tool.coverage.run]
source = ["src"]
branch = true
omit = [
"*/migrations/*",
"*/tests/*",
"*/__pycache__/*",
"*/conftest.py",
]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if TYPE_CHECKING:",
"raise NotImplementedError",
"@overload",
]
fail_under = 80
show_missing = true
```
#### .coveragerc Alternative
If not using `pyproject.toml`, create `.coveragerc`:
```ini
[run]
source = src
branch = true
[report]
fail_under = 80
show_missing = true
exclude_lines =
pragma: no cover
def __repr__
if TYPE_CHECKING:
```
---
### 7. Assertions
#### pytest.raises for Exceptions
```python
def test_raises_value_error():
with pytest.raises(ValueError) as exc_info:
parse_age("not-a-number")
assert "invalid literal" in str(exc_info.value)
def test_raises_with_match():
with pytest.raises(PermissionError, match=r"User .+ lacks role 'admin'"):
authorize(user=viewer, required_role="admin")
```
#### pytest.approx for Floating Point
```python
def test_circle_area():
assert calculate_area(radius=5) == pytest.approx(78.5398, rel=1e-4)
def test_approx_list():
result = distribute_evenly(total=100, buckets=3)
assert result == pytest.approx([33.33, 33.33, 33.34], abs=0.01)
```
#### Custom Assertion Helpers
Build reusable assertion logic for domain-specific validation.
```python
def assert_valid_api_response(response, expected_status=200):
"""Reusable assertion for API responses."""
assert response.status_code == expected_status, (
f"Expected {expected_status}, got {response.status_code}: "
f"{response.text}"
)
data = response.json()
assert "error" not in data, f"Unexpected error: {data['error']}"
return data
def test_create_user(client):
response = client.post("/users", json={"name": "Alice"})
data = assert_valid_api_response(response, expected_status=201)
assert data["name"] == "Alice"
assert "id" in data
```
---
## Best Practices
1. **Name tests descriptively** -- Use `test_[function]_[scenario]_[expected]` so failures are self-explanatory without reading the test body. `test_parse_date_invalid_format_raises_valueerror` tells you everything.
2. **Keep tests independent** -- Never rely on test execution order. Each test should set up its own state via fixtures and tear it down afterward. Shared mutable state between tests is the top cause of flaky suites.
3. **One assertion focus per test** -- A test can have multiple `assert` statements, but they should all verify the same behavior. If you need to check two independent behaviors, write two tests.
4. **Use fixtures over setup methods** -- Prefer composable fixtures over `setUp`/`tearDown` methods or `setup_function`. Fixtures are explicit about dependencies, reusable across files via `conftest.py`, and support scoping.
5. **Mock at the boundary, not in the middle** -- Mock external services, databases, and network calls. Do not mock internal functions unless they are truly expensive. Over-mocking produces tests that pass but verify nothing.
6. **Use `tmp_path` for file operations** -- pytest's built-in `tmp_path` fixture provides a unique temporary directory per test. Never write to the real filesystem in tests.
7. **Pin randomness and time** -- When testing code that depends on randomness or the current time, use `random.seed()` or a time-freezing library to make tests deterministic.
8. **Run the full suite in CI with branch coverage** -- Local development can use `pytest -x` for fast feedback (stop on first failure), but CI must run the full suite with `--cov-branch` to catch untested branches and regressions.
---
## Common Pitfalls
1. **Shared mutable fixtures** -- A module-scoped fixture returning a mutable object (list, dict, instance) gets modified by one test and breaks another. Return fresh copies or use function scope for mutable data.
2. **Patching the wrong import path** -- `@patch("myapp.services.requests.get")` patches where `requests.get` is looked up, not where it is defined. If `services.py` does `from requests import get`, you must patch `myapp.services.get`, not `requests.get`.
3. **Forgetting to await in async tests** -- Omitting `await` makes the test pass vacuously because it never actually runs the coroutine. Always `await` the function under test and use `@pytest.mark.asyncio`.
4. **Tests that depend on execution order** -- If test B relies on side effects from test A, parallel test execution (pytest-xdist) and `--randomly` will expose the coupling immediately. Fix by making each test self-contained.
5. **Asserting on mock call count without checking arguments** -- `mock.assert_called_once()` confirms the call count but not what was passed. Use `assert_called_once_with(...)` or inspect `mock.call_args` to verify the actual arguments.
6. **Ignoring warnings as errors** -- Configure `filterwarnings = ["error"]` in `pyproject.toml` to catch deprecation warnings early. A passing test suite that emits 50 deprecation warnings is a time bomb.
---
## Related Skills
- `vitest` -- JavaScript/TypeScript testing counterpart
- `python` -- Python language patterns and idioms
- `test-driven-development` -- TDD workflow for writing tests first
- `github-actions` — Running pytest in CI/CD pipelines
-842
View File
@@ -1,842 +0,0 @@
# Testing — Vitest Patterns
# Vitest
## When to Use
- Testing JavaScript/TypeScript
- React component testing
- Unit and integration tests
## When NOT to Use
- Python testing -- use the `pytest` skill instead
- Projects that explicitly mandate Jest-only by convention with no Vitest dependency
- Non-JavaScript/TypeScript projects
---
## Core Patterns
### 1. Test Structure
#### describe / it / expect
```typescript
import { describe, it, expect } from 'vitest';
import { formatCurrency } from './format';
describe('formatCurrency', () => {
it('should format whole dollars', () => {
expect(formatCurrency(100)).toBe('$100.00');
});
it('should format cents correctly', () => {
expect(formatCurrency(9.5)).toBe('$9.50');
});
it('should handle zero', () => {
expect(formatCurrency(0)).toBe('$0.00');
});
it('should throw on negative values', () => {
expect(() => formatCurrency(-5)).toThrow('Amount must be non-negative');
});
});
```
#### Lifecycle Hooks
```typescript
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from 'vitest';
import { Database } from './database';
describe('UserRepository', () => {
let db: Database;
beforeAll(async () => {
// Runs once before all tests in this describe block
db = await Database.connect('test://localhost/testdb');
await db.migrate();
});
afterAll(async () => {
await db.disconnect();
});
beforeEach(async () => {
// Runs before each test
await db.seed({ users: [{ id: 1, name: 'Alice' }] });
});
afterEach(async () => {
await db.truncate('users');
});
it('should find user by id', async () => {
const user = await db.users.findById(1);
expect(user).toEqual({ id: 1, name: 'Alice' });
});
it('should return null for missing user', async () => {
const user = await db.users.findById(999);
expect(user).toBeNull();
});
});
```
#### test.each for Parametrized Tests
```typescript
import { describe, it, expect, test } from 'vitest';
import { validateEmail } from './validators';
describe('validateEmail', () => {
test.each([
{ email: 'user@example.com', expected: true },
{ email: 'admin@test.org', expected: true },
{ email: 'name+tag@domain.co.uk', expected: true },
])('should accept valid email: $email', ({ email, expected }) => {
expect(validateEmail(email)).toBe(expected);
});
test.each([
{ email: '', reason: 'empty string' },
{ email: 'no-at-sign', reason: 'missing @' },
{ email: '@no-local.com', reason: 'missing local part' },
{ email: 'spaces in@email.com', reason: 'contains spaces' },
])('should reject invalid email ($reason): $email', ({ email }) => {
expect(validateEmail(email)).toBe(false);
});
});
```
#### Nested describe Blocks
```typescript
describe('ShoppingCart', () => {
describe('when empty', () => {
it('should have zero total', () => {
const cart = new ShoppingCart();
expect(cart.total()).toBe(0);
});
it('should have zero item count', () => {
const cart = new ShoppingCart();
expect(cart.itemCount()).toBe(0);
});
});
describe('with items', () => {
let cart: ShoppingCart;
beforeEach(() => {
cart = new ShoppingCart();
cart.add({ name: 'Widget', price: 9.99, quantity: 2 });
cart.add({ name: 'Gadget', price: 24.99, quantity: 1 });
});
it('should calculate total', () => {
expect(cart.total()).toBeCloseTo(44.97);
});
it('should count all items', () => {
expect(cart.itemCount()).toBe(3);
});
});
});
```
---
### 2. Mocking
#### vi.mock for Module Mocking
```typescript
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { sendWelcomeEmail } from './onboarding';
// Mock the entire email module -- hoisted to the top of the file automatically
vi.mock('./email', () => ({
sendEmail: vi.fn().mockResolvedValue({ messageId: 'msg-123' }),
}));
// Import AFTER vi.mock declaration
import { sendEmail } from './email';
describe('sendWelcomeEmail', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should send email with welcome template', async () => {
await sendWelcomeEmail('alice@example.com');
expect(sendEmail).toHaveBeenCalledWith({
to: 'alice@example.com',
template: 'welcome',
subject: 'Welcome to our platform!',
});
});
it('should return the message id', async () => {
const result = await sendWelcomeEmail('alice@example.com');
expect(result.messageId).toBe('msg-123');
});
});
```
#### vi.fn for Function Spies
```typescript
import { describe, it, expect, vi } from 'vitest';
describe('EventEmitter', () => {
it('should call listener on emit', () => {
const emitter = new EventEmitter();
const listener = vi.fn();
emitter.on('click', listener);
emitter.emit('click', { x: 10, y: 20 });
expect(listener).toHaveBeenCalledOnce();
expect(listener).toHaveBeenCalledWith({ x: 10, y: 20 });
});
it('should track multiple calls', () => {
const callback = vi.fn();
callback('first');
callback('second');
callback('third');
expect(callback).toHaveBeenCalledTimes(3);
expect(callback.mock.calls).toEqual([['first'], ['second'], ['third']]);
});
});
```
#### vi.spyOn
```typescript
import { describe, it, expect, vi, afterEach } from 'vitest';
import * as mathUtils from './math-utils';
describe('calculateTax', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('should use the tax rate function', () => {
const spy = vi.spyOn(mathUtils, 'getTaxRate').mockReturnValue(0.08);
const result = calculateTax(100);
expect(spy).toHaveBeenCalledWith();
expect(result).toBe(8);
});
it('should spy without changing behavior', () => {
const spy = vi.spyOn(console, 'warn');
triggerDeprecationWarning();
expect(spy).toHaveBeenCalledWith(
expect.stringContaining('deprecated')
);
});
});
```
#### mockResolvedValue / mockRejectedValue
```typescript
import { describe, it, expect, vi } from 'vitest';
describe('UserService', () => {
it('should return user on successful fetch', async () => {
const fetchUser = vi.fn().mockResolvedValue({ id: 1, name: 'Alice' });
const user = await fetchUser(1);
expect(user).toEqual({ id: 1, name: 'Alice' });
});
it('should throw on failed fetch', async () => {
const fetchUser = vi.fn().mockRejectedValue(new Error('User not found'));
await expect(fetchUser(999)).rejects.toThrow('User not found');
});
it('should return different values on successive calls', async () => {
const getToken = vi.fn()
.mockResolvedValueOnce('token-1')
.mockResolvedValueOnce('token-2')
.mockRejectedValueOnce(new Error('Expired'));
expect(await getToken()).toBe('token-1');
expect(await getToken()).toBe('token-2');
await expect(getToken()).rejects.toThrow('Expired');
});
});
```
#### MSW (Mock Service Worker) for API Mocking
```typescript
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
import { fetchUsers } from './api-client';
const server = setupServer(
http.get('https://api.example.com/users', () => {
return HttpResponse.json([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]);
}),
http.post('https://api.example.com/users', async ({ request }) => {
const body = await request.json() as { name: string };
return HttpResponse.json(
{ id: 3, name: body.name },
{ status: 201 }
);
})
);
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe('API Client', () => {
it('should fetch users', async () => {
const users = await fetchUsers();
expect(users).toHaveLength(2);
expect(users[0].name).toBe('Alice');
});
it('should handle server errors', async () => {
server.use(
http.get('https://api.example.com/users', () => {
return HttpResponse.json(
{ message: 'Internal Server Error' },
{ status: 500 }
);
})
);
await expect(fetchUsers()).rejects.toThrow('Server error');
});
});
```
---
### 3. React Testing
#### Render and Query
```tsx
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { Greeting } from './Greeting';
describe('Greeting', () => {
it('should display the user name', () => {
render(<Greeting name="Alice" />);
// getBy* throws if not found -- use for elements that must exist
expect(screen.getByText('Hello, Alice!')).toBeInTheDocument();
});
it('should not display admin badge for regular users', () => {
render(<Greeting name="Alice" role="viewer" />);
// queryBy* returns null if not found -- use for asserting absence
expect(screen.queryByText('Admin')).not.toBeInTheDocument();
});
it('should display admin badge for admins', () => {
render(<Greeting name="Alice" role="admin" />);
expect(screen.getByText('Admin')).toBeInTheDocument();
});
});
```
#### userEvent for Interactions
```tsx
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';
describe('LoginForm', () => {
it('should submit credentials', async () => {
const user = userEvent.setup();
const onSubmit = vi.fn();
render(<LoginForm onSubmit={onSubmit} />);
await user.type(screen.getByLabelText('Email'), 'alice@example.com');
await user.type(screen.getByLabelText('Password'), 'secret123');
await user.click(screen.getByRole('button', { name: 'Sign In' }));
expect(onSubmit).toHaveBeenCalledWith({
email: 'alice@example.com',
password: 'secret123',
});
});
it('should show validation error on empty submit', async () => {
const user = userEvent.setup();
render(<LoginForm onSubmit={vi.fn()} />);
await user.click(screen.getByRole('button', { name: 'Sign In' }));
expect(screen.getByText('Email is required')).toBeInTheDocument();
});
it('should toggle password visibility', async () => {
const user = userEvent.setup();
render(<LoginForm onSubmit={vi.fn()} />);
const passwordInput = screen.getByLabelText('Password');
expect(passwordInput).toHaveAttribute('type', 'password');
await user.click(screen.getByRole('button', { name: 'Show password' }));
expect(passwordInput).toHaveAttribute('type', 'text');
});
});
```
#### findBy for Async Rendering and waitFor
```tsx
import { describe, it, expect, vi } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserProfile } from './UserProfile';
describe('UserProfile', () => {
it('should load and display user data', async () => {
render(<UserProfile userId={1} />);
// findBy* waits for the element to appear (async query)
const heading = await screen.findByRole('heading', { name: 'Alice' });
expect(heading).toBeInTheDocument();
});
it('should show loading state initially', () => {
render(<UserProfile userId={1} />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('should update after action', async () => {
const user = userEvent.setup();
render(<UserProfile userId={1} />);
await screen.findByRole('heading', { name: 'Alice' });
await user.click(screen.getByRole('button', { name: 'Deactivate' }));
await waitFor(() => {
expect(screen.getByText('Status: Inactive')).toBeInTheDocument();
});
});
});
```
#### Testing with Context Providers
```tsx
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { ThemeProvider } from './ThemeContext';
import { ThemedButton } from './ThemedButton';
function renderWithProviders(ui: React.ReactElement, options?: { theme?: 'light' | 'dark' }) {
const theme = options?.theme ?? 'light';
return render(
<ThemeProvider value={theme}>
{ui}
</ThemeProvider>
);
}
describe('ThemedButton', () => {
it('should apply light theme styles', () => {
renderWithProviders(<ThemedButton>Click me</ThemedButton>, { theme: 'light' });
expect(screen.getByRole('button')).toHaveClass('btn-light');
});
it('should apply dark theme styles', () => {
renderWithProviders(<ThemedButton>Click me</ThemedButton>, { theme: 'dark' });
expect(screen.getByRole('button')).toHaveClass('btn-dark');
});
});
```
---
### 4. Async Testing
#### Promises and async/await
```typescript
import { describe, it, expect } from 'vitest';
import { fetchUser, processQueue } from './services';
describe('async operations', () => {
it('should resolve with user data', async () => {
const user = await fetchUser(1);
expect(user).toEqual({ id: 1, name: 'Alice' });
});
it('should reject with descriptive error', async () => {
await expect(fetchUser(-1)).rejects.toThrow('Invalid user ID');
});
it('should process all items in queue', async () => {
const results = await processQueue(['a', 'b', 'c']);
expect(results).toHaveLength(3);
expect(results.every((r) => r.status === 'done')).toBe(true);
});
});
```
#### Fake Timers
```typescript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { debounce } from './debounce';
describe('debounce', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('should not call function before delay', () => {
const fn = vi.fn();
const debounced = debounce(fn, 300);
debounced();
vi.advanceTimersByTime(200);
expect(fn).not.toHaveBeenCalled();
});
it('should call function after delay', () => {
const fn = vi.fn();
const debounced = debounce(fn, 300);
debounced();
vi.advanceTimersByTime(300);
expect(fn).toHaveBeenCalledOnce();
});
it('should reset timer on subsequent calls', () => {
const fn = vi.fn();
const debounced = debounce(fn, 300);
debounced();
vi.advanceTimersByTime(200);
debounced(); // reset
vi.advanceTimersByTime(200);
expect(fn).not.toHaveBeenCalled();
vi.advanceTimersByTime(100);
expect(fn).toHaveBeenCalledOnce();
});
});
```
#### Fake Timers with Date
```typescript
import { describe, it, expect, vi } from 'vitest';
import { isExpired } from './token';
describe('isExpired', () => {
it('should detect expired tokens', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2025-06-15T12:00:00Z'));
const token = { expiresAt: '2025-06-15T11:00:00Z' };
expect(isExpired(token)).toBe(true);
vi.useRealTimers();
});
});
```
---
### 5. Snapshot Testing
#### toMatchSnapshot
```typescript
import { describe, it, expect } from 'vitest';
import { render } from '@testing-library/react';
import { Badge } from './Badge';
describe('Badge', () => {
it('should match snapshot for success variant', () => {
const { container } = render(<Badge variant="success">Active</Badge>);
expect(container.firstChild).toMatchSnapshot();
});
});
```
#### toMatchInlineSnapshot
Inline snapshots embed the expected value directly in the test file. Vitest updates them automatically on first run.
```typescript
import { describe, it, expect } from 'vitest';
import { formatError } from './errors';
describe('formatError', () => {
it('should format validation error', () => {
const error = formatError({ field: 'email', rule: 'required' });
expect(error).toMatchInlineSnapshot(`
{
"code": "VALIDATION_ERROR",
"field": "email",
"message": "email is required",
}
`);
});
});
```
#### When to Use Snapshots (and When Not To)
**Use snapshots for:**
- Serialized output that is tedious to write by hand (large objects, rendered markup)
- Catching unintended changes in generated output
- Error message formatting
**Do not use snapshots for:**
- Business logic assertions -- write explicit `expect(value).toBe(expected)` instead
- Frequently changing output -- snapshot churn leads to mindless updates
- Large component trees -- a small change deep in the tree makes the diff unreadable; test specific elements instead
---
### 6. Coverage
#### vitest.config.ts Coverage Settings
```typescript
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
coverage: {
provider: 'v8', // or 'istanbul'
reporter: ['text', 'html', 'lcov'],
reportsDirectory: './coverage',
include: ['src/**/*.{ts,tsx}'],
exclude: [
'src/**/*.test.{ts,tsx}',
'src/**/*.d.ts',
'src/**/index.ts', // barrel files
'src/test-utils/**',
],
thresholds: {
statements: 80,
branches: 80,
functions: 80,
lines: 80,
},
},
},
});
```
#### Running Coverage
```bash
vitest run --coverage # Run once with coverage
vitest --coverage # Watch mode with coverage
vitest run --coverage.provider=v8 # Override provider via CLI
```
#### Per-File Thresholds
```typescript
// vitest.config.ts
export default defineConfig({
test: {
coverage: {
provider: 'v8',
thresholds: {
// Global thresholds
statements: 80,
// Per-glob overrides for critical paths
'src/auth/**': {
statements: 95,
branches: 95,
},
},
},
},
});
```
---
### 7. Setup and Configuration
#### vitest.config.ts Basics
```typescript
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
globals: true, // Use describe/it/expect without imports
environment: 'jsdom', // DOM environment for React (or 'happy-dom')
setupFiles: ['./src/test-setup.ts'],
include: ['src/**/*.test.{ts,tsx}'],
exclude: ['node_modules', 'dist', 'e2e'],
testTimeout: 10_000,
hookTimeout: 30_000,
},
resolve: {
alias: {
'@': '/src',
},
},
});
```
#### Setup File
```typescript
// src/test-setup.ts
import '@testing-library/jest-dom/vitest';
import { cleanup } from '@testing-library/react';
import { afterEach } from 'vitest';
// Automatic cleanup after each test
afterEach(() => {
cleanup();
});
```
#### Workspace Configuration
For monorepos with multiple packages:
```typescript
// vitest.workspace.ts
import { defineWorkspace } from 'vitest/config';
export default defineWorkspace([
{
extends: './vitest.config.ts',
test: {
name: 'ui',
include: ['packages/ui/**/*.test.{ts,tsx}'],
environment: 'jsdom',
},
},
{
extends: './vitest.config.ts',
test: {
name: 'api',
include: ['packages/api/**/*.test.ts'],
environment: 'node',
},
},
]);
```
#### Environment Per File
Use a magic comment at the top of a test file to override the environment:
```typescript
// @vitest-environment happy-dom
import { describe, it, expect } from 'vitest';
describe('DOM-heavy tests', () => {
it('should create elements', () => {
const div = document.createElement('div');
div.textContent = 'Hello';
expect(div.textContent).toBe('Hello');
});
});
```
#### Globals Mode
When `globals: true` is set in config, you do not need to import `describe`, `it`, `expect`, `vi`, etc. Add the types to `tsconfig.json`:
```json
{
"compilerOptions": {
"types": ["vitest/globals"]
}
}
```
---
## Best Practices
1. **Use `userEvent` over `fireEvent`** -- `userEvent` simulates real user behavior (focus, keystrokes, blur) while `fireEvent` dispatches raw DOM events. `userEvent` catches bugs that `fireEvent` misses, such as disabled buttons still receiving clicks.
2. **Query by role and label, not test IDs** -- Prefer `getByRole('button', { name: 'Submit' })` and `getByLabelText('Email')` over `getByTestId('submit-btn')`. Accessible queries validate your markup and are resilient to refactors.
3. **Clear mocks between tests** -- Call `vi.clearAllMocks()` in `beforeEach` or `vi.restoreAllMocks()` in `afterEach`. Leaked mock state between tests causes order-dependent failures that are painful to debug.
4. **Keep tests focused on one behavior** -- Each `it` block should test a single user-observable behavior. If your test description contains "and", split it into two tests.
5. **Avoid testing implementation details** -- Do not assert on component state, internal method calls, or private variables. Test what the user sees and what the component outputs. Implementation tests break on every refactor without catching real bugs.
6. **Use MSW for network mocking over vi.mock on fetch** -- MSW intercepts at the network level, so your tests exercise the actual fetch/axios code paths. Mocking `fetch` directly skips serialization, headers, and error handling logic.
7. **Colocate tests with source files** -- Place `Button.test.tsx` next to `Button.tsx`. This makes it obvious which files have tests and simplifies imports. Reserve a top-level `e2e/` folder only for end-to-end tests.
8. **Run tests in watch mode during development** -- `vitest` (no flags) starts in watch mode and re-runs only affected tests on file change. Use `vitest run` in CI for a single full run with exit code.
---
## Common Pitfalls
1. **Forgetting to await userEvent calls** -- Every `userEvent` method is async. Omitting `await` causes the assertion to run before the interaction completes, leading to false passes or intermittent failures.
2. **vi.mock hoisting confusion** -- `vi.mock()` calls are hoisted to the top of the file. If you define a mock implementation that references a variable declared below the `vi.mock` call, it will be `undefined`. Use `vi.mock` with a factory function or move the variable above.
3. **Not cleaning up after fake timers** -- Forgetting `vi.useRealTimers()` in `afterEach` causes subsequent tests to silently use fake timers, producing mysterious timeouts and passing tests that should fail.
4. **Using `getBy` queries for elements that may not exist** -- `getByText('Error')` throws immediately if the element is absent. When asserting that something is NOT rendered, use `queryByText('Error')` which returns `null`.
5. **Snapshot overuse** -- Developers update snapshots without reviewing the diff. Over time, snapshots become rubber stamps. Limit snapshots to serialized output and error formatting; use explicit assertions for behavior.
6. **Testing third-party library internals** -- Do not test that React Router navigates correctly or that Zustand updates state. Test that your component renders the right thing after navigation or state change. Trust library authors; test your code.
---
## Related Skills
- `pytest` -- Python testing counterpart
- `typescript` -- TypeScript language patterns and strict typing
- `react` -- React component patterns for component testing
- `test-driven-development` -- TDD workflow for writing tests first
- `github-actions` — Running vitest in CI/CD pipelines
-155
View File
@@ -1,155 +0,0 @@
---
name: using-git-worktrees
description: >
Use when starting feature work that needs isolation from the current workspace, before executing implementation plans, or when working on multiple branches simultaneously. Trigger for keywords like "worktree", "isolated branch", "parallel branches", "feature isolation", or when dispatching subagents that need separate working directories. Also activate when the user wants to test against main while developing on a feature branch.
---
# Using Git Worktrees
## When to Use
- Starting feature work that needs isolation from the current workspace
- Executing implementation plans with subagents (one worktree per task)
- Working on multiple branches simultaneously (e.g., hotfix while feature is in progress)
- Testing against main while developing on a feature branch
- Running long builds/tests on one branch while coding on another
## When NOT to Use
- Simple single-branch work with no isolation needs
- Quick fixes that can be done on the current branch
- When the repo has uncommitted changes you haven't stashed (clean up first)
---
## Creating a Worktree
### Basic pattern
```bash
# Create worktree from current branch
git worktree add ../project-feature-auth feature/auth
# Create worktree from a new branch off main
git worktree add -b feature/orders ../project-feature-orders main
# Create worktree for a hotfix off production
git worktree add -b hotfix/session-fix ../project-hotfix-session production
```
### Naming convention
Use `../project-<branch-slug>` to keep worktrees adjacent to the main repo:
```
d:/hop/code/work/
├── myapp/ # Main worktree
├── myapp-feature-auth/ # Feature worktree
├── myapp-feature-orders/ # Another feature
└── myapp-hotfix-session/ # Hotfix worktree
```
### Safety checks before creating
```bash
# 1. Ensure clean working state
git status # no uncommitted changes
# 2. Fetch latest from remote
git fetch origin
# 3. Verify the base branch is up to date
git log --oneline origin/main..main # should be empty
# 4. Create the worktree
git worktree add -b feature/new-feature ../project-feature-new origin/main
```
---
## Working in a Worktree
### Install dependencies (each worktree needs its own)
```bash
# Python
cd ../project-feature-auth
python -m venv venv
source venv/bin/activate # or venv\Scripts\activate on Windows
pip install -r requirements.txt
# Node.js
cd ../project-feature-auth
pnpm install
```
### Run tests independently
```bash
# In worktree — won't affect main workspace
cd ../project-feature-auth
pytest -v --cov=src # Python
npm test # TypeScript
```
### Subagent integration
When dispatching subagents for parallel work, each agent gets its own worktree:
```markdown
Agent 1 worktree: ../project-task-1 (branch: task/backend-api)
Agent 2 worktree: ../project-task-2 (branch: task/frontend-ui)
Agent 3 worktree: ../project-task-3 (branch: task/db-migration)
```
Each agent works in isolation — no merge conflicts during development.
---
## Cleanup
### Remove a worktree after merging
```bash
# 1. Switch back to main worktree
cd ../myapp
# 2. Merge the feature branch
git merge feature/auth
# 3. Remove the worktree
git worktree remove ../project-feature-auth
# 4. Delete the branch if no longer needed
git branch -d feature/auth
```
### Prune stale worktrees
```bash
# List all worktrees
git worktree list
# Remove stale references (worktrees whose directories were deleted)
git worktree prune
```
---
## Common Pitfalls
1. **Forgetting to install dependencies.** Each worktree has its own `node_modules` / `venv`. Run `pnpm install` or `pip install -r requirements.txt` after creating.
2. **Stale worktrees.** If you delete a worktree directory manually, run `git worktree prune` to clean up references.
3. **Branch conflicts.** You can't check out the same branch in two worktrees. If you need to, create a new branch off it.
4. **Database state.** Worktrees share the same git history but not local databases. Ensure migrations are applied in each worktree.
5. **IDE confusion.** Open each worktree as a separate project/window in your IDE. Don't mix paths.
6. **Forgetting to clean up.** After merging, always remove the worktree. Stale worktrees waste disk space and create confusion.
---
## Related Skills
- `subagent-driven-development` — Use worktrees to give each subagent an isolated workspace
- `dispatching-parallel-agents` — Dispatch agents into separate worktrees for true parallel work
- `executing-plans` — Worktrees enable isolated task execution from plans
- `finishing-a-development-branch` — Cleanup and merge workflow after worktree work is complete
@@ -1,342 +0,0 @@
---
name: verification-before-completion
user-invocable: true
description: >
Use when about to claim ANY work is complete, fixed, passing, or done. Activate whenever you are tempted to say "done", "fixed", "tests pass", "build succeeds", "deployed", or any completion claim. Also trigger before committing code, before creating PRs, before responding to the user that a task is finished, or when reviewing agent-produced work. This is mandatory -- NEVER claim completion without running verification commands and reading their output. Evidence before assertions, always.
---
# Verification Before Completion
## When to Use
- Before claiming tests pass
- Before claiming build succeeds
- Before claiming bug is fixed
- Before marking any task complete
- Before declaring success to user
## When NOT to Use
- Mid-task progress updates where you are reporting interim status, not claiming completion
- Research or exploration tasks where the output is knowledge, not code
- Design or brainstorming phases where no verifiable artifacts have been produced yet
---
## The 5-Step Verification Process
### Step 1: IDENTIFY
Determine the command that proves your assertion:
```markdown
Claim: "Tests pass"
Verification command: npm test
Claim: "Build succeeds"
Verification command: npm run build
Claim: "Linting passes"
Verification command: npm run lint
```
### Step 2: EXECUTE
Run the command fully and freshly:
```bash
# Don't rely on cached results
# Don't assume previous run is still valid
npm test
```
### Step 3: READ
Read the complete output and exit codes:
```bash
# Check output carefully
# Don't skim - read every line
# Note exit code (0 = success)
```
### Step 4: VERIFY
Confirm the output matches your claim:
```markdown
Claim: "All tests pass"
Output shows: "42 passing, 0 failing"
Verification: ✓ Claim is accurate
```
### Step 5: CLAIM
Only now make the claim, with evidence:
```markdown
✓ All tests pass (42 passing, verified at 2024-01-15 14:30)
```
---
## Required Validations by Category
### Testing
```bash
# Run test command
npm test
# Verify in output:
# - Zero failures
# - Expected test count
# - No skipped tests (unless intentional)
```
**Not valid**: "Tests should pass" without running them
### Linting
```bash
# Run linter completely
npm run lint
# Verify in output:
# - Zero errors
# - Zero warnings (or acceptable known warnings)
```
**Not valid**: Using lint as proxy for build success
### Building
```bash
# Run build command
npm run build
# Verify:
# - Exit code 0
# - Build artifacts created
# - No errors in output
```
**Not valid**: Assuming lint passing means build passes
### Bug Fixes
```bash
# Step 1: Reproduce original bug
npm test -- --grep "failing test"
# Should fail
# Step 2: Apply fix
# Step 3: Verify fix works
npm test -- --grep "failing test"
# Should pass
```
**Not valid**: Claiming fix works without reproducing original failure
### Regression Tests
Complete red-green cycle required:
```bash
# 1. Write test, run it
npm test # Should PASS with new test
# 2. Revert the fix
git stash
# 3. Run test again
npm test # Should FAIL (proves test catches the bug)
# 4. Restore fix
git stash pop
# 5. Run test again
npm test # Should PASS
```
### Requirements Verification
```markdown
## Original Requirements
1. User can login with email
2. User can reset password
3. Session expires after 24 hours
## Verification Checklist
- [x] Requirement 1: Tested login flow manually + unit tests
- [x] Requirement 2: Tested reset flow manually + integration test
- [x] Requirement 3: Verified SESSION_TIMEOUT=86400 in config + test
```
### Agent Work Verification
Don't trust agent reports blindly:
```bash
# Agent claims: "Fixed the bug in user.ts"
# Verify independently:
git diff src/user.ts # Check actual changes
npm test # Verify tests pass
```
---
## Forbidden Language
Never use these phrases without verification:
| Forbidden | Why |
|-----------|-----|
| "should work" | Implies uncertainty |
| "probably fixed" | Not verified |
| "seems to pass" | Didn't read output |
| "I think it's done" | Guessing |
| "Great!" (before checking) | Premature celebration |
| "Done!" (before verification) | Unverified claim |
### Replace With
| Instead Say | After |
|-------------|-------|
| "Tests pass" | Running tests, seeing 0 failures |
| "Build succeeds" | Running build, exit code 0 |
| "Bug is fixed" | Reproducing bug, verifying fix |
---
## Anti-Patterns
### Partial Verification
```markdown
BAD: "I ran one test and it passed"
GOOD: "Full test suite passes (42/42)"
```
### Relying on Prior Runs
```markdown
BAD: "Tests passed earlier"
GOOD: "Tests pass now (just ran)"
```
### Skipping Verification
```markdown
BAD: "This is a small change, no need to verify"
GOOD: "Small change, but verified: tests pass, lint clean"
```
### Trusting Without Checking
```markdown
BAD: Agent said it's fixed, so it's fixed
GOOD: Agent said it's fixed, I verified by running tests
```
---
## Verification Checklist Template
Use before claiming completion:
```markdown
## Task: [Task Name]
### Verification Steps
- [ ] Tests run: `npm test`
- Result: [X passing, Y failing]
- [ ] Lint passes: `npm run lint`
- Result: [No errors]
- [ ] Build succeeds: `npm run build`
- Result: [Exit code 0]
- [ ] Requirements met:
- [ ] Requirement 1: [How verified]
- [ ] Requirement 2: [How verified]
### Evidence
[Paste relevant output or screenshots]
### Conclusion
✓ Task complete, all verifications passed
```
---
## Stack-Specific Verification Commands
### Python/FastAPI
```bash
pytest -v --cov=src --cov-report=term-missing # Tests + coverage
ruff check . # Linting
mypy src/ --strict # Type checking
# All-in-one
pytest -v && ruff check . && mypy src/
```
### TypeScript/NestJS
```bash
npm test # Tests
npm run lint # Linting
npx tsc --noEmit # Type checking
npm run build # Build (catches import issues)
# All-in-one
npm test && npm run lint && npm run build
```
### Next.js
```bash
npm test # Tests
next lint # Linting
npx tsc --noEmit # Type checking
next build # Build (catches SSR + RSC issues)
# All-in-one
npm test && next lint && next build
```
### React (Vite)
```bash
npx vitest run # Tests
npm run lint # Linting
npx tsc --noEmit # Type checking
npm run build # Build
# All-in-one
npx vitest run && npm run lint && npm run build
```
### Verification Evidence Template
When claiming completion, paste evidence in this format:
```markdown
**Verification Evidence:**
- Tests: `pytest -v` → 47 passed, 0 failed
- Lint: `ruff check .` → no issues
- Types: `mypy src/` → Success: no issues found
```
```markdown
**Verification Evidence:**
- Tests: `npm test` → 23 passed, 0 failed
- Lint: `npm run lint` → no warnings
- Build: `npm run build` → compiled successfully
```
---
## Related Skills
- `test-driven-development` -- TDD naturally produces verifiable work; verification confirms the TDD cycle was followed correctly
- `systematic-debugging` -- After debugging, verification ensures the fix actually resolves the issue
- `requesting-code-review` -- Verification should happen before requesting review to avoid wasting reviewer time on broken code
@@ -1,116 +0,0 @@
# Verification Checklist
Use this checklist before claiming any work is complete. Copy it into your task, PR, or plan and fill in the specifics. Every box must be checked with evidence — not assumptions.
---
## Core Verification
### Tests
- [ ] **All existing tests pass**
- Command: `___`
- Output: [paste summary or confirm "all N tests passed"]
- [ ] **New tests added for new behavior**
- Test files: `___`
- Coverage of changed code: `___`%
- [ ] **Edge cases tested**
- [ ] Empty/null inputs
- [ ] Boundary values
- [ ] Error conditions
- [ ] Concurrent access (if applicable)
### Build
- [ ] **Build succeeds with no errors**
- Command: `___`
- Output: [confirm clean build]
- [ ] **No new warnings introduced**
- Linter: `___`
- Type checker: `___`
### Manual Verification
- [ ] **The specific change works as intended**
- What I did: [exact steps taken]
- What I observed: [exact result]
- What was expected: [matches requirement]
- [ ] **Related functionality still works**
- Checked: [list related features tested]
---
## Safety Checks
### No Unintended Side Effects
- [ ] **Reviewed the full diff** — No accidental changes to unrelated files
```bash
git diff --stat
```
- [ ] **No debug code left in place** — No `console.log`, `print()`, `debugger`, `TODO: remove`
- [ ] **No commented-out code** — Either the code is needed or it isn't
### Error Handling
- [ ] **Errors produce clear messages** — Not generic "something went wrong"
- [ ] **Errors don't leak sensitive information** — No stack traces, internal paths, or credentials in user-facing errors
- [ ] **Failure modes are graceful** — The system degrades rather than crashes
### Security
- [ ] **No hardcoded secrets** — API keys, passwords, tokens are in environment variables
- [ ] **Input is validated** — User input is checked before processing
- [ ] **Output is encoded** — Rendered content is escaped appropriately
- [ ] **No new `eval()` or dynamic code execution**
- [ ] **Dependencies are from trusted sources** — No typosquatting, pinned versions
---
## Documentation
- [ ] **Code is self-documenting** — Clear names, obvious structure
- [ ] **Complex logic has comments** — Explaining WHY, not WHAT
- [ ] **Public API changes are documented** — Updated docstrings, OpenAPI specs, README
- [ ] **Breaking changes are called out** — In commit message, PR description, or changelog
---
## Completion Criteria
- [ ] **All acceptance criteria from the plan/ticket are met**
- [ ] Criterion 1: ___
- [ ] Criterion 2: ___
- [ ] **The change has been verified with actual commands, not just code reading**
- [ ] **Confidence level:** High / Medium / Low
- If Medium or Low, explain what additional verification would increase confidence: ___
---
## Evidence Summary
Record the key evidence here for reviewers:
| Check | Evidence |
|-------|---------|
| Tests pass | [command + result] |
| Build clean | [command + result] |
| Manual test | [steps + result] |
| No regressions | [how verified] |
---
## Usage Notes
- **Do not check boxes without evidence.** "I think it works" is not verification.
- **Run commands and observe output.** Paste or summarize actual results.
- **N/A is acceptable** for items that genuinely don't apply, but add a note explaining why.
- **If confidence is low**, list what would increase it and discuss with the team before marking complete.
+197
View File
@@ -0,0 +1,197 @@
---
name: verification-gate
user-invocable: true
description: >
Use before claiming a task, feature, or PR is complete. Activate for keywords
like "done", "complete", "ready to merge", "ship it", "tests pass", "looks good",
"fixed". Mandatory pre-completion gate. Refuses to mark anything done without
evidence: command outputs, test runs, behavioral checks. Always paste the
evidence -- never assert "it works" without showing it works.
---
# Verification Gate
## Overview
A pre-completion gate that converts the assertion "this is done" into evidence:
the test output, the command run, the behavioral observation. The skill exists
because "done" is a self-reported state and self-reports are wrong roughly half
the time on real changes. The gate is short — five minutes — but it catches the
class of mistake where an engineer claims a fix works after only running tests
in their IDE, only verifying happy-path, only checking a single environment.
This is the load-bearing skill of v4: every other skill funnels through it
before its work is called done.
## When to Use
- Before opening a PR for review
- Before declaring a bug fixed in response to a ticket or incident
- Before checking off an `Acceptance:` line in a plan
- Before pushing to a branch that triggers a deploy
- Whenever you catch yourself thinking "I'm done" — pause and run this gate
## When NOT to Use
- During exploratory work where "done" doesn't apply yet
- Mid-implementation, when the partial work is committed for checkpoint reasons
but not claimed complete
- For changes already merged and ack'd — the gate is pre-completion, not
post-completion
## Process
### Step 1: Restate the claim
**Goal:** Make the "done" claim explicit and falsifiable.
**Inputs:** Whatever you were about to call done.
**Actions:**
1. Write one sentence: `I am claiming <X> is complete because <Y>.` X is the
work; Y is the evidence you intend to show. Don't do anything else until
you've written it.
2. If Y is "tests pass and the code looks right," return to Step 2 — the
evidence is too vague.
**Output:** A claim sentence written down (PR description, scratch file, or
comment).
### Step 2: Run the named tests with full output
**Goal:** Prove the tests asserting this work pass, with evidence pasted.
**Inputs:** The list of tests relevant to this work.
**Actions:**
1. Run the project's test command for this scope. Use the exact form from the
plan's `Acceptance:` line if it exists.
2. Capture the full output, not just "PASSED." The output should show test names
and a pass count.
3. Run the broader suite for the file or module — confirm no regressions.
4. Paste the output (or a referenced artifact) into the PR / scratch file.
**Output:** Test runner output captured.
### Step 3: Run the negative path
**Goal:** Verify the work doesn't claim to handle cases it doesn't.
**Inputs:** The acceptance criteria.
**Actions:**
1. Identify negative cases: invalid input, missing required field, unauthorized
user, network failure, empty result, max-size input.
2. For each, exercise it manually or through a test. Capture what happens.
3. The negative path doesn't have to handle every case gracefully — it has to
fail predictably and visibly. "Crashes the server" is a fail, not a feature.
**Output:** Negative-path observations: each case + what happened + verdict.
### Step 4: Verify in a non-IDE environment
**Goal:** Confirm the work runs outside your editor.
**Inputs:** A way to run the change in a more production-like context.
**Actions:**
1. If a UI: open the running app in a browser and exercise the change. Don't
skip this just because tests pass.
2. If a CLI: run the binary or script from a fresh shell. Confirm output, exit
codes, error messages.
3. If a service endpoint: hit it with `curl` or your project's HTTP test tool.
Confirm response shape, status codes, headers.
4. If a background job: trigger it via the actual job runner, not by calling
the function directly.
5. Capture the observation.
**Output:** A non-IDE verification: the command run, the result observed.
### Step 5: Cross-check the original ask
**Goal:** Confirm the work satisfies what was actually asked, not what got
implemented.
**Inputs:** The original ticket, plan task, or spec criterion.
**Actions:**
1. Re-read the original. Word for word.
2. List each thing it asked for. For each, write where in the work it was
addressed.
3. If something was asked for and the work doesn't address it, the work isn't
done. Either implement it or explicitly defer it (with the deferral
documented in the PR or follow-up ticket).
**Output:** A short matrix: `<asked for> → <addressed in <location> | deferred
to <follow-up>>`.
### Step 6: Sign the gate
**Goal:** Record that the gate ran.
**Inputs:** All evidence from Steps 2-5.
**Actions:**
1. Add a `## Verification` section to the PR description (or a scratch artifact
if no PR yet) containing:
- The Step 1 claim sentence
- Test output (or link to it)
- Negative-path observations
- Non-IDE verification
- Cross-check matrix
2. Mark the work done only after this section exists.
**Output:** A `## Verification` section in the PR.
## Rationalizations
| Excuse | Why it sounds reasonable | Why it's wrong | What to do instead |
|---|---|---|---|
| "Tests pass — that's enough verification." | A green test suite is the standard signal, automated and trusted. | Tests cover what was tested. The verification gate exists because the cases that hit production are typically the cases tests didn't cover — the negative paths, the production environment quirks, the bits the implementer assumed worked. "Tests pass" is necessary evidence, not sufficient evidence. | Run the tests AND run the negative path AND verify in a non-IDE environment AND cross-check the original ask. The first one alone is what produces the "tests passed but it broke in prod" pattern. |
| "I tested it manually in my IDE — it works." | Manual verification does count as evidence for the cases that have no automated test. | "It works in my IDE" passes Step 4 only for the IDE environment. Production doesn't run in your IDE. The IDE has your env vars, your local DB, your hot-reloaded modules, your debugger attached. The non-IDE run catches the cases where production doesn't have those. | Run the change outside the IDE. A 30-second `curl` from a separate shell catches "I forgot to deploy the migration" and "the env var is only set in my .env" — bugs that aren't catchable from inside the IDE. |
| "The negative path is obvious — invalid input throws an error, that's it." | Many systems do throw on invalid input by default; the language/framework provides this for free. | "Throws an error" doesn't tell the user/caller what went wrong or what to do. The default error message is often `Internal Server Error 500` — useful to no one. The negative path verification isn't asserting that errors happen; it's asserting that the error is *useful* to the consumer. | Exercise the negative case. Read the error the user/caller would actually see. If it's "Internal Server Error" or "undefined is not a function," that's a finding even if the test passes. |
| "I'll do the cross-check in code review — that's what review is for." | Reviewers do verify the work matches the ask. | The reviewer cross-checks against what they remember the ask was, not what was originally written. They don't have time to re-read the original ticket and match it line-by-line; they read the PR and approve based on what looks right. The cross-check belongs in the gate so the reviewer can verify the gate ran, not redo the work. | Step 5 takes 60 seconds. Re-read the ticket, list what was asked, point at where each ask was addressed. The matrix saves the reviewer 5 minutes of context-rebuilding and produces a better review. |
| "I don't need to paste the test output — the CI will run it." | CI is the system of record for test results. | CI runs after the PR is open. The verification gate runs before the PR is open. Skipping the local paste means the PR opens with no evidence, the reviewer waits for CI, and if CI fails the round trip is on the reviewer's calendar instead of yours. The paste is also documentation: in two months, when someone bisects, the PR has the receipt of what was tested. | Paste the output. If the CI later runs the same tests and matches your local output, that's confirmation; if it diverges, that's an environment bug worth knowing about. |
| "It's a small change — the gate is overkill." | Most small changes don't break things; the overhead per small change feels disproportionate. | The gate's overhead is ~5 minutes; the cost of skipping the gate on a "small" change that turned out not to be small is ~hours plus a Slack message of apology. The cases that *feel* small enough to skip the gate include the ones where the small-feeling change had a non-small consequence. | Run the gate. For genuinely tiny changes (typo, comment, single-line config) you can collapse Steps 2-5 into one paste — but don't skip the gate. The discipline is uniform; the per-change cost stays low. |
## Evidence Requirements
| Checkpoint | Required artifact | What "no evidence" looks like |
|---|---|---|
| End of Step 1 | A claim sentence written in `<X> is complete because <Y>` form | "I'm done with this." |
| End of Step 2 | Test runner output pasted | "Tests pass." |
| End of Step 3 | Negative-path observations: case + observed behavior | "Error handling looks fine." |
| End of Step 4 | Non-IDE run output (curl, browser screenshot, CLI output) | "Works on my machine." |
| End of Step 5 | Cross-check matrix linking asks to addressed locations | "I implemented what was asked." |
| End of Step 6 | A `## Verification` section in the PR with all of the above | "Marked the task done in the tracker." |
## Red Flags
- The Step 1 claim sentence's `<Y>` is "the code looks right." The claim has no
evidence; the work isn't done.
- Test output is summarized as "all tests pass" without showing the pass count
or any test names. The summary is hiding the truth.
- Negative path is "we'll handle errors in v2." That's not a verification, that's
a deferral. Document it as a follow-up if it's intentional; flag it as a gap
if it's not.
- The non-IDE check was "I ran the same test command in a different terminal."
That's not a non-IDE check; it's the same env.
- The cross-check matrix has more "deferred" rows than "addressed." The work
doesn't satisfy the ask; renegotiate the ask before claiming done.
- The verification section says "see CI." CI is for the reviewer; the gate is
for you, before the reviewer.
## References
- *Site Reliability Engineering*, Beyer et al. (Google, O'Reilly 2016), Chapter
17 "Testing for Reliability" — the principle "the system must be tested in
configurations that match production." Step 4 (non-IDE verification)
operationalizes this for the per-change scale.
- *The Pragmatic Programmer*, Hunt & Thomas (Addison-Wesley, 20th anniversary
ed. 2019), Topic 25 "How to Balance Resources" — the "test what you ship,
ship what you tested" principle is the gate's core posture.
+182
View File
@@ -0,0 +1,182 @@
---
name: write-plan
user-invocable: true
description: >
Use after a spec exists and before any implementation code is written. Activate for
keywords like "plan", "break down", "decompose", "implementation plan", "task list",
"what's the order". Produces a numbered task list where each task names the file,
the change, the test, and what evidence proves it's done. Always cite file paths
and exact commands -- never write a plan with placeholder verbs like "implement"
or "set up".
---
# Write Plan
## Overview
A workflow for converting a spec into an executable plan: a numbered list of tasks,
each with a file path, an exact change, a test command, and an acceptance check.
The skill exists because the most common implementation failure is starting from a
plan that says "implement the cache" and discovering at code-review time that
"implement" hid four sub-decisions nobody made. A plan written through this skill
makes those decisions visible up front. Each task is small enough that a different
engineer could pick it up cold; the entire plan is small enough that the spec's
acceptance criteria are obviously reachable from it. Used after `shape-spec`,
before code or `plan-review`.
## When to Use
- A spec has been approved and you're about to start coding
- The work will span more than one PR or take more than a day
- More than one person will work on the change
- You're handing the work off to a teammate and need them to start without you in
the room
- You ran `plan-review` and the reviewer said the plan is too vague to evaluate
## When NOT to Use
- The change is single-file and you'll finish it in 30 minutes
- A plan exists; you should be running `plan-review` against it, not rewriting it
- You don't have a spec yet — go to `shape-spec` first
## Process
### Step 1: Confirm the spec is sufficient
**Goal:** Avoid planning against a spec that itself is incomplete.
**Inputs:** The spec produced by `shape-spec` (or equivalent).
**Actions:**
1. Read the spec. Check that Goals, Non-Goals, Constraints, and Acceptance Criteria
sections are all populated.
2. For each Acceptance Criterion, write down — in your scratch space, not the plan
— the rough engineering work needed to satisfy it. If you can't, the criterion
is too vague; return to `shape-spec`.
3. Note any constraint that requires research before tasks can be written
(e.g., "must work with the legacy auth middleware" — does it?). Mark these
as Step 0 tasks.
**Output:** Confirmation note `Spec sufficient` or a list of return-to-spec items.
### Step 2: Decompose into tasks
**Goal:** Generate a flat numbered task list. No sub-tasks; flatness forces honesty
about size.
**Inputs:** The spec's Acceptance Criteria.
**Actions:**
1. For each criterion, list the engineering work needed: data model changes,
handler changes, tests, configuration, documentation, deploy steps.
2. Order tasks by data flow: schema/config first, then handlers, then UI, then
tests if not test-first. Reorder if the project uses TDD (test first).
3. Each task is on one line in this form:
`<N>. <file_path> — <verb> <specific change>. Test: <command>.`
4. If a task line exceeds ~120 characters or you can't name a single test
command, the task is too large. Split it.
**Output:** A numbered, flat task list in a Markdown file at
`docs/claudekit/plans/<spec-basename>-plan.md`.
### Step 3: Annotate dependencies and parallelism
**Goal:** Make the order of operations explicit.
**Inputs:** The task list from Step 2.
**Actions:**
1. For each task, note tasks that **must complete first** (dependencies).
2. For each task, note tasks that **can run in parallel** (no shared file, no
shared state).
3. Add a `Blocked by: <task numbers>` and `Parallel with: <task numbers>` line
under each task that has either.
4. If the dependency graph has a long single chain (everything blocks on task 3),
the work is not parallelizable; tell the reader that explicitly so they don't
try to fan out.
**Output:** Each task annotated with dependency and parallelism metadata.
### Step 4: Add acceptance check per task
**Goal:** Define what "this task is done" means concretely, so the implementer
knows when to move on and the reviewer knows what to look for.
**Inputs:** The task list from Step 3.
**Actions:**
1. For each task, append an `Acceptance:` line with a concrete observable check.
- For code tasks: a specific test passing OR a specific behavior observable
in the running app.
- For schema tasks: the migration applies cleanly to a snapshot of prod data.
- For docs tasks: the doc renders, the link is valid, the example runs.
2. The acceptance check is what the implementer pastes into the PR description
for that task.
**Output:** Each task has an `Acceptance:` line.
### Step 5: Risk and rollback notes
**Goal:** Surface the parts of the plan that could go wrong.
**Inputs:** The fully annotated task list.
**Actions:**
1. Add a `## Risks` section at the bottom of the plan. List each task that:
- Touches production data
- Modifies a shared schema
- Changes a public API contract
- Requires a deploy in a specific order with another service
2. For each risk, write a one-line rollback procedure.
3. If a task has no rollback (e.g., destructive migration), write
`Rollback: NOT POSSIBLE — see plan-review-architecture` and flag for the
architecture reviewer.
**Output:** A Risks section. Plan ready for `plan-review`.
## Rationalizations
| Excuse | Why it sounds reasonable | Why it's wrong | What to do instead |
|---|---|---|---|
| "I'll figure out the file paths when I get there." | The plan is for high-level sequencing, not low-level layout. | Plans without file paths are wishlists. They survive review by saying "implement the X" and rot at implementation time when the engineer realizes "X" actually splits across three files with conflicting conventions. The decisions you defer to "when I get there" are the decisions plan-review exists to catch. | Name the file path even if it doesn't exist yet. `src/handlers/billing/charge.ts (new)` is fine. Naming forces you to think about layout before you've written any code. |
| "Test commands will be obvious." | If the project has one test runner, every task uses it; explicit naming feels redundant. | "Obvious" assumes the implementer is you, today. A teammate (or you, in three weeks) won't remember whether this task wants `pytest -k` or the integration suite or the contract test. Naming the exact command in the plan saves the implementer one round trip and avoids the "I ran the wrong tests" PR. | Paste the exact command. `pytest tests/billing/test_charge.py -k test_idempotency`. If three tasks have the same command, that's fine; copy-pasting is cheap. |
| "Acceptance is just 'tests pass.'" | TDD culture tells us tests are the contract. | "Tests pass" is necessary, not sufficient. A task can pass tests and still not satisfy the acceptance criterion if the test was scoped wrong, the wrong cases were covered, or the criterion includes something tests don't catch (a UX flow, a perf budget, a doc update). The acceptance line names *which* observable thing proves the task done. | Write the acceptance line as: "Test X passes AND when I run Y in dev, I see Z." Most tasks need both halves. |
| "I don't need parallelism notes — we'll figure it out as we go." | Most plans are executed sequentially anyway. Annotating parallelism for a one-person project is overhead. | The annotation is cheap; the absence is expensive when a second person joins or the same engineer wants to pick the next task while CI runs the previous one. The cases where parallelism notes don't matter are also the cases where adding them takes 60 seconds. | If the plan has more than 5 tasks, add the parallelism notes. They're not for the first author; they're for whoever is on the project a week from now. |
| "Rollback is the deploy team's problem." | Some risks really do live in the deploy step, owned by SRE / ops. | "Their problem" assumes someone else has the context to write the rollback. They don't. The engineer who wrote the migration knows what to undo; SRE can run the rollback but can't author it. The plan owner is the right author of the rollback note. | Write the one-line rollback yourself. If you don't know what it would be, you don't know what risk you're taking. Flag it; don't punt it. |
## Evidence Requirements
| Checkpoint | Required artifact | What "no evidence" looks like |
|---|---|---|
| End of Step 1 | A `Spec sufficient` confirmation OR a list of items to return to the spec | "I read the spec, I think it's fine." |
| End of Step 2 | A flat numbered task list with file paths and exact test commands | "Step 1: implement the feature." |
| End of Step 3 | Each task annotated with `Blocked by` / `Parallel with` (where applicable) | "We'll figure out the order during implementation." |
| End of Step 4 | Each task has an `Acceptance:` line with concrete observable check | "Tests pass." |
| End of Step 5 | A `## Risks` section with rollback notes for high-risk tasks | "We'll deal with rollback if it comes up." |
## Red Flags
- The plan has fewer than 3 tasks. You wrote a TODO, not a plan.
- The plan has more than 30 tasks. The spec is too big or the tasks are too small;
collapse the trivial ones or split the spec.
- A task has no `Acceptance:` line. You don't know how the implementer will know
they're done.
- The dependency graph is one long chain. Either the work isn't parallelizable
(say so) or you missed parallelism opportunities.
- Multiple tasks reference the same file with conflicting changes (one adds,
another modifies, a third removes). Order them, or the second engineer to pick
one will face a merge conflict the plan invented.
- The plan and the spec have drifted. A goal in the spec has no corresponding
task in the plan, or vice versa. Reconcile before review.
## References
- *Software Engineering at Google*, Wright et al. (O'Reilly, 2020), Chapter 9
"Code Review" — the principle "small, focused changes" applied at the planning
level, not just at the PR level. A plan whose tasks are small enough to merge
individually produces PRs small enough to review individually.
-189
View File
@@ -1,189 +0,0 @@
---
name: writing-concisely
user-invocable: false
description: >
Use this skill when optimizing token usage, reducing response verbosity, or working in high-volume development sessions. Trigger for any mention of token savings, cost optimization, concise output, compressed responses, or the --format=concise/ultra flags. Also applies during repetitive tasks, quick iterations, simple clear requests, or when the user activates token-efficient mode. This is a cross-cutting optimization that applies to all other skills.
---
# Token Optimization
## When to Use
- High-volume development sessions
- Repetitive tasks
- Simple, clear requests
- Cost-sensitive projects
- Quick iterations
## When NOT to Use
- Learning or educational contexts where verbose explanations help the user understand concepts
- Debugging complex issues where detailed analysis and step-by-step reasoning matter
- Security reviews or architecture discussions where thoroughness is more important than brevity
---
## Compression Levels
### Level 1: Concise (30-40% savings)
- Remove conversational filler
- Skip obvious explanations
- Use bullet points
- Shorter variable names in examples
### Level 2: Compact (50-60% savings)
- Code-only responses
- No surrounding prose
- Abbreviated comments
- Reference docs instead of explaining
### Level 3: Ultra (60-70% savings)
- Minimal viable response
- Essential code only
- No comments
- Diff format for changes
---
## Compression Techniques
### Remove Preambles
```markdown
❌ VERBOSE:
"I'll help you with that. Let me analyze the code and provide
a solution. Based on what I see, the issue is..."
✅ CONCISE:
"Issue: null check missing at line 42. Fix:"
```
### Code-Only Responses
```markdown
❌ VERBOSE:
"Here's the implementation. I've added proper error handling
and made sure to follow the existing patterns in your codebase.
The function now validates input and returns early if invalid."
[large code block]
"This should fix the issue. Let me know if you have questions."
✅ CONCISE:
[code block]
```
### Reference Over Explain
```markdown
❌ VERBOSE:
"React's useEffect hook runs after render. The dependency array
controls when it re-runs. Empty array means run once on mount..."
✅ CONCISE:
"Add `userId` to deps array. See: https://react.dev/reference/react/useEffect"
```
### Diff Format for Changes
```markdown
❌ VERBOSE:
"I've updated the file. Here's the complete new version:"
[entire file]
✅ CONCISE:
```diff
- const user = getUser();
+ const user = getUser() ?? defaultUser;
```
Line 42 in user-service.ts
```
---
## Output Templates
### Bug Fix
```
Fix: [brief description]
File: [path:line]
[code or diff]
Verify: [test command]
```
### Feature Addition
```
Added: [feature]
Files: [list]
[code blocks]
Test: [command]
```
### Refactor
```
Refactor: [what]
[diff format changes]
No behavior change.
```
---
## When NOT to Compress
| Situation | Why |
|-----------|-----|
| Complex architecture | Need full context |
| Security issues | Must explain risks |
| Code reviews | Thoroughness required |
| Teaching/explaining | Clarity matters |
| Debugging complex issues | Details help |
| First-time patterns | Context needed |
---
## Activation
### Via Mode
```
Use mode: token-efficient
```
### Via Flag
```
/command --format=concise
/command --format=ultra
```
### Session-Wide
```
For this session, use token-efficient mode.
```
---
## Best Practices
1. **Match compression to task complexity**
- Simple task → High compression
- Complex task → Lower compression
2. **Preserve essential information**
- File paths always included
- Test commands always included
- Error context when relevant
3. **Use progressive disclosure**
- Start concise
- Expand if asked
4. **Know when to stop compressing**
- User confusion → Add context
- Errors occurring → Add detail
- Review needed → Full output
---
## Related Skills
- All skills — this is a cross-cutting optimization that can be combined with any other skill to reduce token usage while maintaining response quality
-378
View File
@@ -1,378 +0,0 @@
---
name: writing-plans
argument-hint: "[task description]"
user-invocable: true
description: >
Use when a multi-step implementation task needs to be broken down before coding begins. Activate for keywords like "plan", "break down", "implementation steps", "task list", "how to implement", "write a plan", or when a feature spans multiple files or components. Also trigger when handing off work to another developer, when the user says "let's plan this out", or when a task is complex enough that jumping straight to code would be risky. If in doubt, plan first.
---
# Writing Plans
## When to Use
- After brainstorming/design is complete
- Before starting implementation
- When handing off work to another developer
- For complex features requiring structured approach
## When NOT to Use
- Single-file changes where the path forward is obvious
- Already has a plan to execute -- use `executing-plans` instead
- Exploration or research tasks where the goal is learning, not building
---
## Save Location
Write the plan document to:
```
docs/claudekit/plans/YYYY-MM-DD-<topic>-plan.md
```
Create the `docs/claudekit/plans/` directory if it does not exist. Use today's date and a short, kebab-case topic slug matching the related design doc (if any).
---
## Plan Document Format
### Header Section
```markdown
# Plan: [Feature Name]
**Required Skill**: executing-plans
## Goal
[One sentence describing what will be built]
## Architecture Overview
[2-3 sentences describing the approach]
## Tech Stack
- [Technology 1]
- [Technology 2]
```
### Task Structure (TypeScript)
Each numbered task contains:
```markdown
## Task [N]: [Task Name]
**Files**:
- Create: `path/to/new-file.ts`
- Modify: `path/to/existing-file.ts`
- Test: `path/to/test-file.test.ts`
**Steps**:
1. Write failing test
```typescript
// Exact test code
```
2. Verify test fails
```bash
npm test -- --grep "test name"
# Expected: 1 failing
```
3. Implement minimally
```typescript
// Exact implementation code
```
4. Verify test passes
```bash
npm test -- --grep "test name"
# Expected: 1 passing
```
5. Commit
```bash
git add .
git commit -m "feat: add [feature]"
```
```
### Task Structure (Python / FastAPI)
```markdown
## Task [N]: [Task Name]
**Files**:
- Create: `src/api/orders.py`
- Create: `src/schemas/order.py`
- Test: `tests/test_orders.py`
**Steps**:
1. Write failing test
```python
import pytest
from httpx import AsyncClient
@pytest.mark.anyio
async def test_create_order_returns_201(client: AsyncClient):
response = await client.post("/api/orders", json={"item": "widget", "quantity": 2})
assert response.status_code == 201
assert response.json()["item"] == "widget"
```
2. Verify test fails
```bash
pytest tests/test_orders.py -v
# Expected: FAILED — 404 (route doesn't exist)
```
3. Implement minimally
```python
from fastapi import APIRouter, status
from pydantic import BaseModel
router = APIRouter(prefix="/api/orders")
class CreateOrderRequest(BaseModel):
item: str
quantity: int
@router.post("", status_code=status.HTTP_201_CREATED)
async def create_order(body: CreateOrderRequest):
return {"id": "ord_1", "item": body.item, "quantity": body.quantity}
```
4. Verify test passes
```bash
pytest tests/test_orders.py -v
# Expected: 1 passed
```
5. Commit
```bash
git add .
git commit -m "feat: add create order endpoint"
```
```
---
## Task Granularity
### Bite-Sized Principle
Each task should be **2-5 minutes** of focused work:
- Write one test
- Implement one function
- Add one validation
### Why Small Tasks?
- Easier to verify correctness
- Natural commit points
- Reduces context switching
- Enables parallel work
- Clearer progress tracking
### Bad vs Good Task Breakdown
```
BAD: "Implement user authentication"
GOOD:
- Task 1: Create User model with email field
- Task 2: Add password hashing to User model
- Task 3: Create login endpoint
- Task 4: Add JWT token generation
- Task 5: Create auth middleware
- Task 6: Add token refresh endpoint
```
---
## Core Requirements
### Exact File Paths Always
Never use vague references:
```
BAD: "Update the user service"
GOOD: "Modify `src/services/user-service.ts`"
```
### Complete Code Samples
Include exact code, not descriptions:
```
BAD: "Add a function that validates email"
GOOD:
```typescript
export function validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
```
```
### Expected Output Specifications
Always specify expected command results:
```bash
npm test
# Expected output:
# PASS src/services/user.test.ts
# User validation
# ✓ validates correct email format (3ms)
# ✓ rejects invalid email format (1ms)
# 2 passing
```
---
### Stack-Specific Task Commands
| Stack | Test Command | Lint Command | Build Command |
|-------|-------------|-------------|---------------|
| Python/FastAPI | `pytest -v --cov=src` | `ruff check .` | N/A |
| Python/Django | `python manage.py test` | `ruff check .` | N/A |
| TypeScript/NestJS | `npm test` | `npm run lint` | `npm run build` |
| Next.js | `npm test` or `npx vitest run` | `next lint` | `next build` |
| React (Vite) | `npx vitest run` | `npm run lint` | `npm run build` |
---
## Guiding Principles
### DRY (Don't Repeat Yourself)
- Identify patterns before implementation
- Plan for reusable components
- Note shared utilities needed
### YAGNI (You Aren't Gonna Need It)
- Only plan what's required now
- Remove speculative features
- Add complexity when justified
### TDD (Test-Driven Development)
Every task follows:
1. Write failing test
2. Verify it fails
3. Implement minimally
4. Verify it passes
5. Refactor if needed
6. Commit
### Frequent Commits
- Commit after each task
- Clear, descriptive messages
- Atomic changes only
---
## Execution Handoff
After plan is complete, offer two implementation pathways:
### Option 1: Subagent-Driven (Current Session)
```
Use the `executing-plans` skill for automated execution with:
- Fresh agent per task
- Code review between tasks
- Quality gates
```
### Option 2: Parallel Session (Separate Worktree)
```
Developer executes in separate environment:
- Read plan file
- Follow tasks sequentially
- Commit after each task
```
---
## Example Plan Snippet
```markdown
# Plan: Add Email Verification
**Required Skill**: executing-plans
## Goal
Add email verification to user registration flow.
## Architecture Overview
Send verification email on registration, validate token on click,
mark user as verified in database.
## Tech Stack
- Node.js, TypeScript
- PostgreSQL
- SendGrid for email
---
## Task 1: Add verified flag to User model
**Files**:
- Modify: `src/models/user.ts`
- Create: `src/migrations/add-verified-flag.ts`
- Test: `src/models/user.test.ts`
**Steps**:
1. Write failing test
```typescript
describe('User model', () => {
it('should have verified flag defaulting to false', () => {
const user = new User({ email: 'test@example.com' });
expect(user.verified).toBe(false);
});
});
```
2. Verify test fails
```bash
npm test -- --grep "verified flag"
# Expected: 1 failing (verified is undefined)
```
3. Add verified field to User model
```typescript
// src/models/user.ts
export class User {
email: string;
verified: boolean = false; // Add this line
// ...
}
```
4. Verify test passes
```bash
npm test -- --grep "verified flag"
# Expected: 1 passing
```
5. Commit
```bash
git add src/models/user.ts src/models/user.test.ts
git commit -m "feat(user): add verified flag with false default"
```
```
---
## Related Skills
- `brainstorming` -- Use before writing plans when requirements are unclear or need exploration
- `autoplan` -- After the plan is written, run autoplan (or individual plan-*-review skills) to pressure-test it on strategy, architecture, design, and DX before implementation
- `plan-ceo-review`, `plan-eng-review`, `plan-design-review`, `plan-devex-review` -- Individual dimension reviews of a written plan
- `executing-plans` -- Use after writing a plan to execute it with subagent-driven development and review gates
- `test-driven-development` -- Plans follow TDD principles; reference this skill for strict red-green-refactor enforcement
-204
View File
@@ -1,204 +0,0 @@
---
name: writing-skills
description: >
Use when creating new skills for this Claude Code kit, editing existing skills, or verifying skills work before deployment. Trigger for keywords like "create a skill", "new skill", "write a skill", "edit skill", "improve skill", "skill format", or when the user wants to add a new capability to the kit. Also activate when auditing skill quality or checking that descriptions trigger correctly.
---
# Writing Skills
## When to Use
- Creating a new skill from scratch
- Editing or improving an existing skill
- Auditing skill quality and trigger accuracy
- Adding stack-specific examples to methodology skills
## When NOT to Use
- Using an existing skill (just invoke it)
- Writing commands (see `.claude/commands/`)
- Writing agents (see `.claude/agents/`)
---
## Skill File Structure
Every skill lives in `.claude/skills/<name>/SKILL.md` with this format:
```markdown
---
name: <skill-name>
description: >
<trigger description — when Claude should activate this skill>
---
# <Skill Title>
## When to Use
- [concrete scenarios]
## When NOT to Use
- [anti-patterns, common misapplications]
---
## Core Content
[patterns, code examples, templates]
---
## Common Pitfalls
[mistakes to avoid]
---
## Related Skills
- `other-skill` — how it relates
```
---
## The Description Field
The `description` field is the **most important part** — it determines when Claude activates the skill. It's a trigger description, not a summary.
### Good descriptions
```yaml
# Specific trigger conditions with keywords
description: >
Trigger this skill whenever writing new features, fixing bugs, or
changing any behavior in production code. Activate for keywords like
"implement", "add feature", "fix bug". This skill should be the
default for ALL implementation work.
```
### Bad descriptions
```yaml
# Too vague — when does this trigger?
description: A skill about testing.
# Too narrow — misses common scenarios
description: Use only when the user says "write a unit test".
```
### Description checklist
- [ ] Lists concrete trigger keywords
- [ ] Describes scenarios, not just topics
- [ ] Includes "also trigger when..." for edge cases
- [ ] Mentions what the skill is NOT for (helps avoid false positives)
---
## Naming Conventions
| Type | Convention | Examples |
|------|-----------|----------|
| Methodology | Gerund (verb-ing) | `brainstorming`, `writing-plans`, `systematic-debugging` |
| Language/Framework | Noun | `python`, `nestjs`, `react`, `postgresql` |
| Pattern | Noun/compound | `performance-optimization`, `session-management`, `git-workflows` |
This matches Anthropic's own naming convention for superpowers skills.
---
## Code Example Guidelines
### Methodology skills: always dual-stack
Methodology skills (brainstorming, TDD, debugging, etc.) must include both Python and TypeScript examples:
```markdown
### TypeScript
\`\`\`typescript
describe('calculateTotal', () => {
it('should sum item prices', () => {
expect(calculateTotal(items)).toBe(30);
});
});
\`\`\`
### Python
\`\`\`python
def test_calculate_total_sums_item_prices():
items = [{"price": 10}, {"price": 20}]
assert calculate_total(items) == 30
\`\`\`
```
### Framework skills: single-stack with depth
Framework skills (nestjs, fastapi, react) use only their stack but go deeper:
```markdown
- Module/DI patterns
- Request validation
- Authentication guards
- Database integration
- Testing patterns
- Error handling
- Deployment
```
### Quality checklist for examples
- [ ] Real code, not pseudocode
- [ ] Includes import statements where relevant
- [ ] Shows the test AND the implementation
- [ ] Includes verification command (`pytest -v`, `npm test`)
- [ ] Matches the kit's code conventions (PEP 8, strict TS)
---
## Skill Quality Checklist
Before committing a new or updated skill:
1. **Trigger accuracy** — Does the description match real usage scenarios?
2. **When to Use / When NOT to Use** — Are both sections specific and non-overlapping with other skills?
3. **Actionable patterns** — Does it tell you WHAT to do, not just WHAT things are?
4. **Code examples** — Real, copy-pasteable code (not theory or pseudocode)?
5. **Verification commands** — Does it show how to verify the patterns work?
6. **Pitfalls section** — Does it warn about common mistakes?
7. **Related Skills** — Does it link to complementary skills with brief explanations?
8. **Line count** — 200-350 lines is the sweet spot. Under 150 is too thin; over 400 is too bloated.
---
## Registering a New Skill
After writing the skill file:
1. **CLAUDE.md** — Add to the skill table under the appropriate category
2. **Description in system** — The description in YAML frontmatter is what Claude Code uses for triggering
### CLAUDE.md skill table format
```markdown
| **Category** | Skills |
|--------------|--------|
| **Languages** | python, typescript, javascript |
| **Methodology - Planning** | brainstorming, writing-plans, executing-plans |
```
---
## Updating Existing Skills
When improving an existing skill:
1. **Read current content first** — understand what's there
2. **ADD, don't rewrite** — preserve existing discipline/rigor
3. **Stack-specific examples** — add Python alongside TS (or vice versa)
4. **Test the trigger** — does the description still match after changes?
5. **Update Related Skills** — if the new content creates new connections
---
## Related Skills
- `writing-plans` — Plan format for multi-step implementation tasks
- `verification-before-completion` — Verify skills work before committing
- `writing-concisely` — Keep skills concise and scannable