@jesdi/skills 0.0.29 → 0.0.31

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.
Files changed (74) hide show
  1. package/README.md +61 -13
  2. package/package.json +9 -1
  3. package/skills/implement-spec/ARCHITECTURE.md +113 -0
  4. package/skills/implement-spec/README.md +214 -0
  5. package/skills/implement-spec/SKILL.md +18 -70
  6. package/skills/implement-spec/driver.py +580 -0
  7. package/skills/implement-spec/driver_agents.py +145 -0
  8. package/skills/implement-spec/driver_answers.py +79 -0
  9. package/skills/implement-spec/driver_decisions.py +152 -0
  10. package/skills/implement-spec/driver_final.py +505 -0
  11. package/skills/implement-spec/driver_fixloop.py +170 -0
  12. package/skills/implement-spec/driver_gate.py +47 -0
  13. package/skills/implement-spec/driver_graph.py +42 -0
  14. package/skills/implement-spec/driver_merge.py +105 -0
  15. package/skills/implement-spec/driver_report.py +115 -0
  16. package/skills/implement-spec/driver_review.py +126 -0
  17. package/skills/implement-spec/driver_review_page.py +51 -0
  18. package/skills/implement-spec/driver_run.py +262 -0
  19. package/skills/implement-spec/driver_slots.py +64 -0
  20. package/skills/implement-spec/driver_state.py +105 -0
  21. package/skills/implement-spec/driver_summary.py +40 -0
  22. package/skills/implement-spec/driver_tickets.py +132 -0
  23. package/skills/implement-spec/driver_tidy.py +57 -0
  24. package/skills/implement-spec/models.json +10 -0
  25. package/skills/implement-spec/prompts/conflict.md +32 -0
  26. package/skills/implement-spec/prompts/correction.md +4 -0
  27. package/skills/implement-spec/prompts/correctness.md +34 -0
  28. package/skills/implement-spec/prompts/final-fixer.md +36 -0
  29. package/skills/implement-spec/prompts/final-reviewer.md +36 -0
  30. package/skills/implement-spec/prompts/final-scoped.md +1 -0
  31. package/skills/implement-spec/prompts/fixer.md +35 -0
  32. package/skills/implement-spec/prompts/implementer.md +33 -0
  33. package/skills/implement-spec/prompts/interrupted.md +1 -0
  34. package/skills/implement-spec/prompts/moved.md +2 -0
  35. package/skills/implement-spec/prompts/overruled.md +5 -0
  36. package/skills/implement-spec/prompts/pr-body.md +19 -0
  37. package/skills/implement-spec/prompts/rebase-conflict.md +33 -0
  38. package/skills/implement-spec/prompts/rereview.md +3 -0
  39. package/skills/implement-spec/prompts/rerun.md +2 -0
  40. package/skills/implement-spec/prompts/reviewer.md +34 -0
  41. package/skills/implement-spec/prompts/stands.md +2 -0
  42. package/skills/implement-spec/prompts/test-writer.md +34 -0
  43. package/skills/implement-spec/prompts/timeout-restored.md +2 -0
  44. package/skills/implement-spec/prompts/timeout-shared.md +2 -0
  45. package/skills/implement-spec/prompts/timeout.md +2 -0
  46. package/skills/implement-spec/run.sh +101 -0
  47. package/skills/implement-spec/tests/conftest.py +38 -0
  48. package/skills/implement-spec/tests/test_agents.py +358 -0
  49. package/skills/implement-spec/tests/test_amendments.py +117 -0
  50. package/skills/implement-spec/tests/test_cli.py +220 -0
  51. package/skills/implement-spec/tests/test_decisions.py +434 -0
  52. package/skills/implement-spec/tests/test_driver.py +767 -0
  53. package/skills/implement-spec/tests/test_final.py +442 -0
  54. package/skills/implement-spec/tests/test_fixloop.py +523 -0
  55. package/skills/implement-spec/tests/test_graph.py +32 -0
  56. package/skills/implement-spec/tests/test_landing.py +355 -0
  57. package/skills/implement-spec/tests/test_merge.py +144 -0
  58. package/skills/implement-spec/tests/test_models.py +47 -0
  59. package/skills/implement-spec/tests/test_report.py +162 -0
  60. package/skills/implement-spec/tests/test_review.py +338 -0
  61. package/skills/implement-spec/tests/test_review_page.py +67 -0
  62. package/skills/implement-spec/tests/test_slots.py +32 -0
  63. package/skills/implement-spec/tests/test_state.py +68 -0
  64. package/skills/implement-spec/tests/test_stops.py +272 -0
  65. package/skills/implement-spec/tests/test_summary.py +28 -0
  66. package/skills/implement-spec/tests/test_tickets.py +72 -0
  67. package/skills/implement-spec/tests/test_tidy.py +582 -0
  68. package/skills/implement-spec/tests/test_walk.py +457 -0
  69. package/skills/review-page/SKILL.md +70 -0
  70. package/skills/review-page/schema.md +59 -0
  71. package/skills/review-page/template.html +232 -0
  72. package/skills/to-tickets/SKILL.md +19 -2
  73. package/skills-manifest.json +11 -5
  74. package/skills/implement-spec/history.md +0 -52
package/README.md CHANGED
@@ -49,41 +49,88 @@ npx @jesdi/skills-cli list
49
49
  npx @jesdi/skills-cli uninstall <skill> [--global]
50
50
  npx @jesdi/skills-cli pin <skill> [--global] # keep the installed version
51
51
  npx @jesdi/skills-cli unpin <skill> [--global]
52
- npx @jesdi/skills-cli hook # register the session update hook
52
+ npx @jesdi/skills-cli hook [--off] # turn automatic updates on (or off)
53
+ npx @jesdi/skills-cli session-update # run the session checks now
53
54
  ```
54
55
 
55
56
  ## Automatic updates
56
57
 
57
- A global install registers a session-start hook in Claude Code
58
+ The first global install asks once whether global skills update themselves,
59
+ and `~/.config/my-skills/state.json` records the answer (`autoUpdate`). A
60
+ non-interactive install does not ask and turns nothing on. To change the
61
+ answer later, run `skills-cli hook` (on) or `skills-cli hook --off` (off).
62
+
63
+ With automatic updates on, a session-start hook is registered in Claude Code
58
64
  (`~/.claude/settings.json`) and Codex (`~/.codex/hooks.json`), for each one
59
- that is present. The first session of a day runs
60
- `skills-cli update --all --global` in the background; the result is in
61
- `~/.my-skills/.session-update.log`. Project installs do not change: they stay
65
+ that is present. Every session runs `skills-cli session-update` in the
66
+ background. It checks all external skills on every run and updates own global
67
+ skills once a day. The result is in `~/.my-skills/.session-update.log`.
68
+ Claude and Codex share a lock so concurrent starts cannot change the installs
69
+ at the same time. External checks run before npm updates, whose requests have
70
+ a 30-second deadline. Because the check runs in the background, a repaired skill
71
+ may appear in the next session. Project installs stay
62
72
  at the versions committed in `.my-skills.json`.
63
73
 
74
+ The hook runs the latest CLI release, so a new release needs no new `hook`.
75
+ A hook written by an earlier CLI keeps the version that wrote it: run
76
+ `npx -y @jesdi/skills-cli@latest hook` once to move it to `@latest`.
77
+
64
78
  To keep a skill at its installed version, pin it: `skills-cli pin <skill>
