@dennisrongo/skills 0.6.0 → 0.8.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,88 @@
1
+ ---
2
+ name: e2e-verify
3
+ description: Verify a change end-to-end in a real browser, routed by one question — who needs this check to run again? Ephemeral checks run via Expect (millionco/expect) if installed or Claude driving the browser directly; behaviors that must never silently regress (auth, money, signup, checkout, deletion) get durable Playwright tests committed under write-tests discipline. Evidence rule either way — an AI-walked flow yields "no issues found in the paths walked" with observations quoted, never "e2e passes"; unobserved behavior is "not verified", never "works". Safety gate — never against production or with real user cookies. Use this skill whenever the user says "verify this in the browser", "test it end to end", "e2e test this", "write playwright tests", "run expect", "smoke test the UI", "does the feature actually work", "check it in the browser", or "/e2e-verify" — even if they don't name the skill. Not for unit/integration authoring (write-tests) or debugging a failing e2e test (diagnose).
4
+ ---
5
+
6
+ # E2E Verify
7
+
8
+ Close the loop between "the diff looks right" and "a user can actually do the thing." Every route through this skill drives a real browser against running code; what differs is durability. The routing question is not which tool — it's **who needs this check to run again?** Nobody (one-off confidence in this change) → ephemeral. CI, forever (a behavior whose silent regression is expensive) → durable Playwright test in the repo. Often both: smoke now, durable test as the deliverable.
9
+
10
+ ## When to use this skill
11
+
12
+ - The user says "verify this in the browser", "test it end to end", "e2e test this", "write playwright tests", "run expect", "smoke test the UI", "does it actually work", "/e2e-verify".
13
+ - A feature from `plan-and-build` / `task-executor` is done and needs proof beyond unit tests; `code-review` or `ship-it` wants runtime evidence.
14
+
15
+ Do **not** auto-trigger for unit/integration authoring (`write-tests`), debugging an already-failing e2e test (`diagnose`), or general browser automation that isn't testing.
16
+
17
+ ## The safety gate — before any browser opens
18
+
19
+ 1. **Confirm the target URL is local or staging.** State it in the report. If the only available URL is production, stop and ask — never assume.
20
+ 2. **No real user cookies or accounts.** Expect extracts system-browser cookies by default — pass `--no-cookies` unless the user explicitly opts in for a non-prod target. Flows that mutate data (payments, deletion, invites that email people) run only against seeded/throwaway data; if none exists, that's a blocker to surface, not a reason to "carefully" test on real data.
21
+ 3. **Confirm something is listening.** Load the base URL and observe a real response before walking any flow. If nothing answers, start the dev server the repo's own way (its dev script / launch config) and confirm it's up — a dead port produces "every flow is broken", which is a false report about the app. Distinguish "the app failed" from "the app wasn't running" in everything you report.
22
+
23
+ ## Route the request
24
+
25
+ | Signal | Route |
26
+ |---|---|
27
+ | "Does my change work?", pre-merge confidence, exploratory | **A: Ephemeral** |
28
+ | The flow is money/auth/signup/checkout/data-loss, or user says "add e2e tests", or the same flow has now been manually re-verified twice | **B: Durable** |
29
+ | Feature just built and it's a critical flow | **A now, then B** — the smoke run's steps become the test's spec |
30
+
31
+ ## Route A: ephemeral verification
32
+
33
+ Pick the engine by what's installed — the discipline is identical across engines:
34
+
35
+ 1. **Expect** (`expect-cli` / `/expect` on PATH): run with an explicit `--url` (the confirmed non-prod target), `--no-cookies` by default, `--target` matching the change scope. Its report is input, not verdict — extract which flows its subagents walked and what they observed.
36
+ 2. **Claude drives the browser** (Playwright MCP, claude-in-chrome, preview tools — whatever this session has): enumerate the user-visible flows the diff touches (from the diff, not imagination), walk each one as a user would, and at each step verify via DOM/accessibility state for text and behavior (screenshots only for layout questions). After each flow: check the browser console for errors and the network log for failed requests — a page that looks right while logging exceptions is a finding, not a pass.
37
+ 3. **Neither available — offer the install, never dead-end and never install silently.** One `AskUserQuestion` with the real options: (a) install Playwright locally (`npm init playwright@latest` — standard tooling, Claude then drives it), (b) install Expect (state plainly what its init does: runs a third-party script that adds a skill + hooks into the agent — third-party init scripts require an explicit yes), or (c) skip. Only if the user skips, report `e2e: not verified — no browser tooling` and stop. Never substitute code-reading for observation, and never report "verified" after a skipped install.
38
+
39
+ **Reporting rule (the core of the skill):** an AI-simulated user is a fallible verifier — same epistemics as any model output. The honest claim shape:
40
+ - ❌ "Ran Expect — e2e tests pass ✅"
41
+ - ✅ "Walked 3 flows against localhost:3000 (seeded account): login → dashboard (observed: redirect + username rendered), create invoice (observed: row appears, total 107.10, console clean), delete invoice (observed: 409 on double-delete — **finding**, quoted below). Not walked: mobile viewport, payment path (no test Stripe key). "
42
+ Every flow walked is enumerated; every "works" is backed by a named observation; everything not walked is listed. Zero findings across enumerated flows is a valid outcome.
43
+
44
+ ## Route B: durable Playwright tests
45
+
46
+ `write-tests` discipline applied to the browser — plus the e2e-specific rules that keep suites from rotting:
47
+
48
+ 1. **Match the repo first.** Grep for `playwright.config`, `cypress.config`, an `e2e/` dir. Extend the existing setup and imitate its strongest test; only scaffold fresh (`npm init playwright@latest`) if nothing exists, and say so.
49
+ 2. **Ration e2e tests by risk.** Each one is 100× a unit test's cost in time and flake surface. Test through the UI only what is *about* the flow: the user journey, the wiring, the redirect. Business-logic variations (12 discount cases) belong in unit tests — write the one journey here and note the push-down. A handful of journeys is a suite; fifty is a liability.
50
+ 3. **Selectors users would recognize:** `getByRole`, `getByLabel`, `getByTestId` — never CSS chains or `nth-child`, which weld the test to today's DOM.
51
+ - ❌ `page.locator('div.card > button:nth-child(2)')` — breaks on any restyle, catches nothing a user cares about.
52
+ - ✅ `page.getByRole('button', { name: 'Submit order' })` — breaks only when the user's world breaks.
53
+ 4. **No sleeps.** Web-first assertions (`await expect(locator).toBeVisible()`) auto-wait; `waitForTimeout` is flake by construction. If a step genuinely needs polling, poll a condition.
54
+ 5. **Independent tests, cheap setup.** Each test arranges its own world — seed state via API calls or fixtures, not by clicking through the UI (UI setup means test 3 fails because of the flow test 1 owns). Auth via a `storageState` fixture created once, not a login walk per test.
55
+ 6. **Prove red-capable** (non-negotiable, per `write-tests`): break the behavior or the assertion target, run, quote the red; revert, quote the green. An e2e test never seen red is the most expensive decoration there is.
56
+ 7. **Run the suite and quote the summary line.** A result you didn't observe is `not run`, never "passed".
57
+
58
+ ## Examples
59
+
60
+ ### Example 1: post-feature smoke → durable
61
+
62
+ **User:** "I just finished the invoice feature — verify it end to end."
63
+
64
+ **Claude:** Confirms target (localhost:3000, seeded user), no Expect installed → drives the browser: walks create/edit/void flows from the diff's touched routes, quotes observations per flow, catches a console 500 on void (finding). After the fix: "void is a money path — want the durable test?" → writes one Playwright journey extending the existing `e2e/` config, `getByRole` selectors, proves it red against the reverted fix, quotes both runs.
65
+
66
+ ### Example 2: "run expect"
67
+
68
+ **User:** "Run expect on my branch."
69
+
70
+ **Claude:** Confirms the dev URL, runs `expect --target branch --url http://localhost:3000 --no-cookies`, then translates its output into the claim shape: flows walked + observations + findings + not-walked list — and flags that the checkout flow wasn't in Expect's generated plan despite being in the diff, walking it manually to close the gap.
71
+
72
+ ## Anti-patterns
73
+
74
+ - ❌ Reporting an Expect or browser-walk green as "e2e tests pass" — the verifier is a model; the claim is "no issues found in the paths walked", paths listed.
75
+ - ❌ Trusting the generated test plan to cover the diff — diff-to-plan is lossy; check the plan against the diff's touched routes and walk what it missed.
76
+ - ❌ Verifying against production, or letting Expect's default cookie extraction ride along silently.
77
+ - ❌ Visual-only verification — a rendered page with console exceptions and failed XHRs is a finding.
78
+ - ❌ Writing an e2e test for every case a unit test could cover — ration by journey, push logic down the pyramid.
79
+ - ❌ Shipping an e2e test never seen red, or "fixing" a flaky one by adding `waitForTimeout`.
80
+ - ❌ Scaffolding a fresh Playwright setup when the repo already has one (or has Cypress — extend what's there).
81
+ - ❌ Running Expect's init script (or any third-party installer) without an explicit yes — offering the install is required, running it unasked is not consent.
82
+ - ✅ Route by who-needs-it-again → safety gate → observations quoted per flow → durable tests proven red-capable → not-walked list always present.
83
+
84
+ ## Notes
85
+
86
+ - Engines are swappable; the discipline isn't. If Expect changes licensing (FSL, hosted version coming) or a better ephemeral tool appears, only Route A's engine list changes.
87
+ - Manual re-verification of the same flow twice is the signal to graduate it to Route B — the third time is a test.
88
+ - Apply `think-like-fable`: flows chosen by risk, every "works" re-derived by observation, the not-walked list is the labeled-assumption discipline, and the report leads with findings, not the tour.
@@ -29,10 +29,12 @@ If the user wants to build *after* grilling, hand off to `plan-and-build` and le
29
29
  ## Core principles
30
30
 
31
31
  - **One question at a time.** Always use `AskUserQuestion` with 2–4 concrete options and a recommended pick first (matches the convention used by [`plan-and-build`](../plan-and-build/SKILL.md)). Never stack three questions in one message — the user can only think clearly about one branch of the design tree at a time.
32
- - **Explore before asking.** If the answer is in the code, read it. Don't ask the user what `OrderService.Cancel` does when you can open the file. Ask only the questions the code can't answer.
32
+ - **Every question must fork the design.** A question earns its slot only if different answers lead to different designs or documents. If every answer leads to the same next step, don't ask it you're performing rigor, not applying it.
33
+ - **Explore before asking.** If the answer is in the code, read it. Don't ask the user what `OrderService.Cancel` does when you can open the file. Ask only the questions the code can't answer. A claim about code you haven't opened this session is a **hypothesis** — label it one ("I suspect `Cancel` soft-deletes; confirming…") and read the file before building a question or an objection on it.
33
34
  - **Terminology rigor.** Surface conflicts between the user's words and the glossary on the spot. Challenge vague or overloaded terms with the canonical alternative. If the user says "account", check whether the glossary distinguishes `Customer` from `User` — and force a choice.
34
35
  - **Probe with concrete scenarios.** Invent edge cases that make abstract language fail. "What happens when two users press *Cancel* on the same order within 50ms?"
35
- - **Contradiction surfacing.** When stated intent contradicts what the code already does, name the discrepancy explicitly and ask which wins. Don't paper over it.
36
+ - **Contradiction surfacing.** When stated intent contradicts what the code already does, name the discrepancy explicitly and ask which wins. Don't paper over it. An objection must cite the specific thing it conflicts with — the `CONTEXT.md` term, the ADR number, or the `file:line` you read this session. If you can't name the conflict, you don't have an objection. Zero real conflicts is a valid, reportable outcome — don't invent friction to look rigorous.
37
+ - **Fold on preference, not on facts.** If the user's answer contradicts a documented decision or code you've cited, present the conflict once more with the citation. If they still overrule you, defer and record their call. Capitulating the moment the user pushes back — while the cited evidence still stands — is as bad as stonewalling.
36
38
  - **In-the-moment doc updates.** The moment a fuzzy term resolves, write the glossary entry into `CONTEXT.md`. Don't batch — batched updates get dropped.
37
39
  - **No code.** This skill writes only to `CONTEXT.md`, `CONTEXT-MAP.md`, and `docs/adr/*.md`. Never edit source files, never run migrations, never run tests. If the conversation reveals a code-level bug, note it and stop — that's a separate task.
38
40
 
@@ -76,6 +78,11 @@ Use `AskUserQuestion`. Lead with the recommendation, append "(Recommended)" to i
76
78
 
77
79
  If the user goes off-piste or picks "Other", absorb their answer, restate it in canonical terms, and continue.
78
80
 
81
+ A forking question vs. a checklist question:
82
+
83
+ - ❌ "Should we handle errors if the payment API fails?" — every answer is "yes"; the design doesn't move. Don't ask it.
84
+ - ✅ "When the payment API fails mid-checkout, does the order persist as `PaymentPending` (new status + retry worker) or roll back entirely (no schema change)?" — answer A adds a status and a background job, answer B adds nothing. The answers diverge, so the question earns its slot.
85
+
79
86
  ## Phase 3 — Update docs in-flight
80
87
 
81
88
  As decisions crystallise, write them immediately. Two artefacts only.
@@ -112,6 +119,11 @@ Create an ADR only when **all** of:
112
119
 
113
120
  If any one of those is missing, don't write an ADR. A glossary entry or a code comment is enough.
114
121
 
122
+ Calibration:
123
+
124
+ - ❌ "Add an index on `Orders(CustomerId)`" — reversed in one migration, obvious from the query plan, no seriously-considered alternative. No ADR; it's just code.
125
+ - ✅ "Webhook delivery becomes at-least-once via queue" — consumers must be idempotent forever (hard to reverse), sync was the obvious default (non-obvious), and outbox / sync-with-retries were genuinely weighed (real trade-offs). Write the ADR.
126
+
115
127
  #### ADR template
116
128
 
117
129
  Drop into `docs/adr/NNNN-short-title-kebab.md`, numbered sequentially after the highest existing ADR:
@@ -147,7 +159,7 @@ Keep ADRs immutable once accepted. If the decision changes, write a new ADR that
147
159
 
148
160
  Stop grilling when **any** of these are true. Don't pad the conversation.
149
161
 
150
- - Further questions stop changing the design — the next question's answer wouldn't move a file or change a name.
162
+ - Further questions stop changing the design — the next question's answer wouldn't move a file or change a name. Operational check: **if the last two answers changed nothing** — no glossary entry, no renamed term, no design fork — the interview is done; summarise and move to crystallisation. Don't stop after two questions because the surface looks calm, and don't grill past the point where answers stop moving files.
151
163
  - All terms in the user's spec have a matching `CONTEXT.md` entry (existing or just-added) and no contradictions remain.
152
164
  - The user explicitly signals "good enough" — accept it, but quickly note any genuinely open questions you can see.
153
165
  - The conversation has revealed that the right next step is **not** to grill further but to prototype, diagnose, or pick a different scope. Say so and stop.
@@ -163,6 +175,10 @@ When you stop, produce a short closing summary:
163
175
 
164
176
  - Asking three questions in one message. One at a time, with options.
165
177
  - Asking the user something the codebase already answers. Read first, ask second.
178
+ - Asking questions whose every answer leads to the same next step. If the answer can't fork the design, cut the question.
179
+ - Raising an objection that cites nothing. Name the `CONTEXT.md` term, ADR, or `file:line` it conflicts with — or drop it. "No conflicts found" is a valid result.
180
+ - Folding on first pushback while the cited evidence still stands. Restate the conflict once with the citation, then defer.
181
+ - Asserting what code does without having read it this session. Unread-code claims are hypotheses — say so.
166
182
  - Letting a vague term ("account", "user", "delete", "cancel", "process") slide because the user used it confidently.
167
183
  - Adding code, tests, or migrations during this skill. Out of scope.
168
184
  - Writing an ADR for a decision that's easily reversed, obvious from the code, or had no real alternative.
@@ -107,6 +107,11 @@ Read `.claude/handoffs/<this-filename>.md` first.
107
107
 
108
108
  The **Next Session Prompt** is the most important section — it's what the user pastes into the new chat to bootstrap continuity. Write it so a fresh Claude with no prior context can act on it immediately.
109
109
 
110
+ A prompt that survives a fresh session vs. one that doesn't:
111
+
112
+ - ❌ "**Goal:** Continue working on auth. **Then:** finish the remaining fixes." — which file? what's broken? what command shows it? Every answer lives in the dead session.
113
+ - ✅ "**Goal:** Make `linkAccount` merge OAuth identities instead of erroring. **Start by reviewing:** `src/auth/link.ts` (the `linkAccount` stub at line 42), `src/auth/__tests__/link.test.ts`. **Then:** implement the merge branch for the `existing-verified-email` case; run `pnpm vitest run src/auth -t linkAccount` — currently failing with `Error: account already exists`." — names the file, the failing test, the exact command, and the next action.
114
+
110
115
  ## Workflow
111
116
 
112
117
  1. **Scan the current conversation** for: the stated objective, files you've read or edited, decisions made (and the reasoning), commands run, errors hit, and open threads.
@@ -115,13 +120,16 @@ The **Next Session Prompt** is the most important section — it's what the user
115
120
  4. **Pick the filename** — today's date + short slug.
116
121
  5. **Create `.claude/handoffs/` if missing**, then write the file with the Write tool.
117
122
  6. **Write the memory pointer** to the auto-memory directory and index it in `MEMORY.md`.
118
- 7. **Report back**: tell the user the file path, the slug, and quote the Next Session Prompt so they can copy it without opening the file.
123
+ 7. **Run the zero-context self-test.** Re-read the Next Session Prompt as if you knew nothing about this conversation: does it name the exact files, the exact command to run, the current failing state, and the immediate next action? If answering any of those requires this conversation, the hand-off is not done — fix it before reporting.
124
+ 8. **Report back**: tell the user the file path, the slug, and quote the Next Session Prompt so they can copy it without opening the file.
119
125
 
120
126
  ## Verifying before you write
121
127
 
122
128
  - If you reference a file path, confirm it exists (or that you created it this session).
123
129
  - If you reference a command, make sure it's the one that actually works in this repo (check `package.json`, `Makefile`, etc.).
124
130
  - If a decision in the conversation was reversed later, record the final decision — not the abandoned one.
131
+ - **Exact artifacts, never paraphrases.** Commands verbatim and runnable (`pnpm vitest run src/auth -t linkAccount`, not "run the auth tests"). Failing test names copied from actual runner output. Branch name from `git branch --show-current`. Uncommitted-state description from actual `git status` output — never from memory of what you think you edited.
132
+ - If tests were failing when work stopped, re-run them now and quote the exact failure. "Some tests failing" forces the next session to rediscover which — that's the context loss this skill exists to prevent.
125
133
 
126
134
  ## Examples
127
135
 
@@ -147,6 +155,8 @@ The new session reads the referenced hand-off file first, confirms files still e
147
155
  - Dumping the entire conversation transcript — hand-off is a synthesis, not a log.
148
156
  - Forgetting the memory pointer — without it, the next session won't know the hand-off exists unless the user remembers to paste the prompt.
149
157
  - Skipping the **Next Session Prompt** section — that's the single highest-value piece of the doc.
158
+ - Quoting commands, test names, or branch names from memory instead of from actual output. `git status`, `git branch --show-current`, and the test runner are the sources of truth.
159
+ - Shipping a Next Session Prompt that fails the zero-context self-test — if understanding any part of it requires this conversation, it's a summary, not a hand-off.
150
160
 
151
161
  ## Notes
152
162
 
@@ -59,15 +59,23 @@ Then use the Agent tool with `subagent_type=Explore` to walk the codebase. Don't
59
59
 
60
60
  Apply the **deletion test** to anything you suspect is shallow: would deleting it concentrate complexity, or just move it? A "yes, concentrates" is the signal you want.
61
61
 
62
+ **Evidence gates — both must hold before anything becomes a candidate:**
63
+
64
+ - **Friction must be observed, not theoretical.** Cite the pain: the awkward call sites (`file:line`), the shotgun-surgery pattern ("these 4 files change together — check `git log --oneline -- <dir>`"), the test that needs 40 lines of setup to exercise one branch. "This could be cleaner" with no cited pain is not a candidate.
65
+ - **Read before ruling.** A claim about a module you haven't opened this session is a hypothesis. Before ruling on the deletion test, read the module **and at least 2 of its call sites**. The operational form of the test: if every caller could call the layer below directly with no loss of clarity, the module fails — it's a pass-through. If deleting it would smear real logic (validation, ordering, error translation) across its callers, it passes.
66
+
62
67
  ### 2. Present candidates
63
68
 
64
69
  Present a numbered list of deepening opportunities. For each candidate:
65
70
 
66
71
  - **Files** — which files/modules are involved
67
72
  - **Problem** — why the current architecture is causing friction
73
+ - **Evidence** — the observed friction, cited: the call sites you read (`file:line`), the co-changing files, the awkward tests. No citation, no candidate.
68
74
  - **Solution** — plain English description of what would change
69
75
  - **Benefits** — explained in terms of locality and leverage, and also in how tests would improve
70
76
 
77
+ **Zero candidates is a valid outcome.** A healthy codebase produces a short or empty list. Report "no deepening opportunities found — here's what I checked" rather than padding the list with theoretical refactors to appear thorough.
78
+
71
79
  **Use `CONTEXT.md` vocabulary for the domain, and [LANGUAGE.md](LANGUAGE.md) vocabulary for the architecture.** If `CONTEXT.md` defines "Order," talk about "the Order intake module" — not "the FooBarHandler," and not "the Order service."
72
80
 
73
81
  **ADR conflicts**: if a candidate contradicts an existing ADR, only surface it when the friction is real enough to warrant revisiting the ADR. Mark it clearly (e.g. _"contradicts ADR-0007 — but worth reopening because…"_). Don't list every theoretical refactor an ADR forbids.
@@ -94,7 +102,7 @@ Side effects happen inline as decisions crystallize:
94
102
 
95
103
  **Claude:**
96
104
  1. **Explore.** Reads `CONTEXT.md` (sees `Order`, `OrderLine`, `Cart`) and `docs/adr/0004-validation-pipeline.md`. Spawns Explore on `src/orders/validation/` and finds 11 single-function modules (`validateSku.ts`, `validateQty.ts`, …) each called from one place in `OrderIntake`.
97
- 2. **Candidates.** Presents one numbered candidate: "Collapse the `validate*` cluster into a deep `OrderIntakeValidator` module — current shallow validators have interface nearly as complex as implementation; the real bugs hide in how `OrderIntake` *composes* them, where no test exercises the seam." Files / Problem / Solution / Benefits. Notes: does not contradict ADR-0004 (which is about *when* to validate, not *how* to compose validators).
105
+ 2. **Candidates.** Presents one numbered candidate: "Collapse the `validate*` cluster into a deep `OrderIntakeValidator` module — current shallow validators have interface nearly as complex as implementation; the real bugs hide in how `OrderIntake` *composes* them, where no test exercises the seam." Files / Problem / Evidence (the `OrderIntake` call site read, plus the two `validate*` modules opened for the deletion test) / Solution / Benefits. Notes: does not contradict ADR-0004 (which is about *when* to validate, not *how* to compose validators).
98
106
  3. **Grilling.** User picks the candidate. Walks the design tree: dependency category (in-process, [DEEPENING.md](DEEPENING.md) §1), seam placement (one external seam at `OrderIntakeValidator`, internal validators stay private), test surface (assert at `validate(order) → Result<Order, ValidationError[]>`, delete the 11 per-function tests). Adds `OrderIntakeValidator` to `CONTEXT.md`.
99
107
 
100
108
  ### Example 2: A candidate that contradicts an ADR — but worth reopening
@@ -106,6 +114,8 @@ Side effects happen inline as decisions crystallize:
106
114
  ## Anti-patterns
107
115
 
108
116
  - ❌ Proposing a refactor without applying the **deletion test** — leads to suggestions that just move complexity instead of concentrating it.
117
+ - ❌ Ruling on the deletion test from a module's name or the file listing. Read the module and ≥2 call sites first; until then it's a hypothesis, not a candidate.
118
+ - ❌ Manufacturing candidates from theoretical concerns: ❌ _"`OrderMapper` could be more flexible"_ (no cited pain) vs. ✅ _"`OrderMapper`'s 3 call sites each re-wrap its output (`intake.ts:41`, `sync.ts:88`, `api.ts:120`) — the seam is in the wrong place."_ The first pads the list; the second names the friction.
109
119
  - ❌ Naming things "FooHandler" / "BarService" / "BazManager" when `CONTEXT.md` already names the concept. Use the domain term.
110
120
  - ❌ Introducing a port + adapter for a dependency with only one implementation. **One adapter = hypothetical seam.**
111
121
  - ❌ Re-litigating an ADR without explicit cause. ADRs are decisions the skill should _not_ reopen unless the constraint that drove them is gone.
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: migration-safety
3
+ description: Review a schema migration for production safety under live traffic — destructive operations (dropped/renamed columns or tables, type narrowing) gated behind expand-contract plans, lock-taking DDL flagged with the specific lock and its duration driver, the deploy-order contract checked both ways (old code on new schema during rollout, new code on old schema during rollback), backfills separated from DDL and batched, and a rollback path stated per migration. Never executes migrations or DDL. Use this skill whenever the user says "review this migration", "is this migration safe", "will this lock the table", "zero-downtime migration", "check the schema change", "expand and contract", "review the EF migration / alembic / prisma migrate diff", or "/migration-safety" — even if they don't name the skill. Distinct from sql-review (T-SQL antipatterns in procs); this reviews SCHEMA CHANGES against live traffic and deploys.
4
+ ---
5
+
6
+ # Migration Safety
7
+
8
+ A migration runs once, against the production database, usually mid-deploy, while old and new application code overlap. This skill reviews it for the three ways that goes wrong: **locks** (DDL that blocks traffic), **ordering** (schema and code versions that can't coexist), and **irreversibility** (data destroyed with no path back). The core discipline: every migration is judged against the deploy timeline, not against an empty dev database where everything is instant and nothing is watching.
9
+
10
+ ## When to use this skill
11
+
12
+ - The user says "review this migration", "is this migration safe", "will this lock the table", "zero-downtime", "expand and contract", "check the schema change", "/migration-safety".
13
+ - A migration file (EF Core, Prisma, alembic, Rails, Flyway, raw DDL) sits in the diff — including ones generated by `plan-and-build`, which writes migrations but never runs them.
14
+ - `ship-it` flags a migration and it needs the dedicated pass.
15
+
16
+ Do **not** auto-trigger for stored-procedure or query changes (`sql-review`) or for greenfield schemas with no production data — on an empty database most of this catalog is N/A, and the report should say that in one line instead of performing the checklist. This skill **never executes** migrations, DDL, or any SQL against a database — it reads files and reports.
17
+
18
+ ## Workflow
19
+
20
+ 1. **Establish the context the file doesn't show.** Which engine (Postgres/MySQL/SQL Server/SQLite — lock behavior differs by engine and version, and a lock claim that doesn't name the engine is a guess)? Is there production data, and are the touched tables large or hot? Get evidence where possible (the user, a row-count comment, table names like `events`/`audit_log` that are large by nature) — otherwise label the assumption out loud: "assuming `orders` is large and hot; if it's small, findings 2–3 downgrade to noise." How do migrations deploy relative to code (before app deploy? in the same release?) — this determines step 4's ordering checks.
21
+ 2. **Walk the destructive-operation gate.** `DROP COLUMN`/`DROP TABLE`, column/table **renames** (a rename IS a drop+add to running code), type **narrowing** (`varchar(500)→(50)`, `bigint→int`, nullable→`NOT NULL`), truncates, and `DELETE`/`UPDATE` data mutations. Each is a **blocker unless** an expand-contract plan is stated: expand (add the new thing, dual-write or backfill), migrate readers, contract (drop the old thing **in a later release**, after old code is provably gone). Drop-in-the-same-release-as-the-code-change fails the rollback test by construction.
22
+ - ❌ "Renamed `users.name` to `users.full_name` in one migration — the ORM was updated too, so it's fine."
23
+ - ✅ "Rename is drop+add to the old code still running during rollout: add `full_name`, backfill, deploy code reading `full_name` (writing both), then drop `name` in release N+2. Or, if the deploy has real downtime, say so and the one-step rename is fine — name the assumption."
24
+ 3. **Flag lock-taking DDL with the specific mechanism.** Name the lock and what drives its duration — "might be slow" is not a finding. The high-yield catalog: index creation without `CONCURRENTLY` (Postgres: blocks writes for the whole build) or `ONLINE = ON` (SQL Server, edition-permitting); `NOT NULL` added without engine-appropriate staging (Postgres: `ADD CONSTRAINT ... NOT VALID` then `VALIDATE`; adding a column `NOT NULL` **with** a constant default is metadata-only on modern PG/SQL Server — don't flag what's actually free, that's severity inflation); full-table-rewrite type changes; adding an FK without `NOT VALID`+`VALIDATE`; MySQL DDL without an online strategy on big tables. For each: the operation, the lock, the duration driver (table size, write rate), and the non-blocking alternative.
25
+ 4. **Check the deploy-order contract both ways.** During rollout, **old code runs against the new schema** — every added `NOT NULL`-without-default, dropped column old code still selects, or renamed anything breaks it. During rollback, **new-ish data meets old schema expectations** — rows written by new code must not violate what old code assumes. Trace both directions against the actual code in the diff (grep for the column names old code uses). Also: does anything in the release *require* the migration to have run (new code reading a column that doesn't exist yet) — if so the migrate-then-deploy ordering is load-bearing; say it.
26
+ 5. **Separate backfills from DDL.** Data backfills inside the schema migration (same transaction as DDL, or a single `UPDATE table SET ...` over millions of rows) are findings: long transactions hold locks, bloat undo/WAL, and can't checkpoint. Recommendation shape: DDL migration adds the nullable column; a separate batched job (with progress + resumability) backfills; a later migration adds the constraint. Small-table backfills are fine — say why (the size evidence or assumption from step 1).
27
+ 6. **Demand the rollback path, per migration.** Three honest answers: (a) reversible — the down-migration exists and actually restores (auto-generated downs that drop a column do NOT restore its data — a down that loses data is not a rollback, label it); (b) roll-forward-only — irreversible by nature, so the plan is a fixing migration, and the deploy gate should know; (c) backup-gated — destructive step requires a named backup/snapshot beforehand. "It has a down file" without reading it is not an answer.
28
+ 7. **Report.** Findings ranked **blocker** (data loss without a stated plan, traffic-blocking lock on a hot table, rollout/rollback contradiction) / **should-fix** / **note**, each with `file:line`, the failure scenario in one sentence (deploy does X → traffic sees Y), and the safe alternative. State the assumptions from step 1 that findings hang on. A green report on an additive, defaulted, indexed-concurrently migration is a valid outcome — two lines, not a performed checklist. Never edit or execute anything unprompted; ask per-finding whether to draft the fix.
29
+
30
+ ## Examples
31
+
32
+ ### Example 1: the innocent-looking rename
33
+
34
+ **User:** "Review this EF migration before I merge — just renames a column and adds an index."
35
+
36
+ **Claude:** Flags the rename as drop+add against live old code (blocker, expand-contract plan sketched across 3 releases), checks the index: `CreateIndex` on `Orders` — SQL Server without `ONLINE = ON`; asks/flags edition support, cites the line. Checks the down: `RenameColumn` back — actually reversible, noted. One assumption stated: `Orders` presumed hot.
37
+
38
+ ### Example 2: honest green
39
+
40
+ **User:** "Is this migration safe? Adds a nullable `preferences` jsonb column and a partial index CONCURRENTLY."
41
+
42
+ **Claude:** Nullable add = metadata-only; `CONCURRENTLY` = non-blocking (notes it must run outside a transaction — verifies the tool's migration wrapper handles that, cites how); old code unaffected (column unreferenced by it), rollback trivial. Report: no findings, one note about the transaction wrapper. Done in four lines.
43
+
44
+ ## Anti-patterns
45
+
46
+ - ❌ Reviewing the migration against an empty dev database mentally — every finding is judged against size, traffic, and the deploy overlap, or labeled with the assumption.
47
+ - ❌ Lock claims without the engine — `ADD COLUMN NOT NULL DEFAULT` is free on modern Postgres and was a rewrite on old versions; flagging the free case is severity inflation that erodes trust in the real findings.
48
+ - ❌ Accepting a rename or drop because "the code was updated in the same PR" — the whole problem is the minutes-to-hours when old code and new schema coexist.
49
+ - ❌ Trusting an auto-generated down-migration as a rollback without reading whether it restores data or just shape.
50
+ - ❌ Letting a million-row backfill ride inside the DDL transaction because the migration tool put it there.
51
+ - ❌ Executing the migration, `dotnet ef database update`, or ANY SQL "to check" — this skill reads and reports, full stop.
52
+ - ❌ Performing the full checklist on a pre-production empty schema — one line of N/A beats a page of theater.
53
+ - ✅ Engine-named lock findings, both-direction deploy-order trace, data-preserving rollback verdicts, assumptions stated, blockers with one-sentence failure scenarios.
54
+
55
+ ## Notes
56
+
57
+ - Tool wrappers matter: some runners wrap every migration in a transaction (breaking `CREATE INDEX CONCURRENTLY`), some apply timeouts, some (SQL Server) do transactional DDL. Find the runner's config in the repo before asserting behavior — a claim about the wrapper you haven't opened is a hypothesis.
58
+ - Recommend `lock_timeout`/`statement_timeout` guards (Postgres) on DDL touching hot tables where the repo's runner supports it — a migration that fails fast beats one that queues behind a long transaction and blocks everything behind *it*.
59
+ - Apply `think-like-fable`: the risk lives in the destructive ops and the deploy overlap, so they get the effort; every lock/rollback claim is re-derived from the engine + the actual file; the report leads with the one migration that must not run as-is.
@@ -60,6 +60,17 @@ Versions are resolved at scaffold time, never hard-pasted into the skill. Before
60
60
  3. Quote the resolved versions back to the user before generating, so they can object.
61
61
  4. Prefer the latest **stable** Next.js (App Router GA from 13.4; resolve current).
62
62
  5. Default to **pnpm** if `pnpm` is present, otherwise **npm**. Match `packageManager` in `package.json`.
63
+ 6. Concrete resolution command when context7 is unavailable: `npm view <pkg> version` (e.g. `npm view next version`; `npm view next-auth dist-tags` — plain `npm view next-auth version` returns the latest *stable*, which may still be v4; never scaffold v4). Never write a version you did not just resolve this session — no versions from memory, no `latest`/`^latest`, no invented numbers.
64
+ 7. **API-drift guard.** The version you resolved in this step decides the API shape — training-data memory is the least trustworthy source in this workflow. Highest-risk hallucination zones:
65
+ - NextAuth **v4 patterns are POISON here**: `authOptions` export, `getServerSession`, `NEXTAUTH_*` env vars, an options object in the `[...nextauth]` route. v5 is `const { handlers, auth, signIn, signOut } = NextAuth(config)` in `src/auth.ts`, with `AUTH_*` env vars.
66
+ - App Router signatures that changed across majors — e.g. in Next 15 `params`/`searchParams` are **Promises** in dynamic route segments and Route Handler contexts; check the resolved major before writing `params.id`. In `'use client'` pages read route params with `useParams()` from `next/navigation`, not the `params` prop.
67
+ - RTK Query / Redux Toolkit option names (`fetchBaseQuery` options, `providesTags`/`invalidatesTags` shapes) and the Prisma client API for the resolved major.
68
+ - Tailwind majors: v4 dropped the `tailwind.config.ts`-first setup for CSS-first config (`@import "tailwindcss"` + `@tailwindcss/postcss`); v3 uses `tailwind.config.ts` + `postcss.config.js`. Emit exactly one style — the one matching the resolved major — never a mix.
69
+ - Zod majors: v4 moved/renamed APIs (top-level `z.email()`, reworked error customization). A v3-only signature against a resolved v4 is a hallucination — check the installed types under `node_modules/zod`.
70
+ - Prisma majors: the `generator` block shape and default client output location changed across majors. After `prisma generate`, confirm the actual import path from what was generated — don't assume `@prisma/client` from memory.
71
+ - The shadcn CLI is `npx shadcn@latest` (`init` / `add`); the old `shadcn-ui` package name is dead.
72
+
73
+ If you are not certain a symbol exists in the resolved version, verify it (read the installed types under `node_modules/<pkg>/`, or check docs) **before** writing code that depends on it.
63
74
 
64
75
  ### Step 2 — Gather inputs (ask once, in one batch)
65
76
 
@@ -85,6 +96,7 @@ Use the **templates in [`references/templates/`](references/templates/)** as the
85
96
  - Create directories before files. On Windows shell use PowerShell `New-Item -ItemType Directory -Force`.
86
97
  - Do **not** run `create-next-app` to bootstrap — write files directly from templates so the layout matches [`references/folder-layout.md`](references/folder-layout.md). Use `npm`/`pnpm install` after `package.json` is written.
87
98
  - Initialize shadcn/ui by writing `components.json` from [`references/templates/components-json.md`](references/templates/components-json.md) and `src/components/ui/` components incrementally as needed (button, input, form, label, dialog, toast, etc.) — don't blanket-install every Radix primitive.
99
+ - If a generator IS run (`shadcn` CLI, `prisma init`, or `create-next-app` at the user's insistence) and its output differs from this skill's layout: **the framework wins on file locations it mandates** (`middleware.ts` placement, `app/` conventions, `prisma/schema.prisma`), **this skill wins on everything the framework doesn't mandate** (redux layout, route groups, `_components`/`_hooks`). Reconcile deliberately and list every deviation in your report — never force the skill layout over a framework requirement, never silently abandon the skill's patterns.
88
100
 
89
101
  ### Step 4 — Database setup
90
102
 
@@ -97,6 +109,7 @@ After `package.json` and `prisma/schema.prisma` are written:
97
109
  - **Never run `prisma db push` against a production-shaped database.** Use migrations.
98
110
  - **Never run `prisma migrate reset` without explicit user confirmation** — it drops the DB.
99
111
  5. Generate `AUTH_SECRET` for the user: `pnpm exec auth secret` (Auth.js CLI) or `openssl rand -base64 32`. Put it in `.env.local` (NOT `.env.example`).
112
+ 6. Order is load-bearing: `prisma generate` MUST run before `pnpm typecheck` / `pnpm build` — the `@prisma/client` types don't exist until generated. If typecheck explodes with missing Prisma types, you skipped or mis-ordered this step; run generate, don't start rewriting imports.
100
113
 
101
114
  ### Step 5 — Verify and report
102
115
 
@@ -104,6 +117,8 @@ After `package.json` and `prisma/schema.prisma` are written:
104
117
  - `pnpm lint`. Must exit 0.
105
118
  - `pnpm test` (Vitest) if any tests were generated. Must exit 0.
106
119
  - `pnpm build`. Must succeed.
120
+ - **A scaffold that hasn't built is not delivered.** Paste the actual proof lines in your report (Next's `✓ Compiled successfully`, Vitest's `Tests N passed`). Never report success from memory of the steps you intended, and never report partial success as success — if something is red, say exactly what is red.
121
+ - **CLI failure protocol** (applies to `pnpm install`, `prisma migrate`, `pnpm build`, etc.): read the full error output, change exactly one thing, retry once. If the same step fails twice, stop scaffolding and surface the verbatim error to the user — do not keep generating files on a broken base, and do not re-run the identical command hoping for a different result.
107
122
  - Reply with a short summary: project path, versions chosen, route groups, env vars to set (especially `AUTH_SECRET` and `DATABASE_URL`), next steps (`pnpm dev`, log in with the seeded test user if Credentials was chosen).
108
123
 
109
124
  ## Project layout (canonical)
@@ -302,7 +317,7 @@ For feature `{{Feature}}` in route group `{{Group}}` (e.g. `(app)`):
302
317
  - `_components/{{Feature}}Form.tsx` — `'use client'`, uses `useForm` + `zodResolver`, wraps `<UnsavedChangesWarning>`, dispatches create/update mutation on submit.
303
318
  7. **Tests**: `tests/unit/{{feature}}.schema.test.ts` (Zod schema valid + invalid). Optional handler test that mocks `db` and `auth`.
304
319
 
305
- After generating: `pnpm typecheck && pnpm test && pnpm build`.
320
+ After generating: `pnpm typecheck && pnpm test && pnpm build`. Apply the Step-5 proof-and-failure protocol — paste the green lines; the same step failing twice = stop and surface the verbatim error.
306
321
 
307
322
  ### Mode 3 — `add-api-slice`
308
323
 
@@ -315,10 +330,27 @@ For domain `{{domain}}` (e.g. `customers`):
315
330
  5. Add the Prisma model(s) and migrate if needed.
316
331
  6. **Do not** create a new `createApi(...)`. One base `api`, many injected slices.
317
332
 
318
- After generating: `pnpm typecheck && pnpm build` must pass.
333
+ After generating: `pnpm typecheck && pnpm build` must pass — Step-5 proof-and-failure protocol applies.
319
334
 
320
335
  ## Verification checklist before reporting "done"
321
336
 
337
+ Run this as a **mechanical pass over the generated tree** — grep, don't recall. The forbidden list is easy to hold at file 1 and forgotten by file 30; do not answer any item from memory of what you intended to write. Start with this grep block — every command must return nothing:
338
+
339
+ ```bash
340
+ grep -rn "'use server'" src/ # Server Actions
341
+ grep -rn "export default async function" src/app --include='page.tsx' # async pages
342
+ grep -rn "from '@/lib/db'" src/app --include='*.tsx' # DB access outside route.ts
343
+ grep -rn "fetch(" src/app src/components --include='*.tsx' # inline fetch in components
344
+ grep -rn "serializableCheck: false\|@ts-ignore\|as any" src/redux src/app/api
345
+ grep -rn "moment" src/ package.json # date-fns only
346
+ grep -rn "NEXTAUTH_\|getServerSession\|authOptions\|next-auth/next" src/ .env.example # NextAuth v4 leaking in
347
+ grep -rn "styled-components\|@emotion" src/ package.json # CSS-in-JS in a Tailwind project
348
+ grep -rn "localStorage\|sessionStorage" src/ # any token/session hit is a bug
349
+ grep -rn "dangerouslySetInnerHTML" src/ # zero on a fresh scaffold
350
+ ```
351
+
352
+ A hit means fix it and re-run the grep — never rationalize it away. Then the full checklist:
353
+
322
354
  - [ ] `pnpm install` succeeds.
323
355
  - [ ] `pnpm exec prisma generate` succeeds.
324
356
  - [ ] `pnpm exec prisma migrate dev --name init` succeeds (or the user confirmed they ran it manually).
@@ -330,7 +362,8 @@ After generating: `pnpm typecheck && pnpm build` must pass.
330
362
  - [ ] Every `route.ts` file in `src/app/api/**` (except `[...nextauth]`) calls `await auth()` or `await requireSession()`.
331
363
  - [ ] No `'use server'` directive anywhere. Grep: `grep -rn "'use server'" src/` returns nothing.
332
364
  - [ ] No `async function .*Page` in any `page.tsx`. Pages are sync `'use client'`.
333
- - [ ] No `serializableCheck: false`, `@ts-ignore`, `as any` in `src/redux/**` or `src/app/api/**`.
365
+ - [ ] No `serializableCheck: false`, `@ts-ignore`, `as any` in `src/redux/**` or `src/app/api/**` (covered by the grep block above).
366
+ - [ ] Exactly one `createApi(` in the codebase (the base `api`). Grep: `grep -rn "createApi(" src/` shows one hit.
334
367
  - [ ] `middleware.ts` exists at project root; its `config.matcher` excludes `/api/auth` (NextAuth handles its own routes).
335
368
  - [ ] `src/auth.ts` and `src/auth.config.ts` both exist. `auth.config.ts` has no `@/lib/db` import (Edge-safe).
336
369
  - [ ] `.env` is **not** committed; `.env.example` is. `AUTH_SECRET` is **not** in `.env.example` (it's documented as required, with instructions to generate it).
@@ -32,6 +32,9 @@ If the feature is large, fuzzy, or still in discovery — defer to [`grill-with-
32
32
  Rules:
33
33
 
34
34
  - **One question at a time.** Wait for the answer before the next one. Use `AskUserQuestion` with 2–4 concrete options and a recommended pick first.
35
+ - **A question earns its slot only if different answers lead to different designs.** Before asking, name (to yourself) what changes per answer. If every answer leads to the same next step, don't ask it — generic checklist questions burn the user's patience without buying design information.
36
+ - ❌ "Should this endpoint be secure and handle errors gracefully?" — every answer is "yes"; nothing about the design forks.
37
+ - ✅ "When an order loses priority, do we keep the history of who set it and when?" — "yes" needs an audit table, "no" is two columns. The answer picks the schema.
35
38
  - **Explore the codebase instead of asking** when the answer is already there. If a similar feature, entity, controller, or migration exists, read it before asking the user how a related thing should work.
36
39
  - **Sharpen fuzzy language.** When the user says "account", ask: "Customer or User? Those are different things here." When they say "cancel", ask whether that means soft-delete, hard-delete, or a status transition.
37
40
  - **Probe with concrete scenarios.** Invent edge cases that force the user to be precise. "What happens if the user submits twice while the first request is still in flight?"
@@ -114,7 +117,8 @@ Whether you drafted one plan or chose between two, the presented plan must inclu
114
117
  - Whether a backfill is required and how it's gated.
115
118
  - **Always end with:** "Migration file will be generated. No SQL, no `dotnet ef database update`, no `ExecuteSql*` calls will be run — the user runs the migration themselves."
116
119
  6. **Pattern reuse table.** Two columns: "New code I'm about to write" and "Existing example it follows" with a path. If a row has no existing example, justify the new pattern.
117
- 7. **Open questions** still unresolved, if any. If there are any, loop back to Phase 1do not exit plan mode with open questions.
120
+ 7. **Validation signal per step.** Every build step names the observable signal that proves it done at plan time — a named test going green, `dotnet build` exiting 0, a `curl` returning the expected body. A step whose completion can't be observed isn't a step, it's a hope split it or attach a signal before presenting.
121
+ 8. **Open questions** still unresolved, if any. If there are any, loop back to Phase 1 — do not exit plan mode with open questions.
118
122
 
119
123
  If Design It Twice ran, end the plan with: **"Considered alternative: `<approach B name>`. Rejected because `<one-line reason>`."** This lets the user redirect to B with a single sentence if they disagree.
120
124
 
@@ -126,6 +130,8 @@ Enter Plan Mode (`EnterPlanMode`) to present. Then exit with `ExitPlanMode` and
126
130
 
127
131
  Build in the order that gives you the fastest feedback loop.
128
132
 
133
+ **The plan is a hypothesis about the codebase.** When execution contradicts it — the pattern Phase 2 found doesn't cover this case, the library API has a different shape, the file the plan modifies doesn't exist — stop. Do not improvise silently and keep typing. Re-plan the remaining steps against what you just learned, tell the user what deviated and why, and re-present via plan mode if the change is material (different files, different schema, a new dependency). A small deviation (a helper's actual name, a slightly different signature) just gets a one-line note in the Phase 6 report.
134
+
129
135
  **If the feature adds or changes an API endpoint, follow TDD:**
130
136
 
131
137
  1. **Locate the test class.** For each API change, search for an existing test class that already covers that controller / service / use-case. Common patterns:
@@ -153,6 +159,7 @@ Build in the order that gives you the fastest feedback loop.
153
159
  - **Minimal comments.** Default to no comments. Only add one when the *why* is non-obvious (a workaround, a non-trivial invariant, a domain rule that isn't visible from the names). Never write block headers, never restate what the next line does, never leave `// TODO` without an issue link.
154
160
  - **Match formatting.** Use the project's brace style, naming, file headers, `using` placement. Don't reformat unrelated code.
155
161
  - **No dead code.** Don't commit commented-out lines or "for future use" stubs.
162
+ - **Build to the plan, nothing more.** Done means the approved plan's acceptance criteria. No unrequested config options, no defensive layers the plan didn't ask for, no speculative extension points. Improvements you notice mid-build are findings for the Phase 6 report, not work to do.
156
163
 
157
164
  ### Phase 5 — Migrations (generate only, never execute)
158
165
 
@@ -207,7 +214,10 @@ When appending:
207
214
  ## Anti-patterns
208
215
 
209
216
  - ❌ Skipping Phase 1 and going straight to "let me read the code and start building" — the whole point of this skill is the grill.
217
+ - ❌ Asking grill questions whose answers all lead to the same design. Every question must fork the design.
210
218
  - ❌ Exiting plan mode while open questions remain. Loop back instead.
219
+ - ❌ Improvising silently when the codebase contradicts the plan. Stop, re-plan the remainder, tell the user what deviated.
220
+ - ❌ Gold-plating the build: config options, abstraction layers, or "robustness" the plan never called for.
211
221
  - ❌ Writing production code before the test it makes pass (when the feature touches an API).
212
222
  - ❌ Creating `FooServiceTests2.cs` because `FooServiceTests.cs` already exists. Always append.
213
223
  - ❌ Inventing a new pattern for something the codebase already has a pattern for. If you can't find the precedent, ask before forking.
@@ -54,6 +54,26 @@ Categorize each comment so the author knows what's required vs optional:
54
54
 
55
55
  Lead with the *why*, not just the *what*. "This will deadlock if two callers hit it simultaneously" is more useful than "use a lock here".
56
56
 
57
+ Same finding, badly and well:
58
+
59
+ - ❌ `blocking: add error handling to fetchUser (api/user.ts:42)` — no failure scenario, no consequence; unverifiable reflex.
60
+ - ✅ `blocking: fetchUser (api/user.ts:42) rejects on 404 but ProfilePage (pages/profile.tsx:18) never catches — a deleted user's profile renders blank with an unhandled rejection. Fix: catch and render not-found.`
61
+
62
+ ## Evidence rules
63
+
64
+ A claim about code you haven't opened this session is a hypothesis — verify it or label it as one.
65
+
66
+ - **Read beyond the hunk.** Before flagging anything, `Read` the full enclosing function (the whole file when small). Most false "missing null check" / "unhandled error" findings dissolve when you see the guard 10 lines above the hunk or the caller that validates. A finding based only on diff-hunk context is not reportable.
67
+ - **`blocking` carries a burden of proof:** one concrete failure-scenario sentence (*this input/state → this wrong outcome*). Can't write it? Demote to `suggestion`, or raise it as a `question`.
68
+ - **Grep before claiming absence.** "Dead code" / "unused" / "never called" / "duplicated elsewhere" require a repo-wide `Grep` for the identifier first — cite the search in the finding.
69
+ - **Quote verbatim.** Error messages, test output, and load-bearing identifiers go into findings exactly as they appear; paraphrase loses the token that matters.
70
+ - **Zero findings is a valid verdict.** A task with nothing wrong gets a clean *Approve* — never pad Blockers or Suggestions to look thorough. Thoroughness is what you checked, not what you wrote.
71
+ - **User framing is input, not conclusion.** "The backend part is fine, just look at the UI" does not exempt the backend — review every task fully.
72
+
73
+ ## Scope discipline
74
+
75
+ A finding must belong to the task (`#NNN`) whose diff introduced it. Pre-existing code that's merely *visible* in the diff context is out of scope — unless the task made it worse (e.g. added a second caller to an already-unsafe function; that's in scope). If you notice a pre-existing issue worth mentioning, note it in one line outside the verdict ("pre-existing, not introduced by #123") — never block a task for code its commits didn't touch.
76
+
57
77
  ## What to look for
58
78
 
59
79
  ### Correctness
@@ -117,7 +137,7 @@ Performance + Readability usually fold into Design — only spawn a dedicated le
117
137
  - The per-task diff (commits from this `#NNN` only — not the whole branch).
118
138
  - The inferred task intent from step 4.
119
139
  - **One** lens with its checklist verbatim from [What to look for](#what-to-look-for).
120
- - Instructions: "Find issues only in your lens. Categorize each as `blocking` / `suggestion` / `question` / `nit`. Cite `file:line` for every finding. Lead each with the *why*. If no findings, say 'no findings' explicitly. Report in ≤500 words."
140
+ - Instructions: "Find issues only in your lens. Read the full enclosing function before flagging — hunk-only findings are not reportable. Only flag code this task's commits introduced or worsened; pre-existing issues are out of scope. Categorize each as `blocking` / `suggestion` / `question` / `nit`; a `blocking` must state a one-sentence concrete failure scenario. Cite `file:line` for every finding. Lead each with the *why*. If no findings, say 'no findings' explicitly — do not pad. Report in ≤500 words."
121
141
  2. **Critique round.** Read all lenses side by side, then:
122
142
  - **Challenge every `blocking`** against context from the other lenses. Demote to `suggestion` or drop if it's defused by another lens's evidence.
123
143
  - **Surface contradictions** as `question` items the user adjudicates (e.g. Correctness flags missing null guard; Security found the caller validates).
@@ -170,6 +190,10 @@ End with a one-line roll-up: e.g. `Overall: 2 approve, 1 request changes, 1 unsc
170
190
  - ❌ Reflexively requesting tests without saying what they should cover
171
191
  - ❌ Drive-by style nits that derail the substantive discussion
172
192
  - ❌ "I would have done it differently" without a concrete reason
193
+ - ❌ Flagging from the diff hunk alone — read the enclosing function first; the guard is often just outside the hunk
194
+ - ❌ Blocking a task for a pre-existing issue its diff didn't introduce or worsen — note it in one line, out of verdict
195
+ - ❌ A `blocking` with no concrete failure scenario — if you can't write "this input → this wrong outcome", it isn't blocking
196
+ - ❌ Padding a clean task with speculative findings — zero findings is a valid Approve
173
197
  - ❌ Collapsing multiple tasks into one verdict — each `#NNN` is independent and should stand or fall on its own
174
198
  - ❌ Silently ignoring commits with no task reference — surface them in the `unscoped` bucket
175
199
  - ❌ Convening the lens council on a 20-line task. Single-pass it.
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: safe-refactor
3
+ description: Execute a behavior-preserving refactor with a proof of preservation — establishes a safety net first (existing tests or new characterization tests over every touched path), locks a baseline green run, then moves in small always-green steps where each step is one mechanical transformation, and treats any needed assertion change as a smuggled behavior change to surface, not fix. Use this skill whenever the user says "refactor this", "clean this up without changing behavior", "extract this into", "restructure this module", "rename this across the codebase", "inline this", "split this function/class", or "/safe-refactor" — even if they don't explicitly say "refactoring skill". Do not use for choosing WHAT to refactor (use improve-codebase-architecture) or for changes that are supposed to alter behavior (use plan-and-build).
4
+ ---
5
+
6
+ # Safe Refactor
7
+
8
+ Execute a refactor as a sequence of proofs, not a rewrite. "Behavior-preserving" is a falsifiable claim: the observable behavior before and after must be identical, and this skill's job is to make that claim checkable at every step instead of asserting it once at the end. The completion of the chain: `improve-codebase-architecture` names the target, `write-tests` builds the net, this skill makes the move.
9
+
10
+ ## When to use this skill
11
+
12
+ - The user says "refactor this", "clean this up", "extract X into Y", "restructure without changing behavior", "rename across the codebase", "split this up", "inline this", "/safe-refactor".
13
+ - `improve-codebase-architecture` produced an approved deepening candidate and the user says "do it".
14
+ - A feature task requires restructuring existing code before the new behavior can land (do the refactor as its own phase, this skill governs that phase only).
15
+
16
+ Do **not** auto-trigger for changes meant to alter behavior — bug fixes, feature work, "improve this error message" — those are `plan-and-build` / `task-executor` territory. If the user's "refactor" turns out to include behavior changes, split the work: refactor first under this skill, behavior change after, never interleaved.
17
+
18
+ ## Workflow
19
+
20
+ 1. **Write the behavior contract.** One short list: the observable surface of the code being moved — public functions/endpoints and their outputs, side effects (writes, emits, logs that anything consumes), error types thrown, and performance characteristics if anything depends on them. This list is what "preserved" means; everything not on it is fair game. If you can't write the list, you haven't read enough code yet — a claim about code you haven't opened is a hypothesis.
21
+ 2. **Audit the safety net.** For each item on the contract, find the test that would fail if it broke — grep the test tree, cite `file:line`. Contract items with no covering test get characterization tests **before any restructuring begins** (per `write-tests`: pin actual behavior, prove each test can go red). No net, no refactor — this gate is not skippable, and "the change is simple" is not an exemption; simple changes with no net are how behavior drifts silently.
22
+ 3. **Lock the baseline.** Run the full suite (single-run mode, never watch mode) and build; quote both summary lines. A result you didn't observe is "not run", never "passed". If anything is red before you start, stop and surface it — you cannot distinguish your breakage from pre-existing breakage on a red baseline.
23
+ 4. **Plan the move as mechanical steps.** Each step is ONE named transformation — extract function, move file, rename symbol, inline variable, introduce parameter, replace conditional with polymorphism — small enough that if the suite goes red, the cause is unambiguous. Order steps so each leaves the code compiling and the suite green. Present the step list before executing.
24
+ 5. **Execute step → verify → step.** After every step: build + run the affected tests (full suite at least at every commit point), quote the result. Green → proceed. Red → the step is wrong; revert or fix *the step*, never the test. Each step (or small coherent group) is a rollback point — ask once, up front, whether to commit at these points; committing without that yes is not authorized by this skill.
25
+ 6. **Renames are grep-verified, not assumed.** After renaming or moving a symbol, grep for the old name repo-wide — including strings, configs, reflection sites, docs, and serialized names (JSON keys, DB column mappings, API routes). Zero hits or each remaining hit justified in one line. Dynamic-dispatch and reflection usages don't break the build; the grep is the only net they have.
26
+ - ❌ "Renamed `getUser` to `fetchUser`; build is green, done." — the build can't see the `"getUser"` string in the route table.
27
+ - ✅ "Renamed; repo grep for `getUser` returns 2 hits: a CHANGELOG entry (historical, left) and `routes.json:14` (updated)."
28
+ 7. **The assertion-change tripwire.** If any step makes an existing test fail and the "fix" would be changing that test's assertion, expected value, or setup — **stop**. That is the definition of a behavior change wearing a refactor's clothes. Surface it: quote the old and new behavior, ask whether the change is intended. Only test changes allowed without asking: mechanical renames mirroring step 6, and import/path updates.
29
+ - ❌ Silently updating `expect(total).toBe(107.10)` to `109.31` mid-refactor because "the new rounding is more correct."
30
+ - ✅ "Step 4 changes the rounding point, which alters totals on multi-line invoices (old: 107.10, new: 109.31). That's a behavior change, not a refactor — proceed with old behavior preserved, or split this into a follow-up?"
31
+ 8. **Final proof.** Full suite + build, summaries quoted. Then re-read the complete diff once, adversarially, hunting for the behavior change you smuggled in without noticing — reordered side effects, a dropped `await`, a widened catch, changed default parameter, iteration-order dependence, log lines something might parse. Report: contract (each item → covering test), steps taken, grep results for renames, and anything intentionally left un-refactored with a one-line why.
32
+
33
+ ## Examples
34
+
35
+ ### Example 1: extract a module
36
+
37
+ **User:** "Pull the pricing logic out of `OrderService` into its own `PricingEngine`."
38
+
39
+ **Claude:** Writes the contract (3 public methods, the discount side effect, 2 error types), finds tests covering 2 of 3 methods, writes characterization tests for the third and proves them red-capable, locks baseline (`112 passed`, build green), then: extract class → move methods one at a time → re-point callers → delete originals, suite quoted green after each, old-name grep clean, final adversarial diff read confirms no reordered side effects.
40
+
41
+ ### Example 2: the smuggled behavior change
42
+
43
+ **User:** "Refactor this date-handling mess to use one formatter."
44
+
45
+ **Claude:** Mid-sequence, unifying formatters makes a test expecting `"3/5/2026"` fail because the shared formatter emits `"03/05/2026"`. Stops at the tripwire, shows both outputs, asks: preserve the legacy format via a format parameter (pure refactor), or adopt the new format (behavior change, separate commit, may affect consumers)?
46
+
47
+ ## Anti-patterns
48
+
49
+ - ❌ Refactoring untested code because "it's a small change" — the net gate (step 2) exists precisely for the changes that feel too small to break anything.
50
+ - ❌ The big-bang rewrite: restructure everything, then see what's red. When five things break you can't attribute any of them.
51
+ - ❌ Fixing a red step by editing the test — the tripwire (step 7) exists because this is the single most common way behavior changes ship inside "refactors".
52
+ - ❌ Interleaving "while I'm in here" behavior improvements with the restructuring — one diff, two intents, zero reviewability. Queue them for after.
53
+ - ❌ Trusting the compiler to catch all breakage from a rename — strings, reflection, routes, and serialization don't compile-check.
54
+ - ❌ Reporting the refactor complete without quoting the final suite run — "not run" is the only honest label for an unobserved result.
55
+ - ❌ Refactoring toward a pattern the repo doesn't use because it's considered best practice — match the codebase's existing idiom; consistency is a feature.
56
+ - ✅ Contract → net → baseline → small proven steps → grep-verified renames → adversarial final read.
57
+
58
+ ## Notes
59
+
60
+ - Commit hygiene: keep refactor commits free of behavior changes so `git bisect` stays useful and reviewers can verify "no behavior change" structurally. Use `conventional-commits` (`refactor:` type) when committing.
61
+ - If the safety-net phase reveals the code is nearly untestable, that finding may reorder the plan — sometimes the first refactor step must be the minimal seam that makes testing possible (extract the untestable I/O, pin the rest). Say so rather than silently expanding scope.
62
+ - Apply `think-like-fable` throughout: the contract is the decomposition into independently checkable pieces, every green claim is re-derived by running (never recognized), and the final adversarial read is attacking your own conclusion before handing it over.