# Technical Documentation: nizos/tdd-guard

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/nizos/tdd-guard)

### Overview (`README.md`)

# TDD Guard

[](https://www.npmjs.com/package/tdd-guard)
[](https://www.npmjs.com/package/tdd-guard)
[](https://github.com/nizos/tdd-guard/actions/workflows/ci.yml)
[](https://github.com/nizos/tdd-guard/actions/workflows/security.yml)
[](LICENSE)

> [!IMPORTANT]
> **TDD Guard grew into [Probity](https://github.com/nizos/probity)**: the same
> TDD enforcement, now for Claude Code, Codex, and GitHub Copilot CLI, with
> more reliable validation and no test reporters to set up. New projects should
> start there. TDD Guard remains maintained for the projects that rely on it.

Automated Test-Driven Development enforcement for Claude Code.

<p align="center">
  <img src="docs/assets/tdd-guard-demo.gif" alt="TDD Guard blocking TDD violations" width="1200">
</p>

TDD Guard ensures Claude Code follows Test-Driven Development principles. When your agent tries to skip tests or over-implement, TDD Guard blocks the action and explains what needs to happen instead.

## Features

- **Test-First Enforcement** - Blocks implementation without failing tests
- **Minimal Implementation** - Prevents code beyond current test requirements
- **Lint Integration** - Enforces refactoring using your linting rules
- **Customizable Rules** - Adjust validation rules to match your TDD style
- **Flexible Validation** - Choose faster or more capable models for your needs
- **Session Control** - Toggle on and off mid-session

## Getting Started

### Requirements

- Node.js 22+
- A supported test framework (Vitest, Jest, Storybook, pytest, PHPUnit, Go, Rust, RSpec, Minitest)

### Installation

Open Claude Code in your project and run:

1. `/plugin marketplace add nizos/tdd-guard`
2. `/plugin install tdd-guard@tdd-guard`
3. `/tdd-guard:setup`

This adds the marketplace, installs the plugin, and configures the test reporter for your project. You may need to restart your terminal session or IDE extension for the setup skill to appear. For manual installation and configuration, see the [installation guide](docs/installation.md).

## Configuration

- [Custom instructions](docs/custom-instructions.md) - Customize TDD validation rules
- [Lint integration](docs/linting.md) - Automated refactoring support
- [Strengthening enforcement](docs/enforcement.md) - Prevent agents from bypassing validation
- [Ignore patterns](docs/ignore-patterns.md) - Control which files are validated
- [Validation Model](docs/validation-model.md) - Choose faster or more capable model
- [All settings](docs/configuration.md) - Complete settings documentation

## Security

TDD Guard hooks run with your user permissions. We maintain automated security scanning, dependency audits, and welcome source code review. See [Claude Code's security considerations](https://docs.anthropic.com/en/docs/claude-code/hooks#security-considerations) for more on hook safety.

## Development

### Contributing

Contributions are welcome! See the [contributing guidelines](CONTRIBUTING.md) to get started.

**Contributors:**

- Python/pytest support: [@Durafen](https://github.com/Durafen)
- PHP/PHPUnit support: [@wazum](https://github.com/wazum)
- Rust/cargo support: [@104hp6u](https://github.com/104hp6u)
- Go support: [@sQVe](https://github.com/sQVe), [@wizzomafizzo](https://github.com/wizzomafizzo)
- Storybook support: [@akornmeier](https://github.com/akornmeier)
- Ruby/RSpec & Minitest support: [@Hiro-Chiba](https://github.com/Hiro-Chiba)

## Support

- [Discussions](https://github.com/nizos/tdd-guard/discussions) - Ask questions and share ideas
- [Issues](https://github.com/nizos/tdd-guard/issues) - Report bugs and request features

## License

[MIT](LICENSE)

---

### Overview (`.devcontainer/README.md`)

# TDD Guard Development Container

A consistent, isolated environment for developing TDD Guard.

## Features

- Network isolation for security
- Automated development environment setup
- Pre-configured development tools
- Persistent command history

## What's Inside

| Component          | Purpose                                         |
| ------------------ | ----------------------------------------------- |
| Node.js 22         | Main CLI & Vitest/Jest/Storybook reporters      |
| Python 3.11 + pipx | Pytest reporter                                 |
| PHP 8.2 + Composer | PHPUnit reporter                                |
| Go 1.24            | Go reporter                                     |
| Rust 1.89          | Rust reporter with cargo-nextest                |
| Ruby 3.x + Bundler | RSpec reporter                                  |
| .NET SDK 10        | Dotnet reporter (Microsoft Testing Platform)    |
| Playwright deps    | Browser automation for Storybook test-runner    |
| Claude Code        | AI assistance                                   |
| Dev tools          | Git, zsh, fzf, Docker, gh, vim, nano, git-delta |

**Key files:**

- `Dockerfile` - Container definition
- `devcontainer.json` - IDE configuration
- `scripts/` - Setup and configuration scripts
  - `init-firewall.sh` - Network security
  - `setup-dev-environment.sh` - Main setup orchestrator

## Prerequisites

Choose one of:

- **[Docker Desktop](https://www.docker.com/products/docker-desktop/)** (macOS/Windows/Linux)
- **[Colima](https://github.com/abiosoft/colima)** (macOS)

## Quick Start

### VS Code

1. Install [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
2. Open project → Click green button (bottom-left) → "Reopen in Container"
3. Wait for build → Start coding

[Learn more about VS Code Dev Containers →](https://code.visualstudio.com/docs/devcontainers/containers)

### IntelliJ IDEA

1. File → Remote Development → Dev Containers → New Dev Container → From Local Project
2. Select Docker/Colima connection
3. Browse to `<project>/.devcontainer/devcontainer.json`
4. Build Container and Continue

**Resources:**

- [Official documentation →](https://www.jetbrains.com/help/idea/start-dev-container-from-welcome-screen.html)
- [Dev Containers tutorial →](https://blog.jetbrains.com/idea/2024/07/using-dev-containers-in-jetbrains-ides-part-1/)

## Tips & Troubleshooting

### Colima Setup

IntelliJ requires more resources than the default:

```bash
# Basic setup
colima start --cpu 2 --memory 4

# With performance optimization
colima start --cpu 4 --memory 8 --vm-type vz --mount-type virtiofs
```

### macOS Performance

For better file performance:

- **Docker Desktop**: Enable VirtioFS in Settings → General → Choose file sharing implementation
- **Colima**: Use ` --vm-type vz --mount-type virtiofs` flag when starting (see above)

### Common Commands

```bash
npm test               # Run all tests
npm run test:unit      # Run unit tests only
npm run test:reporters # Run reporter tests
npm run checks         # Run all quality checks (typecheck, lint, format, test)
```

### Additional Notes

- **Command history** is preserved between container sessions in a persistent volume
- **Network isolation** restricts outbound connections for security (see `scripts/init-firewall.sh` for allowed services)
- **Environment variables** like `NODE_OPTIONS` and `USE_SYSTEM_CLAUDE` are pre-configured
- **Timezone** defaults to Europe/Stockholm but can be changed via `TZ` build arg

---

### Overview (`plugin/README.md`)

# TDD Guard Plugin

Automated Test-Driven Development enforcement for Claude Code.

When Claude Code attempts to edit or write files, TDD Guard validates that changes follow TDD principles — blocking operations that skip tests or over-implement.

## Setup

After installing the plugin, run the setup skill to configure a test reporter for your project:

```
/tdd-guard:setup
```

This detects your test framework and configures the appropriate [reporter](https://github.com/nizos/tdd-guard).

**Note:** You may need to restart your terminal session or IDE extension for the setup skill to appear.

## What's included

- **Hooks** — Automatically registered on install. Validate file operations, handle `tdd-guard on/off` commands, and manage session state.
- **Setup skill** — `/tdd-guard:setup` detects your test framework and configures the reporter.

## Requirements

- Node.js 22+
- A supported test framework

## Learn more

- [Documentation](https://github.com/nizos/tdd-guard)
- [Configuration options](https://github.com/nizos/tdd-guard/blob/main/docs/configuration.md)
- [Custom TDD rules](https://github.com/nizos/tdd-guard/blob/main/docs/custom-instructions.md)

---

### Overview (`reporters/dotnet/README.md`)

# TDD Guard .NET Reporter

Microsoft Testing Platform (MTP) V2 extension that captures test results
for TDD Guard validation. Works with any .NET test framework that supports
MTP V2 — one package covers TUnit, MSTest, xUnit, NUnit, and more.

## How it works

The reporter is an MTP V2 extension that loads in-process alongside your
test framework. It subscribes to `TestNodeUpdateMessage` events — the
standard MTP event type that all frameworks publish when a test completes.

When a test session finishes, the extension writes results to
`.claude/tdd-guard/data/test.json` in the TDD Guard wire format. The
TDD Guard validation hook reads this file to enforce TDD discipline.

The extension is framework-agnostic. It does not know or care which
framework is running. MTP handles the framework integration; we handle
the test result capture.

## Requirements

- .NET 10+
- [TDD Guard](https://github.com/nizos/tdd-guard)

## Installation

Planned distribution: NuGet.org (published by the maintainer).

Add a PackageReference to your test project:

```xml
<ItemGroup>
  <PackageReference Include="TddGuard.Dotnet" Version="*" />
</ItemGroup>
```

For solutions with multiple test projects, add it once via
`Directory.Build.props` in your tests directory:

```xml
<Project>
  <ItemGroup>
    <PackageReference Include="TddGuard.Dotnet" Version="*" />
  </ItemGroup>
</Project>
```

No manual hook code is needed — the package auto-registers via
`buildTransitive/*.props` which injects the MTP builder hook at build time.

## Usage

The extension registers automatically. Running `dotnet test` is enough
once the package is referenced.

## Configuration

### Project Root

Set the `TDD_GUARD_PROJECT_ROOT` environment variable to your project root:

```bash
export TDD_GUARD_PROJECT_ROOT="/absolute/path/to/project/root"
```

`CLAUDE_PROJECT_DIR` is used as a fallback when `TDD_GUARD_PROJECT_ROOT`
is not set. Claude Code sets this variable for hook commands, though it
may not be available in all execution contexts.

### Rules

- Absolute and relative paths are both accepted (per ADR-009)
- Current directory must be within the configured project root
- When neither env var is set, the extension logs a diagnostic to stderr
  and disables itself (per ADR-010) — it does not silently fall back to cwd

## Compatibility

Verified with smoke tests in `TddGuard.Dotnet.Compat.*` projects:

| Framework                                        | MTP V2 support | Status   | Notes                                            |
| ------------------------------------------------ | -------------- | -------- | ------------------------------------------------ |
| [TUnit](https://github.com/thomhurst/TUnit)      | Native         | Verified | File path as module ID                           |
| [MSTest v4](https://github.com/microsoft/testfx) | Native         | Verified | File path as module ID                           |
| [xUnit v3](https://xunit.net/)                   | Native         | Verified | Via `xunit.v3.mtp-v2` package                    |
| [xUnit v2](https://xunit.net/)                   | Via adapter    | Verified | Via `YTest.MTP.XUnit2` (third-party, unofficial) |
| [NUnit 4](https://docs.nunit.org/)               | Via runner     | Verified | UID as module ID (NUnit omits file paths)        |
| [Reqnroll](https://reqnroll.net/)                | Inherited      | Expected | BDD layer over MSTest/NUnit/xUnit/TUnit          |

## Project structure

```
TddGuard.Dotnet.Core/       Domain types and pure functions (no MTP dependency)
TddGuard.Dotnet/             MTP V2 extension (listener, builder, hook)
TddGuard.Dotnet.Tests/       Unit tests, property-based tests, test infrastructure
TddGuard.Dotnet.Compat.*/    Framework compatibility smoke tests
```

`Core` has no dependency on MTP. It defines the domain types
(`TestState`, `CollectedResult`, `TestRunOutput`), the serializer,
the file writer, and the project root resolver. `Dotnet` depends on
Core and MTP, wiring the domain logic into the MTP extension model.

This separation means the domain logic can be tested without MTP,
and the MTP integration can change without affecting the domain.

## Development

All commands run inside the devcontainer:

```bash
docker exec -w /workspace/reporters/dotnet <container> dotnet build
docker exec -w /workspace/reporters/dotnet <container> dotnet test --project TddGuard.Dotnet.Tests
```

Find the container name via `docker ps`.

See `TddGuard.Dotnet.Tests/README.md` for testing conventions,
infrastructure, and the framework smoke test guide.

## License

MIT

---

### Overview (`reporters/go/README.md`)

# TDD Guard Go Reporter

Go test reporter that captures test results for TDD Guard validation.

## Requirements

- Go 1.24+
- [TDD Guard](https://github.com/nizos/tdd-guard) installed globally

## Installation

```bash
go install github.com/nizos/tdd-guard/reporters/go/cmd/tdd-guard-go@latest
```

## Configuration

### Basic Usage

Pipe `go test -json` output to the reporter:

```bash
go test -json ./... 2>&1 | tdd-guard-go
```

### Project Root Configuration

For projects where tests run in subdirectories, specify the project root:

```bash
go test -json ./... 2>&1 | tdd-guard-go -project-root /absolute/path/to/project/root
```

### Configuration Rules

- Path must be absolute when using `-project-root` flag
- Current directory must be within the configured project root
- Falls back to current directory if not specified

### Makefile Integration

Add to your `Makefile`:

```makefile
test:
	go test -json ./... 2>&1 | tdd-guard-go -project-root /absolute/path/to/project/root
```

## How It Works

The reporter acts as a filter that:

1. Reads `go test -json` output from stdin
2. Passes the output through to stdout unchanged
3. Parses test results and transforms them to TDD Guard format
4. Saves results to `.claude/tdd-guard/data/test.json`

This design allows it to be inserted into existing test pipelines without disrupting output.

## More Information

- Test results are saved to `.claude/tdd-guard/data/test.json`
- See [TDD Guard documentation](https://github.com/nizos/tdd-guard) for complete setup

## License

MIT

---

### Overview (`reporters/jest/README.md`)

# TDD Guard Jest Reporter

Jest reporter that captures test results for TDD Guard validation.

## Requirements

- Node.js 18+
- Jest 30.0.5+
- [TDD Guard](https://github.com/nizos/tdd-guard) installed globally

## Installation

```bash
npm install --save-dev tdd-guard-jest
```

## Configuration

### Jest Configuration

Add the reporter to your `jest.config.js`:

```javascript
module.exports = {
  reporters: [
    'default',
    [
      'tdd-guard-jest',
      {
        projectRoot: __dirname,
      },
    ],
  ],
}
```

Or in `jest.config.ts`:

```typescript
import type { Config } from 'jest'
import path from 'path'

const config: Config = {
  reporters: [
    'default',
    [
      'tdd-guard-jest',
      {
        projectRoot: path.resolve(__dirname),
      },
    ],
  ],
}

export default config
```

### Workspace/Monorepo Configuration

For workspaces or monorepos, pass the project root path to the reporter:

```javascript
// jest.config.js in project root
const path = require('path')

module.exports = {
  reporters: [
    'default',
    [
      'tdd-guard-jest',
      {
        projectRoot: path.resolve(__dirname),
      },
    ],
  ],
}
```

If your jest config is in a workspace subdirectory, pass the absolute path to your project root:

```javascript
module.exports = {
  reporters: [
    'default',
    [
      'tdd-guard-jest',
      {
        projectRoot: '/Users/username/projects/my-app',
      },
    ],
  ],
}
```

## More Information

- Test results are saved to `.claude/tdd-guard/data/test.json`
- See [TDD Guard documentation](https://github.com/nizos/tdd-guard) for complete setup

## License

MIT

---

### Overview (`reporters/junit5/README.md`)

# TDD Guard JUnit5 Reporter

JUnit5 TestExecutionListener that captures test results for TDD Guard validation.

## Requirements

- JDK 17+
- JUnit 5 (Jupiter)
- [TDD Guard](https://github.com/nizos/tdd-guard)

## Installation

Planned distribution: Maven Central (published by the maintainer).

Gradle:

```kotlin
testImplementation("io.github.nizos:tdd-guard-junit5:0.1.0")
```

Maven:

```xml
<dependency>
    <groupId>io.github.nizos</groupId>
    <artifactId>tdd-guard-junit5</artifactId>
    <version>0.1.0</version>
    <scope>test</scope>
</dependency>
```

## Usage

The listener registers automatically via the JUnit Platform SPI. Running `./gradlew test` or `mvn test` is enough once the dependency is on the test classpath.

## Configuration

### Project Root Configuration

Set the `TDD_GUARD_PROJECT_ROOT` environment variable to your project root:

```bash
export TDD_GUARD_PROJECT_ROOT="/absolute/path/to/project/root"
```

Or use a relative path, which resolves against the test runner's cwd.

### Configuration Rules

- Absolute and relative paths are both accepted (per ADR-009)
- Current directory must be within the configured project root
- Errors when neither an explicit path nor the env var is configured (per ADR-010)

## Development

```bash
./gradlew test
```

## More Information

- Test results are saved to `.claude/tdd-guard/data/test.json`
- See [TDD Guard documentation](https://github.com/nizos/tdd-guard) for complete setup

## License

MIT

---

### Overview (`reporters/minitest/README.md`)

# TDD Guard Minitest Reporter

Minitest reporter that captures test results for TDD Guard validation.

## Requirements

- Ruby 3.3+
- Minitest 5.0+
- [TDD Guard](https://github.com/nizos/tdd-guard)

## Installation

Install TDD Guard by following the instructions in the [TDD Guard repository](https://github.com/nizos/tdd-guard).

Add the reporter to your Gemfile:

```ruby
gem "tdd-guard-minitest"
```

Or install directly:

```bash
gem install tdd-guard-minitest
```

## Usage

Require the autorun entry point before your test files. This registers the reporter as a Minitest plugin and also installs an `at_exit` hook that captures load errors (for example, a new test file that `require`s an implementation file that doesn't exist yet) as synthetic failed tests, so TDD Guard can see that a test tried to run.

Run a single test file directly:

```bash
ruby -rtdd_guard_minitest/autorun test/my_test.rb
```

For Rails or Rake projects, require it from `test/test_helper.rb`:

```ruby
require "tdd_guard_minitest/autorun"
```

Or pass it to your `Rake::TestTask` so every `rake test` invocation loads it first:

```ruby
Rake::TestTask.new do |t|
  t.ruby_opts << "-rtdd_guard_minitest/autorun"
end
```

## Configuration

### Project Root Configuration

Set the `TDD_GUARD_PROJECT_ROOT` environment variable to your project root:

```bash
export TDD_GUARD_PROJECT_ROOT="/absolute/path/to/project/root"
```

### Configuration Rules

- Set `TDD_GUARD_PROJECT_ROOT` to the project root. Absolute and relative paths are both accepted; relative paths resolve against the working directory at runtime.
- Tests must be run from a directory at or below the project root.
- The reporter raises at startup when the variable is not set. It no longer silently falls back to the current directory.

## Development

```bash
bundle install
bundle exec rspec
```

## More Information

- Test results are saved to `.claude/tdd-guard/data/test.json`
- See [TDD Guard documentation](https://github.com/nizos/tdd-guard) for complete setup

## License

MIT

---

### Overview (`reporters/phpunit/README.md`)

# TDD Guard PHPUnit Reporter

PHPUnit reporter that captures test results for TDD Guard validation.

## Requirements

- PHP 8.1+
- PHPUnit 9.0+ or 10.0+ or 11.0+ or 12.0+
- [TDD Guard](https://github.com/nizos/tdd-guard) installed globally

## Installation

```bash
composer require --dev tdd-guard/phpunit
```

## Configuration

### PHPUnit 10+ Configuration

Add the extension to your `phpunit.xml`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
    
    <extensions>
        <bootstrap class="TddGuard\PHPUnit\TddGuardExtension">
            <parameter name="projectRoot" value="/absolute/path/to/project/root"/>
        </bootstrap>
    </extensions>
</phpunit>
```

### PHPUnit 9.x Configuration

Add the listener to your `phpunit.xml`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
    
    <listeners>
        <listener class="TddGuard\PHPUnit\TddGuardListener">
            <arguments>
                <string>/absolute/path/to/project/root</string>
            </arguments>
        </listener>
    </listeners>
</phpunit>
```

### Project Root Configuration

Set the project root using any ONE of these methods:

**Option 1: PHPUnit Configuration (Recommended)**

Use the `projectRoot` parameter in your `phpunit.xml` (see examples above).

**Option 2: Environment Variable**

```bash
export TDD_GUARD_PROJECT_ROOT=/absolute/path/to/project/root
```

### Configuration Rules

- Project root must be configured via the `projectRoot` parameter or `TDD_GUARD_PROJECT_ROOT` environment variable
- Tests must be run from somewhere within the project root directory
- Relative paths are supported but resolve against the working directory at runtime — if tests may run from different directories, use an absolute path to ensure results are always written to the correct location

## More Information

- Test results are saved to `.claude/tdd-guard/data/test.json`
- See [TDD Guard documentation](https://github.com/nizos/tdd-guard) for complete setup

## License

MIT

---

### Overview (`reporters/phpunit/SYNC_README.md`)

# TDD Guard PHPUnit Reporter

This repository is automatically synchronized from the main [TDD Guard monorepo](https://github.com/nizos/tdd-guard).

## Important Notice

**This is a read-only mirror.** Please do not submit pull requests here.

- **Source code**: https://github.com/nizos/tdd-guard/tree/main/reporters/phpunit
- **Issues**: https://github.com/nizos/tdd-guard/issues
- **Pull requests**: https://github.com/nizos/tdd-guard/pulls

## Installation

```bash
composer require --dev tdd-guard/phpunit
```

## Why a Separate Repository?

Packagist requires composer.json to be at the root of the repository. Since TDD Guard is a monorepo containing multiple packages (npm, Python, PHP), we maintain this synchronized copy specifically for Packagist distribution.

## Synchronization

This repository is automatically updated whenever changes are pushed to the `reporters/phpunit` directory in the main repository.

## License

MIT - See the main repository for details.

---

### Overview (`reporters/pytest/README.md`)

# TDD Guard Pytest Reporter

Pytest plugin that captures test results for TDD Guard validation.

## Requirements

- Python 3.8+
- pytest 6.0+
- [TDD Guard](https://github.com/nizos/tdd-guard) installed globally

## Installation

```bash
pip install tdd-guard-pytest
```

The plugin activates automatically when installed.

## Configuration

### Project Root Configuration

Set `tdd_guard_project_root` to your project root using any ONE of these methods:

**Option 1: pyproject.toml**

```toml
[tool.pytest.ini_options]
tdd_guard_project_root = "/absolute/path/to/project/root"
```

**Option 2: pytest.ini**

```ini
[pytest]
tdd_guard_project_root = /absolute/path/to/project/root
```

**Option 3: setup.cfg**

```ini
[tool:pytest]
tdd_guard_project_root = /absolute/path/to/project/root
```

### Configuration Rules

- Path must be absolute
- Current directory must be within the configured project root
- Falls back to current directory if configuration is invalid

## Development

When developing the pytest reporter, you need to configure the project root to ensure test results are saved to the correct location:

1. Copy the example configuration:

   ```bash
   cp pytest.ini.example pytest.ini
   ```

2. Edit `pytest.ini` and set the absolute path to your TDD Guard project root:
   ```ini
   [pytest]
   tdd_guard_project_root = /absolute/path/to/tdd-guard
   ```

**Note:** `pytest.ini` is gitignored to avoid committing machine-specific paths.

## More Information

- Test results are saved to `.claude/tdd-guard/data/test.json`
- See [TDD Guard documentation](https://github.com/nizos/tdd-guard) for complete setup

## License

MIT

---

### Overview (`reporters/rspec/README.md`)

# TDD Guard RSpec Reporter

RSpec formatter that captures test results for TDD Guard validation.

## Requirements

- Ruby 3.3+
- RSpec 3.0+
- [TDD Guard](https://github.com/nizos/tdd-guard)

## Installation

Install TDD Guard by following the instructions in the [TDD Guard repository](https://github.com/nizos/tdd-guard).

Add the reporter to your Gemfile:

```ruby
gem "tdd-guard-rspec"
```

Or install directly:

```bash
gem install tdd-guard-rspec
```

## Usage

Run RSpec with the TDD Guard formatter:

```bash
rspec --format TddGuardRspec::Formatter
```

Or add it to your `.rspec` file:

```
--format TddGuardRspec::Formatter
```

## Configuration

### Project Root Configuration

Set the `TDD_GUARD_PROJECT_ROOT` environment variable to your project root:

```bash
export TDD_GUARD_PROJECT_ROOT="/absolute/path/to/project/root"
```

### Configuration Rules

- Set `TDD_GUARD_PROJECT_ROOT` to the project root. Absolute and relative paths are both accepted; relative paths resolve against the working directory at runtime.
- Tests must be run from a directory at or below the project root.
- The reporter raises at startup when the variable is not set. It no longer silently falls back to the current directory.

## Development

```bash
bundle install
bundle exec rspec
```

## More Information

- Test results are saved to `.claude/tdd-guard/data/test.json`
- See [TDD Guard documentation](https://github.com/nizos/tdd-guard) for complete setup

## License

MIT

---

### Overview (`reporters/rust/README.md`)

# TDD Guard Rust Reporter

Rust test reporter that captures test results for TDD Guard validation.

**Note:** This reporter is part of the [TDD Guard](https://github.com/nizos/tdd-guard) project, which ensures Claude Code follows Test-Driven Development principles.

## Requirements

- Rust 1.70+
- TDD Guard installed globally
- `cargo-nextest` (recommended) or `cargo test` with JSON output support

## Installation

### Step 1: Install TDD Guard

```bash
npm install tdd-guard
```

### Step 2: Install the Rust reporter

```bash
cargo install tdd-guard-rust
```

## Usage

The reporter works as a filter that processes test output while passing it through unchanged.

### With cargo-nextest (Recommended)

```bash
cargo nextest run 2>&1 | tdd-guard-rust --project-root /absolute/path/to/project
```

### With cargo test

```bash
cargo test -- -Z unstable-options --format json 2>&1 | tdd-guard-rust --project-root /absolute/path/to/project
```

### Direct Execution

The reporter can also execute tests directly:

```bash
# Auto-detect runner (prefers nextest)
tdd-guard-rust --project-root /absolute/path/to/project

# Force specific runner
tdd-guard-rust --project-root /absolute/path/to/project --runner nextest
tdd-guard-rust --project-root /absolute/path/to/project --runner cargo
```

## Makefile Integration

Add to your `Makefile`:

```makefile
.PHONY: test
test:
	cargo nextest run 2>&1 | tdd-guard-rust --project-root $(PWD) --passthrough

.PHONY: test-tdd
test-tdd:
	tdd-guard on && $(MAKE) test
```

## Configuration

### Project Root

The `--project-root` flag must be an absolute path to your project directory. This is where the `.claude/tdd-guard/data/test.json` file will be written.

### Flags

- `--passthrough`: Force passthrough mode even if stdin is a terminal
- `--runner [auto|nextest|cargo]`: Choose test runner for direct execution (default: auto)
- `--project-root`: Absolute path to project directory (required)

## How It Works

The reporter captures JSON-formatted test output, passes it through unchanged to stdout, and saves TDD Guard-formatted results to `.claude/tdd-guard/data/test.json`.

## License

MIT - See LICENSE file in the repository root.

---

### Overview (`reporters/storybook/README.md`)

# TDD Guard Storybook Reporter

Storybook test-runner reporter that captures test results for TDD Guard validation.

## Requirements

- Node.js 18+
- Storybook 7.x - 9.x
- @storybook/test-runner 0.19.0+
- [TDD Guard](https://github.com/nizos/tdd-guard) installed globally

> [!IMPORTANT]
> For Storybook v10.x, use the `tdd-guard-vitest` reporter w/ Vitest

## Installation

```bash
npm install --save-dev tdd-guard-storybook
```

## Configuration

### Basic Configuration

Add the reporter to your `.storybook/test-runner.ts`:

```typescript
import { StorybookReporter } from 'tdd-guard-storybook'

const reporter = new StorybookReporter()

module.exports = {
  async postVisit(page, context) {
    await reporter.onStoryResult(context)
  },
}

process.on('exit', () => {
  reporter.onComplete()
})
```

### Workspace/Monorepo Configuration

For workspaces or monorepos, pass the project root path in the reporter options:

```typescript
import { StorybookReporter } from 'tdd-guard-storybook'
import path from 'path'

const reporter = new StorybookReporter({
  projectRoot: path.resolve(__dirname, '../..'),
})

module.exports = {
  async postVisit(page, context) {
    await reporter.onStoryResult(context)
  },
}

process.on('exit', () => {
  reporter.onComplete()
})
```

If your test-runner config is in a subdirectory, specify the absolute path to your project root in the options, for example `{ projectRoot: '/Users/username/projects/my-app' }`.

## How It Works

The reporter captures results from stories that have `play` functions (interaction tests):

- Stories with `play` functions are reported as tests
- Stories without `play` functions are ignored
- Test results are saved to `.claude/tdd-guard/data/test.json`

## Output Format

The reporter writes test results in a format compatible with TDD Guard validation:

```json
{
  "testModules": [
    {
      "moduleId": "button--primary",
      "tests": [
        {
          "name": "Primary",
          "fullName": "Button > Primary",
          "state": "passed"
        }
      ]
    }
  ],
  "unhandledErrors": [],
  "reason": "passed"
}
```

## More Information

- Test results are saved to `.claude/tdd-guard/data/test.json`
- See [TDD Guard documentation](https://github.com/nizos/tdd-guard) for complete setup

## License

MIT

---

### Overview (`reporters/vitest/README.md`)

# TDD Guard Vitest Reporter

Vitest reporter that captures test results for TDD Guard validation.

## Requirements

- Node.js 18+
- Vitest 3.2.0+
- [TDD Guard](https://github.com/nizos/tdd-guard) installed globally

## Installation

```bash
npm install --save-dev tdd-guard-vitest
```

## Configuration

### Vitest Configuration

Add the reporter to your `vitest.config.ts`:

```typescript
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    reporters: ['default', ['tdd-guard-vitest']],
  },
})
```

### Workspace/Monorepo Configuration

For workspaces or monorepos, pass the project root path in the reporter options:

```typescript
// vitest.config.ts in project root
import { defineConfig } from 'vitest/config'
import path from 'path'

export default defineConfig({
  test: {
    reporters: [
      'default',
      ['tdd-guard-vitest', { projectRoot: path.resolve(__dirname) }],
    ],
  },
})
```

If your vitest config is in a workspace subdirectory, specify the absolute path to your project root in the options, for example `{ projectRoot: '/Users/username/projects/my-app' }`.

## More Information

- Test results are saved to `.claude/tdd-guard/data/test.json`
- See [TDD Guard documentation](https://github.com/nizos/tdd-guard) for complete setup

## License

MIT

---

### Overview (`reporters/dotnet/TddGuard.Dotnet.Tests/README.md`)

# TddGuard.Dotnet.Tests

## Testing philosophy

Tests verify **behaviour, not implementation**. The system under test (SUT) is
`TddGuardListener` exercised through its MTP interface — events go in, JSON
comes out. Tests never reference internal types like `CollectedResult`,
`TestRunSummariser`, or `TestReportSerializer` directly.

This means we can refactor internals freely without breaking tests, and every
test failure points to a genuine change in observable behaviour.

## Test layers

### Unit tests (`TddGuardListenerTests`)

Example-based tests that verify specific, named behaviours: "writes 'passed'
reason for passing test", "ignores in-progress nodes", etc. Each test follows
strict **Arrange/Act/Assert** via the `ListenerFixture` DSL:

```csharp
await ListenerFixture
    .Arrange(async listener =>
    {
        await listener.ConsumeAsync(StubProducer(), An.Event().Named("test1").Passed(), default);
    })
    .Act()
    .Assert(async root =>
    {
        await Assert.That(root.Reason()).IsEqualTo("passed");
    });
```

- **Arrange** feeds MTP events to the listener
- **Act** triggers `OnTestSessionFinishingAsync` (the write)
- **Assert** inspects the resulting JSON

These tests are fast, deterministic, and produce pinpoint failure messages.

### Property-based tests (`TddGuardListenerPropertyTests`)

FsCheck-driven tests that verify universal properties across hundreds of
randomly generated event sequences: "count is preserved", "reason reflects
failures", "output is valid JSON", etc.

Properties use `ListenerFixture.Execute()` which returns a synchronous
`PipelineResult` suitable for FsCheck's `Prop.ForAll`:

```csharp
Prop.ForAll(Arb.From(genEvents), events =>
{
    using var result = RunEvents(events);
    return result.Output!.Value.GetProperty("reason").GetString() == expected;
});
```

These tests explore the input space far more broadly than hand-crafted examples
and catch edge cases a person would never consider.

### Supporting test files

| File                       | Purpose                                                            |
| -------------------------- | ------------------------------------------------------------------ |
| `ProjectRootResolverTests` | Unit tests for env var resolution (pure function, tested directly) |
| `ReportFileWriterTests`    | Unit tests for file I/O (temp dir, error paths, overwrite)         |
| `DiagnosticDecoratorTests` | Unit tests for logging decorators (spy-based)                      |
| `TestReportWriterTests`    | Unit tests for the write-or-skip orchestrator                      |
| `TestNodeMapperTests`      | Unit tests for UID parsing (pure function)                         |
| `TddGuardBuilderTests`     | Unit tests for the MTP registration composition root               |

These test lower-level components directly because their contracts are stable
public APIs (file paths, env vars, JSON output). They don't test internal
wiring — that's covered by the outside-in listener tests and PBTs.

## Test infrastructure

### `ListenerFixture`

The test harness that manages the listener lifecycle. Provides three APIs:

- **`Arrange(...).Act().Assert(...)`** — async DSL for unit tests
- **`Arrange(...).Act().AssertNoOutput()`** — for tests expecting no JSON output
- **`Arrange(...).Act().Execute()`** — synchronous pipeline for PBTs, returns `PipelineResult`
- **`Create()`** — manual session control for tests that need multiple sessions or concurrency

### `TestEventBuilder` (`An.Event()`)

Test Data Builder for MTP events. Hides MTP-specific types behind
intent-revealing methods:

```csharp
An.Event().Named("test1").InFile("/src/A.cs").Passed()
An.Event().Named("test1").Failed("assertion message")
An.Event().Named("test1").FailedWithExplanation("no exception, just text")
An.Event().Named("test1").FailedBare()       // no exception, no explanation
An.Event().Named("test1").Error("setup failed")
An.Event().Named("test1").Skipped()
An.Event().Named("test1").InProgress()
An.Event().Named("test1").Discovered()
An.Event().Named("test1").WithNoFilePath().Passed()  // null file path
```

### `MtpStubs`

Low-level MTP stubs and the FsCheck generator (`GenTaggedEvent`). The generator
produces `TaggedEvent` records that pair an MTP message with a `bool IsFailed`
flag, so PBT properties can compute expected outcomes without inspecting MTP
internals.

### `TestJsonAssert`

Extension methods for navigating `JsonElement` in assertions: `root.Reason()`,
`root.Module().Test().State()`, `root.Module().Test().ErrorMessage()`, etc.

### `TempDir`

Disposable temp directory helper for file I/O tests.

## Framework compatibility smoke tests

The `TddGuard.Dotnet.Compat.*` projects sit alongside the test project
and prove the MTP extension works with every major .NET test framework.
Each is a standalone runnable test project with a single passing test.

| Project         | Framework | Notes                                                       |
| --------------- | --------- | ----------------------------------------------------------- |
| `Compat.TUnit`  | TUnit 0.x | Native MTP, file path as module ID                          |
| `Compat.MSTest` | MSTest v4 | Native MTP, file path as module ID                          |
| `Compat.XUnit`  | xUnit v3  | Native MTP via `xunit.v3.mtp-v2`, file path as module ID    |
| `Compat.XUnit2` | xUnit v2  | Third-party adapter (`YTest.MTP.XUnit2`), hash as module ID |
| `Compat.NUnit`  | NUnit 4   | MTP via NUnit runner, UID as module ID (no file path)       |

### How they work

Each project has three files:

- **`.csproj`** targets net10.0, references the framework package and
  `TddGuard.Dotnet` via ProjectReference. A `<TestingPlatformBuilderHook>`
  MSBuild item tells MTP's codegen where to find the hook class.
- **`TestingPlatformBuilderHook.cs`** is the MTP entry point. MTP's
  source generator calls `AddExtensions` at startup, which delegates to
  `TddGuardBuilder.Register(builder)` to wire up the listener.
- **`SmokeTests.cs`** has one passing test in the framework's native syntax.

When `dotnet test` runs, MTP starts the test host, calls the hook,
registers our listener, the framework discovers and runs the test,
MTP publishes `TestNodeUpdateMessage` events, our listener collects
them, and `OnTestSessionFinishingAsync` writes `test.json`.

The extension never knows which framework is running. It subscribes to
`TestNodeUpdateMessage` which is a generic MTP event type. The only
observable differences between frameworks are in module ID source
(file path vs UID fallback) and UID format, both handled by existing
null-checks in `TestNodeMapper`.

### Why ProjectReference instead of NuGet

The compat projects reference `TddGuard.Dotnet` via ProjectReference
(local source) rather than PackageReference (NuGet). This avoids the
NuGet pack/restore cycle and firewall issues with CDN IPs. The manual
`<TestingPlatformBuilderHook>` MSBuild item replaces what the NuGet
package's `buildTransitive` props would normally inject. The integration
tests (`reporters/test/factories/dotnet.ts`) separately test the NuGet
packaging path.

### Running the smoke tests

```bash
export TDD_GUARD_PROJECT_ROOT=/workspace
cd /workspace/reporters/dotnet/TddGuard.Dotnet.Compat.MSTest
dotnet test
cat /workspace/.claude/tdd-guard/data/test.json
```

Repeat for each `Compat.*` project. If `test.json` appears with
`"reason":"passed"`, the framework works.

## Why outside-in

Tests are written from the perspective of the consumer, not the
implementer. The SUT (system under test) is the `TddGuardListener`
exercised through its MTP interface: feed it events, trigger the
session finish, read the JSON.

This matters because:

- **Refactoring is free.** You can restructure `Summarise`, change how
  `Serialize` works, rename internal types, or merge classes — and no
  test breaks as long as the JSON output stays the same.
- **Tests document behaviour.** Each test says what the system does
  ("writes 'passed' reason for passing test"), not how it does it.
  New contributors read the tests to understand the contract.
- **Agents continue the pattern.** If you are an AI agent adding tests:
  use `ListenerFixture`, use `An.Event()` to build inputs, assert on
  the JSON output. Do not reference `CollectedResult`, `TestRunOutput`,
  `Summarise`, or `Serialize` in test assertions. Those are internals.

The supporting tests (`ProjectRootResolverTests`, `ReportFileWriterTests`,
etc.) test lower-level components directly because their contracts are
stable public boundaries (env vars, file paths, JSON structure). They
follow the same principle: test through the public API, not the internals.

## Running tests

All commands run inside the devcontainer:

```bash
docker exec -w /workspace/reporters/dotnet <container> dotnet test --project TddGuard.Dotnet.Tests
```

For coverage:

```bash
docker exec -w /workspace/reporters/dotnet <container> dotnet test --project TddGuard.Dotnet.Tests \
  -- --coverage --coverage-output-format cobertura --coverage-output /tmp/coverage.xml
```

---

### Config Migration (`docs/config-migration.md`)

# Configuration Migration Guide

This guide helps you migrate from the legacy `MODEL_TYPE` configuration to the new `VALIDATION_CLIENT` system.

## What Changed

The old configuration system required:

- `MODEL_TYPE` to choose between `claude_cli` or `anthropic_api`
- `USE_SYSTEM_CLAUDE` (true/false) to choose between system or local Claude binary
- Complex setup for finding and configuring the Claude binary

The new system simplifies this with:

- `VALIDATION_CLIENT` to choose between `sdk`, `api`, or `cli` (deprecated)
- No binary configuration needed for SDK (the default)

## Migration Instructions

### From Claude CLI to SDK

**Old configuration:**

```bash
MODEL_TYPE=claude_cli
USE_SYSTEM_CLAUDE=true  # or false
```

**New configuration:**

```bash
VALIDATION_CLIENT=sdk  # Or omit entirely, as SDK is the default
```

The SDK client eliminates the need for:

- Finding the Claude binary location
- Setting `USE_SYSTEM_CLAUDE`
- Dealing with symlinks or PATH configuration

### From Anthropic API

**Old configuration:**

```bash
MODEL_TYPE=anthropic_api
TDD_GUARD_ANTHROPIC_API_KEY=your-api-key-here
```

**New configuration:**

```bash
VALIDATION_CLIENT=api
TDD_GUARD_ANTHROPIC_API_KEY=your-api-key-here  # Same key variable
```

The API configuration remains similar, just with a clearer variable name.

## Legacy CLI Client (Deprecated)

If you must continue using the CLI client (not recommended):

```bash
VALIDATION_CLIENT=cli  # Deprecated - use sdk instead
```

You'll still need to configure the Claude binary location as described in the [CLI Binary Configuration](#cli-binary-configuration) section below.

### Why CLI is Deprecated

The SDK client is easier to work with and requires less configuration for different setups and operating systems.

## Deprecated Variables

| Variable            | Replacement         | Notes                                             |
| ------------------- | ------------------- | ------------------------------------------------- |
| `MODEL_TYPE`        | `VALIDATION_CLIENT` | Map `claude_cli` → `sdk`, `anthropic_api` → `api` |
| `USE_SYSTEM_CLAUDE` | None                | No longer needed with SDK                         |
| `TEST_MODEL_TYPE`   | None                | Use consistent configuration                      |

## Common Migration Issues

### API Key Conflicts

For information about API key conflicts and billing, see the [Validdation Model Configuration](validation-model.md) documentation.

## CLI Binary Configuration

If you're still using the deprecated CLI client (`VALIDATION_CLIENT=cli`), you need to help TDD Guard find your Claude installation.

### Finding Your Claude Installation

```bash
# Check system-wide installation
which claude

# Check local installation
ls ~/.claude/local/claude
```

### Configuration Options

**Option 1: Environment Variable**

If Claude is in your PATH:

```bash
USE_SYSTEM_CLAUDE=true
```

**Option 2: Symlink**

Point to your Claude installation:

```bash
# Create directory if needed
mkdir -p ~/.claude/local

# Create symlink to your Claude binary
ln -s /path/to/your/claude ~/.claude/local/claude
```

Example for Homebrew on macOS:

```bash
ln -s /opt/homebrew/bin/claude ~/.claude/local/claude
```

**Option 3: Migrate Installation**

Use Claude Code's built-in command:

```bash
/migrate-installer
```

## Getting Help

If you encounter issues during migration:

1. Check the [main configuration guide](configuration.md)
2. Review the [Validdation Model Configuration](validation-model.md)
3. Open an issue at [github.com/nizos/tdd-guard/issues](https://github.com/nizos/tdd-guard/issues)

---

### Configuration (`docs/configuration.md`)

# Configuration Guide

This guide covers the configuration options for TDD Guard.

## Environment Variables

TDD Guard uses environment variables for configuration.
Create a `.env` file in your project root:

**Note:** If you're migrating from an older version using `MODEL_TYPE`, see the [Configuration Migration Guide](config-migration.md).

```bash
# Validation client for TDD enforcement (optional)
# Options: 'sdk' (default) or 'api'
VALIDATION_CLIENT=sdk

# Model version for validation (optional)
# Default: claude-sonnet-4-6
# See https://docs.anthropic.com/en/docs/about-claude/models/overview
TDD_GUARD_MODEL_VERSION=claude-sonnet-4-6

# Anthropic API Key
# Required when VALIDATION_CLIENT is set to 'api'
# Get your API key from https://console.anthropic.com/
TDD_GUARD_ANTHROPIC_API_KEY=your-api-key-here

# Linter type for refactoring phase support (optional)
# Options: 'eslint', 'golangci-lint' or unset (no linting)
# See docs/linting.md for detailed setup and configuration
LINTER_TYPE=eslint
```

## Model Configuration

TDD Guard supports multiple validation clients:

- **SDK** (default) - Uses your Claude Code subscription
- **API** - Separate billing for CI/CD or faster validation
- **CLI** (deprecated) - Legacy option, not recommended

For detailed configuration, billing information, and troubleshooting, see the [Validation Model Configuration](validation-model.md) guide.

If you're using the deprecated CLI client, see the [Configuration Migration Guide](config-migration.md#cli-binary-configuration).

## Settings File Locations

Choose where to save your settings based on your needs:

- **Project settings** (`.claude/settings.json`) - Recommended for team consistency
- **Local settings** (`.claude/settings.local.json`) - For personal preferences
- **User settings** (`~/.claude/settings.json`) - For global configuration

## Hook Configuration

If you installed TDD Guard as a [plugin](../plugin/README.md), hooks are configured automatically. For manual hook setup, see the [installation guide](installation.md#2-configure-claude-code-hooks).

**Related:**

- [Quick commands](quick-commands.md) - Enable `tdd-guard on/off` commands
- [Session management](session-management.md) - Automatic data clearing and rule setup
- [Lint integration](linting.md) - Automated refactoring support
- [Strengthening enforcement](enforcement.md) - Prevent agents from bypassing validation

## Test Reporter Configuration

- [Manual Installation](installation.md#3-add-test-reporter) - Setup guide for all supported reporters

- **JavaScript/TypeScript**:
  - [Vitest reporter configuration](../reporters/vitest/README.md#configuration)
  - [Jest reporter configuration](../reporters/jest/README.md#configuration)
- **Python**: See [Pytest reporter configuration](../reporters/pytest/README.md#configuration)
- **PHP**: See [PHPUnit reporter configuration](../reporters/phpunit/README.md#configuration)
- **Go**: See [Go reporter configuration](../reporters/go/README.md#configuration)
- **Rust**: See [Rust reporter configuration](../reporters/rust/README.md#configuration)

## Custom Validation Rules

See [Custom Instructions](custom-instructions.md) to customize TDD validation rules to match your practices.

## Data Storage

TDD Guard stores context data in `.claude/tdd-guard/data/`:

- `instructions.md` - Your custom TDD validation rules (created automatically, never overwritten)
- `test.json` - Latest test results from your test runner (Vitest or pytest)
- `todos.json` - Current todo state
- `modifications.json` - File modification history
- `lint.json` - ESLint results (only created when LINTER_TYPE=eslint)

This directory is created automatically and should be added to `.gitignore`.

## Troubleshooting

### Dependency Versions

#### Vitest

Use the latest Vitest version to ensure correct test output format for TDD Guard:

```bash
npm install --save-dev vitest@latest
```

#### pytest

For Python projects, ensure you have a recent version of pytest:

```bash
pip install pytest>=7.0.0
```

### Common Issues

1. **TDD Guard not triggering**: Check that hooks are properly configured in `.claude/settings.json`
2. **Test results not captured**: Ensure `VitestReporter` is added to your Vitest config
3. **"Command not found" errors**: Make sure `tdd-guard` is installed globally with `npm install tdd-guard`
4. **Changes not taking effect**: Restart your Claude session after modifying hooks or environment variables

### Updating TDD Guard

To update to the latest version:

```bash
# Update CLI tool
npm update -g tdd-guard

# For JavaScript/TypeScript projects, update the Vitest reporter in your project
npm update tdd-guard-vitest

# For Python projects, update the pytest reporter
pip install --upgrade tdd-guard-pytest
```

Check your current version:

```bash
npm list -g tdd-guard
pip show tdd-guard-pytest
```

---

### Custom Instructions (`docs/custom-instructions.md`)

# Custom TDD Instructions

Customize TDD Guard's validation rules to match your specific TDD practices.

## How It Works

TDD Guard uses validation rules to enforce TDD principles. You can override these default rules by creating a custom instructions file at `.claude/tdd-guard/data/instructions.md`.

## Automatic Setup

If you have the [SessionStart hook](session-management.md) configured, the instructions file is created automatically with default rules when:

- Starting a new Claude Code session
- Resuming a session
- Using the `/clear` command

Your custom instructions are never overwritten - once created, the file remains under your control.

## Creating Custom Instructions

1. Edit `.claude/tdd-guard/data/instructions.md`
2. Adjust or replace the default rules with your TDD requirements
3. Changes take effect immediately - no restart needed

## Updating to Latest Defaults

When updating TDD Guard, you may want the latest default instructions:

1. Delete `.claude/tdd-guard/data/instructions.md`
2. Trigger the SessionStart hook (start new session or use `/clear`)
3. The latest defaults will be created automatically

Alternatively, you can manually copy the default rules from [`src/validation/prompts/rules.ts`](../src/validation/prompts/rules.ts).

## Protecting Your Instructions

Prevent agents from modifying your custom instructions by denying access to TDD Guard data. See [Strengthening TDD Enforcement](enforcement.md) for details.

## Tips

- Start with the default instructions and modify incrementally
- Keep rules clear and actionable for consistent validation
- Share effective customizations with the TDD Guard community in [GitHub Discussions](https://github.com/nizos/tdd-guard/discussions)

---

### Enforcement (`docs/enforcement.md`)

# Strengthening TDD Enforcement

Ensure consistent TDD validation by preventing agents from modifying guard settings or bypassing file operation hooks.

## Protect Guard Settings

Prevent agents from accessing TDD Guard's configuration and state:

```json
{
  "permissions": {
    "deny": ["Read(.claude/tdd-guard/**)"]
  }
}
```

This protects your custom instructions, guard state, and test results from unintended changes.

## Block File Operation Bypass

If your settings allow shell commands without approval, agents can modify files without triggering TDD validation. Block these commands to maintain enforcement:

```json
{
  "permissions": {
    "deny": [
      "Bash(echo:*)",
      "Bash(printf:*)",
      "Bash(sed:*)",
      "Bash(awk:*)",
      "Bash(perl:*)"
    ]
  }
}
```

**Note:** Only needed if you've configured auto-approval for shell commands. May limit some agent capabilities.

## Where to Apply

Add these settings to your chosen settings file. See [Settings File Locations](configuration.md#settings-file-locations) to choose the appropriate location.

## Need Help?

- Report bypass methods: [GitHub Issues](https://github.com/nizos/tdd-guard/issues)
- Share strategies: [GitHub Discussions](https://github.com/nizos/tdd-guard/discussions)

---

### Ignore Patterns (`docs/ignore-patterns.md`)

# Ignore Patterns Guide

Configure TDD Guard to skip validation for specific files using glob patterns.

## Why Use Ignore Patterns?

Control exactly which files TDD Guard validates. Useful for monorepos, rapid prototyping, or when different parts of your codebase need different validation rules.

## Default Ignore Patterns

By default, TDD Guard ignores files with these extensions:

- `*.md` - Markdown documentation
- `*.txt` - Text files
- `*.log` - Log files
- `*.json` - JSON configuration files
- `*.yml` / `*.yaml` - YAML configuration files
- `*.xml` - XML files
- `*.html` - HTML files
- `*.css` - Stylesheets
- `*.erb` - Rails ERB templates
- `*.rst` - reStructuredText documentation

## Custom Ignore Patterns

You can configure custom ignore patterns by creating a `config.json` file in the TDD Guard data directory (`.claude/tdd-guard/data/`):

```json
{
  "guardEnabled": true,
  "ignorePatterns": [
    "*.md",
    "*.css",
    "*.json",
    "*.yml",
    "**/*.generated.ts",
    "**/public/**",
    "*.config.*"
  ]
}
```

**Note**: Custom patterns replace the default patterns entirely. If you want to keep some defaults (like `*.md` or `*.json`), include them in your custom list.

## Pattern Syntax

Patterns use minimatch syntax (similar to `.gitignore`):

- `*.ext` - Match files with extension (e.g., `*.md`)
- `dir/**` - Match all files in directory (e.g., `dist/**`)
- `**/*.ext` - Match extension anywhere (e.g., `**/*.test.ts`)
- `*.{js,ts}` - Match multiple extensions (e.g., `*.{yml,yaml}`)
- `path/**/*.ext` - Match in specific path (e.g., `src/**/*.spec.js`)

## Managing Patterns

### Viewing Current Patterns

To see which patterns are currently active, check your `config.json` file:

```bash
cat .claude/tdd-guard/data/config.json
```

If no custom patterns are configured, the default patterns listed above are used.

### Updating Patterns

1. Create or edit `.claude/tdd-guard/data/config.json`
2. Add your `ignorePatterns` array
3. The changes take effect immediately

### Testing Patterns

To verify your patterns work as expected:

1. Edit a file that should be ignored
2. TDD Guard should skip validation immediately

## Summary

Ignore patterns provide the flexibility to apply TDD validation exactly where you want it in your codebase. Start with the defaults, then customize as your project's needs evolve.

---

### Installation (`docs/installation.md`)

# Manual Installation

If you installed TDD Guard as a [plugin](../plugin/README.md), you don't need to do any of this manually — the plugin configures hooks automatically and the `/tdd-guard:setup` skill handles reporter installation and configuration.

This document is a reference for manual setup or troubleshooting.

## 1. Install TDD Guard

Using npm:

```bash
npm install tdd-guard
```

Or using Homebrew:

```bash
brew install tdd-guard
```

## 2. Configure Claude Code Hooks

Add the following hooks to your settings file. See [Settings File Locations](configuration.md#settings-file-locations) for guidance on which file to use.

You can also configure hooks interactively by typing `/hooks` in Claude Code.

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit|TodoWrite",
        "hooks": [
          {
            "type": "command",
            "command": "tdd-guard"
          }
        ]
      }
    ],
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "tdd-guard"
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "matcher": "startup|resume|clear",
        "hooks": [
          {
            "type": "command",
            "command": "tdd-guard"
          }
        ]
      }
    ]
  }
}
```

## 3. Add Test Reporter

TDD Guard needs to capture test results from your test runner. Choose your language below:

### JavaScript/TypeScript

Choose your test runner:

#### Vitest

Install the [tdd-guard-vitest](https://www.npmjs.com/package/tdd-guard-vitest) reporter in your project:

```bash
npm install --save-dev tdd-guard-vitest
```

Add to your `vitest.config.ts`:

```typescript
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    reporters: [
      'default',
      ['tdd-guard-vitest', { projectRoot: '/Users/username/projects/my-app' }],
    ],
  },
})
```

#### Jest

Install the [tdd-guard-jest](https://www.npmjs.com/package/tdd-guard-jest) reporter in your project:

```bash
npm install --save-dev tdd-guard-jest
```

Add to your `jest.config.ts`:

```typescript
import type { Config } from 'jest'

const config: Config = {
  reporters: [
    'default',
    [
      'tdd-guard-jest',
      {
        projectRoot: '/Users/username/projects/my-app',
      },
    ],
  ],
}

export default config
```

**Note:** For both Vitest and Jest, specify the project root path when your test config is not at the project root (e.g., in workspaces or monorepos). This ensures TDD Guard can find the test results. See the reporter configuration docs for more details:

- [Vitest configuration](../reporters/vitest/README.md#configuration)
- [Jest configuration](../reporters/jest/README.md#configuration)

#### Storybook

Install the [tdd-guard-storybook](https://www.npmjs.com/package/tdd-guard-storybook) reporter in your project:

```bash
npm install --save-dev tdd-guard-storybook
```

Add to your `.storybook/test-runner.ts`:

```typescript
import { StorybookReporter } from 'tdd-guard-storybook'
import path from 'path'

const reporter = new StorybookReporter({
  projectRoot: path.resolve(__dirname, '..'),
})

module.exports = {
  async postVisit(page, context) {
    await reporter.onStoryResult(context)
  },
}

process.on('exit', () => {
  reporter.onComplete()
})
```

**Note:** The reporter is constructed directly in your `.storybook/test-runner.ts` and wired into Storybook test-runner's `postVisit` hook. Specify the project root path when your Storybook config is not at the project root. See the [Storybook reporter configuration](../reporters/storybook/README.md#configuration) for more details.

**Tip:** For Storybook 10+ with Vite-based frameworks, consider using [`@storybook/addon-vitest`](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon) instead. This runs Storybook tests through Vitest, allowing you to use the `tdd-guard-vitest` reporter for faster test execution. See [Storybook with Vitest Addon](storybook-vitest-addon.md) for setup instructions.

### Python (pytest)

Install the [tdd-guard-pytest](https://pypi.org/project/tdd-guard-pytest) reporter:

```bash
pip install tdd-guard-pytest
```

Configure the project root in your `pyproject.toml`:

```toml
[tool.pytest.ini_options]
tdd_guard_project_root = "/Users/username/projects/my-app"
```

**Note:** Specify the project root path when your tests run from a subdirectory or in a monorepo setup. This ensures TDD Guard can find the test results. See the [pytest reporter configuration](../reporters/pytest/README.md#configuration) for alternative configuration methods (pytest.ini, setup.cfg).

### PHP (PHPUnit)

Install the [tdd-guard/phpunit](https://packagist.org/packages/tdd-guard/phpunit) reporter in your project:

```bash
composer require --dev tdd-guard/phpunit
```

For PHPUnit 9.x, add to your `phpunit.xml`:

```xml
<listeners>
    <listener class="TddGuard\PHPUnit\TddGuardListener">
        <arguments>
            <string>/Users/username/projects/my-app</string>
        </arguments>
    </listener>
</listeners>
```

For PHPUnit 10.x/11.x/12.x, add to your `phpunit.xml`:

```xml
<extensions>
    <bootstrap class="TddGuard\PHPUnit\TddGuardExtension">
        <parameter name="projectRoot" value="/Users/username/projects/my-app"/>
    </bootstrap>
</extensions>
```

**Note:** Specify the project root path when your phpunit.xml is not at the project root (e.g., in subdirectories or monorepos). This ensures TDD Guard can find the test results. The reporter saves results to `.claude/tdd-guard/data/test.json`.

### Go

Install the tdd-guard-go reporter:

```bash
go install github.com/nizos/tdd-guard/reporters/go/cmd/tdd-guard-go@latest
```

Pipe `go test -json` output to the reporter:

```bash
go test -json ./... 2>&1 | tdd-guard-go -project-root /Users/username/projects/my-app
```

For Makefile integration:

```makefile
test:
	go test -json ./... 2>&1 | tdd-guard-go -project-root /Users/username/projects/my-app
```

**Note:** The reporter acts as a filter that passes test output through unchanged while capturing results for TDD Guard. See the [Go reporter configuration](../reporters/go/README.md#configuration) for more details.

### Rust

Install the [tdd-guard-rust](https://crates.io/crates/tdd-guard-rust) reporter:

```bash
cargo install tdd-guard-rust
```

Use it to capture test results from `cargo test` or `cargo nextest`:

```bash
# With nextest (recommended)
cargo nextest run 2>&1 | tdd-guard-rust --project-root /Users/username/projects/my-app --passthrough

# With cargo test
cargo test -- -Z unstable-options --format json 2>&1 | tdd-guard-rust --project-root /Users/username/projects/my-app --passthrough
```

For Makefile integration:

```makefile
test:
	cargo nextest run 2>&1 | tdd-guard-rust --project-root $(PWD) --passthrough
```

**Note:** The reporter acts as a filter that passes test output through unchanged while capturing results for TDD Guard. See the [Rust reporter configuration](../reporters/rust/README.md#configuration) for more details.

## Troubleshooting

### Hooks not working

The recommended installation method is via the plugin, as
described in the [README](../README.md#installation).

Non-plugin installations might be incompatible with your current Claude Code version, hooks will
fail silently or exit with errors.

If you have an existing Homebrew or npm installation, remove it and switch to the plugin
installation.

---

### Linting (`docs/linting.md`)

# Linting and Refactoring Support

TDD Guard can optionally check code quality during the refactoring phase (when tests are green).
The following linters are supported: ESLint (JavaScript/TypeScript), golangci-lint (Go), and RuboCop (Ruby).
When issues are detected, the coding agent will be prompted to fix them.

## Why Use Refactoring Support?

During the TDD green phase, the coding agent may:

- Clean up implementation code
- Extract methods or constants
- Improve naming
- Remove duplication

The refactoring support helps by:

- Running ESLint automatically after file modifications
- Detecting code quality issues
- Prompting the coding agent to fix any issues found

## Setup

1. **Install a supported linter** in your project:

   For JavaScript/TypeScript (ESLint):

   ```bash
   npm install --save-dev eslint@latest
   ```

   For Go (golangci-lint): see the [golangci-lint installation guide](https://golangci-lint.run/welcome/install/).

   For Ruby (RuboCop):

   ```bash
   gem install rubocop
   ```

2. **Enable linting** by setting the environment variable:

   ```bash
   LINTER_TYPE=eslint          # for ESLint
   LINTER_TYPE=golangci-lint   # for golangci-lint
   LINTER_TYPE=rubocop         # for RuboCop
   ```

3. **Configure the PostToolUse hook**

   You can configure this hook either through the interactive `/hooks` command or by manually editing your settings file. See [Settings File Locations](configuration.md#settings-file-locations) to choose the appropriate location. Use the same location as your PreToolUse hook.

   ### Interactive Setup (Recommended)
   1. Type `/hooks` in Claude Code
   2. Select `PostToolUse - After tool execution`
   3. Choose `+ Add new matcher...`
   4. Enter: `Write|Edit|MultiEdit`
   5. Select `+ Add new hook...`
   6. Enter command: `tdd-guard`
   7. Choose where to save

   ### Manual Configuration

   Add to your chosen settings file:

   ```json
   {
     "hooks": {
       "PostToolUse": [
         {
           "matcher": "Write|Edit|MultiEdit",
           "hooks": [
             {
               "type": "command",
               "command": "tdd-guard"
             }
           ]
         }
       ]
     }
   }
   ```

## How It Works

When enabled:

1. After any file modification (Edit, MultiEdit, Write)
2. TDD Guard runs the configured linter on modified files
3. If issues are found, the coding agent receives a notification
4. The agent will then fix the identified issues

Without `LINTER_TYPE` set, TDD Guard skips all linting operations.

**Tip**: Configure ESLint with complexity rules (e.g., `complexity`, `max-depth`) and the SonarJS plugin to encourage meaningful refactoring.
These rules help identify code that could benefit from simplification during the green phase.

## ESLint Configuration

For effective refactoring support, consider adding these rules to your `.eslintrc.js`:

```javascript
module.exports = {
  rules: {
    complexity: ['warn', 10],
    'max-depth': ['warn', 4],
    'max-lines-per-function': ['warn', 50],
    'max-nested-callbacks': ['warn', 3],
    'max-params': ['warn', 4],
  },
}
```

## Troubleshooting

### ESLint Not Running

1. Verify ESLint is installed: `npm list eslint`
2. Check that `LINTER_TYPE=eslint` is set in your `.env` file
3. Ensure the PostToolUse hook is configured
4. Restart your Claude session after making changes

### RuboCop Not Running

1. Verify RuboCop is installed: `rubocop --version`
2. Check that `LINTER_TYPE=rubocop` is set in your `.env` file
3. Ensure the PostToolUse hook is configured
4. Restart your Claude session after making changes

---

### Quick Commands (`docs/quick-commands.md`)

# TDD Guard Quick Commands

TDD Guard can be quickly enabled or disabled using simple commands in your Claude Code session.
This is particularly useful when you need to temporarily disable TDD enforcement during prototyping or exploration phases.

## Usage

Simply type one of these commands in your Claude Code prompt:

- `tdd-guard on` - Enables TDD Guard enforcement
- `tdd-guard off` - Disables TDD Guard enforcement

The commands are case-insensitive, so `TDD-Guard OFF`, `tdd-guard off`, and `Tdd-Guard Off` all work the same way.

## Setup

To enable the quick commands feature, you need to add the UserPromptSubmit hook to your Claude Code configuration.
You can set this up either through the interactive `/hooks` command or by manually editing your settings file. See [Settings File Locations](configuration.md#settings-file-locations) to choose the appropriate location.

### Using Interactive Setup (Recommended)

1. Type `/hooks` in Claude Code
2. Select `UserPromptSubmit - When the user submits a prompt`
3. Select `+ Add new hook...`
4. Enter command: `tdd-guard`
5. Choose where to save

### Manual Configuration (Alternative)

Add the following to your chosen settings file:

```json
{
  "hooks": {
    "userpromptsubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "tdd-guard"
          }
        ]
      }
    ]
  }
}
```

Note: Your configuration file may already have other hooks configured.
Simply add the `userpromptsubmit` section to your existing hooks object.

**Tip**: To prevent agents from modifying the TDD Guard state, see [Strengthening TDD Enforcement](enforcement.md).

---

### Session Management (`docs/session-management.md`)

# TDD Guard Session Management

The SessionStart hook manages TDD Guard's session data and ensures a clean slate for each Claude Code session.

## What It Does

### Clears Transient Data

- Test results from previous sessions
- Lint reports and code quality checks
- Other temporary validation data

### Sets Up Validation Rules

- Creates the customizable instructions file if it doesn't exist
- Preserves your custom rules if already configured
- See [Custom Instructions](custom-instructions.md) for details

**Note:** The guard's enabled/disabled state is preserved across sessions.

## Setup

To enable session management, you need to add the SessionStart hook to your Claude Code configuration.
You can set this up either through the interactive `/hooks` command or by manually editing your settings file. See [Settings File Locations](configuration.md#settings-file-locations) to choose the appropriate location.

### Using Interactive Setup (Recommended)

1. Type `/hooks` in Claude Code
2. Select `SessionStart - When a new session is started`
3. Select `+ Add new matcher…`
4. Enter matcher: `startup|resume|clear`
5. Select `+ Add new hook…`
6. Enter command: `tdd-guard`
7. Choose where to save

### Manual Configuration (Alternative)

Add the following to your chosen settings file:

```json
{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup|resume|clear",
        "hooks": [
          {
            "type": "command",
            "command": "tdd-guard"
          }
        ]
      }
    ]
  }
}
```

Note: Your configuration file may already have other hooks configured.
Simply add the `SessionStart` section to your existing hooks object.

## How It Works

The SessionStart hook triggers when:

- Claude Code starts up (`startup`)
- A session is resumed (`resume`)
- The `/clear` command is used (`clear`)

When triggered, TDD Guard clears all transient data while preserving the guard state and your custom validation rules.

## Tips

- No manual intervention needed - clearing happens automatically
- To toggle the guard on/off, use the [quick commands](quick-commands.md)
- For debugging, check `.claude/tdd-guard/` to see stored data

---

### Storybook Vitest Addon (`docs/storybook-vitest-addon.md`)

# Storybook with Vitest Addon

If you're using Storybook 10+ with a Vite-based framework, you can use `@storybook/addon-vitest` instead of `@storybook/test-runner`. This approach uses TDD Guard's existing Vitest reporter to capture Storybook test results.

## Why Use the Vitest Addon?

The Vitest addon offers several advantages:

- **Faster execution** - Uses Vitest's browser mode instead of Playwright
- **Modern tooling** - Built on Vitest, which is faster and more modern than Jest
- **Unified testing** - Run Storybook tests alongside your other Vitest tests
- **Better DX** - Full Storybook Test experience with interaction, accessibility, and visual tests
- **Simpler setup** - Uses your existing Vitest configuration

## Requirements

- Storybook 10+
- Vite-based Storybook framework (React-Vite, Vue-Vite, Svelte-Vite, etc.)
- Vitest

## Setup

### 1. Install the Vitest Addon

```bash
npm install --save-dev @storybook/addon-vitest
```

### 2. Configure Storybook

Add the addon to your `.storybook/main.ts`:

```typescript
import type { StorybookConfig } from '@storybook/react-vite'

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: ['@storybook/addon-vitest'],
  framework: '@storybook/react-vite',
}

export default config
```

### 3. Configure Vitest Reporter

Since the Vitest addon runs your Storybook tests through Vitest, you use the `tdd-guard-vitest` reporter to capture results:

```bash
npm install --save-dev tdd-guard-vitest
```

Add to your `vitest.config.ts`:

```typescript
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    reporters: [
      'default',
      ['tdd-guard-vitest', { projectRoot: '/Users/username/projects/my-app' }],
    ],
  },
})
```

### 4. Run Tests

```bash
npm run test
```

Your Storybook interaction tests will run alongside your regular Vitest tests, and TDD Guard will capture all results.

## Comparison with Test Runner

| Feature            | @storybook/test-runner | @storybook/addon-vitest |
| ------------------ | ---------------------- | ----------------------- |
| Test framework     | Jest + Playwright      | Vitest browser mode     |
| Storybook version  | 6.4+                   | 10+                     |
| Framework support  | All frameworks         | Vite-based only         |
| TDD Guard reporter | tdd-guard-storybook    | tdd-guard-vitest        |
| Speed              | Slower (full browser)  | Faster                  |

## When to Use Each

**Use `@storybook/addon-vitest` when:**

- You're on Storybook 10+
- You're using a Vite-based framework
- You want faster test execution
- You want to unify your testing setup

**Use `@storybook/test-runner` when:**

- You're on Storybook 6.4-9.x
- You need Webpack-based framework support
- You need full Playwright browser testing

## Troubleshooting

### Tests Not Running

1. Verify Storybook is configured with the Vitest addon
2. Check that your stories have `play` functions (interaction tests)
3. Ensure `tdd-guard-vitest` is in your Vitest reporters

### Results Not Captured

1. Verify `tdd-guard-vitest` is installed and configured
2. Check the project root path is correct in the reporter config
3. Look for `.claude/tdd-guard/data/test.json` after running tests

## Further Reading

- [Storybook Vitest Addon Documentation](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon)
- [Vitest Reporter Configuration](../reporters/vitest/README.md)
- [TDD Guard Configuration](configuration.md)

---

### Validation Model (`docs/validation-model.md`)

# Validation Model Configuration

TDD Guard validates changes using AI. Configure both the validation client (SDK or API) and the Claude model version.

## Claude Agent SDK (Default)

The recommended approach. Uses the Claude Agent SDK to communicate with Claude directly.

```bash
VALIDATION_CLIENT=sdk  # Default, can be omitted
```

**Features:**

- Works automatically with your Claude Code installation
- Uses your Claude Code subscription (no extra charges)
- Requires Claude Code to be installed and authenticated

**Important:** If you have `ANTHROPIC_API_KEY` set in your environment, Claude Code may use it for billing instead of your subscription. To avoid unexpected charges:

```bash
# Check if API key is set
echo $ANTHROPIC_API_KEY

# Unset it if present
unset ANTHROPIC_API_KEY
```

If you've never created an API key, you can ignore this warning.

## Anthropic API

For CI/CD environments or when you need faster validation. Requires separate billing from Claude Code.

```bash
VALIDATION_CLIENT=api
TDD_GUARD_ANTHROPIC_API_KEY=your_api_key_here
```

Get your API key from [console.anthropic.com](https://console.anthropic.com/)

**Notes:**

- Charges separately from your Claude Code subscription ([pricing](https://www.anthropic.com/pricing))
- We use `TDD_GUARD_ANTHROPIC_API_KEY` (not `ANTHROPIC_API_KEY`) to prevent accidental charges. If you used the regular `ANTHROPIC_API_KEY`, Claude Code might use it for all your normal coding tasks, charging your API account instead of using your subscription.

## Model Selection

Configure which Claude model to use for validation (default: `claude-sonnet-4-6`):

```bash
# Fastest but unreliable results
TDD_GUARD_MODEL_VERSION=claude-3-5-haiku-20241022

# Best results but slowest
TDD_GUARD_MODEL_VERSION=claude-opus-4-1
```

See [Claude model overview](https://docs.anthropic.com/en/docs/about-claude/models/overview) for available models and pricing. Note: pricing only applies to API users - SDK uses your Claude Code subscription by default. Balance model capability with [custom instructions](custom-instructions.md) to optimize for your needs.

## Migration from Legacy Configuration

If you're using the old `MODEL_TYPE` configuration, see the [Configuration Migration Guide](config-migration.md) for detailed instructions.

---

### Adr 001 Claude Session Subdirectory (`docs/adr/001-claude-session-subdirectory.md`)

# ADR-001: Execute Claude CLI from Subdirectory for Session Management

## Status

Accepted

## Context

The TDD Guard validation system creates a new Claude session for each validation operation when running `claude` commands. This results in session list clutter and makes it difficult to track the actual development sessions.

We considered two approaches:

1. Clear the context before each validation using `/clear` command
2. Run the Claude CLI from a subdirectory to isolate validation sessions

The first approach has limitations:

- Cannot clear and ask a question in the same command
- Would require multiple command executions
- Still shows all sessions in the same directory listing

## Decision

We will execute all Claude validation commands from a `.claude` subdirectory within the project root.

Implementation details:

- Create `.claude` directory if it doesn't exist
- Set the `cwd` option in `execSync` to the subdirectory path
- The `.claude` directory itself is not ignored (contains settings.json which should be tracked)
- User-specific files like `.claude/settings.local.json` should already be in `.gitignore`

## Consequences

### Positive

- Validation sessions are isolated from development sessions
- No cluttering of the main project's session list
- Automatic trust inheritance from parent directory
- Simple implementation with minimal code changes
- No impact on validation functionality

### Negative

- Creates an additional directory in the project
- Slightly increases complexity in the model client
- Sessions are less visible (though this is mostly a benefit)

### Neutral

- All validation sessions will appear in the `.claude` subdirectory listing
- Developers need to know to look in `.claude` for validation session history

---

### Adr 002 Secure Claude Binary Path (`docs/adr/002-secure-claude-binary-path.md`)

# ADR-002: Secure Claude Binary Path Configuration

## Status

Accepted

## Context

CodeQL security scanning identified a potential command injection vulnerability in `ClaudeModelClient` where the Claude binary path is taken from an environment variable (`CLAUDE_BINARY_PATH`) and interpolated into a shell command executed via `execSync`.

The vulnerability occurs because:

- Environment variables can be manipulated by attackers
- Shell metacharacters in the path could be interpreted, allowing arbitrary command execution
- For example, setting `CLAUDE_BINARY_PATH="claude; rm -rf /"` would execute both commands

We considered several approaches:

1. **Use execFileSync instead of execSync** - Avoids shell interpretation entirely
2. **Validate/sanitize the binary path** - Check for allowed characters only
3. **Use shell-quote library** - Properly escape shell metacharacters
4. **Boolean flag for predefined paths** - Switch between hardcoded safe paths

## Decision

We will use a boolean environment variable `USE_LOCAL_CLAUDE` to switch between two hardcoded, safe paths:

- When `USE_LOCAL_CLAUDE=true`: Use `$HOME/.claude/local/claude`
- Otherwise: Use system `claude` command

Additionally, we will:

- Implement the path logic in `ClaudeModelClient` rather than `Config` class
- Use `execFileSync` instead of `execSync` to prevent shell interpretation
- Keep the Config class focused on just providing the boolean flag

## Consequences

### Positive

- **Eliminates injection risk** - No user-controlled input in command construction
- **Simple and secure** - Only two possible paths, both hardcoded
- **Clear intent** - Boolean flag clearly indicates local vs system Claude
- **Separation of concerns** - Config provides settings, ModelClient handles implementation
- **Future flexibility** - ModelClient can handle OS-specific paths internally

### Negative

- **Less flexible** - Users cannot specify custom installation paths
- **Requires code changes** - Adding new paths requires updating the code
- **Platform-specific paths** - May need adjustment for different operating systems

### Neutral

- Migration from `CLAUDE_BINARY_PATH` to `USE_LOCAL_CLAUDE` for existing users
- Documentation needs to be updated to reflect the new configuration approach

---

### Adr 003 Remove Configurable Data Directory (`docs/adr/003-remove-configurable-data-directory.md`)

# ADR-003: Remove Configurable Data Directory

## Status

Accepted

## Context

A security review identified a potential path traversal vulnerability in TDD Guard where the data directory path is taken from an environment variable (`TDD_DATA_DIR`) and used directly for file system operations without validation.

The vulnerability occurs because:

- Environment variables can be manipulated by attackers
- Path traversal sequences (`../`) in the path could escape the intended directory
- For example, setting `TDD_DATA_DIR="../../../../etc"` would write files to system directories
- The application writes files like `test.txt`, `todo.json`, and `modifications.json` to this directory

We considered several approaches:

1. **Validate and sanitize the path** - Check for `../` sequences and resolve to absolute paths
2. **Restrict to project subdirectories** - Ensure the path stays within the project root
3. **Use a whitelist of allowed paths** - Only allow specific predefined directories
4. **Remove the configuration entirely** - Hardcode the data directory path

## Decision

We will remove the `TDD_DATA_DIR` environment variable and hardcode the data directory path to `.claude/tdd-guard/data` in the Config class.

The implementation will:

- Remove `TDD_DATA_DIR` from environment variable processing
- Hardcode `dataDir` to `.claude/tdd-guard/data` in the Config constructor
- Keep the existing Config class interface unchanged for dependent code
- Remove documentation about `TDD_DATA_DIR` from `.env.example`, README, and CLAUDE.md

## Consequences

### Positive

- **Eliminates path traversal risk** - No user-controlled input for file paths
- **Simpler implementation** - No validation or sanitization code needed
- **Consistent data location** - All TDD Guard data in a predictable location
- **Better security posture** - Follows principle of least privilege
- **No breaking changes for code** - Config class interface remains the same

### Negative

- **Less flexible** - Users cannot customize where TDD Guard stores its data
- **Potential disk space issues** - Users cannot redirect to different drives/partitions
- **Testing limitations** - Integration tests cannot use isolated data directories

### Neutral

- The data stored (test results, todos, modifications) is operational/temporary
- Most users likely never customized this path anyway
- Follows the same security-first approach as ADR-002

---

### Adr 004 Monorepo Architecture (`docs/adr/004-monorepo-architecture.md`)

# ADR-004: Monorepo Architecture for Multi-Language Support

## Status

Accepted

## Context

TDD Guard originally published a single package to both npm and PyPI, with all test framework reporters mixed together in the src directory. This created several problems:

- **Language mixing** - JavaScript and Python code in the same package
- **Publishing complexity** - Single codebase published to multiple package registries
- **Package bloat** - Users installed code for all languages even if using only one
- **Contribution barriers** - Adding new reporters required navigating the entire codebase

We considered several approaches:

1. **Keep monolithic structure** - Continue with mixed languages in one package
2. **Separate repositories** - Create individual repos for each reporter
3. **Monorepo with workspaces** - Keep one repo but separate packages

## Decision

We will restructure TDD Guard as a monorepo using npm workspaces, with each reporter as a separate package.

The new structure:

```
tdd-guard/                  # Main CLI package (npm)
├── src/                    # Core functionality and shared code
└── package.json

reporters/
├── vitest/                 # tdd-guard-vitest package (npm)
│   └── package.json
└── pytest/                 # tdd-guard-pytest package (PyPI)
    └── pyproject.toml
```

Implementation details:

- Main package exports shared functionality (Storage, Config, contracts)
- Each reporter is a standalone package with its own version
- Vitest reporter imports shared code from 'tdd-guard' package
- Python reporter is self-contained (no JavaScript dependencies)

## Consequences

### Positive

- **Clean separation** - Each language has its own package and tooling
- **Smaller packages** - Users only install what they need
- **Independent releases** - Can update reporters without touching others
- **Easier contributions** - Clear boundaries for adding new reporters

### Negative

- **Multiple packages to maintain** - More release overhead
- **Build complexity** - Must ensure correct build order during development

### Neutral

- Users now install two packages (CLI + reporter) instead of one
- Each package has its own documentation and version number

---

### Adr 005 Claude Project Dir Support (`docs/adr/005-claude-project-dir-support.md`)

# ADR-005: Support CLAUDE_PROJECT_DIR for Consistent Data Storage

## Status

Accepted

## Context

TDD Guard stores data in `.claude/tdd-guard/data` relative to the current working directory. This creates issues when:

- Users run commands from subdirectories (e.g., `cd src && npm test`)
- Claude Code executes commands from different locations within a project
- Multiple `.claude` directories are created at different levels

Previously, users had to configure `CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR` in Claude Code to ensure commands always run from the project root. This required additional configuration and restricted how developers could use Claude Code.

Claude Code provides the `CLAUDE_PROJECT_DIR` environment variable that always points to the project root, regardless of where commands are executed. This is part of Claude Code's [security best practices](https://docs.anthropic.com/en/docs/claude-code/hooks#security-best-practices).

## Decision

We will use `CLAUDE_PROJECT_DIR` when available to determine the base path for TDD Guard's data directory.

The implementation:

- Check if `CLAUDE_PROJECT_DIR` is set and valid
- Use it as the base path for `.claude/tdd-guard/data` if available
- Fall back to current working directory if not set
- Apply security validations to prevent path traversal attacks
- Reporter-provided `projectRoot` takes precedence over `CLAUDE_PROJECT_DIR`

Security validations include:

- Validate `CLAUDE_PROJECT_DIR` is an absolute path
- Prevent path traversal by checking for `..` sequences
- Ensure current working directory is within `CLAUDE_PROJECT_DIR`

When validation fails, TDD Guard throws a descriptive error and the operation is blocked, preventing any file system access with invalid paths.

## Consequences

### Positive

- **Consistent data location** - Data is always stored at the project root
- **No user configuration needed** - Works automatically with Claude Code
- **Better developer experience** - Can run commands from any project subdirectory
- **Maintains security** - Path validation prevents directory traversal attacks

### Negative

- **Additional validation code** - Security checks add complexity, but this is centralized in the Config class

### Neutral

- Falls back gracefully when environment variable is not present
- Replaces the need for `CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR` configuration

---

### Adr 006 Phpunit Separate Repository (`docs/adr/006-phpunit-separate-repository.md`)

# ADR-006: Separate Repository for PHPUnit Reporter

## Status

Accepted

## Context

We received a contribution for a PHPUnit reporter that allows PHP developers to use TDD Guard with their PHPUnit test suites. However, when attempting to publish this package to Packagist (the PHP package registry), we encountered a fundamental limitation: Packagist requires the `composer.json` file to be at the root of the repository.

TDD Guard is organized as a monorepo containing:

- The main CLI tool (TypeScript/npm)
- Vitest reporter (TypeScript/npm)
- Pytest reporter (Python/PyPI)
- PHPUnit reporter (PHP/Packagist)

This structure works well for npm (which supports workspaces) and PyPI (which can build from subdirectories), but Packagist does not support monorepos or packages in subdirectories.

### Options Considered

1. **Move composer.json to repository root**
   - Would make the entire TDD Guard project appear as a PHP package
   - Users would download all code (TypeScript, Python, etc.) just to get the PHPUnit reporter
   - Conflicts with the project's primary identity as a CLI tool

2. **Private Packagist subscription**
   - Supports monorepos but requires paid subscription
   - Adds ongoing cost for an open-source project
   - Creates barrier for community adoption

3. **Manual installation instructions**
   - Users would need to configure Composer to use VCS repository
   - Poor developer experience
   - Reduces discoverability and adoption

4. **Separate repository with automated synchronization**
   - Maintains single source of truth in main repository
   - Provides standard Packagist installation experience
   - Can be automated with GitHub Actions

## Decision

We will create a separate repository (`tdd-guard-phpunit`) that mirrors the `reporters/phpunit` directory from the main repository. This mirror will be automatically synchronized using GitHub Actions whenever changes are pushed to the PHPUnit reporter in the main repository.

### Implementation Plan

1. **Initial Setup**
   - Create `tdd-guard-phpunit` repository
   - Use `git subtree split` to extract PHPUnit reporter history
   - Push to new repository maintaining commit history
   - Submit to Packagist for PHP package distribution

2. **Automated Synchronization**
   - GitHub Action triggered on pushes to `reporters/phpunit/**`
   - Uses git subtree to maintain clean history
   - Force pushes to mirror repository to ensure consistency
   - Syncs relevant tags (e.g., `phpunit-v*`)

3. **Clear Communication**
   - Mirror repository README clearly states it's read-only
   - Directs issues and PRs to main repository
   - Explains the monorepo structure and rationale

## Consequences

### Positive

- **Standard installation**: `composer require --dev tdd-guard/phpunit`
- **Packagist compatibility**: Full integration with PHP ecosystem
- **Automated updates**: No manual synchronization needed
- **Clean history**: Git subtree preserves relevant commit history
- **Single source of truth**: All development happens in main repository

### Negative

- **Additional complexity**: Must maintain synchronization workflow
- **Delayed updates**: Packagist updates depend on GitHub Action execution
- **Repository proliferation**: Additional repository to manage
- **Potential confusion**: Contributors might submit PRs to wrong repository

### Security Considerations

- Uses GitHub Personal Access Token (PAT) with minimal required permissions
- Token stored as repository secret, only accessible to repository admins
- Automated workflow reduces human error in synchronization
- Mirror repository can be made explicitly read-only if needed

## Future Considerations

If Packagist adds monorepo support in the future, we could deprecate the mirror repository and publish directly from the main repository. Until then, this approach provides the best balance of maintainability and user experience.

The same pattern could be applied if we need to publish other language-specific packages that don't support monorepos (e.g., RubyGems, CPAN).

---

### Adr 007 Golangci Lint Path Support (`docs/adr/007-golangci-lint-path-support.md`)

# ADR-007: Golangci-lint Path and Working Directory Support

## Status

Proposed

## Context

The golangci-lint integration in TDD Guard faced two specific challenges that required architectural changes to work correctly.

Key problems encountered:

1. **File-based linting limitations** - TDD Guard's default linter interface passes absolute file paths to be linted. When golangci-lint receives individual file paths, it cannot resolve other functions and types in the same Go package, leading to false undefined variable/function errors. golangci-lint is designed to work at the package/directory level, not individual files.

2. **Working directory dependency** - golangci-lint uses the current working directory to locate the project's `go.mod` file for module resolution. This TDD Guard repository is primarily TypeScript with Go test artifacts in subdirectories (`test/artifacts/go/`). When tests run from the project root, golangci-lint fails to find the correct `go.mod` files in the test artifact directories.

We considered several approaches:

1. **Add module resolution to linter interface** - Make the core linter system aware of Go modules and working directories
2. **File-based workarounds** - Continue trying to make golangci-lint work with individual files
3. **Directory-based linting with test isolation** - Use golangci-lint's natural directory mode and isolate tests that need working directory changes
4. **Separate Go project structure** - Move Go artifacts to a different repository

## Decision

We will adapt golangci-lint to work within TDD Guard's file-based linter interface using directory-based execution and test isolation, avoiding changes to the core linter architecture.

Implementation details:

**Directory-based linting:**

- Extract unique directories from provided file paths using `dirname()`
- Pass directories to golangci-lint instead of individual files
- Use `--path-mode=abs` flag to support absolute path arguments
- This allows golangci-lint to resolve Go packages correctly while maintaining the existing linter interface

**Test isolation:**

- Configure golangci-lint tests to run in Vitest's fork pool instead of threads pool
- Use `process.chdir()` in tests with proper cleanup via beforeEach/afterEach hooks
- This allows tests to change working directory to Go test artifacts without affecting other tests

Key changes:

```typescript
// Directory-based argument building
const directories = [...new Set(filePaths.map((file) => dirname(file)))]
const args = [
  'run',
  '--output.json.path=stdout',
  '--path-mode=abs',
  ...directories,
]

// Simplified issue parsing (no path resolution needed)
const issues = results.flatMap(toIssue)
```

**Rationale:** This approach was chosen because the situation (non-Go project testing Go linters) is rare and doesn't warrant adding brittle module resolution logic to the core linter system. The isolated test approach keeps the complexity contained to where it's needed.

## Consequences

### Positive

- **Go language linter support** - TDD Guard now supports the most popular Go linting solution
- **Proper package resolution** - Directory-based linting allows golangci-lint to resolve Go types and functions correctly
- **Alignment with tooling** - Uses golangci-lint as intended (package-level analysis)
- **Comprehensive validation** - Go developers get the same TDD enforcement as other languages
- **Maintainable architecture** - Avoided adding Go-specific complexity to the core linter interface

### Negative

- **Test complexity** - Requires separate Vitest project configuration and fork pool for isolation
- **Different execution model** - Golangci-lint behaves differently than file-based linters (ESLint)
- **Working directory sensitivity** - Tests must manage working directory changes carefully

### Neutral

- **Directory-based results** - May report issues in files not explicitly requested (entire package)

## Security Considerations

The golangci-lint integration performs path manipulation by extracting directories from file paths using `dirname()` and passing them as command line arguments.

---

### Adr 008 Storybook Reporter Design (`docs/adr/008-storybook-reporter-design.md`)

# ADR-008: Storybook Reporter Design

## Status

Accepted

## Context

Storybook interaction tests (stories with `play` functions) provide valuable component test coverage, but TDD Guard didn't recognize these test failures as valid RED-phase evidence. This prevented developers from using TDD workflows with Storybook for component testing.

We needed to design a reporter that:

- Captures test results from Storybook's `@storybook/test-runner`
- Writes results in TDD Guard's standard format
- Handles the unique characteristics of Storybook's test model
- Follows the established patterns from other reporters (Vitest, Jest)

We considered several design decisions:

1. **Test granularity** - Should we report all stories or only those with tests?
2. **Module hierarchy** - How should we group stories into modules?
3. **Reporter lifecycle** - Should we write on each story or accumulate results?
4. **Render failures** - How should we handle stories that crash during render?
5. **Data extraction** - Should we query the page or use provided context?

## Decision

We will implement `tdd-guard-storybook` with the following design:

### Test Granularity

**Only report stories with `play` functions as tests.**

Stories without `play` functions are just renders and don't provide meaningful RED/GREEN signals. This aligns with how other reporters work - they don't report non-test files. TDD Guard requires actual test failures as evidence.

### Module/Test Hierarchy

**One module per story file, multiple stories become tests within that module.**

Structure:

```json
{
  "testModules": [
    {
      "moduleId": "src/Button.stories.tsx",
      "tests": [
        {
          "name": "Primary",
          "fullName": "Button > Primary",
          "state": "passed"
        }
      ]
    }
  ]
}
```

This matches how Vitest/Jest group tests by file. One source file = one module is intuitive and simpler to implement.

### Reporter Lifecycle

**Accumulate results during `postVisit`, write once on `onExit`.**

API:

```typescript
async postVisit(page, context) {
  await reporter.onStoryResult(context)
}
async onExit() {
  await reporter.onComplete()
}
```

This matches the Vitest/Jest pattern, is more efficient (one write vs N writes), provides cleaner separation of concerns, and better handles interruptions.

### Render Failure Handling

**Create synthetic failed test for stories that fail to render.**

A story with a `play` function that crashes during render is still a failing test. This matches VitestReporter pattern for module load failures and provides visibility into all failures, not just interaction test failures.

### Data Extraction Strategy

**Extract minimal data from test-runner context directly, no additional page queries.**

Start simple with what test-runner provides out of the box. Less complexity = fewer failure points. Can enhance later if needed.

### Interruption Handling

**Best-effort save on exit with "interrupted" status.**

Approach:

- Register process exit handler to catch interruptions
- Write accumulated results when possible
- Mark overall status as "interrupted"
- Partial results better than no results

This matches the Vitest/Jest pattern and TDD Guard already handles "interrupted" status.

## Consequences

### Positive

- **Consistent patterns** - Follows established Vitest/Jest reporter patterns
- **Efficient** - Single write operation instead of multiple
- **Complete coverage** - Captures both interaction test failures and render failures
- **Simple API** - Easy to configure with minimal code
- **Robust** - Handles interruptions gracefully
- **TDD-aligned** - Only reports actual tests, not passive renders

### Negative

- **Stories without `play` not tracked** - But these aren't tests, so this is intentional
- **Accumulates in memory** - Could be an issue for extremely large test suites, but matches other reporters
- **Storybook-specific** - Requires `@storybook/test-runner` as peer dependency

### Neutral

- Users need to configure `.storybook/test-runner.ts` with the reporter
- Follows same security validations as other reporters (absolute paths, project root validation)
- Results saved to standard location: `.claude/tdd-guard/data/test.json`

## Implementation Details

### Integration Test Architecture

The Storybook reporter integration tests follow the same real-execution pattern as Jest, Vitest, PHPUnit, Pytest, Go, and Rust reporters.

**Test Flow:**

```
Integration Test
    ↓
Factory (storybook.ts)
    ↓
Copy story files → Write configs → Spawn test-runner
    ↓
@storybook/test-runner (Jest + Playwright)
    ↓
Executes stories → Calls StorybookReporter hooks
    ↓
StorybookReporter.onStoryResult() → Save to FileStorage
    ↓
Factory reads results → Returns to test assertions
```

### Test Artifacts Structure

```
reporters/test/storybook/
├── .storybook/
│   └── main.js                        # Storybook configuration
├── Calculator.js                      # Simple component module
├── single-passing.stories.js          # Story with passing assertions
├── single-failing.stories.js          # Story with failing expect()
└── single-import-error.stories.js     # Story importing non-existent module
```

Stories use JavaScript (`.stories.js`) to match the repository's test file convention (`.test.js`) and reduce complexity.

### Factory Implementation

The `reporters/test/factories/storybook.ts` factory:

1. **Copies test artifacts** - Uses existing `copyTestArtifacts` helper
2. **Generates test-runner config** - Writes `test-runner-jest.config.js` with reporter
3. **Starts Storybook dev server** - Spawns `storybook dev` on dynamically allocated port via `get-port`
4. **Executes test-runner** - Spawns `test-storybook` command via `spawnSync`
5. **Captures results** - Reporter saves to FileStorage, factory reads back

### Key Design Decisions from Implementation

**Test-runner integration:** Uses `@storybook/test-runner` which integrates with Jest's reporter API through `postVisit` and `onExit` hooks. The reporter accumulates results during story execution and writes once on completion.

**Async server startup:** Factory implements timeout-based Storybook server detection by monitoring stdout for "Local:" or port-specific URLs. 240-second timeout accommodates slower container environments.

**Optional reporter execution:** Integration tests use `Promise.allSettled` to make reporters optional, allowing tests to run even when some language-specific reporters aren't installed.

**Error handling:** The reporter captures both interaction test failures and render failures (import errors, component crashes) as synthetic failed tests, ensuring complete coverage of all failure modes.

---

### Adr 009 Relax Reporter Project Root Validation (`docs/adr/009-relax-reporter-project-root-validation.md`)

# ADR-009: Relax Reporter Project Root Path Validation

## Status

Accepted

## Context

TDD Guard reporters write test results to `.claude/tdd-guard/data/test.json` under a configured project root. The project root setting exists because test runners are often invoked from a subdirectory — directly by developers, by Claude Code, and especially in monorepo and workspace setups. Without it, results land relative to the runner's cwd and the validation hook does not find them.

Reporters currently require this path to be **absolute**, and the PHPUnit reporter additionally rejects any path containing `..`. These checks were added early in the project's life, when the agentic-tool ecosystem was still young and there was little established guidance on where defensive layers belonged. Erring on the side of stricter validation was reasonable in that moment.

The ecosystem has since matured. Claude Code and comparable harnesses now provide first-class permission systems and sandboxing; containerized development environments such as devcontainers are a standard isolation pattern; OS-level isolation tooling is widely documented and available. Users running agents on sensitive work are expected to combine these layers as defense-in-depth. Concerns about a tool process writing outside its project are addressed there — at the harness, the container, and the OS — rather than at the application's input validation. TDD Guard's string-level checks on the project root have become atypical for tools at this layer, and the extra caution they represent is now out of step with where these concerns are expected to live.

That extra caution has a real cost in everyday use:

- **Shared repositories** — paths differ between developer machines, so a checked-in absolute path only works for one developer.
- **Docker** — host and container paths do not match.

Issue #139 surfaced this concretely for PHPUnit in a shared/Docker context.

This ADR is **reporter-side only**. The strict validation of `CLAUDE_PROJECT_DIR` in the main `tdd-guard` CLI/hook (ADR-005) is a distinct concern from the user-facing reporter configuration this ADR addresses, and remains unchanged.

## Decision

Reporters will accept relative paths and paths containing `..` for their project root configuration:

- **Drop the absolute-path requirement.** Relative paths are resolved against the reporter process's current working directory using the language's standard path resolution.
- **Drop the `..` rejection** (currently only enforced by PHPUnit).
- **Resolve before validating.** Any remaining checks operate on the resolved absolute path, not on the user-provided string.

This ADR does not change the hardcoded data directory subpath (`.claude/tdd-guard/data`, per ADR-003) or the validation of `CLAUDE_PROJECT_DIR` in the CLI/hook (per ADR-005).

## Out of Scope

The following are deliberately deferred to follow-up work:

- **Environment variable fallback (`TDD_GUARD_PROJECT_ROOT`)** for reporters that do not yet support it.
- **Whether to keep, modify, or remove the cwd-within-root sanity check** — left untouched by this ADR and revisited separately.
- **Loud-vs-silent failure behavior** — pytest, RSpec, and Minitest currently fall back silently when validation fails; whether to make this loud is a separate question.

## Consequences

### Positive

- **Unblocks shared repositories and Docker** — relative paths checked into the repo work regardless of where it is cloned or mounted.
- **Reduces configuration friction** — no per-developer overrides or gitignored entries needed for the project root setting alone.

### Negative

- **Loses the absolute-path requirement as a misconfiguration signal.** A user who passes an unintended path (e.g. a typo) will see results written there silently rather than getting an immediate format error from the reporter.

### Neutral

- **The hardcoded data directory remains.** Reporters still write to `.claude/tdd-guard/data` under the configured project root, with no user-controlled subpath.

## Security Considerations

The hardcoded data directory subpath (per ADR-003) keeps the blast radius narrow: reporters write a single fixed file (`.claude/tdd-guard/data/test.json`), so even if an attacker steers the configured project root, the only thing they can affect is that single file under the chosen directory — they cannot choose a different filename or write to multiple paths. Broader containment of untrusted code execution is handled at the harness, container, and OS layers, where the appropriate primitives now exist.

---

### Adr 010 Standardize Project Root Env Var (`docs/adr/010-standardize-project-root-env-var.md`)

# ADR-010: Standardize TDD_GUARD_PROJECT_ROOT Across All Reporters

## Status

Accepted

## Context

TDD Guard reporters write test results to `.claude/tdd-guard/data/test.json` under a configured project root. The project root must point to a stable location regardless of where the test runner is invoked — otherwise, results land in the wrong directory and the validation hook does not find them.

ADR-009 relaxed the requirement that the project root be an absolute path. This unblocked shared repositories and Docker setups, but relative paths resolve against the process cwd at runtime. When an agent runs tests from a subdirectory, a relative path resolves to the wrong directory.

Not all configuration formats can compute paths at load time. An environment variable sidesteps this limitation — the value is set once per machine or environment and always produces the correct path. Some reporters already support `TDD_GUARD_PROJECT_ROOT`, but the coverage is incomplete.

Defaulting to cwd when no project root is configured is problematic — it silently writes results to the wrong location when tests run from a subdirectory, which is the exact scenario the project root setting was designed to prevent.

## Decision

All reporters will support `TDD_GUARD_PROJECT_ROOT` as an environment variable for configuring the project root. The precedence chain is:

- **Explicit option** (CLI flag, config file parameter, constructor option) takes highest priority.
- **`TDD_GUARD_PROJECT_ROOT` environment variable** is used when no explicit option is provided.
- **Error** when neither is configured. Reporters will not silently default to cwd.

Removing the silent cwd fallback is a breaking change. This is intentional — silent misconfiguration is worse than an upfront error asking the user to set the project root.

Claude Code sets `CLAUDE_PROJECT_DIR` for hook commands, but this variable is not currently available to Bash subprocesses where reporters run. If it becomes available in the future, reporters can read it as an additional fallback between `TDD_GUARD_PROJECT_ROOT` and the error.

This ADR does not change the hardcoded data directory subpath (`.claude/tdd-guard/data`, per ADR-003) or the validation of `CLAUDE_PROJECT_DIR` in the CLI/hook (per ADR-005).

## Out of Scope

- **Whether to keep, modify, or remove the cwd-within-root sanity check** — deferred per ADR-009.
- **Integration with `CLAUDE_PROJECT_DIR`** — dependent on Claude Code exposing it to Bash subprocesses.

## Consequences

### Positive

- **Consistent configuration across all reporters.** One env var works for every language and framework, regardless of config format.
- **Per-machine configuration without repo changes.** Docker, shared repos, and CI each set the env var for their environment.
- **No silent misconfiguration.** Removing the cwd fallback prevents test results from landing in the wrong directory.

### Negative

- **Breaking change for zero-config users.** Users who relied on the cwd default will need to set `TDD_GUARD_PROJECT_ROOT` or pass an explicit option.

### Neutral

- **Existing env var and option users are unaffected.** The explicit option still takes precedence, and reporters that already read `TDD_GUARD_PROJECT_ROOT` see no change in behavior.

---

--- METRICS ---
- Files Extracted: 37
- Estimated Token Budget: ~27964 tokens
- Canonical Reference: https://codewiki.google/github.com/nizos/tdd-guard