65
- --global`. No update touches a pinned skill until `unpin`. To stop all
66
- automatic updates, remove the hook entry from the two config files; the next
67
- global install or `skills-cli hook` adds it again.
79
+ --global`. No own-skill update touches a pinned skill until `unpin`. To stop all
80
+ automatic updates, run `skills-cli hook --off`: it removes the hook from both
81
+ config files and deletes the script.
68
82
 
69
83
  ## Third-party skills (not vendored)
70
84
 
71
85
  Skills authored by other people are **not** copied into this repo — they keep
72
86
  their own authors, upstreams, and licenses. `external-skills.json` records
73
87
  which ones are part of the standard setup, where they come from, and (under
74
- `pins`) the upstream commit the box set is taken from. Install them from
75
- upstream with the [skills.sh](https://skills.sh/) CLI:
88
+ `pins`) the upstream commits for the standard setup and the box set.
89
+ Session checks fetch this catalogue from this repository's `main` branch at
90
+ most once a day. They compare installed files, including resources and executable
91
+ permissions, with the files at each pinned ref. Missing or different files
92
+ are repaired from upstream or a verified local cache. A failed or invalid
93
+ catalogue refresh uses the last valid cached catalogue and reports the error.
94
+ Ref changes take effect
95
+ even when the version label stays the same. Sources without version labels
96
+ are identified by their commit ref.
97
+
98
+ External installs live in `~/.agents/skills/` for Codex and OpenCode, with links
99
+ in `~/.claude/skills/` when Claude Code is present. Their source, path, ref,
100
+ optional version and content hash are recorded in
101
+ `~/.config/my-skills/external.json`. Upstream attribution and the identities
102
+ of directories created by the updater establish
103
+ ownership. Interrupted swaps retain ownership only of their known directory
104
+ identities; failed replacements roll back uncommitted provenance. Existing
105
+ skills.sh entries from the same
106
+ source can be adopted, including Claude copies with matching content and current
107
+ source attribution. Copies awaiting recovery also retain their directory identity.
108
+ Publication and rollback use the OS's atomic no-replace rename through Koffi:
109
+ [`RENAME_EXCL`](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/APFS_Guide/ToolsandAPIs/ToolsandAPIs.html)
110
+ on macOS, [`RENAME_NOREPLACE`](https://man7.org/linux/man-pages/man2/rename.2.html)
111
+ on Linux, and [`MoveFileExW`](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw)
112
+ without replacement flags on Windows. Unsupported filesystems fail without a
113
+ replacement fallback. If a concurrent install blocks rollback, both copies are
114
+ preserved and the log names the backup location. Current
115
+ conflicting skills.sh provenance and independently replaced Claude directories
116
+ are preserved and reported in
117
+ the log. An enabled Claude frontend-design plugin takes precedence over a
118
+ direct Claude install of that skill; the Codex entry is still installed.
119
+
120
+ For a manual setup, use the [skills.sh](https://skills.sh/) CLI:
76
121
 
77
122
  ```bash
78
- npx skills add mattpocock/skills # grill-me, grill-with-docs, grilling, improve-codebase-architecture, to-questionnaire, tdd, code-review, codebase-design, diagnosing-bugs, resolving-merge-conflicts, handoff, teach, wait-what
123
+ npx skills add mattpocock/skills # grill-me, grill-with-docs, grilling, improve-codebase-architecture, to-questionnaire, tdd, code-review, codebase-design, diagnosing-bugs, resolving-merge-conflicts, pr, handoff, teach, wait-what
79
124
  npx skills add JuliusBrussee/caveman # caveman suite
80
125
  npx skills add vercel-labs/skills # find-skills
81
126
  npx skills add vercel-labs/agent-skills # vercel-react-best-practices
82
127
  npx skills add anthropics/skills # frontend-design (skip if using the Claude Code plugin)
83
128
  ```
84
129
 
85
- The skills.sh lockfile (`~/.agents/.skill-lock.json`) tracks installed
86
- versions; `npx skills update` refreshes them.
130
+ The skills.sh lockfile (`~/.agents/.skill-lock.json`) records its own installs.
131
+ Session checks leave that file in place. `npx skills update` can move an install
132
+ away from the configured pin; the next session check restores the catalogue's
133
+ version.
87
134
 
88
135
  ### Forks (the one exception)
89
136
 
@@ -99,6 +146,7 @@ them afterwards). `external-skills.json` lists them under `forks`.
99
146
 
100
147
  `external-skills.json` → `sets.box` names the skills the agent-ops box
101
148
  receives: `own` from this package, `external` from the pinned upstream.
149
+ `pr` is in the external set and is installed directly from mattpocock/skills.
102
150
  agent-ops vendors that set into its claude-home seed with its own refresh
103
151
  command; this repo's job is to publish the set and keep the pins resolvable.
104
152
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jesdi/skills",
3
- "version": "0.0.29",
3
+ "version": "0.0.31",
4
4
  "description": "jesdi's agent skills (content package consumed by @jesdi/skills-cli)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -13,8 +13,12 @@
13
13
  "skills-manifest.json"
14
14
  ],
15
15
  "devDependencies": {
16
+ "@types/jsdom": "^30.0.0",
16
17
  "@types/node": "^22.0.0",
18
+ "@types/proper-lockfile": "^4.1.4",
19
+ "@vitest/coverage-istanbul": "2.1.9",
17
20
  "gray-matter": "^4.0.3",
21
+ "jsdom": "^30.1.1",
18
22
  "tsx": "^4.19.0",
19
23
  "typescript": "^5.5.0",
20
24
  "vitest": "^2.1.0"
@@ -24,6 +28,10 @@
24
28
  },
25
29
  "scripts": {
26
30
  "test": "vitest run",
31
+ "test:coverage": "vitest run --coverage --coverage.reportOnFailure",
32
+ "test:python": "mkdir -p .crap && python3 -m coverage run -m pytest skills/backlog skills/crap-gate -q && python3 -m coverage run -a -m pytest skills/implement-spec -q && python3 -m coverage json -o .crap/python.json",
33
+ "crap-gate": "sh skills/crap-gate/run.sh",
34
+ "check": "sh scripts/check.sh",
27
35
  "generate-manifest": "tsx scripts/generate-manifest.ts"
28
36
  }
29
37
  }
