runcastle 1.6.3 → 1.6.4
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/README.md +4 -4
- package/bin/runcastle.js +13200 -61414
- package/drizzle/0057_jazzy_lila_cheney.sql +1 -0
- package/drizzle/0058_test_note_open_only.sql +9 -0
- package/drizzle/0059_milky_black_knight.sql +17 -0
- package/drizzle/0060_drop_prepare_sessions.sql +6 -0
- package/drizzle/0061_events_type_index.sql +4 -0
- package/drizzle/0062_workers.sql +30 -0
- package/drizzle/0063_worker_dev_server.sql +1 -0
- package/drizzle/0064_worker_tokens.sql +1 -0
- package/drizzle/0065_review_workers.sql +28 -0
- package/drizzle/0066_drive_workers.sql +39 -0
- package/drizzle/meta/0057_snapshot.json +1351 -0
- package/drizzle/meta/0059_snapshot.json +1461 -0
- package/drizzle/meta/0062_snapshot.json +1654 -0
- package/drizzle/meta/0063_snapshot.json +1662 -0
- package/drizzle/meta/0064_snapshot.json +1669 -0
- package/drizzle/meta/0065_snapshot.json +1690 -0
- package/drizzle/meta/0066_snapshot.json +1704 -0
- package/drizzle/meta/_journal.json +70 -0
- package/index.js +12480 -60694
- package/package.json +2 -3
- package/skills/packs/README.md +11 -15
- package/skills/packs/runcastle/.claude-plugin/plugin.json +1 -1
- package/skills/packs/runcastle/skills/code-review/SKILL.md +3 -3
- package/skills/packs/runcastle/skills/feature/SKILL.md +37 -18
- package/skills/packs/runcastle/skills/feature/references/design.md +102 -127
- package/skills/packs/runcastle/skills/feature/references/grilling.md +9 -3
- package/skills/packs/runcastle/skills/feature/references/planning-alone.md +69 -38
- package/skills/packs/runcastle/skills/feature/references/preview-starter.html +22 -20
- package/skills/packs/runcastle/skills/feature/references/preview.md +45 -27
- package/skills/packs/runcastle/skills/feature/references/spec.md +1 -1
- package/skills/packs/runcastle/skills/feature/references/workers.md +144 -0
- package/skills/packs/runcastle/skills/project/SKILL.md +64 -201
- package/skills/packs/runcastle/skills/project/references/charter.md +3 -3
- package/skills/packs/runcastle/skills/project/references/health-sweeps.md +3 -3
- package/skills/packs/runcastle/skills/project/references/intake.md +139 -0
- package/skills/packs/runcastle/skills/{prepare/references/recipes.md → project/references/preparation-recipes.md} +2 -2
- package/skills/packs/runcastle/skills/project/references/preparation.md +176 -0
- package/skills/packs/runcastle/skills/project/references/routing.md +39 -0
- package/skills/packs/runcastle/skills/project/references/triage.md +3 -3
- package/skills/packs/runcastle/skills/qa/SKILL.md +5 -7
- package/skills/review/review-drive.md +33 -0
- package/skills/review/review-gates.md +22 -0
- package/skills/review/review.md +61 -0
- package/skills/review/verify.md +31 -0
- package/web/assets/index-C0TYj0Ka.css +1 -0
- package/web/assets/index-C6JVtDiw.js +193 -0
- package/web/index.html +3 -3
- package/web/manifest.webmanifest +2 -2
- package/web/sw.js +4 -4
- package/skills/burner/implement-ticket.md +0 -118
- package/skills/burner/resolve-conflict.md +0 -78
- package/skills/burner/review-ticket.md +0 -268
- package/skills/burner/verify-fixes.md +0 -59
- package/skills/packs/runcastle/skills/feature/references/rounds.md +0 -97
- package/skills/packs/runcastle/skills/feature/references/tickets.md +0 -231
- package/skills/packs/runcastle/skills/prepare/SKILL.md +0 -148
- package/web/assets/index-CHxfwKvB.js +0 -193
- package/web/assets/index-CVCnjOHN.css +0 -1
package/web/index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content" />
|
|
6
|
-
<meta name="theme-color" content="#
|
|
6
|
+
<meta name="theme-color" content="#04060b" />
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
8
8
|
<link rel="manifest" href="/manifest.webmanifest" />
|
|
9
9
|
<link rel="apple-touch-icon" href="/icon-192.png" />
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
document.documentElement.dataset.theme = t
|
|
22
22
|
})()
|
|
23
23
|
</script>
|
|
24
|
-
<script type="module" crossorigin src="/assets/index-
|
|
25
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
24
|
+
<script type="module" crossorigin src="/assets/index-C6JVtDiw.js"></script>
|
|
25
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C0TYj0Ka.css">
|
|
26
26
|
</head>
|
|
27
27
|
<body>
|
|
28
28
|
<div id="root"></div>
|
package/web/manifest.webmanifest
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"start_url": "/",
|
|
5
5
|
"scope": "/",
|
|
6
6
|
"display": "standalone",
|
|
7
|
-
"background_color": "#
|
|
8
|
-
"theme_color": "#
|
|
7
|
+
"background_color": "#04060b",
|
|
8
|
+
"theme_color": "#04060b",
|
|
9
9
|
"icons": [
|
|
10
10
|
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
|
|
11
11
|
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
|
package/web/sw.js
CHANGED
|
@@ -17,21 +17,21 @@ const OFFLINE_PAGE = `<!doctype html>
|
|
|
17
17
|
<head>
|
|
18
18
|
<meta charset="utf-8">
|
|
19
19
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
20
|
-
<meta name="theme-color" content="#
|
|
20
|
+
<meta name="theme-color" content="#04060b">
|
|
21
21
|
<title>Runcastle</title>
|
|
22
22
|
<style>
|
|
23
23
|
:root { color-scheme: dark; }
|
|
24
24
|
body {
|
|
25
25
|
margin: 0; min-height: 100dvh; display: grid; place-content: center; gap: 16px;
|
|
26
26
|
padding: 24px; box-sizing: border-box; text-align: center;
|
|
27
|
-
background: #
|
|
27
|
+
background: #04060b; color: #eceef4;
|
|
28
28
|
font: 15px/22px system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
29
29
|
}
|
|
30
30
|
h1 { margin: 0; font-size: 17px; line-height: 24px; font-weight: 600; }
|
|
31
|
-
p { margin: 0; color: #
|
|
31
|
+
p { margin: 0; color: #a9b1c1; }
|
|
32
32
|
button {
|
|
33
33
|
justify-self: center; height: 44px; padding: 0 20px; border: 0; border-radius: 10px;
|
|
34
|
-
background: #eceef4; color: #
|
|
34
|
+
background: #eceef4; color: #04060b; font: inherit; font-weight: 500;
|
|
35
35
|
}
|
|
36
36
|
</style>
|
|
37
37
|
</head>
|
|
@@ -1,118 +0,0 @@
|
|
|
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
|
-
|
|
3
|
-
# Implement this ticket — unattended
|
|
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'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
|
-
|
|
7
|
-
## How you run
|
|
8
|
-
|
|
9
|
-
You run **non-interactively** — your agent CLI in print/exec mode, no terminal, no human — for up to a few fresh iterations:
|
|
10
|
-
|
|
11
|
-
- You are the only writer in this tree. Subagents may READ and REPORT only — they never edit, never run tests. Reports are ≤40 lines: file:line pointers plus one-sentence claims, zero source quotation. Tell subagents what you already searched so they do not repeat it.
|
|
12
|
-
- **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, typechecks) 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.
|
|
13
|
-
- **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.
|
|
14
|
-
- **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.
|
|
15
|
-
- **Amending is allowed on your private attempt branch.** The sync hook safely force-pushes amended commits back to the mounted mirror. Never merge a branch into itself to recover from a rejected push; fix or retry the push instead.
|
|
16
|
-
- **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.
|
|
17
|
-
- **Run tests as the repo defines them, narrowed to files or patterns.** Do not add `--maxWorkers`, `--pool`, `--shard`, `--runInBand`, or hand-split a run 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 run that takes ~55s at its configured concurrency takes **10–20 minutes** at `--maxWorkers=1`. You run the test files that cover your change, never the whole suite. If a run is killed for memory (exit 137), that is an environment fault — say so plainly in your final message and run fewer files, never the same run serialised.
|
|
18
|
-
|
|
19
|
-
## Where to work
|
|
20
|
-
|
|
21
|
-
{{WORKSPACE_NOTES}}
|
|
22
|
-
|
|
23
|
-
## Use your file tools, not the shell
|
|
24
|
-
|
|
25
|
-
You have `Read`, `Grep`, `Glob`, `Edit`, and `Write`. **Use them.** Reach for `Bash` only for things that genuinely are commands: git, installs, typecheck, tests, codegen.
|
|
26
|
-
|
|
27
|
-
This is not a style preference — it is one of the largest measured costs in real burns, where agents ran **1641 Bash calls and not one file tool**:
|
|
28
|
-
|
|
29
|
-
- **Reading.** `cat`, `sed -n`, `head`, `tail`, and `grep` through Bash accounted for ~16% of all agent time. `Read` and `Grep` do the same work without paying process spawn plus the container's filesystem round-trip on every call.
|
|
30
|
-
- **Editing.** Rewriting a file by piping a `python3 - <<'PY' … s.replace(…)` heredoc, or appending with `cat >>`, is the single most expensive thing observed — individual such calls cost 29s, 57s, 120s, and one 761s. `Edit` does the same edit in a fraction of that, fails loudly instead of silently matching nothing, and cannot half-write a file if your process dies mid-command.
|
|
31
|
-
|
|
32
|
-
If you catch yourself writing a heredoc to edit a file, stop and use `Edit`.
|
|
33
|
-
|
|
34
|
-
## The standards you are held to
|
|
35
|
-
|
|
36
|
-
{{PROJECT_STANDARDS}}
|
|
37
|
-
|
|
38
|
-
## Feature context
|
|
39
|
-
|
|
40
|
-
{{FEATURE_BRIEF}}
|
|
41
|
-
|
|
42
|
-
## Feature docs (spec / decisions digest)
|
|
43
|
-
|
|
44
|
-
{{DOCS_DIGEST}}
|
|
45
|
-
|
|
46
|
-
## How to verify
|
|
47
|
-
|
|
48
|
-
{{VERIFY_NOTES}}
|
|
49
|
-
|
|
50
|
-
Check only what you touched — a full suite on a monorepo is minutes of your budget, and in real burns running one two or three times per ticket was the largest single waste:
|
|
51
|
-
|
|
52
|
-
- **Report a gate that cannot run.** If the static check or test runner named by the project's commands cannot start at all because its command or required runtime is missing, write `GATE_UNRUNNABLE.md` at the sandbox root as JSON with the exact strings `{"command":"<exact command>","error":"<exact error>"}` (or a JSON array of those objects for multiple gates). Continue every other gate you can run. This is an environment report, never your ticket's failure, and it does not replace `BLOCKED.md` or `DIGEST.md`.
|
|
53
|
-
|
|
54
|
-
- **Capture, then read.** Redirect a run to a file (`<command> > /tmp/test-run.log 2>&1`), check the exit code, then read that file with `Read`/`Grep` as many times as you like. Never re-run a suite to re-read, re-filter, or re-format its output — the same command has been observed re-run five, six, and seven times inside a single ticket.
|
|
55
|
-
- **Never run the full test suite or the verify commands whole.** The host runs them once at the end of the round; a break elsewhere comes back to the lead as a fix round. You run the static check (the typecheck, or whatever plays that part in the project's commands: a compile, `go vet`, a linter) once across the project, *after* your work is committed (see "How to work", step 4) — narrowing it to one package only where the project's commands make that plain; a project with no static check gets only narrowed tests. For tests, run only the files or patterns that cover your change, as often as you need while you work.
|
|
56
|
-
- **Never `git stash` to get a clean-tree comparison.** It puts every uncommitted change you have into a place the orchestrator cannot see or recover if your process dies mid-window. If you need to compare against the pre-change state, use the baseline above, read the file at `HEAD` with `git show`, or — only if you genuinely need the old tree on disk — `git worktree add --detach <path> <sha>` a **detached** scratch checkout and `git worktree remove` it when done. Never `git worktree add <path> <branch>`: that checks the branch out, and a worktree left holding a branch is one nobody else can check it out in.
|
|
57
|
-
|
|
58
|
-
## How to work
|
|
59
|
-
|
|
60
|
-
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.
|
|
61
|
-
|
|
62
|
-
2. **Test at the seams, and only what earns a test.** A test is worth writing when it protects behaviour someone relies on that could plausibly break: logic with branches, a contract between modules, a bug just fixed (as its repro). Not worth writing: class names, copy text, static config, markup snapshots, tests that restate the implementation. A change with nothing worth testing gets no new test. Where one is worth writing, 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.
|
|
63
|
-
|
|
64
|
-
3. **Work through the criteria.** Implement the acceptance criteria one at a time. Verify each by a test where one is worth writing, otherwise by actually running the code / driving the demoable path, and state what you ran. Do not bulk-write tests for imagined behaviour, and do not recompute the expected value the way the code does (use known-good literals). You may delete or rewrite a test that fails the rule above only where your change already touches that test or the code it covers; leave useless tests elsewhere alone.
|
|
65
|
-
|
|
66
|
-
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.
|
|
67
|
-
|
|
68
|
-
So: **the moment a slice is green, commit it.** Aim for 3–4 slice commits per ticket; never commit a comment-only or formatting-only change on its own — fold it into the next slice. 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:
|
|
69
|
-
|
|
70
|
-
`ticket(<seq>): <summary>`
|
|
71
|
-
|
|
72
|
-
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.
|
|
73
|
-
|
|
74
|
-
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.
|
|
75
|
-
|
|
76
|
-
**Commit before the project-wide static check.** Long checks are the commands most likely to kill your process — in real burns they were OOM-killed mid-run, and every agent that ran them before committing lost a finished, passing fix. So once the targeted tests for your change pass, commit it *before* you start the project-wide static check. If it finds something, fix it in a follow-up commit.
|
|
77
|
-
|
|
78
|
-
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, as in step 4, once the targeted tests for those fixes pass, commit the fixes *before* you re-run the targeted tests and the static check:
|
|
79
|
-
- **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.
|
|
80
|
-
- **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.
|
|
81
|
-
|
|
82
|
-
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.
|
|
83
|
-
|
|
84
|
-
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:
|
|
85
|
-
- **What was done** — past tense, what you actually built, *including where it deviated* from the approach the ticket described. Say which tests you added and what each protects — or plainly that no new test was worth writing, and why.
|
|
86
|
-
- **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.
|
|
87
|
-
- **Left undone** — adjacent work you noticed and deliberately did not do, so the next agent inherits the observation instead of re-finding it.
|
|
88
|
-
|
|
89
|
-
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.
|
|
90
|
-
## Keep the drive machinery true
|
|
91
|
-
|
|
92
|
-
{{DRIVE_NOTES}}
|
|
93
|
-
|
|
94
|
-
## Hard rules
|
|
95
|
-
|
|
96
|
-
{{GUARD_NOTES}}
|
|
97
|
-
|
|
98
|
-
- **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.
|
|
99
|
-
- **Never `git stash`.** See "How to verify" — stashed work is invisible to the orchestrator and unrecoverable if your process dies.
|
|
100
|
-
- **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.
|
|
101
|
-
- **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.
|
|
102
|
-
- **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.
|
|
103
|
-
|
|
104
|
-
## Your ticket
|
|
105
|
-
|
|
106
|
-
Everything above is the standing brief for this burn. This is the part that is yours alone.
|
|
107
|
-
|
|
108
|
-
```json
|
|
109
|
-
{{TICKET_JSON}}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### What landed before you
|
|
113
|
-
|
|
114
|
-
{{BLOCKERS}}
|
|
115
|
-
|
|
116
|
-
{{FIX_NOTES}}
|
|
117
|
-
|
|
118
|
-
Now do it.
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
<!-- Rendered by the ticket-burner when a ticket's branch fails to land on the feature branch. Placeholder tokens are filled in by the workflow. -->
|
|
2
|
-
|
|
3
|
-
# Resolve a landing conflict — unattended
|
|
4
|
-
|
|
5
|
-
You already implemented a ticket. Your commits are on your branch, but they could **not** be landed on the feature branch: while you were working, sibling tickets landed their own commits and you both touched the same files.
|
|
6
|
-
|
|
7
|
-
Your one job now is to **merge the feature branch into your branch and resolve the conflicts**, so your work and theirs both survive. You are not re-implementing anything, and you are not starting new work.
|
|
8
|
-
|
|
9
|
-
There is **no human to ask** — everything you need is in this prompt and in the repo. This is the last automated attempt before a human is pulled in, so be careful, not fast.
|
|
10
|
-
|
|
11
|
-
## How you run
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
- **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.
|
|
17
|
-
- **Signal completion.** When the merge commit is in, print exactly `<promise>COMPLETE</promise>` as the last line of your final message. Do the same after writing `BLOCKED.md` (see hard rules).
|
|
18
|
-
- **Run the repo's test command as the repo defines it** — no `--maxWorkers`, `--pool`, `--shard`, or `--runInBand`. Serialising a suite has been measured at 10–20 minutes for work its configured concurrency does in under a minute.
|
|
19
|
-
- **Use `Read`, `Grep`, and `Edit`** for files; keep `Bash` for git, typecheck, and tests. Reading with `cat`/`sed` and editing with `python3` heredocs is among the largest measured time sinks in real burns.
|
|
20
|
-
|
|
21
|
-
## Where to work
|
|
22
|
-
|
|
23
|
-
{{WORKSPACE_NOTES}}
|
|
24
|
-
|
|
25
|
-
## The conflict
|
|
26
|
-
|
|
27
|
-
Merging your branch into `{{FEATURE_BRANCH}}` conflicts on:
|
|
28
|
-
|
|
29
|
-
{{CONFLICT_FILES}}
|
|
30
|
-
|
|
31
|
-
These commits landed on `{{FEATURE_BRANCH}}` while you were working — this is the other side of the merge, the work you must preserve alongside your own:
|
|
32
|
-
|
|
33
|
-
{{OTHER_SIDE}}
|
|
34
|
-
|
|
35
|
-
## The ticket you implemented
|
|
36
|
-
|
|
37
|
-
```json
|
|
38
|
-
{{TICKET_JSON}}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Feature context
|
|
42
|
-
|
|
43
|
-
{{FEATURE_BRIEF}}
|
|
44
|
-
|
|
45
|
-
## Feature docs (spec / decisions digest)
|
|
46
|
-
|
|
47
|
-
{{DOCS_DIGEST}}
|
|
48
|
-
|
|
49
|
-
## How to work
|
|
50
|
-
|
|
51
|
-
1. **Orient before touching anything.** `git log --oneline -15` to see your own commits, then read the conflicting files. Understand what *your* commits were doing there (the ticket above is the intent) and what the *other* side's commits were doing (the log above, plus `git show` on them).
|
|
52
|
-
|
|
53
|
-
2. **Start the merge:**
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
{{MERGE_COMMAND}}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
It will stop with conflicts. `git status` lists the unmerged paths.
|
|
60
|
-
|
|
61
|
-
3. **Resolve by intent, not by picking a side.** For every `<<<<<<< / ======= / >>>>>>>` hunk, keep *both* behaviours working. Reconcile the logic where the two sides genuinely overlap — the feature docs above are the tie-breaker on intent. Never delete one side's behaviour just to make the conflict go away, and never resolve by taking your whole side (`--ours`) or theirs (`--theirs`) wholesale unless one side is genuinely a strict superset.
|
|
62
|
-
|
|
63
|
-
4. **Watch for semantic conflicts, not just textual ones.** Files that merged cleanly can still be broken by the other side's rename, signature change, or moved export. After resolving the marked hunks, run typecheck and the tests covering both sides' touched code, and fix what the merge broke.
|
|
64
|
-
|
|
65
|
-
Spend your checks carefully: capture a run's output to a file (`<command> > /tmp/verify.log 2>&1`) and grep that file rather than re-running to re-read it. Never `git stash` mid-merge: it is unrecoverable if your process dies, and it will strand the merge state.
|
|
66
|
-
|
|
67
|
-
{{VERIFY_NOTES}}
|
|
68
|
-
|
|
69
|
-
5. **Complete the merge.** Stage the resolved files and commit — keep it a merge commit (`git commit --no-verify`, do not `--squash`, do not rebase, do not amend your earlier commits). A merge commit whose message names what was reconciled is ideal.
|
|
70
|
-
|
|
71
|
-
## Hard rules
|
|
72
|
-
|
|
73
|
-
{{GUARD_NOTES}}
|
|
74
|
-
|
|
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
|
-
- **Never lose committed work.** Not yours, not theirs. `git merge --abort` + "start over from one side" is not a resolution.
|
|
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 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,268 +0,0 @@
|
|
|
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 Gates mode in step 2b 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
|
-
|
|
3
|
-
# Review this feature — unattended
|
|
4
|
-
|
|
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
|
-
|
|
7
|
-
You review it in **exactly one of two modes**, and you pick which before you do anything else:
|
|
8
|
-
|
|
9
|
-
1. **Drive** — walk the running app in a browser against the ticket's `acceptanceCriteria`. For a lap with a surface a human could operate, when a drive is available.
|
|
10
|
-
2. **Gates** — read the server's gate results on the branch, then read the branch's diff along two axes. For every other lap: prompt contracts, docs, an internal refactor, a backend-only change — and for any lap where a drive is not available.
|
|
11
|
-
|
|
12
|
-
**One mode, never both — except the explicit drive-failure fallback below.** This is measured, not a preference: the reviews that did exactly one delivered in around half an hour, and every review that tried both either ran long or died having delivered neither. Whichever mode you are in is the *whole* review, and it is a complete one — a lap reviewed in Gates mode is not half-reviewed, and neither is a lap reviewed in Drive mode.
|
|
13
|
-
|
|
14
|
-
You leave two deliverables behind: the **findings** (one report per finding, typed) and the **digest** (the lap's account — see step 5; it is not an afterthought). Its first line is a standalone one-liner naming the lap, what landed, the counts and the mode you ran: that line is the one the review page renders on arrival, and the account behind it is what the human opens when they want more.
|
|
15
|
-
|
|
16
|
-
You are **not** implementing anything. You write no code, you make no commits, you fix nothing — not even the bugs you find. Every defect you report goes to the feature's lead when this round ends, and the lead decides how it is fixed; fixing one yourself would collide with that. Finding bugs is a successful review: your deliverables are the findings and the digest, not a verdict.
|
|
17
|
-
|
|
18
|
-
There is **no human to ask** — everything you need is in this prompt and in the repo.
|
|
19
|
-
|
|
20
|
-
## How you run
|
|
21
|
-
|
|
22
|
-
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:
|
|
23
|
-
|
|
24
|
-
- **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.
|
|
25
|
-
- **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.
|
|
26
|
-
- **Signal completion.** Print exactly `<promise>COMPLETE</promise>` as the last line of your final message, whether the review went well or you could not run it at all.
|
|
27
|
-
|
|
28
|
-
## The review ticket
|
|
29
|
-
|
|
30
|
-
```json
|
|
31
|
-
{{TICKET_JSON}}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
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.
|
|
35
|
-
|
|
36
|
-
**The ticket prescribes the drive, not the mode.** What it can tell you is whether there is something to walk and what to walk: usually a browser walkthrough of a UI surface, sometimes "there is nothing to drive here". Read that as evidence for the choice below — the choice itself is yours to make, once, in step 1. A ticket that says "use Gates" is evidence about whether there is a surface, nothing more: when its `acceptanceCriteria` name a screen, flow or state a human could operate and a drive is available, the mode is Drive, whatever the ticket's text tells you. And "the installed or host runcastle doesn't have this branch's code yet" is never a reason against a drive — the review pass runs on the installed runcastle, but `review_drive` boots `{{FEATURE_BRANCH}}` itself, its own code, migrations and assets. One real review obeyed a ticket that prescribed Gates on exactly that reason, and a UI feature reached the human without ever being driven.
|
|
37
|
-
|
|
38
|
-
## Whether a drive is available
|
|
39
|
-
|
|
40
|
-
{{DRIVE_AVAILABILITY}}
|
|
41
|
-
|
|
42
|
-
## The server's gate results
|
|
43
|
-
|
|
44
|
-
The server has already run this project's verify commands on `{{FEATURE_BRANCH}}` — whichever mode you pick, this is the gate record for the branch you are reviewing:
|
|
45
|
-
|
|
46
|
-
{{GATE_NOTES}}
|
|
47
|
-
|
|
48
|
-
## Feature context
|
|
49
|
-
|
|
50
|
-
{{FEATURE_BRIEF}}
|
|
51
|
-
|
|
52
|
-
## Feature docs (spec / decisions digest)
|
|
53
|
-
|
|
54
|
-
{{DOCS_DIGEST}}
|
|
55
|
-
|
|
56
|
-
## What the implementers say they did
|
|
57
|
-
|
|
58
|
-
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, exactly like a sub-agent's finding.
|
|
59
|
-
|
|
60
|
-
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.
|
|
61
|
-
|
|
62
|
-
{{LAP_DIGESTS}}
|
|
63
|
-
|
|
64
|
-
## How to work
|
|
65
|
-
|
|
66
|
-
### 1. Choose your mode — before anything else
|
|
67
|
-
|
|
68
|
-
Two questions, in this order:
|
|
69
|
-
|
|
70
|
-
1. **Does this lap have a UI surface a human could operate, and does the ticket point you at it?** The `acceptanceCriteria` naming screens, flows or empty states is a yes. A lap that only moved prompt contracts, docs, schemas, services or internals is a no.
|
|
71
|
-
2. **Is a drive available?** The section above answers this from the host — it is a fact, not a judgement.
|
|
72
|
-
|
|
73
|
-
Both yes → **Drive mode**, step 2a — even when the ticket's text tells you to use Gates. Anything else → **Gates mode**, step 2b. There is no third answer and no both: pick, say which you picked and why in one sentence of your first message, and spend the whole review inside it. When your pick goes against a mode the ticket states, that same sentence says so and gives the reason — for example, "Drive mode, against the ticket's Gates: the criteria name the settings screen and a drive is available."
|
|
74
|
-
|
|
75
|
-
### 2a. Drive mode — walk the app
|
|
76
|
-
|
|
77
|
-
{{DRIVE_INSTRUCTIONS}}
|
|
78
|
-
|
|
79
|
-
**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.
|
|
80
|
-
|
|
81
|
-
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.
|
|
82
|
-
|
|
83
|
-
**A refusal comes back classified — read `retriable` before you decide what to do about it.** There are two of them, and they want opposite things:
|
|
84
|
-
|
|
85
|
-
- **`deniedCode: "slot_held"` (`retriable: true`)** — somebody else holds the machine-wide drive slot: the human's own drive, another review's, or a preparation dry run. It frees itself when they finish, so wait it out. Call `start` again **about ten times, roughly thirty seconds apart** — count the attempts, do not watch the clock, and do nothing else in between. If one of them takes, you are driving; carry on. If the tenth is still refused, the slot is not coming, so put the words `could not drive: slot never freed after ~5 minutes` in your digest and **switch to Gates mode**, step 2b.
|
|
86
|
-
- **`deniedCode: "dirty"` (`retriable: false`)** — the human's own uncommitted files block the checkout switch, and no amount of waiting clears them. Do not call `start` a second time. Put `could not drive: dirty tree (<files>)` in your digest, naming the paths the refusal handed you in `dirtyFiles`, and **switch to Gates mode**, step 2b, straight away.
|
|
87
|
-
|
|
88
|
-
Either way: report an observation saying the drive could not start and which of the two it was, run the step 4 cleanup for whatever you got as far as starting, and spend the whole review in Gates mode. That is a complete review too. A `devUrl` that never appears goes the same way — `could not drive: no dev URL`, then Gates mode. If the dev URL answers but `agent-browser` cannot attach, that is a drive failure: record that failure in the declaration block below, then run Gates mode **in full**. This is the one explicit exception to the never-run-both-modes rule.
|
|
89
|
-
|
|
90
|
-
**Never build your own environment to drive in.** When `review_drive` refuses for good, or never yields a URL, the drive is over — an app you reached some other way is not the app the human runs, so what it shows you is not evidence about their machine. Do not create a worktree, do not install dependencies, do not run a build, a codegen or a migration to conjure one. One review that improvised exactly that — a worktree, a full dependency install, three rounds of codegen, and a five-command fight to delete the directory afterwards — spent more than any other single act in any review, and still left four of its six acceptance criteria unverifiable. Gates mode is what is left to you, and it is enough: read the server's gate results, read the diff along both axes, and report every criterion the drive would have covered as an observation saying it is unverified.
|
|
91
|
-
|
|
92
|
-
{{SCENARIOS}}
|
|
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
|
-
```
|
|
106
|
-
|
|
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:
|
|
108
|
-
|
|
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 findings are the deliverable; the video is evidence.
|
|
110
|
-
- **Always `record stop` where you stop the drive.** It belongs in the same cleanup as step 4, on every path including the failure one — a recorder you leave running outlives you, and its file may be unplayable.
|
|
111
|
-
|
|
112
|
-
**Take the screenshots.** The human may accept this feature from their phone, from your pictures alone, so they are a deliverable and not a nicety. Whenever a screen shows what you just checked, and at each scenario's entry page, call:
|
|
113
|
-
|
|
114
|
-
```
|
|
115
|
-
mcp__runcastle__review_drive({ action: "capture", caption: "the table fits the page width at A4" })
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Runcastle takes the picture of your browser and keeps it; you never write the file. The caption is one line saying what the picture shows, for someone who was not there. Take at least one per acceptance criterion a screen can show, and one of anything you report as a defect. A picture belongs to the latest scenario that landed; pass `outside: true` for one that belongs to none. If the criteria name a phone layout, resize the browser and capture that. A capture that fails never fails the review: carry on.
|
|
119
|
-
|
|
120
|
-
**`@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.
|
|
121
|
-
|
|
122
|
-
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.
|
|
123
|
-
|
|
124
|
-
Then go to step 3. **Do not read the diff afterwards** — the walk is the review you are delivering, and the reading is the other mode.
|
|
125
|
-
|
|
126
|
-
### 2b. Gates mode — the server's gate results, then the diff
|
|
127
|
-
|
|
128
|
-
No app, no browser, no drive slot: this mode needs only the repository.
|
|
129
|
-
|
|
130
|
-
**Start from the server's gate results** above — they are the gates, already run on the branch. Do not run any verify command yourself: this checkout is on `{{BASE_BRANCH}}`, so anything you run here tests the base's code, not the branch's.
|
|
131
|
-
|
|
132
|
-
A gate that failed is a defect like any other, and one worth the human's attention above the rest — name the command, quote the failure from its output file, make the command the repro step, and carry on to the diff rather than stopping there. A gate the server could not run at all is an observation.
|
|
133
|
-
|
|
134
|
-
**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}}`: the lap's merge moved the `{{FEATURE_BRANCH}}` ref without switching any checkout, so diffing `HEAD` here reads an empty diff on a perfectly healthy lap.
|
|
135
|
-
|
|
136
|
-
Three-dot, so you see the branch's own work and not everything that landed on the base meanwhile:
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
git log {{BASE_BRANCH}}...{{FEATURE_BRANCH}} --oneline # the commits under review
|
|
140
|
-
git diff {{BASE_BRANCH}}...{{FEATURE_BRANCH}} # the diff both axes read
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
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 reporting.
|
|
144
|
-
|
|
145
|
-
**Read the branch; never check it out, and never leave a worktree holding it.** Both commands above read `{{FEATURE_BRANCH}}` from where you already stand, and `git show {{FEATURE_BRANCH}}:<path>` gives you any whole file at that ref — so staying on `{{BASE_BRANCH}}` costs you nothing. Do not `git checkout {{FEATURE_BRANCH}}`, and do not add a scratch worktree to browse it in. If you truly cannot answer a question without the files on disk, there is exactly one acceptable shape:
|
|
146
|
-
|
|
147
|
-
```
|
|
148
|
-
git worktree add --detach <path> <sha> # DETACHED — never `git worktree add <path> <branch>`
|
|
149
|
-
git worktree remove <path> # before you finish, on every path out
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
`git worktree add <path> <branch>` **checks that branch out**, which makes your scratch directory the branch's one holder — and this is the human's real machine, where runcastle's talk worktree has to hold `{{FEATURE_BRANCH}}` to open the next session on it. A review that left `<repo>/.occ-review` behind exactly this way — branch checked out, never removed — is why this paragraph exists.
|
|
153
|
-
|
|
154
|
-
**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.
|
|
155
|
-
|
|
156
|
-
Mysterious Name · Duplicated Code · Feature Envy · Data Clumps · Primitive Obsession · Repeated Switches · Shotgun Surgery · Divergent Change · Speculative Generality · Message Chains · Middle Man · Refused Bequest.
|
|
157
|
-
|
|
158
|
-
**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.
|
|
159
|
-
|
|
160
|
-
- **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. Judge tests by one rule: a test is worth its place when it protects behaviour someone relies on that could plausibly break (branching logic, a contract between modules, a bug just fixed as its repro) — not class names, copy text, static config, markup snapshots, or tests that restate the implementation. Report a missing test only where the diff adds behaviour worth protecting that nothing covers, and a test not worth its place as a low-severity observation. Under 400 words. Do not spawn further agents — perform this review yourself."*
|
|
161
|
-
- **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."*
|
|
162
|
-
|
|
163
|
-
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.
|
|
164
|
-
|
|
165
|
-
**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 you report a finding, open the file and confirm the hunk says what the sub-agent claims. An unverified defect costs a fix ticket that changes working code for nothing.
|
|
166
|
-
|
|
167
|
-
Each surviving finding is reported on its own — step 3.
|
|
168
|
-
|
|
169
|
-
### 3. Report one finding at a time, as you go
|
|
170
|
-
|
|
171
|
-
```
|
|
172
|
-
mcp__runcastle__report_finding({
|
|
173
|
-
kind: "defect" | "observation",
|
|
174
|
-
severity: "high" | "medium" | "low",
|
|
175
|
-
title: "one line, the whole finding in a glance",
|
|
176
|
-
location: "path/to/file.ts hunk, or: screen + the steps that reach it",
|
|
177
|
-
citation: "the standard + rule, the smell + hunk, the spec line, or the gate command",
|
|
178
|
-
detail: "what you did, what happened, what you expected",
|
|
179
|
-
reproStep: "the exact steps or command that shows it — mandatory for a defect"
|
|
180
|
-
})
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
Findings from **either** mode go through it — a cited Standards or Spec finding, or a failed gate, is a finding exactly as a drive bug is. One call per finding, never one call carrying three. Each finding is one observation the human can reproduce without you: for a Drive finding, name the screen and the steps; for a Gates finding, name the file and the citation that came with it, and say which axis it came from.
|
|
184
|
-
|
|
185
|
-
**A finding is a `defect` when the human's problem is not actually solved** — even when every acceptance criterion passes. The criteria describe the change that was asked for; the defect boundary is the outcome the human wanted. The canonical case, from a real project: the fix lands correctly at the store seam, but the controller endpoint builds its own response map and never calls the repaired path, so the operator-facing surface shows exactly what it showed before. Every criterion passes and the problem is untouched — that is a defect, and its `reproStep` is the human's own path to the unchanged surface.
|
|
186
|
-
|
|
187
|
-
**Tests follow the same worth rule as the implementers'.** A missing test is a finding only where the diff adds behaviour worth protecting that nothing covers (a fixed bug with no repro is the clearest case) — never raise a missing test for class names, copy text, static config, markup, or logic with nothing that could plausibly break. A test that is not worth its place is a low-severity `observation`, never a `defect`: a defect becomes fix work of its own, and a whole sandbox is too much to spend deleting a few lines. The lead folds such observations into a fix ticket the round needs anyway.
|
|
188
|
-
|
|
189
|
-
A defect is also what a fix ticket can act on, so it carries something to act *from*: a cited hunk, a failing gate, or a drive step that reproduces. Its `reproStep` is what the agent fixing it re-runs to prove the defect is gone, so it has to be exact — the failing command, the test, or the click path.
|
|
190
|
-
|
|
191
|
-
**`observation` is everything else**, and that is a narrow bucket: your summary of the pass, scope deliberately deferred, something you could not verify, a warning that a surface is only half built, and findings that are clearly inert — an environment quirk like "no local Maven on the host", metric drift trivia. **Unsure whether the human's problem is solved → defect.** Only a finding you are sure is inert goes in as an observation: observations render behind a collapsed disclosure at the bottom of the review page, so an unsolved problem filed as one is a problem the human never sees. A false defect costs a fix ticket; a misfiled real one costs the feature.
|
|
192
|
-
|
|
193
|
-
**Severity is `high` when an acceptance criterion is unmet, when data/flow is broken, or when every criterion passes and the human's problem is still not solved**; `medium` for a real defect that is not that, `low` for the rest. It orders and labels; it never gates.
|
|
194
|
-
|
|
195
|
-
**Report defects highest severity first.** Your defects reach the feature's lead in the order you report them, and the lead reads them in that order to decide what gets fixed first.
|
|
196
|
-
|
|
197
|
-
Report as you find things, never in a batch at the end: a finding you have sent survives an iteration that ends early, and one you were saving up does not. There is no closing wrap-up call — the digest (step 5) is the summary, and your observations render inside the page's collapsed *Full account* disclosure, alongside the digest's long account.
|
|
198
|
-
|
|
199
|
-
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 — report that as an observation and say it in the digest too: the human must know they are reading a review of a partially-built feature.
|
|
200
|
-
|
|
201
|
-
### 4. Stop the drive
|
|
202
|
-
|
|
203
|
-
Only if you started one, so only in Drive mode — and also on the path where the drive refused or you abandoned it for Gates mode. `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.
|
|
204
|
-
|
|
205
|
-
### 5. Write your digest — "What landed this lap"
|
|
206
|
-
|
|
207
|
-
At exactly:
|
|
208
|
-
|
|
209
|
-
`{{DIGEST_PATH}}`
|
|
210
|
-
|
|
211
|
-
**Open with one short line naming the mode**, the lap, what it landed and the counts — a single standalone line, then a blank line, then the rest. The review page lifts that first line out and renders it alone as the lap account, so it has to stand on its own: one line, no heading, no markdown, no lead-in, and nothing that reads as half a sentence needing the paragraph after it.
|
|
212
|
-
|
|
213
|
-
Its shape — `Lap <N>: <what landed, in the language of the product> · <X> defects found, <Y> fixed in-run · <Drive|Gates> mode`:
|
|
214
|
-
|
|
215
|
-
```
|
|
216
|
-
Lap 2: the review page now opens on state and one action · 3 defects found, 2 fixed in-run · Gates mode
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
The counts are the ones you know as you write: how many defects you reported, and how many of those were fixed in-run — `0`, since nothing burns a fix while the round is still running; the lead fixes them afterwards. The lap number is `lap` on `mcp__runcastle__get_feature_context`. If you could not drive, the line still names the mode you actually ran.
|
|
220
|
-
|
|
221
|
-
**The rest is not a review log. It is the lap's long account**, and the page keeps it behind a collapsed *Full account* disclosure at the bottom, with your observations — it is what the human opens when the one-liner is not enough, so it is neither the first thing they read nor rendered verbatim at the top. Write it anyway and write it well: they arrive 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, and you have every implementer's own account above.
|
|
222
|
-
|
|
223
|
-
So write **prose**, roughly 10–15 lines, for a reader who has none of your context:
|
|
224
|
-
|
|
225
|
-
- **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".
|
|
226
|
-
- **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.
|
|
227
|
-
- **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. Discuss the server's gate results — which commands passed, which failed or could not run, and on which sha — and in Gates mode the worst issue *within each axis* — never one winner across the two. If the drive never started, say why in the words step 2a gives you — `could not drive: slot never freed after ~5 minutes` or `could not drive: dirty tree (<files>)` — so the human knows at a glance whether the machine was busy or their own uncommitted files are what stopped it.
|
|
228
|
-
- **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.
|
|
229
|
-
|
|
230
|
-
Findings belong in `report_finding`, not here — your observations sit inside the same disclosure as this prose, so repeating them costs the reader twice. One honest line about the headline problem is right; the catalogue is not.
|
|
231
|
-
|
|
232
|
-
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.
|
|
233
|
-
|
|
234
|
-
End `DIGEST.md` with exactly this machine-readable declaration block. Name the mode you actually completed. The reason must be one line and is required for an unverified pass (and should record a failed Drive fallback even when the full Gates run verified the pass):
|
|
235
|
-
|
|
236
|
-
```
|
|
237
|
-
REVIEW-MODE: drive|gates
|
|
238
|
-
REVIEW-VERDICT: verified|unverified
|
|
239
|
-
REVIEW-REASON: <one line; required when unverified>
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
## Could not review
|
|
243
|
-
|
|
244
|
-
The bar is high, because Gates mode 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 an observation, a switch to Gates mode, and a line in the digest saying which refusal it was: a slot that never freed after your ten attempts, or a dirty tree you were never going to be able to wait out.
|
|
245
|
-
|
|
246
|
-
The review **failed** only when Gates mode itself could not run and so there was nowhere to fall back to:
|
|
247
|
-
|
|
248
|
-
- the repository is unreadable, or `{{BASE_BRANCH}}` / `{{FEATURE_BRANCH}}` cannot be resolved at all,
|
|
249
|
-
- `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 — report it and carry on.
|
|
250
|
-
|
|
251
|
-
When that happens: run the step 4 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 report findings speculating about a feature you never saw.
|
|
252
|
-
|
|
253
|
-
## Hard rules
|
|
254
|
-
|
|
255
|
-
- **Never run both modes, except for the explicit drive-failure fallback above.** Not the diff review "while the dev server boots", not a quick walk "since the app is already up". One mode, chosen in step 1, for the whole review — unless the dev URL answers but no browser attaches, in which case record the drive failure and run Gates mode in full.
|
|
256
|
-
- **Never skip the review.** Every lap gets one of the two modes, 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.
|
|
257
|
-
- **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.
|
|
258
|
-
- **Never merge or re-rank the two review axes**, and never report a finding without its citation.
|
|
259
|
-
- **Never let a sub-agent spawn more agents.** The guard line goes in both briefs, every time.
|
|
260
|
-
- **Never leave the drive — or the recorder — running.** Stop both on every path, including the one where you gave up on the drive.
|
|
261
|
-
- **Never leave a worktree holding `{{FEATURE_BRANCH}}`.** Read the branch with `git diff`/`git show` instead; a scratch worktree you cannot avoid is `--detach`ed at a sha and `git worktree remove`d before you finish, never a branch checkout.
|
|
262
|
-
- **Never build your own environment.** No worktrees, no dependency installs, no builds, no generated artifacts. If `review_drive` did not hand you the app, the drive did not happen: say `could not drive:` and which refusal it was, run Gates mode — the server's gate results and the diff — and leave it there.
|
|
263
|
-
- **Never run the verify commands**, in either mode. Not a typecheck, not a test suite, not "just one file": the checkout you are in is the human's, on `{{BASE_BRANCH}}` outside a drive, so what you run there tests the base's code and proves nothing about the branch. The server's gate run is the only gate result.
|
|
264
|
-
- **Never wait out a refusal that will not clear.** The ten polls are for `retriable: true` — a held slot — and nothing else. Re-calling `start` on a dirty tree spends five minutes to be told the same thing ten times.
|
|
265
|
-
- **Never report a finding you did not observe.** Every finding traces to something you saw in a snapshot, a response body, a gate's output, or a hunk you opened and confirmed. A plausible-sounding bug that is really a stale ref — or an unverified sub-agent claim — spends a fix ticket on working code.
|
|
266
|
-
- **Never report a defect without a repro step**, and never one you would not stake a code change on. If it does not reproduce, it is an observation.
|
|
267
|
-
- **Never write the digest as a log.** Its first line is the standalone lap account the page renders on arrival; after it comes the lap's prose summary, which the human opens behind a disclosure.
|
|
268
|
-
- **Stay inside this ticket.** Review what it names and the lap it landed. Adjacent things you notice are observations, not a sprawl of speculative defects.
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# Verification pass — tour and verify
|
|
2
|
-
|
|
3
|
-
You are running a bounded verification pass on `{{FEATURE_BRANCH}}` against `{{BASE_BRANCH}}`. The pass you verify is {{VERIFIES_PASS}}. The mode is inherited, not chosen afresh — with one exception. A Gates that pass took only because its ticket told it to is not inherited blindly: the review's own rule applies, so when the acceptance criteria name a screen, flow or state a human could operate and a drive is available, the mode is Drive. "The installed or host runcastle doesn't have this branch's code" is never a reason against a drive, because `review_drive` boots `{{FEATURE_BRANCH}}` itself.
|
|
4
|
-
|
|
5
|
-
{{DRIVE_AVAILABILITY}}
|
|
6
|
-
|
|
7
|
-
## How you run
|
|
8
|
-
|
|
9
|
-
You run **non-interactively** — your agent CLI in print/exec mode, no terminal, no human. So:
|
|
10
|
-
|
|
11
|
-
- **Signal completion.** Print exactly `<promise>COMPLETE</promise>` as the last line of your final message, whether the pass went well or you could not run it at all. It is the only completion signal there is, and it is read from your message — a marker written into a file is invisible, and without one in your message this same pass runs again from the top.
|
|
12
|
-
|
|
13
|
-
## What landed
|
|
14
|
-
|
|
15
|
-
{{LANDED_FIXES}}
|
|
16
|
-
|
|
17
|
-
## Context
|
|
18
|
-
|
|
19
|
-
Ticket:
|
|
20
|
-
|
|
21
|
-
{{TICKET_JSON}}
|
|
22
|
-
|
|
23
|
-
Feature:
|
|
24
|
-
|
|
25
|
-
{{FEATURE_BRIEF}}
|
|
26
|
-
|
|
27
|
-
Project docs digest:
|
|
28
|
-
|
|
29
|
-
{{DOCS_DIGEST}}
|
|
30
|
-
|
|
31
|
-
Run digests:
|
|
32
|
-
|
|
33
|
-
{{LAP_DIGESTS}}
|
|
34
|
-
|
|
35
|
-
## Tour and verify
|
|
36
|
-
|
|
37
|
-
In **Drive mode**, call `mcp__runcastle__review_drive({ action: "start" })`, start the recorder at `{{WALKTHROUGH_PATH}}`, and walk every acceptance criterion's user-facing surface once at pace. Keep the recorder running for the whole tour. At each surface, once the screen shows what you checked, call `mcp__runcastle__review_drive({ action: "capture", caption })` with what it shows in one line: the human may accept the feature from these pictures alone. Scrutinise only the landed fixes: check whether each listed repro step still reproduces and inspect the surfaces those fixes touched. Report anything plainly broken during the tour, but do not hunt for unrelated defects. Stop the recorder and drive when finished. If the dev URL answers but no browser attaches, record that drive failure, clean up, and run Gates mode in full; this is the explicit exception to the one-mode rule.
|
|
38
|
-
|
|
39
|
-
{{DRIVE_INSTRUCTIONS}}
|
|
40
|
-
|
|
41
|
-
{{SCENARIOS}}
|
|
42
|
-
|
|
43
|
-
In **Gates mode**, read each fix diff against its finding and read the server's gate results for the fixed branch, below. Do not perform a second two-axis review of the whole branch. Read the branch from where you stand — `git diff {{BASE_BRANCH}}...{{FEATURE_BRANCH}}`, and `git show {{FEATURE_BRANCH}}:<path>` for a whole file: never check `{{FEATURE_BRANCH}}` out, and never leave a worktree holding it. A scratch worktree you cannot avoid is `git worktree add --detach <path> <sha>`, removed with `git worktree remove <path>` before you finish — never `git worktree add <path> <branch>`, which checks the branch out and leaves your directory holding it against the next session's launch.
|
|
44
|
-
|
|
45
|
-
Whichever mode you are in, the server has already run this project's verify commands on the fixed `{{FEATURE_BRANCH}}` — these are its results. **Never run a verify command yourself**: the checkout you are in is the human's, on `{{BASE_BRANCH}}` outside a drive, so it would test the base's code, not the fixes.
|
|
46
|
-
|
|
47
|
-
{{GATE_NOTES}}
|
|
48
|
-
|
|
49
|
-
Report findings through `mcp__runcastle__report_finding` as usual. Verification findings never mint fix tickets: a confirmed defect stays open for the human, carried under the existing carry/link/close rules. The ordinary review auto-fix cap is {{AUTO_FIX_CAP}}.
|
|
50
|
-
|
|
51
|
-
Write `{{DIGEST_PATH}}`. Its first line must name the mode you ran and say "verification pass" (for example, `Drive verification pass`) — and, when that is Drive over an inherited Gates, say why. Summarise which fixes held, which did not, and anything plainly broken on the tour. No `<promise>` markers inside the digest — the completion signal is a line in your message, never a line in this file. If the pass cannot run at all, write `{{BLOCKED_PATH}}` with the precise reason instead. Either way, the file you write is the last thing you do before signalling COMPLETE.
|
|
52
|
-
|
|
53
|
-
End the digest with exactly this machine-readable block. Name the mode actually completed; after a Drive failure followed by full Gates use `gates` and keep the drive failure in the one-line reason:
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
REVIEW-MODE: drive|gates
|
|
57
|
-
REVIEW-VERDICT: verified|unverified
|
|
58
|
-
REVIEW-REASON: <one line; required when unverified>
|
|
59
|
-
```
|