runcastle 1.2.6 → 1.2.9
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.
- package/bin/runcastle.js +8467 -6620
- package/drizzle/0026_polite_vertigo.sql +2 -0
- package/drizzle/0027_dashing_texas_twister.sql +1 -0
- package/drizzle/0028_tired_deadpool.sql +2 -0
- package/drizzle/0029_damp_stellaris.sql +1 -0
- package/drizzle/meta/0026_snapshot.json +921 -0
- package/drizzle/meta/0027_snapshot.json +928 -0
- package/drizzle/meta/0028_snapshot.json +942 -0
- package/drizzle/meta/0029_snapshot.json +949 -0
- package/drizzle/meta/_journal.json +29 -1
- package/index.js +8424 -6588
- package/package.json +1 -1
- package/sandcastle-template/Containerfile +9 -1
- package/sandcastle-template/Dockerfile +9 -1
- package/skills/burner/implement-ticket.md +32 -47
- package/skills/burner/resolve-conflict.md +3 -3
- package/skills/burner/review-ticket.md +118 -37
- package/skills/packs/README.md +99 -7
- package/skills/packs/runcastle/.claude-plugin/plugin.json +1 -1
- package/skills/packs/runcastle/skills/code-review/SKILL.md +131 -0
- package/skills/packs/runcastle/skills/converge/SKILL.md +26 -21
- package/skills/packs/runcastle/skills/ideate/SKILL.md +14 -8
- package/skills/packs/runcastle/skills/prepare/SKILL.md +96 -0
- package/skills/packs/runcastle/skills/prepare/references/recipes.md +72 -0
- package/skills/packs/runcastle/skills/project/SKILL.md +54 -55
- package/skills/packs/runcastle/skills/project/references/charter.md +79 -0
- package/skills/packs/runcastle/skills/project/references/health-sweeps.md +44 -0
- package/skills/packs/runcastle/skills/qa/SKILL.md +29 -13
- package/skills/packs/runcastle/skills/revisit/SKILL.md +8 -8
- package/skills/packs/runcastle/skills/spec/SKILL.md +3 -3
- package/skills/packs/runcastle/skills/tickets/SKILL.md +19 -3
- package/skills/packs/runcastle/skills/waypoint/SKILL.md +14 -9
- package/web/assets/index-BRjcVBgj.js +142 -0
- package/web/assets/index-CNSn8X1O.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-bof4-dhY.css +0 -1
- package/web/assets/index-qHtxEVWq.js +0 -142
package/package.json
CHANGED
|
@@ -15,6 +15,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
15
15
|
ENV BUN_INSTALL=/usr/local
|
|
16
16
|
RUN curl -fsSL https://bun.sh/install | bash
|
|
17
17
|
|
|
18
|
+
# Codex CLI (the second agent runtime) — authenticated at runtime via CODEX_API_KEY.
|
|
19
|
+
# Installed as root, before the user switch, so it lands in the system-wide npm
|
|
20
|
+
# prefix: after `USER` a global install has nowhere writable to go.
|
|
21
|
+
RUN npm install -g @openai/codex
|
|
22
|
+
|
|
18
23
|
# Rootless-podman invariant (research #32): the in-container agent user MUST be at
|
|
19
24
|
# UID/GID 1000 so bind-mounted host files share an owner. Sandcastle passes the host
|
|
20
25
|
# uid/gid as build args (defaulting here to 1000); rename the stock `node` user to match.
|
|
@@ -24,7 +29,10 @@ RUN groupmod -o -g "$AGENT_GID" node \
|
|
|
24
29
|
&& usermod -o -u "$AGENT_UID" -g "$AGENT_GID" -d /home/agent -m -l agent node
|
|
25
30
|
USER ${AGENT_UID}:${AGENT_GID}
|
|
26
31
|
|
|
27
|
-
# Claude Code CLI (the agent runtime) — authenticated at runtime via
|
|
32
|
+
# Claude Code CLI (the other agent runtime) — authenticated at runtime via
|
|
33
|
+
# CLAUDE_CODE_OAUTH_TOKEN. Installed per-user, so it must follow the switch.
|
|
34
|
+
# One image serves both runtimes: which CLI a burn invokes is decided by the
|
|
35
|
+
# resolved model's runtime, not by the image.
|
|
28
36
|
RUN curl -fsSL https://claude.ai/install.sh | bash
|
|
29
37
|
ENV PATH="/home/agent/.local/bin:$PATH"
|
|
30
38
|
|
|
@@ -15,6 +15,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
15
15
|
ENV BUN_INSTALL=/usr/local
|
|
16
16
|
RUN curl -fsSL https://bun.sh/install | bash
|
|
17
17
|
|
|
18
|
+
# Codex CLI (the second agent runtime) — authenticated at runtime via CODEX_API_KEY.
|
|
19
|
+
# Installed as root, before the user switch, so it lands in the system-wide npm
|
|
20
|
+
# prefix: after `USER` a global install has nowhere writable to go.
|
|
21
|
+
RUN npm install -g @openai/codex
|
|
22
|
+
|
|
18
23
|
# Rootless-podman invariant (research #32): the in-container agent user MUST be at
|
|
19
24
|
# UID/GID 1000 so bind-mounted host files share an owner. Sandcastle passes the host
|
|
20
25
|
# uid/gid as build args (defaulting here to 1000); rename the stock `node` user to match.
|
|
@@ -24,7 +29,10 @@ RUN groupmod -o -g "$AGENT_GID" node \
|
|
|
24
29
|
&& usermod -o -u "$AGENT_UID" -g "$AGENT_GID" -d /home/agent -m -l agent node
|
|
25
30
|
USER ${AGENT_UID}:${AGENT_GID}
|
|
26
31
|
|
|
27
|
-
# Claude Code CLI (the agent runtime) — authenticated at runtime via
|
|
32
|
+
# Claude Code CLI (the other agent runtime) — authenticated at runtime via
|
|
33
|
+
# CLAUDE_CODE_OAUTH_TOKEN. Installed per-user, so it must follow the switch.
|
|
34
|
+
# One image serves both runtimes: which CLI a burn invokes is decided by the
|
|
35
|
+
# resolved model's runtime, not by the image.
|
|
28
36
|
RUN curl -fsSL https://claude.ai/install.sh | bash
|
|
29
37
|
ENV PATH="/home/agent/.local/bin:$PATH"
|
|
30
38
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
<!-- Forked from Matt Pocock's implement + tdd + code-review skills, 2026-07-14, adapted for runcastle's unattended burner. Rendered per ticket
|
|
1
|
+
<!-- Forked from Matt Pocock's implement + tdd + code-review skills, 2026-07-14, adapted for runcastle's unattended burner. Rendered per ticket by the ticket-burner workflow. ORDERING IS LOAD-BEARING: everything above "## Your ticket" is byte-identical for every ticket in a burn and across a ticket's own retry iterations, so concurrent agents share a long cacheable prefix. The two ticket-specific placeholders live at the END, under "Your ticket", and must stay there — moving one up collapses the shared prefix to whatever precedes it. -->
|
|
2
2
|
|
|
3
3
|
# Implement this ticket — unattended
|
|
4
4
|
|
|
5
|
-
You are a single agent in a sandbox on **your ticket's own temp branch**, `runcastle/ticket/<slug>/<seq>-<unique>`, forked from the feature branch
|
|
5
|
+
You are a single agent in a sandbox on **your ticket's own temp branch**, `runcastle/ticket/<slug>/<seq>-<unique>`, forked from the feature's integration branch. Commit there and only there — the run merges your branch back into the integration branch once you land, so never check out or commit to the integration branch yourself. You have **one ticket**, stated at the end of this prompt. There is **no human to ask** — no follow-up questions are possible. Everything you need is in this prompt and in the repo. Work carefully, commit only green work, and stop when the ticket is done.
|
|
6
6
|
|
|
7
7
|
## How you run
|
|
8
8
|
|
|
9
|
-
You run **non-interactively**
|
|
9
|
+
You run **non-interactively** — your agent CLI in print/exec mode, no terminal, no human — for up to a few fresh iterations:
|
|
10
10
|
|
|
11
11
|
- **Ending your turn ends your process.** There are no background-task completion notifications in print mode — a "the notification will re-invoke me" plan never fires. Never end your turn to wait on a background command; run long commands (dependency installs, full test suites) in the foreground with a generous timeout, or poll a backgrounded command to completion *within* the same turn. **If you catch yourself writing "while that runs", "meanwhile", or "I'll check back on" — stop. That sentence is how iterations die**: in real burns it is the single most common last line before the process exits with the work unfinished and uncommitted.
|
|
12
12
|
- **A next iteration is not a free retry.** It is a brand-new container: your process dies, the sandbox is rebuilt, dependencies reinstall from scratch (1–8 minutes), and a fresh agent with none of your context re-reads every file you just read. Budget roughly ten wasted minutes per iteration you burn. Finishing in one is worth real effort.
|
|
13
|
-
- **You may not be the first iteration.**
|
|
13
|
+
- **You may not be the first iteration.** Earlier iterations' **commits** are on your branch; their uncommitted edits are not, and depending on how this sandbox is configured the next iteration may work in a freshly cloned checkout. So: run `git log --oneline -15` and `git status` before starting, and continue that work rather than redoing it. Only commits carry across.
|
|
14
14
|
- **Signal completion.** When the ticket is fully done — every acceptance criterion verified, self-review finished, all work committed — print exactly `<promise>COMPLETE</promise>` as the last line of your final message. Do the same after writing `BLOCKED.md` (see hard rules). Without the signal, the harness assumes you were cut off and spends another iteration.
|
|
15
15
|
- **Run the repo's test command as the repo defines it.** Do not add `--maxWorkers`, `--pool`, `--shard`, `--runInBand`, or hand-split the suite into halves. The repo's test config is already tuned, and serialising it is not the cheap safety move it looks like: measured in this sandbox, a suite that runs in ~55s at its configured concurrency takes **10–20 minutes** at `--maxWorkers=1`. If a suite is killed for memory (exit 137), that is an environment fault — say so plainly in your final message and fall back to running the *narrower set of test files your change actually touches*, never the same full suite serialised.
|
|
16
16
|
|
|
@@ -29,11 +29,9 @@ This is not a style preference — it is one of the largest measured costs in re
|
|
|
29
29
|
|
|
30
30
|
If you catch yourself writing a heredoc to edit a file, stop and use `Edit`.
|
|
31
31
|
|
|
32
|
-
## The
|
|
32
|
+
## The standards you are held to
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
{{TICKET_JSON}}
|
|
36
|
-
```
|
|
34
|
+
{{PROJECT_STANDARDS}}
|
|
37
35
|
|
|
38
36
|
## Feature context
|
|
39
37
|
|
|
@@ -55,7 +53,7 @@ Whatever the commands are, spend them well — a full suite on a monorepo is min
|
|
|
55
53
|
|
|
56
54
|
## How to work
|
|
57
55
|
|
|
58
|
-
1. **Orient.** Read
|
|
56
|
+
1. **Orient.** Read your ticket's `goal`, `context`, `acceptanceCriteria`, and `seams` (they are at the end of this prompt). Read the files its `context` names and the existing patterns it points to. Match the conventions of the surrounding code — you are extending this codebase, not starting a new one.
|
|
59
57
|
|
|
60
58
|
2. **Test at the seams (forked tdd).** The ticket's `seams` are the public interfaces to test at — test *there*, at the boundary where behaviour is observable, never against internals or private helpers. A good test reads like a spec ("user can X"), uses the public API only, and survives refactors. Mock only true system boundaries (network, clock, external services); never mock your own modules.
|
|
61
59
|
|
|
@@ -63,64 +61,51 @@ Whatever the commands are, spend them well — a full suite on a monorepo is min
|
|
|
63
61
|
|
|
64
62
|
4. **Commit early, commit often — an uncommitted slice is a slice you can lose.** A commit is the *only* thing that survives your process ending. Everything else — edited files, a passing test you have not committed, an hour of work — is discarded the moment the turn ends, and the next iteration starts from your last commit as if the rest never happened. Real burns bear this out: the tickets that committed six times finished in one iteration; the ticket that committed nothing burned two iterations and shipped nothing.
|
|
65
63
|
|
|
66
|
-
So: **the moment a slice is green, commit it.** Do not save commits up for a tidy end-of-ticket batch, do not wait for the next criterion, do not let more than about ten minutes of work sit uncommitted. Before each commit run typecheck and the *relevant* tests — **never commit red** — then commit
|
|
64
|
+
So: **the moment a slice is green, commit it.** Do not save commits up for a tidy end-of-ticket batch, do not wait for the next criterion, do not let more than about ten minutes of work sit uncommitted. Before each commit run typecheck and the *relevant* tests — **never commit red** — then commit with a subject line of the form:
|
|
67
65
|
|
|
68
|
-
`
|
|
66
|
+
`ticket(<seq>): <summary>`
|
|
67
|
+
|
|
68
|
+
where `<seq>` is the `seq` field of the ticket JSON at the end of this prompt (so ticket 4 commits `ticket(4): add the login form`). Use that prefix on every commit you make.
|
|
69
69
|
|
|
70
70
|
A half-done ticket with four green commits is a good outcome the next iteration can finish. A nearly-done ticket with zero commits is a total loss.
|
|
71
71
|
|
|
72
72
|
5. **Self-review before you finish (forked code-review — two axes).** When all acceptance criteria pass, review your own diff along both axes, then fix what you find and re-run typecheck + tests:
|
|
73
|
-
- **Standards** — does the diff follow the conventions of the surrounding code? Watch for the smells: duplicated logic (extract it), mysterious names (rename), primitive obsession (give the concept a type), speculative generality (delete anything the ticket did not ask for), feature envy, data clumps.
|
|
73
|
+
- **Standards** — does the diff follow the documented standards above and the conventions of the surrounding code? Watch for the smells: duplicated logic (extract it), mysterious names (rename), primitive obsession (give the concept a type), speculative generality (delete anything the ticket did not ask for), feature envy, data clumps.
|
|
74
74
|
- **Spec = this ticket** — is every acceptance criterion actually met, and is there **nothing in the diff the ticket did not ask for**? Missing and extra both count.
|
|
75
|
-
|
|
75
|
+
|
|
76
|
+
A review agent runs against your diff at the end of this burn, along exactly these two axes and against exactly those standards files. Everything you catch here is a finding that never becomes someone's fix ticket. Commit the fixes.
|
|
76
77
|
|
|
77
78
|
6. **Write your digest — the last thing you do.** Once every acceptance criterion passes and the self-review fixes are committed, write `DIGEST.md` (see "Where to work" for exactly where) as your final act before printing `<promise>COMPLETE</promise>`. Roughly 10–15 lines, three parts:
|
|
78
79
|
- **What was done** — past tense, what you actually built, *including where it deviated* from the approach the ticket described.
|
|
79
80
|
- **Surprises** — what the ticket or the spec did not anticipate: the coupling nobody mentioned, the test that was already red, the API that did not behave as documented.
|
|
80
81
|
- **Left undone** — adjacent work you noticed and deliberately did not do, so the next agent inherits the observation instead of re-finding it.
|
|
81
82
|
|
|
82
|
-
Write it in plain prose, for a reader who does not have your context. **Never commit `DIGEST.md`** — it is harvested from the workspace, not from the repo, and a committed one is diff noise. It is a **success artifact only**: if you are blocked and writing `BLOCKED.md`, write no digest — `BLOCKED.md` is your record.
|
|
83
|
+
Write it in plain prose, for a reader who does not have your context. It is read: the tickets that depend on yours are handed it verbatim, and so is the agent that reviews this whole lap. **Never commit `DIGEST.md`** — it is harvested from the workspace, not from the repo, and a committed one is diff noise. It is a **success artifact only**: if you are blocked and writing `BLOCKED.md`, write no digest — `BLOCKED.md` is your record.
|
|
83
84
|
|
|
84
|
-
## Keep the drive
|
|
85
|
+
## Keep the drive machinery true
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
{{DRIVE_NOTES}}
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
- a **service** the app now needs — a database, a queue, a container in the compose file;
|
|
91
|
-
- a **required env var** the app reads at boot and fails without;
|
|
92
|
-
- a **seed** requirement — data that has to exist before the app is usable;
|
|
93
|
-
- a **process** the dev environment must run alongside the app — a worker, a watcher.
|
|
94
|
-
|
|
95
|
-
Anything short of those needs no edit. The steps are idempotent by design — install, migrate, compose up all run unconditionally — so a branch that merely adds a package or a migration is already covered. Only structural changes need you, and you are the agent who knows about them. This is part of your ticket, not adjacent work: a branch whose drive cannot boot is not done.
|
|
96
|
-
|
|
97
|
-
The contract facts you need:
|
|
98
|
-
|
|
99
|
-
- The scripts are **committed source** — edit them like any other file, alongside the change that made them necessary.
|
|
100
|
-
- **Every step stays idempotent** — safe to re-run against an already-prepared world, correct against a fresh one.
|
|
101
|
-
- **`drive-setup` writes `.runcastle/drive.env`** (gitignored, plain `KEY=VALUE`). That file is the only way a value the script computes — a port, a database name, a URL — reaches the app. If your ticket makes the app require an env var, the setup script must write it there.
|
|
102
|
-
- **`RUNCASTLE_SLUG`, `RUNCASTLE_BRANCH`, and `RUNCASTLE_ID` are provided by the server** to every drive hook. Derive per-drive identity from those; never from git inside the script.
|
|
103
|
-
- **Exit 0 means ready** — if you add a service, add the wait for it too.
|
|
89
|
+
## Hard rules
|
|
104
90
|
|
|
105
|
-
|
|
91
|
+
{{GUARD_NOTES}}
|
|
106
92
|
|
|
107
|
-
|
|
93
|
+
- **Never end your turn with uncommitted green work.** If you are about to stop for any reason — done, blocked, unsure, out of room — commit what is green first. This rule outranks tidiness: a scrappy commit beats losing the work.
|
|
94
|
+
- **Never `git stash`.** See "How to verify" — stashed work is invisible to the orchestrator and unrecoverable if your process dies.
|
|
95
|
+
- **Never expand scope beyond the ticket.** If you notice adjacent work, worthwhile refactors, or another ticket's territory, leave it. Note it in your final commit body or your digest if it matters; do not do it.
|
|
96
|
+
- **No questions, no guessing into the void.** Resolve ambiguity from the ticket context and the code. If two readings both satisfy the acceptance criteria, take the smaller one.
|
|
97
|
+
- **If genuinely blocked** (a dependency ticket's output is not there, the environment fails, or a requirement truly cannot be resolved from context + code): **commit nothing for the blocked part**, write `BLOCKED.md` — **at the path given in "Where to work", and nowhere else** — stating precisely what blocked you and what is needed to unblock, print `<promise>COMPLETE</promise>`, and stop. Green, complete parts may still be committed; the blocked part must not be. The orchestrator reads your exit state from the commits — a part with no commits is read as not done.
|
|
108
98
|
|
|
109
|
-
|
|
99
|
+
## Your ticket
|
|
110
100
|
|
|
111
|
-
|
|
112
|
-
- **Every referenced file exists.** Read the script and check that each path it names — compose file, seed file, env sample, sourced helper — is present in the repo at that path.
|
|
113
|
-
- **The compose file parses**, if you changed one: `docker compose config -q` when the CLI happens to be installed, otherwise parse it as YAML.
|
|
114
|
-
- **New env vars appear in the output.** If your change made the app require an env var, grep the setup script and confirm it actually writes that key into `.runcastle/drive.env`.
|
|
101
|
+
Everything above is the standing brief for this burn. This is the part that is yours alone.
|
|
115
102
|
|
|
116
|
-
|
|
103
|
+
```json
|
|
104
|
+
{{TICKET_JSON}}
|
|
105
|
+
```
|
|
117
106
|
|
|
118
|
-
|
|
107
|
+
### What landed before you
|
|
119
108
|
|
|
120
|
-
|
|
109
|
+
{{BLOCKERS}}
|
|
121
110
|
|
|
122
|
-
|
|
123
|
-
- **Never `git stash`.** See "How to verify" — stashed work is invisible to the orchestrator and unrecoverable if your process dies.
|
|
124
|
-
- **Never expand scope beyond the ticket.** If you notice adjacent work, worthwhile refactors, or another ticket's territory, leave it. Note it in your final commit body if it matters; do not do it.
|
|
125
|
-
- **No questions, no guessing into the void.** Resolve ambiguity from the ticket context and the code. If two readings both satisfy the acceptance criteria, take the smaller one.
|
|
126
|
-
- **If genuinely blocked** (a dependency ticket's output is not there, the environment fails, or a requirement truly cannot be resolved from context + code): **commit nothing for the blocked part**, write `BLOCKED.md` at the repo root stating precisely what blocked you and what is needed to unblock, print `<promise>COMPLETE</promise>`, and stop. Green, complete parts may still be committed; the blocked part must not be. The orchestrator reads your exit state from the commits — a part with no commits is read as not done.
|
|
111
|
+
Now do it.
|
|
@@ -10,7 +10,7 @@ There is **no human to ask** — everything you need is in this prompt and in th
|
|
|
10
10
|
|
|
11
11
|
## How you run
|
|
12
12
|
|
|
13
|
-
You run **non-interactively**
|
|
13
|
+
You run **non-interactively** — your agent CLI in print/exec mode, no terminal, no human — for up to a few fresh iterations. Only committed work carries from one iteration to the next — an iteration may start from a freshly cloned checkout of your branch:
|
|
14
14
|
|
|
15
15
|
- **Ending your turn ends your process.** There are no background-task completion notifications in print mode — a "the notification will re-invoke me" plan never fires. Run long commands (test suites) in the foreground with a generous timeout. If you catch yourself writing "while that runs" or "meanwhile", stop — that sentence is how an iteration dies mid-merge.
|
|
16
16
|
- **An unfinished merge does not survive you.** Resolved-but-uncommitted files are discarded when your process ends, and the next iteration restarts the merge from scratch. Once the conflicts are resolved and the tree is sane, land the merge commit — then verify and fix forward on top of it.
|
|
@@ -70,9 +70,9 @@ These commits landed on `{{FEATURE_BRANCH}}` while you were working — this is
|
|
|
70
70
|
|
|
71
71
|
## Hard rules
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
{{GUARD_NOTES}}
|
|
74
74
|
|
|
75
75
|
- **Never expand scope.** Resolving the conflict is the whole job. No refactors, no improvements, no adjacent fixes, not even tempting ones in the files you are already editing.
|
|
76
76
|
- **Never lose committed work.** Not yours, not theirs. `git merge --abort` + "start over from one side" is not a resolution.
|
|
77
77
|
- **Never push, and never touch the remote.** The orchestrator lands your branch itself once the merge commit exists.
|
|
78
|
-
- **If the two sides genuinely cannot be reconciled without a product decision** (they implement contradictory behaviour, not merely overlapping code): `git merge --abort`, write `BLOCKED.md` at the
|
|
78
|
+
- **If the two sides genuinely cannot be reconciled without a product decision** (they implement contradictory behaviour, not merely overlapping code): `git merge --abort`, write `BLOCKED.md` — at the path given in "Where to work", and nowhere else — naming the exact contradiction and the decision needed, print `<promise>COMPLETE</promise>`, and stop. A human will decide. Do not guess.
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
<!-- Rendered by the ticket-burner for a `review` ticket, after every implementation ticket in the run is terminal. Placeholder tokens are filled in by the workflow. -->
|
|
1
|
+
<!-- Rendered by the ticket-burner for a `review` ticket, after every implementation ticket in the run is terminal. Placeholder tokens are filled in by the workflow. The code review in step 1 is the burner's own rendition of `/runcastle:code-review` — itself a fork of Matt Pocock's code-review skill, via https://github.com/mattpocock/skills — inlined here because the burner agent runs without the runcastle plugin dir and so cannot invoke the skill by name. Keep the two in step. -->
|
|
2
2
|
|
|
3
3
|
# Review this feature — unattended
|
|
4
4
|
|
|
5
|
-
Every implementation ticket in this burn has landed on `{{FEATURE_BRANCH}}`. Your job is to **
|
|
5
|
+
Every implementation ticket in this burn has landed on `{{FEATURE_BRANCH}}`, which forked from `{{BASE_BRANCH}}`. Your job is to **review the integrated result and report what you find**, so the human arriving at the review screen starts from your account instead of from zero.
|
|
6
6
|
|
|
7
|
-
You
|
|
7
|
+
You do two things, in this order:
|
|
8
|
+
|
|
9
|
+
1. **A code review — always.** Every lap gets one. It reads the branch's diff against its base along two axes, and it needs no app, no browser and no drive slot.
|
|
10
|
+
2. **A drive of the app — additionally, when there is something to drive.** Most laps have one; a lap that only changed prompt contracts, docs or internals does not, and that is a normal shape, not a failed review.
|
|
11
|
+
|
|
12
|
+
And you leave two deliverables behind: the **notes** (one per finding) and the **digest** (the lap's prose summary, which the human reads first — see step 6; it is not an afterthought).
|
|
13
|
+
|
|
14
|
+
You are **not** implementing anything. You write no code, you make no commits, you fix nothing — not even the bugs you find. Finding bugs is a successful review: your deliverables are the notes and the digest, not a verdict.
|
|
8
15
|
|
|
9
16
|
There is **no human to ask** — everything you need is in this prompt and in the repo.
|
|
10
17
|
|
|
11
18
|
## How you run
|
|
12
19
|
|
|
13
|
-
You run **non-interactively**
|
|
20
|
+
You run **non-interactively** — your agent CLI in print/exec mode, no terminal, no human — on the **host**, in the project's real checkout: not a container, not a worktree. So:
|
|
14
21
|
|
|
15
22
|
- **Ending your turn ends your process.** There are no background-task completion notifications in print mode. Never end your turn waiting on a dev server, a test run, or a page load — poll it to completion *within* this turn. If you catch yourself writing "meanwhile" or "I'll check back on", stop.
|
|
16
23
|
- **You are on someone's machine.** The drive you start switches their checkout and runs their dev server. Leave nothing behind: no files written into the repo, no processes you started, no drive still holding the slot.
|
|
@@ -22,7 +29,9 @@ You run **non-interactively** (`claude --print`) on the **host**, in the project
|
|
|
22
29
|
{{TICKET_JSON}}
|
|
23
30
|
```
|
|
24
31
|
|
|
25
|
-
Its `goal` says what to verify and its `acceptanceCriteria` say how you will know. Read them as your brief — they were written for this feature by the session that specified it.
|
|
32
|
+
Its `goal` says what to verify and its `acceptanceCriteria` say how you will know. Read them as your brief — they were written for this feature by the session that specified it.
|
|
33
|
+
|
|
34
|
+
**The ticket prescribes the drive, not the code review.** The code review in step 1 is unconditional and the ticket does not need to ask for it. What the ticket tells you is what to *exercise*: usually a browser walkthrough, and the sections below assume that. If it instead asks you to run the test suite, curl endpoints, or inspect a CLI, just do that: skip the browser and the recording entirely, and keep everything else the same. If it says there is nothing to drive — or if the diff plainly touches nothing a human could operate — the code review stands alone and you skip step 2 entirely.
|
|
26
35
|
|
|
27
36
|
## Feature context
|
|
28
37
|
|
|
@@ -32,63 +41,135 @@ Its `goal` says what to verify and its `acceptanceCriteria` say how you will kno
|
|
|
32
41
|
|
|
33
42
|
{{DOCS_DIGEST}}
|
|
34
43
|
|
|
44
|
+
## What the implementers say they did
|
|
45
|
+
|
|
46
|
+
Each ticket in this burn wrote its own account of its work before finishing. They are reproduced here in full, and they are **evidence, not truth** — an implementer's claim is a hypothesis you confirm against the diff, exactly like a sub-agent's finding.
|
|
47
|
+
|
|
48
|
+
Read them for the two things a diff cannot express at any price: what **surprised** each implementer (the coupling nobody specified, the test that was already red, the API that misbehaved), and what each one **left undone** on purpose. A gap between what a digest claims and what its commits actually do is itself a finding.
|
|
49
|
+
|
|
50
|
+
{{LAP_DIGESTS}}
|
|
51
|
+
|
|
35
52
|
## How to work
|
|
36
53
|
|
|
37
|
-
1.
|
|
54
|
+
### 1. Code review — always, and first
|
|
55
|
+
|
|
56
|
+
First, because it is the part that always runs and the part that needs nothing booted. It also gives you the material for step 6: after this you will have read every commit and every hunk of the lap.
|
|
57
|
+
|
|
58
|
+
**Pin the fixed point.** Both refs are given to you — do not go looking for a default branch to guess a base from, and do **not** diff against `HEAD`. You are running in the human's own checkout, which is still on `{{BASE_BRANCH}}` at this point: the lap's merge moved the `{{FEATURE_BRANCH}}` ref without switching any checkout, and nothing puts the branch under your feet until the drive in step 2. Diffing `HEAD` here reads an empty diff on a perfectly healthy lap.
|
|
38
59
|
|
|
39
|
-
|
|
60
|
+
Three-dot, so you see the branch's own work and not everything that landed on the base meanwhile:
|
|
40
61
|
|
|
41
|
-
|
|
62
|
+
```
|
|
63
|
+
git log {{BASE_BRANCH}}...{{FEATURE_BRANCH}} --oneline # the commits under review
|
|
64
|
+
git diff {{BASE_BRANCH}}...{{FEATURE_BRANCH}} # the diff both axes read
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Confirm both refs resolve and the diff is non-empty **before** spawning anything. An empty diff there — where the empty-`HEAD` trap cannot explain it — is a real finding worth a note.
|
|
68
|
+
|
|
69
|
+
**Gather the standards.** `CLAUDE.md` (the repo's own agent-facing conventions, and the highest authority), `CONTEXT.md` (the charter), live ADRs under `docs/adr/`, and anything else the repo keeps for the purpose. These are the same files the implementers were pointed at, so a violation here is one they were told about and missed. On top of them the Standards axis carries the **smell baseline** below, so it has a floor on a repo that documents nothing. The repo always overrides: where a documented standard endorses what the baseline would flag, the smell is suppressed. Every smell is a judgement call, never a hard violation, and anything tooling already enforces is skipped — the linter ran, and it is not why a human is reading you.
|
|
70
|
+
|
|
71
|
+
Mysterious Name · Duplicated Code · Feature Envy · Data Clumps · Primitive Obsession · Repeated Switches · Shotgun Surgery · Divergent Change · Speculative Generality · Message Chains · Middle Man · Refused Bequest.
|
|
42
72
|
|
|
43
|
-
|
|
73
|
+
**Spawn both axes in parallel**, in one dispatch, so neither pollutes the other's context. Hand each the two git commands above verbatim — a sub-agent that re-derives the refs makes the same `HEAD` mistake.
|
|
44
74
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
agent-browser record start {{WALKTHROUGH_PATH}} # then, immediately — see below
|
|
48
|
-
agent-browser snapshot -i # the page as interactive elements, each with an @eN ref
|
|
49
|
-
agent-browser click @e7 # act on a ref from the snapshot you just took
|
|
50
|
-
agent-browser snapshot -i # RE-SNAPSHOT — see below
|
|
51
|
-
agent-browser wait --load networkidle # after a navigation, before snapshotting
|
|
52
|
-
agent-browser record stop # before you close, always
|
|
53
|
-
agent-browser close # when you are done driving
|
|
54
|
-
```
|
|
75
|
+
- **Standards** — plus the standards files **by path** (it can read them itself; do not paste their contents) and the smell list above. Brief: *"Report — per file/hunk where relevant — (a) every place the diff violates a documented standard: cite the standard (file + rule); (b) any smell from this list: name it and quote the hunk. Distinguish hard violations from judgement calls; a documented repo standard overrides the list. Skip what tooling enforces. Under 400 words. Do not spawn further agents — perform this review yourself."*
|
|
76
|
+
- **Spec** — plus the acceptance criteria of the review ticket and the paths `docs/features/<slug>/spec.md` and `decisions.md`, which it reads itself. Brief: *"Report: (a) requirements the spec asked for that are missing or partial; (b) behaviour in the diff that wasn't asked for (scope creep); (c) requirements that look implemented but where the implementation looks wrong. Quote the spec line for each finding. Under 400 words. Do not spawn further agents — perform this review yourself."*
|
|
55
77
|
|
|
56
|
-
|
|
78
|
+
That last sentence in each brief is load-bearing: without it a sub-agent can rediscover this review and fan out again. Two sub-agents, one level deep, and that is the whole tree.
|
|
57
79
|
|
|
58
|
-
|
|
59
|
-
- **Always `record stop` where you stop the drive.** It belongs in the same cleanup as step 5, on every path including the failure one — a recorder you leave running outlives you, and its file may be unplayable.
|
|
80
|
+
**Then triage what comes back.** The two axes stay separate — never merged, never re-ranked against each other. Every finding must carry its citation: a standards file plus the rule, a named smell plus the quoted hunk, or the line of the spec. **A finding with no citation is dropped, not softened.** Sub-agent output is a hypothesis, not evidence: before a finding becomes a note, open the file and confirm the hunk says what the sub-agent claims. A note the human cannot check costs them a fix ticket for nothing.
|
|
60
81
|
|
|
61
|
-
|
|
82
|
+
Each surviving finding becomes its own note — step 3.
|
|
62
83
|
|
|
63
|
-
|
|
84
|
+
### 2. Drive the app — when there is something to drive
|
|
64
85
|
|
|
65
|
-
|
|
86
|
+
Skip this whole step when the ticket says there is nothing to run. Otherwise:
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
**Boot the app.** Call `mcp__runcastle__review_drive({ action: "start" })`. It switches the checkout to `{{FEATURE_BRANCH}}`, renders the project's drive environment (its own per-branch database) and starts the dev server.
|
|
68
89
|
|
|
69
|
-
|
|
90
|
+
The URL is **not** ready when `start` returns — the dev server has to print it first. Poll `mcp__runcastle__review_drive({ action: "status" })` until `drive.devUrl` appears, waiting a few seconds between calls. Give it a couple of minutes before you conclude it is never coming.
|
|
91
|
+
|
|
92
|
+
A refusal is **final** — a dirty tree, or a drive the human is already running. Do not retry it in a loop. It no longer sinks the review, though: your code review already ran and still has to land. Write a note saying the drive could not start and why, say it again in the summary note and the digest, and carry on to step 6.
|
|
93
|
+
|
|
94
|
+
**Walk the app with `agent-browser`.** Its core loop, which you repeat:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
agent-browser open <devUrl> # once, to start
|
|
98
|
+
agent-browser record start {{WALKTHROUGH_PATH}} # then, immediately — see below
|
|
99
|
+
agent-browser snapshot -i # the page as interactive elements, each with an @eN ref
|
|
100
|
+
agent-browser click @e7 # act on a ref from the snapshot you just took
|
|
101
|
+
agent-browser snapshot -i # RE-SNAPSHOT — see below
|
|
102
|
+
agent-browser wait --load networkidle # after a navigation, before snapshotting
|
|
103
|
+
agent-browser record stop # before you close, always
|
|
104
|
+
agent-browser close # when you are done driving
|
|
105
|
+
```
|
|
70
106
|
|
|
71
|
-
|
|
107
|
+
**Record the walkthrough.** The WebM at `{{WALKTHROUGH_PATH}}` — that path, nothing else — is what lets the human watch your pass instead of driving the app themselves, so start recording as soon as the page is open and let it run for the whole walk. Two rules about it:
|
|
72
108
|
|
|
73
|
-
|
|
109
|
+
- **A recording failure never fails the review.** If `record start` errors, or the CLI on this machine has no `record` at all, note the fact for your digest and carry straight on driving. The notes are the deliverable; the video is evidence.
|
|
110
|
+
- **Always `record stop` where you stop the drive.** It belongs in the same cleanup as step 5, on every path including the failure one — a recorder you leave running outlives you, and its file may be unplayable.
|
|
74
111
|
|
|
75
|
-
|
|
112
|
+
**`@eN` refs go stale on any page change.** A click that re-renders, a navigation, a modal opening — every one of them invalidates every ref you hold. Re-snapshot after each, and act only on refs from the newest snapshot. Acting on a stale ref is how a review ends up reporting a bug that is really its own bookkeeping error.
|
|
76
113
|
|
|
77
|
-
|
|
114
|
+
Work the ticket's `acceptanceCriteria` one at a time: reach the part of the app each one names, do the thing, and read the result off a fresh snapshot. Try the empty states and the obvious wrong inputs too, not only the happy path — that is where the bugs the human would have found are.
|
|
115
|
+
|
|
116
|
+
### 3. Write one note per finding, as you go
|
|
117
|
+
|
|
118
|
+
`mcp__runcastle__add_test_note({ text: "..." })`, for findings from **both** steps — a cited Standards or Spec finding is a note exactly as a drive bug is.
|
|
119
|
+
|
|
120
|
+
Each note is one observation the human can reproduce without you: **what you did, what happened, what you expected**. For a drive finding, name the screen and the steps. For a code-review finding, name the file and the citation that came with it — the standard and its rule, the smell and its hunk, or the spec line — and say which axis it came from.
|
|
121
|
+
|
|
122
|
+
Keep them separate — **one note per finding** — because each one can be promoted to a fix ticket in a click, and a note bundling three findings makes a bad ticket.
|
|
123
|
+
|
|
124
|
+
Write notes as you find things, not in a batch at the end: a note you have sent survives an iteration that ends early, and one you were saving up does not.
|
|
125
|
+
|
|
126
|
+
### 4. Close with a summary note
|
|
127
|
+
|
|
128
|
+
One last `add_test_note` covering the pass as a whole: what the code review found per axis (counts, and the worst issue *within each axis* — never one winner across the two), which criteria you verified by driving and how, what you could not reach and why. This is the note the human reads first among the notes.
|
|
129
|
+
|
|
130
|
+
If some of this feature's implementation tickets failed — you are reviewing it anyway, on purpose, and the signature is a surface that is missing outright rather than misbehaving — say so here: the human must know they are reading a review of a partially-built feature.
|
|
131
|
+
|
|
132
|
+
### 5. Stop the drive
|
|
133
|
+
|
|
134
|
+
Only if you started one. `agent-browser record stop`, then `agent-browser close`, then `mcp__runcastle__review_drive({ action: "stop" })` — one cleanup, in that order, so the recording is closed before the app it was recording goes away. Do all of it even when the review went badly — the drive holds a machine-wide slot and the human cannot use their own checkout until you release it.
|
|
135
|
+
|
|
136
|
+
### 6. Write your digest — "What landed this lap"
|
|
137
|
+
|
|
138
|
+
At exactly:
|
|
139
|
+
|
|
140
|
+
`{{DIGEST_PATH}}`
|
|
141
|
+
|
|
142
|
+
**This is not a review log. It is the lap's summary, written for a human, and the review page renders it verbatim as the first thing they read.** They arrive at the review screen wanting one question answered — *what did this lap actually do?* — and you are the agent best placed to answer it: you ran last, you hold the spec, you have every implementer's own account above, and you have just read every commit and every hunk on the branch. Synthesizing those into one honest paragraph is work only you can do; none of the implementers could see past its own ticket.
|
|
143
|
+
|
|
144
|
+
So write **prose**, roughly 10–15 lines, for a reader who has none of your context:
|
|
145
|
+
|
|
146
|
+
- **What the lap delivered**, in the language of the product, not the codebase. "The tickets ledger and the notes panel now group by lap, with prior laps collapsed" — not "modified TicketList.tsx and NotesPanel.tsx".
|
|
147
|
+
- **Synthesize, don't enumerate.** Not a ticket-by-ticket walk, not a changed-files list, not a commit log — those all exist elsewhere on the page. Say what the lap adds up to, and where the shape that landed differs from what the spec promised.
|
|
148
|
+
- **Say what it means for them**: what they can now do that they could not before, and what is worth their attention — the thing the drive was rough at, the deferred scope, the criterion you could not verify.
|
|
149
|
+
- **Plain sentences.** No headings, no bullet lists, no "I did X then Y", no tool names, no `<promise>` markers, no acceptance-criteria checklists. If it reads like an agent's log, rewrite it as something you would say out loud.
|
|
150
|
+
|
|
151
|
+
Findings belong in the notes, not here. One honest line about the headline problem is right; the catalogue is not.
|
|
152
|
+
|
|
153
|
+
Write it at that path and nowhere else — **never inside the repo**, which is the human's real working tree. This is the last thing you do before signalling COMPLETE.
|
|
78
154
|
|
|
79
155
|
## Could not review
|
|
80
156
|
|
|
81
|
-
|
|
157
|
+
The bar is now high, because the code review needs almost nothing: no app, no browser, no drive slot. Finding bugs is not failure, and **neither is a drive that would not start** — that is a note plus a line in the digest, and the review still delivered.
|
|
158
|
+
|
|
159
|
+
The review **failed** only when the code review itself could not run:
|
|
82
160
|
|
|
83
|
-
- the
|
|
84
|
-
- the
|
|
85
|
-
- `agent-browser` will not run, or the app will not load at all.
|
|
161
|
+
- the repository is unreadable, or `{{BASE_BRANCH}}` / `{{FEATURE_BRANCH}}` cannot be resolved at all,
|
|
162
|
+
- `git diff {{BASE_BRANCH}}...{{FEATURE_BRANCH}}` is empty *and* `{{FEATURE_BRANCH}}` does not exist. An empty diff on a branch that does exist is a **finding**, not a failed review — write the note and carry on.
|
|
86
163
|
|
|
87
164
|
When that happens: run the step 5 cleanup for whatever you got as far as starting — recorder, browser, drive — write **`{{BLOCKED_PATH}}`** — that path, not the repo — saying in one or two sentences precisely what stopped you, and print `<promise>COMPLETE</promise>`. Write no digest; the blocked file is your record. Do not write notes speculating about a feature you never saw.
|
|
88
165
|
|
|
89
166
|
## Hard rules
|
|
90
167
|
|
|
168
|
+
- **Never skip the code review.** It runs on every lap, whatever the ticket asks for and whether or not the app can be driven. A lap nobody reviewed is the silence this ticket exists to end.
|
|
91
169
|
- **Never edit the repo.** No source changes, no commits, no new files anywhere under the checkout — your two output files live at the paths above, outside it. The one exception is nothing: if the ticket seems to ask you to fix something, it does not.
|
|
170
|
+
- **Never merge or re-rank the two review axes**, and never report a finding without its citation.
|
|
171
|
+
- **Never let a sub-agent spawn more agents.** The guard line goes in both briefs, every time.
|
|
92
172
|
- **Never leave the drive — or the recorder — running.** Stop both on every path, including the failure path.
|
|
93
|
-
- **Never report a finding you did not observe.** Every note traces to something you saw in a snapshot, a response body, or a
|
|
94
|
-
- **
|
|
173
|
+
- **Never report a finding you did not observe.** Every note traces to something you saw in a snapshot, a response body, a test run, or a hunk you opened and confirmed. A plausible-sounding bug that is really a stale ref — or an unverified sub-agent claim — costs the human a fix ticket for nothing.
|
|
174
|
+
- **Never write the digest as a log.** It is the lap's prose summary and it is rendered verbatim to the human.
|
|
175
|
+
- **Stay inside this ticket.** Review what it names and the diff it landed. Adjacent things you notice go in the summary note, not into a sprawl of speculative findings.
|