@@ -0,0 +1,113 @@
1
+ # implement-spec driver: architecture
2
+
3
+ For a person or an agent who changes the driver. `README.md` tells how to use it. The spec is
4
+ `specs/implement-spec-driver/spec.md`; the tests in `tests/` are named after its scenarios.
5
+
6
+ The code has few comments on purpose. A rule that more than one function keeps is written here, one time.
7
+ A comment in the code is for a line that looks wrong or removable without it.
8
+
9
+ ## Modules
10
+
11
+ | Module | It owns |
12
+ |---|---|
13
+ | `driver.py` | The command line, the checks before the first agent, the walk of the ticket graph, one ticket from its phase to merged or blocked, the landing on the PR branch, the exit code |
14
+ | `driver_run.py` | `Run` (what all ticket threads share), one agent with its one rerun, the gate call, the ledger, `Blocked` |
15
+ | `driver_fixloop.py` | A gated ticket tip in, a reviewed tip out: review, fixture correction, fix rounds |
16
+ | `driver_final.py` | The end of run: tidy, rebase, gate, final review, one fix batch, push |
17
+ | `driver_tickets.py`, `driver_graph.py` | Ticket files in, tickets or errors out; which tickets can start now |
18
+ | `driver_agents.py` | Model entries, the CLI command of each provider, prompt templates, the blocking launch |
19
+ | `driver_report.py` | Validation of one agent report against its schema, git and the agent's log |
20
+ | `driver_review.py` | The locked-file check of a ticket's own tests, the routing of findings, mutation proofs |
21
+ | `driver_merge.py` | Merges by git plumbing, the locked-file check of a landing, the move of the PR ref |
22
+ | `driver_gate.py` | One run of the gate command and its record |
23
+ | `driver_slots.py` | The worktrees that tickets run in, and the `git` helper |
24
+ | `driver_state.py` | `state.json` (the resume contract) and the lock of one driver per repository |
25
+ | `driver_decisions.py`, `driver_answers.py`, `driver_review_page.py` | Operator questions, their answers, the page that shows them |
26
+ | `driver_tidy.py`, `driver_summary.py` | The history with one commit per ticket; `summary.md` |
27
+
28
+ `driver.py`, `driver_fixloop.py` and `driver_final.py` decide what comes next. The other modules do one thing
29
+ and do not know about the run, except `driver_run.py`, which the three deciding modules share.
30
+
31
+ ## Conventions
32
+
33
+ - **A step that cannot go on raises `Blocked`.** The message says why. `with during("the merge"):` puts the step
34
+ in front. Only two places catch it for good: `driver._run_ticket` (the ticket becomes BLOCKED) and
35
+ `driver_final.run` (the end of run becomes BLOCKED). Another `except Blocked` only cleans up and raises again.
36
+ - **A check before the first agent returns a list of errors**, so the operator sees all of them at one time
37
+ (exit 3). Examples: `precondition_errors`, `driver_tickets.parse`, `driver_report.validate`.
38
+ - **The modules that do not know about the run return a result, not `Blocked`.** Some return `""` or the reason
39
+ as a string (`driver_merge.refused`, `driver_merge.land`); the deciding module raises.
40
+ - A slot is a worktree. Slot 1 is the PR worktree; the others are in `.agent/worktrees/`.
41
+ - A number of a ticket is a string (`"03"`). `FINAL` is used in its place for the end of run.
42
+
43
+ ## Rules the code keeps
44
+
45
+ ### Trust
46
+
47
+ - An agent report is a claim. `driver_report.validate` accepts it only when git and the agent's log show it.
48
+ The schema of a role is the JSON block of its prompt template, so the agent reads the text the driver enforces.
49
+ - The driver writes no prose of its own into a prompt: a prompt is a template with its pointers filled. The same
50
+ holds for each title, question and ruling in `driver_decisions.py`.
51
+ - The ledger is written only by the driver, and the driver reads no fact from it.
52
+ - A check that git cannot do is a failed check, never a passed one (`locked_changes`, `refused`, `written`).
53
+
54
+ ### Locked tests
55
+
56
+ - The commit of the test-writer is the reference of the ticket's locked files. Before each review, a locked file
57
+ that differs from its reference is a critical finding (`driver_review.locked_findings`).
58
+ - A correction of the test-writer is a new reference. For that reason the driver first puts each locked file back
59
+ to its locked version (`Run.restore_locked_files`), and a correction may change only the files it lists.
60
+ - The locked files of merged tickets are checked when a ticket lands (`driver_merge.refused`): the tree that lands
61
+ must have each of them as the PR tip has it. A ticket's test-writer may change one only when it lists it.
62
+ - A conflict resolution, of a merge or of the rebase, may not change a locked file.
63
+
64
+ ### The PR branch
65
+
66
+ - No slot has the PR branch checked out while tickets run. A merge moves the ref and changes no tree.
67
+ - A landing holds `Run.merging` from the merge commit to the move of the ref. The gate of a merge commit and
68
+ the conflict agent run under that lock, so the commit that the gate saw is the commit that lands.
69
+ - `git update-ref <new> <old>` moves the ref only from the tip the check read. When another hand moved the
70
+ branch, the landing starts again, one time.
71
+ - The gate runs only on a commit the driver is about to trust, and one green record settles that commit.
72
+ A gate on a dirty tree, or one that leaves the tree changed or HEAD moved, is red.
73
+ - The end of run rewrites the branch one time, before the first final review, and never after a review. A history
74
+ that origin has is never rewritten. `refs/implement/recovery` holds the tip before the rewrite.
75
+ - A rewrite never changes a file: the driver compares trees (`driver_tidy.same_tree`) before it moves the branch.
76
+ - The push sends the reviewed commit, not the branch name, with no force.
77
+
78
+ ### Work is never dropped
79
+
80
+ - A role that commits keeps its tree after a timeout or an interrupt; its rerun continues from it.
81
+ - A read-only role (reviewer, correctness, final-reviewer) has no work to keep. When it changes its slot, the
82
+ driver restores the slot and runs the review one more time (`Run.read_only`).
83
+ - A slot with uncommitted work of an interrupted or parked ticket is kept for that ticket (`driver_state.kept`).
84
+ - A blocked ticket keeps its slot when the slot is not clean.
85
+
86
+ ### State and resume
87
+
88
+ - `state.json` is the only input of a resume. Each write is a read-modify-write under one lock, published by a
89
+ rename. Ticket threads share one `State`.
90
+ - When two writes belong together, the order is chosen so that a stop between them is safe. The ledger line of
91
+ a ruling comes before the state change. The record of the tidied tip comes before the move of the branch, and
92
+ `finish_interrupted_move` completes it at the next start.
93
+ - At a resume, git must confirm each recorded tip (`driver_state.verified`). A ticket that git contradicts goes
94
+ back to `implement` from its test-writer commit.
95
+ - The lock of one driver per repository is a kernel lock on a file in the git common directory. The kernel frees
96
+ it when the process ends, so it is never stale.
97
+
98
+ ### Agents
99
+
100
+ - Each agent runs in its own process group. `driver_agents._LIVE` makes three events exclusive: an agent starts,
101
+ an agent is reaped, the run stops. No group gets a signal after its leader is reaped.
102
+ - A role gets one rerun. The rerun prompt is the first prompt, the rejection, then the same ending again.
103
+ A timeout doubles the time of the rerun.
104
+ - Fix rounds stop at 4, and round 3 goes to the next implementer entry. The correctness pass never runs on the
105
+ provider of the implementer.
106
+
107
+ ### Questions
108
+
109
+ - A stop is a bounded question in `state.json`. Only `driver_decisions.stop` adds one. An answered question is
110
+ never open again.
111
+ - An answers file counts only when it is bound to the revision of the current `decisions.json`.
112
+ - Only a question whose kind parks gives exit 2. A third round of the final review is never started: its open
113
+ findings become questions.
@@ -0,0 +1,214 @@
1
+ # implement-spec driver
2
+
3
+ `run.sh` turns a spec and its tickets into one reviewed PR branch. It is a script, not a session: it walks the ticket graph, starts one agent for each role, runs the gate, merges, and stops only at a bounded question or a BLOCKED ticket. [SKILL.md](SKILL.md) only starts it.
4
+
5
+ This file is written from the code. Where it and `specs/implement-spec-driver/` differ, the code is right.
6
+
7
+ ## Run it
8
+
9
+ Run it in the PR worktree, on the PR branch, with a clean tree:
10
+
11
+ ```sh
12
+ sh skills/implement-spec/run.sh --gate 'pnpm check' --spec specs/<slug>/spec.md
13
+ ```
14
+
15
+ Before any agent starts, the driver checks these. Each failed check prints one line, and the run then ends with exit 3.
16
+
17
+ - `.agent/` is gitignored and the worktree has no uncommitted change.
18
+ - Each ticket file `NN-name.md` has a title, a **Blocked by** line and a **Seam** line, and the graph has no cycle (`driver_tickets.py`). A ticket needs one criterion or more, unless its Seam is `none — refactor`. A ticket needs a **Touches** line when the set has more than one ticket. The [to-tickets skill](../to-tickets/SKILL.md) has the line format.
19
+ - Each model entry is valid, and the CLI of the first entry of each role is on PATH. For the correctness role, the entry is the first of its list on another provider than the first implementer entry: that list must have such an entry, and its CLI must be on PATH. This applies to every run, also when no ticket has a **Risk** line.
20
+ - The `review-diff` skill is installed next to this one: the final review of every run reads it, and so does the correctness pass.
21
+ - With `--push` or `--unattended`, the repository has an `origin` remote.
22
+
23
+ A run that stopped is continued with the same command. `state.json` is the only input of a resume.
24
+
25
+ One driver runs in a repository at a time. The driver holds a kernel file lock on `implement-spec.lock` in the git common directory (`git rev-parse --git-common-dir`) for the whole run. Each worktree of the repository shares that directory, so a second driver in the same worktree or in another one ends with exit 3 and names the lock file. The file is not in a worktree, so `git status` never shows it. The lock ends with its process, so a driver that was killed leaves no lock behind. `--dry-run` takes no lock. A wrong call in a worktree that has no `.agent/implement/` takes no lock and makes no file; where an earlier run left that folder, the driver takes the lock first, then makes the checks.
26
+
27
+ ## Flags
28
+
29
+ | Flag | Meaning |
30
+ |---|---|
31
+ | `--gate COMMAND` | Required. The check command of the repository, as one shell line. The driver runs it on each commit it is about to trust, with `IMPLEMENT_SPEC_BASE` set to the PR branch. |
32
+ | `--spec FILE` | Required without `--dry-run`. The spec file the prompts point at. Its first heading is the PR title. |
33
+ | `--tickets DIR` | The directory of the ticket files. Default: `.agent/tickets`. |
34
+ | `--models FILE` | A JSON file with one ordered list of `provider/model@effort` for each role. A role in this file replaces the list of that role in `models.json`. |
35
+ | `--parallel N` | The number of tickets in flight at once. Default: 2. |
36
+ | `--prepare COMMAND` | A shell command that the driver runs once in each new slot 2..N, in `.agent/worktrees/slot-K/`. Use it to install or link the dependencies. |
37
+ | `--timeout ROLE=MINUTES` | The timeout of one role. Repeatable. The rerun after a timeout gets twice the time. |
38
+ | `--answer QUESTION=OPTION` | The answer to one open question. Repeatable. The driver writes it to `.agent/implement-answers.json`. |
39
+ | `--unattended` | Print no question. Write `--stage-json` for the dispatcher, and push without `--push` when the final review has passed. |
40
+ | `--stage-json FILE` | With `--unattended`: the file that says how the run ended. |
41
+ | `--push` | Local mode: when the final review has passed, push the PR branch and open the PR. Without it nothing is pushed. |
42
+ | `--dry-run` | Validate, print `max frontier width: N`, change nothing. |
43
+ | `--runner SCRIPT` | Test only. The driver calls `<script> <role> <prompt> <report>` in the slot, in place of the CLI. |
44
+ | `--only ROLE` | Test only. Run one role (`test-writer`, `implementer`, `reviewer` or `correctness`) on the first ticket, with no gate and no merge. |
45
+ | `--help`, `-h` | Print the flags and exit. |
46
+
47
+ Default timeouts in minutes: explorer 30, test-writer 30, implementer 90, reviewer 45, correctness 30, fixer 60, final-reviewer 60, conflict 30.
48
+
49
+ `run.sh` downloads `uv`, Python 3.13 and `jsonschema` once into `~/.cache/implement-spec`. `IMPLEMENT_SPEC_CACHE_DIR` moves that cache. `IMPLEMENT_SPEC_OFFLINE=1` forbids the download. `run.sh` supports macOS and Linux only, on x86_64 or ARM64, and needs `curl`, `tar` and `sha256sum` or `shasum` for the first download.
50
+
51
+ ## Exit codes
52
+
53
+ | Code | Meaning |
54
+ |---|---|
55
+ | 0 | Done: every ticket is merged and no question is open. Also the end of a valid `--dry-run`. In local mode without `--push`, nothing is pushed. |
56
+ | 1 | A ticket is BLOCKED, or the end of run is BLOCKED. The other tickets ran as far as they could. |
57
+ | 2 | Parked: a question of kind `locked-test` or `final-finding` is open. Exit 2 wins over exit 1. |
58
+ | 3 | Invalid input or a failed precondition: a usage error, a ticket error, a model error, an uncommitted change, a slot that cannot be made, another driver that runs in the same repository. No agent starts. The one exception: a review page that cannot be written gives exit 3 at the end of a run, after agents ran. |
59
+ | 130 | The operator interrupted the run. The driver stops each agent. The work stays in the slots for the next run. |
60
+
61
+ With `--unattended`, the `--stage-json` file holds `{"stage": "implement", "status": …}`:
62
+
63
+ | Exit | `status` | Other fields |
64
+ |---|---|---|
65
+ | 2 | `awaiting-review` | `artifact` (the review page), `open_questions` (a count) |
66
+ | 1 | `blocked` | `blocked` (ticket numbers, and `final` for the end of run) |
67
+ | 0, pushed | `done` | none |
68
+
69
+ ## Layout
70
+
71
+ Everything is under `.agent/`, which is never committed.
72
+
73
+ | Path | Content |
74
+ |---|---|
75
+ | `.agent/implement/state.json` | The resume contract: the run record and one record for each ticket (`driver_state.py`). |
76
+ | `<git common directory>/implement-spec.lock` | An empty file, not in the worktree. The driver that runs holds a kernel file lock on it (`driver_state.py` `lock`). Do not remove it during a run. |
77
+ | `.agent/implement/ledger.md` | One plain sentence for each event. Only the driver writes it, and it reads no fact from it. |
78
+ | `.agent/implement/summary.md` | The result in sentences, and the measurement table: gates, agents, wall time, fix rounds, human stops, cost. |
79
+ | `.agent/implement/decisions.json` | The open questions and their `revision`. Written only after a stop. |
80
+ | `.agent/implement/<NN>/` | One folder for each ticket. |
81
+ | `.agent/implement/<NN>/<role>-<K>.json`, `.log`, `.prompt.md` | The report, the log and the prompt of one agent. K is the fix round. |
82
+ | `.agent/implement/<NN>/gate-<sha7>.log` | One gate record: start, commit, status, command, output, exit, the state after the command, end. |
83
+ | `.agent/implement/<NN>/findings-<K>.json` | The merged findings of the review after fix round K. |
84
+ | `.agent/implement/final/` | The same files for the end of run, and `pr-body.md`. |
85
+ | `.agent/implement/pycache/` | Compiled Python of the gate, kept out of the slots. |
86
+ | `.agent/implement-review.html` | The page with the open questions. |
87
+ | `.agent/implement-answers.json` | The answers: `{"v": 1, "stage": "implement", "submitted": …, "revision": …, "answers": {…}}`. |
88
+ | `.agent/worktrees/slot-K/` | Slots 2..N. Slot 1 is the PR worktree. |
89
+ | `refs/implement/recovery` | A git ref: the PR tip before the tidy. |
90
+
91
+ ## Roles of one ticket
92
+
93
+ A ticket is ready when each ticket in its **Blocked by** line is merged and no running ticket touches the same place. The places are the backticked entries of the **Touches** lines: `file` or `file: function`. Two entries are the same place when they name the same file and the same function, or when one of them names the whole file. Each ready ticket takes the lowest free slot.
94
+
95
+ 1. **test-writer** (`prompts/test-writer.md`). It writes one black-box test for each criterion through the Seam and commits them red. Those files are locked. A ticket with Seam `none — refactor` has no test-writer.
96
+ 2. **implementer** (`prompts/implementer.md`). It starts from that commit and makes the locked tests green.
97
+ 3. **Gate.** The driver runs `--gate` on the ticket tip.
98
+ 4. **Review.** The driver compares each locked file of the ticket with the test-writer commit. A file that differs is a critical finding `locked file changed: <path>`. Then the **reviewer** (`prompts/reviewer.md`) runs, and for a ticket with a **Risk** line the **correctness** pass (`prompts/correctness.md`) runs at the same time in the same slot.
99
+ 5. **Merge.** The driver merges the reviewed tip into the PR branch with git plumbing, then deletes the ticket branch. A conflict goes to the **conflict** agent (`prompts/conflict.md`) once. Its resolution may change no locked file of the ticket. A second conflict blocks the ticket.
100
+ **The landing check.** Merges are serial. Before the PR branch moves, the driver compares the tree that is about to land (the ticket tip of a fast-forward, or the merge commit) with the PR tip: each locked file of each merged ticket must be the same tree entry in both: the same blob with the same mode, so a file that became a symlink is a difference. A file that only one of them has is a difference. A file that the landing ticket lists in its own `locked` is not checked: the test-writer of a later ticket can add a test to a file that an earlier ticket locked, and must list that file. A test-writer report that changes such a file and does not list it is rejected, with the usual one rerun. A difference refuses the landing: the ticket is BLOCKED with `locked file changed: <path>` in the ledger, the PR branch does not move, and no fix round runs. The PR branch moves only from the tip that the check read; when another hand moved the branch in between, the driver makes the merge, the check and the gate again, one time. Only that case starts the landing again: a refused landing and a red merge gate block the ticket. The second try runs no conflict agent, so a conflict there blocks the ticket, and so does a branch that moved again.
101
+ **Before a correction.** A test-writer that corrects locked tests (after `overrule-test`, or for a fixture finding) starts from the locked versions: the driver puts back, in the tree, each locked file of the ticket and each locked file of a merged ticket that the ticket changed, and the ledger names each file. A file of a merged ticket goes back to the version of the PR branch.
102
+
103
+ Rules for each agent:
104
+
105
+ - A call blocks until the agent ends. The driver does not poll.
106
+ - An agent gets one rerun: after a rejected report, after a timeout, or after it left the ticket branch. The rerun prompt is the first prompt plus the reason.
107
+ - A report with status `BLOCKED` or `NEEDS_CONTEXT` blocks the ticket.
108
+ - A BLOCKED ticket stops the tickets that it blocks. All other tickets run on.
109
+
110
+ ### Findings and fix rounds
111
+
112
+ | Finding | What the driver does |
113
+ |---|---|
114
+ | kind `fixture`, any severity | One test-writer correction, outside the fix rounds. The next review is a full review. |
115
+ | `critical` or `important` | One fix round. |
116
+ | `minor` | It stays in the ledger and in the findings file. The final reviewer gets the list. |
117
+
118
+ A fix round is one **fixer** (`prompts/fixer.md`), then the gate on the new tip, then a re-review of the range of the fix. A mutation proof is repeated only when the fix changed its production line.
119
+
120
+ - Rounds 1 and 2 run on the first entry of the implementer list.
121
+ - Rounds 3 and 4 run on the second entry of the implementer list. When the list has one entry, they stay on it and the ledger says so.
122
+ - There is no ruling at round 2.
123
+ - Findings that are open after round 4 make the ticket BLOCKED. The run ends with exit 1, and the driver writes one question of kind `finding` for each.
124
+
125
+ ### End of run
126
+
127
+ The end of run starts only when every ticket is merged and every slot is idle. Its agents work in slot 1.
128
+
129
+ 1. **Tidy.** The driver saves the PR tip in `refs/implement/recovery`. It then makes one commit for each ticket, in merge order, named `feat(NN): <title>`. Each commit holds the tests, the implementation and the fixes of its ticket. The tree of the new tip must equal the tree of the old tip, or the branch stays as it was.
130
+ 2. **Rebase** onto the fetched tip of the default branch of `origin`. A conflict goes to the conflict agent (`prompts/rebase-conflict.md`) once, and the rebased tip is gated.
131
+ 3. **Gate**, then the **final-reviewer** (`prompts/final-reviewer.md`) on the whole range, with the method of the `review-diff` skill. It gets the minor findings of the tickets and the rulings of the operator.
132
+ 4. **Fix batch.** Critical and important findings go to one fixer (`prompts/final-fixer.md`). The driver names each of its commits `fix(NN): …` from the ticket in the report. Then the gate, then one scoped review of those commits.
133
+ 5. Findings that are open after the second review become questions of kind `final-finding`. There is no third review.
134
+ 6. **Push.** With `--push` or `--unattended`, the driver pushes the reviewed tip once, with no force, and opens the PR with `gh`, ready for review. Without them it prints the two tips and the command to run again with `--push`.
135
+
136
+ History rules:
137
+
138
+ - The branch is rewritten once, before the first review, and never after it. Fixes of the final review stay as `fix(NN)` commits.
139
+ - A history that `origin` already has is never rewritten. When `origin` has the PR branch or one of its commits, the driver does no tidy and no rebase. The gate, the final review and a fast-forward push still run.
140
+ - The push happens only when the branch tip is the reviewed tip and that tip has a green gate record.
141
+
142
+ A repository with no `origin` remote skips only step 2 and step 6. The tidied commits stay on the commit that the PR branch had when the run started, and the review range starts there. One ledger sentence says that the rebase and the push were skipped. The driver prints the two tips and ends with exit 0. When you add `origin` after such an end of run, `--push` does not push: the branch is on no base of that remote, so the end of run is BLOCKED.
143
+
144
+ ### Resume
145
+
146
+ - A green gate record is settled. The driver never runs the gate again for that commit.
147
+ - A ticket whose recorded tip is not on its branch goes back to `implement`, from the test-writer commit. A merged ticket whose tip is not in the PR branch does the same.
148
+ - A slot with the uncommitted work of an interrupted role stays as it is. The role runs there again and is told so.
149
+
150
+ ## Models
151
+
152
+ `models.json` holds the default list for each role. An entry is `provider/model@effort`; an entry with no provider is `anthropic`.
153
+
154
+ | Provider | CLI | Efforts |
155
+ |---|---|---|
156
+ | `anthropic` | `claude -p` | low, medium, high, xhigh, max |
157
+ | `openai` | `codex exec` | minimal, low, medium, high, xhigh |
158
+
159
+ The driver uses the first entry of a role. The fixer of a ticket runs on the `implementer` list; the `fixer` list is only for the fix batch of the final review. The driver uses a later entry in two cases only: fix rounds 3 and 4 (the implementer list), and the correctness pass, which takes the first correctness entry of another provider than the first implementer entry. The reviewer never does the correctness pass: when the list has no such entry, or the CLI of that entry is not on PATH, the run ends with exit 3 before any agent. The `explorer` list is read and checked, but the driver starts no explorer.
160
+
161
+ The defaults need both `claude` and `codex` on PATH. Read "Known limits" before you use them as they are.
162
+
163
+ ## Report schema per role
164
+
165
+ Each agent writes one JSON report. The schema of a role is the `json` block of its prompt template, so the agent reads the text that the driver enforces. `driver_report.py` `validate` checks the report against the schema, against git and against the log of the agent. A field that the schema does not name is ignored.
166
+
167
+ Every report has `status` (`DONE`, `DONE_WITH_CONCERNS`, `NEEDS_CONTEXT`, `BLOCKED`), `commits`, `files` and `concerns`.
168
+
169
+ | Role | Template | More fields | Checks after the schema |
170
+ |---|---|---|---|
171
+ | test-writer | `prompts/test-writer.md` | `locked` (paths), `red` (`test`, `line`) | With DONE: one commit, one locked file and one red line or more. Each locked path is in a commit that the role made. Each `line` is in the log. A locked file of a merged ticket that the role changed is in `locked`. |
172
+ | implementer | `prompts/implementer.md` | `green` | With DONE: one commit or more. |
173
+ | reviewer | `prompts/reviewer.md` | `findings` (`severity`, `file`, `line`, `title`, `evidence`, `kind`), `mutations` (`test`, `file`, `line`, `red`) | In the first review of a tip: `mutations` is required, with a proof for each locked test file. Each `red` is in the log. |
174
+ | correctness | `prompts/correctness.md` | the same as the reviewer | `mutations` is optional. |
175
+ | fixer | `prompts/fixer.md` | `green`, `ticket` | With DONE: one commit or more. |
176
+ | final-reviewer | `prompts/final-reviewer.md` | `findings` | None. |
177
+ | fixer of the final review | `prompts/final-fixer.md` | `green`; `commits` is a list of (`sha`, `ticket`) | Each new commit on the branch is in `commits`, with a ticket of the run. |
178
+ | conflict | `prompts/conflict.md`, `prompts/rebase-conflict.md` | none | With DONE: one commit or more. No locked file may change. |
179
+
180
+ For each role, every commit in `commits` must be new since the role started and on its branch. `severity` is `critical`, `important` or `minor`. `kind` is `code`, `test` or `fixture`. A read-only `codex` agent cannot write a file, so it gives the JSON as its last message and the CLI saves it as the report.
181
+
182
+ ## Question kinds
183
+
184
+ A question is a bounded choice for the operator. `driver_decisions.py` `KINDS` holds each kind with its fixed sentences.
185
+
186
+ | Kind | Question id | Asked when | Run exit | Options |
187
+ |---|---|---|---|---|
188
+ | `locked-test` | `<NN>-locked-test-<n>` | An implementer reports BLOCKED with a concern that starts with `locked test contradicts spec:`. | 2 | `overrule-test`: the test-writer corrects the test, then the implementer runs again. `overrule-implementer` (recommended): the implementer runs again. |
189
+ | `finding` | `<NN>-finding-<n>` | A finding is open after fix round 4. One question for each finding. | 1 | `accept`: close the finding; the ticket merges when each of its findings is accepted. `split` (recommended) and `redesign`: the ticket stays BLOCKED, with the ruling in the ledger. |
190
+ | `final-finding` | `final-finding-<n>` | A finding is open after the second round of the final review. | 2 | `fix` (recommended): the finding stays open and nothing is pushed. `accept`: close the finding. |
191
+
192
+ `n` counts the questions of that ticket and kind in the run, so an id is never used twice.
193
+
194
+ Answer with `--answer <question id>=<option id>` on the next run, or through the page, which gives `.agent/implement-answers.json`. An answers file counts only when it is submitted and its `revision` equals the `revision` of `decisions.json`. The driver ignores any other file or answer and says so in the ledger. Each answer becomes one `Ruling:` sentence in the ledger.
195
+
196
+ ## The five non-negotiable checks
197
+
198
+ | Check | Where it lives |
199
+ |---|---|
200
+ | Locked tests with a demonstrated red | `driver_report.py` `_proof_errors`: each red line of the test-writer report must be in its log. `driver_review.py` `locked_findings`: a locked file of the ticket that differs from the test-writer commit is a critical finding, before each review. `driver_merge.py` `refused`, called from `driver.py` `_checked_landing` under the merge lock: a locked file of a merged ticket that differs between the PR tip and the tree that is about to land refuses the landing, so a later ticket cannot change the locked test of an earlier one. `driver_report.py` `_merged_errors` rejects a test-writer report that changes such a file and does not list it. |
201
+ | One mutation proof for each locked test | `prompts/reviewer.md` asks for one proof for each locked test. `driver_report.py` `_locked_errors` and `_proof_errors` reject a first review with no proof for a locked test file, or with a red line that is not in the log. `driver_review.py` `follow` finds the proofs that a fix makes due again. |
202
+ | One independent correctness pass for each risky ticket | `driver_fixloop.py` `_review` adds the pass for a ticket with a **Risk** line. `driver_agents.py` `correctness_entry` picks the entry of another provider, and `load_models` stops the run when there is none or its CLI is not on PATH. |
203
+ | One full gate for each candidate | `driver_gate.py` `run`, called from `driver_run.py` `Run.gate`: one record for each commit the driver is about to trust. Green is exit 0 on a tree that is clean before and after, with HEAD not moved. |
204
+ | Post-merge check on the PR branch | `driver.py` `_checked_landing`: a merge commit is gated before the PR branch moves to it. A fast-forward of a gated tip uses the record of that tip. `driver_final.py` `_review_until_reviewed` gates the tip again after the tidy and after the fix batch. |
205
+
206
+ The driver enforces the mutation proof for each locked test file, not for each locked test.
207
+
208
+ ## Known limits
209
+
210
+ - **A `codex` agent cannot commit.** A role that commits (test-writer, implementer, fixer, conflict) on an `openai` entry runs with `--sandbox workspace-write`, and that sandbox cannot run `git commit`. In the default `models.json` this is the second implementer entry, so fix rounds 3 and 4 fail. What to do: give a `--models` file with no `openai` entry in the `implementer`, `test-writer`, `fixer` and `conflict` lists.
211
+ - **A `codex` reviewer cannot make a mutation proof.** A reviewer on an `openai` entry runs with `--sandbox read-only` and cannot make the temporary edit. The default `models.json` has such an entry first for the reviewer, and the first review needs the proofs. What to do: give a `--models` file whose reviewer list starts with an `anthropic` entry.
212
+ - **The correctness pass reads a tree that the reviewer changes.** The two agents share one slot, and the reviewer makes temporary edits. `prompts/correctness.md` tells the pass to read from git at the reviewed commit and to run no tests. That sentence is the only guard: the driver cannot enforce it, so a pass that ignores it can report a false finding and start a fix round. What to do: when a correctness finding looks wrong, compare it with `git show <reviewed commit>:<file>` before you rule on it.
213
+ - **A ticket that is not merged stops the end of run.** While one ticket is BLOCKED, there is no tidy and no final review of the merged tickets.
214
+ - **A published PR branch keeps its history.** See "End of run".
@@ -7,78 +7,26 @@ disable-model-invocation: true
7
7
  <!--
