release 1.0.0: cost control, 41 tools, 29 skills, custom commands, auto-load
This commit is contained in:
1 parent
a22d8e13b1
commit
ffa9a02c26
97 files changed
+4791
-788
No files matched your search
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: accessibility
|
||||
description: Make a UI accessible. Use when adding a feature that must work with a keyboard or screen reader, fixing contrast or focus issues, or reviewing for WCAG.
|
||||
---
|
||||
|
||||
# Accessibility
|
||||
|
||||
Accessibility is usability for everyone, including people using a keyboard, a screen reader, a
|
||||
magnifier, or a noisy display. Build it in, not on.
|
||||
|
||||
## Semantic HTML does the heavy lifting
|
||||
|
||||
A `<button>`, `<a>`, `<input>`, `<nav>`, `<main>` carries behaviour and meaning for free
|
||||
that a `<div>` with a click handler does not. Reach for the native element first; add ARIA only
|
||||
when no native element fits. The first rule of ARIA is do not use ARIA if a native element exists.
|
||||
|
||||
## Keyboard is the baseline
|
||||
|
||||
- Every interactive element is reachable and operable with Tab and Enter/Space alone.
|
||||
- A visible focus indicator on everything — never `outline: none` without a replacement.
|
||||
- Logical tab order following the visual order, and focus managed into and out of modals,
|
||||
menus, and dialogs (trapped while open, returned to the trigger on close).
|
||||
|
||||
## Screen readers hear structure
|
||||
|
||||
- Headings in order (`h1` once, then down a level at a time) so the page has a navigable outline.
|
||||
- Every `<input>` has a `<label>`; every icon-only button has an accessible name; every image
|
||||
has alt text that conveys its point (or empty alt when it is purely decorative).
|
||||
- Dynamic changes announce themselves: a toast, an error, a loaded region uses a live region so
|
||||
it is heard, not just seen.
|
||||
|
||||
## Contrast and meaning
|
||||
|
||||
Text meets 4.5:1 against its background (3:1 for large text). Colour is never the only carrier
|
||||
of meaning — pair it with an icon, a label, or a pattern. A red-only "error" is invisible to a
|
||||
colour-blind user.
|
||||
|
||||
## Test it the way it is used
|
||||
|
||||
Tab through the whole flow. Turn on a screen reader and listen. Zoom to 200% and 400%. Run an
|
||||
automated checker for the mechanical half — then do the manual half it cannot cover, because
|
||||
most accessibility failures are not machine-detectable.
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: api-design
|
||||
description: Design or revise an HTTP or library API. Use when adding an endpoint, shaping request/response bodies, naming resources, or reviewing an API for consistency.
|
||||
---
|
||||
|
||||
# API design
|
||||
|
||||
An API is a contract. Every choice is a promise you cannot take back without a major version.
|
||||
|
||||
## Resource before action
|
||||
|
||||
Name things, not verbs. `POST /users` to create, not `POST /createUser`. The URL is the
|
||||
noun; the method is the verb. When you reach for a verb in the path, that is a sign the
|
||||
resource is missing — `POST /users/:id/deactivations` reads better than `/deactivateUser`
|
||||
when the operation has state of its own.
|
||||
|
||||
## Shape the body for the reader
|
||||
|
||||
- Field names are `snake_case` or `camelCase`, picked once for the whole API. A body that
|
||||
mixes both is a body nobody documented.
|
||||
- Return the object, not a wrapper, unless the wrapper carries something: `{ "user": {...} }`
|
||||
only when there is also pagination, a cursor, or an error envelope.
|
||||
- Errors have a stable shape: a machine-readable `code`, a human `message`, and the field
|
||||
that failed. A client should never have to parse the message.
|
||||
|
||||
## Status codes mean something
|
||||
|
||||
- `201` for a created resource, with the resource in the body.
|
||||
- `204` for success with nothing to return.
|
||||
- `400` for a body that failed validation, `401` unauthenticated, `403` authenticated but
|
||||
not allowed, `404` not found or not allowed to know, `409` a conflict with current state,
|
||||
`422` well-formed but semantically wrong.
|
||||
- Never `200` with an error in the body. A client checking only the status will treat it as
|
||||
success.
|
||||
|
||||
## Idempotency and safety
|
||||
|
||||
GET, PUT, DELETE must be safe to retry: same request, same state. POST is not. If a client can
|
||||
double-submit, provide an idempotency key or a natural unique constraint, and say which.
|
||||
|
||||
## Version when you must, not before
|
||||
|
||||
Add fields freely; removing or renaming is a break. If you are not yet committed, say so with
|
||||
a `beta` or `v0` marker rather than locking a shape you have not used. Document the contract
|
||||
you guarantee, not the implementation that happens to produce it.
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: ci-cd
|
||||
description: Write or repair CI/CD pipelines and workflow files. Use when a build fails in CI but not locally, when adding a workflow, or when caching, matrix, or deploy steps need design.
|
||||
---
|
||||
|
||||
# CI/CD
|
||||
|
||||
CI is a second machine that does not have your setup. "Works on my machine" means the pipeline
|
||||
is missing something your machine has.
|
||||
|
||||
## Reproduce the environment, not the symptom
|
||||
|
||||
When CI fails and local passes, the difference is the environment: the toolchain version, an
|
||||
uncommitted file, a cache, an env var, the OS. Diff those before touching the code. Read the
|
||||
failing log literally — the first error, not the last, which is usually a downstream echo.
|
||||
|
||||
## Pin everything that can move
|
||||
|
||||
- Toolchain versions (`node`, `bun`, `python`), action versions, base images. `latest`
|
||||
is a build that breaks on a day you did nothing.
|
||||
- Lockfiles go in the repo and the install respects them (`--frozen-lockfile`, `ci`). An
|
||||
install that re-resolves in CI is a different build from the one you tested.
|
||||
|
||||
## Cache the expensive, deterministic part
|
||||
|
||||
Dependencies are the cache; build output usually is not. Key the cache on the lockfile hash so
|
||||
a changed dependency invalidates it. A cache that is too broad serves stale artifacts; too
|
||||
narrow saves nothing.
|
||||
|
||||
## Fail fast, in the right order
|
||||
|
||||
Cheap checks first: lint and typecheck before the test matrix, tests before the deploy. A
|
||||
pipeline that deploys before it verifies publishes the bug it was built to catch.
|
||||
|
||||
## Secrets and deploys
|
||||
|
||||
Secrets live in the CI secret store, never in the file, and are masked in logs. A deploy step
|
||||
is gated: on a tag, on a protected branch, on a manual approval — never on every push. Assume
|
||||
every log line is public and write the pipeline accordingly.
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
name: commit
|
||||
description: Stage and commit work. Use when asked to commit, or to split existing changes into commits.
|
||||
---
|
||||
|
||||
# Committing
|
||||
|
||||
Never commit unless the user asked. If it is unclear whether they did, ask. A commit is a
|
||||
durable statement about shared history, not a save-point.
|
||||
|
||||
## Look before you stage
|
||||
|
||||
`git_status` and `git_diff` first — read the whole diff you are about to commit. You are
|
||||
looking for three things:
|
||||
|
||||
1. **Changes that are not yours.** Another agent or the user may share this worktree, and
|
||||
`git add .` takes their half-finished work with yours.
|
||||
2. **Files that should never be committed:** `.env`, credentials, keys, large build
|
||||
output, anything a `.gitignore` rule was supposed to catch and did not. Flag these to
|
||||
the user rather than committing them — a committed secret is a secret to rotate.
|
||||
3. **Your own accidents:** debug prints, commented-out code, a stray `TODO`, a file you
|
||||
opened and saved by mistake. Revert them before staging, not in a follow-up commit.
|
||||
|
||||
Stage the specific paths you changed. `git add .` is how unrelated work ends up in a
|
||||
commit that then has to be reverted whole.
|
||||
|
||||
## One commit, one reason
|
||||
|
||||
If the diff does two unrelated things, make two commits. A commit that both fixes a bug
|
||||
and renames a module cannot be reverted, cherry-picked, or bisected usefully. Each commit
|
||||
should pass the tests on its own — a series of broken commits defeats `git bisect`.
|
||||
|
||||
## The message
|
||||
|
||||
Match the repository's existing style — read `git_log` before writing one. Failing that:
|
||||
|
||||
- A subject line under 70 characters, imperative mood, saying what changed: "Fix off-by-one
|
||||
in pagination", not "fixed a bug" or "changes".
|
||||
- A body explaining *why* when the reason is not obvious from the diff. Wrap at 72.
|
||||
- No "as requested", no restating the diff line by line, no emoji unless the repo uses them,
|
||||
no sign-off noise the repo does not already use.
|
||||
|
||||
## Do not
|
||||
|
||||
- Do not `--amend` a commit that has been pushed. Write a new one.
|
||||
- Do not `--no-verify`. If a hook rejects the commit, the hook found something — read it.
|
||||
- Do not `git push` unless asked, and never force-push without being asked explicitly.
|
||||
- Do not commit and then immediately fix it up with a second commit. Get it right, or say
|
||||
what is wrong.
|
||||
|
||||
## After committing
|
||||
|
||||
Report the short hash and the subject. If a hook rewrote files, say so, and confirm the
|
||||
final state — `git_status` again — is what was intended.
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: data
|
||||
description: Process, validate, or transform data. Use when parsing files, cleaning datasets, designing a data pipeline, or debugging a transform that produces wrong output.
|
||||
---
|
||||
|
||||
# Data
|
||||
|
||||
Bad data fails silently and far away from where it entered. Validate at the boundary, keep the
|
||||
raw, and make every transform checkable.
|
||||
|
||||
## Validate at the boundary
|
||||
|
||||
Parse and validate when data enters the system, not when it is used. A schema check at the edge
|
||||
turns a corrupt record into a clear rejection; skipping it turns the same record into a wrong
|
||||
answer three layers later. Reject loudly, with the record and the reason — never coerce and
|
||||
carry on.
|
||||
|
||||
## Keep the raw
|
||||
|
||||
Store the untransformed input alongside the derived. When a transform turns out to be wrong,
|
||||
the raw lets you recompute; without it, the information is gone. Derived data is rebuildable;
|
||||
source data is not.
|
||||
|
||||
## Transformations are pure and tested
|
||||
|
||||
A transform takes input and returns output with no hidden state, so it can be tested on a
|
||||
fixture and re-run safely. Test the edge cases that actually occur in data: the empty field,
|
||||
the wrong type, the unexpected null, the duplicate, the encoding that is not UTF-8.
|
||||
|
||||
## Duplicates, nulls, and ranges are the usual corruption
|
||||
|
||||
Check for: unexpected duplicates on a key, nulls where a value is required, values outside a
|
||||
sane range (a negative age, a date in the future), and referential breaks (an id pointing at
|
||||
nothing). These four catch most real-world data problems before they reach a report.
|
||||
|
||||
## Idempotent pipelines
|
||||
|
||||
A step that can be re-run without duplicating or corrupting its output is a step you can retry
|
||||
after a failure. Key on a stable id and upsert rather than blind-insert. A pipeline you cannot
|
||||
safely re-run is a pipeline you will one day have to fix by hand at 2am.
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: db
|
||||
description: Design schemas, write migrations, or fix query and data problems. Use when adding a table, writing a migration, debugging a slow query, or choosing keys and indexes.
|
||||
---
|
||||
|
||||
# Databases
|
||||
|
||||
The schema is the hardest thing to change in the whole system. Design it for the queries, not
|
||||
the object model.
|
||||
|
||||
## Keys and constraints are the real schema
|
||||
|
||||
- Every table has a primary key; prefer a surrogate `id` unless a natural key is truly stable.
|
||||
- Foreign keys and `NOT NULL` are not optional decoration — they are the constraints that stop
|
||||
bad data at the door instead of in application code six months later.
|
||||
- Unique constraints belong on the thing that must be unique (email, slug), enforced by the
|
||||
database, not by a check-then-insert that races.
|
||||
|
||||
## Migrations are one-way and additive where possible
|
||||
|
||||
- Never edit a migration that has run anywhere. Add a new one.
|
||||
- Destructive changes (drop column, rename, change type) are two migrations: add the new shape,
|
||||
deploy code that writes both, then remove the old in a later release. A single migration that
|
||||
renames a column breaks every old copy of the app still running.
|
||||
- Test a migration against real data volume. `ALTER` on ten rows is instant; on ten million it
|
||||
locks the table.
|
||||
|
||||
## Indexes follow the queries
|
||||
|
||||
Index the columns you filter and join on, in the order the query uses them. A composite index
|
||||
`(a, b)` serves `WHERE a` and `WHERE a, b` but not `WHERE b` alone. Read the query plan
|
||||
(`EXPLAIN`) before adding one — a guess is an index that costs writes and serves nothing.
|
||||
|
||||
## The N+1 is the default bug
|
||||
|
||||
A query per row in a loop is the most common database performance defect. Fetch the set with a
|
||||
join or a batched `WHERE id IN (...)`. If a page does one query per item, that is the fix
|
||||
before any caching.
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
name: debug
|
||||
description: Track down a bug whose cause is not obvious. Use when a test fails for unclear reasons, behaviour differs between environments, or an earlier fix did not hold.
|
||||
---
|
||||
|
||||
# Debugging
|
||||
|
||||
Do not guess. A guess that happens to work leaves the real cause in place, and it will
|
||||
fire again — usually in production, usually at a worse time.
|
||||
|
||||
## Reproduce first
|
||||
|
||||
Find the smallest command that shows the failure and record it with `remember`. If you
|
||||
cannot reproduce it, say so and ask what the user did differently — do not proceed on a
|
||||
hypothesis you cannot test.
|
||||
|
||||
Shrink the reproduction until it is minimal: one input, one call, one assertion. Every
|
||||
moving part you leave in is a place the bug can hide. A reproduction that takes thirty
|
||||
steps will not get run often enough to confirm the fix.
|
||||
|
||||
## Three hypotheses, then evidence
|
||||
|
||||
Write down at least three causes that would produce this exact symptom — not "the code is
|
||||
wrong" but specific mechanisms: "the offset is off by one when the page is empty", "the
|
||||
cache is read before the write lands". Rank them by how cheap they are to disprove, then
|
||||
disprove them in that order. State which one you are testing before you test it.
|
||||
|
||||
Evidence means observed output: a log line, a failing assertion, a value printed at the
|
||||
point of failure. "It should be X" is not evidence. When the evidence contradicts your
|
||||
favoured hypothesis, the hypothesis is wrong — do not explain the evidence away.
|
||||
|
||||
## Localise before you fix
|
||||
|
||||
Assert the value at each boundary until one is wrong. The bug lives between the last
|
||||
boundary where the value is right and the first where it is wrong. Fixing before you have
|
||||
that bracket means editing the wrong place and learning nothing.
|
||||
|
||||
## Bisect when the space is large
|
||||
|
||||
- Recent regression: `git bisect` or read what changed last. The bug arrived in a commit;
|
||||
find which one.
|
||||
- Unclear layer: assert the value at each boundary until one is wrong.
|
||||
- Intermittent: run it in a loop and capture the failing case with full logging. Do not
|
||||
reason about a race abstractly — make it happen on demand, then it is no longer
|
||||
intermittent.
|
||||
|
||||
## Fix the cause, not the symptom
|
||||
|
||||
Once you know the cause, fix that and nothing else. Do not tidy surrounding code in the
|
||||
same change — a bugfix diff should contain only the bug, so it can be reverted whole if it
|
||||
is wrong.
|
||||
|
||||
Write a test that fails before the fix and passes after. Watch it fail first; a test you
|
||||
never saw fail proves nothing. If you cannot express the bug as a test, say why — and say
|
||||
what you ran instead to confirm the fix.
|
||||
|
||||
## After two failed attempts
|
||||
|
||||
Stop. Re-read the error text literally, character by character — most "impossible" bugs
|
||||
are a misread message. Then check your assumption about which code is actually running:
|
||||
the wrong file, a stale build, a shadowed import, a cached dependency, or an env var that
|
||||
differs from your shell. Verify by printing something at the point you *think* executes;
|
||||
if it does not print, that is your answer.
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: deps
|
||||
description: Manage dependencies: choosing, adding, updating, or removing them. Use when evaluating a library, resolving a version conflict, pruning unused deps, or hardening the supply chain.
|
||||
---
|
||||
|
||||
# Dependencies
|
||||
|
||||
Every dependency is code you did not write but now maintain. Add deliberately, prune regularly.
|
||||
|
||||
## Choose on maintenance, not features
|
||||
|
||||
Before adding: is it actively maintained (recent commits, responsive issues), widely used, and
|
||||
small enough to be worth it? A dependency that saves a day and is abandoned in a year costs a
|
||||
week. For something small and stable, a dozen lines in your own codebase often beats a package.
|
||||
|
||||
## Pin and lock
|
||||
|
||||
Exact versions in the manifest for anything that matters, a lockfile committed, and installs
|
||||
that respect it. A `^` range means your build tomorrow differs from your build today. The
|
||||
lockfile is the build's memory; do not delete it to "fix" a conflict — resolve the conflict.
|
||||
|
||||
## Update on a schedule, read the changelog
|
||||
|
||||
Routine small updates beat a yearly painful one. For a major bump: read the changelog and the
|
||||
migration guide, find every call site of the changed API, and apply one shape of change (see
|
||||
the migrate skill). Update one thing at a time so a regression has an obvious cause.
|
||||
|
||||
## Know your transitive tree
|
||||
|
||||
A direct dependency drags in dozens of transitive ones. Audit the tree for: known
|
||||
vulnerabilities (`audit`/SCA tooling), abandoned packages deep in it, and duplicate copies of
|
||||
the same library at different versions bloating the bundle. Remove what you no longer use — an
|
||||
unused dependency is attack surface and install time for nothing.
|
||||
|
||||
## Supply chain is a trust decision
|
||||
|
||||
A package runs its install scripts with your permissions. Prefer packages with provenance and a
|
||||
reproducible build, be wary of sudden ownership transfers, and pin so a hijacked publish does
|
||||
not reach you automatically. The lockfile is also your audit trail of exactly what shipped.
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: docker
|
||||
description: Write or fix Dockerfiles and container setups. Use when an image is too large, a build is slow, a container will not start, or layering and caching need design.
|
||||
---
|
||||
|
||||
# Docker
|
||||
|
||||
An image is a build artifact. Small, reproducible, and boring is the goal.
|
||||
|
||||
## Layer cache is the whole speed game
|
||||
|
||||
Order instructions from least to most frequently changed: base image, then dependency
|
||||
manifests, then `install`, then source copy, then build. Copying `.` before installing
|
||||
dependencies means every code change re-runs the install — the single most common Dockerfile
|
||||
mistake.
|
||||
|
||||
## Small images, on purpose
|
||||
|
||||
- Use multi-stage builds: build in a full toolchain stage, copy only the artifact into a slim
|
||||
runtime stage. The compiler does not ship to production.
|
||||
- Pick a slim or distroless base unless you need the tooling. Alpine is small but musl breaks
|
||||
some binaries; know why you chose it.
|
||||
- One `RUN` with `&&` for related steps, cleaning up in the same layer — a separate `RUN rm`
|
||||
does not shrink the image, the data is still in the earlier layer.
|
||||
|
||||
## The container is not a VM
|
||||
|
||||
- One process per container, as PID 1, so signals work. Use an init if the app spawns children.
|
||||
- Do not run as root. Add a user and `USER` it.
|
||||
- Read-only filesystem where possible; write to a mounted volume for anything that must persist.
|
||||
Nothing in the image is writable state.
|
||||
|
||||
## .dockerignore is as important as the Dockerfile
|
||||
|
||||
Exclude `.git`, `node_modules`, build output, and any secret file. A context that sends the
|
||||
whole repo is slow, and a secret copied into an image layer is a secret to rotate.
|
||||
|
||||
## Healthcheck and logs
|
||||
|
||||
The process logs to stdout/stderr, never to a file inside the container — the runtime collects
|
||||
it. Add a `HEALTHCHECK` that proves the service answers, not just that the process exists.
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: docs
|
||||
description: Write or update documentation, READMEs, and guides. Use when asked to document a feature, write usage docs, or bring docs back in line with the code.
|
||||
---
|
||||
|
||||
# Documentation
|
||||
|
||||
Docs lie by omission. Write only what you have verified in the code.
|
||||
|
||||
## Ground every claim in the source
|
||||
|
||||
Before documenting a behaviour, read it. A flag, a default, an error message — open the
|
||||
code and quote what it actually does, not what the name suggests. The most damaging doc
|
||||
line is the confident one that was true two versions ago. If the code and the existing
|
||||
docs disagree, the code is right; say so and fix the doc.
|
||||
|
||||
## Answer the reader's actual question
|
||||
|
||||
A reader opens a doc with a task, not a desire for completeness. Lead with the thing they
|
||||
came to do, in the order they will do it:
|
||||
|
||||
- **A reference** lists what exists: every flag, every field, with its default and its type.
|
||||
- **A guide** walks one path to one outcome. Resist documenting every branch — link instead.
|
||||
- **A README** orients in sixty seconds: what it is, install, the first command that works.
|
||||
|
||||
## Show, then say
|
||||
|
||||
A working example beats a paragraph about one. Every command in the doc must be one you
|
||||
ran, with its real output. A snippet that was never executed is a bug waiting for a reader.
|
||||
|
||||
## Match the house style
|
||||
|
||||
Read the neighbouring docs first: their heading depth, their code-fence language tags,
|
||||
their tone. A doc that reads foreign is a doc nobody trusts enough to maintain.
|
||||
|
||||
## Keep it true over time
|
||||
|
||||
Document the stable contract, not the current implementation, unless the point is the
|
||||
implementation. The fewer specifics a doc pins down, the less it rots.
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: frontend
|
||||
description: Build or fix a web UI. Use when working on components, state, rendering performance, forms, or anything the user sees and interacts with in a browser.
|
||||
---
|
||||
|
||||
# Frontend
|
||||
|
||||
The user's experience is the metric. Fast, clear, and forgiving beats clever.
|
||||
|
||||
## State lives as low as it can
|
||||
|
||||
Lift state only as high as the components that share it. Global state for something two siblings
|
||||
need is re-render and complexity for everything. Server data is not client state — cache it with
|
||||
the data layer rather than duplicating it into a store you must keep in sync by hand.
|
||||
|
||||
## Rendering is the usual bottleneck
|
||||
|
||||
Before optimising, find what re-renders. A component that re-renders on every parent render
|
||||
because of an inline object or function prop is the common case. Memoize the expensive subtree,
|
||||
not everything — `useMemo` and `useCallback` have a cost too, and slapping them everywhere is
|
||||
its own slowdown.
|
||||
|
||||
## Forms respect the user
|
||||
|
||||
- Validate on blur or submit, not on every keystroke, and show the message at the field.
|
||||
- Never clear a form on an error. The user's input is the most expensive thing on the page.
|
||||
- Disable the submit while submitting, and say what is happening. A double-submitted form is a
|
||||
duplicate record.
|
||||
|
||||
## Accessibility is not a later pass
|
||||
|
||||
Semantic HTML first: a `<button>` that looks like a button beats a `<div>` with a click
|
||||
handler. Keyboard-reachable everything, visible focus, labels on inputs, alt text that conveys
|
||||
the point not the pixels. Colour is never the only carrier of meaning.
|
||||
|
||||
## Measure what the user feels
|
||||
|
||||
Load: get the first meaningful paint and the time-to-interactive down before micro-tuning.
|
||||
Bundle: split the route nobody opens, lazy-load the heavy component. A Lighthouse number is a
|
||||
proxy; the goal is that it never feels slow.
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: git-workflow
|
||||
description: Work with branches, rebases, merges, and history. Use when untangling a branch, preparing a PR, deciding rebase vs merge, or recovering from a git mistake.
|
||||
---
|
||||
|
||||
# Git workflow
|
||||
|
||||
History is a communication tool. Write it for the person who reads it in six months — usually you.
|
||||
|
||||
## One branch, one purpose
|
||||
|
||||
A branch that does two things produces a PR that can only be reviewed as all-or-nothing and
|
||||
reverted only whole. Keep it small and single-purpose; open the second thing as its own branch.
|
||||
|
||||
## Rebase to clean up, merge to preserve
|
||||
|
||||
- Rebase your own unpushed work freely: it makes a linear, readable history.
|
||||
- Never rebase a branch others have pulled — it rewrites commits they have, and the next pull
|
||||
becomes a mess. Merge shared branches instead.
|
||||
- Interactive rebase before opening the PR: squash the "fix typo" and "wip" commits into the
|
||||
change they belong to. The PR should read as a series of intentional steps, not a diary.
|
||||
|
||||
## Recover without panic
|
||||
|
||||
- `git reflog` finds almost anything you "lost": the branch you deleted, the commit you reset
|
||||
away. Nothing committed is truly gone for ~30 days.
|
||||
- A bad merge: `git merge --abort`. A bad rebase: `git rebase --abort`. Both stop cleanly
|
||||
rather than pushing forward into a worse state.
|
||||
- Committed to the wrong branch: `git reset --soft` to keep the work, switch, recommit.
|
||||
|
||||
## The commit message is the review's first page
|
||||
|
||||
Subject under 70 chars, imperative, says what changed. Body explains *why* when it is not
|
||||
obvious. A reviewer who cannot tell why a change exists from its message will ask, or worse,
|
||||
approve without understanding.
|
||||
|
||||
## Read the conflict, do not guess
|
||||
|
||||
On a conflict, open the file and understand both sides before resolving. Taking "ours" or
|
||||
"theirs" wholesale because it is faster is how a resolved conflict silently drops someone's
|
||||
work.
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: i18n
|
||||
description: Internationalise or localise a product. Use when extracting strings for translation, formatting dates and numbers for a locale, handling pluralisation, or fixing layout that breaks in another language.
|
||||
---
|
||||
|
||||
# Internationalisation
|
||||
|
||||
Hard-coded English is a bug in every other language. Externalise strings and never assume a
|
||||
grammar.
|
||||
|
||||
## Every user-facing string is a key
|
||||
|
||||
No string in the UI lives in code; it lives in a message catalogue under a key. Concatenating
|
||||
translated fragments is the classic bug: "You have " + n + " messages" cannot be reordered for
|
||||
a language whose grammar puts the number elsewhere. Use a format with named placeholders:
|
||||
`{count, plural, ...}`, translated as a whole.
|
||||
|
||||
## Pluralisation and gender are not English
|
||||
|
||||
Languages have one, two, several, or no plural forms, with rules that do not map to "1 vs other".
|
||||
Use the ICU plural machinery of your i18n library and let the translator fill in every form the
|
||||
locale needs. The same goes for gendered agreement.
|
||||
|
||||
## Format dates, numbers, and currencies by locale
|
||||
|
||||
Never `dd/mm/yyyy` by hand: `03/04/2025` is March 4th to one user and April 3rd to another.
|
||||
Use the platform's locale-aware formatter (`Intl.DateTimeFormat`, `Intl.NumberFormat`).
|
||||
Store and transmit ISO 8601 / UTC; format for display only.
|
||||
|
||||
## Layout breaks in translation
|
||||
|
||||
German runs ~30% longer than English; some scripts are right-to-left. Flexible layout, no fixed
|
||||
widths on translated text, and CSS logical properties (`margin-inline-start` not
|
||||
`margin-left`) so RTL mirrors correctly. Test with a pseudo-locale that lengthens and accents
|
||||
every string to find the overflows before a translator does.
|
||||
|
||||
## Sort and search correctly
|
||||
|
||||
String order is locale-dependent: `ä` sorts with `a` in German, after `z` in Swedish. Use
|
||||
locale-aware collation (`Intl.Collator` or the database's) rather than byte order, and normalise
|
||||
Unicode before comparing, because the same character has more than one byte representation.
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: incident
|
||||
description: Respond to a production incident. Use when something is down, degraded, or misbehaving in production and must be diagnosed and mitigated under time pressure.
|
||||
---
|
||||
|
||||
# Incident response
|
||||
|
||||
Mitigate first, diagnose second. Restore service, then find out why.
|
||||
|
||||
## Confirm and scope before touching anything
|
||||
|
||||
What is actually broken, for whom, since when? Check the signal, not the report: the dashboard,
|
||||
the error rate, the health endpoint. A wrong scope sends you chasing a symptom. State the impact
|
||||
plainly in one line before you start changing things.
|
||||
|
||||
## Recent change is the prime suspect
|
||||
|
||||
Most incidents follow a deploy, a config change, a flag flip, or a scaling event. What changed
|
||||
in the window before it broke? Check the deploy log and the diff. The fastest fix is usually to
|
||||
undo the last change, not to understand it.
|
||||
|
||||
## Mitigate, then understand
|
||||
|
||||
- Roll back the deploy, flip the flag off, fail over, scale up, restart the wedged process —
|
||||
whichever restores service fastest, even if you do not yet know the root cause.
|
||||
- A mitigation you can reverse beats a perfect diagnosis that takes an hour. Note what you did so
|
||||
it can be undone or made permanent later.
|
||||
- Do not deploy an unreviewed "fix" into the fire; it adds a second change to a system already
|
||||
misbehaving.
|
||||
|
||||
## Preserve evidence before it rotates away
|
||||
|
||||
Capture the logs, the error, the relevant metrics, a snapshot of the state — before a restart or
|
||||
a rollback destroys it. You will want it for the postmortem, and it may be the only copy.
|
||||
|
||||
## Communicate and follow up
|
||||
|
||||
Say what is broken, what you are doing, and when the next update is — to whoever is affected,
|
||||
in plain language, on a schedule. Afterwards: write the timeline, the root cause, and the
|
||||
follow-ups that stop it recurring. An incident with no follow-up is a loan against the next one.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: logging
|
||||
description: Add or improve logging and observability. Use when debugging in production, adding structured logs, choosing log levels, or making a system traceable.
|
||||
---
|
||||
|
||||
# Logging
|
||||
|
||||
Logs are how you debug a system you cannot attach a debugger to. Write them for the 3am
|
||||
incident, not the happy path.
|
||||
|
||||
## Structure over prose
|
||||
|
||||
Emit fields, not sentences: `{ user: id, action: "checkout", ms: 142, ok: false }`, not
|
||||
`"User checked out"`. Structured logs are searchable and aggregable; a sentence is neither.
|
||||
One event, one line, one level.
|
||||
|
||||
## Levels are a contract
|
||||
|
||||
- `error` — something is broken and someone should look. Not "a user gave bad input".
|
||||
- `warn` — unexpected but handled; worth a glance.
|
||||
- `info` — the meaningful state transitions: started, finished, the decision made. Sparse.
|
||||
- `debug` — everything you might want while diagnosing, off in production.
|
||||
|
||||
A log at the wrong level trains people to ignore the right one. If everything is `error`,
|
||||
nothing is.
|
||||
|
||||
## Log the decision points, not every line
|
||||
|
||||
At a boundary — a request in, a call out, a branch taken — log what was decided and the inputs
|
||||
that decided it, with a correlation id that follows the request across services. You should be
|
||||
able to trace one request end to end from the id alone.
|
||||
|
||||
## Never log a secret
|
||||
|
||||
No passwords, tokens, session ids, full card numbers, or personal data beyond what policy
|
||||
allows. Redact at the point of logging, not by hoping a downstream filter catches it. A secret
|
||||
in a log aggregator is a secret to rotate.
|
||||
|
||||
## Measure, do not just log
|
||||
|
||||
For anything with a latency or a rate, a metric answers "is it slow?" faster than a thousand
|
||||
log lines. Logs explain *why*; metrics tell you *that* something is wrong in the first place.
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
name: migrate
|
||||
description: Upgrade a dependency, framework, or language version across a codebase. Use when a major version bump, a deprecation, or a breaking API change has to be applied.
|
||||
---
|
||||
|
||||
# Migration
|
||||
|
||||
The failure mode is a half-applied migration: it compiles, most tests pass, and one code
|
||||
path still uses the old API.
|
||||
|
||||
## Read the changelog before the code
|
||||
|
||||
Find what actually broke. A major version usually has a migration guide; read it and list
|
||||
the changes that apply to this codebase specifically. Below 1.0, treat a minor bump as
|
||||
breaking — semver promises nothing there.
|
||||
|
||||
## Find every call site before changing one
|
||||
|
||||
Grep for the old API across the whole repository, including tests, scripts, config, CI
|
||||
workflows, Dockerfiles, and documentation. A version literal pinned in a workflow while the
|
||||
manifest says something else is a split-brain deploy.
|
||||
|
||||
Write the list down with `todo_write`. The list is the migration; the edits are mechanical.
|
||||
|
||||
## Change in one shape
|
||||
|
||||
Apply the same transformation everywhere rather than improving each site as you pass
|
||||
through it. A migration mixed with refactoring cannot be reviewed, and cannot be reverted
|
||||
if the upgrade turns out to be wrong.
|
||||
|
||||
`apply_patch` is the tool for this: one atomic patch across the files that must land
|
||||
together.
|
||||
|
||||
## Verify at the boundary that broke
|
||||
|
||||
Type checks catch signature changes and miss behaviour changes — the two ways a migration
|
||||
actually breaks you. Run the tests, then actually *use* the thing that was upgraded: start
|
||||
the server, run the CLI, execute the query, hit the endpoint. A green suite over an
|
||||
untested upgrade path proves only that the suite did not cover it.
|
||||
|
||||
Pay special attention to silent behaviour changes: a default that flipped, a deprecated
|
||||
call that still runs but does something subtly different, an error type that changed shape.
|
||||
These compile, pass type checks, and still break production.
|
||||
|
||||
## Never hand-merge a lockfile
|
||||
|
||||
On a conflict, take either side whole and regenerate with the package manager. The resolver
|
||||
owns that file; a hand-merge is a split-brain dependency tree that installs differently on
|
||||
every machine.
|
||||
|
||||
## Report
|
||||
|
||||
The version before and after, every file class touched, what you verified by running, the
|
||||
behaviour changes you checked by hand, and anything the changelog said applies that you
|
||||
deliberately did not do — with the reason.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: onboarding
|
||||
description: Orient in an unfamiliar codebase. Use when dropped into a new project and asked to understand it, or when writing the docs that help a newcomer get productive.
|
||||
---
|
||||
|
||||
# Onboarding to a codebase
|
||||
|
||||
Understand the running system before the source. The goal is a correct mental model, not to have
|
||||
read every file.
|
||||
|
||||
## Get it running first
|
||||
|
||||
Build it, run it, run the tests. A project you can execute you can interrogate; one you have
|
||||
only read you can only guess at. The README and the `package.json`/`Makefile` scripts tell you
|
||||
the intended commands; if they do not work, that is your first finding.
|
||||
|
||||
## Trace one request end to end
|
||||
|
||||
Pick the central thing the system does and follow it: the entry point, the route or main, the
|
||||
handler, the data out and back. One full path teaches you the architecture faster than reading
|
||||
any single module. Note the layers you cross — that is the system's real structure.
|
||||
|
||||
## Read the structure, not the files
|
||||
|
||||
- The directory layout names the major components and their boundaries.
|
||||
- The dependency manifest names the frameworks and the big choices already made.
|
||||
- The tests show what the code is supposed to do, often better than the code does.
|
||||
- `git log` on a core file shows what changes often and why — the living parts versus the
|
||||
stable ones.
|
||||
|
||||
## Map the seams
|
||||
|
||||
Where does data enter and leave (HTTP, a queue, a file)? Where is state kept (a database,
|
||||
memory, a cache)? Where are the trust boundaries? Those are the places bugs and features both
|
||||
live. You do not need to know every file; you need to know where a change of a given kind would
|
||||
go.
|
||||
|
||||
## Ask the codebase, then a person
|
||||
|
||||
Grep and the outline/symbol tools answer most "where is X" faster than reading. When genuinely
|
||||
stuck on *why* something exists — that is a question for a person or the history, not more
|
||||
reading.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: optimize-sql
|
||||
description: Diagnose and fix a slow SQL query. Use when a query is slow, a page makes too many queries, or an execution plan needs reading.
|
||||
---
|
||||
|
||||
# SQL optimisation
|
||||
|
||||
Read the plan before changing anything. `EXPLAIN` (or `EXPLAIN ANALYZE`) tells you what the
|
||||
database actually does; guessing at it is how you add an index that helps nothing.
|
||||
|
||||
## Read the plan for the expensive node
|
||||
|
||||
Find the node with the highest cost: a sequential scan over a large table, a nested loop over
|
||||
many rows, a sort that spills to disk. Optimise that node. A plan with ten cheap nodes and one
|
||||
expensive one has exactly one thing to fix.
|
||||
|
||||
## The index that matches the query
|
||||
|
||||
- Index the columns in the `WHERE` and `JOIN` clauses, and for a sort, the `ORDER BY`.
|
||||
- A composite index `(a, b, c)` serves a leftmost prefix: `a`, `a,b`, `a,b,c` — not
|
||||
`b` alone. Order the columns by the equality filters first, then the range, then the sort.
|
||||
- A covering index includes every column the query reads, so the table is never touched. That
|
||||
is the fastest a read gets.
|
||||
|
||||
## Write the query so the index is usable
|
||||
|
||||
- `WHERE lower(email) = ...` cannot use a plain index on `email`; either store it lowered or
|
||||
use a functional index. A function on the column defeats the index.
|
||||
- Leading `LIKE '%x'` cannot use a B-tree index; `LIKE 'x%'` can.
|
||||
- `OR` across different columns often defeats an index; `UNION` of two indexed queries can be
|
||||
faster.
|
||||
|
||||
## Kill the N+1 first
|
||||
|
||||
Before any index: if the page runs one query per row, that is the fix. Batch with
|
||||
`WHERE id IN (...)` or a join. Ten queries become one beats ten individually-fast queries.
|
||||
|
||||
## Measure the change
|
||||
|
||||
`EXPLAIN ANALYZE` before and after, against realistic data volume. An index that helps a
|
||||
100-row table may not justify its write cost at 100 million. Report the timing you actually
|
||||
measured, not the improvement you expected.
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: perf-frontend
|
||||
description: Make a web page faster. Use when a page loads slowly, feels janky, fails Core Web Vitals, or ships too much JavaScript.
|
||||
---
|
||||
|
||||
# Frontend performance
|
||||
|
||||
Measure the user's experience first: a Lighthouse lab score and, better, real-user data. Optimise
|
||||
the metric that is actually failing, not the one easiest to move.
|
||||
|
||||
## The vitals and what drives them
|
||||
|
||||
- **LCP** (largest contentful paint) — almost always the hero image or a web font. Preload it,
|
||||
size it correctly, serve it in a modern format, and do not let render-blocking resources delay it.
|
||||
- **INP / responsiveness** — long tasks on the main thread. Break up work, defer non-urgent JS,
|
||||
and keep event handlers fast. A click that responds in 50ms feels instant; 300ms feels broken.
|
||||
- **CLS** (layout shift) — images and embeds without dimensions, late-injected banners, web fonts
|
||||
swapping. Reserve the space before the content arrives.
|
||||
|
||||
## Ship less JavaScript
|
||||
|
||||
The bundle is usually the problem. Route-level code splitting so a page loads only what it needs,
|
||||
lazy-load the heavy below-the-fold component, and audit the dependency tree for a large library
|
||||
imported for one function. Removing 100KB of JS beats most micro-optimisations.
|
||||
|
||||
## Network discipline
|
||||
|
||||
- Cache static assets with long, content-hashed lifetimes; the second visit should cost almost
|
||||
nothing.
|
||||
- Compress (brotli/gzip) and serve images at the size they are displayed, responsive `srcset`,
|
||||
not a 3000px original in a 300px slot.
|
||||
- Fetch in parallel, not in waterfalls: start independent requests together, and preload the
|
||||
critical few.
|
||||
|
||||
## Change one thing, measure it
|
||||
|
||||
Take a baseline (the metric, the page, the device class), make one change, re-measure on the same
|
||||
setup. Two changes at once and you do not know which paid. Report the before/after you actually
|
||||
measured, on a realistic device and connection — a developer's fast laptop and fiber hides what a
|
||||
mid-range phone on 4G feels.
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
name: perf
|
||||
description: Make something faster, or find out why it is slow. Use when a command, request, test suite, or build takes longer than it should.
|
||||
---
|
||||
|
||||
# Performance
|
||||
|
||||
Measure first. A change made without a number before it is a guess with extra steps, and
|
||||
most "optimisations" made on a guess make the code worse and no faster.
|
||||
|
||||
## Get a number
|
||||
|
||||
Time the actual operation, not a proxy for it: `time`, the framework's own timing output,
|
||||
or a loop around the slow call with a timestamp either side. Use realistic input — a fast
|
||||
result on a tiny fixture tells you nothing about the production case. Record the baseline
|
||||
with `remember` so the comparison survives compaction, and run it enough times that a
|
||||
warm cache and jitter do not fool you.
|
||||
|
||||
If you cannot measure it, say so and stop. Optimising an unmeasured path is how a codebase
|
||||
accumulates complexity that buys nothing.
|
||||
|
||||
## Find where the time goes
|
||||
|
||||
- **Wall-clock dominated by one call?** Look there and nowhere else. The biggest node is
|
||||
the only one worth touching.
|
||||
- **Spread evenly?** Suspect the loop around it: an O(n²) walk, a query per row, a file
|
||||
read per iteration, an allocation per element.
|
||||
- **Idle time?** It is waiting, not computing: a sequential chain of independent awaits, an
|
||||
unpooled connection, a contended lock, a slow remote call.
|
||||
|
||||
The usual culprits, in the order they actually appear: N+1 queries, work repeated inside a
|
||||
loop that could be hoisted, a missing index, sequential awaits that could run together,
|
||||
reading a whole file to use one line, and re-parsing something that could be parsed once.
|
||||
|
||||
## Change one thing
|
||||
|
||||
One change, then re-measure on the same setup. Two changes together and you do not know
|
||||
which one paid — and one of them may have cost. If the number did not move, revert the
|
||||
change; an optimisation that does not measure is just complexity.
|
||||
|
||||
## Stop when it is fast enough
|
||||
|
||||
State the target before you start: "the test suite under a minute", "the endpoint under
|
||||
200ms". Past the target, further work is complexity with no user on the other end of it.
|
||||
|
||||
## Report
|
||||
|
||||
Baseline, the change, the new number, and what you deliberately did not do. A 40% win with
|
||||
one line changed is a better report than a 45% win that restructured a module.
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: plan
|
||||
description: Break a non-trivial task into an ordered, verifiable sequence before writing code. Use when a request is large, spans several files, or its steps depend on each other.
|
||||
---
|
||||
|
||||
# Planning
|
||||
|
||||
A plan that cannot be checked is a wish. Every step ends in something you can run.
|
||||
|
||||
## Understand before you sequence
|
||||
|
||||
Read enough to know the real shape of the work: the entry point, the data's path, the
|
||||
module that owns the behaviour. A plan made from filenames alone reorders itself the
|
||||
moment you open the first file. Grep the actual call sites; do not plan around a guess.
|
||||
|
||||
## Order by dependency, not by file
|
||||
|
||||
A step may depend on another's output: a type before its callers, a schema before its
|
||||
migration, a test helper before the tests that use it. Sequence so nothing references
|
||||
what does not exist yet. If two steps are independent, say so — the order between them
|
||||
is free and you may take the cheaper one first to derisk the rest.
|
||||
|
||||
## One step, one verifiable outcome
|
||||
|
||||
Each step names the command that proves it done: a test that passes, a build that
|
||||
compiles, a script that runs. "Wire it up" is not a step. Write the list with
|
||||
`todo_write`, then work it in order, marking done immediately — not in a batch at the end.
|
||||
|
||||
## Keep it small
|
||||
|
||||
The plan is a scaffold, not the building. If a step grows past "change these few files",
|
||||
split it. If the task turns out smaller than it looked, drop the remaining steps and say
|
||||
why rather than inventing work to fill them.
|
||||
|
||||
## Replan when the ground moves
|
||||
|
||||
New information that changes the order or the scope is a reason to rewrite the list, not
|
||||
to push through it. A stale plan followed faithfully is worse than no plan.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: readme
|
||||
description: Write or fix a project README. Use when creating a README, when a new user cannot get the project running from it, or when it has drifted from the code.
|
||||
---
|
||||
|
||||
# README
|
||||
|
||||
A README has sixty seconds to answer: what is this, do I want it, and how do I run it. Everything
|
||||
else is secondary to those three.
|
||||
|
||||
## The first screen answers three questions
|
||||
|
||||
1. **What it is** in one or two sentences, concrete about the problem it solves — not "a modern
|
||||
solution" but "a CLI that lints Terraform plans against your org's policies".
|
||||
2. **Install** — the one command that gets it.
|
||||
3. **The first thing that works** — the minimal command or snippet that produces visible output.
|
||||
If a new user cannot get a win in two minutes, most leave.
|
||||
|
||||
## Verify every command
|
||||
|
||||
Run each command in the README against a clean environment and paste its real output. The most
|
||||
common README defect is an install or quickstart that no longer works because the code moved and
|
||||
the doc did not. If you cannot run it, do not write it.
|
||||
|
||||
## Structure for scanning
|
||||
|
||||
After the quickstart, in the order a new user needs them: features as a short list of what it
|
||||
does (not how), the common tasks as copy-paste examples, configuration as a table of options with
|
||||
defaults, then links to deeper docs. Headings let a reader jump; a wall of prose gets skimmed
|
||||
past the thing they needed.
|
||||
|
||||
## Show, do not tell
|
||||
|
||||
A three-line example of real use beats a paragraph describing capability. Show the input and the
|
||||
output. A screenshot or asciinema of the actual tool running is worth a hundred adjectives —
|
||||
include one if the tool has any visual surface.
|
||||
|
||||
## Keep it true
|
||||
|
||||
Document the stable interface, not this week's implementation, or the README rots. Re-read it on
|
||||
every release: a README that contradicts the current version is worse than a short one, because
|
||||
it actively misleads.
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: refactor
|
||||
description: Restructure code without changing behaviour. Use when asked to refactor, clean up, extract, or reorganise.
|
||||
---
|
||||
|
||||
# Refactoring
|
||||
|
||||
Behaviour must not change. That is the whole constraint — every other goal (clarity,
|
||||
structure, naming) is subordinate to it. The moment behaviour changes, you are no longer
|
||||
refactoring, you are editing, and the safety argument below stops holding.
|
||||
|
||||
## Establish the safety net first
|
||||
|
||||
Run the existing tests and record that they pass — with `remember`, so the baseline
|
||||
survives compaction. If the code has no tests, write one that pins current behaviour,
|
||||
*including the ugly parts*: the odd return value, the quirk callers depend on. You are not
|
||||
judging the behaviour, you are freezing it. Refactoring untested code is not refactoring;
|
||||
it is rewriting, and it belongs under the edit workflow with its own verification.
|
||||
|
||||
## Then move in small steps
|
||||
|
||||
One transformation at a time, tests green between each. Rename, then extract, then move —
|
||||
not all three in one edit. The mechanical refactorings are the safe ones: rename, extract
|
||||
function, inline, move. Compose them. A large refactor that fails leaves you unable to
|
||||
tell which of five steps broke it; a small one that fails tells you exactly which.
|
||||
|
||||
After each step, run the tests, not just the typechecker. Types catch signature drift;
|
||||
they do not catch a reordered conditional or a dropped early return.
|
||||
|
||||
## What not to do
|
||||
|
||||
- Do not fix bugs while refactoring. Note them, finish the refactor green, then fix in a
|
||||
separate change — otherwise a regression could be either the refactor or the fix.
|
||||
- Do not add abstraction for a single caller. Duplication beats a premature interface;
|
||||
the third caller is when the abstraction earns its name.
|
||||
- Do not widen the scope. The request was this code, not its neighbours. A refactor that
|
||||
"while we're here" touches five more files is five more files of unreviewable risk.
|
||||
- Do not change public API unless asked; if it must change, say so first and update every
|
||||
caller in the same change.
|
||||
|
||||
## Done means
|
||||
|
||||
Tests pass, behaviour is identical, and the diff is smaller than the reader feared. If the
|
||||
diff is larger than the code it moved, you abstracted too early — put it back.
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: release
|
||||
description: Cut a release: versioning, changelogs, tagging, publishing. Use when asked to release, bump a version, write release notes, or fix a broken publish.
|
||||
---
|
||||
|
||||
# Release
|
||||
|
||||
A release is a promise that a specific, identified state of the code works. Make it
|
||||
reproducible or do not make it.
|
||||
|
||||
## Version says what changed
|
||||
|
||||
Semver: breaking is a major, a feature is a minor, a fix is a patch. The number is a message to
|
||||
whoever upgrades, not a marketing choice. Below 1.0, say so plainly — semver promises nothing
|
||||
and the version should not pretend otherwise.
|
||||
|
||||
## The changelog is for the upgrader
|
||||
|
||||
- Group by what the reader must do: breaking changes and required actions first, then features,
|
||||
then fixes.
|
||||
- Write it as "you can now X" or "Y no longer Z", from the user's side, not the commit's. A
|
||||
changelog that is a git log is a changelog nobody reads.
|
||||
- Every breaking change names the migration: what to change to keep working.
|
||||
|
||||
## Verify before you tag
|
||||
|
||||
The release candidate builds clean from a fresh checkout, the tests pass, and the version string
|
||||
in the source matches the tag you are about to push. A version/tag mismatch published is the
|
||||
kind of thing that ships "0.4" labelled as "0.3" forever.
|
||||
|
||||
## Tag the commit, publish the artifact
|
||||
|
||||
Tag the exact commit that was verified, and build the artifact from that tag — not from a
|
||||
working tree that has since moved. The tag is immutable; never move it to a different commit.
|
||||
If a release is wrong, cut a new one with a new number; do not quietly re-tag.
|
||||
|
||||
## If it goes wrong
|
||||
|
||||
Have the rollback ready before you need it: the previous artifact still available, the deploy
|
||||
reversible. A bad release is fixed forward with a patch release, not by deleting the evidence.
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: review
|
||||
description: Review a diff or a file for defects. Use when asked to review, critique, or check code before it ships.
|
||||
---
|
||||
|
||||
# Code review
|
||||
|
||||
Severity order. Do not lead with style — a review that opens on naming while a real bug
|
||||
sits three lines down has failed at its one job.
|
||||
|
||||
1. **Incorrect behaviour** — wrong result, wrong edge case, wrong state after failure.
|
||||
2. **Missing validation at trust boundaries** — user input, network responses, file contents,
|
||||
anything crossing a process line. Internal calls need no defensive checks.
|
||||
3. **Security** — injection, path traversal, secrets in logs or errors, missing authz.
|
||||
4. **Resource handling** — unclosed handles, unbounded growth, unawaited promises.
|
||||
5. **Clarity** — only when it will cause a future defect.
|
||||
|
||||
## How to read the change
|
||||
|
||||
- Read the diff against its intent. Does it actually do what the title/commit says? A
|
||||
correct-looking diff that solves the wrong problem is the most expensive approval.
|
||||
- Read the *deleted* lines as carefully as the added ones. Behaviour is often lost in a
|
||||
removal, and diffs render deletions quietly.
|
||||
- Follow each new call one level into the callee. The assumption that breaks it is usually
|
||||
one level down, invisible in the diff itself.
|
||||
|
||||
## For each finding
|
||||
|
||||
State file and line, the concrete failure (what input makes it break, or why it always
|
||||
breaks), and the change. Show the fix as code when it is short. "This could be a problem"
|
||||
without a path to a real input is noise; either trace it or drop it.
|
||||
|
||||
Order findings by severity and lead with the worst. Skip anything a formatter would fix.
|
||||
Skip preference. If a choice is defensible, leave it — a review is not a place to impose
|
||||
your style on code that works.
|
||||
|
||||
## Say when it is fine
|
||||
|
||||
A review that invents problems to look thorough is worse than a short one. If the change
|
||||
is correct, say so plainly and stop. "Looks correct, and here is what I checked" is a
|
||||
complete and useful review.
|
||||
|
||||
## Verify, do not assume
|
||||
|
||||
Read the surrounding code before calling something a bug. A "missing" null check often
|
||||
exists one level up; a "redundant" guard often covers a caller you have not seen. Run the
|
||||
tests or write the failing input if that is what settles it. A finding you verified is
|
||||
worth ten you suspected.
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
name: security
|
||||
description: Review code for security defects, or write code that handles untrusted input. Use when touching authentication, user input, file paths, shell commands, SQL, or anything reachable from the network.
|
||||
---
|
||||
|
||||
# Security
|
||||
|
||||
Find the trust boundary first. Everything crossing it is hostile until parsed.
|
||||
|
||||
## The boundaries in most codebases
|
||||
|
||||
- Request bodies, query strings, headers, cookies.
|
||||
- File contents and filenames, including paths a user supplied.
|
||||
- Environment variables in a multi-tenant deployment.
|
||||
- Anything a model or a third-party API returned.
|
||||
|
||||
Inside a boundary, values are already validated and re-checking them is noise. At the
|
||||
boundary, nothing is optional.
|
||||
|
||||
## What to look for, in order
|
||||
|
||||
1. **Injection.** String-built SQL, shell commands assembled from input, `eval`, template
|
||||
rendering with user data as the template rather than the data. The fix is parameters and
|
||||
argument arrays, never escaping.
|
||||
2. **Missing authorisation.** An endpoint that checks *who* you are but not *what* you may
|
||||
touch. Look for an id taken from the request and used without an ownership check.
|
||||
3. **Path traversal.** `../` in anything joined onto a filesystem root. Resolve, then verify
|
||||
the result is still inside the root — a prefix check on the raw input misses
|
||||
`a/../../secret`.
|
||||
4. **Secrets in the wrong place.** Keys in source, in logs, in error messages, in a commit.
|
||||
A secret that reached a log is a secret to rotate.
|
||||
5. **Server-side request forgery.** A URL from input, fetched. Block private and loopback
|
||||
addresses by *resolved* address, and re-check every redirect hop.
|
||||
6. **Weak crypto and hand-rolled auth.** Homemade token formats, `Math.random` for anything
|
||||
security-bearing, comparisons on secrets that are not constant time.
|
||||
|
||||
## Verify the path before reporting
|
||||
|
||||
Trace each candidate from an attacker-controlled value to the sink before you name it. A
|
||||
"this could be unsafe" without that path is noise that buries the real finding. If you
|
||||
cannot construct the malicious input that reaches the sink, either keep looking or say
|
||||
plainly that you could not confirm it.
|
||||
|
||||
Do not fix a symptom at one caller when the sink is shared. Grep every caller and fix the
|
||||
seam once — a sanitiser at one of five call sites is four open holes and one false sense
|
||||
of safety.
|
||||
|
||||
## Reporting
|
||||
|
||||
File, line, the path from input to sink, a concrete payload, and the fix. Rank by
|
||||
exploitability: a reachable injection beats a theoretical weakness in dead code. Say
|
||||
plainly when a thing that looks dangerous is actually fine, and why — a reviewer's
|
||||
confidence in the clean parts is worth as much as a finding.
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
name: test
|
||||
description: Write or repair tests. Use when adding coverage, fixing a flaky test, or asked how something should be tested.
|
||||
---
|
||||
|
||||
# Testing
|
||||
|
||||
A test earns its place by failing when the code is wrong. A test that cannot fail — or
|
||||
that passes regardless — is not a test, it is overhead with a green checkmark.
|
||||
|
||||
## Match the project
|
||||
|
||||
Read two existing test files first. Use their runner, their assertion style, their file
|
||||
layout, their naming, their way of building fixtures. A test that looks foreign is a test
|
||||
nobody maintains, and an unmaintained test is deleted the first time it goes red.
|
||||
|
||||
## Test behaviour, not implementation
|
||||
|
||||
Assert on what a caller observes: the return value, the written file, the emitted event,
|
||||
the status code. A test that reaches into private state or mocks a collaborator's
|
||||
internals breaks on every refactor while catching nothing real. If you cannot say what
|
||||
the caller sees, you are testing the how, and the how is allowed to change.
|
||||
|
||||
Cover, in this order of value:
|
||||
- **The failure** — the bad input, the missing file, the null. Failure cases catch more
|
||||
real defects than happy paths, because most code is written for the happy path first.
|
||||
- **The boundaries** — empty, one, the maximum, off-by-one at each edge.
|
||||
- **The normal case** — one, to prove the wiring works at all.
|
||||
|
||||
## Never do this
|
||||
|
||||
- Do not assert what the code currently returns without knowing it is correct — that pins
|
||||
the bug into the suite and calls it a specification.
|
||||
- Do not weaken an assertion to make a test pass. If it fails, either the code or the
|
||||
expectation is wrong; find out which before you touch either.
|
||||
- Do not delete a failing test to go green. It is telling you something; listen.
|
||||
- Do not test the framework or the library. Your code is the subject; their code has its
|
||||
own suite.
|
||||
|
||||
## Flaky tests
|
||||
|
||||
A test that passes alone and fails in a suite is a shared-state problem: a global, a temp
|
||||
directory, a port, an unawaited promise, leftover data, or ordering. Find which — run it
|
||||
repeatedly and in isolation to confirm, then remove the shared state. Do not add a retry:
|
||||
a retried flake is a real intermittent bug you have decided to stop hearing about.
|
||||
|
||||
## Verify
|
||||
|
||||
Run the test and watch it fail before the fix, pass after. A test you never saw fail is
|
||||
not known to test anything.
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: ux-copy
|
||||
description: Write user-interface text: labels, errors, empty states, onboarding. Use when wording a button, an error message, a confirmation, or any text the user reads in the product.
|
||||
---
|
||||
|
||||
# UX copy
|
||||
|
||||
Interface text is part of the interface. Clear, short, and honest beats clever.
|
||||
|
||||
## Lead with what the user cares about
|
||||
|
||||
- Buttons say the action and the object: "Save changes", not "OK". "Delete project", not "Yes".
|
||||
- Headings say the outcome or the thing, not the category: "Your invoices" over "Billing section".
|
||||
- Front-load the information word. Users scan the first two words; "3 errors found" scans, "Found
|
||||
3 errors" buries it.
|
||||
|
||||
## Error messages say what happened and what to do
|
||||
|
||||
Never blame, never jargon, never just a code. "Couldn't save because you're offline. Your changes
|
||||
are kept — try again when you're back." The pattern is: what went wrong, whether their work is
|
||||
safe, the one action to take. An error that only says "Something went wrong" makes the user do
|
||||
the debugging.
|
||||
|
||||
## Empty states teach, they do not apologise
|
||||
|
||||
An empty screen is a chance to say what goes here and how to start: "No projects yet. Create your
|
||||
first to see it here." plus the button. "Nothing to display" wastes the moment the user is most
|
||||
receptive to guidance.
|
||||
|
||||
## Be honest about destructive and irreversible actions
|
||||
|
||||
A confirmation names exactly what will happen and that it cannot be undone: "Delete 'Invoices
|
||||
2024'? This permanently removes 3,120 records and cannot be undone." The destructive button
|
||||
repeats the verb: "Delete", never a bare "Confirm" that could mean anything.
|
||||
|
||||
## Consistent words for consistent things
|
||||
|
||||
Pick one term per concept and use it everywhere — if it is a "project" in one place it is not a
|
||||
"workspace" in another. Sentence case for labels, no exclamation marks, no "please", no "simply".
|
||||
The tone is a competent colleague, not a marketing page.
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
name: verify
|
||||
description: Confirm a change actually works by using it, not by reading it. Use before reporting a task complete, or when asked whether something works.
|
||||
---
|
||||
|
||||
# Verification
|
||||
|
||||
A green test suite says the tests pass. It does not say the feature works. The two are
|
||||
different claims, and only one of them is what the user asked for.
|
||||
|
||||
## Run the artifact, not the source
|
||||
|
||||
Build it and use it the way a user would, end to end:
|
||||
|
||||
- **CLI** — build the binary and run it. Happy path, bad input, `--help`. Read the actual
|
||||
output, not the output you expected.
|
||||
- **HTTP service** — start it and `curl` the endpoint. Check the status line and the body,
|
||||
not just that it returned something.
|
||||
- **Library** — write a throwaway script that imports and calls the new code end to end,
|
||||
the way a consumer would.
|
||||
- **Script or job** — run it against real input and inspect what it produced.
|
||||
|
||||
Delete the throwaway afterwards. A verification script left behind becomes clutter the
|
||||
next person trips over.
|
||||
|
||||
## What counts as evidence
|
||||
|
||||
Command output you actually saw. Paste the relevant lines, not a summary of them — a
|
||||
summary hides the one line that mattered.
|
||||
|
||||
These are not evidence:
|
||||
|
||||
- "The tests pass" for a change the tests do not cover.
|
||||
- "The types check" for anything about runtime behaviour.
|
||||
- "It compiles" for anything about correctness.
|
||||
- "It should work now" for anything at all.
|
||||
|
||||
## Check the failure path too
|
||||
|
||||
Feed it the input you expect to be rejected and confirm it is rejected, with a message
|
||||
that says why. Then the edge case at the boundary. A feature that works only on correct
|
||||
input is half-built, and the half that is missing is the half users hit first.
|
||||
|
||||
## Report what you did not verify
|
||||
|
||||
Say plainly what you could not run and why: a missing credential, a service you cannot
|
||||
start, a platform you are not on. An honest gap is useful — the reader can fill it. A
|
||||
claim that hides one is a bug you just shipped in prose.
|
||||
|
||||
## When verification fails
|
||||
|
||||
The defect is yours to fix in this turn. Do not report the task complete with a note that
|
||||
it did not work — that is a failure report, not a completion.
|
||||
Reference in new issue
Block a user