Links

Examples

Sam Hatoum

    # Development Guide

    ## Pomodoro + TDD + TCR + 100% Coverage

    ```
    ╔═════════════════════════════════════════════════════════════════════╗
    ║   Red ───► Green ───►[TCR]───► Refactor ───►[TCR]───► Done          ║
    ║                                                                     ║
    ║   [TCR] = test ──┬── pass ──► commit ──► continue                   ║
    ║                  └── fail ──► REVERT ──► RETHINK                    ║
    ║                                                                     ║
    ║   REVERT means STOP. Don't fix in place.                            ║
    ║   RETHINK means try again with SMALLER STEPS OR try a NEW DESIGN    ║
    ╚═════════════════════════════════════════════════════════════════════╝
    ```

    ---

    ## 🍅 Pomodoro Workflow

    One atomic test + implementation per cycle (5-15 min).

    **Planning a Pomodoro:**

    - Independent, valuable, small, testable
    - Define: value added, code surface size (S/M/L), complexity (S/M/L)
    - 1-2 line approach description
    - Create `pomodoro-plan.md` with sections: TODO / DONE
    - Commit the plan with initial pomodoros

    **Execution:**

    1. Write ONE failing test (Red)
    2. Write MINIMAL code to pass (Green)
    3. TCR: `test && commit || revert` (include plan update)
    4. Refactor if needed
    5. TCR: `test && commit || revert`
    6. Move pomodoro to DONE in the `pomodoro-plan.md`, commit with TCR

    **TCR Command:**

    ```bash
    pnpm test --run && \
    git add -A && git commit -m "<COMMITIZEN FORMAT>" || \
    git checkout -- packages/<package>/
    ```

    ---

    ## 🎯 TDD + 100% Coverage

    All thresholds enforced at 100%. Tests drive the code.

    | Do                              | Don't                                    |
    | ------------------------------- | ---------------------------------------- |
    | Let tests drive all code        | Write code without a failing test first  |
    | Add branches only when tested   | Defensive `??`, `?:`, `if/else` untested |
    | Test all error paths            | Leave error handling unverified          |
    | Remove dead code after each run | Keep unused code "just in case"          |

    **NEVER Exclude Files to Dodge Coverage**

    ```
    ╔══════════════════════════════════════════════════════════════════════╗
    ║  Excluding a file from coverage to avoid testing it is FORBIDDEN.    ║
    ║  "Hard to test" code (file system, dynamic imports, network, etc.)   ║
    ║  → Inject dependencies and mock them. That's what mocks are for.     ║
    ╚══════════════════════════════════════════════════════════════════════╝
    ```

    Coverage exclusions are ONLY for:
    - Type-only files (interfaces, type definitions)
    - Barrel exports (index.ts re-exports)
    - Files that are genuinely 0% logic (pure declarations)

    If you're tempted to exclude a file because it's "infrastructure" or "integration-focused" — STOP. The coverage requirement exists precisely to force testable design.

    ---

    ## 🧠 Design Philosophy

    ### Behavior First, Everything Else Emerges
    ```
    ╔═══════════════════════════════════════════════════════════════════════╗
    ║  Start with BEHAVIOR. Always.                                         ║
    ║  Types, interfaces, data structures — these emerge to serve behavior. ║
    ║  If it doesn't affect runtime outcomes, it's not the design.          ║
    ╚═══════════════════════════════════════════════════════════════════════╝
    ```

    **The Core Insight:**

    Software exists to *do things*. A program that compiles perfectly but produces wrong outputs is worthless. A program with ugly types that produces correct outputs is valuable. This tells us where to focus.

    Behavior is the only thing that matters at runtime. Everything else — types, interfaces, abstractions, patterns — exists solely to help us write correct behavior more reliably.

    **The Wrong Way (Structure-First):**
    ```
    1. Design the data shapes / types / interfaces
    2. Define the abstractions and relationships  
    3. Finally, implement the actual behavior
    ```

    This is backwards. You're making decisions about shape before you understand what transformations you need. You're building a warehouse before you know what you're storing.

    **The Right Way (Behavior-First):**
    ```
    1. What OUTCOME do I need? (input → output)
    2. Write a test that asserts on that outcome
    3. Implement the simplest code that produces the outcome
    4. Let structure emerge to support the behavior
    ```

    **Why This Works:**

    - You only create structure that's *actually needed*
    - Tests verify behavior, which implicitly validates any supporting structure
    - You discover the real shape of data by *using* it, not by speculating
    - Refactoring is safe because behavior is locked in by tests

    **Practical Rule:**

    Your first test for any feature should call a **function** and assert on its **output**. Whatever types, interfaces, or data shapes that function needs will get created *because* the function needs them — not before, not separately, not speculatively.
    ```ts
    // ❌ WRONG: Starting with structure
    // "Create User type"
    // "Create UserService interface"  
    // "Design the repository pattern"

    // ✅ RIGHT: Starting with behavior
    it('creates user with generated id', () => {
    const result = createUser({ name: 'Alice' });
    expect(result).toEqual({ id: expect.any(String), name: 'Alice' });
    });
    // Types, interfaces, whatever — they emerge because this function needs them
    ```

    **The Mantra:**

    > "What should this function return when given this input?"

    If you can't answer that question, you're not ready to write code. If you *can* answer it, write the test first, then make it pass. The rest takes care of itself.


    ---

    ## 📝 Testing Guidelines

    ### Test Title = Spec

    The `it('should...')` title defines what you're testing. Body proves exactly that.

    ```ts
    // ✅ Title matches body
    it('should reject empty usernames', () => {
    const result = validate({ username: '' });
    expect(result.valid).toBe(false);
    });

    // ❌ Body does more than title claims
    it('should reject empty usernames', () => {
    const result = validate({ username: '' });
    expect(result.valid).toBe(false);
    expect(result.errors).toContain('Username required'); // second spec
    expect(logger.warn).toHaveBeenCalled(); // third spec
    });
    ```

    ### Stubs Over Mocks

    Use deterministic stubs. Mock at boundaries only when stubs aren't possible.

    ```ts
    // ✅ Deterministic stub
    function createTestIdGenerator() {
    let counter = 0;
    return () => `test-id-${counter++}`;
    }

    it('creates user with generated id', () => {
    const generateId = createTestIdGenerator();
    const user = createUser({ name: 'Alice' }, generateId);
    expect(user).toEqual({ id: 'test-id-0', name: 'Alice' });
    });

    // ❌ Mocking couples tests to implementation
    it('creates user with generated id', () => {
    const mockGenerateId = vi.fn().mockReturnValue('user-123');
    const user = createUser({ name: 'Alice' }, mockGenerateId);
    expect(mockGenerateId).toHaveBeenCalled();
    });
    ```

    ### Assert Whole Objects

    Catch structural changes. Don't cherry-pick properties.

    ```ts
    // ✅ Catches any unexpected changes
    expect(result).toEqual({ type: 'USER', name: 'Alice', processed: true });

    // ❌ Misses if extra properties added/removed
    expect(result.type).toBe('USER');
    expect(result.processed).toBe(true);
    ```

    ### Squint Test

    All tests should look identical when you squint: **SETUP → EXECUTE → VERIFY**

    ```ts
    // ✅ Single clear structure
    it('transforms user to uppercase', () => {
    const input = { type: 'user' }; // SETUP
    const result = transform(input); // EXECUTE
    expect(result).toEqual({ type: 'USER' }); // VERIFY
    });

    // ❌ Multiple execute/verify = split into separate tests
    it('does too many things', () => {
    const result = transform({ type: 'user' });
    expect(result.type).toBe('USER');
    const updated = updateResult(result); // second execute
    expect(updated.modified).toBe(true); // second verify
    });
    ```


Tags: ai   agent   coding  

Last modified 15 January 2026