@epicparty/skills 0.0.0-stage → 0.1.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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +27 -0
  3. package/README.md +109 -2
  4. package/bin/cli.mjs +175 -0
  5. package/lib/transform.mjs +185 -0
  6. package/package.json +37 -4
  7. package/skills/architect/DESIGN-IT-TWICE.md +46 -0
  8. package/skills/architect/HTML-REPORT.md +126 -0
  9. package/skills/architect/SKILL.md +101 -0
  10. package/skills/babysit/SKILL.md +164 -0
  11. package/skills/code-review/SKILL.md +111 -0
  12. package/skills/copy/COPY.md +142 -0
  13. package/skills/copy/SKILL.md +87 -0
  14. package/skills/deliver/SKILL.md +259 -0
  15. package/skills/diagnose/SKILL.md +114 -0
  16. package/skills/diagnose/scripts/hitl-loop.template.sh +44 -0
  17. package/skills/explore/SKILL.md +78 -0
  18. package/skills/grill/SKILL.md +75 -0
  19. package/skills/implement/SKILL.md +132 -0
  20. package/skills/implement/TDD.md +131 -0
  21. package/skills/implement/TEST-VALUE.md +114 -0
  22. package/skills/outdated/SKILL.md +125 -0
  23. package/skills/research/SKILL.md +115 -0
  24. package/skills/router/BOARD.md +157 -0
  25. package/skills/router/DECISIONS.md +71 -0
  26. package/skills/router/GIT.md +70 -0
  27. package/skills/router/GLOSSARY-FORMAT.md +48 -0
  28. package/skills/router/SKILL.md +251 -0
  29. package/skills/setup/SKILL.md +99 -0
  30. package/skills/ship/SKILL.md +132 -0
  31. package/skills/to-pr/SKILL.md +88 -0
  32. package/skills/to-story/SEAMS.md +51 -0
  33. package/skills/to-story/SKILL.md +79 -0
  34. package/skills/to-story/SPLITTING.md +53 -0
  35. package/skills/to-story/STORIES.md +179 -0
  36. package/skills/triage/SKILL.md +179 -0
  37. package/skills/upgrade/SKILL.md +139 -0
  38. package/skills/writing-for-agents/SKILL-MECHANICS.md +24 -0
  39. package/skills/writing-for-agents/SKILL.md +88 -0
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bash
2
+ # Human-in-the-loop reproduction loop.
3
+ # Copy this file, edit the steps below, and run it.
4
+ # The agent runs the script; the user follows prompts in their terminal.
5
+ #
6
+ # Usage:
7
+ # bash hitl-loop.template.sh
8
+ #
9
+ # Two helpers:
10
+ # step "<instruction>" → show instruction, wait for Enter
11
+ # capture VAR "<question>" → show question, read response into VAR
12
+ #
13
+ # At the end, captured values are printed as KEY=VALUE for the agent to parse.
14
+ #
15
+ # `capture` prints its value back to the terminal, where the agent reads it,
16
+ # so capture observations, and leave signing in to the user as a `step`.
17
+
18
+ set -euo pipefail
19
+
20
+ step() {
21
+ printf '\n>>> %s\n' "$1"
22
+ read -r -p " [Enter when done] " _
23
+ }
24
+
25
+ capture() {
26
+ local var="$1" question="$2" answer
27
+ printf '\n>>> %s\n' "$question"
28
+ read -r -p " > " answer
29
+ printf -v "$var" '%s' "$answer"
30
+ }
31
+
32
+ # --- edit below ---------------------------------------------------------
33
+
34
+ step "Open the app at http://localhost:3000 and sign in."
35
+
36
+ capture ERRORED "Click the 'Export' button. Did it throw an error? (y/n)"
37
+
38
+ capture ERROR_MSG "Paste the error message (or 'none'):"
39
+
40
+ # --- edit above ---------------------------------------------------------
41
+
42
+ printf '\n--- Captured ---\n'
43
+ printf 'ERRORED=%s\n' "$ERRORED"
44
+ printf 'ERROR_MSG=%s\n' "$ERROR_MSG"
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: explore
3
+ description: Maps what a piece of work involves before anything is decided — provenance, seams, prior art, open questions. Use at the start of unfamiliar work.
4
+ metadata:
5
+ pack: epicparty/skills
6
+ version: 0.1.0
7
+ ---
8
+
9
+ # Explore
10
+
11
+ The first move on a piece of work. Explore ends in a **brief**: what already exists, where the change lands, and what nobody has decided yet.
12
+
13
+ It answers questions. The decisions stay with the user, and the code stays untouched — a brief is what [`/ep`](../router/SKILL.md) reads to choose a route, and what any question put to the user is built on.
14
+
15
+ ## 1. Restate the request
16
+
17
+ One or two sentences, in the project's own words — the domain terms from its glossary, where it keeps one — `/ep`'s [`DECISIONS.md`](../router/DECISIONS.md) says where.
18
+
19
+ A request you cannot restate is a request you have not understood, and every step below would explore the wrong thing.
20
+
21
+ Done when the restatement uses the domain's vocabulary and the user would recognise their own ask in it.
22
+
23
+ ## 2. Find the provenance
24
+
25
+ Only when the user named no issue. Search the tracker for one that already describes this work, with the request's own nouns. The tracker is the **Tracker** setting when the settings home has one — read it, never ask for it — or GitHub when `gh repo view` finds the repo: `gh issue list --search "<keywords>" --state all`, then `gh issue view <n> --comments` on a hit. With neither, say no tracker was searched. A hit describing the same work is the provenance; an adjacent one is context, never provenance.
26
+
27
+ A hit is worth more than anything the codebase will tell you: it carries decisions already made, and its comments carry the ones that never reached the body. A closed hit is worth as much — the work may already be done.
28
+
29
+ Done when you have the issue, or have searched and found none, or have said no tracker was searched.
30
+
31
+ ## 3. Locate the work
32
+
33
+ Name where the change lands, concretely:
34
+
35
+ - **The scope** — from the **Scopes** setting, when the settings home has one; read it, never ask for it. A scope that is not in the vocabulary means the work is scoped wrong, or the vocabulary needs a finding raised against it. With no setting, skip this line.
36
+ - **Packages and apps** touched, and the **nearest `AGENTS.md` or `CLAUDE.md`** to each.
37
+ - **The standards that govern it** — the documents and skills those agent files route the touched paths to, rather than a guess at which files apply.
38
+ - **The validation command** the work will have to pass, as the agent files name it — or, without agent files, the `package.json` scripts.
39
+
40
+ Done when every path the work will touch has its scope, its nearest agent file, and its routed standards named.
41
+
42
+ ## 4. Read the prior art
43
+
44
+ Find the code that already does the nearest thing to this, and read it. Then name:
45
+
46
+ - **The seams** the change can be tested at, existing ones first. The fewest seams is the best number, and the highest seam is the best one. A new seam is a decision, so it goes on the unsettled list rather than into the brief as a fact.
47
+ - **The pattern to match** — the neighbouring implementation this work should look like, named by path.
48
+ - **The tests that cover the area today**, and at which level — named by the tool that runs them.
49
+
50
+ Reading three neighbours is cheaper than inventing one pattern, and this is the step that stops the work from arriving as a stranger in its own codebase.
51
+
52
+ Done when you can point at the file this work should resemble, and at the tests it will sit beside.
53
+
54
+ ## 5. List what is unsettled
55
+
56
+ The decisions this work turns on that nobody has made. Each one gets a sentence and, where you have one, a recommendation with its reason.
57
+
58
+ Separate them from the facts. A fact you found in the codebase is yours to report; an unsettled question is the user's to answer, and presenting a guess as a finding is how the wrong feature gets built confidently.
59
+
60
+ Done when every question the exploration raised is on the list, and nothing on the list is something you could have looked up.
61
+
62
+ ## 6. Report the brief
63
+
64
+ Short and scannable:
65
+
66
+ - The restated request, and the issue it came from or that it has none
67
+ - Where it lands — product, area, packages, standards
68
+ - The seam, the prior art, the test level
69
+ - **What is unsettled** — the list, as questions to the user
70
+ - **The route you would take**, per `/ep`'s table, and the property of the request that chose it — left out when another skill ran this one and the route is already set
71
+
72
+ Done when the user can answer the unsettled list without opening a file.
73
+
74
+ ## Guardrails
75
+
76
+ - **Explore reads.** Edits, commits and issues are the later stages' work; this one leaves the tree exactly as it found it.
77
+ - **Depth follows stakes.** A one-line copy change earns a two-minute look. A change across a critical path earns every step above in full. Say which you did.
78
+ - **An empty unsettled list is a real result.** Some work genuinely has nothing left to decide, and saying so is what sends `/ep` straight to `/ep-to-story`. Manufacturing a question to look thorough costs the user an answer they did not need to give.
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: grill
3
+ description: Grills the user on a plan, decision or idea in rounds of questions until nothing is left assumed, writing each term and decision it settles into the glossary or an ADR. Use when the user asks to be grilled or to have an idea stress-tested, when decisions turn on each other so no list of questions can be written up front, or to pin down a domain term or record an ADR.
4
+ metadata:
5
+ pack: epicparty/skills
6
+ version: 0.1.0
7
+ ---
8
+
9
+ # Grill
10
+
11
+ <!-- upstream: mattpocock/skills skills/productivity/grilling/SKILL.md -->
12
+
13
+ Interview the user **relentlessly** until you reach a shared understanding. Map the work as a **design tree**: every decision branches into the decisions that hang off it.
14
+
15
+ The decisions are the user's. Finding the facts is yours.
16
+
17
+ ## 1. Gather the facts first
18
+
19
+ Read what already holds the work: the `/ep-explore` brief, the issue and its comments, the triage note's seed questions — whichever this session has. Read the project's glossary and the ADRs in the area, where `/ep`'s [`DECISIONS.md`](../router/DECISIONS.md) says they live.
20
+
21
+ Without a brief, on code you have not read this session, run [`/ep-explore`](../explore/SKILL.md) first: it also searches the tracker for an issue that already holds the work.
22
+
23
+ A question the code, an ADR or a shipped story already answers is a fact, not a decision: settle it yourself and cite where. A fact no tool here can reach — the state of production data, what a person knows — goes to the user as a fact question, marked as one.
24
+
25
+ Done when every decision on the tree is either answered by a cited fact or waiting on the user.
26
+
27
+ ## 2. Ask in rounds
28
+
29
+ The **frontier** is every decision whose prerequisites are settled — the questions you can ask _now_ without guessing at answers you have not heard. Ask the whole frontier in one round, numbered, each with your recommended answer, then wait.
30
+
31
+ ```
32
+ ❓ **Q1** - **<question title>**: <the question, with the context and the choices it needs>
33
+
34
+ ➡️ <your recommended answer>
35
+
36
+ ---
37
+
38
+ ❓ **Q2** - **<question title>**: <…>
39
+
40
+ ➡️ <your recommended answer>
41
+ ```
42
+
43
+ Word each question so "yes" accepts your recommendation. A fact question carries **(fact)** after its title, and its recommendation is your best guess at the fact. An ADR offer is a question in the next round. A bare **yes** to a round accepts every recommendation in it; any other answer names the questions it changes.
44
+
45
+ Each round reshapes the tree: settled decisions push the frontier outward and unblock what depended on them. A question that depends on another question still open in this round belongs to a later round.
46
+
47
+ When a frontier question needs a fact from the environment, dispatch a subagent with a fresh context to find it rather than asking the user — or find it yourself where subagents are unavailable. Do not block on it: only the questions downstream of that fact wait, and the rest of the frontier goes out now.
48
+
49
+ Done when the frontier is empty — every branch of the tree visited, nothing silently assumed — and the user confirms you have reached a shared understanding: list the decisions in a few lines and ask whether they capture it. Start no work on it — no story, no code — before that; the glossary and ADR writes of § 3 happen during the rounds.
50
+
51
+ ## 3. Hold the language, in every round
52
+
53
+ <!-- upstream: mattpocock/skills skills/engineering/domain-modeling/SKILL.md -->
54
+
55
+ The words settled in a grilling outlive it. Keep them sharp while you ask:
56
+
57
+ - **Challenge against the glossary.** A term the user uses differently from the glossary gets named at once: "Your glossary defines *cancellation* as X, but you seem to mean Y — which is it?"
58
+ - **Sharpen fuzzy words.** A vague or overloaded term gets a precise canonical one proposed: "You say *account* — the Customer or the User? Those are different things."
59
+ - **Invent scenarios.** Stress-test the relationships between concepts with concrete edge cases that force the boundaries into the open.
60
+ - **Check the code.** When the user states how something works, check whether the code agrees, and surface a contradiction: "Your code cancels whole Orders, but you said partial cancellation is possible — which is right?"
61
+
62
+ Write as you go, never batched: a resolved term goes into the glossary at once, and a decision that meets the three-part test in [`DECISIONS.md`](../router/DECISIONS.md#writing-an-adr) is offered as an ADR. Most decisions belong in the story instead, and `DECISIONS.md` says which home is which.
63
+
64
+ A request only to pin a term or record a decision is this step alone: settle it with the user, write it, and report where it went.
65
+
66
+ Done when every term the session settled is in the glossary and every ADR the user accepted is written.
67
+
68
+ ## 4. Hand on
69
+
70
+ Report the decisions reached, in the order they were settled, with the glossary and ADR changes beside them. Then run the next skill in this same window, unless the user stops you — the decisions are freshest here:
71
+
72
+ - **No issue yet** — [`/ep-to-story`](../to-story/SKILL.md) writes the story from this conversation.
73
+ - **An issue that needed grilling** — `/ep-to-story` rewrites its body with the answers, per `/ep-triage` § 7.
74
+
75
+ A grilling that ends with decisions still open says which, and who owes the answer.
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: implement
3
+ description: Builds one story on one branch — test-first, validated, reviewed, committed. Use when an issue is ready to build.
4
+ metadata:
5
+ pack: epicparty/skills
6
+ version: 0.1.0
7
+ ---
8
+
9
+ # Implement
10
+
11
+ One story in, one committed branch out. `/ep-implement` starts from a fresh context and the issue, and ends where [`/ep-ship`](../ship/SKILL.md) picks up.
12
+
13
+ The story is the spec. Where it and your judgement disagree, the story wins until the user says otherwise — that disagreement is worth raising out loud, and it is never worth silently building the other thing.
14
+
15
+ ## 1. Load the story
16
+
17
+ Fetch the issue **and its comments** — the comments often carry decisions the body never got. The tracker is the **Tracker** setting when the settings home has one, or GitHub when `gh repo view` finds the repo. Where the work arrived with no issue, say so and build from what the conversation settled.
18
+
19
+ Check this checkout before anything runs, the way `/ep-deliver`'s worker § 1 does — clean, at `origin/<default>` — on the default branch, or a fresh branch with no commits past it — and set up for its kind per the **Checkout** setting where it is set. A checkout that fails is somebody's work: stop, name what fails, and change nothing. A worker `/ep-deliver` started has done this already.
20
+
21
+ Then claim it: assign yourself — on GitHub, `gh issue edit <n> --add-assignee @me` — and, where the project keeps a board, move the item to the Building stage (`/ep`'s [`BOARD.md`](../router/BOARD.md)). Write the status **before** cutting the branch, and re-read the item afterwards — the write is what claims the story against other workers, and an issue still reading as ready to start invites one of them to claim it too. A worker that does not see itself as the writer lost the race and stops here. Run directly, the user's request is the go-ahead: claim the story from whatever stage it is in. Hold it to the Ready bar ([`STORIES.md`](../to-story/STORIES.md) `## Ready`) all the same — a gap is an **open question** you raise before building, not a reason to stop.
22
+
23
+ Done when you can state the outcome, the seams, and the test level the story named.
24
+
25
+ ## 2. Cut the branch
26
+
27
+ Name it per `/ep`'s [`GIT.md`](../router/GIT.md) `## Naming an issue, a PR and a branch`. The type follows the issue's type label where the **Labels** setting defines a type axis and the label names a Conventional Commit type — `fix`, `refactor`, `chore` — and the change otherwise: a label such as `feature` or `improvement` is `feat` when the change adds behaviour, `fix` when it repairs it. Where the change contradicts the label — a defect fix labelled a chore — the change decides, and the hand-off names the mismatch. The scope follows the issue's title — from the **Scopes** vocabulary where the project has one.
28
+
29
+ Branch off the default branch unless the user asked to stack this on another, and link the branch to the issue: on GitHub, create it with `gh issue develop <n> --name <branch> --base <default> --checkout`; on another tracker, comment its name on the issue. The link is how `/ep-deliver` finds a branch before its PR exists.
30
+
31
+ Done when you are on a fresh branch named to the standard, cut from an up-to-date base.
32
+
33
+ ## 3. Route the standards
34
+
35
+ Before editing, read what governs the code you are about to touch:
36
+
37
+ - The project's agent files — `AGENTS.md` or `CLAUDE.md` — from the root to the nearest one to each path.
38
+ - Every standard and skill they route those paths to.
39
+ - The glossary and the ADRs for the area, per `/ep`'s [`DECISIONS.md`](../router/DECISIONS.md), so the code speaks the domain's words. Where the story uses a word the glossary avoids, the code takes the glossary's term and the hand-off names the mismatch.
40
+
41
+ Then read the neighbouring implementation. The pattern already in the codebase beats the pattern you would invent, and matching it is what keeps the next agent able to navigate.
42
+
43
+ Done when every path you are about to touch has its routed standards and its neighbouring pattern named.
44
+
45
+ ## 4. Map the coverage
46
+
47
+ Read the **Test levels** setting now, per [Settings](#settings).
48
+
49
+ **Every numbered user story in the issue gets its own test.** That list is the acceptance criteria (`/ep-to-story`'s `STORIES.md` `## Writing the story sentence`), so a story with no test is a promise nothing holds the build to.
50
+
51
+ A body with no numbered stories — a bug, an older story — maps its `## Acceptance` lines, or the bug's expected behaviour, instead; a surface those miss gets its numbered story.
52
+
53
+ Write the map before editing anything — story → the surface that proves it → the level → the setup, data, auth and cleanup it needs. `## Testing Decisions` names the level for the behaviours it covers; this step decides the rest, by the "proves" column of each level.
54
+
55
+ A surface the goal needs that no numbered story covers — the person without permission, the thing that is absent — gets its numbered story on the issue now, and its row here. The body is working material until the merge (`STORIES.md` `## Frozen once live`).
56
+
57
+ A line no level can prove — installing on a real device, a third-party dashboard — is proven by hand: its row names the manual check, and the PR body reports it as evidence.
58
+
59
+ **Each story lands at exactly one level, the cheapest that can catch its defect.** Coverage bought twice is paid for on every gate run. A rule proved at a lower level is settled there, and the journey covers only what the person sees when the rule applies to them. [`TEST-VALUE.md`](./TEST-VALUE.md) decides whether a test earns its place and at which level; read it before writing a row you are unsure of.
60
+
61
+ Done when every numbered story has a row in the map naming its level and its setup, and no story appears at two levels.
62
+
63
+ ## 5. Build it test-first
64
+
65
+ Build at the seams the story agreed, by [`TDD.md`](./TDD.md) — one red-green slice at a time, working the map row by row. Read it before the first test.
66
+
67
+ Test the externally observable behaviour, never the implementation. Reuse the fixtures, page objects and factories the suite already has, and copy the prior art the story pointed at rather than inventing a new test shape beside it.
68
+
69
+ Typecheck the package you are in with its own script, and run the single test file as you go, with the level's run command from **Test levels**; the full check comes next. A tight loop is what makes the slices cheap.
70
+
71
+ Done when every row of the map has a test that has been run, and every numbered user story reads true against the running code.
72
+
73
+ ## 6. Validate
74
+
75
+ Read the **Scoped check** setting now, per [Settings](#settings), and run what it names for every part of the project the change touches. Review whatever a formatter changed.
76
+
77
+ A change to code that other parts depend on validates those parts too, not only the one you were aiming at — and so does a change that writes generated files into them.
78
+
79
+ Done when the check passes, or its failure is understood and fixed rather than worked around.
80
+
81
+ ## 7. Review the diff
82
+
83
+ Run [`/ep-code-review`](../code-review/SKILL.md) against the merge-base, and pass it the story as its spec — the issue with its comments, or what the conversation settled when there is none. It reviews on two axes: **Standards**, whether the code follows the project's documented rules, and **Spec**, whether it does what the story asked.
84
+
85
+ Fix the Standards findings. A Spec finding naming a missing requirement means the feature is not done — go back to step 4, give the requirement its map row, and build it, rather than carrying the gap into the PR.
86
+
87
+ Done when the diff has been reviewed and every finding is fixed or dispositioned with a stated reason.
88
+
89
+ ## 8. Commit
90
+
91
+ Commit per [`GIT.md`](../router/GIT.md) `## Commits`: Conventional Commit subjects, each commit standing alone, written for the reader who finds it on the default branch.
92
+
93
+ Done when your work is committed and `git status` shows nothing of yours left behind.
94
+
95
+ ## Hand off
96
+
97
+ Report the branch, the issue, what you built, the validation result, every **small fix** you committed, every larger finding, and every **open question** you are leaving — the calls you made *for* the user. Then [`/ep-to-pr`](../to-pr/SKILL.md) opens the pull request, carrying the findings and the questions into its body, and [`/ep-ship`](../ship/SKILL.md) carries it to merged and into production.
98
+
99
+ ## Guardrails
100
+
101
+ - **The story's goal is this branch's scope, and a small fix rides along.** What the goal needs is built here and written into the story (step 4). A **small fix** the work turns up lands here as its own commit, and anything larger goes under **Found in passing** in the PR body. A **sibling** of the defect you are fixing is an **open question**: ask before it ships (`/ep` `## Vocabulary`).
102
+ - **Validation is evidence.** Make the code satisfy the gate, and leave the gate's config alone.
103
+ - **A test you had to reach into the implementation to write is telling you the seam is wrong.** Raise it rather than reaching further.
104
+
105
+ ## Settings
106
+
107
+ This skill asks for two **settings** when they are missing: **Test levels** at § 4, defined in `/ep` `## Settings` and shared with the other skills that name a level, and its own **Scoped check** at § 6. Each `###` heading below is one setting: its topic, its question, and its options. `/ep-setup` reads the same headings to ask every setting up front.
108
+
109
+ 1. **The settings home.** The `## Agent skills` block of the root `AGENTS.md` — or `CLAUDE.md`, whichever the project has — names where `ep-*` settings live and in what form. With neither file present, first ask which to create, recommending `AGENTS.md`, which most agents read. With no such line, ask the user where to keep them, the default first:
110
+ - **`docs/agents/<topic>.md`** (default) — one Markdown file per topic, one `##` heading per setting, named as the setting. Other headings in the file are the project's own notes.
111
+ - **One file** — at a path the user names.
112
+ - **Another form** — as the user describes it.
113
+
114
+ Write their answer as one line in that block, creating the block when it is missing — for the default, `` `ep-*` settings: `docs/agents/<topic>.md`, one heading per setting. ``
115
+ 2. **The setting.** Read it at the settings home. Present: use it, and ask nothing.
116
+ 3. **Missing.** Ask its question below, recommended answer first, with the follow-up its option needs. Write the answer at the settings home, tell the user where it went and that they can edit it there or through `/ep-setup`, then continue.
117
+
118
+ It also reads four settings `/ep` `## Settings` defines — **Tracker** and **Checkout** at § 1, **Labels** and **Scopes** at § 2 — when they are set, and never asks for them: without them it uses GitHub where `gh repo view` finds the repo, and names the branch from the change.
119
+
120
+ ### Scoped check
121
+
122
+ Topic `validation`. Question: *Which commands validate a change, by what it touches?*
123
+
124
+ | Option | Follow-up | Recommend when |
125
+ | --- | --- | --- |
126
+ | **A document** | The file and heading that hold the table | The agent files already have a validation section |
127
+ | **This table** | One row per kind of change — a package, a layer, root files — and the commands it runs, in order | The project has several packages or products with their own checks |
128
+ | **These commands** | The commands, in order, such as `pnpm lint`, `pnpm typecheck`, `pnpm test` — or one script that runs them all | One package: the same commands validate every change |
129
+
130
+ Propose the commands from the root and package `package.json` scripts, run with the package manager the lockfile names: format first, then lint, typecheck and tests, where each exists.
131
+
132
+ This is the fast run while building. **Full suite** (`/ep`) proves the branch once, on its head commit, before the PR.
@@ -0,0 +1,131 @@
1
+ # Test-first
2
+
3
+ <!-- upstream: mattpocock/skills skills/engineering/tdd/SKILL.md -->
4
+
5
+ The red → green loop of [`/ep-implement` § 5](./SKILL.md#5-build-it-test-first), and what makes it produce tests worth keeping: what a good test is, where tests go, the anti-patterns, and the rules of the loop. Every section applies on every cycle — consult them before and during the loop, not after.
6
+
7
+ ## What a good test is
8
+
9
+ Tests verify behaviour through public interfaces, not implementation details. Code can change entirely; tests should not. A good test reads like a specification — "user can checkout with valid cart" says exactly what capability exists — and survives refactors because it does not care about internal structure.
10
+
11
+ ## Seams — where tests go
12
+
13
+ A **seam** is the public boundary you test at: the interface where you observe behaviour without reaching inside. Tests live at seams, never against internals.
14
+
15
+ **Test only at agreed seams.** The story names them — [`/ep-to-story` § 3](../to-story/SKILL.md#3-settle-the-seams) put them to the user. A seam the story does not name is an **open question** before its first test is written: give each proposed seam a one-line note on what it catches and what it misses. Agreeing the seams up front is how testing effort lands on the critical paths and the complex logic instead of every edge case.
16
+
17
+ When the shape of the interface is itself in question — how deep the module is, where the seam belongs, what the interface should expose — [`SEAMS.md`](../to-story/SEAMS.md) holds the vocabulary: module, interface, depth, seam, adapter, leverage, locality.
18
+
19
+ ## Anti-patterns
20
+
21
+ - **Implementation-coupled** — mocks internal collaborators, tests private methods, or verifies through a side channel (querying the database instead of using the interface). The tell: the test breaks on a refactor that changed no behaviour.
22
+ - **Tautological** — the assertion recomputes the expected value the way the code does (`expect(add(a, b)).toBe(a + b)`, a snapshot derived by hand the same way, a constant asserted equal to itself), so it passes by construction and can never disagree with the code. Expected values come from an independent source of truth: a known-good literal, a worked example, the spec.
23
+ - **Horizontal slicing** — writing all tests first, then all implementation. Bulk tests verify _imagined_ behaviour: they test the _shape_ of things rather than what the user sees, they go insensitive to real changes, and they commit to a test structure before the implementation is understood. Work in **vertical slices** instead — one test → one implementation → repeat, each test a **tracer bullet** that responds to what the last cycle taught you.
24
+
25
+ ## Rules of the loop
26
+
27
+ - **Red before green.** Write the failing test first, then only enough code to pass it. Do not anticipate future tests or add speculative features.
28
+ - **One slice at a time.** One seam, one test, one minimal implementation per cycle.
29
+ - **Refactoring is not part of the loop.** It belongs to the review, [`/ep-implement` § 7](./SKILL.md#7-review-the-diff), not the red → green cycle.
30
+
31
+ ## Good and bad tests
32
+
33
+ <!-- upstream: mattpocock/skills skills/engineering/tdd/tests.md -->
34
+
35
+ **Through real interfaces**, not mocks of internal parts:
36
+
37
+ ```typescript
38
+ // GOOD: tests observable behaviour
39
+ test("user can checkout with valid cart", async () => {
40
+ const cart = createCart();
41
+ cart.add(product);
42
+ const result = await checkout(cart, paymentMethod);
43
+ expect(result.status).toBe("confirmed");
44
+ });
45
+ ```
46
+
47
+ A good test cares about what callers care about, uses the public API only, survives internal refactors, describes _what_ and not _how_, and makes one logical assertion.
48
+
49
+ **Coupled to internal structure:**
50
+
51
+ ```typescript
52
+ // BAD: tests implementation details
53
+ test("checkout calls paymentService.process", async () => {
54
+ const mockPayment = jest.mock(paymentService);
55
+ await checkout(cart, payment);
56
+ expect(mockPayment.process).toHaveBeenCalledWith(cart.total);
57
+ });
58
+ ```
59
+
60
+ Red flags: mocking internal collaborators, testing private methods, asserting on call counts or order, a test name that describes _how_, and verifying by other means than the interface:
61
+
62
+ ```typescript
63
+ // BAD: bypasses the interface to verify
64
+ test("createUser saves to database", async () => {
65
+ await createUser({ name: "Alice" });
66
+ const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]);
67
+ expect(row).toBeDefined();
68
+ });
69
+
70
+ // GOOD: verifies through the interface
71
+ test("createUser makes user retrievable", async () => {
72
+ const user = await createUser({ name: "Alice" });
73
+ const retrieved = await getUser(user.id);
74
+ expect(retrieved.name).toBe("Alice");
75
+ });
76
+ ```
77
+
78
+ **Tautological** — the expected value restates the implementation:
79
+
80
+ ```typescript
81
+ // BAD: expected value recomputed the way the code computes it
82
+ test("calculateTotal sums line items", () => {
83
+ const items = [{ price: 10 }, { price: 5 }];
84
+ const expected = items.reduce((sum, i) => sum + i.price, 0);
85
+ expect(calculateTotal(items)).toBe(expected);
86
+ });
87
+
88
+ // GOOD: expected value is an independent, known literal
89
+ test("calculateTotal sums line items", () => {
90
+ expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15);
91
+ });
92
+ ```
93
+
94
+ ## When to mock
95
+
96
+ <!-- upstream: mattpocock/skills skills/engineering/tdd/mocking.md -->
97
+
98
+ Mock at **system boundaries** only: external APIs (payment, email), time and randomness, and — sometimes — the database and the file system, where the project has no test stand-in for them. Never mock your own modules or internal collaborators: anything you control is tested for real.
99
+
100
+ At a system boundary, shape the interface so it is easy to mock:
101
+
102
+ - **Inject the dependency** rather than creating it inside:
103
+
104
+ ```typescript
105
+ // Easy to mock
106
+ function processPayment(order, paymentClient) {
107
+ return paymentClient.charge(order.total);
108
+ }
109
+
110
+ // Hard to mock
111
+ function processPayment(order) {
112
+ const client = new StripeClient(process.env.STRIPE_KEY);
113
+ return client.charge(order.total);
114
+ }
115
+ ```
116
+
117
+ - **One function per external operation**, not one generic fetcher with conditional logic. Each mock then returns one shape, test setup carries no conditionals, and each test shows which endpoints it exercises:
118
+
119
+ ```typescript
120
+ // GOOD: each function is independently mockable
121
+ const api = {
122
+ getUser: (id) => fetch(`/users/${id}`),
123
+ getOrders: (userId) => fetch(`/users/${userId}/orders`),
124
+ createOrder: (data) => fetch("/orders", { method: "POST", body: data }),
125
+ };
126
+
127
+ // BAD: the mock needs conditional logic
128
+ const api = {
129
+ fetch: (endpoint, options) => fetch(endpoint, options),
130
+ };
131
+ ```
@@ -0,0 +1,114 @@
1
+ # Test Value
2
+
3
+ What a test is worth, and when it should not exist. It applies at every level the **Test levels** setting names (`/ep` `## Settings`). The project's own test standards say how to write a test at each level; this file says whether it should be written, kept, moved or deleted. The project's rules add to it and win where they differ.
4
+
5
+ A test costs run time on every gate and attention on every change that breaks it. It earns that cost only by catching a real defect. A test that cannot catch one, or that catches only what another test already catches, is a cost with no return.
6
+
7
+ The levels below are named by what they observe: **unit** (a return value or emitted output, no I/O), **integration** (a real store or service behind a public API), **journey** (what a person sees in a running app). Map each of the project's levels to the nearest one.
8
+
9
+ ## The four axes
10
+
11
+ The score is Vladimir Khorikov's four pillars (*Unit Testing: Principles, Practices, and Patterns*, ch. 4). Score each axis 0–3. The axes are the same at every level, so a test can be compared with a cheaper test at another level.
12
+
13
+ **P — Protection against regressions.** Would a plausible defect in the code under test make this test fail?
14
+
15
+ | | Meaning |
16
+ | --- | --- |
17
+ | 3 | It covers logic where a defect changes what a person sees, the data stored, a permission, or money, and it fails on that defect. |
18
+ | 2 | It covers real logic with a narrower reach: a branch of a formatter, an edge case, a refusal code. |
19
+ | 1 | It covers code with little logic (wiring, a pass-through, a constant), or it asserts so loosely that most defects pass. |
20
+ | 0 | It cannot fail on a plausible defect: it asserts a mock it set up, a guarantee of the framework, a type the compiler already checks, or nothing. |
21
+
22
+ **R — Resistance to refactoring.** Does it still pass when the code changes and the behaviour does not?
23
+
24
+ | | Meaning |
25
+ | --- | --- |
26
+ | 3 | It asserts only what its level observes: what the person sees (journey), the API result and the stored data (integration), the return value or the emitted output (unit). |
27
+ | 2 | Mostly behaviour, with one incidental detail: an exact call count that is not a contract, a class name. |
28
+ | 1 | It is coupled to structure: a mock of the project's own module, an internal call order, DOM structure or a test id where a role or label exists. |
29
+ | 0 | It asserts the implementation itself. Any refactor breaks it. |
30
+
31
+ **F — Fast feedback.** Measured, per test, against its own level. A journey is never fast next to a unit test; that trade-off is why the levels exist.
32
+
33
+ | | Unit | Integration | Journey |
34
+ | --- | --- | --- | --- |
35
+ | 3 | under 50 ms | under 300 ms | under 5 s |
36
+ | 2 | under 500 ms | under 1 s | under 15 s |
37
+ | 1 | under 5 s | under 5 s | under 30 s |
38
+ | 0 | 5 s or more | 5 s or more | 30 s or more, or a raised timeout |
39
+
40
+ **M — Maintainability.** How cheap is it to read and to keep working?
41
+
42
+ | | Meaning |
43
+ | --- | --- |
44
+ | 3 | Arrange, act, assert, readable in one screen. It uses the suite's shared fixtures and has no logic of its own. |
45
+ | 2 | Some setup noise, still clear. |
46
+ | 1 | Long or duplicated setup, conditional logic, or one of the smells below. |
47
+ | 0 | A reader must run it to understand what it proves. |
48
+
49
+ The **value** is the product P × R × F × M, from 0 to 81. Khorikov multiplies on purpose: a 0 on any axis drags the value to nothing, however well the test does on the others. The value ranks tests; the rule below decides what happens to them, and it treats the axes differently. A 0 on P or R means the test cannot earn its place at all — it catches no defect, or it breaks on every refactor — so it goes. A 0 on F or M is a cost, not a verdict: a slow or obscure test that protects real behaviour is made faster or clearer, not deleted.
50
+
51
+ ## The checks around the score
52
+
53
+ **Level fit.** Each level has an entry rule. A test that fails its level's rule is at the wrong level.
54
+
55
+ | Level | Entry rule | Belongs elsewhere when |
56
+ | --- | --- | --- |
57
+ | Journey | A journey a person performs, ending in what they see. | It proves a rule the backend decides — that goes to integration, and the journey keeps only what the person sees when the rule refuses them. |
58
+ | Integration | Backend behaviour against the real store. A test whose claim is a guard, a permission or a refusal goes through the public API as the caller it names, so the guard runs. | It tests a pure function — that goes to unit. |
59
+ | Unit | Pure logic with real branches, or a hook or component with meaningful logic of its own. | It renders a whole page, form or route in a simulated DOM — that proves the harness, not the product, and goes to a journey. |
60
+
61
+ **Need.** Does another test already fail on the same defect? A test is **redundant** when another test at the same level or a cheaper one asserts the same behaviour. Partial overlap is not redundancy: two tests that share a setup but assert different branches both earn their place.
62
+
63
+ **Determinism.** A gate, not a score. A test passes the gate when its outcome cannot change with the time of day, the date, machine speed, machine load, test order or anything else outside the code under test. A test that fails the gate is fixed or deleted; it is never kept with a low score.
64
+
65
+ - A test whose assertion depends on time pins the clock with the test tool's fake timers. Fixture data built from the current time is fine only when no assertion can change with the value it reads.
66
+ - No arbitrary sleep. Wait for the condition — a web-first assertion in a journey, a fake-timer advance elsewhere.
67
+ - No state that outlives a test: no module-level value one test sets and another reads, nothing written outside a temporary directory.
68
+ - A journey fails on any page error or console error it did not declare.
69
+ - A test that depends on machine state — the working tree, `git`, a child process — states that dependency in a comment and owns a time budget sized from measurement.
70
+
71
+ **A slow suite earns a budget, never a retry.** A retry re-runs a failure without anyone reading it, which is how a real defect gets waved through; and it cannot reach a failure that is not a failed test, such as a worker pool that never starts. A suite that does real I/O gets a timeout sized from measurement, per package, re-sized when the work grows. A browser journey is the one place a retry may absorb network nondeterminism, and only in CI.
72
+
73
+ ## The rule
74
+
75
+ | The test | Action |
76
+ | --- | --- |
77
+ | Scores P = 0 or R = 0 | Delete it. Where the behaviour it meant to cover is real and uncovered, replace it with a test that covers it. |
78
+ | Is redundant | Delete it, and name the test that covers it. |
79
+ | Fails its level's entry rule | Move it to the level the rule names. |
80
+ | Fails the determinism gate | Fix it. Delete it only when the fix costs more than the protection it gives, and say so. |
81
+ | Scores F = 0 or M = 0 | Fix the cost: move it to a cheaper level, cut its setup, or name what it proves. |
82
+ | Scores R = 1 or M = 1 | Keep it, and fix it when you next touch it. |
83
+ | Anything else | Keep it. |
84
+
85
+ A deletion carries its reason in the PR that makes it: the axis that scored 0, or the test that already covers it.
86
+
87
+ ## Naming a defect
88
+
89
+ Name a test's defects with the smells from Gerard Meszaros, *xUnit Test Patterns*, so a reason reads the same in every review:
90
+
91
+ - **Erratic Test** — passes and fails on the same code. Name the cause: Interacting Tests, Resource Leakage, Nondeterministic Test (clock, randomness, order).
92
+ - **Fragile Test** — breaks on a change that kept the behaviour. Usually R ≤ 1.
93
+ - **Sleepy Test** — waits a fixed time instead of for a condition.
94
+ - **Slow Test** — F = 0.
95
+ - **Obscure Test** — a reader cannot see what it proves. Its forms: **Mystery Guest** (the data it depends on is out of sight), **Eager Test** (it verifies several behaviours at once), **Irrelevant Information**.
96
+ - **Assertion Roulette** — several assertions, and a failure does not say which behaviour broke.
97
+ - **Conditional Test Logic** — an `if` or a loop decides what the test asserts.
98
+ - **Test Code Duplication** — the same setup copied across tests instead of a fixture.
99
+
100
+ ## Finding a flaky test
101
+
102
+ A clean run proves less than it looks. After N passing runs, the rule of three bounds the failure rate below 3/N at 95% confidence: 10 runs prove less than 30%, 20 runs less than 15%.
103
+
104
+ A flake has three causes, and each needs its own condition to show:
105
+
106
+ | Cause | Condition that shows it |
107
+ | --- | --- |
108
+ | Intrinsic — the test or the product races itself | Run alone, repeated — 20 runs. |
109
+ | Load — it fails only on a busy machine | Run while other suites run on the same machine. |
110
+ | Order — it depends on another test's leftovers | Run in shuffled order. |
111
+
112
+ A fix counts when the test passes 20 of 20 alone and five full suite runs under load. Record the cause of every flake found: a bad test, or a product defect. A product defect is fixed in the product, not hidden in the test.
113
+
114
+ **Completion criterion:** every test the change adds or touches scores above 0 on P and R, fits its level's entry rule, duplicates no other test, and passes the determinism gate; every test the change deletes is listed in its PR with the reason.