doneguard 0.2.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.
@@ -0,0 +1,5 @@
1
+ __pycache__/
2
+ .pytest_cache/
3
+ .venv/
4
+ dist/
5
+ .ruff_cache/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 doneguard contributors
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.
@@ -0,0 +1,407 @@
1
+ Metadata-Version: 2.5
2
+ Name: doneguard
3
+ Version: 0.2.0
4
+ Summary: Your AI coding agent can't say "done" until the checks pass.
5
+ Project-URL: Homepage, https://github.com/abranchesia/doneguard
6
+ Project-URL: Issues, https://github.com/abranchesia/doneguard/issues
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: ai-agents,aider,claude-code,codex,coding-agent,copilot,cursor,docker,e2e,gemini-cli,harness,hooks,playwright,postgres,verification
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Software Development :: Quality Assurance
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+
21
+ <div align="center">
22
+
23
+ # doneguard
24
+
25
+ **Your AI coding agent can't say "done" until the checks pass.**
26
+
27
+ ![zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)
28
+ ![python](https://img.shields.io/badge/python-3.9%2B-blue)
29
+ ![license](https://img.shields.io/badge/license-MIT-lightgrey)
30
+ ![agents](https://img.shields.io/badge/agents-Claude%20Code%20%C2%B7%20Cursor%20%C2%B7%20Codex%20%C2%B7%20Gemini%20%C2%B7%20Copilot%20%C2%B7%20%2B5-black)
31
+
32
+ <img src="https://raw.githubusercontent.com/abranchesia/doneguard/main/demo/claude.gif" alt="Real Claude Code session: the agent makes total() return a string and tries to stop; doneguard starts a throwaway Postgres, seeds it, runs vitest and Playwright, and blocks it with both failures; the agent fixes the page and the test; doneguard stats shows 1 blocked, 1 fixed" width="820">
33
+
34
+ <sub>A real Claude Code session. The agent can only read and edit files, so it can't run anything itself. When it tries to stop, doneguard starts a throwaway Postgres, seeds it, runs the unit tests and the Playwright tests, and sends back both failures.</sub>
35
+
36
+ </div>
37
+
38
+ Agents love to finish with *"Done! ✅"* while the types are broken and the tests are red. You find out ten minutes later.
39
+
40
+ doneguard is a **stop hook**. When the agent tries to finish, doneguard runs your type checker, linter and *related* tests on the files changed in the session. If anything fails, the errors go straight back to the agent and it keeps working until they pass.
41
+
42
+ - **Tests that need a database?** doneguard starts a throwaway Postgres, MySQL, Redis or MongoDB in Docker, migrates and seeds it, points `DATABASE_URL` at it, runs the tests and removes it. Your real database stays out of it.
43
+ - **Playwright or Cypress in the project?** It runs the end-to-end tests in a real browser and keeps a screenshot and a video of every test in `.doneguard/evidence/`. When one fails, the agent gets the error, the failure screenshot and a snapshot of the page.
44
+
45
+ - [Works with your agent](#works-with-your-agent)
46
+ - [How it works](#how-it-works)
47
+ - [Install](#install)
48
+ - [Add it to an existing project](#add-it-to-an-existing-project) · [Start from zero](#start-a-new-project-from-zero)
49
+ - [Tests that need a database](#tests-that-need-a-database) · [End-to-end tests with evidence](#end-to-end-tests-with-evidence)
50
+ - [Without hooks: CI, git hooks, any agent](#without-hooks-ci-git-hooks-any-agent)
51
+ - [Stats](#stats) · [Day to day](#day-to-day) · [Teams](#teams)
52
+ - [Stacks](#what-init-detects) · [Configuration](#configuration) · [Commands](#commands) · [FAQ](#faq)
53
+
54
+ ## Works with your agent
55
+
56
+ Not using Claude Code? doneguard speaks each agent's own hook format. `doneguard init` hooks into the agents it finds, or name them: `doneguard init cursor codex`.
57
+
58
+ | Agent | How doneguard keeps it working | Status |
59
+ |---|---|---|
60
+ | **Claude Code** | `Stop` hook → `decision: block` | ✅ tested end to end |
61
+ | **Cursor** (IDE and CLI) | `stop` hook → `followup_message` | 🧪 |
62
+ | **OpenAI Codex CLI** | `Stop` hook → `decision: block` | 🧪 |
63
+ | **Gemini CLI** | `AfterAgent` hook → `decision: block` | 🧪 |
64
+ | **GitHub Copilot** (CLI and VS Code) | `agentStop` hook → `decision: block` | 🧪 |
65
+ | **Google Antigravity** | `Stop` hook → `decision: continue` | 🧪 |
66
+ | **Devin Local** (successor of Windsurf Cascade) | `Stop` hook → `decision: block` | 🧪 |
67
+ | **Kiro CLI** | `Stop` hook → `decision: block` | 🧪 |
68
+ | **Aider** | `lint-cmd` → Aider's own fix loop | 🧪 |
69
+ | **OpenCode** | plugin on `session.idle` → new prompt | ⚗️ experimental |
70
+ | **Cline, Windsurf Cascade, anything else** | a rule: *"run `doneguard check` before saying done"* | manual |
71
+
72
+ ✅ the GIF above. 🧪 built from the agent's official hook docs and tested against its documented payloads. ⚗️ experimental. If one misbehaves with your version, please open an issue.
73
+
74
+ Cursor, Copilot and Devin also run the hooks in `.claude/`. When Claude Code is hooked too, `init` doesn't add theirs, so nothing runs twice.
75
+
76
+ ## How it works
77
+
78
+ ```mermaid
79
+ sequenceDiagram
80
+ participant You
81
+ participant Agent as Agent (Claude Code, Cursor, Codex…)
82
+ participant DG as doneguard
83
+ participant Tools as Your tools (tsc, eslint, vitest, pytest…)
84
+ You->>Agent: "Make total() return a formatted string"
85
+ Note over Agent,DG: session starts → doneguard start (marks the time)
86
+ Agent->>Agent: edits src/cart.ts
87
+ Agent->>DG: "Done!" → stop hook → doneguard verify
88
+ DG->>Tools: checks on files changed since the mark
89
+ Note over DG,Tools: tsc → throwaway Postgres + seed → vitest → Playwright
90
+ Tools-->>DG: vitest fails, Playwright fails (screenshot saved)
91
+ DG-->>Agent: not done yet + the errors + the screenshot path
92
+ Agent->>Agent: fixes server.ts and src/cart.test.ts
93
+ Agent->>DG: "Done!" → doneguard verify
94
+ DG->>Tools: same checks again
95
+ Tools-->>DG: all green
96
+ DG-->>Agent: ok
97
+ Agent->>You: Done, for real
98
+ ```
99
+
100
+ 1. **`doneguard init`** looks at your project, writes the checks to `.doneguard.conf` and registers two hooks in your agent's config.
101
+ 2. **Session starts:** the agent runs `doneguard start`, which notes the time.
102
+ 3. **The agent says it's done:** before letting it stop, the agent runs `doneguard verify`.
103
+ 4. **verify** lists the files modified since the mark (respecting `.gitignore`), runs only the checks whose globs match them, and only the tests related to them. If a database is needed, it starts one in Docker first and removes it at the end. End-to-end runs leave their evidence in `.doneguard/evidence/`.
104
+ 5. **Something fails:** the errors go back to the agent in its own format and it keeps working. **Everything passes:** the agent stops and the mark moves forward.
105
+ 6. **Safety valve:** after 3 failed attempts in a row, doneguard lets the agent stop and tells you, so it never loops forever.
106
+
107
+ ## Install
108
+
109
+ ```bash
110
+ uv tool install doneguard
111
+ # or: pipx install doneguard
112
+ doneguard --help
113
+ ```
114
+
115
+ Want to try it first? `uvx doneguard init`, then `uvx doneguard check`, runs it without installing. The agent hooks need it installed, since they call `doneguard` by name.
116
+
117
+ If your shell can't find `doneguard` afterwards, run `uv tool update-shell` (or `pipx ensurepath`) and open a new terminal. The hooks call `doneguard` by name, so it has to be on your `PATH`.
118
+
119
+ Requirements: Python 3.9+ (only to run doneguard itself), macOS or Linux, and the tools your checks call (`tsc`, `ruff`, `cargo`…) installed in the project. git is optional; with it, change detection respects `.gitignore`. Docker is optional, for [test databases](#tests-that-need-a-database). Playwright browsers are optional, for [end-to-end tests](#end-to-end-tests-with-evidence).
120
+
121
+ ## Add it to an existing project
122
+
123
+ <img src="https://raw.githubusercontent.com/abranchesia/doneguard/main/demo/init.gif" alt="doneguard init in an existing TypeScript project with Postgres and Playwright detects tsc, a throwaway Postgres with its migrate and seed scripts, vitest and Playwright, and hooks into Claude Code" width="820">
124
+
125
+ ```bash
126
+ cd your-project
127
+ doneguard init # detects the stack and your agents, writes .doneguard.conf, adds the hooks
128
+ doneguard check src/app.ts # optional: dry-run the checks on any file
129
+ ```
130
+
131
+ Then restart your agent so it loads the hooks, and:
132
+
133
+ 1. **Read `.doneguard.conf`.** One line per check, plain text. Delete what you don't want, add what's missing ([format](#configuration)).
134
+ 2. **Commit `.doneguard.conf`.** Your team and your CI share the same checks.
135
+ 3. **Work as usual.** Old errors elsewhere in the repo don't matter: doneguard only looks at files changed in the session.
136
+
137
+ Using an agent that `init` didn't find (or several)? Name them: `doneguard init codex gemini`, or `doneguard init all`.
138
+
139
+ Already have a `.doneguard.conf`? `init` never rewrites it, but it lists what it detects now and the file doesn't have yet, so upgrading is copy and paste.
140
+
141
+ ## Start a new project from zero
142
+
143
+ In an empty folder there's nothing to detect yet. Run `init` once to install the hooks, scaffold the project, then run `init` again: it fills in the checks as soon as it finds a stack.
144
+
145
+ <img src="https://raw.githubusercontent.com/abranchesia/doneguard/main/demo/new.gif" alt="doneguard init in an empty folder writes 0 checks; after npm init and installing typescript and vitest, init again detects 2 checks" width="820">
146
+
147
+ ```bash
148
+ mkdir my-app && cd my-app
149
+ doneguard init # hooks installed, 0 checks for now
150
+ npm init -y && npm i -D typescript vitest && npx tsc --init
151
+ doneguard init # now detects tsc + vitest
152
+ ```
153
+
154
+ Letting the agent scaffold the project? Tell it: *"once the project is set up, run `doneguard init`"*. The config is read on every stop, so the checks kick in right away.
155
+
156
+ ## Tests that need a database
157
+
158
+ Integration tests usually need a database with the schema and some data in it. When `init` sees that your project talks to one, it adds a line for it right above the tests, at the end of the file:
159
+
160
+ ```text
161
+ *.ts,*.tsx | @package.json | filter | npx --no -- tsc --noEmit --pretty false -p .
162
+ * | . | postgres=postgres:17-alpine | npm run db:migrate && npm run db:seed
163
+ *.js,*.jsx,*.ts,*.tsx | @package.json | files | npx --no -- vitest related --run --passWithNoTests
164
+ ```
165
+
166
+ Right before the first check below it runs, doneguard:
167
+
168
+ 1. **starts a throwaway database** in Docker, on a free port of `127.0.0.1`. Same image as your `docker-compose.yml` when there is one (pgvector and PostGIS included), so the version and extensions match.
169
+ 2. **points your project at it.** For every check below, doneguard overrides `DATABASE_URL`, every variable holding a URL of the same kind in your `.env` files or your shell (`DIRECT_URL`, `POSTGRES_PRISMA_URL`…), and every URL variable your ORM config reads (Prisma's `env("…")`, `process.env.…` in `drizzle.config` and `knexfile`). For Postgres it also sets `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD` and `PGDATABASE`. dotenv, Prisma, Next.js, Vite and pydantic all let a real environment variable win over `.env`.
170
+ 3. **migrates and seeds it** with the command on the line, with `NODE_ENV=test`. A broken migration or seed is reported to the agent as the cause, and the tests after it are not run, so it isn't buried under fifty "relation does not exist" errors. If the command can't run at all, the tests below are skipped rather than run on an empty database.
171
+ 4. **runs the checks below it**, then **removes the container**. If the run is killed, the container still removes itself after 15 minutes.
172
+
173
+ If no check below it runs (only a README changed, or no related tests), no database starts. With the image cached, Postgres is ready in about a second.
174
+
175
+ | Detected by | Database | Environment |
176
+ |---|---|---|
177
+ | `pg`, `postgres`, `psycopg`, `asyncpg`, gem `pg`, Prisma `provider = "postgresql"`, or a `postgres`/`postgis`/`pgvector` image in compose | `postgres` | `DATABASE_URL`, `PG*` |
178
+ | `mysql2`, `mysqlclient`, `pymysql`, Prisma `provider = "mysql"` | `mysql` | `DATABASE_URL` |
179
+ | `redis`, `ioredis`, `bullmq` | `redis` | `REDIS_URL` |
180
+ | `mongodb`, `mongoose`, `pymongo`, `motor`, `mongoid` | `mongo` | `MONGODB_URI`, `MONGO_URL` |
181
+
182
+ The migrate and seed command `init` writes:
183
+
184
+ | Project | Command |
185
+ |---|---|
186
+ | Prisma | `prisma migrate deploy` (or `prisma db push` without migrations), then your `db:seed`/`seed` script or `prisma db seed` |
187
+ | Drizzle | `drizzle-kit push`, then your seed script |
188
+ | Knex | `knex migrate:latest`, then your seed script |
189
+ | Other Node projects | your `db:migrate`/`migrate` and `db:seed`/`seed` scripts |
190
+ | Alembic (when `env.py` reads the environment) | `alembic upgrade head` |
191
+ | Rails | `RAILS_ENV=test bin/rails db:prepare` |
192
+ | Django, Redis, MongoDB | `-` (nothing: Django's test runner builds its own test database) |
193
+
194
+ `init` only writes a migrate and seed command when it can see the connection comes from a variable it redirects: the ORM config reads a URL variable, or, for plain npm scripts, your `.env` files hold a database URL. Otherwise it writes `-` and tells you to fill it in. A config that builds the connection from `DB_HOST`, `DB_USER`… can't be redirected that way, so doneguard never runs a migration through it on its own.
195
+
196
+ **Docker not running?** The database and the checks below it are skipped, never failed, and doneguard says so (Claude Code shows it after the turn). `init` puts these lines last, so your linters and the stacks that don't need a database still run. Your tests don't need a database after all? Delete the line.
197
+
198
+ ## End-to-end tests with evidence
199
+
200
+ <img src="https://raw.githubusercontent.com/abranchesia/doneguard/main/demo/check.gif" alt="doneguard check: tsc passes, a throwaway Postgres starts and is seeded, vitest and Playwright fail, and the output lists the failure screenshot, the page snapshot and the HTML report in .doneguard/evidence; after the fix every check passes" width="820">
201
+
202
+ With `@playwright/test` and a `playwright.config.*` that starts the app (`webServer`), `init` adds an `e2e` line. When the agent changes a page, a component or a stylesheet, doneguard runs the suite in a real browser and keeps everything in your project:
203
+
204
+ ```text
205
+ .doneguard/
206
+ ├── .gitignore # "*": evidence stays out of git unless you delete this
207
+ └── evidence/
208
+ └── 2026-10-30_12-08-37/
209
+ ├── report/ # npx playwright show-report .doneguard/evidence/2026-10-30_12-08-37/report
210
+ └── results/
211
+ └── cart-two-mugs-cost-20-00/
212
+ ├── test-failed-1.png # what the page looked like when it failed
213
+ ├── error-context.md # the page as text, for the agent
214
+ ├── video.webm
215
+ └── trace.zip # npx playwright show-trace …
216
+ ```
217
+
218
+ - **Every test gets a screenshot and a video**, passing or not; failures also get a trace. Your own Playwright config is used as is: doneguard wraps it for the run (a temporary `.doneguard.playwright.config.*` next to yours) only to add the evidence and the output folder.
219
+ - **When a test fails, the agent gets** the error, the paths of the failure screenshot and the page snapshot, and opens them like any other file. Claude Code reads screenshots.
220
+ - **Which server the tests hit:** without a test database, `CI` is unset, like a run in your terminal, so a `webServer` with `reuseExistingServer: !process.env.CI` reuses the dev server you already have running. With a test database, `CI` stays set, so Playwright starts its own server pointed at it; if your dev server holds the port, the check is skipped and doneguard tells you, instead of clicking through your dev database.
221
+ - **The last 10 runs are kept.** Older ones are deleted.
222
+ - **No browsers installed?** The check is skipped until you run `npx playwright install chromium`.
223
+ - **Timeout:** 7 minutes, then the check is skipped.
224
+
225
+ <img src="https://raw.githubusercontent.com/abranchesia/doneguard/main/demo/evidence.png" alt="The failure screenshot Playwright saved: two mugs in the cart, total $10.00 instead of $20.00" width="560">
226
+
227
+ <sub>`test-failed-1.png` from the run above: two mugs in the cart, total $10.00. The agent sees this.</sub>
228
+
229
+ Any other runner works too: put `{evidence}` in the command and doneguard replaces it with that run's folder. pytest-playwright gets one automatically when there is a `tests/e2e` or `e2e` folder. Cypress needs your app running, so `init` suggests a line instead of writing it:
230
+
231
+ ```text
232
+ *.py,*.html,*.css,*.js | . | e2e | pytest -q --tb=short tests/e2e --screenshot on --video on --tracing retain-on-failure --output {evidence}
233
+ *.tsx,*.css | . | e2e | npx --no -- start-server-and-test dev http://localhost:3000 'cypress run --config screenshotsFolder={evidence}/screenshots,videosFolder={evidence}/videos,video=true'
234
+ ```
235
+
236
+ ## Without hooks: CI, git hooks, any agent
237
+
238
+ `doneguard check` runs the same checks on your git changes (or on the files you pass), one progress line per check, and exits 1 on failure. No agent needed. The GIF in [End-to-end tests](#end-to-end-tests-with-evidence) shows it.
239
+
240
+ **GitHub Actions**, on the files changed in the pull request:
241
+
242
+ ```yaml
243
+ on: pull_request
244
+ jobs:
245
+ doneguard:
246
+ runs-on: ubuntu-latest
247
+ steps:
248
+ - uses: actions/checkout@v4
249
+ with:
250
+ fetch-depth: 0
251
+ - run: npm ci # install your project's tools
252
+ - run: npx playwright install --with-deps chromium # if you have e2e tests
253
+ - run: pipx install doneguard
254
+ - run: doneguard check $(git diff --name-only origin/${{ github.base_ref }}...HEAD)
255
+ ```
256
+
257
+ GitHub's Ubuntu runners have Docker, so the test databases work there too.
258
+
259
+ **Git pre-commit hook:**
260
+
261
+ ```bash
262
+ printf '#!/bin/sh\nexec doneguard check $(git diff --cached --name-only)\n' > .git/hooks/pre-commit
263
+ chmod +x .git/hooks/pre-commit
264
+ ```
265
+
266
+ **Agents without a stop hook** (Cline, Windsurf Cascade…): add to `AGENTS.md` or the agent's rules: *"Before saying you're done, run `doneguard check` and fix everything it reports."*
267
+
268
+ ## Stats
269
+
270
+ doneguard keeps a tiny local log of every checked stop. Ask it how many times it caught your agent:
271
+
272
+ ```text
273
+ $ doneguard stats
274
+ doneguard stats since 2026-10-30
275
+ stops checked 2 in 1 session(s)
276
+ "done" blocked 1
277
+ fixed by the agent 1 of 1 (100%)
278
+ most failing vitest (1)
279
+ agents claude (2)
280
+
281
+ Caught 1 broken "done" claim(s); the agents fixed 100% on their own.
282
+ ```
283
+
284
+ That's the real output of the session in the GIF at the top: one stop blocked by Playwright and vitest, fixed on the next try. The log lives in `~/.local/state/doneguard/log.jsonl` (or `$XDG_STATE_HOME`) and stores only times, agent names, results and tool names: no code, no paths. Delete it to start over.
285
+
286
+ ## Day to day
287
+
288
+ - **You don't run anything.** The hooks fire on their own when a session starts and every time the agent tries to stop.
289
+ - **When a check fails**, the agent gets the errors and keeps going. You see it fixing things instead of saying "done".
290
+ - **If it still fails after 3 tries**, the agent stops (Claude Code shows `doneguard: checks still failing after 3 fix attempts`).
291
+ - **After an e2e run**, the screenshots and videos are in `.doneguard/evidence/`, newest last. Open the report with `npx playwright show-report .doneguard/evidence/<run>/report`.
292
+ - **Pause a check** by commenting its line with `#` in `.doneguard.conf`. It takes effect on the next stop.
293
+ - **Check by hand** at any time: `doneguard check`.
294
+
295
+ ## Teams
296
+
297
+ Commit `.doneguard.conf`. Each developer installs doneguard and runs `doneguard init`, which keeps the existing config and adds their hooks.
298
+
299
+ `.doneguard/` ignores itself, so evidence never lands in a commit by accident. To keep evidence in the repo (for review, or as a CI artifact), delete `.doneguard/.gitignore`.
300
+
301
+ Hook files that live in the repo (`.cursor/hooks.json`, `.codex/hooks.json`, `.gemini/settings.json`, `.github/hooks/doneguard.json`…) run for everyone who opens the project with that agent. Commit them only if everyone has doneguard installed: without it, their agent reports a failed hook on every stop. Claude Code's hooks go to `.claude/settings.local.json`, which is personal.
302
+
303
+ ## What `init` detects
304
+
305
+ | Stack | Detected by | Check |
306
+ |---|---|---|
307
+ | TypeScript | `tsconfig.json` | `tsc --noEmit`, errors in changed files only |
308
+ | ESLint / Biome | `eslint` or `@biomejs/biome` in `package.json` | `eslint --quiet` / `biome lint` on changed files |
309
+ | Vitest / Jest | `vitest` or `jest` in `package.json` | `vitest related` / `jest --findRelatedTests` |
310
+ | Playwright | `playwright.config.*` with a `webServer` | the e2e suite, with [evidence](#end-to-end-tests-with-evidence) (Cypress: a suggested line) |
311
+ | Python | `pyproject.toml`, `setup.py`, `requirements.txt` | `ruff` (syntax errors, undefined names), `pytest` on related tests, pytest-playwright e2e |
312
+ | Ruby | `Gemfile` | `rubocop` (errors only), `bin/rails test` or `rspec` on related tests |
313
+ | Go | `go.mod` | `go vet` (findings in changed files), `go test` on the packages with related tests |
314
+ | Rust | `Cargo.toml` | `cargo check`, errors in changed files only |
315
+ | Java / Kotlin | `pom.xml`, `build.gradle*` + `gradlew` | `mvn compile` / `./gradlew classes`, errors in changed files only |
316
+ | C# | `*.sln`, `*.csproj` | `dotnet build`, errors in changed files only |
317
+ | PHP | `composer.json` | `php -l` on changed files, Pest or PHPUnit on related tests |
318
+ | Deno | `deno.json` | `deno check`, `deno test` on related tests |
319
+ | Swift | `Package.swift` | `swift build`, errors in changed files only |
320
+ | Databases | dependencies, Prisma schema, `docker-compose.yml` | a [throwaway Postgres, MySQL, Redis or MongoDB](#tests-that-need-a-database) |
321
+
322
+ It looks at the root and one folder down, so monorepos work: JS checks run once per package (`@package.json`). For Python it uses `uv run` or `.venv/bin/` when it finds them. Anything else is one line of [configuration](#configuration) away.
323
+
324
+ ## Configuration
325
+
326
+ `.doneguard.conf` lives at the root of the project. One check per line, `#` for comments:
327
+
328
+ ```text
329
+ # <globs> | <dir> | <mode> | <command>
330
+ *.ts,*.tsx | @package.json | filter | npx --no -- tsc --noEmit --pretty false -p .
331
+ *.py | . | files | .venv/bin/ruff check --select E9,F63,F7,F82 --output-format concise
332
+ * | . | postgres | .venv/bin/alembic upgrade head
333
+ * | . | redis | -
334
+ *.py | . | tests | .venv/bin/pytest -x -q --tb=short {tests}
335
+ *.go | . | tests | go test {test_dirs}
336
+ *.tsx,*.css | web | e2e | npx --no -- playwright test
337
+ *.sql | . | command | ./scripts/check-migrations.sh
338
+ ```
339
+
340
+ | Field | Meaning |
341
+ |---|---|
342
+ | `globs` | Comma-separated, matched against the path relative to the root. `*` also crosses folders. |
343
+ | `dir` | Where the command runs, relative to the root. `@<file>` means the nearest folder holding `<file>`, so `@package.json` runs once per package in a monorepo. |
344
+ | `mode` | How the result is judged, see below. |
345
+ | `command` | Any shell command. Pipes are fine. |
346
+
347
+ | Mode | Runs | Fails when |
348
+ |---|---|---|
349
+ | `files` | `command <changed files>` | exit code is not 0 |
350
+ | `filter` | `command` as is | an output line cites a changed file (perfect for whole-project tools like `tsc`) |
351
+ | `tests` | `command` with `{tests}` replaced by the related tests (`test_x.py`, `x_test.go`, `x.test.ts`, `x.spec.ts`, `XTest.php`…), or `{test_dirs}` by their folders (`./cart`) | exit code is not 0; skipped when there are no related tests |
352
+ | `command` | `command` as is | exit code is not 0 |
353
+ | `e2e` | `command` with `{evidence}` replaced by a new folder in `.doneguard/evidence/`; `playwright test` gets screenshots, videos and traces there on its own | exit code is not 0; the failure screenshots are listed for the agent |
354
+ | `postgres`, `mysql`, `redis`, `mongo` | a throwaway database in Docker (`postgres=<image>` picks the image), before the first check below it that runs; then `command` (`-` for none) with `DATABASE_URL` and friends pointing at it | the command's exit code is not 0; the checks below it are then skipped |
355
+
356
+ A tool that isn't installed, Docker that isn't running, missing browsers, or a check that takes longer than 150 s (7 minutes for `e2e`) is skipped, never failed.
357
+
358
+ ## Commands
359
+
360
+ | Command | What it does |
361
+ |---|---|
362
+ | `doneguard init [agents…]` | Detect the stack, write `.doneguard.conf` (if it has no checks yet, otherwise list what's new) and add the hooks to the agents found here, or to the ones you name (`all` for every one). Safe to run again. |
363
+ | `doneguard check [files…]` | Run the checks on the given files, or on your git changes, with a progress line per check. Exit 1 on failure. |
364
+ | `doneguard stats` | How many "done" claims were blocked, and how many the agents fixed. |
365
+ | `doneguard start` | Hook: mark the start of a session (reads the event JSON on stdin). |
366
+ | `doneguard verify` | Hook: check the files changed since the mark and answer in the agent's format. |
367
+
368
+ ## FAQ
369
+
370
+ **Do I need Claude Code?** No. See [Works with your agent](#works-with-your-agent). Without any agent, `doneguard check` works in CI and git hooks.
371
+
372
+ **Does it slow the agent down?** It runs once, when the agent tries to stop, and only the checks that match the changed files. Usually a few seconds.
373
+
374
+ **Can it touch my real database?** The migrate and seed command only runs after the throwaway database is up, with `DATABASE_URL`, the `PG*` variables, every same-kind URL from your `.env` files and shell, and the URL variables your ORM config reads all pointing at it. If Docker can't start it, the command doesn't run at all. `init` only writes the command when it can see the connection goes through one of those variables, and e2e tests never reuse your dev server while a test database is up. What doneguard can't redirect is a connection hardcoded in your code: read the database line before you commit it.
375
+
376
+ **Docker is off on my laptop half the time.** Then the database line and the checks below it are skipped, with a note, and everything above it still runs (`init` puts them last). Start Docker Desktop, OrbStack or Colima and the next stop runs them again.
377
+
378
+ **Isn't a database per stop slow?** About a second with the image cached, plus your migrations and seed. It only starts when a check below it actually runs, and it uses `fsync=off` because the data is thrown away anyway.
379
+
380
+ **My e2e suite takes 10 minutes.** Then it doesn't fit in a stop hook: give the `e2e` line narrower globs (only the pages that matter), point it at a smoke subset (`npx --no -- playwright test e2e/smoke`), or keep the full suite in CI with `doneguard check`.
381
+
382
+ **My project already has lint errors.** `filter` checks (tsc, cargo, go vet, mvn, swift) only count errors in changed files. `files` checks lint whole files, so the default ruff rules are fatal errors only: syntax errors and undefined names.
383
+
384
+ **The agent changed a function and broke a file it didn't touch. Will doneguard see it?** Related tests will: `vitest related` and `jest --findRelatedTests` follow imports. Type errors in untouched files are ignored on purpose, so old errors never block anyone.
385
+
386
+ **What if I edit files while the agent works?** Your changes count too: doneguard checks every file modified since the agent last finished, whoever changed it.
387
+
388
+ **Does it catch edits the agent makes through shell commands?** Yes. It compares modification times, so `sed`, codegen and scripts count, not just the edit tool.
389
+
390
+ **Does it send my code anywhere?** No. It runs your own tools on your machine. No network calls, no telemetry (Docker may pull a database image the first time). It's one file: [`doneguard.py`](https://github.com/abranchesia/doneguard/blob/main/doneguard.py).
391
+
392
+ **How do I uninstall?** Remove the `doneguard` entries from your agent's hook config, delete `.doneguard.conf`, then `uv tool uninstall doneguard`.
393
+
394
+ **Windows?** macOS and Linux for now: commands run through `sh`.
395
+
396
+ ## Contributing
397
+
398
+ Small on purpose: one file, standard library only. New stack detections, agent adapters and reports from agents marked 🧪 are welcome.
399
+
400
+ ```bash
401
+ uvx pytest # run the tests
402
+ vhs demo/check.tape # re-record a GIF (needs vhs, npm and Docker; claude.tape also needs Claude Code)
403
+ ```
404
+
405
+ ## License
406
+
407
+ [MIT](https://github.com/abranchesia/doneguard/blob/main/LICENSE)