agentscamp 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: "migration-writer"
3
+ description: "Write a safe, reversible, zero-downtime database migration using expand-contract — add the new shape, backfill in batches, switch reads/writes, then drop the old — so every deploy stays compatible with the running app version. Use when adding or changing schema on a live system, renaming/dropping a column, adding NOT NULL or a foreign key on a large table, or when a migration risks locks, table rewrites, or an unrevertable step."
4
+ allowed-tools: "Read, Grep, Glob, Edit"
5
+ version: 1.0.0
6
+ ---
7
+
8
+ Most schema migrations break production not because the SQL is wrong but because they assume the database and the app flip over in one atomic instant. During a rolling deploy, old and new code run **at the same time** against **one schema** — so a migration that the new code needs will crash the old code, and a rollback that the old code needs is gone the moment you `DROP`. This skill writes migrations the **expand-contract** way: each step is independently deployable against the version before and after it, every change has a real `down`, and no step takes a lock that blocks writes on a hot table.
9
+
10
+ ## When to use this skill
11
+
12
+ - Adding, renaming, dropping, or retyping a column on a table that a live app reads/writes.
13
+ - Adding `NOT NULL`, a `CHECK`, a foreign key, or a unique constraint to a table with existing rows.
14
+ - Creating an index on a large/busy table, or backfilling a new column across millions of rows.
15
+ - Splitting/merging tables, moving a column, or any change where old and new app code must coexist during the deploy.
16
+
17
+ ## Instructions
18
+
19
+ 1. **Decide the expand-contract phases first, before writing SQL.** A column rename `a → b` is not one migration; it is: (1) add `b` nullable, (2) dual-write `a` and `b` in app code, (3) backfill `b` from `a`, (4) switch reads to `b`, (5) stop writing `a`, (6) drop `a`. Each phase ships and is safe to roll back to the phase before it. Name the phases explicitly in the output, mapped to app deploys.
20
+ 2. **Make additive changes nullable / without a default rewrite.** `ADD COLUMN ... NULL` is instant. Adding a column with a non-constant default (or, on old engines, any default) rewrites the table under a lock — split it into add-nullable, then backfill, then set default for future rows.
21
+ 3. **Add `NOT NULL` and `CHECK` without a blocking scan.** On Postgres: `ADD CONSTRAINT ... CHECK (...) NOT VALID`, then `VALIDATE CONSTRAINT` (takes only a `SHARE UPDATE EXCLUSIVE` lock, doesn't block writes). For `NOT NULL`, add the validated `CHECK (col IS NOT NULL)` first, then promote — never `SET NOT NULL` cold on a big table, which full-scans under an `ACCESS EXCLUSIVE` lock.
22
+ 4. **Build indexes and FKs concurrently / unvalidated.** `CREATE INDEX CONCURRENTLY` (and `DROP INDEX CONCURRENTLY`) so writes keep flowing; add foreign keys as `NOT VALID` then `VALIDATE CONSTRAINT` in a second step. Concurrent index builds run outside a transaction — keep them in their own migration with no other statements.
23
+ 5. **Backfill in bounded batches, never one transaction.** Update in chunks (e.g. `WHERE id BETWEEN ...` or `LIMIT n` loops) committing each batch, with a short sleep between batches to spare replication and locks. Keep the backfill in a **separate migration/job** from the schema DDL so a slow backfill can't hold a DDL lock and a failed batch doesn't roll back the whole table.
24
+ 6. **Write a real `down` for every `up`.** The down must actually reverse the change (drop the added column/index/constraint), or, where reversal loses data (a dropped column, a narrowed type), say so loudly and add an export/backup step to the up rather than pretending it's reversible.
25
+ 7. **State the deploy ordering contract.** For each migration, note which app version it requires and which it must remain compatible with: backward-compatible (expand) migrations run **before** the code that needs them; destructive (contract) migrations run **after** all code that used the old shape is fully rolled out and confirmed.
26
+
27
+ > [!WARNING]
28
+ > A single-transaction backfill (`UPDATE big_table SET ...` with no batching) holds row locks on every touched row until commit, bloats WAL, can deadlock with live traffic, and on failure rolls back hours of work. Always batch and commit; treat any unbounded `UPDATE`/`DELETE` on a large table as a production incident waiting to happen.
29
+
30
+ > [!WARNING]
31
+ > Type changes that rewrite the table (`ALTER COLUMN ... TYPE` between incompatible types, e.g. `int → bigint` on older Postgres) take an `ACCESS EXCLUSIVE` lock and block all reads and writes for the duration. Prefer expand-contract: add a new column of the target type, backfill, switch over, drop the old — never an in-place rewrite on a hot table.
32
+
33
+ > [!NOTE]
34
+ > Don't take `ACCESS EXCLUSIVE` DDL with `lock_timeout = 0`. Set a short `lock_timeout` (e.g. `5s`) so a migration that can't grab its lock fails fast and retries, instead of queueing behind a long query and stalling every write that piles up behind it.
35
+
36
+ ## Output
37
+
38
+ For the requested change, produce:
39
+ - **The `up` and `down` migration** — split into separate files per expand-contract phase, with `CONCURRENTLY` / `NOT VALID` / `VALIDATE` used where they avoid blocking locks.
40
+ - **The backfill + rollout sequence** — the ordered phases (add → dual-write → backfill → switch reads → stop old writes → drop), each tagged with the app deploy it pairs with, and the batched backfill loop as a separate step.
41
+ - **Locking & risk notes** — for each statement: the lock it takes, whether it blocks reads/writes, whether it rewrites the table, and whether the `down` is lossless — with destructive/irreversible steps called out explicitly.
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: "prompt-regression-tester"
3
+ description: "Build a regression test harness for an LLM prompt so a prompt edit or model upgrade can't silently degrade quality — a fixed eval set, checkable assertions, and a diff against a committed baseline. Use when changing a production prompt, migrating model versions, or any time 'I tweaked the prompt' needs to be backed by evidence instead of eyeballing two outputs."
4
+ allowed-tools: "Read, Grep, Glob, Bash, Write"
5
+ version: 1.0.0
6
+ ---
7
+
8
+ A prompt edit that "looks fine on a couple of examples" is the single most common way teams ship a quality regression. The fix is not heroic — it's a fixed eval set, assertions a machine can check, and a baseline you diff against. This skill builds that harness so any prompt change or model migration produces a pass/fail + regression report instead of a vibe.
9
+
10
+ ## When to use this skill
11
+ - You're about to edit a production prompt and want proof the edit doesn't regress existing behavior.
12
+ - You're migrating models (e.g. `claude-opus-4-7` → `claude-opus-4-8`, or onto a new provider) and need to confirm output quality holds.
13
+ - A prompt regressed in the past and you want a committed test that would have caught it.
14
+ - You're standing up a new prompt and want defensible coverage before it goes live.
15
+ - Someone "improved" a prompt and you need to confirm it actually improved rather than traded one failure for another.
16
+
17
+ ## Instructions
18
+
19
+ 1. **Find the prompt and its call site first.** `Grep` for the system/user prompt text, the model ID (`claude-`, `gpt-`, `model=`, `model:`), and the SDK call (`messages.create`, `chat.completions`, etc.). Pin down exactly what's under test: the prompt string, the model ID, and every generation parameter (`effort`/`temperature`/`max_tokens`/`tools`). The harness must reproduce that call exactly — a regression test that uses different params than production tests the wrong thing.
20
+
21
+ 2. **Assemble a FIXED eval set — and freeze it.** Collect 15–40 representative inputs into a version-controlled file (`evals/cases.jsonl`, one JSON object per line: `{id, input, ...}`). Include, deliberately: the happy-path cases, the boundary cases, and — most importantly — every input that has *previously failed* (past bug reports, support tickets, the example that motivated the last prompt edit). The set is an asset; it only grows by deliberate commit, never shrinks or mutates per run.
22
+
23
+ > [!WARNING]
24
+ > An eval set that changes between runs is not a regression test — it's a fresh experiment each time, and the diff against baseline is meaningless. Do not generate inputs on the fly, sample randomly, or pull "the latest N production logs" at runtime. Commit the inputs.
25
+
26
+ 3. **Write checkable assertions per case — prefer deterministic over judged.** For each input, specify what "correct" means as machine-checkable expectations, picking the *tightest* check that fits:
27
+ - `exact` — output equals a string (classification labels, enum values).
28
+ - `contains` / `not_contains` — a required substring is present / a forbidden phrase (a leaked secret, a banned disclaimer, "As an AI") is absent.
29
+ - `regex` — a format pattern (an order ID shape, a date, a currency string).
30
+ - `json_schema` — output parses as JSON and validates against a schema (the highest-value check for structured-output prompts; catches missing fields, wrong types, extra keys).
31
+ - `structural` — list length, required keys present, sorted order, no duplicates.
32
+
33
+ Store assertions alongside each case. A single case may carry several. **Reserve an LLM-as-judge only for qualities no code can decide** — tone, helpfulness, faithfulness to a source, "did it refuse appropriately" — and even then express the judge as a rubric with a discrete verdict (`pass`/`fail` or a 1–5 score with a threshold), not a freeform "rate this."
34
+
35
+ 4. **Default to the latest, most capable model for both the system-under-test and the judge.** Use `claude-opus-4-8` (current most-capable Opus; 1M context, adaptive thinking) unless the prompt's production config pins a different model — in which case test *that* model and add `claude-opus-4-8` as a comparison column. For the judge, also default to `claude-opus-4-8`: a weak judge is a noisy judge. Use adaptive thinking (`thinking: {type: "adaptive"}`) for the judge; do not send `temperature`/`top_p`/`budget_tokens` (removed on Opus 4.7/4.8 — they 400). Keep the judge model **separate and named** in the report so a judge swap is never confused with a quality change.
36
+
37
+ 5. **Run the set across each variant under test and score every case.** A "variant" is a (prompt, model, params) tuple — typically `baseline` (current production) vs `candidate` (your edit). Iterate the frozen cases, call the real SDK with the exact production config, run each assertion, and record a per-case result: `pass`/`fail`, which assertion failed, and the raw output. Run candidate and baseline in the same invocation so they see identical inputs. Cache or store raw outputs so a re-score (e.g. after fixing an assertion) doesn't require re-generating.
38
+
39
+ 6. **Diff against the committed baseline and flag regressions.** Snapshot the baseline results to `evals/baseline.json` (committed). On each run, compare candidate-vs-baseline per case and classify:
40
+ - **Regression** — passed in baseline, fails now. (The line that blocks the PR.)
41
+ - **Fix** — failed in baseline, passes now. (The intended win — confirm it's real, not luck.)
42
+ - **Unchanged pass / unchanged fail** — note but don't alarm.
43
+
44
+ > [!WARNING]
45
+ > Don't treat "candidate pass-rate ≥ baseline pass-rate" as green. Aggregate rate hides swaps — a candidate can fix two cases and break two others for the same headline number. The per-case regression list is the signal; the aggregate is a summary, not the gate.
46
+
47
+ 7. **Make the baseline a deliberate, reviewed artifact.** Update `evals/baseline.json` only via an explicit "accept" step (a `--update-baseline` flag or a separate command), and commit it in the same PR as the prompt change so a reviewer sees both the new prompt and exactly which case results moved. Never let the harness silently rewrite the baseline on every run — that's how a regression gets absorbed into "the new normal" unnoticed.
48
+
49
+ > [!NOTE]
50
+ > LLM outputs vary run-to-run even at fixed settings, so a single judged case flipping isn't necessarily a real regression. For judge-scored cases, run N=3 and require a majority verdict before flagging; for deterministic assertions, one failure is one failure — no repetition needed.
51
+
52
+ ## Output
53
+
54
+ The skill produces a committed, runnable harness:
55
+
56
+ - **Layout** —
57
+ ```
58
+ evals/
59
+ cases.jsonl # frozen inputs + per-case assertions (version-controlled)
60
+ baseline.json # accepted baseline results (version-controlled)
61
+ run.(py|ts) # loads cases, runs each variant, scores, diffs
62
+ schema/*.json # json_schema assertions, if any
63
+ ```
64
+ - **The assertion set** — each case lists its checks, e.g.
65
+ ```json
66
+ {"id": "refund-001", "input": "...", "assert": [
67
+ {"type": "json_schema", "schema": "schema/refund.json"},
68
+ {"type": "not_contains", "value": "As an AI"},
69
+ {"type": "judge", "rubric": "Output politely declines without admitting fault.", "threshold": "pass"}
70
+ ]}
71
+ ```
72
+ - **A pass/fail + regression report** — printed and written to `evals/report.md`:
73
+ ```
74
+ Variant: candidate (prompt@HEAD, claude-opus-4-8) vs baseline (prompt@main, claude-opus-4-8)
75
+ Judge: claude-opus-4-8
76
+
77
+ 42 cases | 39 pass / 3 fail (baseline: 40 pass / 2 fail)
78
+
79
+ REGRESSIONS (2) ← blocks merge
80
+ refund-001 json_schema: missing field "reason"
81
+ tone-014 judge(2/3): newly apologetic where baseline was neutral
82
+
83
+ FIXES (1)
84
+ parse-007 regex: now matches order-id format
85
+
86
+ Exit code: 1 (regressions present)
87
+ ```
88
+
89
+ A non-zero exit on any regression makes the harness drop straight into CI. Report file paths back as absolute paths so the user can wire it up.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: "rate-limiter-designer"
3
+ description: "Design and implement API rate limiting that actually holds under load — pick the algorithm (token bucket vs sliding-window-counter vs fixed window) and justify it, choose the limiting key and per-tier limits, use cross-instance atomic storage, and return standard 429 signals. Use when protecting an API from abuse or scrapers, enforcing per-tier quotas, or replacing an in-memory limiter that breaks behind multiple replicas."
4
+ allowed-tools: "Read, Grep, Glob, Edit"
5
+ version: 1.0.0
6
+ ---
7
+
8
+ A rate limiter is only as correct as its storage and its atomicity. An in-memory counter behind three replicas enforces 3x the limit; a `GET` then `SET` without an atomic increment lets a burst of concurrent requests all read the same pre-increment value and pass. This skill makes the decisions explicit — algorithm, key, limits, storage, failure mode — and produces an implementation sketch that survives horizontal scaling and concurrency.
9
+
10
+ ## When to use this skill
11
+
12
+ - You're protecting an API (public, partner, or internal) from abuse, scrapers, credential stuffing, or runaway clients.
13
+ - You need per-tier quotas (free vs pro vs enterprise) or per-endpoint limits (cheap reads vs expensive writes/exports).
14
+ - You have an existing in-memory limiter and the service now runs more than one instance, so the effective limit drifts with replica count.
15
+ - A downstream dependency (a paid API, a database, an LLM provider) needs protecting from your own traffic spikes.
16
+
17
+ ## Instructions
18
+
19
+ 1. **Pick the algorithm from the traffic shape, and justify it.** Three viable choices:
20
+ - **Token bucket** — refills at a steady rate, allows configurable bursts up to bucket capacity. Use for interactive/bursty clients (a user clicking fast, batch jobs) where occasional bursts are legitimate. Default choice for most APIs.
21
+ - **Sliding-window counter** — approximates a true sliding window by weighting the previous and current fixed windows. Use when you need *smooth* enforcement without burst spikes (protecting a fragile downstream). Cheap: two counters per key.
22
+ - **Fixed window** — one counter per key per interval. Use *only* when simplicity outweighs correctness; it permits up to **2x the limit** across a window boundary (full quota at the end of window N plus full quota at the start of N+1). Never use it to protect something that genuinely caps at N.
23
+ State which you chose and the burst/smoothness tradeoff that drove it.
24
+
25
+ 2. **Choose the limiting key — and prefer a composite.** Options and their failure modes:
26
+ - **IP** — defeated by NAT (one office shares an IP → collateral throttling) and by rotating proxies. Use only for unauthenticated traffic.
27
+ - **API key / authenticated user** — the right granularity for quotas; ties the limit to identity, not network. Requires the limiter to run *after* auth.
28
+ - **Composite** (e.g. `user + endpoint`, or `apiKey + route-class`) — lets expensive endpoints have tighter limits than cheap ones under the same identity.
29
+ Pick the key per route class. Unauthenticated routes fall back to IP; authenticated routes key on identity.
30
+
31
+ 3. **Set limits per tier, written down as a table.** Define explicit numbers: e.g. free = 60 req/min, pro = 600 req/min, enterprise = custom; expensive endpoints (export, search, LLM-backed) get their own lower limit. Don't invent one global number — the whole point is differentiation.
32
+
33
+ 4. **Use storage that is shared and atomic.** The counter must live in a store all instances reach — **Redis** (or equivalent) — and the increment-and-check must be **atomic**. With Redis, use `INCR` + `EXPIRE` on the same key (or a single Lua script for token bucket, so read-refill-decrement is one atomic operation). A `GET` then `SET` from application code is a race: concurrent requests read the same value and all pass. In-memory (`Map`, an LRU) is correct only for a single-process service and is otherwise a silent bug — each replica keeps its own private quota.
34
+
35
+ 5. **Return standard signals.** On limit exceeded, respond **`429 Too Many Requests`** with:
36
+ - `Retry-After: <seconds>` — when the client may retry.
37
+ - `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` — emit these on *every* response (not just 429s) so well-behaved clients self-throttle before hitting the wall. Reset is seconds-until-reset (or a Unix timestamp — be consistent and document which).
38
+
39
+ 6. **Decide fail-open vs fail-closed when the store is down.** This is a deliberate choice, not a default:
40
+ - **Fail-open** (allow when Redis is unreachable) — preserves availability; correct for limiters that protect against *abuse* where a brief gap is acceptable.
41
+ - **Fail-closed** (reject) — correct when the limit guards a hard resource cap (a paid downstream, a quota you're contractually bound to). Wrap the store call in a short timeout so a slow store doesn't hang every request; on timeout, apply the chosen policy.
42
+
43
+ 7. **Handle clock skew and bursts.** Compute windows from the **store's clock** (e.g. Redis `TIME`) or a single source, not each instance's wall clock — skewed instances otherwise disagree on window boundaries. For token bucket, set capacity = the largest legitimate burst and refill rate = the sustained limit; document both.
44
+
45
+ > [!WARNING]
46
+ > Per-instance in-memory limiting in a horizontally-scaled deploy is the most common rate-limiter bug: with N replicas and a round-robin load balancer, the effective limit is roughly N x the configured value, and it changes silently when you autoscale. If the service has more than one replica, the limiter state MUST be in shared storage.
47
+
48
+ > [!WARNING]
49
+ > Read-then-write without atomicity defeats the limiter under exactly the load it exists to stop. Concurrent requests all read the pre-increment count and all pass. Use an atomic `INCR` (fixed/sliding window) or a single Lua script (token bucket) — never `GET` then conditional `SET` from app code.
50
+
51
+ > [!NOTE]
52
+ > Don't rate-limit at the app when an upstream layer does it better. A CDN/WAF or API gateway (Vercel Firewall, Cloudflare, Kong) can enforce coarse IP limits at the edge before traffic reaches your origin; reserve app-level limiting for identity- and tier-aware quotas that need request context.
53
+
54
+ ## Output
55
+
56
+ A short design block stating: the chosen **algorithm** + rationale, the **key** per route class, a **per-tier limits table**, the **storage** mechanism (and why it's atomic + cross-instance), and the **fail-open/closed** policy with timeout. Followed by a concrete middleware/handler sketch that performs the atomic increment-and-check against the store, sets `RateLimit-*` headers on every response, returns `429` + `Retry-After` on breach, and applies the chosen failure policy when the store is unreachable.
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: "react-render-profiler"
3
+ description: "Find and fix wasteful React re-renders by classifying the cause — unstable prop/callback/object identities, context value churn, state lifted too high, expensive work in render, or unvirtualized lists — confirming it with a measurement, then applying the one targeted fix and re-measuring. Use when a React UI is janky, slow to type in, or re-renders far more than the data actually changed."
4
+ allowed-tools: "Read, Grep, Glob, Edit, Bash"
5
+ version: 1.0.0
6
+ ---
7
+
8
+ A janky React UI is almost always re-rendering more than the data changed — and the reflex fix, wrapping everything in `useMemo`/`memo`, usually adds cost and complexity without helping, because it doesn't address *why* the component re-rendered. This skill makes the work diagnostic: name the cause class, prove it with a measurement, apply exactly one matching fix, and re-measure. No blind memoization.
9
+
10
+ ## When to use this skill
11
+
12
+ - Typing in an input is laggy, or interacting with one widget visibly re-renders unrelated parts of the page.
13
+ - The React DevTools Profiler shows a component (or a whole subtree) committing on interactions that shouldn't touch it.
14
+ - A list or table with hundreds of rows stutters on scroll, filter, or keystroke.
15
+ - A `useEffect`/`useMemo` runs every render even though its inputs "look" the same.
16
+ - You're tempted to sprinkle `memo`/`useCallback` and want to confirm where they actually pay off first.
17
+
18
+ ## Instructions
19
+
20
+ 1. **Measure before you touch code.** Open React DevTools → Profiler, record the slow interaction, and read the flamegraph: which components committed, how many times, and why (enable "Record why each component rendered"). For a sharper signal on a specific component, wire up `@welldone-software/why-did-you-render` in dev and check the console for which prop/state changed identity. Do not edit anything until you have a named culprit and a render count.
21
+ 2. **Classify the cause — pick exactly one per culprit.** (a) *Unstable identity*: an object/array/function literal created in the parent's render and passed as a prop, so a `memo`'d child or an effect dep changes every render. (b) *Context churn*: a context Provider whose `value={{...}}` is a fresh object each render, re-rendering every consumer. (c) *State too high*: state lives in an ancestor, so a localized change re-renders a large subtree. (d) *Expensive render work*: heavy compute (sorting/formatting/parsing) runs inline in render. (e) *Unvirtualized long list*: hundreds/thousands of DOM rows all committing.
22
+ 3. **Fix (c) by moving state, not memoizing.** If a keystroke or toggle re-renders a big subtree, *colocate* the state into the smallest component that uses it, or *lift it down* into a child. Moving state is the cheapest, most durable fix and often deletes the need for any `memo` at all — try this before reaching for memoization.
23
+ 4. **Fix (a) by stabilizing identity at the source.** Wrap callbacks passed to memoized children in `useCallback`, and derived objects/arrays in `useMemo`, with honest dependency arrays. This only helps if the *child is memoized* (`React.memo`) or the value is an *effect/memo dependency* — stabilizing a prop to an unmemoized child does nothing.
24
+ 5. **Fix (b) by splitting or memoizing context.** Memoize the Provider `value` with `useMemo`, and split a single fat context into separate contexts (e.g. state vs. dispatch, or per-concern) so a consumer only re-renders when the slice it reads changes.
25
+ 6. **Fix (d) by memoizing the computation or moving it out.** Wrap the expensive calculation in `useMemo` keyed on its real inputs, or hoist it out of render (precompute, server-side, or `useDeferredValue` for low-priority work). Memoize the *work*, not the component.
26
+ 7. **Fix (e) by virtualizing.** Render only visible rows with `@tanstack/react-virtual` (or `react-window`); `memo` on the row component matters here because virtualization recycles rows.
27
+ 8. **Re-measure and report the delta.** Re-record the same interaction in the Profiler and capture the new render count per culprit. If the count didn't drop, you classified the cause wrong — revert the change (don't leave a `memo` that bought nothing) and go back to step 2.
28
+
29
+ > [!WARNING]
30
+ > Blanket memoization is a regression, not a fix. `memo`/`useMemo`/`useCallback` each cost a comparison and retained memory every render, add dependency-array bugs, and break the moment one prop's identity still churns. Never add them without a Profiler reading showing they remove a real render — and when the true cause is class (c), *moving state deletes the problem* while memoization only masks it.
31
+
32
+ > [!NOTE]
33
+ > `React.memo` compares props shallowly, so it is *defeated* by a single unstable prop (an inline `style={{...}}`, `onClick={() => ...}`, or `data={[...]}`). A `memo`'d child that still re-renders on every parent commit is the signature of an unstable-identity prop (cause a) — not a reason to remove the `memo`.
34
+
35
+ ## Output
36
+
37
+ Per culprit: the component name, the **measured** cause class with the evidence (Profiler "why it rendered" reason or why-did-you-render line), the single targeted fix as an `Edit` diff, and **before/after render counts** for the same recorded interaction. End with a one-line verdict per fix (kept / reverted-no-effect) so no no-op memoization is left behind.
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: "semver-advisor"
3
+ description: "Decide the correct semantic-version bump — major, minor, or patch — by diffing a release range, mapping the changes onto the public API surface, and classifying each as breaking, additive, or a fix. Use before cutting a release when you are unsure whether changes are breaking, when a teammate proposes a bump you want to sanity-check, or when a behavior change has no signature change and you need to know if it is still breaking."
4
+ allowed-tools: "Read, Grep, Glob, Bash"
5
+ version: 1.0.0
6
+ ---
7
+
8
+ The wrong call here is silent until a consumer's build breaks. "It compiled, ship a minor" is how breaking changes escape — a tightened validation rule, a changed default, or a removed export looks small in the diff but breaks every downstream caller. This skill makes the bump a defensible decision: it pins down what your *public API surface* actually is, diffs the release range against it, classifies each change, and applies the SemVer rules — including the pre-1.0 exception people forget.
9
+
10
+ ## When to use this skill
11
+
12
+ - You are about to tag a release and are unsure whether the changes are breaking.
13
+ - Someone proposed `minor` or `patch` and you want to verify it against the real diff.
14
+ - You changed behavior without changing a signature and need to know if that is still breaking (it often is).
15
+ - You maintain a `0.x` library and keep forgetting that SemVer treats pre-1.0 differently.
16
+ - A CI/release gate failed on version mismatch and you need the correct bump with a rationale.
17
+
18
+ ## Instructions
19
+
20
+ 1. **Define the public API surface first — it is narrower or wider than you think.** Enumerate every contract a consumer can depend on, not just the language exports:
21
+ - **Code exports**: the package entry points (`exports`/`main` in `package.json`, `__all__`, `pub`/`public` symbols). Anything reachable from the documented entry point is public; deep imports into internal paths usually are not (unless your docs/`exports` map expose them).
22
+ - **CLI**: command names, flags, positional args, env vars they read, and exit codes.
23
+ - **Config**: accepted keys, their types, defaults, and required-ness in config files / schema.
24
+ - **Wire contracts**: HTTP routes, request/response shapes, status codes, GraphQL schema, event/message payloads.
25
+ - **File formats**: on-disk formats you read or write, serialization versions, migration outputs.
26
+
27
+ ```bash
28
+ # entry points and public surface clues
29
+ git show HEAD:package.json | grep -E '"(main|module|types|exports|bin)"' -A3
30
+ grep -rEn '__all__|^export (default |const |function |class |\{)' src | head -50
31
+ ```
32
+
33
+ 2. **Diff the release range, scoped to the surface.** Use the last released tag as the lower bound; review only files that touch the surface from step 1.
34
+
35
+ ```bash
36
+ LAST_TAG=$(git describe --tags --abbrev=0)
37
+ git diff "$LAST_TAG"..HEAD --stat
38
+ git diff "$LAST_TAG"..HEAD -- <surface paths: src/index.*, cli/, openapi.*, *.schema.json>
39
+ ```
40
+
41
+ 3. **Classify each surface change into exactly one bucket.**
42
+ - **Breaking** (forces major): removed or renamed export/flag/route/config key; changed function signature, required arg added, or narrowed/changed return type; changed *default behavior* a consumer relied on; stricter validation that rejects previously-valid input; changed error type/exit code/status code; removed config default; changed file-format output that old readers can't parse.
43
+ - **Additive** (minor): new export, flag, optional config key with a safe default, new route, new optional response field — all 100% backward compatible.
44
+ - **Fix** (patch): bug fix that restores documented behavior with no API change, internal refactor, perf, docs, deps that don't change the public contract.
45
+
46
+ 4. **Apply the rule, then handle the pre-1.0 caveat.** Take the highest-severity bucket present: any breaking → **major**; else any additive → **minor**; else **patch**. Then check the current version:
47
+ - **`>= 1.0.0`**: apply the rule directly.
48
+ - **`0.y.z` (pre-1.0)**: SemVer special-cases this. A breaking change bumps the **minor** (`0.y` → `0.(y+1)`), and additive/fix changes bump the **patch**. State explicitly that you are using pre-1.0 semantics.
49
+
50
+ 5. **Re-check every "no signature change" item before finalizing.** A change with an identical signature can still be breaking — search the diff for default-value changes, validation tightening, altered side effects, and changed return *values* (not just types). These are the ones that get mislabeled as patches.
51
+
52
+ 6. **Output the recommendation with receipts.** Give the bump, the resulting version number, the one-line rule that decided it, and the itemized changes per bucket — with each breaking change named explicitly so a reviewer can challenge it.
53
+
54
+ > [!WARNING]
55
+ > A behavior change with an unchanged signature is still breaking. Tightening input validation, flipping a default (e.g. `cache: false` → `true`), changing rounding/sort order, or returning a different value for the same input all break consumers even though the API "didn't change." Grep the diff for changed literals and default arguments, not just modified declarations.
56
+
57
+ > [!CAUTION]
58
+ > Pre-1.0 SemVer is not "anything goes" but it is not the 1.0 rule either: breaking changes go in the **minor** slot (`0.4.x` → `0.5.0`), not the major. If you mechanically bump major for a `0.x` package you will jump to `1.0.0` and signal stability you didn't intend. Confirm the current version before recommending.
59
+
60
+ ## Output
61
+
62
+ A bump recommendation, reproducible from the diff:
63
+
64
+ ```markdown
65
+ ## SemVer recommendation: MAJOR (1.4.2 → 2.0.0)
66
+
67
+ Rule applied: contains ≥1 breaking change → major (current version ≥ 1.0.0).
68
+
69
+ ### Breaking (forces major)
70
+ - Removed export `parseLegacy()` from package entry — consumers importing it will fail to resolve.
71
+ - `loadConfig()` now throws on unknown keys (was: ignored) — stricter validation rejects previously-valid config.
72
+ - Default of `--timeout` changed 0 (infinite) → 30000ms — changes runtime behavior for callers relying on the old default.
73
+
74
+ ### Additive (would be minor on its own)
75
+ - New optional flag `--format json`.
76
+
77
+ ### Fix (would be patch on its own)
78
+ - Fixed off-by-one in `splitRange()` matching documented behavior.
79
+
80
+ Note: if this were a 0.x package, the same set would be a MINOR bump (0.y → 0.(y+1)).
81
+ ```
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: "type-coverage-improver"
3
+ description: "Raise TypeScript type strictness incrementally — measure the any/implicit-any baseline, enable one strict sub-flag at a time, and fix the fallout per flag instead of all at once, keeping the typecheck green at every step. Use when a codebase is loosely typed, when you want strict mode on without a big-bang break, or when `any` keeps hiding bugs that surface in production."
4
+ allowed-tools: "Read, Grep, Glob, Edit, Bash"
5
+ version: 1.0.0
6
+ ---
7
+
8
+ Turn on TypeScript strictness without a big-bang break. The skill measures where you stand (explicit `any`, implicit `any`, which strict flags are already on), then enables the strict family **one sub-flag at a time** — `noImplicitAny`, `strictNullChecks`, and the rest — fixing the fallout from each flag before touching the next. Every step ends with `tsc --noEmit` passing, so you ratchet coverage up monotonically instead of staring at 600 errors and rage-casting them away.
9
+
10
+ ## When to use this skill
11
+
12
+ - The codebase runs with `strict: false` (or a partial strict config) and is littered with `any`, implicit `any` parameters, and unchecked nullables.
13
+ - You want to reach `strict: true` but a single flip produces an unfixable wall of errors and a stalled PR.
14
+ - `any` is masking real defects — `undefined is not a function`, missing-property crashes — that strict typing would have caught at compile time.
15
+
16
+ > [!WARNING]
17
+ > Do not "fix" strict errors with `any`-casts, `as` assertions, `@ts-ignore`, or `@ts-expect-error`. Those silence the exact diagnostic strict mode exists to surface — you ship the bug *and* the suppression. The only acceptable fixes are: a precise type, a real null/undefined narrow, or (rarely) a documented `// @ts-expect-error` with a linked issue when the fix is genuinely a separate change. A PR whose net effect is "more suppressions" is negative progress.
18
+
19
+ ## Instructions
20
+
21
+ 1. **Measure the baseline before changing anything.** Read `tsconfig.json` and record which strict sub-flags are already set (`strict` implies `noImplicitAny`, `strictNullChecks`, `strictFunctionTypes`, `strictBindCallApply`, `strictPropertyInitialization`, `noImplicitThis`, `useUnknownInCatchVariables`, `alwaysStrict`). Then quantify the `any` surface:
22
+
23
+ ```bash
24
+ # explicit `any` annotations
25
+ grep -rIn -E ':\s*any\b|\bas any\b|<any>|Array<any>|any\[\]' --include='*.ts' --include='*.tsx' src | wc -l
26
+ # existing suppressions (these are debt you must not add to)
27
+ grep -rIn -E '@ts-ignore|@ts-expect-error' --include='*.ts' --include='*.tsx' src | wc -l
28
+ # implicit any + the full error count under the strictest config (dry run, no edits)
29
+ npx tsc --noEmit --strict --noErrorTruncation 2>&1 | grep -c 'error TS'
30
+ ```
31
+
32
+ If `type-coverage` is available, `npx type-coverage --detail` gives a single percentage and a per-identifier list — capture the starting number; it is your headline metric.
33
+
34
+ 2. **Order the work by risk and traffic, not alphabetically.** Use `git log --format= --name-only --since='6 months ago' | sort | uniq -c | sort -rn` to find churned files, and grep for the modules with the most `any` and the most importers (entry points, shared utils, API/DB boundaries). Fix these first: a precise type on a widely-imported util propagates correctness everywhere; an `any` at a data boundary (HTTP response, DB row, JSON parse) is where wrong-shape bugs originate.
35
+
36
+ 3. **Enable exactly one sub-flag at a time.** Add a single flag to `tsconfig.json` (`"noImplicitAny": true`), run `npx tsc --noEmit`, and fix only the errors that flag produces. Recommended order, easiest-to-hardest:
37
+ - `noImplicitAny` — annotate parameters/returns the compiler couldn't infer.
38
+ - `strictNullChecks` — the big one; surfaces every place `null`/`undefined` was silently allowed.
39
+ - `strictFunctionTypes`, `strictBindCallApply`, `noImplicitThis` — usually small fallout.
40
+ - `strictPropertyInitialization` — class fields; often the last and noisiest.
41
+ Once each flag is green individually, the final flip to `"strict": true` is a no-op verification.
42
+
43
+ 4. **Replace `any` with the real type, narrow at the boundary.** For explicit `any`: infer the actual shape from how the value is used and from the producer, and write the `interface`/`type`. For external/untyped data (`JSON.parse`, `fetch().json()`, env vars, dynamic imports), type the boundary as `unknown` and narrow with a type guard or a schema parse (e.g. `zod`'s `.parse()`) — `unknown` forces a check; `any` skips it. Add explicit return types to exported functions so inference errors surface at the definition, not three call sites away.
44
+
45
+ 5. **Keep the typecheck green at every commit.** After each flag's fallout is fixed, run the project's real check (`npm run typecheck` / `tsc --noEmit`) and the test suite, then commit that flag as its own commit. Never enable the next flag on a red tree — you lose the ability to attribute a new error to a specific flag, and the diff becomes unreviewable.
46
+
47
+ 6. **Re-measure and report the delta.** Re-run the baseline commands from step 1. Report the before/after `any` count, the `type-coverage` percentage delta, which flags are now on, and any honest residue: spots that genuinely need `unknown` + a follow-up, third-party `@types` gaps, or generated code excluded via `tsconfig` `exclude` rather than suppressed inline.
48
+
49
+ > [!NOTE]
50
+ > Don't refactor logic while fixing types. A type-only PR should change annotations, guards, and config — not behavior. If a strict error reveals a real bug (a nullable that was actually being dereferenced), fix it in a **separate** commit with a test, so reviewers can tell "added a type" apart from "changed runtime behavior."
51
+
52
+ ## Output
53
+
54
+ 1. **Baseline metrics** — current `tsconfig` strict flags, explicit-`any` count, suppression count, total error count under `--strict`, and `type-coverage` percentage if available.
55
+ 2. **An ordered flag-by-flag plan** — the sub-flags to enable in sequence, each with its estimated fallout count and the highest-priority files to fix first, e.g.:
56
+
57
+ | Step | Flag | Errors introduced | Fix-first files |
58
+ |------|------|-------------------|-----------------|
59
+ | 1 | `noImplicitAny` | 38 | `src/lib/api/client.ts`, `src/utils/parse.ts` |
60
+ | 2 | `strictNullChecks` | 142 | `src/db/repository.ts`, `src/lib/session.ts` |
61
+ | 3 | `strictPropertyInitialization` | 21 | `src/services/*.ts` |
62
+
63
+ 3. **Concrete type changes for the first file** — the actual diff: `any` → named types, added return annotations, and `unknown`-at-the-boundary guards, with `tsc --noEmit` shown passing afterward. For example:
64
+
65
+ ```diff
66
+ - export function parseUser(raw: any) {
67
+ - return { id: raw.id, name: raw.name };
68
+ - }
69
+ + interface User { id: string; name: string }
70
+ + export function parseUser(raw: unknown): User {
71
+ + if (typeof raw !== "object" || raw === null) throw new Error("invalid user");
72
+ + const r = raw as Record<string, unknown>;
73
+ + if (typeof r.id !== "string" || typeof r.name !== "string") throw new Error("invalid user");
74
+ + return { id: r.id, name: r.name };
75
+ + }
76
+ ```
77
+
78
+ ```bash
79
+ $ npx tsc --noEmit
80
+ $ # exit 0 — clean
81
+ ```
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: "version-bumper"
3
+ description: "Bump the project version everywhere it lives in one consistent pass — package.json, lockfile, nested/CLI package manifests, version constants, README badges, docs — then roll the changelog's Unreleased section under the new version and stage an annotated git tag. Use when you've already decided the new version (X.Y.Z or a pre-release like -rc.1) and need every artifact updated to the same value without drift, or before cutting a release."
4
+ allowed-tools: "Read, Edit, Bash"
5
+ version: 1.0.0
6
+ ---
7
+
8
+ Bumping a version is rarely one line. The number hides in `package.json`, a lockfile, a nested CLI or submodule manifest, a `__version__` constant, a README badge, and a docs install snippet — and any one you miss ships as drift. This skill finds every occurrence, sets them all to a single agreed value, rolls the changelog, and stages the tag. It never picks the version for you and never publishes without your say-so.
9
+
10
+ ## When to use this skill
11
+
12
+ - You've decided the new version (e.g. `2.4.0`, or a pre-release `2.4.0-rc.1`) and need every artifact updated to match in one pass.
13
+ - You're cutting a release and want the bump commit to be clean, atomic, and correctly tagged.
14
+ - A previous bump left drift — `package.json` says one version, the lockfile or a badge says another — and you want them reconciled.
15
+ - You maintain a monorepo or a repo with a bundled CLI sub-package whose versions and dependency ranges must move together.
16
+
17
+ > [!NOTE]
18
+ > This skill applies a version you've already chosen. If you haven't decided whether the change is major/minor/patch, run a semver analysis first (see `semver-advisor`) — getting the number right is out of scope here.
19
+
20
+ ## Instructions
21
+
22
+ 1. **Confirm the exact target version before touching anything.** Read the current version from the root `package.json` (or `pyproject.toml`, `Cargo.toml`, etc.). State the old → new transition explicitly and stop if the new value isn't strictly greater, or if it's malformed. A pre-release identifier (`-rc.1`, `-beta.2`, `-next.0`) is valid and must be carried verbatim into every artifact — do not silently drop it.
23
+
24
+ 2. **Find every place the version lives.** Don't assume — grep. The number leaks into more files than you expect:
25
+
26
+ ```bash
27
+ OLD="1.2.3" # current version, escaped if it contains dots
28
+ grep -rnF "$OLD" \
29
+ --include='*.json' --include='*.toml' --include='*.md' \
30
+ --include='*.ts' --include='*.js' --include='*.py' --include='*.yml' --include='*.yaml' \
31
+ --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=dist .
32
+ ```
33
+
34
+ Then triage each hit. Update version *declarations*; never blanket-replace — a `1.2.3` in changelog history or a test fixture must stay put.
35
+
36
+ 3. **Update the canonical manifest(s).** Edit the `version` field in the root `package.json`. For nested packages (a `cli/package.json`, workspace packages, a submodule manifest), update each one to the same value unless they version independently — confirm which model the repo uses before assuming lockstep.
37
+
38
+ 4. **Update the lockfile so it doesn't drift.** Editing `package.json` alone leaves `package-lock.json` (and the `packages[""].version` entry inside it) stale. Regenerate it deterministically rather than hand-editing:
39
+
40
+ ```bash
41
+ npm install --package-lock-only --ignore-scripts
42
+ ```
43
+
44
+ For `pnpm` use `pnpm install --lockfile-only`; for `yarn` run `yarn install --mode update-lockfile`.
45
+
46
+ 5. **Update version constants and human-facing references.** Catch the non-manifest spots the grep surfaced: a `VERSION` / `__version__` constant in source, a README shields.io badge (`version-1.2.3-` → `version-2.4.0-`), install snippets pinning `pkg@1.2.3`, and any `docs/` page that names the current version. Skip historical mentions (changelog entries, migration notes about old releases).
47
+
48
+ 6. **Roll the changelog.** Move everything under the `## [Unreleased]` heading into a new `## [X.Y.Z] — YYYY-MM-DD` section dated today, then leave `## [Unreleased]` empty above it. Update the link-reference footer if the changelog uses compare-URL refs (`[X.Y.Z]: …/compare/vOLD...vX.Y.Z` and a fresh `[Unreleased]: …/compare/vX.Y.Z...HEAD`).
49
+
50
+ 7. **For monorepos, keep interdependent versions and ranges consistent.** When package A depends on package B and both bump, update A's dependency range on B (e.g. `"@scope/b": "^2.4.0"`) so a consumer doesn't resolve a mismatched pair. Verify no `workspace:*` range was accidentally pinned to a literal.
51
+
52
+ 8. **Stage — do not run — the release commit and annotated tag.** Print the exact commands and wait for the user. The bump commit must land *before* the tag points at it; tag from the wrong commit and you've published a tag that doesn't match its tree.
53
+
54
+ ```bash
55
+ git add -A
56
+ git commit -m "chore(release): vX.Y.Z"
57
+ git tag -a vX.Y.Z -m "vX.Y.Z"
58
+ # push only when asked: git push origin HEAD vX.Y.Z
59
+ ```
60
+
61
+ > [!WARNING]
62
+ > Never run `git tag` before the bump commit is committed — an annotated tag captures the commit it points to, so a premature tag will reference the *previous* state, and moving a published tag breaks anyone who already fetched it. Commit first, verify `git show HEAD --stat` contains the version edits, then tag.
63
+
64
+ > [!WARNING]
65
+ > A lockfile left at the old version is the single most common bump bug: CI installs, sees `package-lock.json` disagrees with `package.json`, and either fails or silently resolves the old version. Always regenerate the lockfile in the same commit as the manifest bump.
66
+
67
+ ## Output
68
+
69
+ Two artifacts, both reviewable before anything is committed:
70
+
71
+ 1. **A change table** of every file touched, old → new:
72
+
73
+ | File | Old | New |
74
+ | --- | --- | --- |
75
+ | `package.json` | `1.2.3` | `2.4.0` |
76
+ | `package-lock.json` | `1.2.3` | `2.4.0` |
77
+ | `cli/package.json` | `1.2.3` | `2.4.0` |
78
+ | `src/version.ts` | `1.2.3` | `2.4.0` |
79
+ | `README.md` (badge) | `1.2.3` | `2.4.0` |
80
+ | `CHANGELOG.md` | Unreleased | `## [2.4.0] — 2026-06-17` |
81
+
82
+ 2. **The exact release commands**, ready to paste and run only on request:
83
+
84
+ ```bash
85
+ git add -A
86
+ git commit -m "chore(release): v2.4.0"
87
+ git tag -a v2.4.0 -m "v2.4.0"
88
+ # git push origin HEAD v2.4.0
89
+ ```
90
+
91
+ Plus a one-line note of anything skipped on purpose (historical version mentions left untouched) or anything that needs a human decision (a sub-package that may version independently).
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: "webhook-handler-scaffolder"
3
+ description: "Scaffold a robust inbound webhook handler that verifies the signature on the raw body first, dedupes on the provider's event id, acknowledges fast, and processes asynchronously — the four things naive handlers get wrong. Use when wiring up events from a third party (Stripe, GitHub, Shopify, Slack, Twilio), when a provider keeps retrying because your endpoint times out or 500s, or when duplicate events are double-charging or double-creating records."
4
+ allowed-tools: "Read, Grep, Glob, Edit, Write"
5
+ version: 1.0.0
6
+ ---
7
+
8
+ A webhook endpoint is untrusted input that arrives over the public internet, claims to be from your payment provider, and may be a duplicate, a replay, or out of order. Handlers written for the happy path — parse the JSON, do the work, return 200 — fail in exactly the ways that cost money: a forged request gets processed, a retried event double-charges a customer, or a slow database write makes the provider time out and retry, compounding the duplicate. This skill scaffolds the handler in the one shape that survives real delivery semantics: **verify → dedupe → persist → ack → process async**.
9
+
10
+ ## When to use this skill
11
+
12
+ - Wiring up an inbound webhook from a third party (Stripe, GitHub, Shopify, Slack, Twilio, Square, GitLab) and you want it correct on the first commit.
13
+ - A provider's dashboard shows repeated retries or your endpoint is flagged as failing because it times out or returns 5xx.
14
+ - Duplicate events are causing double-charges, duplicate emails, or duplicate records, and you need idempotency retrofitted.
15
+ - A security review flagged that the endpoint trusts the payload without verifying its origin.
16
+
17
+ ## Instructions
18
+
19
+ 1. **Identify the provider's contract before writing code.** Find, for the specific provider: the signature scheme (HMAC-SHA256 is typical), which header carries the signature (`Stripe-Signature`, `X-Hub-Signature-256`, `X-Shopify-Hmac-Sha256`), what string is actually signed (often `timestamp + "." + raw_body`, not the body alone), the stable event id field (`id`, `delivery` GUID), and the event-type field. Grep the codebase for an existing handler to match its framework and error conventions rather than introducing a new pattern.
20
+ 2. **Capture the raw body, then verify before anything else.** Read the request body as raw bytes/string and compute the HMAC over those exact bytes with the signing secret from config (never hardcoded). Compare using a **constant-time** comparison (`crypto.timingSafeEqual`, `hmac.compare_digest`) — never `==`, which leaks the secret via timing. If verification fails, return `401` and stop. Only after a valid signature do you `JSON.parse` the body.
21
+ 3. **Reject stale requests to block replay.** If the provider signs a timestamp, parse it and reject (`400`) when it is outside a tolerance window (Stripe uses 5 minutes). This stops a captured-and-replayed valid request from being reprocessed indefinitely.
22
+ 4. **Dedupe on the provider's event id.** Before doing any work, insert the event id into a store with a **unique constraint** (a `webhook_events` table, or Redis `SET key NX EX`). If the insert conflicts, the event was already received — return `200` immediately and do nothing else. Treat the provider's id as the idempotency key; never derive your own from payload contents (two distinct events can have identical bodies).
23
+ 5. **Persist the raw event, then acknowledge fast.** Store the raw body, headers, event id, and type with a `received_at` timestamp and `processed = false`. Return `2xx` as soon as the event is durably recorded — do not run business logic inline. Providers enforce short timeouts (Stripe ~10s, GitHub ~10s); slow synchronous work guarantees a timeout and a redundant retry.
24
+ 6. **Process asynchronously and idempotently.** Enqueue the stored event to a worker/queue (or a background task) that performs the real side effects, then marks `processed = true`. The worker must itself be safe to re-run, because the queue is also at-least-once: scope writes by the event id, use upserts, and never assume the event arrives in causal order (a `subscription.updated` can land before `subscription.created`) — reconcile from the payload's own state rather than from event sequence.
25
+ 7. **Define the failure path explicitly.** Decide what a `5xx` means (provider will retry) versus a `2xx` on a malformed-but-authentic event (you accept and dead-letter it instead of looping forever). Add structured logging keyed by event id and a dead-letter destination for events the worker can't process after N attempts.
26
+
27
+ > [!WARNING]
28
+ > Parsing the body before verifying the signature is the single most common webhook vulnerability — and the most common cause of "signature mismatch" bugs. Framework middleware that auto-parses JSON (Express `express.json()`, Next.js default body parsing) consumes the raw stream, so the bytes you sign over no longer match the bytes the provider sent. Reserve the raw body for the webhook route (e.g. `express.raw({ type: 'application/json' })`, `bodyParser: false`) before doing anything else.
29
+
30
+ > [!NOTE]
31
+ > Never treat delivery as exactly-once or ordered. Every major provider documents at-least-once delivery with retries, which means duplicates and reordering are normal operation, not edge cases. The unique-constraint dedupe in step 4 and the order-independent reconciliation in step 6 are what make that safe — without them the handler is correct only by luck.
32
+
33
+ ## Output
34
+
35
+ - A complete webhook handler scaffold in the project's framework, structured as **verify (constant-time HMAC on raw body) → replay check → dedupe on event id → persist raw event → return 2xx → enqueue for async processing**, with the signature check, secret loading, and raw-body wiring filled in for the detected provider and the business logic left as a clearly marked TODO in the worker.
36
+ - The idempotency and storage design: the `webhook_events` schema (event id with a unique constraint, raw payload, type, `received_at`, `processed`, attempt count), the dedupe-on-insert flow, and the dead-letter/retry policy.
37
+ - A short note listing the provider-specific details that must be confirmed against its docs: signing secret location, signature header name, the exact signed string, event-id field, and the timeout/retry behavior the handler is built to satisfy.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentscamp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Install AgentsCamp agents, skills, and slash commands into Claude Code from your terminal.",
5
5
  "license": "MIT",
6
6
  "type": "module",