tdd-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tdd_cli-0.1.0/.gitignore +9 -0
- tdd_cli-0.1.0/CHANGELOG.md +42 -0
- tdd_cli-0.1.0/LICENSE +21 -0
- tdd_cli-0.1.0/PKG-INFO +391 -0
- tdd_cli-0.1.0/README.md +363 -0
- tdd_cli-0.1.0/SECURITY.md +29 -0
- tdd_cli-0.1.0/examples/claude-code-hooks/README.md +44 -0
- tdd_cli-0.1.0/examples/claude-code-hooks/bash_hook.py +68 -0
- tdd_cli-0.1.0/examples/claude-code-hooks/stop_hook.py +46 -0
- tdd_cli-0.1.0/examples/plan.md +160 -0
- tdd_cli-0.1.0/examples/skills/tdd-drive/README.md +30 -0
- tdd_cli-0.1.0/examples/skills/tdd-drive/SKILL.md +92 -0
- tdd_cli-0.1.0/examples/skills/tdd-handoff/README.md +43 -0
- tdd_cli-0.1.0/examples/skills/tdd-handoff/SKILL.md +555 -0
- tdd_cli-0.1.0/pyproject.toml +60 -0
- tdd_cli-0.1.0/src/tddcli/__init__.py +6 -0
- tdd_cli-0.1.0/src/tddcli/adapters/__init__.py +48 -0
- tdd_cli-0.1.0/src/tddcli/adapters/base.py +119 -0
- tdd_cli-0.1.0/src/tddcli/adapters/pytest_adapter.py +179 -0
- tdd_cli-0.1.0/src/tddcli/adapters/vitest_adapter.py +170 -0
- tdd_cli-0.1.0/src/tddcli/advance.py +423 -0
- tdd_cli-0.1.0/src/tddcli/cli.py +1043 -0
- tdd_cli-0.1.0/src/tddcli/config.py +255 -0
- tdd_cli-0.1.0/src/tddcli/contract.py +237 -0
- tdd_cli-0.1.0/src/tddcli/envelope.py +96 -0
- tdd_cli-0.1.0/src/tddcli/fleet.py +128 -0
- tdd_cli-0.1.0/src/tddcli/gitutil.py +138 -0
- tdd_cli-0.1.0/src/tddcli/identity.py +82 -0
- tdd_cli-0.1.0/src/tddcli/leases.py +118 -0
- tdd_cli-0.1.0/src/tddcli/ledger.py +433 -0
- tdd_cli-0.1.0/src/tddcli/machine.py +390 -0
- tdd_cli-0.1.0/src/tddcli/render.py +275 -0
- tdd_cli-0.1.0/src/tddcli/snapshot.py +90 -0
- tdd_cli-0.1.0/src/tddcli/staging.py +130 -0
- tdd_cli-0.1.0/tests/conftest.py +155 -0
- tdd_cli-0.1.0/tests/test_artifact_regeneration.py +76 -0
- tdd_cli-0.1.0/tests/test_baseline_integrity.py +148 -0
- tdd_cli-0.1.0/tests/test_config_and_staging.py +166 -0
- tdd_cli-0.1.0/tests/test_config_drift.py +91 -0
- tdd_cli-0.1.0/tests/test_contract.py +139 -0
- tdd_cli-0.1.0/tests/test_doctor_attribution.py +75 -0
- tdd_cli-0.1.0/tests/test_end_to_end.py +310 -0
- tdd_cli-0.1.0/tests/test_example_plan.py +69 -0
- tdd_cli-0.1.0/tests/test_fleet.py +164 -0
- tdd_cli-0.1.0/tests/test_heartbeat.py +175 -0
- tdd_cli-0.1.0/tests/test_init_detection.py +63 -0
- tdd_cli-0.1.0/tests/test_pin_cycles.py +119 -0
- tdd_cli-0.1.0/tests/test_progress.py +151 -0
- tdd_cli-0.1.0/tests/test_project_commands.py +126 -0
- tdd_cli-0.1.0/tests/test_python_env_managers.py +84 -0
- tdd_cli-0.1.0/tests/test_refactor_cycles.py +115 -0
- tdd_cli-0.1.0/tests/test_release_surface.py +146 -0
- tdd_cli-0.1.0/tests/test_run_claim.py +240 -0
- tdd_cli-0.1.0/tests/test_single_project_repo.py +122 -0
- tdd_cli-0.1.0/tests/test_snapshot_and_identity.py +106 -0
- tdd_cli-0.1.0/tests/test_stub_hint.py +102 -0
- tdd_cli-0.1.0/tests/test_vitest_adapter.py +78 -0
- tdd_cli-0.1.0/tests/test_worker_leases.py +185 -0
tdd_cli-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
|
+
and the project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-08-08
|
|
10
|
+
|
|
11
|
+
Initial release.
|
|
12
|
+
|
|
13
|
+
- Ledger-backed TDD process controller: phase is derived from observed test
|
|
14
|
+
execution, never asserted by the caller.
|
|
15
|
+
- Plan contracts in YAML front-matter, hashed at the committed blob.
|
|
16
|
+
- Standard, pin, and contract cycle kinds.
|
|
17
|
+
- pytest and vitest adapters. The pytest adapter runs suites through the
|
|
18
|
+
project's environment manager (`uv.lock`, `poetry.lock`, `Pipfile`,
|
|
19
|
+
`pdm.lock`, or `[tool.poetry]`), falling back to bare `pytest`; an explicit
|
|
20
|
+
`test_command` always wins.
|
|
21
|
+
- Single-project repositories (`root = "."`) and monorepos alike; `tdd init`
|
|
22
|
+
scaffolds the registry from evidence and reports directories it could not
|
|
23
|
+
match instead of guessing.
|
|
24
|
+
- Phase-derived staging and committing; artifact regeneration in separate commits.
|
|
25
|
+
- Machine-wide worker leases so concurrent agents share cores.
|
|
26
|
+
- `tdd fleet`: read-only view of every agent's run on a repository.
|
|
27
|
+
- Fidelity metrics, typed blockers, friction-log rendering.
|
|
28
|
+
- `tdd --version`; every JSON envelope carries `envelope_version`.
|
|
29
|
+
- Ledger schema versioning: older ledgers are migrated forward on open; a
|
|
30
|
+
ledger written by a newer tdd-cli is refused with a clear error.
|
|
31
|
+
- Third-party adapters via the `tddcli.adapters` entry-point group
|
|
32
|
+
(built-in names cannot be shadowed).
|
|
33
|
+
- Windows is refused at startup (`reason: "unsupported_platform"`); use WSL.
|
|
34
|
+
- `docs/harness-integration.md`: the contract for writing a driving skill
|
|
35
|
+
against any harness — the envelope, the closed verb set, and authoring rules.
|
|
36
|
+
- Example Claude Code integrations: Stop + PreToolUse hooks
|
|
37
|
+
(`examples/claude-code-hooks/`), a driving skill (`examples/skills/tdd-drive/`),
|
|
38
|
+
and a plan-hardening skill (`examples/skills/tdd-handoff/`).
|
|
39
|
+
- `examples/plan.md`: a complete example plan exercising every cycle kind and
|
|
40
|
+
the full front-matter vocabulary, registered by the test suite so it cannot
|
|
41
|
+
drift from the contract parser.
|
|
42
|
+
- SECURITY.md documenting the command-execution trust model.
|
tdd_cli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 geuben
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tdd_cli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tdd-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Ledger-backed TDD process controller for autonomous coding agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/geuben/tdd-cli
|
|
6
|
+
Project-URL: Repository, https://github.com/geuben/tdd-cli
|
|
7
|
+
Project-URL: Issues, https://github.com/geuben/tdd-cli/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/geuben/tdd-cli/blob/main/CHANGELOG.md
|
|
9
|
+
Author: geuben
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agents,ai,pytest,tdd,testing,vitest,workflow
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: POSIX
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
+
Classifier: Topic :: Software Development :: Testing
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Requires-Dist: pyyaml>=6.0
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# tdd-cli
|
|
30
|
+
|
|
31
|
+
A ledger-backed TDD process controller for autonomous coding agents.
|
|
32
|
+
|
|
33
|
+
Process state is **derived from observed test execution**, never asserted by the caller.
|
|
34
|
+
There is no command that accepts a phase, and no file an agent can edit to claim progress
|
|
35
|
+
it has not made.
|
|
36
|
+
|
|
37
|
+
Implements [`tdd-cli-prd.md`](./docs/PRD.md). Requirement ids (`R9.14`, `§6.2`) in the source
|
|
38
|
+
refer to that document.
|
|
39
|
+
|
|
40
|
+
## Why
|
|
41
|
+
|
|
42
|
+
An agent instructed to follow TDD will report that it did. The usual ways to hold it to
|
|
43
|
+
that — prompt rules, a checklist, a state file in the worktree — all share one flaw: the
|
|
44
|
+
record of progress is written by the same agent it is meant to constrain. That flaw
|
|
45
|
+
produces four failure classes, reliably: state that is corrupted or edited to claim
|
|
46
|
+
progress never made; "the test failed first" as an unverifiable self-report; runs that
|
|
47
|
+
stop silently mid-plan; and no record comparable across runs, plans, or models.
|
|
48
|
+
|
|
49
|
+
This tool removes the agent from the reporting path. It runs the suites itself, computes
|
|
50
|
+
every phase transition from what the tests observably did, and records the whole run in a
|
|
51
|
+
ledger the agent cannot reach — which is also what makes the friction logs and metrics at
|
|
52
|
+
the end trustworthy.
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
pip install tdd-cli # or: uv tool install tdd-cli
|
|
58
|
+
tdd --help
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
From source:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
uv sync
|
|
65
|
+
uv run tdd --help
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Quick start
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
tdd init # scaffold tdd.toml from detected projects — then review it
|
|
72
|
+
tdd doctor # environment preflight
|
|
73
|
+
tdd plan register tasks/my-plan.md
|
|
74
|
+
tdd run start --plan tasks/my-plan.md
|
|
75
|
+
tdd advance # the only command that changes phase
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Every command emits JSON with a `next_action`. That verb is the single authority on control
|
|
79
|
+
flow — skills describe *how* to do the work and must never contain stopping instructions.
|
|
80
|
+
[`docs/harness-integration.md`](./docs/harness-integration.md) specifies the verb set and how
|
|
81
|
+
to write such a skill; [`examples/skills/tdd-drive/`](./examples/skills/tdd-drive/) is a
|
|
82
|
+
runnable one for Claude Code. Its planning-side counterpart,
|
|
83
|
+
[`examples/skills/tdd-handoff/`](./examples/skills/tdd-handoff/), hardens a
|
|
84
|
+
draft plan and authors its contract before the run starts.
|
|
85
|
+
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
`tdd.toml` at the worktree root. Roots are declared, never discovered by scanning for marker
|
|
89
|
+
files: two projects can share a marker, and directory-listing order must not decide which
|
|
90
|
+
suite runs.
|
|
91
|
+
|
|
92
|
+
A single-project repository declares the worktree root itself:
|
|
93
|
+
|
|
94
|
+
```toml
|
|
95
|
+
[project.app]
|
|
96
|
+
root = "."
|
|
97
|
+
adapter = "pytest"
|
|
98
|
+
test_paths = ["tests/"]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A monorepo declares one project per root:
|
|
102
|
+
|
|
103
|
+
```toml
|
|
104
|
+
[project.backend]
|
|
105
|
+
root = "backend"
|
|
106
|
+
adapter = "pytest"
|
|
107
|
+
test_paths = ["tests/"]
|
|
108
|
+
lint = ["ruff check"]
|
|
109
|
+
typecheck = ["mypy ."]
|
|
110
|
+
|
|
111
|
+
[project.frontend]
|
|
112
|
+
root = "frontend"
|
|
113
|
+
adapter = "vitest"
|
|
114
|
+
test_paths = ["**/__tests__/**", "**/*.test.ts"]
|
|
115
|
+
typecheck = ["tsc --noEmit"]
|
|
116
|
+
|
|
117
|
+
[artifact.openapi]
|
|
118
|
+
path = "schema/openapi.json"
|
|
119
|
+
produced_by = "backend"
|
|
120
|
+
regenerate = "uv run python -m app.export_openapi"
|
|
121
|
+
consumed_by = ["frontend"]
|
|
122
|
+
|
|
123
|
+
[artifact.api_client]
|
|
124
|
+
path = "frontend/generated"
|
|
125
|
+
produced_by = "artifact.openapi" # artifacts chain
|
|
126
|
+
regenerate = "npm --prefix codegen run generate"
|
|
127
|
+
check = "npm --prefix codegen run check"
|
|
128
|
+
consumed_by = ["frontend"]
|
|
129
|
+
generated = true # excluded from authorship accounting
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
A generator that is never hand-edited (`codegen`) is an artifact regeneration command, not a
|
|
133
|
+
project. It has no tests and no cycles.
|
|
134
|
+
|
|
135
|
+
## Sharing cores between concurrent agents
|
|
136
|
+
|
|
137
|
+
Several agents running tdd-cli on one machine (each in its own worktree) face a bad
|
|
138
|
+
trade: a fixed worker count in the test command either oversubscribes the box when
|
|
139
|
+
agents run together or serialises every suite when an agent is alone. Instead, declare
|
|
140
|
+
where the worker count goes and let the tool compute it:
|
|
141
|
+
|
|
142
|
+
```toml
|
|
143
|
+
[project.backend]
|
|
144
|
+
test_command = "uv run pytest -n {workers}"
|
|
145
|
+
|
|
146
|
+
[project.frontend]
|
|
147
|
+
test_command = "npx vitest run --maxWorkers={workers}"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Each suite invocation takes a lease in a machine-wide directory (`~/.cache/tdd-cli/leases`,
|
|
151
|
+
override with `TDD_LEASE_DIR`) held for the duration of the run, and receives
|
|
152
|
+
`max(1, cores // live_leases)` workers: one agent gets the whole machine, four agents get a
|
|
153
|
+
quarter each. Leases whose process has died, or older than a suite could legitimately run,
|
|
154
|
+
are swept — a crash never throttles the machine.
|
|
155
|
+
|
|
156
|
+
`{workers}` is opt-in per project; without it the declared command runs verbatim, but the
|
|
157
|
+
budget is still exported as `TDD_WORKERS` for commands that prefer to read it themselves,
|
|
158
|
+
and the lease is still held so other agents account for the running suite. Set
|
|
159
|
+
`TDD_CORE_BUDGET` to cap the total below `os.cpu_count()` and keep headroom for the agents
|
|
160
|
+
themselves.
|
|
161
|
+
|
|
162
|
+
The split is computed at lease acquisition: an agent arriving mid-run takes the smaller
|
|
163
|
+
share immediately, and the earlier agent's share corrects on its next invocation. Per-file
|
|
164
|
+
collection stays serial — collection is cheap and xdist adds startup cost per file.
|
|
165
|
+
|
|
166
|
+
## Watching every agent at once
|
|
167
|
+
|
|
168
|
+
The ledger is one database per repository, shared by all worktrees, so every agent's
|
|
169
|
+
progress is already in one place. `tdd fleet` reads it:
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
tdd fleet # one line per active run, plus in-flight baselines and executing suites
|
|
173
|
+
tdd fleet --json # the same as a machine envelope
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Each run line carries the worktree, plan, cycle N of M, phase, and the age of the newest
|
|
177
|
+
suite invocation — a stale age is the signal for a wedged agent. Baselines still being
|
|
178
|
+
collected are listed separately, and the worker-lease directory is read (never modified)
|
|
179
|
+
to show how many suites are executing right now and each one's share of the cores.
|
|
180
|
+
|
|
181
|
+
The command is safe to run while agents are mid-run from any worktree on any branch: it
|
|
182
|
+
opens the ledger with SQLite's read-only mode, so it is structurally incapable of creating,
|
|
183
|
+
migrating, or writing the database, and it requires no `tdd.toml`, plan, or active run.
|
|
184
|
+
|
|
185
|
+
## Plan contracts
|
|
186
|
+
|
|
187
|
+
The plan carries its own contract in YAML front-matter, so a planning agent needs no
|
|
188
|
+
integration with this tool. The contract is hashed at the **committed blob**, so editing
|
|
189
|
+
front-matter mid-run raises `plan_blob_changed`.
|
|
190
|
+
|
|
191
|
+
```yaml
|
|
192
|
+
---
|
|
193
|
+
cycles:
|
|
194
|
+
- n: 1
|
|
195
|
+
project: backend
|
|
196
|
+
title: "unmapped exception is not swallowed"
|
|
197
|
+
test: "tests/test_map.py::test_unmapped_is_not_swallowed"
|
|
198
|
+
stub_expected: ["app/exception_map.py"]
|
|
199
|
+
commit_red: "test: unmapped exception is not swallowed"
|
|
200
|
+
commit_green: "feat: domain exception map skeleton"
|
|
201
|
+
- n: 8
|
|
202
|
+
project: backend
|
|
203
|
+
pin_cycle: true # characterisation; passes on arrival by design
|
|
204
|
+
test: "tests/test_keys.py::test_enrol_maps_signature_error_to_422"
|
|
205
|
+
- n: 12
|
|
206
|
+
projects: ["backend", "frontend"]
|
|
207
|
+
contract_cycle: true # breaking change: no intermediate green state
|
|
208
|
+
tests:
|
|
209
|
+
- "backend::tests/test_openapi.py::test_upload_body_schema"
|
|
210
|
+
- "frontend::services/__tests__/upload.test.ts > matches contract"
|
|
211
|
+
annotation_keys: ["literal_detail_handlers_kept"]
|
|
212
|
+
---
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Absent front-matter is legitimate — the run proceeds as `undeclared` with
|
|
216
|
+
`--allow-undeclared`, and fidelity metrics are unavailable. **Malformed** front-matter
|
|
217
|
+
hard-fails registration: it is almost always a defect in the planning process, and that
|
|
218
|
+
signal must surface rather than degrade silently.
|
|
219
|
+
|
|
220
|
+
[`examples/plan.md`](./examples/plan.md) is a complete plan — every cycle kind, the full
|
|
221
|
+
front-matter vocabulary, and the body structure (context, verified repo facts, per-cycle
|
|
222
|
+
expected failures) that lets an agent execute it without conversation context. The test
|
|
223
|
+
suite registers it, so it cannot drift from the contract parser.
|
|
224
|
+
|
|
225
|
+
Producing a plan of that shape is itself a process.
|
|
226
|
+
[`examples/skills/tdd-handoff/`](./examples/skills/tdd-handoff/) is a Claude
|
|
227
|
+
Code skill that takes a draft plan, verifies its claims against the codebase, probes each
|
|
228
|
+
cycle's RED path empirically, assigns cycle kinds, and authors the contract — gated on
|
|
229
|
+
`tdd plan register` succeeding with the intended cycle count and kind breakdown.
|
|
230
|
+
|
|
231
|
+
## The friction log
|
|
232
|
+
|
|
233
|
+
`tdd log render` projects the ledger into a markdown friction log — the feedback channel
|
|
234
|
+
back to the **planning** process. It reports plan fidelity (declared vs delivered vs
|
|
235
|
+
skipped vs never-reached cycles, human interventions) and, per cycle: the target, suite
|
|
236
|
+
runs by phase, the first-run outcome against expectation, sensitivity checks, commits,
|
|
237
|
+
and integrity events.
|
|
238
|
+
|
|
239
|
+
Every observable fact in it is projected from recorded events. The agent that did the
|
|
240
|
+
work cannot compose it — that is what makes it worth reading, and why the log is
|
|
241
|
+
rendered, never written. Judgement enters in exactly two ways:
|
|
242
|
+
|
|
243
|
+
- **Per cycle, through `tdd annotate`** — rendered inline in the cycle it concerns.
|
|
244
|
+
Beyond keys the plan requires via `annotation_keys`, these keys are reserved for
|
|
245
|
+
judgement agents volunteer: `plan_defect`, `friction_note`, `red_expectation`,
|
|
246
|
+
`commit_shape_deviation`, `test_setup_smell`, `unplanned_change`, `new_work_raised`.
|
|
247
|
+
`plan_defect` is the one that matters most: it records where the plan and the codebase
|
|
248
|
+
disagreed, which is precisely what the next plan needs to know.
|
|
249
|
+
- **Per run, as prose appended below the rendered document.** Legitimate and expected —
|
|
250
|
+
post-run narrative (CI failures, patterns noticed) has no cycle to attach to. But it
|
|
251
|
+
is unverified: an auditor should trust the projected sections and read appended
|
|
252
|
+
narrative as the agent's opinion.
|
|
253
|
+
|
|
254
|
+
`tdd metrics` is the quantitative companion: attempts per cycle, RED-first violation
|
|
255
|
+
rate, fidelity, blockers, interventions. Cross-plan aggregates are deliberately labelled
|
|
256
|
+
non-comparable — cycle difficulty varies too much — so compare runs of the same contract
|
|
257
|
+
only (e.g. the same plan executed by two models).
|
|
258
|
+
|
|
259
|
+
The loop closes when the rendered log is committed alongside the plan and read before
|
|
260
|
+
the next plan is written.
|
|
261
|
+
|
|
262
|
+
## Adapters
|
|
263
|
+
|
|
264
|
+
`pytest` and `vitest` are built in. The pytest adapter runs the suite through the
|
|
265
|
+
project's own environment manager, detected from its marker files — `uv.lock`,
|
|
266
|
+
`poetry.lock`, `Pipfile`, `pdm.lock`, or `[tool.poetry]` in `pyproject.toml` — checked at
|
|
267
|
+
the project root first, then the worktree root (workspace layouts keep one lockfile at the
|
|
268
|
+
top). With no marker, the active environment's bare `pytest` runs. An explicit
|
|
269
|
+
`test_command` always wins.
|
|
270
|
+
|
|
271
|
+
Third-party adapters register under the
|
|
272
|
+
`tddcli.adapters` entry-point group:
|
|
273
|
+
|
|
274
|
+
```toml
|
|
275
|
+
[project.entry-points."tddcli.adapters"]
|
|
276
|
+
cargo = "tddcli_cargo:CargoAdapter"
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
The class must implement `tddcli.adapters.base.Adapter`. Built-in names cannot be
|
|
280
|
+
shadowed: a plugin named `pytest` is ignored, so what "observed test execution" means for
|
|
281
|
+
existing configs can never change underneath them.
|
|
282
|
+
|
|
283
|
+
## Platform support
|
|
284
|
+
|
|
285
|
+
Linux and macOS. Windows is refused at startup with `reason: "unsupported_platform"` —
|
|
286
|
+
worker leases and process-liveness checks are POSIX-only. Use WSL.
|
|
287
|
+
|
|
288
|
+
Every JSON envelope carries `envelope_version`; consumers should check it rather than
|
|
289
|
+
assuming the shape is stable across releases. See also [SECURITY.md](./SECURITY.md) for
|
|
290
|
+
the trust model: running `tdd` executes the repository's declared commands.
|
|
291
|
+
|
|
292
|
+
## Cycle kinds
|
|
293
|
+
|
|
294
|
+
**Standard / contract** — `AWAITING_TEST → AWAITING_IMPL → AWAITING_REFACTOR → CLOSED`
|
|
295
|
+
|
|
296
|
+
**Pin** — `AWAITING_PIN → SENSITIVITY_REQUIRED → AWAITING_REFACTOR → CLOSED`
|
|
297
|
+
|
|
298
|
+
A pin characterises existing behaviour before deleting or restructuring it, so its test
|
|
299
|
+
passes on arrival by design and its sensitivity check is mandatory. Pins are excluded from
|
|
300
|
+
the RED-first violation metric; a *standard* cycle that passes on arrival remains a violation
|
|
301
|
+
and is never reclassified as a pin.
|
|
302
|
+
|
|
303
|
+
## What the tool does, that agents do not
|
|
304
|
+
|
|
305
|
+
- **Stages and commits.** The staged set is derived from the phase: RED takes tests and
|
|
306
|
+
declared stubs, GREEN takes the rest. This makes "implementation written during RED" an
|
|
307
|
+
exact, language-independent detection with no source parsing.
|
|
308
|
+
- **Regenerates stale artifacts**, in their own commit, so hand-written and generated changes
|
|
309
|
+
stay separately reviewable.
|
|
310
|
+
- **Resolves executor identity** from the session transcript. It is never an argument.
|
|
311
|
+
- **Runs the close sweep** over the cycle's projects plus anything downstream of an artifact
|
|
312
|
+
it touched — not every project every time.
|
|
313
|
+
|
|
314
|
+
## Commands
|
|
315
|
+
|
|
316
|
+
| Command | Purpose |
|
|
317
|
+
|---|---|
|
|
318
|
+
| `tdd init` / `tdd doctor` | scaffold config; environment preflight |
|
|
319
|
+
| `tdd plan register <path>` | parse and hash the contract |
|
|
320
|
+
| `tdd run start --plan <path>` | capture baselines, resolve executor, open cycle 1 |
|
|
321
|
+
| `tdd status` | position and `next_action`; safe any time |
|
|
322
|
+
| `tdd advance [--retry]` | run suites, compute the transition, commit |
|
|
323
|
+
| `tdd cycle skip --reason` | sanctioned path for a cycle the plan got wrong |
|
|
324
|
+
| `tdd sensitivity begin\|check\|end` | prove a passing test can fail; verify restore |
|
|
325
|
+
| `tdd annotate --key --value` | attach judgement to the current cycle |
|
|
326
|
+
| `tdd blocker --kind --detail` | typed blocker; releases the stop hook |
|
|
327
|
+
| `tdd resume [--unblock --note]` | reconstruct position; human intervention |
|
|
328
|
+
| `tdd log render [--out]` | project the ledger into a friction log |
|
|
329
|
+
| `tdd metrics` | fidelity, attempts, violations, interventions |
|
|
330
|
+
| `tdd fleet [--json]` | all active runs across every worktree; read-only |
|
|
331
|
+
|
|
332
|
+
## Running a long baseline
|
|
333
|
+
|
|
334
|
+
`run start` probes every project's suite before a run exists (R9.5a), and on a real project
|
|
335
|
+
that can take minutes — well past an agent harness's default Bash timeout. If the command
|
|
336
|
+
appears to hang or time out, **do not re-run it**: the probe is still making progress in the
|
|
337
|
+
background, and a second `run start` against the same worktree is refused with
|
|
338
|
+
`reason: "baseline_in_progress"` — retrying on timeout just stacks refusals on top of a
|
|
339
|
+
baseline that was never stuck. In order of preference:
|
|
340
|
+
|
|
341
|
+
1. **Background it.** Run `tdd run start` in the background if your harness supports it. The
|
|
342
|
+
heartbeat (`baseline_captured` / `project_completed` lines on stderr) lands in the task log
|
|
343
|
+
as each project finishes, and most harnesses re-invoke the agent when a backgrounded command
|
|
344
|
+
exits — a real completion callback, with no timeout ceiling.
|
|
345
|
+
2. **Raise the timeout.** Claude Code's Bash tool takes an explicit `timeout` (default 120000ms,
|
|
346
|
+
max 600000ms). A baseline that takes 3–8 minutes fits inside ten.
|
|
347
|
+
3. **Poll.** `tdd progress` (and `tdd status`) report `collecting_baseline` with per-project
|
|
348
|
+
counters and elapsed time while a baseline is in flight, with `next_action.verb ==
|
|
349
|
+
"await_baseline"` — the fallback for an agent that inherited a run it did not start itself.
|
|
350
|
+
|
|
351
|
+
## Storage
|
|
352
|
+
|
|
353
|
+
One SQLite ledger **per repository**, in `~/.local/share/tdd-cli/` (override with
|
|
354
|
+
`TDD_LEDGER_HOME`), keyed by the common git dir. Never inside the worktree, never resolved
|
|
355
|
+
from the current directory, never committed. `worktree_path` is a column, so concurrent runs
|
|
356
|
+
in separate worktrees are isolated without a pruned worktree orphaning its history.
|
|
357
|
+
|
|
358
|
+
## Enforcement boundary
|
|
359
|
+
|
|
360
|
+
The CLI cannot compel an agent — only the harness can.
|
|
361
|
+
|
|
362
|
+
**Hard gates here:** phase is never caller-supplied; a cycle cannot close over a stale
|
|
363
|
+
artifact; a passed-on-arrival cycle cannot close without a verified sensitivity check;
|
|
364
|
+
`advance` refuses an unchanged tree unless `--retry`.
|
|
365
|
+
|
|
366
|
+
**Recorded, never blocked:** non-stub writes during RED, undeclared file touches, scope
|
|
367
|
+
divergence, extra attempts. Prevention rules with edge cases produce false denials, and a
|
|
368
|
+
blocked agent improvises around them — putting it right back in the reporting path the
|
|
369
|
+
tool exists to keep it out of.
|
|
370
|
+
|
|
371
|
+
**Delegated to hooks:** a Stop hook that queries `tdd status` and refuses to let an agent
|
|
372
|
+
stop while a run is live; a Bash hook redirecting bare `pytest`/`vitest` through `tdd advance`.
|
|
373
|
+
Ready-made Claude Code implementations of both live in
|
|
374
|
+
[`examples/claude-code-hooks/`](./examples/claude-code-hooks/).
|
|
375
|
+
|
|
376
|
+
**Delegated to the skill:** how to respond to each `next_action` verb — writing the test,
|
|
377
|
+
the stub, the implementation. [`docs/harness-integration.md`](./docs/harness-integration.md)
|
|
378
|
+
is the contract for writing one against any harness.
|
|
379
|
+
|
|
380
|
+
## Development
|
|
381
|
+
|
|
382
|
+
```sh
|
|
383
|
+
uv run pytest
|
|
384
|
+
uv run ruff check src tests
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
388
|
+
|
|
389
|
+
## License
|
|
390
|
+
|
|
391
|
+
[MIT](./LICENSE)
|