added pi config files

This commit is contained in:
2026-05-27 14:09:18 +02:00
parent e29e48a633
commit 9eca6b572d
18 changed files with 1599 additions and 0 deletions
@@ -0,0 +1,269 @@
# Strict TDD Module — Verify Phase
> **This module is loaded ONLY when Strict TDD Mode is enabled AND a test runner is available.**
> If you are reading this, the orchestrator already verified both conditions. Follow every instruction.
## TDD Verification Philosophy
When Strict TDD Mode is active, verification goes beyond "does the code work?" to "was the code built correctly?" — meaning: was TDD actually followed? The apply phase reports TDD evidence; your job is to validate that evidence against reality.
## Step 5a: TDD Compliance Check (includes Assertion Quality Audit)
Read the `apply-progress` artifact and verify that TDD was actually followed:
```
Read apply-progress artifact:
├── Find the "TDD Cycle Evidence" table
├── FOR EACH task row:
│ ├── RED column:
│ │ ├── Must say "✅ Written"
│ │ ├── Verify: test file EXISTS in the codebase
│ │ └── Flag: CRITICAL if test file does not exist
│ │
│ ├── GREEN column:
│ │ ├── Must say "✅ Passed"
│ │ ├── Cross-reference with Step 5b test execution results:
│ │ │ └── The test file listed must PASS when you run it
│ │ └── Flag: CRITICAL if test fails now (was it really green?)
│ │
│ ├── TRIANGULATE column:
│ │ ├── If "✅ N cases" → verify N test cases exist in the test file
│ │ ├── If " Single" → verify spec truly has only one scenario for this task
│ │ └── Flag: WARNING if spec has multiple scenarios but only 1 test case
│ │
│ ├── SAFETY NET column:
│ │ ├── If "✅ N/N" → existing tests were run before modification (good)
│ │ ├── If "N/A (new)" → verify the file was actually NEW (not modified)
│ │ └── Flag: WARNING if file was modified but safety net shows "N/A"
│ │
│ └── REFACTOR column:
│ ├── Not strictly verifiable (subjective quality)
│ └── Skip verification, trust the report
├── If NO "TDD Cycle Evidence" table found:
│ └── Flag: CRITICAL — apply phase did not report TDD evidence
│ (Strict TDD was enabled but apply did not follow the protocol)
└── Summary: "{N}/{total} tasks have complete TDD evidence"
```
## Step 5 Expanded: Test Layer Validation
Classify ALL test files related to this change by their testing layer:
```
Scan test files created/modified by this change:
├── Classify each test file:
│ ├── Unit test: tests a single function/class in isolation
│ │ └── Indicators: no render(), no page., no HTTP calls, mocked dependencies
│ ├── Integration test: tests component interaction or user behavior
│ │ └── Indicators: render(), screen., userEvent., testing-library imports
│ ├── E2E test: tests full system through real browser/HTTP
│ │ └── Indicators: page.goto(), playwright/cypress imports, browser context
│ └── Unknown: cannot classify → report as-is
├── Report distribution:
│ ├── Unit: {N} tests across {N} files
│ ├── Integration: {N} tests across {N} files
│ ├── E2E: {N} tests across {N} files
│ └── Total: {N} tests
├── Cross-reference with capabilities:
│ ├── If integration tests exist but tools not in capabilities → how?
│ ├── If E2E tests exist but tools not in capabilities → how?
│ └── Flag: WARNING if tests use tools not detected in capabilities
└── For each spec scenario: note which layer covers it
└── Flag: SUGGESTION if critical business logic only has unit tests
(only if integration/E2E tools are available)
```
## Step 5d Expanded: Changed File Coverage
When coverage tool is available, report coverage for CHANGED files specifically:
```
IF coverage tool available (from cached capabilities):
├── Run: {test_command} --coverage (or equivalent)
├── Parse the coverage report
├── Filter to ONLY files created or modified in this change
│ (get file list from apply-progress "Files Changed" table)
├── Report per-file:
│ ├── File path
│ ├── Line coverage %
│ ├── Branch coverage % (if available)
│ ├── Uncovered line ranges (specific lines, not just %)
│ └── Flag per file:
│ ├── ≥ 95% → ✅ Excellent
│ ├── ≥ 80% → ⚠️ Acceptable
│ └── < 80% → ⚠️ Low (list uncovered lines)
├── Report aggregate:
│ ├── Average coverage of changed files
│ ├── Total uncovered lines in changed files
│ └── Compare to threshold if configured
└── Flag: WARNING if any changed file < 80% coverage
IF coverage tool NOT available:
└── Report: "Coverage analysis skipped — no coverage tool detected"
(NOT a failure — just not available)
```
## Step 5e: Quality Metrics (if tools available)
Run quality checks ONLY on changed files, ONLY if tools are available:
```
Read quality tools from cached capabilities:
IF linter available:
├── Run linter on changed files only
├── Report: errors and warnings
└── Flag: WARNING for errors, SUGGESTION for warnings
IF type checker available:
├── Run type checker (usually whole-project, not per-file)
├── Filter output to changed files
├── Report: type errors in changed files
└── Flag: WARNING for type errors
IF neither available:
└── Report: "Quality metrics skipped — no tools detected"
```
## Report Template Extension
When Strict TDD Mode is active, your verification report MUST include these additional sections:
```markdown
### TDD Compliance
| Check | Result | Details |
|-------|--------|---------|
| TDD Evidence reported | ✅ / ❌ | {Found in apply-progress / Missing} |
| All tasks have tests | ✅ / ❌ | {N}/{total} tasks have test files |
| RED confirmed (tests exist) | ✅ / ⚠️ | {N}/{total} test files verified |
| GREEN confirmed (tests pass) | ✅ / ❌ | {N}/{total} tests pass on execution |
| Triangulation adequate | ✅ / ⚠️ / | {N} tasks triangulated / {N} single-case |
| Safety Net for modified files | ✅ / ⚠️ | {N}/{total} modified files had safety net |
**TDD Compliance**: {N}/{total} checks passed
---
### Test Layer Distribution
| Layer | Tests | Files | Tools |
|-------|-------|-------|-------|
| Unit | {N} | {N} | {tool} |
| Integration | {N} | {N} | {tool or "not installed"} |
| E2E | {N} | {N} | {tool or "not installed"} |
| **Total** | **{N}** | **{N}** | |
---
### Changed File Coverage
| File | Line % | Branch % | Uncovered Lines | Rating |
|------|--------|----------|-----------------|--------|
| `path/to/file.ext` | 95% | 90% | — | ✅ Excellent |
| `path/to/other.ext` | 82% | 75% | L45-48, L62 | ⚠️ Acceptable |
| `path/to/new.ext` | 100% | 100% | — | ✅ Excellent |
**Average changed file coverage**: {N}%
{or "Coverage analysis skipped — no coverage tool detected"}
---
### Assertion Quality
| File | Line | Assertion | Issue | Severity |
|------|------|-----------|-------|----------|
| ... | ... | ... | ... | ... |
**Assertion quality**: {N} CRITICAL, {N} WARNING
{or "✅ All assertions verify real behavior"}
---
### Quality Metrics
**Linter**: ✅ No errors / ⚠️ {N} warnings / ❌ {N} errors / Not available
**Type Checker**: ✅ No errors / ❌ {N} errors / Not available
```
## Step 5f: Assertion Quality Audit (MANDATORY)
Scan ALL test files created or modified by this change and check for trivial/meaningless assertions:
```
FOR EACH test file related to the change:
├── Read the file content
├── Scan for BANNED assertion patterns:
│ ├── Tautologies: expect(true).toBe(true), assert True, expect(1).toBe(1)
│ ├── Orphan empty checks: expect(result).toEqual([]) or assert len(result) == 0
│ │ └── UNLESS there is a companion test with same setup that asserts NON-EMPTY
│ ├── Type-only assertions used alone: toBeDefined(), not.toBeNull(), typeof checks
│ │ └── These are OK if COMBINED with value assertions in the same test
│ ├── Assertions that never call production code (no function call, no render, no request)
│ ├── Ghost loops: assertions inside for/forEach over queryAll/filter results
│ │ └── Check if the collection could be empty — if so, the assertions NEVER RUN
│ │ Flag: CRITICAL — a loop over an empty array is a test that ALWAYS passes
│ ├── Incomplete TDD cycle: test passes because preconditions prevent code from running
│ │ └── e.g., testing behavior of a component that is never rendered due to state
│ │ Flag: CRITICAL — test must set up conditions where the code path IS exercised
│ ├── Smoke-test-only: render() + toBeInTheDocument() without behavioral assertions
│ │ └── "Renders without crash" is NOT a valid test — it must assert WHAT was rendered
│ │ Flag: WARNING — smoke tests do not count toward TDD coverage
│ ├── Implementation detail coupling: assertions on CSS classes, internal state, mock call counts
│ │ └── expect(el.className).toContain("text-xs") or expect(mock.calls.length).toBe(3)
│ │ Flag: WARNING — tests must assert behavior, not implementation
│ └── Mock/assertion ratio: count vi.mock() calls vs expect() calls per test file
│ └── If mocks > 2× assertions → Flag: WARNING — "Mock-heavy test ({N} mocks, {N} assertions)"
│ Recommend: extract logic to pure function or move to higher test layer
├── For each violation found:
│ ├── Record: file, line number, the assertion, why it's trivial
│ └── Classify:
│ ├── CRITICAL: tautology (expect(true).toBe(true)) — test proves NOTHING
│ ├── CRITICAL: assertion without production code call — test exercises nothing
│ ├── CRITICAL: ghost loop — assertions inside loop over possibly-empty collection
│ ├── WARNING: empty collection without companion non-empty test
│ ├── WARNING: type-only assertion without value assertion
│ ├── WARNING: smoke-test-only — render + toBeInTheDocument without behavioral check
│ ├── WARNING: CSS class / implementation detail assertion
│ └── WARNING: mock-heavy test (mocks > 2× assertions) — wrong test layer
├── Check triangulation quality:
│ ├── Count distinct test cases per behavior
│ ├── If only 1 test case exists for a behavior with multiple spec scenarios:
│ │ └── Flag: WARNING — "Insufficient triangulation for {behavior}"
│ ├── If all test cases assert the SAME type of value (e.g., all check empty arrays):
│ │ └── Flag: WARNING — "No variance in test expectations — all assert empty/trivial"
│ └── A well-triangulated behavior has tests asserting DIFFERENT expected values
└── Summary: "{N} trivial assertions found across {N} files"
```
### Assertion Quality Report Table
Include this table in the verification report when any issues are found:
```markdown
### Assertion Quality
| File | Line | Assertion | Issue | Severity |
|------|------|-----------|-------|----------|
| `path/test.ts` | 15 | `expect(true).toBe(true)` | Tautology — proves nothing | CRITICAL |
| `path/test.ts` | 23 | `expect(result).toEqual([])` | Empty without companion non-empty test | WARNING |
| `path/test.ts` | 31 | `expect(result).toBeDefined()` | Type-only — no value asserted | WARNING |
**Assertion quality**: {N} CRITICAL, {N} WARNING
```
If zero issues found, report: "**Assertion quality**: ✅ All assertions verify real behavior"
## Rules (Strict TDD Verify specific)
- ALWAYS check the TDD Cycle Evidence table from apply-progress — it's the primary artifact
- ALWAYS cross-reference reported test files against actual execution — don't trust the report blindly
- ALWAYS run the Assertion Quality Audit (Step 5f) — trivial tests are WORSE than missing tests
- If apply-progress has no TDD evidence table, flag as CRITICAL — the protocol was not followed
- If tautology assertions are found (expect(true).toBe(true)), flag as CRITICAL — these MUST be rewritten
- Coverage and quality metrics are informational, NOT blocking — only flag as WARNING, never CRITICAL
- Test layer distribution is informational — SUGGESTION level only
- DO NOT fix issues — only report. The orchestrator decides.
- If coverage/quality tools are not available, say so cleanly and move on — never flag missing tools as failures
+364
View File
@@ -0,0 +1,364 @@
# Strict TDD Module — Apply Phase
> **This module is loaded ONLY when Strict TDD Mode is enabled AND a test runner is available.**
> If you are reading this, the orchestrator already verified both conditions. Follow every instruction.
## TDD Philosophy
TDD is not testing. TDD is **software design driven by tests**. You write a test that describes what the code SHOULD do, then write the minimum code to make it real. The tests design the API, the contracts, the behavior. Code is a side effect of tests.
### The Three Laws
1. **Do NOT write production code** until you have a failing test
2. **Do NOT write more test** than is necessary to fail
3. **Do NOT write more code** than is necessary to pass the test
## TDD Implementation Cycle
For EVERY task assigned to you, follow this cycle strictly:
```
FOR EACH TASK:
├── 0. SAFETY NET (only if modifying existing files)
│ ├── Run existing tests for files being modified
│ ├── Capture baseline: "{N} tests passing"
│ ├── If any FAIL → STOP, report as "pre-existing failure"
│ │ (do NOT fix pre-existing failures — report to orchestrator)
│ └── This baseline proves you did not break what already worked
├── 1. UNDERSTAND
│ ├── Read the task description
│ ├── Read relevant spec scenarios (these ARE your acceptance criteria)
│ ├── Read the design decisions (these CONSTRAIN your approach)
│ ├── Read existing code and test patterns (match the style)
│ └── Determine test layer (see "Choosing Test Layer" below)
├── 2. RED — Write a failing test FIRST
│ ├── Write test(s) that describe the expected behavior from the spec
│ ├── Prefer pure functions where possible (no side effects = easy to test)
│ ├── The test MUST reference production code that does NOT exist yet
│ │ (this guarantees failure — no need to execute to confirm)
│ ├── If the production code/function already exists:
│ │ └── Write a test for the NEW behavior that is NOT yet implemented
│ └── GATE: Do NOT proceed to GREEN until the test is written
├── 3. GREEN — Write the MINIMUM code to pass
│ ├── Implement ONLY what the failing test needs
│ ├── Fake It is VALID here (hardcoded return values are OK)
│ ├── EXECUTE tests → must PASS
│ │ ├── ✅ Passed → proceed to TRIANGULATE or REFACTOR
│ │ └── ❌ Failed → fix the implementation, NOT the test
│ └── GATE: Do NOT proceed until GREEN is confirmed by execution
├── 4. TRIANGULATE (MANDATORY for most tasks)
│ ├── DEFAULT: triangulation is REQUIRED. You need a compelling reason to skip it.
│ ├── Add a second test case with DIFFERENT inputs/expected outputs
│ ├── EXECUTE tests → if Fake It breaks (hardcoded no longer works):
│ │ └── Generalize to real logic (this is the whole point)
│ ├── Repeat until ALL spec scenarios for this task are covered
│ ├── Each triangulation pass: write test → run → fix implementation
│ ├── MINIMUM: at least 2 test cases per behavior (happy path + one edge case)
│ │ ├── One test with data that produces a NON-EMPTY/NON-TRIVIAL result
│ │ └── One test with data that exercises a DIFFERENT code path
│ ├── WATCH OUT for GREEN that passes trivially:
│ │ ├── If your test passes because the component/element isn't rendered → NOT a real GREEN
│ │ ├── If your test passes because a loop iterates 0 times → NOT a real GREEN
│ │ ├── If your test passes because the setup doesn't trigger the code path → NOT a real GREEN
│ │ └── A real GREEN means: production code RAN and produced the expected output
│ ├── Skip triangulation ONLY when ALL of these are true:
│ │ ├── The task is purely structural (config file, constant definition, type export)
│ │ ├── There is literally ONE possible output (no branching, no logic)
│ │ └── You explicitly note "Triangulation skipped: {reason}" in the evidence table
│ └── GATE: All spec scenarios for this task must have tests before REFACTOR
├── 5. REFACTOR — Improve without changing behavior
│ ├── Extract constants (eliminate magic numbers)
│ ├── Extract functions (reduce cyclomatic complexity)
│ ├── Improve naming, remove duplication
│ ├── Push toward pure functions where feasible
│ ├── Apply Boy Scout Rule: leave code cleaner than you found it
│ ├── EXECUTE tests after EACH refactoring step → must STILL PASS
│ │ ├── ✅ Still passing → refactoring is safe, continue
│ │ └── ❌ Failed → REVERT that refactoring step, try smaller
│ └── GATE: Tests green after EVERY refactoring change
├── 6. Mark task complete [x]
└── 7. Note any deviations or issues discovered
```
## Choosing Test Layer
Based on the testing capabilities cached in Engram (`sdd/{project}/testing-capabilities`), choose the appropriate test layer for each task:
```
Determine test layer by WHAT the task does:
├── Pure logic, utility function, calculation, data transformation
│ └── Unit test (always available if test runner exists)
├── Component rendering, user interaction, state changes
│ ├── IF integration tools available → Integration test
│ └── IF NOT → Unit test with mocks (degrade gracefully)
├── Multi-component flow, API interaction, context/provider behavior
│ ├── IF integration tools available → Integration test
│ └── IF NOT → Unit test with mocks
├── Critical business flow, full user journey, cross-page navigation
│ ├── IF E2E tools available → E2E test
│ ├── IF NOT but integration available → Integration test
│ └── IF neither → Unit test (degrade gracefully)
└── Default: Unit test (always the fallback)
```
**Key rule**: Use the HIGHEST available layer that fits the task. But NEVER skip a task because a layer is unavailable — degrade to the next available layer.
## Test Execution
Detect the test runner from the cached testing capabilities:
```
Read test command from:
├── Cached capabilities → test_runner.command (fastest — already detected)
├── openspec/config.yaml → rules.apply.test_command (override)
└── Fallback: detect from package.json/pyproject.toml/go.mod
When executing tests during TDD:
├── Run ONLY the relevant test file, not the entire suite
│ ├── JS/TS: {runner} {test-file-path} (e.g., pnpm vitest run src/utils/tax.test.ts)
│ ├── Python: pytest {test-file-path}
│ ├── Go: go test ./{package}/... -run {TestName}
│ └── Adapt to the runner's CLI
├── This keeps the cycle FAST
└── Full suite runs happen in sdd-verify, not here
```
## Pure Function Preference
When writing production code in GREEN/TRIANGULATE steps, prefer pure functions:
```
✅ PREFER (pure — easy to test):
function calculateDiscount(price: number, quantity: number): number {
return quantity >= 5 ? price * quantity * 0.1 : 0
}
❌ AVOID (impure — hard to test):
function calculateDiscount(item: Item) {
globalState.lastDiscount = item.price * 0.1 // side effect
updateDOM() // side effect
return globalState.lastDiscount
}
```
**Why**: Pure functions are deterministic (same input → same output), have no side effects, and are trivially testable. TDD naturally pushes you toward pure functions — embrace it.
## Approval Testing (for refactoring existing code)
When a task involves REFACTORING existing code (not writing new code):
```
BEFORE touching production code:
├── 1. Identify existing behavior to preserve
├── 2. Write "approval tests" that capture current behavior:
│ ├── Call the function with known inputs
│ ├── Assert the CURRENT outputs (even if ugly or wrong)
│ └── These tests document what the code does NOW
├── 3. Run approval tests → must PASS (they describe current reality)
├── 4. NOW refactor the production code
├── 5. Run approval tests again → must STILL PASS
│ ├── ✅ Passing → refactoring preserved behavior
│ └── ❌ Failing → refactoring broke something, revert
└── 6. If the spec says behavior should CHANGE:
├── Update the approval test to reflect NEW expected behavior
├── Run → test FAILS (RED — new behavior not implemented yet)
└── Implement new behavior → GREEN
```
## Return Summary Extension
When Strict TDD Mode is active, your return summary MUST include this section:
```markdown
### TDD Cycle Evidence
| Task | Test File | Layer | Safety Net | RED | GREEN | TRIANGULATE | REFACTOR |
|------|-----------|-------|------------|-----|-------|-------------|----------|
| 1.1 | `path/test.ext` | Unit | ✅ 5/5 | ✅ Written | ✅ Passed | ✅ 3 cases | ✅ Clean |
| 1.2 | `path/test.ext` | Integration | N/A (new) | ✅ Written | ✅ Passed | Single | ✅ Clean |
| 1.3 | `path/test.ext` | Unit | ✅ 2/2 | ✅ Written | ✅ Passed | ✅ 2 cases | None needed |
### Test Summary
- **Total tests written**: {N}
- **Total tests passing**: {N}
- **Layers used**: Unit ({N}), Integration ({N}), E2E ({N})
- **Approval tests** (refactoring): {N} or "None — no refactoring tasks"
- **Pure functions created**: {N}
```
**Column definitions**:
- **Safety Net**: Pre-existing tests run before modifying files. "N/A (new)" for new files.
- **RED**: Test written first, referencing code that doesn't exist yet. Always "✅ Written".
- **GREEN**: Tests executed and passing after minimal implementation. Must show execution result.
- **TRIANGULATE**: Additional test cases added to force real logic. " Single" if spec has only one scenario.
- **REFACTOR**: Code improved with tests still passing. " None needed" if code was already clean.
## Assertion Quality Rules (MANDATORY)
**Every assertion must verify REAL behavior.** A test that passes without exercising production logic is worse than no test — it gives false confidence.
### Banned Assertion Patterns (NEVER write these)
```
# TRIVIAL ASSERTIONS — test proves nothing
expect(true).toBe(true) # ❌ Tautology
expect(false).toBe(false) # ❌ Tautology
expect(1).toBe(1) # ❌ Tautology — no production code involved
assert True # ❌ Always passes
assert 1 == 1 # ❌ Always passes
# EMPTY COLLECTION ASSERTIONS without setup context
expect(result).toEqual([]) # ❌ ONLY valid if you set up conditions for empty
expect(result).toHaveLength(0) # ❌ Same — why is it empty? Did production code run?
assert len(result) == 0 # ❌ Same — prove the emptiness comes from real logic
assert result == [] # ❌ Same
# TYPE-ONLY ASSERTIONS — proves existence, not behavior
expect(result).toBeDefined() # ❌ Alone is useless — WHAT is the value?
expect(result).not.toBeNull() # ❌ Alone is useless — assert the actual value
expect(typeof result).toBe('object') # ❌ Alone is useless — what does the object contain?
assert result is not None # ❌ Alone — assert what result actually IS
# GHOST LOOP — assertion inside a loop that iterates 0 times
const items = screen.queryAllByTestId("item"); // returns []
for (const item of items) {
expect(item).toHaveTextContent("value"); # ❌ NEVER EXECUTES — loop body is dead code
}
# FIX: assert the collection is non-empty FIRST, or set up data so it IS non-empty:
expect(items).toHaveLength(3); # ✅ Proves items exist
for (const item of items) { ... } # ✅ Now the loop actually runs
# INCOMPLETE TDD CYCLE — GREEN without TRIANGULATE
# If your GREEN test passes because the setup doesn't exercise the code path,
# you are NOT done. You MUST triangulate with a setup that DOES exercise it.
# Example: testing "search doesn't update until Enter" but the component
# that receives the search is never rendered → the test proves nothing.
# FIX: add a test where the component IS rendered and verify the behavior.
```
### What Makes a REAL Assertion
Every test assertion must satisfy ALL of these:
1. **Calls production code** — the test invokes a function, method, or component from the implementation
2. **Asserts a specific output** — compares against a concrete expected value derived from the spec
3. **Would FAIL if the production code were wrong** — if you change the implementation logic, THIS test breaks
```
# ✅ REAL assertions — production code determines the result
expect(calculateDiscount(100, 10)).toBe(10) # Real input → real output
expect(screen.getByText('Welcome, John')).toBeInTheDocument() # Rendered from data
assert result[0].status == "FAIL" # Specific finding from check execution
assert response.status_code == 403 # Real HTTP response from the endpoint
expect(result).toHaveLength(3) # AND you set up exactly 3 items
```
### Empty Collection Rule
`expect(result).toEqual([])` or `assert len(result) == 0` is ONLY valid when:
1. You set up a specific precondition that SHOULD produce an empty result (e.g., no matching records)
2. The production code actually ran and filtered/processed data to arrive at empty
3. A companion test with different setup produces a NON-EMPTY result (triangulation)
If you cannot explain WHY the result is empty based on setup → the assertion is trivial.
### Smoke Test Rule
A test that only renders a component without asserting any output is NOT a valid test:
```
# ❌ SMOKE TEST ONLY — proves nothing about behavior
render(<MyComponent data={mockData} />);
expect(screen.getByTestId("wrapper")).toBeInTheDocument(); # Just proves it rendered
# ✅ BEHAVIORAL TEST — proves what the component DOES with the data
render(<MyComponent data={mockData} />);
expect(screen.getByText("Expected Title")).toBeInTheDocument(); # Verifies output from data
expect(screen.getByRole("button")).toHaveTextContent("Submit"); # Verifies real content
```
"Renders without crash" is a smoke test. It is NOT a unit test, NOT an integration test, and it does NOT count toward TDD coverage. If you need a smoke test, it must be accompanied by real behavioral assertions.
### Mock Hygiene Rules
**If you need more mocks than assertions, you are testing at the WRONG level.**
```
Mock/assertion ratio guide:
├── ≤ 3 mocks for a test file → ✅ Healthy — focused test
├── 46 mocks → ⚠️ Consider extracting logic to a pure function
├── 7+ mocks → ❌ STOP — you are testing at the wrong layer
│ ├── Extract the logic under test to a PURE FUNCTION and test it without mocks
│ ├── OR move the test to integration/E2E layer where real dependencies exist
│ └── NEVER write 10+ mocks to verify a one-line transformation
```
**Extract-Before-Mock Rule**: If the behavior you want to test is a data transformation, mapping, filtering, or conditional logic (e.g., `MUTED → FAIL` status conversion), EXTRACT it to a pure function FIRST, then test the pure function directly. No mocks needed.
```
# ❌ BAD: 15 mocks to test a one-line status conversion
vi.mock("next/navigation", ...);
vi.mock("next/link", ...);
vi.mock("@/components/shadcn", ...);
// ... 12 more mocks ...
render(<StatusCell row={mutedRow} />);
expect(screen.getByText("FAIL")).toBeInTheDocument();
# ✅ GOOD: extract and test the logic directly
// In production code:
export function resolveDisplayStatus(status: string, isMuted: boolean): string {
return status === "MUTED" ? "FAIL" : status;
}
// In test — ZERO mocks needed:
expect(resolveDisplayStatus("MUTED", true)).toBe("FAIL");
expect(resolveDisplayStatus("PASS", false)).toBe("PASS");
```
### Implementation Detail Coupling Rule
Tests must assert **behavior visible to the user**, not internal implementation details:
```
# ❌ COUPLED TO IMPLEMENTATION — breaks on any style refactor
expect(element.className).toContain("text-xs");
expect(element.className).toContain("-mt-2.5");
expect(element.className).toContain("border-border-error-primary");
expect(element.style.color).toBe("red");
# ❌ COUPLED TO INTERNALS — breaks when implementation changes
expect(mockService.mock.calls.length).toBe(3); # Why 3? Brittle.
expect(component.state.isLoading).toBe(true); # Internal state, not behavior.
# ✅ BEHAVIORAL — survives refactors, tests what users see
expect(screen.getByText("Error: Payment failed")).toBeInTheDocument();
expect(screen.getByRole("alert")).toHaveTextContent("Risk:");
expect(screen.getByRole("button")).toBeDisabled();
```
**CSS class assertions are NEVER valid test assertions.** If you need to verify visual styling:
1. Test the **semantic outcome** (e.g., element has `role="alert"`, text is visible, button is disabled)
2. OR use a visual regression tool / E2E screenshot comparison
3. NEVER assert specific Tailwind/CSS class names — they are implementation details
## Rules (Strict TDD specific)
- NEVER write production code before writing its test — this is the ONE rule that cannot be broken
- NEVER skip the GREEN execution gate — you MUST run tests and confirm they pass
- NEVER skip triangulation when the spec defines multiple scenarios — hardcoded Fake It must be forced out
- NEVER write trivial assertions (see Banned Assertion Patterns above) — they are WORSE than no test
- ALWAYS verify that every assertion CALLS production code and asserts a SPECIFIC expected value
- ALWAYS run the Safety Net before modifying existing files — protect what already works
- ALWAYS report the TDD Cycle Evidence table — the verify phase will check it
- If a test runner execution fails for infrastructure reasons (not test failures), report as "Blocked" and continue to next task
- Prefer pure functions — but don't force it where it doesn't fit (e.g., React components with state)
- For refactoring tasks, ALWAYS write approval tests before touching code
- Run ONLY the relevant test file during the cycle, not the full suite