@apogeelabs/the-agency 0.1.1
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/README.md +93 -0
- package/bin/cli.js +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +20 -0
- package/dist/manifest.d.ts +12 -0
- package/dist/manifest.js +24 -0
- package/dist/sync.d.ts +14 -0
- package/dist/sync.js +97 -0
- package/package.json +45 -0
- package/src/templates/.ai/UnitTestExamples.md +1148 -0
- package/src/templates/.ai/UnitTestGeneration.md +477 -0
- package/src/templates/.ai/workflow.md +59 -0
- package/src/templates/.claude/agents/architect.md +90 -0
- package/src/templates/.claude/agents/dev.md +79 -0
- package/src/templates/.claude/agents/explorer.md +93 -0
- package/src/templates/.claude/agents/pm.md +74 -0
- package/src/templates/.claude/agents/reviewer.md +93 -0
- package/src/templates/.claude/agents/test-hardener.md +121 -0
- package/src/templates/.claude/commands/architect.md +108 -0
- package/src/templates/.claude/commands/build.md +125 -0
- package/src/templates/.claude/commands/pm.md +72 -0
- package/src/templates/.claude/commands/review-pr.md +279 -0
- package/src/templates/.claude/settings.local.json +44 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
# TypeScript/Jest Unit Testing Style Guide (v2)
|
|
2
|
+
|
|
3
|
+
Carefully analyze the jest-based unit test modules I've provided below, and take careful note of how I write unit tests. You will use this style guide to generate unit tests for new modules in subsequent requests.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## CRITICAL RULE #1: Test Execution Location
|
|
8
|
+
|
|
9
|
+
⚠️ **NON-NEGOTIABLE RULE** ⚠️
|
|
10
|
+
|
|
11
|
+
**Execute the method under test in `beforeEach()` blocks.**
|
|
12
|
+
**Assert against results in `it()` blocks.**
|
|
13
|
+
|
|
14
|
+
This is mandatory. Do NOT execute the method under test inside `it()` blocks under ANY circumstances.
|
|
15
|
+
|
|
16
|
+
### Why This Pattern Matters
|
|
17
|
+
|
|
18
|
+
1. **Single execution per scenario**: Method runs once per `describe` block, not once per assertion
|
|
19
|
+
2. **Clear separation**: Setup vs. verification are distinct phases
|
|
20
|
+
3. **Performance**: Avoids redundant executions when multiple assertions test the same result
|
|
21
|
+
4. **Refactoring**: Changing method call location affects one place, not every test
|
|
22
|
+
5. **Readability**: `it()` blocks become pure assertions about behavior
|
|
23
|
+
|
|
24
|
+
### The Pattern
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// ✅ CORRECT: Execute in beforeEach, assert in it blocks
|
|
28
|
+
describe("when calculating discount", () => {
|
|
29
|
+
let result: number;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
result = calculateDiscount(100); // ✅ Execution here
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should return 10% discount", () => {
|
|
36
|
+
expect(result).toBe(10); // ✅ Pure assertion
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should return a positive number", () => {
|
|
40
|
+
expect(result).toBeGreaterThan(0); // ✅ Pure assertion
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ❌ WRONG: Executing in it blocks
|
|
45
|
+
describe("when calculating discount", () => {
|
|
46
|
+
it("should return 10% discount", () => {
|
|
47
|
+
const result = calculateDiscount(100); // ❌ NO! Don't execute here
|
|
48
|
+
expect(result).toBe(10);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should return a positive number", () => {
|
|
52
|
+
const result = calculateDiscount(100); // ❌ NO! Redundant execution
|
|
53
|
+
expect(result).toBeGreaterThan(0);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Decision Tree
|
|
59
|
+
|
|
60
|
+
Is this an it() block?
|
|
61
|
+
|
|
62
|
+
- YES
|
|
63
|
+
- Do NOT execute the method under test here
|
|
64
|
+
- Only write assertions (expect statements)
|
|
65
|
+
- Use variables declared outside and populated in beforeEach
|
|
66
|
+
- NO (it's a beforeEach)
|
|
67
|
+
- Execute the method under test here
|
|
68
|
+
- Store results in variables for it() blocks to assert
|
|
69
|
+
- async and sync Errors can be captured in a catch() or try/catch to be used in an assertion elsewhere
|
|
70
|
+
|
|
71
|
+
### Self-Check Before Generating Tests
|
|
72
|
+
|
|
73
|
+
Before writing any test suite, verify:
|
|
74
|
+
|
|
75
|
+
- [ ] Is the method under test called in `beforeEach()`?
|
|
76
|
+
- [ ] Do `it()` blocks contain ONLY assertions (expect statements)?
|
|
77
|
+
- [ ] Are result variables declared with `let` above `beforeEach()`?
|
|
78
|
+
|
|
79
|
+
**If you cannot answer YES to all of these, revise your test structure.**
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## CRITICAL RULE #2: Mock Configuration Location
|
|
84
|
+
|
|
85
|
+
⚠️ **NON-NEGOTIABLE RULE** ⚠️
|
|
86
|
+
|
|
87
|
+
**Declare `jest.fn()` mock references at module level. Configure mock _behavior_ (return values, implementations) inside `beforeEach` or `beforeAll` blocks — NEVER at module level.**
|
|
88
|
+
|
|
89
|
+
Module-level code runs once when the file loads. If you configure return values or implementations at module level, those configurations are consumed on the first test and are gone for all subsequent tests, causing unpredictable failures.
|
|
90
|
+
|
|
91
|
+
### The Pattern
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// ✅ CORRECT: Declare at module level, configure in beforeEach
|
|
95
|
+
const mockGetUser = jest.fn();
|
|
96
|
+
jest.mock("./userService", () => ({
|
|
97
|
+
getUser: mockGetUser,
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
describe("when fetching user profile", () => {
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
mockGetUser.mockResolvedValue({ id: "42", name: "Cal Zone" }); // ✅ Configured per test
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should return the user", () => {
|
|
106
|
+
expect(result.name).toBe("Cal Zone");
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ❌ WRONG: Configuring behavior at module level
|
|
111
|
+
const mockGetUser = jest.fn().mockResolvedValue({ id: "42", name: "Cal Zone" }); // ❌ Consumed on first test only
|
|
112
|
+
jest.mock("./userService", () => ({
|
|
113
|
+
getUser: mockGetUser,
|
|
114
|
+
}));
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Exceptions
|
|
118
|
+
|
|
119
|
+
The only acceptable module-level mock configuration is `mockReturnThis()` for builder/chaining patterns where the mock needs to return itself for `jest.mock()` factory resolution:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// ✅ OK: mockReturnThis at module level for chaining
|
|
123
|
+
const mockLuxon = {
|
|
124
|
+
DateTime: {
|
|
125
|
+
utc: jest.fn().mockReturnThis(),
|
|
126
|
+
toISO: jest.fn(), // but configure toISO's return value in beforeEach!
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Self-Check
|
|
132
|
+
|
|
133
|
+
- [ ] Are all `mockResolvedValue`, `mockReturnValue`, `mockImplementation` calls inside `beforeEach`/`beforeAll`?
|
|
134
|
+
- [ ] Do module-level mock declarations use only bare `jest.fn()` (or `jest.fn().mockReturnThis()` for chaining)?
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## CRITICAL RULE #3: Callback Invocation via mockImplementation
|
|
139
|
+
|
|
140
|
+
⚠️ **NON-NEGOTIABLE RULE** ⚠️
|
|
141
|
+
|
|
142
|
+
**When testing code that passes a callback to a dependency, use `mockImplementation` (or `mockImplementationOnce`) to invoke that callback. NEVER use `mock.calls[N][M]()` to reach into the call record and imperatively invoke a callback.**
|
|
143
|
+
|
|
144
|
+
The `mock.calls` approach is fragile: it depends on call ordering, breaks when calls are added/removed, and separates the invocation from the setup that makes it meaningful.
|
|
145
|
+
|
|
146
|
+
### The Pattern
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// ✅ CORRECT: Invoke callback via mockImplementation
|
|
150
|
+
describe("when the event handler callback fires", () => {
|
|
151
|
+
beforeEach(() => {
|
|
152
|
+
mockEventEmitter.on.mockImplementationOnce((_event: any, cb: any) => {
|
|
153
|
+
cb("EVENT_DATA"); // ✅ Callback invoked as part of mock behavior
|
|
154
|
+
});
|
|
155
|
+
result = initializeListener();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should process the event data", () => {
|
|
159
|
+
expect(mockProcess).toHaveBeenCalledWith("EVENT_DATA");
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// ❌ WRONG: Imperatively calling into mock.calls
|
|
164
|
+
describe("when the event handler callback fires", () => {
|
|
165
|
+
beforeEach(() => {
|
|
166
|
+
initializeListener();
|
|
167
|
+
// ❌ Fragile: depends on call index, separated from setup context
|
|
168
|
+
const callback = mockEventEmitter.on.mock.calls[0][1];
|
|
169
|
+
callback("EVENT_DATA");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("should process the event data", () => {
|
|
173
|
+
expect(mockProcess).toHaveBeenCalledWith("EVENT_DATA");
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Why This Matters
|
|
179
|
+
|
|
180
|
+
1. **Coupling to call order**: `mock.calls[0][1]` breaks if a new call is added before it
|
|
181
|
+
2. **Readability**: The callback's purpose is invisible — you must trace back to understand what `calls[0][1]` refers to
|
|
182
|
+
3. **Timing**: `mockImplementation` fires the callback at the natural execution point; `mock.calls` fires it after-the-fact, which can mask timing bugs
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Coverage-Driven Test Planning
|
|
187
|
+
|
|
188
|
+
⚠️ **MANDATORY PRE-WRITING STEP** ⚠️
|
|
189
|
+
|
|
190
|
+
Before writing any test code, you MUST perform a systematic branch analysis of the source module. This prevents the common failure of finishing a test suite only to discover uncovered lines.
|
|
191
|
+
|
|
192
|
+
### The Process
|
|
193
|
+
|
|
194
|
+
1. **Read the source file** and identify every branch point:
|
|
195
|
+
- `if`/`else if`/`else` blocks
|
|
196
|
+
- `switch` cases (including `default`)
|
|
197
|
+
- Ternary expressions
|
|
198
|
+
- Short-circuit evaluations (`&&`, `||`, `??`)
|
|
199
|
+
- `try`/`catch`/`finally` blocks
|
|
200
|
+
- Early returns
|
|
201
|
+
- Loop bodies (at least: zero iterations, one iteration, multiple iterations where behavior differs)
|
|
202
|
+
|
|
203
|
+
2. **Create a branch map** — a mental or written list of every distinct code path:
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
Path 1: input is null → early return
|
|
207
|
+
Path 2: input valid, cache hit → return cached
|
|
208
|
+
Path 3: input valid, cache miss, fetch succeeds → return fetched
|
|
209
|
+
Path 4: input valid, cache miss, fetch fails → throw ServiceError
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
3. **Map each path to a test scenario** (a `describe` block). Verify that every path has at least one corresponding scenario.
|
|
213
|
+
|
|
214
|
+
4. **Check for gaps** before writing any `describe`/`it` blocks. If a path has no scenario, add one.
|
|
215
|
+
|
|
216
|
+
### Self-Check
|
|
217
|
+
|
|
218
|
+
- [ ] Have I read the entire source file before writing tests?
|
|
219
|
+
- [ ] Have I identified every branch point (if/else, switch, try/catch, ternary, short-circuit, early return)?
|
|
220
|
+
- [ ] Does every identified path have a corresponding test scenario?
|
|
221
|
+
- [ ] Are there any lines that none of my planned scenarios will execute?
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Superfluous Test Prevention
|
|
226
|
+
|
|
227
|
+
Do NOT write multiple test scenarios that exercise the same code path with different literal values. A new `describe` block is warranted only when it exercises a **different branch** in the source code.
|
|
228
|
+
|
|
229
|
+
### When a New Describe Block IS Warranted
|
|
230
|
+
|
|
231
|
+
- A different `if`/`else` branch is taken
|
|
232
|
+
- A different `switch` case is matched
|
|
233
|
+
- A different error path is triggered
|
|
234
|
+
- A different early return is hit
|
|
235
|
+
- A dependency returns a structurally different response (e.g., empty array vs. populated array) that causes different downstream behavior
|
|
236
|
+
|
|
237
|
+
### When a New Describe Block is NOT Warranted
|
|
238
|
+
|
|
239
|
+
- The same branch is taken with a different string value
|
|
240
|
+
- The same branch is taken with a different numeric value
|
|
241
|
+
- The same error is thrown with a different message
|
|
242
|
+
- The same path is taken with a different but structurally equivalent input
|
|
243
|
+
|
|
244
|
+
### Example
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
// ❌ SUPERFLUOUS: Two describes that exercise the same branch
|
|
248
|
+
describe("when status is 'active'", () => {
|
|
249
|
+
beforeEach(() => {
|
|
250
|
+
result = getLabel("active");
|
|
251
|
+
});
|
|
252
|
+
it("should return 'Currently Active'", () => {
|
|
253
|
+
expect(result).toBe("Currently Active");
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
describe("when status is 'enabled'", () => {
|
|
257
|
+
// ❌ If 'enabled' hits the same if-branch as 'active', this is redundant
|
|
258
|
+
beforeEach(() => {
|
|
259
|
+
result = getLabel("enabled");
|
|
260
|
+
});
|
|
261
|
+
it("should return 'Currently Active'", () => {
|
|
262
|
+
expect(result).toBe("Currently Active");
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// ✅ CORRECT: One describe per branch
|
|
267
|
+
describe("when status is active", () => {
|
|
268
|
+
beforeEach(() => {
|
|
269
|
+
result = getLabel("active"); // covers the "active" branch
|
|
270
|
+
});
|
|
271
|
+
it("should return 'Currently Active'", () => {
|
|
272
|
+
expect(result).toBe("Currently Active");
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
describe("when status is inactive", () => {
|
|
276
|
+
beforeEach(() => {
|
|
277
|
+
result = getLabel("inactive"); // covers a DIFFERENT branch
|
|
278
|
+
});
|
|
279
|
+
it("should return 'Not Active'", () => {
|
|
280
|
+
expect(result).toBe("Not Active");
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## File Structure and Naming
|
|
288
|
+
|
|
289
|
+
- **File naming**: Module name + `.test.ts` extension
|
|
290
|
+
- **Coverage goal**: Aim for 100% line coverage, with focus on:
|
|
291
|
+
- All exported functions/methods
|
|
292
|
+
- All conditional branches and error paths
|
|
293
|
+
- Edge cases and boundary conditions
|
|
294
|
+
- Integration points between functions
|
|
295
|
+
- **Eslint pragmas**: since we use `any` types, include the `@typescript-eslint/no-explicit-any` pragma. If the file uses `export default {};`, also include the `import/no-anonymous-default-export` pragma.
|
|
296
|
+
- **Default exports**: our test modules require an `export default {};` to prevent global collision with other tests. Place this export at the top of the test module, underneath any file-level pragmas. **Forgetting this causes hard-to-debug global collision failures between test files.**
|
|
297
|
+
|
|
298
|
+
## Error Testing Strategy
|
|
299
|
+
|
|
300
|
+
- **Comprehensive error coverage**: Test all _explicit_ error paths, not just happy paths - explicit error paths are try/catch blocks and promise rejections, etc. _do not invent error paths and then test them!_
|
|
301
|
+
- **Error propagation**: Verify errors bubble up correctly through the call stack
|
|
302
|
+
- **Error logging verification**: Assert that errors are logged with expected context
|
|
303
|
+
- **Graceful degradation**: Test fallback behaviors when dependencies fail
|
|
304
|
+
- **Input validation**: Test with invalid, missing, and malformed inputs
|
|
305
|
+
- Fun fake error codes are great ("E_COLD_CALZONE", "E_SOGGY_STROMBOLI")
|
|
306
|
+
|
|
307
|
+
## Test Suite Organization
|
|
308
|
+
|
|
309
|
+
- **Hierarchical describe blocks**:
|
|
310
|
+
- Outer `describe` identifies the module
|
|
311
|
+
- Individual exported methods get their own `describe` block
|
|
312
|
+
- Inner `describe` blocks handle specific scenarios ("when X condition")
|
|
313
|
+
- **Test descriptions**:
|
|
314
|
+
- `describe` blocks handle the "when" conditions/scenarios
|
|
315
|
+
- `it` blocks focus purely on "should" assertions (no conditions). See **CRITICAL RULE #1** above.
|
|
316
|
+
- Avoid redundancy - don't repeat conditions in both `describe` and `it`
|
|
317
|
+
- **Grouping logic**:
|
|
318
|
+
- Group related test cases under shared setup scenarios
|
|
319
|
+
- Keep setup complexity minimal - prefer multiple simple setups over one complex one
|
|
320
|
+
- Use nested describes sparingly - max 3 levels deep for readability
|
|
321
|
+
|
|
322
|
+
## Test Data Patterns
|
|
323
|
+
|
|
324
|
+
- **Consistent naming**: Use UPPER_CASE for test-specific values to distinguish from real data
|
|
325
|
+
- **Realistic data**: Use plausible values that reflect real-world usage
|
|
326
|
+
- **Playfulness is GOOD**: fake-but-realistic data values can be fun. For example
|
|
327
|
+
- "Cal Zone" for a name
|
|
328
|
+
- "cal@zone.com" for an email address
|
|
329
|
+
- numeric or ID values like "8675309" or zip codes like "90210"
|
|
330
|
+
- fun, light-hearted pop-culture, geeky, or literary references keep things fun
|
|
331
|
+
- **Minimal fixtures**: Include only the fields necessary for the test scenario
|
|
332
|
+
- **Isolation**: Each test should create its own data - avoid shared mutable test data
|
|
333
|
+
|
|
334
|
+
⚠️ **DATA SAFETY RULE** ⚠️
|
|
335
|
+
|
|
336
|
+
**NEVER include in tests or examples:**
|
|
337
|
+
|
|
338
|
+
- Real customer data, names, or contact information
|
|
339
|
+
- Production database values, IDs, or records
|
|
340
|
+
- Actual API keys, credentials, or connection strings
|
|
341
|
+
- Real business financial data or metrics
|
|
342
|
+
- Internal IP addresses or production URLs
|
|
343
|
+
|
|
344
|
+
**ALWAYS use clearly fake/playful test data as demonstrated in this guide.**
|
|
345
|
+
|
|
346
|
+
## State Management
|
|
347
|
+
|
|
348
|
+
- **Variable declarations**: Chain `let` declarations with commas above `beforeEach`
|
|
349
|
+
- **State initialization**: Variables initialized in `beforeEach`
|
|
350
|
+
- **Fresh state guarantee**:
|
|
351
|
+
- Call `jest.clearAllMocks()` and `jest.resetModules()` in outer `beforeEach`
|
|
352
|
+
- **Do NOT use `jest.resetAllMocks()`** in the outer `beforeEach` — it wipes implementations, which breaks module-level `mockReturnThis()` chains (see CRITICAL RULE #2 exception)
|
|
353
|
+
- Use `mockReset()` on **individual mocks** when a nested `beforeEach` needs to replace behavior already configured by an outer `beforeEach`
|
|
354
|
+
- **Never use `mockRestore()`** — it only applies to `jest.spyOn`, which this codebase does not use
|
|
355
|
+
|
|
356
|
+
### Mock Reset Decision Tree
|
|
357
|
+
|
|
358
|
+
Am I in the **outer** `beforeEach`?
|
|
359
|
+
|
|
360
|
+
- YES → Use `jest.clearAllMocks()` + `jest.resetModules()`. Then configure default mock behaviors for this suite.
|
|
361
|
+
- NO (nested `beforeEach` for a specific scenario) →
|
|
362
|
+
- Does the mock already have behavior from an outer `beforeEach` that I need to **override**?
|
|
363
|
+
- YES → Call `mockName.mockReset()` on that specific mock, then configure its new behavior
|
|
364
|
+
- NO → Just configure the mock (the outer `beforeEach` hasn't set conflicting behavior)
|
|
365
|
+
- **Dynamic vs. static imports**:
|
|
366
|
+
- **Static import** — Use for stateless utilities, types, enums, and constants (no module-level singletons, no cached state)
|
|
367
|
+
- **Dynamic import** (`await import("./module")` inside `beforeEach`) — Use for everything else, especially modules that hold state, use singletons, or have mocked dependencies. This only works because `jest.resetModules()` in the outer `beforeEach` busts the module cache — without it, repeated dynamic imports return the same cached instance
|
|
368
|
+
|
|
369
|
+
## Mocking Strategy
|
|
370
|
+
|
|
371
|
+
- **Module-level mock declarations**: Dependencies mocked above test suites with bare `jest.fn()` — see **CRITICAL RULE #2** for configuration rules
|
|
372
|
+
- **Mock behavior configuration**: Configured per scenario in `beforeEach` blocks
|
|
373
|
+
- **Mock naming**: Consistent mock prefix (e.g., `mockLogger`, `mockGetMessageBroker`)
|
|
374
|
+
- **Selective mocking**: Mock only the methods being used unless it significantly raises complexity
|
|
375
|
+
- **Mock verification**: Always verify mock calls when behavior depends on them
|
|
376
|
+
- **Mock realism**: Ensure mocks behave like real implementations (same async patterns, error types)
|
|
377
|
+
- **Mock methods**:
|
|
378
|
+
- Use `mockImplementation` when invoking callbacks passed to the mocked function — see **CRITICAL RULE #3**
|
|
379
|
+
- Use `mockResolvedValue` for async returns
|
|
380
|
+
- Use `mockReturnValue` for sync returns
|
|
381
|
+
- Prefer "Once" versions when appropriate (`mockResolvedValueOnce`, etc.)
|
|
382
|
+
- **Special cases**:
|
|
383
|
+
- Do NOT mock lodash unless uniquely warranted
|
|
384
|
+
- Do NOT mock luxon - use jest's fake timers instead (restore real timers in `afterEach`)
|
|
385
|
+
- **Empty mocks**: Use only when necessary (not common)
|
|
386
|
+
- **Types**: Don't mock types/enums unless instructed to do so
|
|
387
|
+
|
|
388
|
+
## Async Testing Patterns
|
|
389
|
+
|
|
390
|
+
- **Preferred**: `async/await` pattern
|
|
391
|
+
- **`done` callback** — Last resort only. Use `done` when the method under test does not return a promise and completion depends on a mock callback firing (e.g., calling `done()` inside a `mockImplementation`). In all other cases, use `async/await`. Never mix `done` with `async` — Jest will hang or produce misleading errors.
|
|
392
|
+
- **Promise handling**: Use `.then()` and `.catch()` for result capture when needed
|
|
393
|
+
|
|
394
|
+
## Assertions and Testing Patterns
|
|
395
|
+
|
|
396
|
+
- **Call verification**:
|
|
397
|
+
- Always assert **count first**, then **arguments**: `toHaveBeenCalledTimes` before `toHaveBeenCalledWith`
|
|
398
|
+
- Use `toHaveBeenNthCalledWith` for multiple calls with different args
|
|
399
|
+
- **Equality matchers**:
|
|
400
|
+
- Use `toBe` for primitives (strings, numbers, booleans, null, undefined) — it checks reference equality
|
|
401
|
+
- Use `toEqual` for objects and arrays — it checks deep equality
|
|
402
|
+
- Never use `toBe` on objects (it will fail even if the contents match) or `toEqual` on primitives (it works but is imprecise)
|
|
403
|
+
- **Assertion granularity** (in order of preference):
|
|
404
|
+
1. **Full match** with `toEqual` — assert the entire object/array when possible
|
|
405
|
+
2. **Partial match** with `expect.objectContaining` / `expect.arrayContaining` — when the full shape is impractical or irrelevant to the test
|
|
406
|
+
3. **Individual property assertions** — last resort only. Never assert properties deep in an array one at a time when a full or partial match would work.
|
|
407
|
+
- **String matching**: Prefer matching full strings
|
|
408
|
+
- **Strings vs enumerations**: If an enum is used, the assertion comparing values should use the enumeration, not the equivalent string
|
|
409
|
+
- **Test data**: Use UPPER_CASE strings to indicate test data intent
|
|
410
|
+
- **Error testing**:
|
|
411
|
+
- Comprehensive error scenario coverage
|
|
412
|
+
- Verify error logging calls
|
|
413
|
+
- Test error propagation and handling strategies
|
|
414
|
+
- **Logging**:
|
|
415
|
+
- I do not write assertions for logs at any log level below "warn" (e.g., info, debug, silly, etc.), except if the log statement is the only thing executed as part of the code path (for example - an "else" block that _only_ has a logger.debug)
|
|
416
|
+
- Log levels of critical, error, and warn are asserted in unit tests
|
|
417
|
+
|
|
418
|
+
## Variable Naming
|
|
419
|
+
|
|
420
|
+
- No strict preferences, but:
|
|
421
|
+
- Match real method/argument names where possible for readability
|
|
422
|
+
- Use descriptive names for test-specific variables (e.g., `result`, `upsertResult`)
|
|
423
|
+
|
|
424
|
+
## Import and Module Management
|
|
425
|
+
|
|
426
|
+
- **Dynamic imports**: Use in `beforeEach` for modules that need fresh instances between tests
|
|
427
|
+
- **Static imports**: Can be used for types, constants, and utilities that don't maintain state
|
|
428
|
+
- **Mock placement**: All `jest.mock()` calls must be at the top level, before any imports unless otherwise required
|
|
429
|
+
- **Module isolation**: Use `jest.resetModules()` in outer `beforeEach` to ensure clean module state
|
|
430
|
+
|
|
431
|
+
## Test Quality Guidelines
|
|
432
|
+
|
|
433
|
+
- **Readability first**: Tests serve as living documentation - prioritize clarity over cleverness
|
|
434
|
+
- **Single responsibility**: Each test should verify one specific behavior
|
|
435
|
+
- **Descriptive names**: Test names should clearly state the expected behavior
|
|
436
|
+
- **Avoid test interdependence**: Tests should be runnable in any order
|
|
437
|
+
- **Performance consideration**: Avoid unnecessary async operations or complex computations in tests
|
|
438
|
+
- **Assertion clarity**: Use specific matchers (toHaveBeenCalledWith vs toHaveBeenCalled)
|
|
439
|
+
|
|
440
|
+
## IMPORTANT! Common Pitfalls to Avoid
|
|
441
|
+
|
|
442
|
+
Pay close attention to these items, since they may apply some nuance to the above rules.
|
|
443
|
+
|
|
444
|
+
- **Complex test setup**: If setup is complex, consider breaking into smaller, focused tests
|
|
445
|
+
- **Unused arguments**: If a mock (for example mockImplementation) has a method with unused arguments, be sure to prefix them with "\_" to prevent eslint/typescript compiler errors related to unused arguments.
|
|
446
|
+
- **Missing cleanup**: Always restore mocked timers, global variables, and environment changes
|
|
447
|
+
- **Snapshot overuse**: Use snapshots sparingly - prefer explicit assertions for critical data
|
|
448
|
+
- **Over-engineering test cases**: do not over-engineer test scenarios that don't exist in the actual implementation. For example, if a method takes a callback (like expresses app.listen()), don't test for cases like "if app.listen returns without calling callback" unless you are specifically told to do so.
|
|
449
|
+
- **Do not invent error paths**: Do not invent error paths when there aren't explicit try/catch or promise rejections
|
|
450
|
+
- **Executing method under test in it() blocks**: The method under test MUST be executed in `beforeEach()`, with results stored in variables. The `it()` blocks should ONLY contain assertions against those variables. This is a non-negotiable rule - review **CRITICAL RULE #1** before generating any tests.
|
|
451
|
+
- **Configuring mock behavior at module level**: Mock return values and implementations MUST be set inside `beforeEach`/`beforeAll`, not at module level. Review **CRITICAL RULE #2**.
|
|
452
|
+
- **Imperatively invoking callbacks via mock.calls**: Use `mockImplementation`/`mockImplementationOnce` to invoke callbacks, not `mock.calls[N][M]()`. Review **CRITICAL RULE #3**.
|
|
453
|
+
- **Writing superfluous tests**: Do not write multiple test scenarios that exercise the same code path with different literal values. Review the **Superfluous Test Prevention** section.
|
|
454
|
+
- **Skipping branch analysis**: Always perform a systematic branch analysis before writing tests. Review the **Coverage-Driven Test Planning** section.
|
|
455
|
+
- **Missing `export default {}`**: Every test file must include `export default {};` at the top (after pragmas) to prevent global collision. Forgetting it causes cryptic failures across test files.
|
|
456
|
+
- **Over-asserting log calls**: Do not assert info/debug/silly log calls unless the log is the _only_ statement in a code path. Only assert warn, error, and critical level logs.
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
The above guidelines represent my core testing patterns. For full working examples that demonstrate these conventions, see `.ai/UnitTestExamples.md`. You may also analyze the style of other `*.test.ts` files in this project and generate tests. Avoid repeating all test cases for all permutations of a function. Focus on covering as much as possible in general test cases and then only assert against the specific differences in more specific test cases.
|
|
461
|
+
Be sure to pay attention to the "Common Pitfalls to Avoid" section above.
|
|
462
|
+
Before writing error tests, verify that the function actually has: - try/catch blocks - explicit error handling - promise rejection handling
|
|
463
|
+
If none exist, do NOT write error tests.
|
|
464
|
+
|
|
465
|
+
**CRITICAL REMINDER — Pre-Writing Checklist:**
|
|
466
|
+
Before generating any tests, complete these steps in order:
|
|
467
|
+
|
|
468
|
+
1. **Branch analysis** (Coverage-Driven Test Planning): Read the source, enumerate every branch, map each to a test scenario.
|
|
469
|
+
2. **Superfluous test check** (Superfluous Test Prevention): Verify each planned scenario covers a distinct branch, not the same branch with different values.
|
|
470
|
+
3. **Execution location check** (CRITICAL RULE #1): Execute methods under test in `beforeEach()`, not in `it()` blocks.
|
|
471
|
+
4. **Mock configuration check** (CRITICAL RULE #2): Configure mock behavior in `beforeEach`, not at module level.
|
|
472
|
+
5. **Callback invocation check** (CRITICAL RULE #3): Use `mockImplementation` for callbacks, not `mock.calls[N][M]()`.
|
|
473
|
+
|
|
474
|
+
Once tests are generated:
|
|
475
|
+
|
|
476
|
+
- verify that you haven't fallen into one of the pitfalls I've described
|
|
477
|
+
- you can run the test to see if the output has errors
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
## Multi-Agent Development Workflow
|
|
2
|
+
|
|
3
|
+
This project uses a phased development workflow with two modes of operation:
|
|
4
|
+
|
|
5
|
+
### Interactive Commands (for design and discovery)
|
|
6
|
+
|
|
7
|
+
Run these as slash commands in a Claude Code session. They're conversational — expect back-and-forth.
|
|
8
|
+
|
|
9
|
+
- `/pm` — Product Manager: Collaborate to define features, scope MVP, write user stories
|
|
10
|
+
- `/architect` — Architect: Collaborate to design technical approach, create build plan
|
|
11
|
+
|
|
12
|
+
**Start a fresh session for each command.** The PM and architect are deliberately separate contexts. The handoff document (brief or build plan) carries the context between them.
|
|
13
|
+
|
|
14
|
+
### Autonomous Agents (for execution)
|
|
15
|
+
|
|
16
|
+
These run as subagents with isolated context windows. They read files, do their job, write reports.
|
|
17
|
+
|
|
18
|
+
- **pm** — Draft a product brief from notes/descriptions (when you trust the output pattern)
|
|
19
|
+
- **architect** — Draft a build plan from a brief or notes (when you trust the output pattern)
|
|
20
|
+
- **dev** — Implement a build plan, task by task, with happy-path tests
|
|
21
|
+
- **reviewer** — Adversarial code review with fresh eyes (read-only)
|
|
22
|
+
- **test-hardener** — Edge case testing, failure modes, tries to break things
|
|
23
|
+
- **explorer** — Map and document an unfamiliar codebase (read-only)
|
|
24
|
+
|
|
25
|
+
### Orchestrator
|
|
26
|
+
|
|
27
|
+
- `/build` — Runs the execution pipeline: dev → review → test, with automatic fix loops
|
|
28
|
+
|
|
29
|
+
### Document Flow
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
docs/briefs/[feature].md ← PM output
|
|
33
|
+
docs/build-plans/[feature].md ← Architect output
|
|
34
|
+
docs/codebase-map.md ← Explorer output
|
|
35
|
+
docs/reports/
|
|
36
|
+
├── dev-report-[feature].md ← Dev agent output
|
|
37
|
+
├── review-report-[feature].md ← Review agent output
|
|
38
|
+
├── review-fixes-[feature].md ← Fix loop items (if triggered)
|
|
39
|
+
└── test-report-[feature].md ← Test agent output
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Key Principle
|
|
43
|
+
|
|
44
|
+
Agents have zero shared context. Communication between phases happens through the filesystem only. This is intentional — it eliminates cognitive bleed between personas.
|
|
45
|
+
|
|
46
|
+
### Choosing Interactive vs. Autonomous
|
|
47
|
+
|
|
48
|
+
Use **interactive commands** (`/pm`, `/architect`) when:
|
|
49
|
+
|
|
50
|
+
- You're still figuring out what to build
|
|
51
|
+
- Requirements are vague and need discussion
|
|
52
|
+
- You want to challenge assumptions and explore trade-offs
|
|
53
|
+
- You're early in adopting this workflow
|
|
54
|
+
|
|
55
|
+
Use **autonomous agents** when:
|
|
56
|
+
|
|
57
|
+
- You have clear input (notes, brief, description) and trust the agent to draft well
|
|
58
|
+
- You want a first draft to react to rather than building from scratch
|
|
59
|
+
- You've used the interactive versions enough to know what good output looks like
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: architect
|
|
3
|
+
description: Designs technical approach and produces a build plan from a product brief or feature description. Surveys the existing codebase to ensure the design fits. For interactive design sessions, use /architect command instead.
|
|
4
|
+
tools: Read, Write, Edit, Glob, Grep, Bash
|
|
5
|
+
model: sonnet
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are a Senior Software Architect. You've been given context about a feature — a product brief, notes, or a description — and your job is to produce a concrete build plan.
|
|
9
|
+
|
|
10
|
+
Unlike the interactive /architect command, you are NOT having a design conversation. You are making decisions and producing a plan. Be opinionated about the approach. Document your reasoning so decisions can be challenged.
|
|
11
|
+
|
|
12
|
+
## Your Process
|
|
13
|
+
|
|
14
|
+
1. Check `docs/briefs/` for a product brief. If one exists, use it as primary input.
|
|
15
|
+
2. If no brief exists, work from whatever context you've been given.
|
|
16
|
+
3. Read `docs/codebase-map.md` if it exists. If not, survey the project structure, key patterns, and conventions before designing anything.
|
|
17
|
+
4. Identify existing patterns in the codebase. Don't propose new patterns when established ones exist.
|
|
18
|
+
5. Design the approach. Favor boring, proven solutions.
|
|
19
|
+
6. Break it into ordered, independently testable tasks.
|
|
20
|
+
7. Write the build plan.
|
|
21
|
+
|
|
22
|
+
## Output
|
|
23
|
+
|
|
24
|
+
Write the plan to `docs/build-plans/[feature-name].md`:
|
|
25
|
+
|
|
26
|
+
```markdown
|
|
27
|
+
# Build Plan: [Feature Name]
|
|
28
|
+
|
|
29
|
+
## Context Source
|
|
30
|
+
|
|
31
|
+
Where requirements came from (brief, conversation, notes, etc.)
|
|
32
|
+
|
|
33
|
+
## Problem Summary
|
|
34
|
+
|
|
35
|
+
1-2 paragraph distillation of what we're building and why.
|
|
36
|
+
|
|
37
|
+
## Technical Approach
|
|
38
|
+
|
|
39
|
+
High-level description of the approach. 2-3 paragraphs max.
|
|
40
|
+
|
|
41
|
+
## Key Design Decisions
|
|
42
|
+
|
|
43
|
+
- **Decision**: [What we decided]
|
|
44
|
+
- **Why**: [Reasoning]
|
|
45
|
+
- **Trade-off**: [What we're giving up]
|
|
46
|
+
- **Assumption**: [If this decision rests on an assumption, flag it]
|
|
47
|
+
|
|
48
|
+
## Existing Patterns to Follow
|
|
49
|
+
|
|
50
|
+
Patterns, conventions, or utilities already in the codebase that this feature should use.
|
|
51
|
+
|
|
52
|
+
## Implementation Tasks
|
|
53
|
+
|
|
54
|
+
Tasks are ordered. Each task should be completable independently and testable.
|
|
55
|
+
|
|
56
|
+
### Task 1: [Name]
|
|
57
|
+
|
|
58
|
+
- **What**: Description of what to build
|
|
59
|
+
- **Files**: Which files to create/modify
|
|
60
|
+
- **Basic Tests**: Happy-path tests the developer should write alongside this task
|
|
61
|
+
- **Done when**: Clear completion criteria
|
|
62
|
+
|
|
63
|
+
### Task 2: [Name]
|
|
64
|
+
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
## Technical Risks
|
|
68
|
+
|
|
69
|
+
- **Risk**: [Description]
|
|
70
|
+
- **Mitigation**: [How to handle it]
|
|
71
|
+
- **Likelihood**: Low/Medium/High
|
|
72
|
+
|
|
73
|
+
## Dependencies
|
|
74
|
+
|
|
75
|
+
External packages, services, or APIs needed.
|
|
76
|
+
|
|
77
|
+
## Handoff Notes for Developer
|
|
78
|
+
|
|
79
|
+
Anything the dev needs to know that isn't obvious from the tasks — gotchas, performance considerations, "don't do X because Y" warnings.
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Personality
|
|
83
|
+
|
|
84
|
+
You've been burned by over-engineering before. You have a healthy distrust of abstractions that don't pay for themselves. You'd rather have a slightly repetitive codebase than one where you need a PhD to trace a function call.
|
|
85
|
+
|
|
86
|
+
## Important
|
|
87
|
+
|
|
88
|
+
- Do NOT write implementation code. Pseudocode is fine for clarifying intent.
|
|
89
|
+
- Do NOT ask the user questions. Make decisions, document your reasoning, flag assumptions.
|
|
90
|
+
- Survey the actual codebase before designing. Don't propose patterns that conflict with what exists.
|