Companion repository for the book The Second Time Is a Bug: A field workbook for engineering with AI coding agents. Everything the book shows runs here:
- Invoicer (
app/), a small invoicing app for freelancers, the running example. - billing-export (
legacy/), a deliberately tangled 2014 exporter for the legacy chapter. - Guards (
guards/), one small script per guard the book builds, and the gate that runs them. - Drills (
drills/), one per agent mistake. Each makes the mistake in a scratch copy and shows the guard catching it. No API key, no model, no network. - A toy agent (
toy-agent/), an agent loop of two dozen lines driven by a scripted fake model. - Templates (
templates/) to copy into your own repository, and an experiment kit (experiment/) for measuring whether the harness pays, with the author's own run records inexperiment/results/.
TypeScript on Node, with zero runtime dependencies and no build step.
Book website: https://secondtimeisabug.com
The code the book quotes, and the messages the guards and drills
print, are kept within 64 columns, the width of the book's printed
page. Guard output wraps with a hanging indent
(guards/lib/wrap.ts).
Requirements
- Node.js 22.18 or later. Node runs the
.tsfiles directly (type stripping is on by default from 22.18). Check withnode --version. - git (the drills make throwaway git repositories in your temp folder).
- For the two browser drills (D13, D14) and
npm run e2e: Chromium for Playwright, installed once withnpx playwright install chromium.
Quickstart
npm install # dev tools only: typescript, @types/node, @playwright/test, axe-core npm test # unit tests and the legacy golden master npm run typecheck # tsc --noEmit npm run gate # every guard, fast ones first; refuses on any failure npm run drill D06 # one drill: the float-money guard catches a parseFloat npx playwright install chromium # once, for drills D13 and D14 npm run drills # all 26 drills and a summary table (a few minutes) npm run e2e # browser and accessibility tests npm start -- --seed-demo # Invoicer on http://127.0.0.1:3000 with a demo client
Without Playwright's Chromium, npm run drills does not run D13 and D14. It says
so and ends with exit code 1, because the run is not complete:
24 of 26 drills behaved as expected; 2 need Chromium (npx playwright install chromium).
Install Chromium and run it again. The runner's own tests, which are not part of
npm test: node --test drills/test/*.test.ts.
npm run gate compares your working tree with HEAD. Outside a git repository the
diff-based guards report "skipped" rather than guessing. In CI, set
GUARD_BASE=origin/main to check a whole branch, as .github/workflows/gate.yml does.
Folder map
| Path | What it is |
|---|---|
app/src/domain/ |
Pure rules: money in integer cents, totals, due dates, reminders, CSV |
app/src/db/ |
node:sqlite storage and SQL migrations with up and down steps |
app/src/http/ |
JSON API on node:http, including /health and /version |
app/src/ui/ |
Server-rendered list page and the static invoice form |
app/src/jobs/ |
Overdue-reminder job and a tiny scheduler |
app/test/, app/e2e/ |
Unit tests (node:test) and browser tests (Playwright, axe-core) |
legacy/ |
billing-export.js and its characterisation (golden master) tests |
guards/ |
The guards, gate.ts, seeded-defects.ts, guard-audit.ts, shared lib/ |
drills/ |
run.ts and one folder per drill (README.md, drill.json, patch or script) |
toy-agent/ |
loop.ts, scripted-model.ts, tools.ts, unattended.ts and scripts |
templates/ |
AGENTS.md starter, incident log, plan, harness review checklist, hooks |
experiment/ |
Chapter 12 protocol, seven tasks with hidden acceptance tests, score.ts |
plans/ |
Where plan and handoff files go (templates/plan.md) |
.github/workflows/gate.yml |
CI: type check, tests and the gate against the branch's base, then browser tests and mutation runs |
.harness/ |
Guard state: test baseline, approved dependencies; at run time, the guard log and test evidence |
AGENTS.md |
This repository's own instruction file, built from the template |
DRILLS.md |
Every drill's commands and real output, generated by npm run drills:docs |
Drills
Run one with npm run drill DNN. Each folder's README.md says what the mistake is,
what you will see, and how to try the same mistake with a real agent.
| ID | Title | Chapter | Card |
|---|---|---|---|
| D01 | Toy loop | 1 | P01 |
| D02 | Incident log | 2 | P02 |
| D03 | Coupling | 3 | P04 |
| D04 | Instructions lint | 3 | P03 |
| D05 | Plan required | 4 | P07 |
| D06 | Float money | 5 | P11 |
| D07 | Gate | 5 | P12 |
| D08 | Layer boundary | 5 | P13 |
| D09 | Deleted test | 6 | P15 |
| D10 | Skipped test | 6 | P14 |
| D11 | Hollow assertion | 6 | P16 |
| D12 | False done | 6 | P17 |
| D13 | Hidden button | 7 | P18 |
| D14 | Accessibility | 7 | P18 |
| D15 | Smoke | 7 | P19 |
| D16 | Seeded defects | 7 | P20 |
| D17 | Destructive command | 8 | P21 |
| D18 | Secret in diff | 8 | P22 |
| D19 | Irreversible migration | 8 | P23 |
| D20 | Phantom package | 9 | P24 |
| D21 | Dependency drift | 9 | P25 |
| D22 | Legacy refactor | 10 | P27 |
| D23 | Diff budget | 10 | P28 |
| D24 | Bounded loop | 11 | P30 |
| D25 | Merge gate | 11 | P29 |
| D26 | Guard audit | 13 | P34 |
How the guards report
Every guard prints name: ok (...) or name: FAILED followed by one entry per problem:
where it is, what is wrong, and what to do next. They are written to be read by an
agent as much as by a person. Exit code 0 means pass, 1 means fail;
destructive-command.ts uses 2 to block, the usual convention for agent hooks. Each
time a guard fires it appends a line to .harness/guard-log.jsonl, which
guards/guard-audit.ts reads to find guards ready to retire.
Network
Nothing here needs the network after npm install, except the optional
node guards/package-check.ts --online, which asks registry.npmjs.org.
Licence
Code: MIT, see LICENSE. The book's text is not part of this repository.