@tamsensedev/runner 0.10.0 → 0.18.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.
- package/LICENSE +5 -5
- package/README.md +61 -50
- package/dist/cli.js +1188 -324
- package/dist/cli.js.map +1 -1
- package/dist/recorder.cjs +42 -6
- package/dist/recorder.cjs.map +1 -1
- package/library/.claude-plugin/plugin.json +5 -5
- package/library/THIRD_PARTY_NOTICES.md +123 -123
- package/library/agents/code-architect.md +67 -67
- package/library/agents/code-explorer.md +61 -61
- package/library/agents/code-reviewer.md +73 -73
- package/library/agents/code-simplifier.md +62 -62
- package/library/agents/design-critic.md +52 -52
- package/library/agents/second-opinion.md +64 -64
- package/library/agents/security-auditor.md +86 -86
- package/library/agents/silent-failure-hunter.md +70 -70
- package/library/agents/test-coverage-analyzer.md +78 -78
- package/library/agents/test-runner.md +48 -48
- package/library/agents/ui-tester.md +50 -50
- package/library/licenses/Apache-2.0.txt +202 -202
- package/library/skills/accessibility/SKILL.md +144 -144
- package/library/skills/accessibility/references/wcag-checks.md +168 -168
- package/library/skills/addressing-review-feedback/SKILL.md +109 -109
- package/library/skills/app-screens/SKILL.md +96 -96
- package/library/skills/app-screens/references/shell-kpis-charts.md +108 -108
- package/library/skills/app-screens/references/states-density-phone.md +54 -54
- package/library/skills/app-screens/references/tables-forms-settings.md +104 -104
- package/library/skills/avoiding-generic-ai-design/SKILL.md +92 -92
- package/library/skills/avoiding-generic-ai-design/references/tells.md +112 -112
- package/library/skills/clean-commits/SKILL.md +85 -85
- package/library/skills/design-review/SKILL.md +107 -107
- package/library/skills/design-review/references/critique-passes.md +101 -101
- package/library/skills/design-review/references/screenshot-procedure.md +121 -121
- package/library/skills/differential-security-review/LICENSE +427 -427
- package/library/skills/differential-security-review/SKILL.md +105 -105
- package/library/skills/differential-security-review/references/adversarial.md +115 -115
- package/library/skills/differential-security-review/references/methodology.md +145 -145
- package/library/skills/differential-security-review/references/patterns.md +108 -108
- package/library/skills/false-positive-check/LICENSE +427 -427
- package/library/skills/false-positive-check/SKILL.md +95 -95
- package/library/skills/false-positive-check/references/bug-class-verification.md +128 -128
- package/library/skills/false-positive-check/references/deep-verification.md +113 -113
- package/library/skills/false-positive-check/references/evidence-templates.md +93 -93
- package/library/skills/false-positive-check/references/false-positive-patterns.md +122 -122
- package/library/skills/false-positive-check/references/gate-reviews.md +36 -36
- package/library/skills/false-positive-check/references/standard-verification.md +80 -80
- package/library/skills/focused-implementation/SKILL.md +104 -104
- package/library/skills/implementation-plan/SKILL.md +66 -66
- package/library/skills/interface-design/SKILL.md +144 -144
- package/library/skills/interface-design/references/craft-defaults.md +130 -130
- package/library/skills/interface-design/references/new-direction.md +62 -62
- package/library/skills/interface-design/references/writing.md +52 -52
- package/library/skills/interface-polish/SKILL.md +103 -103
- package/library/skills/interface-polish/references/detail-rules.md +154 -154
- package/library/skills/interface-polish/references/worst-case-data.md +158 -158
- package/library/skills/motion-design/SKILL.md +106 -106
- package/library/skills/motion-design/references/recipes.md +243 -243
- package/library/skills/postgres-performance/SKILL.md +97 -97
- package/library/skills/postgres-performance/references/rules.md +265 -265
- package/library/skills/project-orientation/SKILL.md +77 -77
- package/library/skills/reviewing-changes/SKILL.md +183 -183
- package/library/skills/safe-database-changes/SKILL.md +111 -111
- package/library/skills/safe-database-changes/references/backfills.md +75 -75
- package/library/skills/safe-database-changes/references/migration-tools.md +69 -69
- package/library/skills/safe-database-changes/references/postgres-ddl.md +199 -199
- package/library/skills/security-review/SKILL.md +132 -132
- package/library/skills/security-review/references/checks-by-area.md +107 -107
- package/library/skills/security-review/references/do-not-report.md +86 -86
- package/library/skills/spec-writing/SKILL.md +144 -144
- package/library/skills/systematic-debugging/SKILL.md +130 -130
- package/library/skills/systematic-debugging/references/flaky-tests.md +51 -51
- package/library/skills/systematic-debugging/references/root-cause-tracing.md +55 -55
- package/library/skills/task-interview/SKILL.md +84 -84
- package/library/skills/test-driven-development/SKILL.md +116 -116
- package/library/skills/test-driven-development/references/writing-good-tests.md +85 -85
- package/library/skills/ui-testing/SKILL.md +102 -102
- package/library/skills/ui-testing/references/headless-check.md +295 -280
- package/library/skills/ui-testing/references/local-stand.md +51 -51
- package/library/skills/ui-testing/references/playwright-patterns.md +134 -134
- package/library/skills/ui-testing/references/recording.md +61 -58
- package/library/skills/verification-before-completion/SKILL.md +113 -113
- package/library/skills/web-interface-guidelines/SKILL.md +202 -202
- package/package.json +46 -46
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: task-interview
|
|
3
|
-
description: Use when a task or spec leaves a decision open and you are tempted to ask: research first, ask only questions that pass three tests, settle corner cases as assumptions with a reason, and either ask in short rounds with a recommended answer first (when a person can answer, or on the TAMsense spec step with submit_questions) or decide with the recommendation and report it (on any other unattended TAMsense step).
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Task interview
|
|
7
|
-
|
|
8
|
-
Finding facts is your job. Decisions about intent belong to the person who owns the task. Most "questions" are facts you have not looked up yet, or corners with a safe default. Ask only what is left after both.
|
|
9
|
-
|
|
10
|
-
## 1. Research before any question
|
|
11
|
-
|
|
12
|
-
Look before you ask: the task, the spec and its decisions, the project's rules (`project-orientation`), the code around the change, existing tests, data you can read locally.
|
|
13
|
-
|
|
14
|
-
- Never ask where something is, which component to use or how the code works today. Look it up.
|
|
15
|
-
- If the answer has several plausible candidates in the code, the question becomes "which of these", with the real paths as options.
|
|
16
|
-
- When a person corrects a fact, check it in the code before you rely on it.
|
|
17
|
-
|
|
18
|
-
## 2. Sweep the corners instead of asking about them
|
|
19
|
-
|
|
20
|
-
Most defects are corners: one part of the change meeting one unusual condition. More questions do not find them; a sweep does. For each part of the change, go through:
|
|
21
|
-
|
|
22
|
-
- empty or missing data, one item, very many items;
|
|
23
|
-
- invalid or too long input, first use and repeated use;
|
|
24
|
-
- missing access, a signed-out user, two people editing at once;
|
|
25
|
-
- a failed or slow external call, old stored data, existing users;
|
|
26
|
-
- phone width; numbers and money (zero, rounding, unknown versus zero); dates and time zones.
|
|
27
|
-
|
|
28
|
-
Settle every condition that applies in one of three ways: an assumption with its reason (a safe default exists), out of scope, or a question (only if it passes the test below).
|
|
29
|
-
|
|
30
|
-
## 3. The question test
|
|
31
|
-
|
|
32
|
-
Ask a question only if all three hold:
|
|
33
|
-
|
|
34
|
-
1. **Not discoverable.** The code, the data, the spec and the project's rules do not answer it.
|
|
35
|
-
2. **It changes the result.** The two most likely answers lead to different changes or different acceptance criteria.
|
|
36
|
-
3. **No safe default.** Or a wrong default is costly: lost or wrong data, money, permissions and privacy, a public API or stored format, a migration that is hard to undo, or visible text and behaviour the owner cares about.
|
|
37
|
-
|
|
38
|
-
Rank the questions that pass by impact times uncertainty. On a tie: scope first, then data and security, then what the user sees, then technical details. Stop when you can predict the answer to the next questions; record those as assumptions.
|
|
39
|
-
|
|
40
|
-
## 4. How to write a question
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
id: empty_state
|
|
44
|
-
Question: What should the task list show when a board has no tasks?
|
|
45
|
-
Why it matters: It decides the empty-state text and whether a "Create a task" button is added.
|
|
46
|
-
Options:
|
|
47
|
-
1. A short hint (recommended): the page says "No tasks yet", nothing else changes.
|
|
48
|
-
2. A hint and a button: the page also offers "Create a task", which needs a new route.
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
- One plain sentence; the owner may not be a developer.
|
|
52
|
-
- A "why it matters" line.
|
|
53
|
-
- 2 to 4 mutually exclusive options, the recommended one first and marked; each option says what happens if it is chosen.
|
|
54
|
-
- Several picks only when the options are additive.
|
|
55
|
-
- Zero questions is a good result.
|
|
56
|
-
|
|
57
|
-
## 5. Rounds
|
|
58
|
-
|
|
59
|
-
- **Round 1:** at most 4 questions, ideally 2 or 3. Ask only questions that do not depend on another open question of the same round; note dependent decisions for later.
|
|
60
|
-
- **Round 2 (optional):** at most 3 more, only if an answer was vague ("not sure", "depends"), contradicts another answer or the code, or unlocked a dependent decision.
|
|
61
|
-
- No third round. About 6 questions in total. Whatever is left becomes an assumption.
|
|
62
|
-
- An unanswered question takes the recommended option and is recorded as an assumption.
|
|
63
|
-
|
|
64
|
-
## 6. When nobody can answer (a TAMsense run)
|
|
65
|
-
|
|
66
|
-
On the TAMsense spec step the owner answers after the run: hand the brief and the round's questions in with `submit_questions` and finish. The owner clicks through them on the task page, and the next round or the spec continues from your brief.
|
|
67
|
-
|
|
68
|
-
On any other step of a TAMsense run nobody answers, so never stop to wait:
|
|
69
|
-
|
|
70
|
-
1. Take the recommended option and keep working.
|
|
71
|
-
2. If a wrong guess would be costly and hard to undo (deleting data, a destructive migration, money, permissions), build the safe part only and leave the risky part out.
|
|
72
|
-
3. In the report, list each decision as the question, the options, what you chose and why, so the owner can correct it in one reply. If the board lets you hand the task back to the spec step and the open decision blocks the work, hand it back with these questions in the note.
|
|
73
|
-
|
|
74
|
-
## Common mistakes
|
|
75
|
-
|
|
76
|
-
| Thought | What to do instead |
|
|
77
|
-
|---|---|
|
|
78
|
-
| "I'll ask which file handles this" | Search the code; ask only to choose between real candidates. |
|
|
79
|
-
| "Better ask about the empty state too" | If a safe default exists, write it as an assumption with the reason. |
|
|
80
|
-
| "Let me ask everything at once" | One round of independent questions; dependent ones wait. |
|
|
81
|
-
| "Whatever you think is best" came back | That is a delegation: take your recommendation and say so. |
|
|
82
|
-
| "I'll wait for an answer" | In a run nobody answers: decide, record, report. |
|
|
83
|
-
|
|
84
|
-
Sources: adapted from openai/codex Plan Mode template and request_user_input tool (Apache-2.0, commit 430a167), github/spec-kit clarify and specify commands (MIT, commit c06e670), mattpocock/skills grilling (MIT, commit 5beb35b), awslabs/aidlc-workflows corner checklist and requirements analysis (MIT-0, commit 95d2b44), addyosmani/agent-skills interview-me (MIT, commit aeac41d) and humanlayer/humanlayer create_plan (Apache-2.0, commit 99abe67). Changed: merged into one question policy, rewritten for batched rounds and unattended runs.
|
|
1
|
+
---
|
|
2
|
+
name: task-interview
|
|
3
|
+
description: Use when a task or spec leaves a decision open and you are tempted to ask: research first, ask only questions that pass three tests, settle corner cases as assumptions with a reason, and either ask in short rounds with a recommended answer first (when a person can answer, or on the TAMsense spec step with submit_questions) or decide with the recommendation and report it (on any other unattended TAMsense step).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Task interview
|
|
7
|
+
|
|
8
|
+
Finding facts is your job. Decisions about intent belong to the person who owns the task. Most "questions" are facts you have not looked up yet, or corners with a safe default. Ask only what is left after both.
|
|
9
|
+
|
|
10
|
+
## 1. Research before any question
|
|
11
|
+
|
|
12
|
+
Look before you ask: the task, the spec and its decisions, the project's rules (`project-orientation`), the code around the change, existing tests, data you can read locally.
|
|
13
|
+
|
|
14
|
+
- Never ask where something is, which component to use or how the code works today. Look it up.
|
|
15
|
+
- If the answer has several plausible candidates in the code, the question becomes "which of these", with the real paths as options.
|
|
16
|
+
- When a person corrects a fact, check it in the code before you rely on it.
|
|
17
|
+
|
|
18
|
+
## 2. Sweep the corners instead of asking about them
|
|
19
|
+
|
|
20
|
+
Most defects are corners: one part of the change meeting one unusual condition. More questions do not find them; a sweep does. For each part of the change, go through:
|
|
21
|
+
|
|
22
|
+
- empty or missing data, one item, very many items;
|
|
23
|
+
- invalid or too long input, first use and repeated use;
|
|
24
|
+
- missing access, a signed-out user, two people editing at once;
|
|
25
|
+
- a failed or slow external call, old stored data, existing users;
|
|
26
|
+
- phone width; numbers and money (zero, rounding, unknown versus zero); dates and time zones.
|
|
27
|
+
|
|
28
|
+
Settle every condition that applies in one of three ways: an assumption with its reason (a safe default exists), out of scope, or a question (only if it passes the test below).
|
|
29
|
+
|
|
30
|
+
## 3. The question test
|
|
31
|
+
|
|
32
|
+
Ask a question only if all three hold:
|
|
33
|
+
|
|
34
|
+
1. **Not discoverable.** The code, the data, the spec and the project's rules do not answer it.
|
|
35
|
+
2. **It changes the result.** The two most likely answers lead to different changes or different acceptance criteria.
|
|
36
|
+
3. **No safe default.** Or a wrong default is costly: lost or wrong data, money, permissions and privacy, a public API or stored format, a migration that is hard to undo, or visible text and behaviour the owner cares about.
|
|
37
|
+
|
|
38
|
+
Rank the questions that pass by impact times uncertainty. On a tie: scope first, then data and security, then what the user sees, then technical details. Stop when you can predict the answer to the next questions; record those as assumptions.
|
|
39
|
+
|
|
40
|
+
## 4. How to write a question
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
id: empty_state
|
|
44
|
+
Question: What should the task list show when a board has no tasks?
|
|
45
|
+
Why it matters: It decides the empty-state text and whether a "Create a task" button is added.
|
|
46
|
+
Options:
|
|
47
|
+
1. A short hint (recommended): the page says "No tasks yet", nothing else changes.
|
|
48
|
+
2. A hint and a button: the page also offers "Create a task", which needs a new route.
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- One plain sentence; the owner may not be a developer.
|
|
52
|
+
- A "why it matters" line.
|
|
53
|
+
- 2 to 4 mutually exclusive options, the recommended one first and marked; each option says what happens if it is chosen.
|
|
54
|
+
- Several picks only when the options are additive.
|
|
55
|
+
- Zero questions is a good result.
|
|
56
|
+
|
|
57
|
+
## 5. Rounds
|
|
58
|
+
|
|
59
|
+
- **Round 1:** at most 4 questions, ideally 2 or 3. Ask only questions that do not depend on another open question of the same round; note dependent decisions for later.
|
|
60
|
+
- **Round 2 (optional):** at most 3 more, only if an answer was vague ("not sure", "depends"), contradicts another answer or the code, or unlocked a dependent decision.
|
|
61
|
+
- No third round. About 6 questions in total. Whatever is left becomes an assumption.
|
|
62
|
+
- An unanswered question takes the recommended option and is recorded as an assumption.
|
|
63
|
+
|
|
64
|
+
## 6. When nobody can answer (a TAMsense run)
|
|
65
|
+
|
|
66
|
+
On the TAMsense spec step the owner answers after the run: hand the brief and the round's questions in with `submit_questions` and finish. The owner clicks through them on the task page, and the next round or the spec continues from your brief.
|
|
67
|
+
|
|
68
|
+
On any other step of a TAMsense run nobody answers, so never stop to wait:
|
|
69
|
+
|
|
70
|
+
1. Take the recommended option and keep working.
|
|
71
|
+
2. If a wrong guess would be costly and hard to undo (deleting data, a destructive migration, money, permissions), build the safe part only and leave the risky part out.
|
|
72
|
+
3. In the report, list each decision as the question, the options, what you chose and why, so the owner can correct it in one reply. If the board lets you hand the task back to the spec step and the open decision blocks the work, hand it back with these questions in the note.
|
|
73
|
+
|
|
74
|
+
## Common mistakes
|
|
75
|
+
|
|
76
|
+
| Thought | What to do instead |
|
|
77
|
+
|---|---|
|
|
78
|
+
| "I'll ask which file handles this" | Search the code; ask only to choose between real candidates. |
|
|
79
|
+
| "Better ask about the empty state too" | If a safe default exists, write it as an assumption with the reason. |
|
|
80
|
+
| "Let me ask everything at once" | One round of independent questions; dependent ones wait. |
|
|
81
|
+
| "Whatever you think is best" came back | That is a delegation: take your recommendation and say so. |
|
|
82
|
+
| "I'll wait for an answer" | In a run nobody answers: decide, record, report. |
|
|
83
|
+
|
|
84
|
+
Sources: adapted from openai/codex Plan Mode template and request_user_input tool (Apache-2.0, commit 430a167), github/spec-kit clarify and specify commands (MIT, commit c06e670), mattpocock/skills grilling (MIT, commit 5beb35b), awslabs/aidlc-workflows corner checklist and requirements analysis (MIT-0, commit 95d2b44), addyosmani/agent-skills interview-me (MIT, commit aeac41d) and humanlayer/humanlayer create_plan (Apache-2.0, commit 99abe67). Changed: merged into one question policy, rewritten for batched rounds and unattended runs.
|
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: test-driven-development
|
|
3
|
-
description: Use when adding behaviour or fixing a bug in a project, before writing the implementation: write a test in the project's own framework, watch it fail for the right reason, make it pass with minimal code, then run the suite. Covers what to do when the project has no test setup.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Test-driven development
|
|
7
|
-
|
|
8
|
-
If you never saw a test fail, you do not know that it tests anything. Write the test first, watch it fail, then write the code.
|
|
9
|
-
|
|
10
|
-
## Step 0: Find the project's test setup
|
|
11
|
-
|
|
12
|
-
Before anything else (see `project-orientation`):
|
|
13
|
-
|
|
14
|
-
- Which framework, where tests live, how they are named, which helpers and fixtures exist, and the exact command to run one file and the whole suite.
|
|
15
|
-
- Copy the shape of a neighbouring test: imports, setup, naming, assertion style.
|
|
16
|
-
|
|
17
|
-
**If the project has no test setup at all:** do not add a test framework, new dependencies or a test folder structure the maintainers did not choose. Verify the change another way that you can show evidence for (type check, build, running the code with a fixture input, a throwaway script outside the repository) and write in the report: "No test setup in this repository; verified by: ...".
|
|
18
|
-
|
|
19
|
-
**If tests exist but not for this area:** add the test in the existing framework, next to the code, following the closest existing example.
|
|
20
|
-
|
|
21
|
-
## When to use it, and when not
|
|
22
|
-
|
|
23
|
-
Use it for new behaviour, bug fixes, and behaviour changes in code that has a reasonable seam to test at.
|
|
24
|
-
|
|
25
|
-
Usually skip it, and say so in the report, for: configuration-only changes, generated code, pure markup or styling with no logic, copy changes, dependency bumps, and code with no seam that can show the behaviour (then explain the gap rather than writing a hollow test). Exploratory spikes are fine, but throw the spike away or start the test-first cycle from it before you commit.
|
|
26
|
-
|
|
27
|
-
## The cycle
|
|
28
|
-
|
|
29
|
-
### Red: write one failing test
|
|
30
|
-
|
|
31
|
-
One behaviour per test, a name that reads as a sentence about it, real code rather than mocks where that is cheap.
|
|
32
|
-
|
|
33
|
-
```ts
|
|
34
|
-
test('retries a failing call up to 3 times, then succeeds', async () => {
|
|
35
|
-
let attempts = 0
|
|
36
|
-
const call = async () => {
|
|
37
|
-
attempts++
|
|
38
|
-
if (attempts < 3) throw new Error('fail')
|
|
39
|
-
return 'ok'
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
await expect(retry(call)).resolves.toBe('ok')
|
|
43
|
-
expect(attempts).toBe(3)
|
|
44
|
-
})
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Before writing the body, name the production change that would make this test fail. If you cannot name one, the test is not testing behaviour. More in [references/writing-good-tests.md](references/writing-good-tests.md).
|
|
48
|
-
|
|
49
|
-
### Verify red: run it and read the failure
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
pnpm vitest run src/retry.test.ts
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
- It must **fail**, not error. A typo, a missing import or a syntax error is not a red test.
|
|
56
|
-
- The failure message must be the one you expect ("expected 'ok', received undefined").
|
|
57
|
-
- It must fail because the behaviour is missing, not because of setup.
|
|
58
|
-
- If it passes immediately, it tests existing behaviour or nothing at all: fix the test.
|
|
59
|
-
|
|
60
|
-
### Green: the minimal code that passes
|
|
61
|
-
|
|
62
|
-
Write the simplest code that makes this test pass. No extra options, no handling for cases no test asks for yet. Do not change the test to fit the code.
|
|
63
|
-
|
|
64
|
-
### Verify green
|
|
65
|
-
|
|
66
|
-
Run the test again, then the tests around it, then the project's full suite (or the widest suite that runs in reasonable time). A green file is not a green suite. Output should be clean: no new warnings or errors in the log.
|
|
67
|
-
|
|
68
|
-
Any failure in the wider run goes in the report by name, including ones you did not cause (check against the baseline from `project-orientation`).
|
|
69
|
-
|
|
70
|
-
### Refactor
|
|
71
|
-
|
|
72
|
-
Only on green: remove duplication, improve names, extract helpers. Re-run after each change. Do not add behaviour while refactoring.
|
|
73
|
-
|
|
74
|
-
Repeat for the next behaviour: one test, one minimal implementation, at a time. Do not write the whole test file first.
|
|
75
|
-
|
|
76
|
-
## If you wrote the code first
|
|
77
|
-
|
|
78
|
-
It happens: an exploration turned into the fix, or the change was made before this skill was loaded. Do not pretend. Prove the test can fail:
|
|
79
|
-
|
|
80
|
-
1. Save your change to the production file: `git diff -- path/to/file > "$TMPDIR/fix.patch"` (any temp location outside the repository).
|
|
81
|
-
2. Restore the original: `git checkout -- path/to/file`.
|
|
82
|
-
3. Run the new test and confirm it fails for the right reason.
|
|
83
|
-
4. Re-apply: `git apply "$TMPDIR/fix.patch"`, check with `git diff --stat` that the change is back, and run the test again to see it pass.
|
|
84
|
-
|
|
85
|
-
If the test passes without your change, it does not cover the change. Fix the test.
|
|
86
|
-
|
|
87
|
-
## Bug fixes
|
|
88
|
-
|
|
89
|
-
1. Write a test that reproduces the reported bug exactly (the input, the wrong output). For a hard bug, follow `systematic-debugging` first.
|
|
90
|
-
2. Watch it fail with the symptom from the report.
|
|
91
|
-
3. Fix the cause. Watch it pass.
|
|
92
|
-
4. Run the suite.
|
|
93
|
-
|
|
94
|
-
## Keep tests deterministic
|
|
95
|
-
|
|
96
|
-
Fixed dates and time zones, seeded random data, no dependence on test order, no real network, temp folders per test. Wait for conditions, not for time (see `systematic-debugging`, flaky tests reference).
|
|
97
|
-
|
|
98
|
-
## Common rationalisations
|
|
99
|
-
|
|
100
|
-
| Excuse | Reality |
|
|
101
|
-
|---|---|
|
|
102
|
-
| "Too simple to test" | Simple code breaks too; the test takes a minute. |
|
|
103
|
-
| "I'll add tests after" | Tests written after pass immediately and prove nothing; they check what you built, not what was asked. |
|
|
104
|
-
| "I already tried it by hand" | A manual try leaves no record and is not repeated on the next change. |
|
|
105
|
-
| "The existing code has no tests" | Add tests for the part you change, in the project's framework. |
|
|
106
|
-
| "Hard to test" | Often a sign the design is hard to use. Find a better seam, or report the gap. |
|
|
107
|
-
| "I need to mock everything" | The code is too coupled, or the test is at the wrong level. Test higher up with real collaborators. |
|
|
108
|
-
|
|
109
|
-
## Done when
|
|
110
|
-
|
|
111
|
-
- [ ] Each new behaviour and each fixed bug has a test that you saw fail for the right reason, then pass.
|
|
112
|
-
- [ ] Tests assert real behaviour through the public surface, with expected values written out by hand.
|
|
113
|
-
- [ ] The full relevant suite passes, or failures are listed with their cause (yours fixed, pre-existing reported).
|
|
114
|
-
- [ ] The report says which tests were added, or why none were (no test setup, no seam, not applicable).
|
|
115
|
-
|
|
116
|
-
Sources: obra/superpowers (MIT, commit bb92a77) test-driven-development. Changed: rewritten for unattended work in existing repositories.
|
|
1
|
+
---
|
|
2
|
+
name: test-driven-development
|
|
3
|
+
description: Use when adding behaviour or fixing a bug in a project, before writing the implementation: write a test in the project's own framework, watch it fail for the right reason, make it pass with minimal code, then run the suite. Covers what to do when the project has no test setup.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Test-driven development
|
|
7
|
+
|
|
8
|
+
If you never saw a test fail, you do not know that it tests anything. Write the test first, watch it fail, then write the code.
|
|
9
|
+
|
|
10
|
+
## Step 0: Find the project's test setup
|
|
11
|
+
|
|
12
|
+
Before anything else (see `project-orientation`):
|
|
13
|
+
|
|
14
|
+
- Which framework, where tests live, how they are named, which helpers and fixtures exist, and the exact command to run one file and the whole suite.
|
|
15
|
+
- Copy the shape of a neighbouring test: imports, setup, naming, assertion style.
|
|
16
|
+
|
|
17
|
+
**If the project has no test setup at all:** do not add a test framework, new dependencies or a test folder structure the maintainers did not choose. Verify the change another way that you can show evidence for (type check, build, running the code with a fixture input, a throwaway script outside the repository) and write in the report: "No test setup in this repository; verified by: ...".
|
|
18
|
+
|
|
19
|
+
**If tests exist but not for this area:** add the test in the existing framework, next to the code, following the closest existing example.
|
|
20
|
+
|
|
21
|
+
## When to use it, and when not
|
|
22
|
+
|
|
23
|
+
Use it for new behaviour, bug fixes, and behaviour changes in code that has a reasonable seam to test at.
|
|
24
|
+
|
|
25
|
+
Usually skip it, and say so in the report, for: configuration-only changes, generated code, pure markup or styling with no logic, copy changes, dependency bumps, and code with no seam that can show the behaviour (then explain the gap rather than writing a hollow test). Exploratory spikes are fine, but throw the spike away or start the test-first cycle from it before you commit.
|
|
26
|
+
|
|
27
|
+
## The cycle
|
|
28
|
+
|
|
29
|
+
### Red: write one failing test
|
|
30
|
+
|
|
31
|
+
One behaviour per test, a name that reads as a sentence about it, real code rather than mocks where that is cheap.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
test('retries a failing call up to 3 times, then succeeds', async () => {
|
|
35
|
+
let attempts = 0
|
|
36
|
+
const call = async () => {
|
|
37
|
+
attempts++
|
|
38
|
+
if (attempts < 3) throw new Error('fail')
|
|
39
|
+
return 'ok'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await expect(retry(call)).resolves.toBe('ok')
|
|
43
|
+
expect(attempts).toBe(3)
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Before writing the body, name the production change that would make this test fail. If you cannot name one, the test is not testing behaviour. More in [references/writing-good-tests.md](references/writing-good-tests.md).
|
|
48
|
+
|
|
49
|
+
### Verify red: run it and read the failure
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
pnpm vitest run src/retry.test.ts
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- It must **fail**, not error. A typo, a missing import or a syntax error is not a red test.
|
|
56
|
+
- The failure message must be the one you expect ("expected 'ok', received undefined").
|
|
57
|
+
- It must fail because the behaviour is missing, not because of setup.
|
|
58
|
+
- If it passes immediately, it tests existing behaviour or nothing at all: fix the test.
|
|
59
|
+
|
|
60
|
+
### Green: the minimal code that passes
|
|
61
|
+
|
|
62
|
+
Write the simplest code that makes this test pass. No extra options, no handling for cases no test asks for yet. Do not change the test to fit the code.
|
|
63
|
+
|
|
64
|
+
### Verify green
|
|
65
|
+
|
|
66
|
+
Run the test again, then the tests around it, then the project's full suite (or the widest suite that runs in reasonable time). A green file is not a green suite. Output should be clean: no new warnings or errors in the log.
|
|
67
|
+
|
|
68
|
+
Any failure in the wider run goes in the report by name, including ones you did not cause (check against the baseline from `project-orientation`).
|
|
69
|
+
|
|
70
|
+
### Refactor
|
|
71
|
+
|
|
72
|
+
Only on green: remove duplication, improve names, extract helpers. Re-run after each change. Do not add behaviour while refactoring.
|
|
73
|
+
|
|
74
|
+
Repeat for the next behaviour: one test, one minimal implementation, at a time. Do not write the whole test file first.
|
|
75
|
+
|
|
76
|
+
## If you wrote the code first
|
|
77
|
+
|
|
78
|
+
It happens: an exploration turned into the fix, or the change was made before this skill was loaded. Do not pretend. Prove the test can fail:
|
|
79
|
+
|
|
80
|
+
1. Save your change to the production file: `git diff -- path/to/file > "$TMPDIR/fix.patch"` (any temp location outside the repository).
|
|
81
|
+
2. Restore the original: `git checkout -- path/to/file`.
|
|
82
|
+
3. Run the new test and confirm it fails for the right reason.
|
|
83
|
+
4. Re-apply: `git apply "$TMPDIR/fix.patch"`, check with `git diff --stat` that the change is back, and run the test again to see it pass.
|
|
84
|
+
|
|
85
|
+
If the test passes without your change, it does not cover the change. Fix the test.
|
|
86
|
+
|
|
87
|
+
## Bug fixes
|
|
88
|
+
|
|
89
|
+
1. Write a test that reproduces the reported bug exactly (the input, the wrong output). For a hard bug, follow `systematic-debugging` first.
|
|
90
|
+
2. Watch it fail with the symptom from the report.
|
|
91
|
+
3. Fix the cause. Watch it pass.
|
|
92
|
+
4. Run the suite.
|
|
93
|
+
|
|
94
|
+
## Keep tests deterministic
|
|
95
|
+
|
|
96
|
+
Fixed dates and time zones, seeded random data, no dependence on test order, no real network, temp folders per test. Wait for conditions, not for time (see `systematic-debugging`, flaky tests reference).
|
|
97
|
+
|
|
98
|
+
## Common rationalisations
|
|
99
|
+
|
|
100
|
+
| Excuse | Reality |
|
|
101
|
+
|---|---|
|
|
102
|
+
| "Too simple to test" | Simple code breaks too; the test takes a minute. |
|
|
103
|
+
| "I'll add tests after" | Tests written after pass immediately and prove nothing; they check what you built, not what was asked. |
|
|
104
|
+
| "I already tried it by hand" | A manual try leaves no record and is not repeated on the next change. |
|
|
105
|
+
| "The existing code has no tests" | Add tests for the part you change, in the project's framework. |
|
|
106
|
+
| "Hard to test" | Often a sign the design is hard to use. Find a better seam, or report the gap. |
|
|
107
|
+
| "I need to mock everything" | The code is too coupled, or the test is at the wrong level. Test higher up with real collaborators. |
|
|
108
|
+
|
|
109
|
+
## Done when
|
|
110
|
+
|
|
111
|
+
- [ ] Each new behaviour and each fixed bug has a test that you saw fail for the right reason, then pass.
|
|
112
|
+
- [ ] Tests assert real behaviour through the public surface, with expected values written out by hand.
|
|
113
|
+
- [ ] The full relevant suite passes, or failures are listed with their cause (yours fixed, pre-existing reported).
|
|
114
|
+
- [ ] The report says which tests were added, or why none were (no test setup, no seam, not applicable).
|
|
115
|
+
|
|
116
|
+
Sources: obra/superpowers (MIT, commit bb92a77) test-driven-development. Changed: rewritten for unattended work in existing repositories.
|
|
@@ -1,85 +1,85 @@
|
|
|
1
|
-
# Writing good tests
|
|
2
|
-
|
|
3
|
-
Read this when writing or changing a test, adding a mock, or adding a test helper.
|
|
4
|
-
|
|
5
|
-
A test exists to catch a specific break. Two rules carry most of the weight:
|
|
6
|
-
|
|
7
|
-
1. Every test names the break it catches.
|
|
8
|
-
2. Every test exercises the real thing.
|
|
9
|
-
|
|
10
|
-
## Rule 1: name the break
|
|
11
|
-
|
|
12
|
-
Before writing the body, answer: which production change would make this test fail, and is that change a bug or a deliberate decision? A good test catches a wrong branch, a missing side effect, a wrong argument, a boundary case or a broken contract.
|
|
13
|
-
|
|
14
|
-
**Derive expected values independently.** Use literals and hand-checked fixtures. An expected value computed by the code under test (or its helpers) passes whatever the code does.
|
|
15
|
-
|
|
16
|
-
```ts
|
|
17
|
-
// Bad: the same builder produces both sides, so it is always equal
|
|
18
|
-
const expected = buildSearchQuery({ tag: 'urgent' })
|
|
19
|
-
expect(buildSearchQuery({ tag: 'urgent' })).toBe(expected)
|
|
20
|
-
|
|
21
|
-
// Bad: recomputes the answer the way the code does
|
|
22
|
-
expect(add(a, b)).toBe(a + b)
|
|
23
|
-
|
|
24
|
-
// Good: written out by hand
|
|
25
|
-
expect(buildSearchQuery({ tag: 'urgent' })).toBe('tag:"urgent"')
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Table-driven tests with literal expected values are a good shape for many cases.
|
|
29
|
-
|
|
30
|
-
**No change detectors.** A test that only fails on intentional decisions (a constant's value, exact message wording, private structure) fires on every redesign and sleeps through real bugs. Test the behaviour that depends on the decision: not `expect(MAX_RETRIES).toBe(5)` but "a failing call is retried 5 times and a sixth attempt never happens".
|
|
31
|
-
|
|
32
|
-
**Behaviour, not source text.** Asserting that a file contains a certain line proves only that the file is the file. Run the script, config or template against a controlled input and check the output, side effect or exit code.
|
|
33
|
-
|
|
34
|
-
**Your code, not the framework.** Test the contract your code makes at its boundary: the route you register, the query you build, the payload you send. Do not test that the router calls a registered handler; that is the framework's job. Constructors, getters and simple pass-throughs earn a test only when they validate, normalise, default, derive or cause a side effect.
|
|
35
|
-
|
|
36
|
-
**Through the public surface.** Test via the function, endpoint, component or command that callers use. A test that breaks when you refactor internals without changing behaviour is coupled to implementation. Avoid checking results through a side channel (querying the database directly) when the public interface can show them.
|
|
37
|
-
|
|
38
|
-
## Rule 2: exercise the real thing
|
|
39
|
-
|
|
40
|
-
**Never assert on a mock.** An assertion that a mock was rendered or exists passes when the mock is there and says nothing about the component.
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
// Bad: tests the mock
|
|
44
|
-
expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument()
|
|
45
|
-
|
|
46
|
-
// Good: tests the real behaviour
|
|
47
|
-
expect(screen.getByRole('navigation')).toBeInTheDocument()
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
**Mock at the right level.** Before replacing a method, learn all its side effects. Mock the slow or external part below it (network, paid API, clock, email) and keep what the test depends on real. A mock that swallows a write that the code later reads makes the test pass while the real thing breaks.
|
|
51
|
-
|
|
52
|
-
**Make doubles specific.** When arguments, call counts or order are part of the contract, assert them. Give success, error and malformed responses their own fixtures, so the wrong branch cannot satisfy the test.
|
|
53
|
-
|
|
54
|
-
**Mirror real data completely.** Fake responses should have the full structure of the real one, not only the fields this test reads. Partial fakes hide failures in code that reads the other fields.
|
|
55
|
-
|
|
56
|
-
**Production code carries production methods only.** Cleanup or reset methods that only tests call belong in test utilities, not on the production class.
|
|
57
|
-
|
|
58
|
-
**Prefer real components over elaborate mocks.** When mock setup is longer than the test logic, or the test breaks whenever the mock changes, switch to a test with real collaborators (an in-memory database, the real module, the project's existing test server).
|
|
59
|
-
|
|
60
|
-
## The mutation check
|
|
61
|
-
|
|
62
|
-
Before finishing, imagine small breaks in the production code. At least one test should fail for each realistic one:
|
|
63
|
-
|
|
64
|
-
- A wrong constant or argument.
|
|
65
|
-
- A wrong branch taken.
|
|
66
|
-
- A missing state change or side effect.
|
|
67
|
-
- An empty or default return value.
|
|
68
|
-
- Missing validation for zero, empty, null, unauthorised or malformed input.
|
|
69
|
-
|
|
70
|
-
If nothing fails for a realistic mutation, that behaviour is unprotected. For an important change you can make the mutation for real: invert one condition, run the tests, confirm a failure, restore the line, and confirm with `git diff` that it is restored.
|
|
71
|
-
|
|
72
|
-
## Warning signs
|
|
73
|
-
|
|
74
|
-
- Setup and assertion share the same object, so they are equal by construction.
|
|
75
|
-
- The test can only fail by crashing or by a missing selector.
|
|
76
|
-
- The test fails on every intentional change but never on accidental breakage.
|
|
77
|
-
- Expected values are hidden behind loops, builders or helpers.
|
|
78
|
-
- The test greps source text.
|
|
79
|
-
- The test exists for coverage and checks no outcome.
|
|
80
|
-
- An assertion checks a `*-mock` test id, or fails if you remove the mock.
|
|
81
|
-
- A method is called only from test files.
|
|
82
|
-
- Mock setup is more than half the test, or you cannot say why the mock is needed.
|
|
83
|
-
- Writing all the tests for a feature before any implementation: tests for imagined behaviour test the shape of things, not what users see. Go one test at a time.
|
|
84
|
-
|
|
85
|
-
Sources: obra/superpowers (MIT, commit bb92a77) writing-good-tests and mattpocock/skills (MIT, commit 5beb35b) tdd. Changed: shortened and merged.
|
|
1
|
+
# Writing good tests
|
|
2
|
+
|
|
3
|
+
Read this when writing or changing a test, adding a mock, or adding a test helper.
|
|
4
|
+
|
|
5
|
+
A test exists to catch a specific break. Two rules carry most of the weight:
|
|
6
|
+
|
|
7
|
+
1. Every test names the break it catches.
|
|
8
|
+
2. Every test exercises the real thing.
|
|
9
|
+
|
|
10
|
+
## Rule 1: name the break
|
|
11
|
+
|
|
12
|
+
Before writing the body, answer: which production change would make this test fail, and is that change a bug or a deliberate decision? A good test catches a wrong branch, a missing side effect, a wrong argument, a boundary case or a broken contract.
|
|
13
|
+
|
|
14
|
+
**Derive expected values independently.** Use literals and hand-checked fixtures. An expected value computed by the code under test (or its helpers) passes whatever the code does.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// Bad: the same builder produces both sides, so it is always equal
|
|
18
|
+
const expected = buildSearchQuery({ tag: 'urgent' })
|
|
19
|
+
expect(buildSearchQuery({ tag: 'urgent' })).toBe(expected)
|
|
20
|
+
|
|
21
|
+
// Bad: recomputes the answer the way the code does
|
|
22
|
+
expect(add(a, b)).toBe(a + b)
|
|
23
|
+
|
|
24
|
+
// Good: written out by hand
|
|
25
|
+
expect(buildSearchQuery({ tag: 'urgent' })).toBe('tag:"urgent"')
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Table-driven tests with literal expected values are a good shape for many cases.
|
|
29
|
+
|
|
30
|
+
**No change detectors.** A test that only fails on intentional decisions (a constant's value, exact message wording, private structure) fires on every redesign and sleeps through real bugs. Test the behaviour that depends on the decision: not `expect(MAX_RETRIES).toBe(5)` but "a failing call is retried 5 times and a sixth attempt never happens".
|
|
31
|
+
|
|
32
|
+
**Behaviour, not source text.** Asserting that a file contains a certain line proves only that the file is the file. Run the script, config or template against a controlled input and check the output, side effect or exit code.
|
|
33
|
+
|
|
34
|
+
**Your code, not the framework.** Test the contract your code makes at its boundary: the route you register, the query you build, the payload you send. Do not test that the router calls a registered handler; that is the framework's job. Constructors, getters and simple pass-throughs earn a test only when they validate, normalise, default, derive or cause a side effect.
|
|
35
|
+
|
|
36
|
+
**Through the public surface.** Test via the function, endpoint, component or command that callers use. A test that breaks when you refactor internals without changing behaviour is coupled to implementation. Avoid checking results through a side channel (querying the database directly) when the public interface can show them.
|
|
37
|
+
|
|
38
|
+
## Rule 2: exercise the real thing
|
|
39
|
+
|
|
40
|
+
**Never assert on a mock.** An assertion that a mock was rendered or exists passes when the mock is there and says nothing about the component.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// Bad: tests the mock
|
|
44
|
+
expect(screen.getByTestId('sidebar-mock')).toBeInTheDocument()
|
|
45
|
+
|
|
46
|
+
// Good: tests the real behaviour
|
|
47
|
+
expect(screen.getByRole('navigation')).toBeInTheDocument()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Mock at the right level.** Before replacing a method, learn all its side effects. Mock the slow or external part below it (network, paid API, clock, email) and keep what the test depends on real. A mock that swallows a write that the code later reads makes the test pass while the real thing breaks.
|
|
51
|
+
|
|
52
|
+
**Make doubles specific.** When arguments, call counts or order are part of the contract, assert them. Give success, error and malformed responses their own fixtures, so the wrong branch cannot satisfy the test.
|
|
53
|
+
|
|
54
|
+
**Mirror real data completely.** Fake responses should have the full structure of the real one, not only the fields this test reads. Partial fakes hide failures in code that reads the other fields.
|
|
55
|
+
|
|
56
|
+
**Production code carries production methods only.** Cleanup or reset methods that only tests call belong in test utilities, not on the production class.
|
|
57
|
+
|
|
58
|
+
**Prefer real components over elaborate mocks.** When mock setup is longer than the test logic, or the test breaks whenever the mock changes, switch to a test with real collaborators (an in-memory database, the real module, the project's existing test server).
|
|
59
|
+
|
|
60
|
+
## The mutation check
|
|
61
|
+
|
|
62
|
+
Before finishing, imagine small breaks in the production code. At least one test should fail for each realistic one:
|
|
63
|
+
|
|
64
|
+
- A wrong constant or argument.
|
|
65
|
+
- A wrong branch taken.
|
|
66
|
+
- A missing state change or side effect.
|
|
67
|
+
- An empty or default return value.
|
|
68
|
+
- Missing validation for zero, empty, null, unauthorised or malformed input.
|
|
69
|
+
|
|
70
|
+
If nothing fails for a realistic mutation, that behaviour is unprotected. For an important change you can make the mutation for real: invert one condition, run the tests, confirm a failure, restore the line, and confirm with `git diff` that it is restored.
|
|
71
|
+
|
|
72
|
+
## Warning signs
|
|
73
|
+
|
|
74
|
+
- Setup and assertion share the same object, so they are equal by construction.
|
|
75
|
+
- The test can only fail by crashing or by a missing selector.
|
|
76
|
+
- The test fails on every intentional change but never on accidental breakage.
|
|
77
|
+
- Expected values are hidden behind loops, builders or helpers.
|
|
78
|
+
- The test greps source text.
|
|
79
|
+
- The test exists for coverage and checks no outcome.
|
|
80
|
+
- An assertion checks a `*-mock` test id, or fails if you remove the mock.
|
|
81
|
+
- A method is called only from test files.
|
|
82
|
+
- Mock setup is more than half the test, or you cannot say why the mock is needed.
|
|
83
|
+
- Writing all the tests for a feature before any implementation: tests for imagined behaviour test the shape of things, not what users see. Go one test at a time.
|
|
84
|
+
|
|
85
|
+
Sources: obra/superpowers (MIT, commit bb92a77) writing-good-tests and mattpocock/skills (MIT, commit 5beb35b) tdd. Changed: shortened and merged.
|