8
8
  Modified copy of https://github.com/mattpocock/skills/tree/5b15a47f2d7150f545fbcacbfe381787fc0230dc/skills/in-progress/implement-spec
9
9
  (in-progress, after mattpocock-skills v1.2.3). Copyright (c) 2026 Matt Pocock, MIT — see LICENSE.
10
- Changes: a model tier per subagent; a context-isolated test-writer commits
11
- locked acceptance tests at the ticket's Seam, the implementer turns them green
12
- with /tdd and reports to a contract; per-ticket review before merge, fix loop
13
- capped at 4 rounds with escalation; a ruling ledger and four stop conditions;
14
- a per-ticket gate (check command + crap-gate) before DONE, verified by the reviewer;
15
- already-green locked tests need a demonstrated mutation; a Codex correctness
16
- pass per risky ticket; tickets touching the same functions run in sequence;
17
- check command after each merge; final /review-diff on the most capable model, one
18
- fixer; ticket-grouped history with fixes folded in before review; closes
19
- tickets only if they are issues; no push and no PR until every
20
- ticket is merged and the final review has passed. Review loop and ledger adapt
21
- superpowers' subagent-driven-development (obra/superpowers, MIT).
22
- Verification schedule (after a 44-hour run with 65 full gates for six tickets):
23
- one gate record per candidate, verified by the reviewer instead of re-run;
24
- mutation proofs once, then only for changed lines; a budget ruling after round 2;
25
- fixture corrections outside the fix loop; a named fallback when codex exec is refused;
26
- one report file per agent; plain-sentence ledger.
10
+ Changes: the method is carried by a deterministic driver (run.sh, driver*.py), not by
11
+ this session; this file only starts the driver. The method text is in the prompt
12
+ templates (prompts/*.md) and in README.md. The driver keeps what this fork added
13
+ to upstream: a model list per role; a context-isolated test-writer that commits
14
+ locked acceptance tests at the ticket's Seam; a review before each merge with a
15
+ mutation proof per locked test file; at most 4 fix rounds with escalation at round 3;
16
+ a correctness pass on another provider for a ticket with a Risk line; one gate
17
+ record per trusted commit, run by the driver; tickets that touch the same function
18
+ run in sequence; a gate on each merge commit; one commit per ticket before the
19
+ final review; no push and no PR until every ticket is merged and the final review
20
+ has passed. The review rounds adapt superpowers' subagent-driven-development
21
+ (obra/superpowers, MIT).
27
22
  -->
28
23
 
29
- You have been provided a spec. This spec should have tickets associated with it, describing how to implement the spec.
24
+ Run the driver. It makes every choice of the run; you start it and relay its result.
30
25
 
31
- The goal is a PR which implements the entire spec on a single branch.
26
+ 1. In the PR worktree, run `sh <skill-dir>/run.sh` with the flags the operator gave, unchanged. `<skill-dir>` is the directory of this file. The driver needs `--gate` and `--spec`: ask the operator for a flag that is missing. A run can take hours: run the command in the background and wait for it to exit.
27
+ 2. When it exits, relay `.agent/implement/summary.md` and the exit code: 0 is done, 1 is a BLOCKED ticket, 2 is parked on questions, 3 is invalid input. Relay the lines the driver printed after the summary too.
28
+ 3. With exit 2, and with exit 1 when the driver printed questions, show the operator those questions. When the operator answers, run the same command again with one `--answer <question id>=<option id>` for each answer.
32
29
 
33
- The tickets are not a list of steps. They are a **task graph** with blocking relationships between them. This means there is always a **frontier** of tickets which are ready to be grabbed.
30
+ Do not answer a question for the operator. Do not edit `.agent/implement/`, the code or the branches during a run.
34
31
 
35
- Communication to and from subagents should be sparse. Communicate primarily through **context pointers**: to the spec, tickets, research notes, and previous commits. Don't duplicate information already available via pointers.
36
-
37
- Run ticket subagents in the background where possible for **maximum concurrency**. Wait on a running subagent with one long wait, in minutes. Each subagent leaves one report file plus its gate log in the notes directory; mutation evidence is a table in the report, and a tree-hash manifest or a per-mutant log is a sign the agent is building its own procedure.
38
-
39
- **Every dispatch prompt says: never `git stash`, reset or check out other branches; worktrees share one stash and one ref store.**
40
-
41
- **Name the model on every dispatch.** Omitted, it inherits yours. Exploration, test-writer, reviewer: mid tier (reviewer higher for risky diffs). Implementer: mid tier; cheapest for a small, fully specified ticket; most capable for design judgment or broad codebase work. Merger: cheapest. Escalations: one tier above the agent that got stuck. Final reviewer: most capable.
42
-
43
- **Rule, don't stall.** Keep a **ledger** file in the notes directory: every decision as `Ruling: <what> — <why> — <cost if wrong>`, and each ticket's state, so you can resume after compaction. Ledger entries are plain sentences, one line per event, written for a human who was not there. After a compaction, read the ledger and continue from its last state: a recorded green gate, review or merge stays settled. Don't check in between tickets. Stop and ask only for: an irreversible or destructive operation, a security-sensitive action, a side effect outside the worktrees (a push to a shared branch, a publish), or a spec so broken that every path forward is a guess.
44
-
45
- ## Steps
46
-
47
- 1. Read the spec and tickets. Read enough to understand the task graph.
48
-
49
- 2. Create a notes directory outside the repo, readable by all subagents, and start the ledger there. (optional) An **exploration subagent** saves its notes on relevant code and docs there, so implementers focus on implementation. It is the only exploration or preparation agent: a ticket starts with its test-writer, when its frontier turn comes.
50
-
51
- 3. Create the PR branch locally. Do **not** push it and do **not** open a PR yet: nothing leaves the
52
- machine until every ticket is merged and the final review has passed (step 10). Intermediate
53
- states (red tests, half-merged tickets) are never pushed. If the spec and tickets are issues,
54
- note their numbers in the ledger so the PR can close them later.
55
-
56
- 4. Each ticket gets its own worktree and branch. For a ticket with behavioral criteria, a **test-writer subagent** goes first. It sees ONLY the spec, the ticket, CONTEXT.md/ADRs and the public interface of the ticket's **Seam** line (pointers, not summaries), never the implementer's plan or code. It writes one black-box acceptance test per criterion through that interface (defining the interface if it doesn't exist yet), confirms each fails for the right reason (missing behavior, not an import or syntax error), commits them red, and reports the SHA and files. A test that is already green before any implementation is vacuous until proven otherwise (an earlier guard or early return often satisfies it for an unrelated reason): the test-writer rewrites it to fail, or, when the criterion truly holds already, reports the production-code mutation that turns it red and the red output. Those files are now **locked**. A ticket with no behavioral criteria (Seam: `none — refactor`) skips this: its existing tests are the contract, unmodified.
57
-
58
- 5. An **implementer subagent** starts from that commit and makes the locked tests green, using /tdd vertical slices for anything internal. It never edits locked files and never dispatches subagents. While working it runs targeted tests. Before reporting DONE it runs the **gate** once, on a clean tree at its final commit, as one **gate record**: the repo's check command (lint, typecheck, full test suite: `make check`, or what AGENTS.md documents) and, when the repo has `.crap-gate.json`, crap-gate scoped to the ticket (`run.sh --base <PR branch>`, or the repo's `make crap-gate` wrapper), wrapped so the log in the notes directory carries the start and end time, the commit, and the status:
59
-
60
- ```sh
61
- { date -u; git rev-parse HEAD; git status --short; <gate commands>; echo "gate exit $?"; date -u; } 2>&1 | tee <notes>/<ticket>-gate-$(git rev-parse --short HEAD).log
62
- ```
63
-
64
- DONE means the record is green; a gate it can't turn green is BLOCKED, with the output. CRAP findings are the implementer's to resolve inside the ticket, within this same gate. It reports: status (DONE, DONE_WITH_CONCERNS, NEEDS_CONTEXT or BLOCKED), commits, red→green evidence for locked tests and its own slices, the gate record's path and final lines, files touched, concerns. If a locked test contradicts the spec or ticket, it reports BLOCKED with evidence; you rule, and either send the test-writer a correction or overrule the implementer. Handle any BLOCKED by changing something: more context, a stronger model, a split ticket, or a ruling. Never retry unchanged.
65
-
66
- 6. On DONE, a **reviewer subagent** verifies the gate record first: the recorded commit is the ticket tip, the status was clean, and the tail is green. A missing, mismatched or red record is a Critical finding, and only then does the reviewer run the gate itself. It then checks the diff against the ticket and spec, then for quality. It also checks that `git diff <test-writer-sha>..HEAD -- <locked files>` is empty and that every criterion has a locked test; that one diff is the whole locked-file check. In the first review, for each locked test, it breaks the production line the test exists to protect (a temporary edit, reverted after) and confirms that test goes red; a test that stays green is Critical. It re-runs every mutation the test-writer reported. A mutation proof is keyed to its test and production line: a later round repeats it only when that line changed. Name your specific suspicions in the reviewer prompt (a sibling caller, a key built from the old identity, a guard another ticket added): named suspicions find regressions a generic review misses.
67
-
68
- A **risky** ticket touches shared state, persistence keys or identities, concurrency, money or auth. For those, run the correctness pass from /review-diff yourself, in parallel with the reviewer: `codex exec` with that skill's `correctness.md` on range `<PR branch>...<ticket branch>` (three dots: from the merge-base, so tickets merged meanwhile don't show up), in quick mode (the command is in the review-diff skill). Its findings join the reviewer's, with the same severities. When `codex exec` is refused or unavailable, the reviewer's brief gains `correctness.md` for the ticket's risk; that reviewer is the correctness pass, with no second agent.
69
-
70
- A finding that a locked test encodes a stale path, format, fixture layout or setup detail is a **fixture correction**, outside the fix loop: one test-writer dispatch fixes it, the ledger records the file and reason, and the review continues. A setup or environment failure (a missing dependency link, a cache permission, a leftover process) is fixed at its cause before any gate runs again.
71
-
72
- Critical or Important findings start a fix loop of at most 4 rounds, each a fix plus a re-review scoped to that fix: the implementer uses targeted tests, ends the round with one gate record on its final commit, and the re-review verifies that record and the changed lines. Rounds 1-2 resume the implementer; rounds 3-4 go to a fresh implementer one tier up. After round 2, rule in the ledger before continuing: continue, split the ticket, or redesign it. After round 4, rule on each open finding in the ledger and move on. Minor findings go to the ledger for the final review.
73
-
74
- 7. Once a ticket passes review, a **merger subagent** merges it to the PR branch and runs the repo's check command (lint, typecheck, full suite). If it fails, an implementer fixes the PR branch before the next merge. Record the ticket's commits and any integration fixes in the ledger. After the merge and checks pass, remove that ticket's worktree and delete its branch with `git branch -d` from the PR branch, while its commits are still ancestors of the PR tip. Cleanup is part of completing the ticket, before any rebase.
75
-
76
- 8. If this changes the **frontier** of available tickets, start the next tickets. This allows for maximum concurrency. Exception: two ready tickets that change the same function never run at once. Compare their **Touches** lines (or ask the exploration subagent where each will land); on overlap, start one and hold the other until the first merges, then branch it from the updated PR branch. When in doubt, hold: a ticket you suspect builds on another ready or running ticket waits for it, even without a declared edge. Record the hold as a ruling.
77
-
78
- 9. Once all tickets are complete, fetch and rebase the unpublished PR branch onto the latest base, then tidy its history by ticket using [history.md](history.md). A reviewer on the most capable model runs /review-diff on the resulting PR branch, given the ledger's minor findings and rulings. Record its resolved range and reviewed tip in the ledger. One **implementer subagent** fixes every finding as one batch, which may contain several working commits; it records the owning ticket for each fix. Use targeted checks during the fixes. Once the whole batch is complete, tidy it into the ticket commits, then run /review-diff on the full rewritten branch. If history was preserved, a scoped /review-diff on `<last-reviewed-tip>..HEAD` suffices. Its gate runs once for the completed batch. Record the new review's range and tip, marking superseded coverage, or `not needed` if there were no fixes. Rule on anything left.
79
-
80
- 10. Before pushing, compare `git rev-parse HEAD` with the last reviewed tip and confirm the worktree is clean. Every commit through the branch tip must be covered by the full review plus any scoped re-reviews. Collect any later commits, including orchestrator changes, into one batch and follow step 9's tidy-and-review procedure. A rebase or history rewrite invalidates the recorded coverage: review the full rewritten branch, with one full gate for that completed batch. The push is ready only when the history is grouped by ticket, the clean branch tip equals the last reviewed tip, and every finding has a fix or a ledger ruling. If the review cannot finish, report the blocker and each unreviewed commit with its subject, and keep the branch local.
81
-
82
- Now push the PR branch once and open the PR, ready for review (not draft), using /pr for the body when installed. If the spec and tickets are issues, mark the PR as 'closing' them. In your final message, list every ruling, the final ticket-to-commit map, and the review coverage: the latest full review's range and tip, any scoped re-review ranges and tips since it (or `not needed`), the current branch tip, and whether it equals the last reviewed tip. Earlier reviews of superseded history stay in the ledger.
83
-
84
- 11. Confirm every ticket worktree and branch was removed in step 7.
32
+ [README.md](README.md) has each flag, the exit codes, the files of a run and the method.