Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 79 additions & 5 deletions src/validation/prompts/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ export const RULES = `## TDD Fundamentals
The foundation of TDD is the Red-Green-Refactor cycle:

1. **Red Phase**: Write ONE failing test that describes desired behavior
- The test must fail for the RIGHT reason (not syntax/import errors)
- The test must fail for the RIGHT reason (not syntax/import errors, BUT compilation errors ARE valid test failures in compiled languages)
- **Compilation errors ARE valid test failures** in compiled languages (Go, Java, Swift, Kotlin, Rust, C++, etc.)
- Examples of valid failures: undefined functions/types, missing methods, type mismatches
- Only one test at a time - this is critical for TDD discipline
- **Adding a single test to a test file is ALWAYS allowed** - no prior test output needed
- Starting TDD for a new feature is always valid, even if test output shows unrelated work
Expand Down Expand Up @@ -37,14 +39,86 @@ The foundation of TDD is the Red-Green-Refactor cycle:
- Refactoring when tests haven't been run or are failing

### Critical Principle: Incremental Development
Each step in TDD should address ONE specific issue:

Each step in TDD should address ONE specific issue. The definition of "minimal" differs between interpreted and compiled languages:

**Interpreted languages (JavaScript, Python, Ruby, PHP):**
- Test fails "not defined" β†’ Create empty stub/class only
- Test fails "not a function" β†’ Add method stub only
- Test fails "not a function" β†’ Add method stub only
- Test fails with assertion β†’ Implement minimal logic only

**Compiled languages (Go, Java, Swift, Kotlin, Rust, C++):**
Each compilation error is a separate TDD step:
1. "undefined: TypeName" β†’ Create empty struct/class (type TodoItem struct {})
2. "undefined: function" β†’ Add function signature with zero-value return
3. "missing return statement" β†’ Return zero value/default
4. "type mismatch" β†’ Adjust type, keep zero-value return
5. "field/property undefined" β†’ Add field/property to type
6. Assertion failure β†’ NOW implement actual logic

**Zero values ARE minimal implementation for compiled languages:**

When a test causes a compilation error, creating the minimal structure to satisfy the compiler is the correct first step. This includes:
- Creating empty types/structs/classes
- Adding method signatures that return zero/default values
- Using language-specific unimplemented markers when needed

Progressive compilation errors guide development - this IS the Red phase working correctly.

### Compiled Language Specifics

**Compiler satisfaction is required, not over-implementation:**

When a compilation error occurs, the minimum code to satisfy the compiler IS the correct minimal implementation. This includes:

- **Required imports**: Add import statements needed for types used in test
- **Type definitions**: Create structs/classes/interfaces referenced by test
- **Method signatures**: Add methods with correct signature but zero-value returns
- **Return statements**: Use language defaults (zero values, null, Default trait)

**Unimplemented markers for untested code:**

If implementing an interface/protocol/trait forces methods not yet tested, use explicit unimplemented markers:
- Go: panic("not implemented") or return ZeroValue
- Rust: todo!() or unimplemented!()
- Kotlin: TODO("not implemented")
- Swift: fatalError("not implemented")
- Java: throw new UnsupportedOperationException("not implemented")

These markers satisfy the compiler while making it clear the code is intentionally incomplete. They will fail loudly if accidentally called before being implemented.

**What constitutes over-implementation in compiled languages:**

- ❌ Adding business logic when test only shows compilation error
- ❌ Implementing multiple methods when test only requires one
- ❌ Adding validation, error handling, or complex logic before assertions fail
- ❌ Setting non-zero values when test hasn't shown what values are needed
- βœ… Creating empty types when compilation error says type is undefined
- βœ… Adding fields/properties when compilation error says they're missing
- βœ… Returning zero/default values to satisfy "missing return" errors
- βœ… Adding required imports and type signatures

**Example progression:**

Step 1 - Test: NewTodoItem("task")
Error: undefined: NewTodoItem
β†’ Add: func NewTodoItem(title string) TodoItem { return TodoItem{} }

Step 2 - Test runs: type TodoItem has no field Title
β†’ Add: type TodoItem struct { Title string }

Step 3 - Test runs: Expected "task", got ""
β†’ Modify: return TodoItem{Title: title}

Step 4 - Test passes βœ“

Each step addresses exactly one failure - this is minimal implementation for compiled languages.

### General Information
- Sometimes the test output shows as no tests have been run when a new test is failing due to a missing import or constructor. In such cases, allow the agent to create simple stubs. Ask them if they forgot to create a stub if they are stuck.
- It is never allowed to introduce new logic without evidence of relevant failing tests. However, stubs and simple implementation to make imports and test infrastructure work is fine.
- Sometimes the test output shows no tests have been run when a test is failing due to a missing import or constructor. In such cases, allow the agent to create simple stubs. Ask them if they forgot to create a stub if they are stuck.
- **For all languages**: It is never allowed to introduce business logic without evidence of relevant failing tests.
- **For interpreted languages** (JavaScript, Python, Ruby, PHP): Only implement what the test explicitly requires. No anticipatory code or infrastructure setup.
- **For compiled languages** (Go, Java, Swift, Kotlin, Rust, C++): Compiler satisfaction is required - stubs, imports, type definitions, and zero-value returns are necessary to reach test assertions (see Compiled Language Specifics for details).
- In the refactor phase, it is perfectly fine to refactor both teest and implementation code. That said, completely new functionality is not allowed. Types, clean up, abstractions, and helpers are allowed as long as they do not introduce new behavior.
- Adding types, interfaces, or a constant in order to replace magic values is perfectly fine during refactoring.
- Provide the agent with helpful directions so that they do not get stuck when blocking them.
Expand Down