git:20260828.d21a313 to git:20260829.7f8ff6e
25 added, 700 removed. Audit A to A.
- # TDD Strategy Pattern - Archetype Documentation
-
- **Status**: Reference Architecture
- **Pattern**: Strategy Pattern (GoF) with Protocol interface
- **Purpose**: Language-specific TDD enforcement with zero coupling to the handler
-
- This module is the **canonical archetype** for all language-aware strategy implementations in the hooks daemon. Any future handler that needs language-specific behaviour MUST follow this pattern exactly.
-
- ---
-
- ## Architecture Overview
-
- ```
- TddEnforcementHandler (Orchestrator)
- │
- │ Zero language logic. Pure workflow.
- │
- ├── TddStrategyRegistry (Lookup)
- │ │
- │ │ Maps file extensions → strategy instances
- │ │
- │ └── TddStrategy (Protocol Interface)
- │ │
- │ │ Contract: 7 members (2 properties + 5 methods),
- │ │ structural typing
- │ │
- │ ├── PythonTddStrategy
- │ ├── GoTddStrategy
- │ ├── JavaScriptTddStrategy (covers JS + TS)
- │ ├── PhpTddStrategy
- │ ├── RustTddStrategy
- │ ├── JavaTddStrategy
- │ ├── CSharpTddStrategy
- │ ├── KotlinTddStrategy
- │ ├── RubyTddStrategy
- │ ├── SwiftTddStrategy
- │ └── DartTddStrategy
- │
- └── common.py (DRY Utilities)
- │
- ├── COMMON_TEST_DIRECTORIES (shared constant)
- ├── is_in_common_test_directory()
- └── matches_directory()
- ```
-
- ### Separation of Concerns (SOLID)
-
- | Component | Responsibility | Knows About Languages? |
- | ------------------ | ---------------------------------------------------- | ---------------------- |
- | **Handler** | Orchestrates workflow (matches → handle → test path) | NO |
- | **Protocol** | Defines the contract all strategies must satisfy | NO |
- | **Registry** | Maps extensions to strategy instances | NO (data-driven) |
- | **Strategy** | ALL language-specific logic for one language | YES (its own only) |
- | **Common** | Shared utilities used by multiple strategies | NO |
- | **LanguageConfig** | Pure config data (names, extensions) | N/A (data only) |
-
- ---
-
- ## The Protocol (Interface Contract)
-
- ```python
- @runtime_checkable
- class TddStrategy(Protocol):
- @property
- def language_name(self) -> str: ...
-
- @property
- def extensions(self) -> tuple[str, ...]: ...
-
- def is_test_file(self, file_path: str) -> bool: ...
-
- def is_production_source(self, file_path: str) -> bool: ...
-
- def should_skip(self, file_path: str, content: str = "") -> bool: ...
-
- def compute_test_filename(self, source_filename: str) -> str: ...
-
- def get_acceptance_tests(self) -> list[Any]: ...
- ```
-
- ### Why Protocol, Not ABC?
-
- | Feature | Protocol (chosen) | ABC |
- | ------------- | ------------------------------------- | --------------------- |
- | Typing | Structural (duck typing) | Nominal (inheritance) |
- | Coupling | Zero - no base class import needed | Tight - must inherit |
- | Testing | Any object satisfying shape works | Must subclass ABC |
- | Runtime check | `@runtime_checkable` + `isinstance()` | `isinstance()` |
- | Python idiom | Pythonic, modern (PEP 544) | Traditional OOP |
-
- **Decision**: Protocol enables true Open/Closed Principle. New strategies need zero imports from the framework - they just need to match the shape.
-
- ### Method Contract Details
-
- #### `language_name: str` (property)
-
- - Human-readable name for error messages (e.g., `"Python"`, `"JavaScript/TypeScript"`)
- - Used in TDD denial messages shown to the developer
- - Must be stable - changing it changes user-facing output
-
- #### `extensions: tuple[str, ...]` (property)
-
- - File extensions this strategy handles (e.g., `(".py",)`, `(".js", ".jsx", ".ts", ".tsx")`)
- - Must include the dot prefix
- - Must be lowercase
- - Used by Registry for extension-to-strategy mapping
- - One strategy can handle multiple extensions (JS/TS pattern)
-
- #### `is_test_file(file_path: str) -> bool`
-
- - Returns `True` if the file IS a test file (should be allowed through)
- - MUST check common test directories via `is_in_common_test_directory()`
- - MUST check language-specific naming patterns (e.g., `test_*.py`, `*_test.go`)
- - Called by handler's `matches()` to skip test files
-
- #### `is_production_source(file_path: str) -> bool`
-
- - Returns `True` if the file is in a production source directory
- - Checks language-specific source directory conventions (e.g., `/src/`, `/lib/`, `/app/`)
- - Should exclude language-specific init/config files (e.g., Python's `__init__.py`)
- - Called by handler's `matches()` to identify files that need TDD enforcement
-
- #### `should_skip(file_path: str, content: str = "") -> bool`
-
- - Returns `True` if the file should be skipped entirely (no TDD enforcement)
- - Checks for vendor dirs, build output, virtual environments, etc.
- - The optional `content` argument enables content-based skipping where the path
- alone is not decisive (e.g. PHP interface declarations). Strategies that do
- not need it still MUST accept it — the parameter is part of the contract, so
- the handler can call every strategy identically
- - Called by handler's `matches()` before any other checks
-
- #### `compute_test_filename(source_filename: str) -> str`
-
- - Takes a source filename (not full path), returns expected test filename
- - Language-specific transformation:
- - Python: `module.py` -> `test_module.py`
- - Go: `server.go` -> `server_test.go`
- - JS/TS: `helpers.ts` -> `helpers.test.ts`
- - PHP: `UserService.php` -> `UserServiceTest.php`
- - Java: `UserService.java` -> `UserServiceTest.java`
- - Rust: `parser.rs` -> `parser_test.rs`
- - C#: `UserService.cs` -> `UserServiceTests.cs`
- - Kotlin: `UserService.kt` -> `UserServiceTest.kt`
- - Ruby: `user_service.rb` -> `user_service_spec.rb`
- - Swift: `Parser.swift` -> `ParserTests.swift`
- - Dart: `parser.dart` -> `parser_test.dart`
- - Used by handler to construct the expected test file path
-
- #### `get_acceptance_tests(self) -> list[Any]` (MANDATORY)
-
- - Returns a list of `AcceptanceTest` instances for this language
- - MUST return at least one test (enforced by QA checker)
- - Tests should be BLOCKING type (expected_decision=Decision.DENY)
- - Tests use `/tmp/acceptance-test-tdd-{language}/` paths for safety
- - Must include `setup_commands` (create temp dirs) and `cleanup_commands` (remove them)
- - `expected_message_patterns` MUST include the exact `language_name` string
- - Handler aggregates tests from all strategies via `get_acceptance_tests()`
- - See existing strategies for examples
-
- ---
-
- ## Acceptance Test Provision (MANDATORY)
-
- Every strategy MUST provide acceptance tests. This is enforced by:
-
- 1. **Protocol contract**: `get_acceptance_tests()` is part of the TddStrategy Protocol
- 2. **Unit tests**: `test_acceptance_tests.py` validates all 11 strategies provide valid tests
- 3. **QA checker**: `strategy_pattern_checker.py` AST-checks for missing `get_acceptance_tests()`
-
- ### How It Works
-
- ```
- Strategy (provides tests) Handler (aggregates) Playbook (executes)
- ┌─────────────────────┐ ┌──────────────────┐ ┌─────────────────┐
- │ PythonTddStrategy │─────▶│ │ │ │
- │ GoTddStrategy │─────▶│ TddEnforcement │─────▶│ Acceptance Test │
- │ JavaScriptStrategy │─────▶│ Handler │ │ Runner │
- │ ... (11 total) │─────▶│ .get_acceptance │ │ (Haiku agents) │
- │ │ │ _tests() │ │ │
- └─────────────────────┘ └──────────────────┘ └─────────────────┘
- Each returns 1+ test Collects & dedupes Runs in parallel
- ```
-
- ### AcceptanceTest Structure
-
- ```python
- from claude_code_hooks_daemon.core import AcceptanceTest, Decision, TestType
-
- AcceptanceTest(
- title="TDD Enforcement - {Language} source without test",
- command='Write file_path="/tmp/acceptance-test-tdd-{lang}/src/pkg/example.{ext}" content="..."',
- description="Should block creating {Language} source file without test",
- expected_decision=Decision.DENY,
- expected_message_patterns=["TDD REQUIRED", "{Language}", "test file"],
- test_type=TestType.BLOCKING,
- safety_notes="Uses /tmp path, cleaned up after test",
- setup_commands=["mkdir -p /tmp/acceptance-test-tdd-{lang}/src/pkg"],
- cleanup_commands=["rm -rf /tmp/acceptance-test-tdd-{lang}"],
- )
- ```
-
- ### Rules
-
- 1. **Every strategy provides its own tests** - strategies own their test definitions
- 2. **Handler is a thin aggregator** - collects from all strategies, adds nothing of its own
- 3. **No duplicate languages** - handler deduplicates by `language_name`
- 4. **Tests use safe paths** - `/tmp/` only, with cleanup commands
- 5. **Patterns include language name** - exact match for deduplication verification
-
- ---
-
- ## Strategy Implementation Pattern
-
- Every strategy follows this exact structure. No exceptions.
-
- ### Template
-
- ```python
- """[Language] TDD strategy implementation."""
-
- from pathlib import Path # Only if needed for stem/name operations
-
- from claude_code_hooks_daemon.strategies.tdd.common import (
- is_in_common_test_directory,
- matches_directory,
- )
-
- # ── Language-specific constants (NO MAGIC STRINGS) ──────────────
- _LANGUAGE_NAME = "[Language]"
- _EXTENSIONS: tuple[str, ...] = (".[ext]",)
- _SOURCE_DIRECTORIES: tuple[str, ...] = ("/src/",)
- _SKIP_DIRECTORIES: tuple[str, ...] = ("vendor/",)
-
- # Any additional constants specific to this language
- # e.g., _INIT_FILENAME, _TEST_SUFFIX, _TEST_PREFIX
-
-
- class [Language]TddStrategy:
- """TDD enforcement strategy for [Language] projects.
-
- Test convention: source.[ext] -> [test pattern]
- Source directories: [list]
- """
-
- @property
- def language_name(self) -> str:
- return _LANGUAGE_NAME
-
- @property
- def extensions(self) -> tuple[str, ...]:
- return _EXTENSIONS
-
- def is_test_file(self, file_path: str) -> bool:
- # ALWAYS check common directories first (DRY)
- if is_in_common_test_directory(file_path):
- return True
-
- # Then check language-specific naming pattern
- ...
-
- def is_production_source(self, file_path: str) -> bool:
- # Exclude init/config files if applicable
- ...
-
- # Check source directories
- return matches_directory(file_path, _SOURCE_DIRECTORIES)
-
- def should_skip(self, file_path: str, content: str = "") -> bool:
- # Accept `content` even when unused - it is part of the Protocol contract
- # so the handler can call every strategy with the same arguments.
- return matches_directory(file_path, _SKIP_DIRECTORIES)
-
- def compute_test_filename(self, source_filename: str) -> str:
- # Language-specific transformation
- ...
-
- def get_acceptance_tests(self) -> list[Any]:
- from claude_code_hooks_daemon.core import AcceptanceTest, Decision, TestType
- return [
- AcceptanceTest(
- title="TDD Enforcement - [Language] source without test",
- command='Write file_path="/tmp/acceptance-test-tdd-[lang]/src/pkg/example.[ext]" content="..."',
- description="Should block creating [Language] source without test",
- expected_decision=Decision.DENY,
- expected_message_patterns=["TDD REQUIRED", _LANGUAGE_NAME, "test file"],
- test_type=TestType.BLOCKING,
- safety_notes="Uses /tmp path, cleaned up after test",
- setup_commands=["mkdir -p /tmp/acceptance-test-tdd-[lang]/src/pkg"],
- cleanup_commands=["rm -rf /tmp/acceptance-test-tdd-[lang]"],
- )
- ]
- ```
-
- ### Rules
-
- 1. **ALL strings are named constants** - Module-level, prefixed with `_`
- 2. **Always use `common.is_in_common_test_directory()`** in `is_test_file()` - DRY
- 3. **Always use `common.matches_directory()`** for directory matching - DRY
- 4. **No imports from other strategies** - Each strategy is fully independent
- 5. **No imports from the handler** - Strategies know nothing about handlers
- 6. **No imports from LanguageConfig** - Config is config, strategy is behaviour (SRP)
- 7. **Class does NOT inherit from anything** - Protocol is structural, not nominal
- 8. **One file per language** - Named `{language}_strategy.py`
-
- ---
-
- ## Registry
-
- The `TddStrategyRegistry` is a simple extension-to-strategy map:
-
- ```python
- registry = TddStrategyRegistry()
- registry.register(PythonTddStrategy()) # Registers ".py"
- registry.register(GoTddStrategy()) # Registers ".go"
-
- strategy = registry.get_strategy("/workspace/src/app/server.go")
- # Returns GoTddStrategy instance (matched ".go")
-
- strategy = registry.get_strategy("/workspace/README.md")
- # Returns None (no strategy for ".md")
- ```
-
- ### `create_default()` Factory
-
- The `create_default()` class method creates a registry with ALL built-in strategies. This is the standard entry point used by the handler.
-
- **Adding a new strategy**: Add one line to `create_default()`:
-
- ```python
- registry.register(NewLanguageTddStrategy())
- ```
-
- ---
-
- ## Shared Utilities (DRY)
-
- `common.py` contains logic used by multiple strategies:
-
- ### `COMMON_TEST_DIRECTORIES`
-
- ```python
- COMMON_TEST_DIRECTORIES: tuple[str, ...] = (
- "/tests/",
- "/test/",
- "/__tests__/",
- "/spec/",
- )
- ```
-
- Cross-language test directory names. Every strategy's `is_test_file()` checks these first.
-
- ### `is_in_common_test_directory(file_path: str) -> bool`
-
- Quick check if a file is in any common test directory. Called by ALL strategies in `is_test_file()`.
-
- ### `matches_directory(file_path: str, directories: tuple[str, ...]) -> bool`
-
- Generic directory pattern matcher. Handles normalization (leading `/`, trailing `/`). Used by strategies for both `is_production_source()` and `should_skip()`.
-
- ### When to Add to Common vs Strategy
-
- | Logic | Where | Why |
- | ------------------------------------------ | ----------- | ----------------- |
- | Test directory names used by 3+ languages | `common.py` | DRY |
- | Directory matching utility | `common.py` | DRY |
- | Test filename pattern (e.g., `test_*.py`) | Strategy | Language-specific |
- | Source directory list (e.g., `/src/main/`) | Strategy | Language-specific |
- | Skip directory list (e.g., `vendor/`) | Strategy | Language-specific |
- | Init file exclusion (e.g., `__init__.py`) | Strategy | Language-specific |
-
- **Rule**: If 3+ strategies share the same logic, extract to `common.py`. Otherwise keep it in the strategy.
-
- ---
-
- ## How to Add a New Language
-
- ### Step-by-Step (TDD)
-
- #### 1. Create the test file FIRST
-
- ```bash
- # File: tests/unit/strategies/tdd/test_{language}_strategy.py
- ```
-
- Write comprehensive tests covering:
-
- ```python
- """Tests for {Language} TDD strategy."""
-
- import pytest
-
- from claude_code_hooks_daemon.strategies.tdd.{language}_strategy import {Language}TddStrategy
-
-
- @pytest.fixture
- def strategy() -> {Language}TddStrategy:
- return {Language}TddStrategy()
-
-
- class TestProperties:
- def test_language_name(self, strategy: {Language}TddStrategy) -> None:
- assert strategy.language_name == "{Language}"
-
- def test_extensions(self, strategy: {Language}TddStrategy) -> None:
- assert strategy.extensions == (".[ext]",)
-
-
- class TestIsTestFile:
- def test_common_test_directory(self, strategy: {Language}TddStrategy) -> None:
- assert strategy.is_test_file("/workspace/tests/foo.[ext]") is True
-
- def test_language_specific_pattern(self, strategy: {Language}TddStrategy) -> None:
- assert strategy.is_test_file("/workspace/src/test_foo.[ext]") is True
-
- def test_production_file(self, strategy: {Language}TddStrategy) -> None:
- assert strategy.is_test_file("/workspace/src/foo.[ext]") is False
-
-
- class TestIsProductionSource:
- def test_in_source_directory(self, strategy: {Language}TddStrategy) -> None:
- assert strategy.is_production_source("/workspace/src/foo.[ext]") is True
-
- def test_outside_source(self, strategy: {Language}TddStrategy) -> None:
- assert strategy.is_production_source("/workspace/foo.[ext]") is False
-
-
- class TestShouldSkip:
- def test_vendor_directory(self, strategy: {Language}TddStrategy) -> None:
- assert strategy.should_skip("/workspace/vendor/foo.[ext]") is True
-
- def test_normal_path(self, strategy: {Language}TddStrategy) -> None:
- assert strategy.should_skip("/workspace/src/foo.[ext]") is False
-
-
- class TestComputeTestFilename:
- def test_standard_file(self, strategy: {Language}TddStrategy) -> None:
- assert strategy.compute_test_filename("foo.[ext]") == "[expected_test_name]"
- ```
-
- #### 2. Run tests - they MUST FAIL
-
- ```bash
- pytest tests/unit/strategies/tdd/test_{language}_strategy.py -v
- # Expected: ImportError (no implementation yet)
- ```
-
- #### 3. Create the strategy implementation
-
- ```bash
- # File: src/claude_code_hooks_daemon/strategies/tdd/{language}_strategy.py
- ```
-
- Follow the template above exactly.
-
- #### 4. Run tests - they MUST PASS
-
- ```bash
- pytest tests/unit/strategies/tdd/test_{language}_strategy.py -v
- # Expected: ALL PASS
- ```
-
- #### 5. Register in the Registry
-
- Edit `registry.py`:
-
- ```python
- # In create_default():
- from claude_code_hooks_daemon.strategies.tdd.{language}_strategy import {Language}TddStrategy
-
- registry.register({Language}TddStrategy())
- ```
-
- #### 6. Update registry tests
-
- Edit `tests/unit/strategies/tdd/test_tdd_strategy_registry.py`:
-
- - Add language to `test_create_default_registers_all_languages` expected list
- - Add extension test case to `test_get_strategy_returns_correct_strategy`
-
- #### 7. Run full QA
-
- ```bash
- ./scripts/qa/llm_qa.py all
- # Expected: ALL CHECKS PASSED
- ```
-
- #### 8. Restart daemon
-
- ```bash
- .claude/hooks-daemon/bin/hooks-daemon restart
- .claude/hooks-daemon/bin/hooks-daemon status
- # Expected: RUNNING
- ```
-
- ---
-
- ## Registered Languages (11 total)
-
- | Language | Strategy Class | Extensions | Test Pattern | Source Dirs |
- | --------------------- | ----------------------- | ---------------------------- | ------------------------ | --------------------------------------- |
- | Python | `PythonTddStrategy` | `.py` | `test_*.py` | `/src/` |
- | Go | `GoTddStrategy` | `.go` | `*_test.go` | `/src/`, `/cmd/`, `/pkg/`, `/internal/` |
- | JavaScript/TypeScript | `JavaScriptTddStrategy` | `.js`, `.jsx`, `.ts`, `.tsx` | `*.test.*`, `*.spec.*` | `/src/`, `/lib/`, `/app/` |
- | PHP | `PhpTddStrategy` | `.php` | `*Test.php` | `/src/`, `/app/` |
- | Rust | `RustTddStrategy` | `.rs` | `*_test.rs` | `/src/` |
- | Java | `JavaTddStrategy` | `.java` | `*Test.java` | `/src/main/` |
- | C# | `CSharpTddStrategy` | `.cs` | `*Tests.cs`, `*Test.cs` | `/src/` |
- | Kotlin | `KotlinTddStrategy` | `.kt` | `*Test.kt` | `/src/main/` |
- | Ruby | `RubyTddStrategy` | `.rb` | `*_spec.rb`, `*_test.rb` | `/lib/`, `/app/` |
- | Swift | `SwiftTddStrategy` | `.swift` | `*Tests.swift` | `/Sources/`, `/src/` |
- | Dart | `DartTddStrategy` | `.dart` | `*_test.dart` | `/lib/` |
-
- ---
-
- ## Design Principles Applied
-
- ### SOLID
-
- - **S** (Single Responsibility): Each strategy handles exactly one language. Handler handles only workflow. Registry handles only lookup. Common handles only shared utilities.
- - **O** (Open/Closed): Adding a language = adding a file + one registry line. Zero changes to handler, protocol, common, or other strategies.
- - **L** (Liskov Substitution): Every strategy is substitutable through the Protocol. Handler uses the same 7 members regardless of language.
- - **I** (Interface Segregation): The Protocol has exactly 7 members - 2 properties (`language_name`, `extensions`) and 5 methods (`is_test_file`, `is_production_source`, `should_skip`, `compute_test_filename`, `get_acceptance_tests`) - no more. Every strategy implements all 7.
- - **D** (Dependency Inversion): Handler depends on Protocol (abstraction), not on any concrete strategy class.
-
- ### DRY
-
- - Common test directories defined once in `common.py`
- - Directory matching logic defined once in `matches_directory()`
- - Handler path mapping logic defined once (not per-language)
-
- ### NO MAGIC
-
- - Every string literal is a named constant (`_LANGUAGE_NAME`, `_EXTENSIONS`, `_SOURCE_DIRECTORIES`, etc.)
- - Module-level constants prefixed with `_` (private to module)
- - Handler path constants also extracted (`_TEST_DIR`, `_TEST_UNIT_DIR`, `_SRC_DIR`)
-
- ### YAGNI
-
- - No abstract base class (Protocol is sufficient)
- - No config-driven strategy loading (create_default() is simple and explicit)
- - No strategy caching (strategies are lightweight, stateless)
- - No lazy loading (all strategies registered eagerly - they're cheap)
-
- ### FAIL FAST
-
- - `get_strategy()` returns `None` for unknown extensions - handler allows through (no silent failure)
- - `@runtime_checkable` Protocol enables isinstance validation if needed
- - Missing strategy methods cause immediate `TypeError` at call site
-
- ---
-
- ## Anti-Patterns to Avoid
-
- ### DO NOT put language logic in the handler
-
- ```python
- # WRONG - handler knows about Python
- if file_path.endswith(".py"):
- test_name = f"test_{filename}"
-
- # RIGHT - handler delegates to strategy
- test_name = strategy.compute_test_filename(filename)
- ```
-
- ### DO NOT use if/elif chains for languages
-
- ```python
- # WRONG - God method with language awareness
- if config.name == "python":
- ...
- elif config.name == "go":
- ...
-
- # RIGHT - Strategy lookup via registry
- strategy = registry.get_strategy(file_path)
- strategy.is_test_file(file_path)
- ```
-
- ### DO NOT import between strategies
-
- ```python
- # WRONG - coupling between strategies
- from claude_code_hooks_daemon.strategies.tdd.python_strategy import _SOURCE_DIRECTORIES
-
- # RIGHT - use common.py for shared logic
- from claude_code_hooks_daemon.strategies.tdd.common import matches_directory
- ```
-
- ### DO NOT use magic strings
-
- ```python
- # WRONG
- if "/src/" in file_path:
-
- # RIGHT
- _SOURCE_DIRECTORIES: tuple[str, ...] = ("/src/",)
- return matches_directory(file_path, _SOURCE_DIRECTORIES)
- ```
-
- ### DO NOT inherit from a base class
-
- ```python
- # WRONG - nominal typing, tight coupling
- class PythonTddStrategy(BaseTddStrategy):
- ...
-
- # RIGHT - structural typing, zero coupling
- class PythonTddStrategy: # Just satisfies Protocol shape
- ...
- ```
-
- ---
-
- ## Applying This Pattern to Other Handlers
-
- This TDD strategy is the archetype. Future language-aware handlers (e.g., linting enforcement, import validation, naming conventions) MUST follow the same structure:
-
- 1. **Define a Protocol** in `strategies/{domain}/protocol.py`
- 2. **Create shared utilities** in `strategies/{domain}/common.py`
- 3. **Implement one strategy per language** in `strategies/{domain}/{language}_strategy.py`
- 4. **Create a Registry** in `strategies/{domain}/registry.py`
- 5. **Keep the handler language-free** - delegate all language decisions to strategies
- 6. **TDD each strategy independently** with its own test file
- 7. **Document the pattern** with a `CLAUDE.md` in the strategy directory
-
- ### Example Future Domains
-
- ```
- strategies/
- ├── tdd/ # THIS MODULE (archetype)
- │ ├── CLAUDE.md
- │ ├── protocol.py
- │ ├── common.py
- │ ├── registry.py
- │ └── {language}_strategy.py (x11)
- │
- ├── lint/ # Future: language-specific lint enforcement
- │ ├── protocol.py (LintStrategy)
- │ ├── common.py
- │ ├── registry.py
- │ └── {language}_strategy.py
- │
- ├── naming/ # Future: naming convention enforcement
- │ ├── protocol.py (NamingStrategy)
- │ ├── common.py
- │ ├── registry.py
- │ └── {language}_strategy.py
- │
- └── imports/ # Future: import order/validation
- ├── protocol.py (ImportStrategy)
- ├── common.py
- ├── registry.py
- └── {language}_strategy.py
- ```
-
- ---
+ # TDD Strategy Module — the Strategy Pattern archetype
- ## File Index
+ This module is the **reference implementation** of the daemon's language-aware
+ Strategy Pattern: a `TddStrategy` Protocol, one `{language}_strategy.py` per
+ language, a registry keyed by file extension, and shared utilities in
+ `common.py` — with zero language logic in the handler.
- ```
- strategies/tdd/
- ├── CLAUDE.md # This document (archetype reference)
- ├── __init__.py # Public API: TddStrategy, TddStrategyRegistry
- ├── protocol.py # Strategy Protocol interface
- ├── common.py # Shared DRY utilities
- ├── registry.py # Extension-to-strategy mapping
- ├── python_strategy.py # Python strategy
- ├── go_strategy.py # Go strategy
- ├── javascript_strategy.py # JavaScript/TypeScript strategy
- ├── php_strategy.py # PHP strategy
- ├── rust_strategy.py # Rust strategy
- ├── java_strategy.py # Java strategy
- ├── csharp_strategy.py # C# strategy
- ├── kotlin_strategy.py # Kotlin strategy
- ├── ruby_strategy.py # Ruby strategy
- ├── swift_strategy.py # Swift strategy
- └── dart_strategy.py # Dart strategy
+ **Canonical documentation**:
+ [CLAUDE/Code/StrategyPattern.md](/CLAUDE/Code/StrategyPattern.md) — the
+ single source of truth for the architecture, Protocol-vs-ABC rationale,
+ method contract semantics, implementation template and rules, the mandatory
+ acceptance-test provision, the add-a-new-language TDD walkthrough, and the
+ anti-patterns. Read it BEFORE adding or changing anything here, and follow
+ it exactly when building a strategy set for any other domain.
- tests/unit/strategies/tdd/
- ├── test_protocol.py # Protocol conformance tests
- ├── test_protocol_isinstance.py # @runtime_checkable isinstance() checks
- ├── test_common.py # Shared utility tests
- ├── test_tdd_strategy_registry.py # Registry tests
- ├── test_acceptance_tests.py # Acceptance test provision tests
- ├── test_python_strategy.py # Python strategy tests
- ├── test_go_strategy.py # Go strategy tests
- ├── test_javascript_strategy.py # JS/TS strategy tests
- ├── test_php_strategy.py # PHP strategy tests
- ├── test_rust_strategy.py # Rust strategy tests
- ├── test_java_strategy.py # Java strategy tests
- ├── test_csharp_strategy.py # C# strategy tests
- ├── test_kotlin_strategy.py # Kotlin strategy tests
- ├── test_ruby_strategy.py # Ruby strategy tests
- ├── test_swift_strategy.py # Swift strategy tests
- └── test_dart_strategy.py # Dart strategy tests
- ```
+ ## Edit guards (this directory)
- ---
+ - `registry.py`'s `create_default()` is the single source of truth for which
+ languages are registered — never maintain a language roster in docs.
+ - Strategies import from `common.py` only — never from each other, the
+ handler, or `LanguageConfig`.
+ - Strategy classes inherit from nothing (structural typing); every string
+ literal is a module-level `_`-prefixed named constant.
+ - Every strategy MUST return at least one acceptance test from
+ `get_acceptance_tests()` — enforced by
+ `tests/unit/strategies/tdd/test_acceptance_tests.py` and the
+ `qa/strategy_pattern_checker.py` AST check.
- **Pattern Source**: GoF Strategy Pattern adapted for Python Protocol typing
- **Languages**: 11 (Python, Go, JS/TS, PHP, Rust, Java, C#, Kotlin, Ruby, Swift, Dart)
- **Tests**: counts are deliberately not recorded here - a number frozen into a
- document is stale the next time a test is added. Ask the test runner instead:
- `pytest tests/unit/strategies/tdd/ tests/unit/handlers/test_tdd_enforcement.py --collect-only -q`
+ Tests live in `tests/unit/strategies/tdd/` (one file per strategy plus
+ protocol, common, registry and acceptance-test suites).