@kodrunhq/opencode-autopilot 1.4.0 → 1.5.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/assets/commands/brainstorm.md +7 -0
- package/assets/commands/stocktake.md +7 -0
- package/assets/commands/tdd.md +7 -0
- package/assets/commands/update-docs.md +7 -0
- package/assets/commands/write-plan.md +7 -0
- package/assets/skills/brainstorming/SKILL.md +295 -0
- package/assets/skills/code-review/SKILL.md +241 -0
- package/assets/skills/e2e-testing/SKILL.md +266 -0
- package/assets/skills/git-worktrees/SKILL.md +296 -0
- package/assets/skills/go-patterns/SKILL.md +240 -0
- package/assets/skills/plan-executing/SKILL.md +258 -0
- package/assets/skills/plan-writing/SKILL.md +278 -0
- package/assets/skills/python-patterns/SKILL.md +255 -0
- package/assets/skills/rust-patterns/SKILL.md +293 -0
- package/assets/skills/strategic-compaction/SKILL.md +217 -0
- package/assets/skills/systematic-debugging/SKILL.md +299 -0
- package/assets/skills/tdd-workflow/SKILL.md +311 -0
- package/assets/skills/typescript-patterns/SKILL.md +278 -0
- package/assets/skills/verification/SKILL.md +240 -0
- package/package.json +1 -1
- package/src/index.ts +4 -0
- package/src/orchestrator/skill-injection.ts +38 -0
- package/src/review/sanitize.ts +1 -1
- package/src/skills/adaptive-injector.ts +122 -0
- package/src/skills/dependency-resolver.ts +88 -0
- package/src/skills/linter.ts +113 -0
- package/src/skills/loader.ts +88 -0
- package/src/templates/skill-template.ts +4 -0
- package/src/tools/create-skill.ts +12 -0
- package/src/tools/stocktake.ts +170 -0
- package/src/tools/update-docs.ts +116 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: systematic-debugging
|
|
3
|
+
description: 4-phase root cause analysis methodology for systematic bug diagnosis and resolution
|
|
4
|
+
stacks: []
|
|
5
|
+
requires: []
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Systematic Debugging
|
|
9
|
+
|
|
10
|
+
A disciplined 4-phase methodology for diagnosing and fixing bugs: Reproduce, Isolate, Diagnose, Fix. This skill replaces ad-hoc debugging (changing things until it works) with a systematic process that finds the root cause and prevents recurrence.
|
|
11
|
+
|
|
12
|
+
Every bug fix should produce a regression test. A bug fixed without a test is a bug that will return.
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
**Activate this skill when:**
|
|
17
|
+
|
|
18
|
+
- A bug report comes in (user-reported, automated alert, test failure)
|
|
19
|
+
- Tests fail unexpectedly after a change
|
|
20
|
+
- Behavior doesn't match specification or documentation
|
|
21
|
+
- Performance degrades without an obvious cause
|
|
22
|
+
- Integration between modules produces unexpected results
|
|
23
|
+
- A production incident requires root cause analysis
|
|
24
|
+
|
|
25
|
+
**Do NOT use when:**
|
|
26
|
+
|
|
27
|
+
- The issue is a feature request, not a bug
|
|
28
|
+
- The fix is obvious and trivial (typo, missing import, wrong config value)
|
|
29
|
+
- The issue has a known fix documented in the codebase or issue tracker
|
|
30
|
+
- You need a code review (use the code-review skill instead)
|
|
31
|
+
|
|
32
|
+
## The 4-Phase Debugging Process
|
|
33
|
+
|
|
34
|
+
Follow the phases in order. Do not skip phases. The most common debugging mistake is jumping to Phase 4 (Fix) before completing Phase 3 (Diagnose).
|
|
35
|
+
|
|
36
|
+
### Phase 1: Reproduce
|
|
37
|
+
|
|
38
|
+
**Purpose:** Confirm the bug exists and get a reliable way to trigger it.
|
|
39
|
+
|
|
40
|
+
**Process:**
|
|
41
|
+
|
|
42
|
+
1. Read the bug report carefully. Extract the exact steps, inputs, and expected vs actual behavior.
|
|
43
|
+
2. Reproduce the bug locally using the reported steps.
|
|
44
|
+
3. If the bug reproduces, create a MINIMAL reproduction case:
|
|
45
|
+
- Strip away everything not needed to trigger the bug
|
|
46
|
+
- The minimal case should be a single test or a 5-10 line script
|
|
47
|
+
- Document the exact command to run: `bun test tests/auth.test.ts -t "rejects expired tokens"`
|
|
48
|
+
4. If the bug does NOT reproduce:
|
|
49
|
+
- Check environment differences (OS, runtime version, config)
|
|
50
|
+
- Check input data differences (encoding, edge cases, null values)
|
|
51
|
+
- Check timing differences (race conditions, async ordering)
|
|
52
|
+
- Ask for more context: logs, screenshots, exact input data
|
|
53
|
+
5. Record the reproduction steps for the regression test in Phase 4.
|
|
54
|
+
|
|
55
|
+
**Output:** A reproducible test case or script that triggers the bug on demand.
|
|
56
|
+
|
|
57
|
+
**Exit criterion:** You can trigger the bug reliably. If you cannot reproduce it after 15 minutes, escalate for more information.
|
|
58
|
+
|
|
59
|
+
**A bug you cannot reproduce is a bug you cannot fix.** Do not proceed to Phase 2 until you have a reproduction.
|
|
60
|
+
|
|
61
|
+
### Phase 2: Isolate
|
|
62
|
+
|
|
63
|
+
**Purpose:** Narrow the scope from "the whole system" to "this specific function/line."
|
|
64
|
+
|
|
65
|
+
**Process:**
|
|
66
|
+
|
|
67
|
+
1. Start with the reproduction case from Phase 1.
|
|
68
|
+
2. **Binary search the codebase:** Comment out or bypass half the code path. Does the bug persist?
|
|
69
|
+
- If yes: the bug is in the remaining half. Repeat.
|
|
70
|
+
- If no: the bug is in the removed half. Restore and bisect that half.
|
|
71
|
+
3. **Check recent changes:** The bug may have been introduced recently.
|
|
72
|
+
```
|
|
73
|
+
git log --oneline -20
|
|
74
|
+
git diff HEAD~5
|
|
75
|
+
git bisect start
|
|
76
|
+
git bisect bad HEAD
|
|
77
|
+
git bisect good <known-good-commit>
|
|
78
|
+
```
|
|
79
|
+
4. **Add strategic logging** at module boundaries:
|
|
80
|
+
- Log inputs and outputs at each function call in the chain
|
|
81
|
+
- Compare expected vs actual values at each step
|
|
82
|
+
- The first point where actual diverges from expected is the bug location
|
|
83
|
+
5. **Check the call stack:** If the bug produces an error, read the full stack trace. The bug is usually near the top of the stack, but the root cause may be deeper.
|
|
84
|
+
|
|
85
|
+
**Output:** The exact function, file, and approximate line number where behavior diverges from expectation.
|
|
86
|
+
|
|
87
|
+
**Exit criterion:** You can point to a specific code location and say "the bug is here because [expected X but got Y]."
|
|
88
|
+
|
|
89
|
+
**Isolation tips:**
|
|
90
|
+
|
|
91
|
+
- If the code path is long, log at 3-4 strategic points first (entry, middle, exit, error path)
|
|
92
|
+
- If the bug is intermittent, add logging and run the reproduction 10 times to collect data
|
|
93
|
+
- If the bug only happens in production, check for environment-specific behavior (env vars, feature flags, data volume)
|
|
94
|
+
|
|
95
|
+
### Phase 3: Diagnose
|
|
96
|
+
|
|
97
|
+
**Purpose:** Understand WHY the bug exists, not just WHERE it is. The difference matters -- knowing where tells you what to change, knowing why tells you what to change it TO.
|
|
98
|
+
|
|
99
|
+
**Process:**
|
|
100
|
+
|
|
101
|
+
1. Read the code path end-to-end from the entry point to the bug location (Phase 2 output).
|
|
102
|
+
2. For each function in the path, check these assumptions:
|
|
103
|
+
- **Types:** Is the value the expected type? (Watch for implicit coercion, especially in JS/TS)
|
|
104
|
+
- **Null/undefined:** Can the value be null where the code assumes it's defined?
|
|
105
|
+
- **Async timing:** Are operations completing in the expected order? Are there missing awaits?
|
|
106
|
+
- **State mutation:** Is an object being modified in place when the caller expects immutability?
|
|
107
|
+
- **Boundary values:** Are off-by-one errors possible? (Array indices, string slicing, pagination)
|
|
108
|
+
- **Error handling:** Is an error being caught and swallowed somewhere in the chain?
|
|
109
|
+
3. Identify the root cause category (see Common Root Cause Patterns below).
|
|
110
|
+
4. Verify the diagnosis: can you predict the exact output given the root cause? If your diagnosis is correct, you should be able to predict the bug's behavior for any input.
|
|
111
|
+
|
|
112
|
+
**Output:** A one-paragraph explanation of WHY the bug exists, referencing the specific code and the root cause pattern.
|
|
113
|
+
|
|
114
|
+
**Exit criterion:** You can explain the bug to someone who has never seen the code, and they understand why it happens.
|
|
115
|
+
|
|
116
|
+
### Phase 4: Fix
|
|
117
|
+
|
|
118
|
+
**Purpose:** Apply the minimal fix and prevent recurrence with a regression test.
|
|
119
|
+
|
|
120
|
+
**Process:**
|
|
121
|
+
|
|
122
|
+
1. **Write the regression test FIRST** (TDD-style):
|
|
123
|
+
- The test should reproduce the exact bug from Phase 1
|
|
124
|
+
- Run the test -- it MUST fail (confirming the bug exists)
|
|
125
|
+
- The test becomes a permanent guard against recurrence
|
|
126
|
+
2. **Apply the minimal fix:**
|
|
127
|
+
- Change only what is needed to fix the root cause (Phase 3 output)
|
|
128
|
+
- Do not refactor adjacent code in the same change
|
|
129
|
+
- Do not add unrelated improvements
|
|
130
|
+
3. **Verify the fix:**
|
|
131
|
+
- Run the regression test -- it MUST pass
|
|
132
|
+
- Run ALL existing tests -- they MUST still pass (no regressions)
|
|
133
|
+
- Run the original reproduction case from Phase 1 -- bug should be gone
|
|
134
|
+
4. **Search for similar patterns:**
|
|
135
|
+
- The same bug often exists in multiple places in the codebase
|
|
136
|
+
- Search for the same pattern: `grep -rn "similar_pattern" src/`
|
|
137
|
+
- If found, fix those too and add regression tests for each
|
|
138
|
+
|
|
139
|
+
**Output:** A fix commit with a regression test and a brief explanation of the root cause.
|
|
140
|
+
|
|
141
|
+
**Commit format:**
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
fix: [brief description of what was wrong]
|
|
145
|
+
|
|
146
|
+
Root cause: [one sentence explaining why the bug existed]
|
|
147
|
+
Regression test: [test name that guards against recurrence]
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Common Root Cause Patterns
|
|
151
|
+
|
|
152
|
+
### Race Conditions
|
|
153
|
+
|
|
154
|
+
**What happens:** Async operations complete in an unexpected order. Operation B reads data before Operation A finishes writing it.
|
|
155
|
+
|
|
156
|
+
**Signs:** Bug is intermittent. Bug disappears with added logging (timing changes). Bug only appears under load.
|
|
157
|
+
|
|
158
|
+
**Fix pattern:** Add proper awaiting, use locks/mutexes, or redesign to eliminate the shared state.
|
|
159
|
+
|
|
160
|
+
### State Mutation
|
|
161
|
+
|
|
162
|
+
**What happens:** An object is modified in place when the caller expected the original to be unchanged. Function A passes an object to Function B, which mutates it, and Function A's subsequent code uses the now-changed object.
|
|
163
|
+
|
|
164
|
+
**Signs:** Values change "mysteriously" between operations. Adding a `structuredClone` before the call fixes the bug.
|
|
165
|
+
|
|
166
|
+
**Fix pattern:** Clone objects at function boundaries. Use spread operators to create new objects. Follow immutability patterns.
|
|
167
|
+
|
|
168
|
+
### Boundary Errors
|
|
169
|
+
|
|
170
|
+
**What happens:** Off-by-one errors in array indexing, string slicing, pagination, or loop bounds. Empty collections handled incorrectly.
|
|
171
|
+
|
|
172
|
+
**Signs:** Bug only appears with certain input sizes (empty, one element, exactly N elements). Bug appears at page boundaries.
|
|
173
|
+
|
|
174
|
+
**Fix pattern:** Test with 0, 1, N, N+1 elements. Use inclusive/exclusive bounds consistently. Handle empty inputs explicitly.
|
|
175
|
+
|
|
176
|
+
### Type Coercion
|
|
177
|
+
|
|
178
|
+
**What happens:** Implicit type conversions produce unexpected values. String "0" treated as falsy. Number comparison on string values.
|
|
179
|
+
|
|
180
|
+
**Signs:** Bug only appears with specific values (0, empty string, null, NaN). Comparison operators behave unexpectedly.
|
|
181
|
+
|
|
182
|
+
**Fix pattern:** Use strict equality (`===`). Explicit type conversion before comparison. Schema validation at input boundaries.
|
|
183
|
+
|
|
184
|
+
### Stale Closures
|
|
185
|
+
|
|
186
|
+
**What happens:** A callback captures a variable's value at creation time, not at execution time. By the time the callback runs, the variable has changed.
|
|
187
|
+
|
|
188
|
+
**Signs:** Bug only appears in async code or event handlers. The value in the callback is always the "old" value. Adding a log shows the variable changed between capture and execution.
|
|
189
|
+
|
|
190
|
+
**Fix pattern:** Capture the current value in a local variable. Use function arguments instead of closures. In React: add missing dependencies to useEffect/useCallback.
|
|
191
|
+
|
|
192
|
+
### Missing Error Handling
|
|
193
|
+
|
|
194
|
+
**What happens:** An error occurs but is caught and silently swallowed. The caller receives undefined/null instead of an error, and proceeds with invalid data.
|
|
195
|
+
|
|
196
|
+
**Signs:** No error in logs, but behavior is wrong. Adding a throw in the catch block reveals the actual error. Values are unexpectedly null/undefined deep in the call chain.
|
|
197
|
+
|
|
198
|
+
**Fix pattern:** Never use empty catch blocks. Always log the error with context. Re-throw or return a meaningful error value.
|
|
199
|
+
|
|
200
|
+
### Incorrect Assumptions About External Data
|
|
201
|
+
|
|
202
|
+
**What happens:** Code assumes an API response, file content, or user input has a certain shape, but the actual data differs (missing fields, different types, unexpected nulls).
|
|
203
|
+
|
|
204
|
+
**Signs:** Bug only appears with certain inputs or after an external service changes. Works in tests (mocked data) but fails in production (real data).
|
|
205
|
+
|
|
206
|
+
**Fix pattern:** Validate external data at the boundary with a schema. Handle missing/unexpected fields explicitly. Never assume the shape of data you don't control.
|
|
207
|
+
|
|
208
|
+
## Anti-Pattern Catalog
|
|
209
|
+
|
|
210
|
+
### Anti-Pattern: Shotgun Debugging
|
|
211
|
+
|
|
212
|
+
**What goes wrong:** Making random changes hoping something fixes the bug. Changing multiple things at once so you don't know which change actually helped.
|
|
213
|
+
|
|
214
|
+
**Signs:** Multiple unrelated changes in the fix commit. "Try this" mentality. Reverting changes randomly.
|
|
215
|
+
|
|
216
|
+
**Instead:** Follow the 4-phase process. One change at a time, tested after each change.
|
|
217
|
+
|
|
218
|
+
### Anti-Pattern: Fixing Symptoms
|
|
219
|
+
|
|
220
|
+
**What goes wrong:** Adding a null check without understanding why the value is null. Adding a retry without understanding why the operation fails. The root cause remains and will manifest differently.
|
|
221
|
+
|
|
222
|
+
**Signs:** The fix adds a guard clause but doesn't explain why the guarded condition occurs. The same module needs frequent "fixes." New bugs appear shortly after the fix.
|
|
223
|
+
|
|
224
|
+
**Instead:** Complete Phase 3 (Diagnose) before Phase 4 (Fix). Understand WHY before fixing WHAT.
|
|
225
|
+
|
|
226
|
+
### Anti-Pattern: No Regression Test
|
|
227
|
+
|
|
228
|
+
**What goes wrong:** The bug is fixed but no test guards against it recurring. Three months later, a refactoring reintroduces the exact same bug.
|
|
229
|
+
|
|
230
|
+
**Signs:** Fix commit has no test changes. The bug has been fixed before (check git log). Similar bugs keep appearing in the same module.
|
|
231
|
+
|
|
232
|
+
**Instead:** Always write the regression test FIRST (Phase 4, step 1). The test should fail before the fix and pass after.
|
|
233
|
+
|
|
234
|
+
### Anti-Pattern: Debugging in Production
|
|
235
|
+
|
|
236
|
+
**What goes wrong:** Adding console.log or debug statements to production code instead of reproducing locally. Production debugging is slow, risky, and often modifies the bug's behavior (observer effect).
|
|
237
|
+
|
|
238
|
+
**Signs:** `console.log` scattered in production code. Debug endpoints exposed. Debugging requires deploying to staging.
|
|
239
|
+
|
|
240
|
+
**Instead:** Reproduce the bug locally first (Phase 1). Use structured logging that is always present, not ad-hoc debug statements.
|
|
241
|
+
|
|
242
|
+
### Anti-Pattern: Blame-Driven Debugging
|
|
243
|
+
|
|
244
|
+
**What goes wrong:** Spending time on `git blame` to find who introduced the bug instead of understanding what the bug is. Attribution is irrelevant to the fix.
|
|
245
|
+
|
|
246
|
+
**Signs:** First action is `git blame`. Discussion focuses on who, not what. The fix is delayed by organizational process.
|
|
247
|
+
|
|
248
|
+
**Instead:** Focus on WHAT the bug is (Phase 3). Use `git log` and `git bisect` to find WHEN the bug was introduced (useful for understanding context), not WHO.
|
|
249
|
+
|
|
250
|
+
## Integration with Our Tools
|
|
251
|
+
|
|
252
|
+
**`oc_forensics`:** Use during Phase 2 (Isolate) to analyze failed pipeline runs. `oc_forensics` identifies the failing phase, agent, and root cause from pipeline execution logs. Particularly useful for bugs in the orchestration pipeline where the failure is in a subagent's output.
|
|
253
|
+
|
|
254
|
+
**`oc_review`:** Use after Phase 4 (Fix) to review the fix for introduced issues. The review catches cases where the fix solves the immediate bug but introduces a new one (incomplete error handling, missing edge cases).
|
|
255
|
+
|
|
256
|
+
**`oc_logs`:** Use during Phase 2 (Isolate) to inspect session event history. Useful for timing-related bugs where the order of events matters. The structured log shows exact timestamps, event types, and data payloads.
|
|
257
|
+
|
|
258
|
+
## Failure Modes
|
|
259
|
+
|
|
260
|
+
### Cannot Reproduce
|
|
261
|
+
|
|
262
|
+
**Symptom:** Phase 1 fails -- the bug doesn't appear in your environment.
|
|
263
|
+
|
|
264
|
+
**Recovery:**
|
|
265
|
+
1. Compare environments exactly: OS, runtime version, config, env vars
|
|
266
|
+
2. Check for data-dependent bugs: request the exact input that triggered the bug
|
|
267
|
+
3. Check for timing-dependent bugs: add artificial delays or run under load
|
|
268
|
+
4. If still cannot reproduce: ask the reporter to record a session (screen recording, network trace)
|
|
269
|
+
5. Last resort: add structured logging to the relevant code path and deploy. Wait for the bug to occur and analyze the logs.
|
|
270
|
+
|
|
271
|
+
### Reproduce But Cannot Isolate
|
|
272
|
+
|
|
273
|
+
**Symptom:** Phase 2 fails -- the bug appears but you cannot narrow it to a specific location.
|
|
274
|
+
|
|
275
|
+
**Recovery:**
|
|
276
|
+
1. Add more granular logging between existing log points
|
|
277
|
+
2. Check async operation ordering -- add timestamps to all log messages
|
|
278
|
+
3. Use a debugger with breakpoints at module boundaries
|
|
279
|
+
4. Create a stripped-down reproduction that eliminates as much code as possible
|
|
280
|
+
5. If the codebase is complex, draw the call flow on paper and mark where you've verified correct behavior
|
|
281
|
+
|
|
282
|
+
### Root Cause Unclear
|
|
283
|
+
|
|
284
|
+
**Symptom:** Phase 3 fails -- you know WHERE the bug is but not WHY.
|
|
285
|
+
|
|
286
|
+
**Recovery:**
|
|
287
|
+
1. Rubber duck debugging: explain the code path to an imaginary colleague, out loud, line by line
|
|
288
|
+
2. Read the surrounding code more widely -- the bug may be caused by an interaction with adjacent logic
|
|
289
|
+
3. Check the git history for the buggy function -- was it recently changed? What was the intent of the change?
|
|
290
|
+
4. If the root cause is genuinely unclear after 30 minutes, take a break. Bugs often become obvious after stepping away.
|
|
291
|
+
|
|
292
|
+
### Fix Introduces New Bugs
|
|
293
|
+
|
|
294
|
+
**Symptom:** Phase 4 fix causes other tests to fail.
|
|
295
|
+
|
|
296
|
+
**Recovery:**
|
|
297
|
+
1. The fix changed behavior beyond the bug -- revert and apply a more targeted fix
|
|
298
|
+
2. The failing tests were depending on the buggy behavior -- update those tests (they were wrong)
|
|
299
|
+
3. The fix exposed a latent bug elsewhere -- debug that bug separately using this same 4-phase process
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tdd-workflow
|
|
3
|
+
description: Strict RED-GREEN-REFACTOR TDD methodology with anti-pattern catalog and explicit failure modes
|
|
4
|
+
stacks: []
|
|
5
|
+
requires: []
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# TDD Workflow
|
|
9
|
+
|
|
10
|
+
Strict RED-GREEN-REFACTOR test-driven development methodology. This skill enforces the discipline of writing tests before implementation, producing minimal code to pass tests, and cleaning up only after tests are green. Every cycle produces a commit. Every phase has a clear purpose and exit criterion.
|
|
11
|
+
|
|
12
|
+
TDD is not "writing tests." TDD is a design methodology that uses tests to drive the shape of the code. The test defines the behavior. The implementation satisfies the test. The refactor improves the code without changing behavior.
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
**Activate this skill when:**
|
|
17
|
+
|
|
18
|
+
- Implementing business logic with defined inputs and outputs
|
|
19
|
+
- Building API endpoints with request/response contracts
|
|
20
|
+
- Writing data transformations, parsers, or formatters
|
|
21
|
+
- Implementing validation rules or authorization checks
|
|
22
|
+
- Building algorithms, state machines, or decision logic
|
|
23
|
+
- Fixing a bug (write the regression test first, then fix)
|
|
24
|
+
- Implementing any function where you can describe the expected behavior
|
|
25
|
+
|
|
26
|
+
**Do NOT use when:**
|
|
27
|
+
|
|
28
|
+
- UI layout and styling (visual output is hard to assert meaningfully)
|
|
29
|
+
- Configuration files and static data
|
|
30
|
+
- One-off scripts or migrations
|
|
31
|
+
- Simple CRUD with no business logic (getById, list, delete)
|
|
32
|
+
- Prototyping or exploring an unfamiliar API (spike first, then TDD the real implementation)
|
|
33
|
+
|
|
34
|
+
## The RED-GREEN-REFACTOR Cycle
|
|
35
|
+
|
|
36
|
+
Each cycle implements ONE behavior. Not two. Not "a few related things." One behavior, one test, one cycle. Repeat until the feature is complete.
|
|
37
|
+
|
|
38
|
+
### Phase 1: RED (Write a Failing Test)
|
|
39
|
+
|
|
40
|
+
**Purpose:** Define the expected behavior BEFORE writing any production code. The test is a specification.
|
|
41
|
+
|
|
42
|
+
**Process:**
|
|
43
|
+
|
|
44
|
+
1. Write ONE test that describes a single expected behavior
|
|
45
|
+
2. The test name should read as a behavior description, not a method name:
|
|
46
|
+
- DO: `"rejects expired tokens with 401 status"`
|
|
47
|
+
- DO: `"calculates total with tax for US addresses"`
|
|
48
|
+
- DON'T: `"test validateToken"` or `"test calculateTotal"`
|
|
49
|
+
3. Structure the test using Arrange-Act-Assert:
|
|
50
|
+
- **Arrange:** Set up inputs and expected outputs
|
|
51
|
+
- **Act:** Call the function or trigger the behavior
|
|
52
|
+
- **Assert:** Verify the output matches expectations
|
|
53
|
+
4. Run the test -- it MUST fail
|
|
54
|
+
5. Read the failure message -- it should describe the missing behavior clearly
|
|
55
|
+
6. If the test passes without any new implementation, the behavior already exists or the test is wrong
|
|
56
|
+
|
|
57
|
+
**Commit:** `test: add failing test for [behavior]`
|
|
58
|
+
|
|
59
|
+
**Exit criterion:** The test fails with a clear, expected error message.
|
|
60
|
+
|
|
61
|
+
**Common mistakes in RED:**
|
|
62
|
+
|
|
63
|
+
- Writing multiple tests at once (write ONE, see it fail, then proceed)
|
|
64
|
+
- Writing the test and implementation simultaneously (defeats the purpose)
|
|
65
|
+
- Writing a test that cannot fail (tautology: `expect(true).toBe(true)`)
|
|
66
|
+
- Testing implementation details instead of behavior (asserting internal state)
|
|
67
|
+
|
|
68
|
+
### Phase 2: GREEN (Make It Pass)
|
|
69
|
+
|
|
70
|
+
**Purpose:** Write the MINIMUM code to make the test pass. Nothing more.
|
|
71
|
+
|
|
72
|
+
**Process:**
|
|
73
|
+
|
|
74
|
+
1. Read the failing test to understand what behavior is expected
|
|
75
|
+
2. Write the simplest possible code that makes the test pass
|
|
76
|
+
3. Do NOT add error handling the test does not require
|
|
77
|
+
4. Do NOT handle edge cases the test does not cover
|
|
78
|
+
5. Do NOT optimize -- performance improvements are Phase 3 or a new cycle
|
|
79
|
+
6. Do NOT "clean up" -- that is Phase 3
|
|
80
|
+
7. Run the test -- it MUST pass
|
|
81
|
+
8. Run all existing tests -- they MUST still pass (no regressions)
|
|
82
|
+
|
|
83
|
+
**Commit:** `feat: implement [behavior]`
|
|
84
|
+
|
|
85
|
+
**Exit criterion:** The new test passes AND all existing tests pass.
|
|
86
|
+
|
|
87
|
+
**The hardest discipline:** Resist the urge to write "good" code in this phase. You WILL see opportunities for abstraction, error handling, and optimization. Ignore them. Write the minimum. Phase 3 exists specifically for cleanup.
|
|
88
|
+
|
|
89
|
+
**Why minimum matters:** If you write more code than the test requires, that extra code is untested. Untested code is the source of bugs. The RED-GREEN cycle guarantees that every line of production code exists to satisfy a test.
|
|
90
|
+
|
|
91
|
+
### Phase 3: REFACTOR (Clean Up)
|
|
92
|
+
|
|
93
|
+
**Purpose:** Improve the code without changing behavior. The tests are your safety net.
|
|
94
|
+
|
|
95
|
+
**Process:**
|
|
96
|
+
|
|
97
|
+
1. Review the implementation from Phase 2 -- what can be improved?
|
|
98
|
+
2. Common refactoring targets:
|
|
99
|
+
- Extract repeated logic into named functions
|
|
100
|
+
- Rename variables for clarity
|
|
101
|
+
- Remove duplication between test and production code
|
|
102
|
+
- Simplify complex conditionals
|
|
103
|
+
- Extract constants for magic numbers/strings
|
|
104
|
+
3. After EVERY change, run the tests -- they MUST still pass
|
|
105
|
+
4. If a test fails during refactoring, REVERT the last change immediately
|
|
106
|
+
5. Make smaller changes -- one refactoring at a time, verified by tests
|
|
107
|
+
|
|
108
|
+
**Commit (if changes were made):** `refactor: clean up [behavior]`
|
|
109
|
+
|
|
110
|
+
**Exit criterion:** Code is clean, all tests pass, no new behavior added.
|
|
111
|
+
|
|
112
|
+
**When to skip REFACTOR:** Never. Even if the code "looks fine," do a quick review pass. The habit of always refactoring prevents technical debt accumulation. If nothing needs changing, that's fine -- move to the next RED.
|
|
113
|
+
|
|
114
|
+
## Test Writing Guidelines
|
|
115
|
+
|
|
116
|
+
### Name Tests as Behavior Descriptions
|
|
117
|
+
|
|
118
|
+
Tests are documentation. The test name should explain what the system does, not how the test works.
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
// DO: Behavior descriptions
|
|
122
|
+
"creates user with hashed password"
|
|
123
|
+
"rejects duplicate email addresses"
|
|
124
|
+
"returns empty array when no results match"
|
|
125
|
+
"sends welcome email after successful registration"
|
|
126
|
+
|
|
127
|
+
// DON'T: Implementation descriptions
|
|
128
|
+
"test createUser"
|
|
129
|
+
"test email validation"
|
|
130
|
+
"test empty result"
|
|
131
|
+
"test sendEmail"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### One Assertion Per Test
|
|
135
|
+
|
|
136
|
+
Each test should verify one behavior. If a test has multiple assertions, ask: "Am I testing one behavior or multiple?"
|
|
137
|
+
|
|
138
|
+
**Acceptable:** Multiple assertions that verify different aspects of the SAME behavior:
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
// OK: Both assertions verify the "create user" behavior
|
|
142
|
+
expect(result.id).toBeDefined()
|
|
143
|
+
expect(result.email).toBe("user@example.com")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Not acceptable:** Assertions that verify DIFFERENT behaviors:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
// WRONG: Testing creation AND retrieval in one test
|
|
150
|
+
const created = await createUser(data)
|
|
151
|
+
const fetched = await getUser(created.id)
|
|
152
|
+
expect(created.id).toBeDefined()
|
|
153
|
+
expect(fetched.email).toBe(data.email)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Arrange-Act-Assert Structure
|
|
157
|
+
|
|
158
|
+
Every test has three distinct sections. Separate them with blank lines for readability.
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
test("calculates discount for premium customers", () => {
|
|
162
|
+
// Arrange
|
|
163
|
+
const customer = { tier: "premium", orderTotal: 100 }
|
|
164
|
+
|
|
165
|
+
// Act
|
|
166
|
+
const discount = calculateDiscount(customer)
|
|
167
|
+
|
|
168
|
+
// Assert
|
|
169
|
+
expect(discount).toBe(15)
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Use Descriptive Failure Messages
|
|
174
|
+
|
|
175
|
+
When a test fails, the failure message should tell you what went wrong without reading the test code.
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
// DO: Descriptive
|
|
179
|
+
expect(response.status, "Expected 401 for expired token").toBe(401)
|
|
180
|
+
|
|
181
|
+
// DON'T: Generic
|
|
182
|
+
expect(response.status).toBe(401)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Test Edge Cases in Separate Cycles
|
|
186
|
+
|
|
187
|
+
Each edge case gets its own RED-GREEN-REFACTOR cycle:
|
|
188
|
+
|
|
189
|
+
1. RED: Write test for empty input
|
|
190
|
+
2. GREEN: Handle empty input
|
|
191
|
+
3. REFACTOR: Clean up
|
|
192
|
+
4. RED: Write test for null input
|
|
193
|
+
5. GREEN: Handle null input
|
|
194
|
+
6. REFACTOR: Clean up
|
|
195
|
+
|
|
196
|
+
Do NOT bundle edge cases into the initial implementation.
|
|
197
|
+
|
|
198
|
+
## Anti-Pattern Catalog
|
|
199
|
+
|
|
200
|
+
### Anti-Pattern: Writing Tests After Code
|
|
201
|
+
|
|
202
|
+
**What goes wrong:** Tests become assertions of what the code already does, not specifications of what it should do. The tests verify the implementation, not the behavior. When the implementation has a bug, the test has the same bug.
|
|
203
|
+
|
|
204
|
+
**Signs:** All tests pass on the first run. Tests mirror implementation structure. Changing the implementation always requires changing the tests.
|
|
205
|
+
|
|
206
|
+
**Instead:** Always write the test FIRST. The test should fail before any implementation exists. If it doesn't fail, the test is wrong.
|
|
207
|
+
|
|
208
|
+
### Anti-Pattern: Skipping RED
|
|
209
|
+
|
|
210
|
+
**What goes wrong:** Writing the test and implementation together means you never verified that the test can actually detect a failure. The test might be a tautology that always passes.
|
|
211
|
+
|
|
212
|
+
**Signs:** You never see a red test. Tests are written alongside implementation in the same commit. You feel confident the test works but have no evidence.
|
|
213
|
+
|
|
214
|
+
**Instead:** Run the test, see the red failure message, read it, confirm it describes the missing behavior. Only then write the implementation.
|
|
215
|
+
|
|
216
|
+
### Anti-Pattern: Over-Engineering in GREEN
|
|
217
|
+
|
|
218
|
+
**What goes wrong:** Adding error handling, edge case coverage, performance optimizations, and abstractions before the test requires them. This extra code is untested and may contain bugs.
|
|
219
|
+
|
|
220
|
+
**Signs:** The implementation is significantly more complex than what the test verifies. Functions handle cases no test covers. You justify additions with "we'll need this later."
|
|
221
|
+
|
|
222
|
+
**Instead:** Write only what the current test needs. If you need error handling, write a RED test for the error case first. If you need optimization, write a benchmark test first.
|
|
223
|
+
|
|
224
|
+
### Anti-Pattern: Skipping REFACTOR
|
|
225
|
+
|
|
226
|
+
**What goes wrong:** Technical debt accumulates as each GREEN phase adds minimal code without cleanup. After 20 cycles, the codebase is a pile of special cases.
|
|
227
|
+
|
|
228
|
+
**Signs:** Production code has obvious duplication. Variable names are unclear. Functions grow linearly with each new test. You dread adding new features because the code is messy.
|
|
229
|
+
|
|
230
|
+
**Instead:** Always do a REFACTOR pass, even if it's a 30-second review that concludes "looks fine." Build the habit.
|
|
231
|
+
|
|
232
|
+
### Anti-Pattern: Testing Implementation Details
|
|
233
|
+
|
|
234
|
+
**What goes wrong:** Tests assert on internal method calls, private state, or call counts instead of observable behavior. Refactoring breaks all tests even though behavior is unchanged.
|
|
235
|
+
|
|
236
|
+
**Signs:** Tests use `toHaveBeenCalledWith` on internal methods. Tests assert on intermediate variables. Renaming an internal function breaks 15 tests.
|
|
237
|
+
|
|
238
|
+
**Instead:** Test the public API. Assert on outputs, side effects (emails sent, records created), and error behaviors. Never assert on how the implementation achieves the result.
|
|
239
|
+
|
|
240
|
+
### Anti-Pattern: Large Test Suites with No RED
|
|
241
|
+
|
|
242
|
+
**What goes wrong:** All tests are written at once, all passing from the start. This is "test-after" development, not TDD. The tests validate the existing implementation rather than driving the design.
|
|
243
|
+
|
|
244
|
+
**Signs:** A PR adds 20 tests and an implementation, all in one commit. No commit shows a failing test. The test file was written after the production code.
|
|
245
|
+
|
|
246
|
+
**Instead:** One test at a time, one cycle at a time. Each cycle produces 1-3 commits (RED, GREEN, optional REFACTOR). The git history tells the story.
|
|
247
|
+
|
|
248
|
+
## Integration with Our Tools
|
|
249
|
+
|
|
250
|
+
**After GREEN phase:** Invoke `oc_review` for a quick quality check on the implementation. The review catches issues (naming, error handling gaps, security concerns) that the REFACTOR phase should address.
|
|
251
|
+
|
|
252
|
+
**During REFACTOR:** If a test fails unexpectedly after a refactoring change, use `oc_forensics` to diagnose the root cause. It identifies the exact change that broke the test.
|
|
253
|
+
|
|
254
|
+
**After completing all cycles:** Run `oc_review` on the full changeset to catch cross-cutting concerns (duplication across files, missing integration tests, inconsistent patterns).
|
|
255
|
+
|
|
256
|
+
**Commit hygiene:** Each RED-GREEN-REFACTOR cycle produces up to 3 commits. This granular history is valuable -- it shows design evolution and makes bisecting easier.
|
|
257
|
+
|
|
258
|
+
## Failure Modes
|
|
259
|
+
|
|
260
|
+
### Test Won't Fail (RED Phase)
|
|
261
|
+
|
|
262
|
+
**Symptom:** You write the test and it passes immediately without any new implementation.
|
|
263
|
+
|
|
264
|
+
**Diagnosis:**
|
|
265
|
+
- The behavior is already implemented (check existing code)
|
|
266
|
+
- The test is asserting something trivially true (tautology)
|
|
267
|
+
- The test is calling the wrong function or using stale imports
|
|
268
|
+
|
|
269
|
+
**Recovery:** Delete the test. Read the existing implementation. Write a test for behavior that is genuinely NOT implemented yet.
|
|
270
|
+
|
|
271
|
+
### Test Won't Pass (GREEN Phase)
|
|
272
|
+
|
|
273
|
+
**Symptom:** You write the implementation but the test still fails.
|
|
274
|
+
|
|
275
|
+
**Diagnosis:**
|
|
276
|
+
- Re-read the test carefully -- are you implementing what the test actually checks?
|
|
277
|
+
- Check for typos in function names, property names, import paths
|
|
278
|
+
- Simplify: can you make the test pass with a hardcoded return value? If yes, work backwards from there
|
|
279
|
+
|
|
280
|
+
**Recovery:** Start with the simplest possible implementation (even a hardcoded value). Then generalize one step at a time, running the test after each change.
|
|
281
|
+
|
|
282
|
+
### Refactoring Breaks Tests
|
|
283
|
+
|
|
284
|
+
**Symptom:** Tests fail after a refactoring change in Phase 3.
|
|
285
|
+
|
|
286
|
+
**Diagnosis:**
|
|
287
|
+
- The refactoring changed behavior (not just structure) -- revert
|
|
288
|
+
- A test was testing implementation details, not behavior -- the test needs updating
|
|
289
|
+
- The refactoring introduced a subtle bug (argument order, missing return, etc.)
|
|
290
|
+
|
|
291
|
+
**Recovery:** Revert the last change immediately. Make a smaller refactoring step. If the tests are too coupled to implementation, that's a separate problem to fix in a dedicated cycle.
|
|
292
|
+
|
|
293
|
+
### Can't Think of the Next Test
|
|
294
|
+
|
|
295
|
+
**Symptom:** The current behavior works, but you're not sure what to test next.
|
|
296
|
+
|
|
297
|
+
**Diagnosis:** This is normal and healthy -- it means the current scope might be complete.
|
|
298
|
+
|
|
299
|
+
**Recovery:**
|
|
300
|
+
1. Review the requirements -- is there untested behavior?
|
|
301
|
+
2. Check edge cases: empty input, null, boundary values, error conditions
|
|
302
|
+
3. Check integration points: does this work correctly with adjacent modules?
|
|
303
|
+
4. If nothing emerges, the feature may be done. Run coverage to confirm.
|
|
304
|
+
|
|
305
|
+
### TDD Feels Slow
|
|
306
|
+
|
|
307
|
+
**Symptom:** TDD seems like it takes twice as long as just writing the code.
|
|
308
|
+
|
|
309
|
+
**Reality:** TDD front-loads the time you would spend debugging later. The total time is usually equal or less. The difference: TDD time is predictable (small cycles), debug time is unpredictable (hours chasing a bug).
|
|
310
|
+
|
|
311
|
+
**If genuinely slow:** Your cycles are too large. Each cycle should take 5-15 minutes (RED: 2-3 min, GREEN: 2-5 min, REFACTOR: 1-5 min). If a cycle takes longer, the behavior being tested is too complex -- split it.
|