@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.
@@ -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.