runcastle 1.2.6 → 1.2.7
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 +316 -85
- package/drizzle/0026_polite_vertigo.sql +2 -0
- package/drizzle/meta/0026_snapshot.json +921 -0
- package/drizzle/meta/_journal.json +7 -0
- package/index.js +312 -81
- package/package.json +1 -1
- package/skills/burner/review-ticket.md +118 -36
- package/skills/packs/README.md +6 -4
- 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/project/SKILL.md +38 -13
- package/skills/packs/runcastle/skills/tickets/SKILL.md +6 -1
- package/web/assets/index-B3xwZ1FR.css +1 -0
- package/web/assets/index-C0S4i8wY.js +142 -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
|
@@ -1,10 +1,17 @@
|
|
|
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}}`. 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
|
|
|
@@ -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
|
|
|
@@ -34,61 +43,134 @@ Its `goal` says what to verify and its `acceptanceCriteria` say how you will kno
|
|
|
34
43
|
|
|
35
44
|
## How to work
|
|
36
45
|
|
|
37
|
-
1.
|
|
46
|
+
### 1. Code review — always, and first
|
|
47
|
+
|
|
48
|
+
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.
|
|
49
|
+
|
|
50
|
+
**Pin the fixed point.** Resolve the base the branch forked from — the feature's `baseBranch` if you were handed one, else the repo's default branch (`git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`, else `master`). Then, three-dot, so you see the branch's own work and not everything that landed on base meanwhile:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
git log <base>...HEAD --oneline # the commits under review
|
|
54
|
+
git diff <base>...HEAD # the diff both axes read
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Confirm the ref resolves and the diff is non-empty **before** spawning anything. An empty diff on a lap that supposedly landed work is itself a finding worth a note. Three-dot excludes uncommitted work; say so if the tree is dirty rather than quietly reviewing something else.
|
|
58
|
+
|
|
59
|
+
**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. On top of those 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.
|
|
60
|
+
|
|
61
|
+
- **Mysterious Name** — a name that doesn't reveal what it does or holds. → rename it; if no honest name comes, the design's murky.
|
|
62
|
+
- **Duplicated Code** — the same logic shape in more than one hunk or file. → extract it, call it from both.
|
|
63
|
+
- **Feature Envy** — a method that reaches into another object's data more than its own. → move it onto the data it envies.
|
|
64
|
+
- **Data Clumps** — the same few fields or params keep travelling together. → bundle them into one type.
|
|
65
|
+
- **Primitive Obsession** — a primitive standing in for a domain concept. → give the concept its own small type.
|
|
66
|
+
- **Repeated Switches** — the same switch/if-cascade on the same type recurs. → polymorphism, or one shared map.
|
|
67
|
+
- **Shotgun Surgery** — one logical change forces scattered edits across many files. → gather what changes together.
|
|
68
|
+
- **Divergent Change** — one file edited for several unrelated reasons. → split so each module changes for one reason.
|
|
69
|
+
- **Speculative Generality** — abstraction or hooks for needs the spec doesn't have. → delete it.
|
|
70
|
+
- **Message Chains** — long `a.b().c().d()` navigation. → hide the walk behind one method.
|
|
71
|
+
- **Middle Man** — a function that mostly just delegates onward. → cut it, call the real target.
|
|
72
|
+
- **Refused Bequest** — an implementer that ignores most of what it inherits. → composition, not inheritance.
|
|
73
|
+
|
|
74
|
+
**Spawn both axes in parallel**, in one dispatch, so neither pollutes the other's context:
|
|
75
|
+
|
|
76
|
+
- **Standards** — hand it the diff command, the commit list, the standards files by path, and the smell baseline **pasted in full** (it has no other access to it). Brief: *"Report — per file/hunk where relevant — (a) every place the diff violates a documented standard: cite the standard (file + rule); (b) any baseline smell: name it and quote the hunk. Distinguish hard violations from judgement calls; a documented repo standard overrides the baseline. Skip what tooling enforces. Under 400 words. Do not spawn further agents — perform this review yourself."*
|
|
77
|
+
- **Spec** — hand it the diff command, the commit list, and the spec above (`spec.md` plus `decisions.md`; read `docs/features/<slug>/` if you need them unabridged). 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."*
|
|
78
|
+
|
|
79
|
+
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.
|
|
80
|
+
|
|
81
|
+
**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.
|
|
82
|
+
|
|
83
|
+
Each surviving finding becomes its own note — step 3.
|
|
38
84
|
|
|
39
|
-
|
|
85
|
+
### 2. Drive the app — when there is something to drive
|
|
40
86
|
|
|
41
|
-
|
|
87
|
+
Skip this whole step when the ticket says there is nothing to run. Otherwise:
|
|
42
88
|
|
|
43
|
-
|
|
89
|
+
**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.
|
|
44
90
|
|
|
45
|
-
|
|
46
|
-
agent-browser open <devUrl> # once, to start
|
|
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
|
-
```
|
|
91
|
+
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.
|
|
55
92
|
|
|
56
|
-
|
|
93
|
+
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.
|
|
57
94
|
|
|
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.
|
|
95
|
+
**Walk the app with `agent-browser`.** Its core loop, which you repeat:
|
|
60
96
|
|
|
61
|
-
|
|
97
|
+
```
|
|
98
|
+
agent-browser open <devUrl> # once, to start
|
|
99
|
+
agent-browser record start {{WALKTHROUGH_PATH}} # then, immediately — see below
|
|
100
|
+
agent-browser snapshot -i # the page as interactive elements, each with an @eN ref
|
|
101
|
+
agent-browser click @e7 # act on a ref from the snapshot you just took
|
|
102
|
+
agent-browser snapshot -i # RE-SNAPSHOT — see below
|
|
103
|
+
agent-browser wait --load networkidle # after a navigation, before snapshotting
|
|
104
|
+
agent-browser record stop # before you close, always
|
|
105
|
+
agent-browser close # when you are done driving
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**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:
|
|
109
|
+
|
|
110
|
+
- **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.
|
|
111
|
+
- **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.
|
|
112
|
+
|
|
113
|
+
**`@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.
|
|
114
|
+
|
|
115
|
+
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.
|
|
116
|
+
|
|
117
|
+
### 3. Write one note per finding, as you go
|
|
118
|
+
|
|
119
|
+
`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.
|
|
120
|
+
|
|
121
|
+
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.
|
|
62
122
|
|
|
63
|
-
|
|
123
|
+
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.
|
|
64
124
|
|
|
65
|
-
|
|
125
|
+
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.
|
|
66
126
|
|
|
67
|
-
|
|
127
|
+
### 4. Close with a summary note
|
|
68
128
|
|
|
69
|
-
|
|
129
|
+
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.
|
|
70
130
|
|
|
71
|
-
|
|
131
|
+
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.
|
|
72
132
|
|
|
73
|
-
|
|
133
|
+
### 5. Stop the drive
|
|
74
134
|
|
|
75
|
-
|
|
135
|
+
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.
|
|
76
136
|
|
|
77
|
-
|
|
137
|
+
### 6. Write your digest — "What landed this lap"
|
|
138
|
+
|
|
139
|
+
At exactly:
|
|
140
|
+
|
|
141
|
+
`{{DIGEST_PATH}}`
|
|
142
|
+
|
|
143
|
+
**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 only agent in the burn that can answer it: you ran last, you hold the spec, and you have just read every commit and every hunk on the branch.
|
|
144
|
+
|
|
145
|
+
So write **prose**, roughly 10–15 lines, for a reader who has none of your context:
|
|
146
|
+
|
|
147
|
+
- **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".
|
|
148
|
+
- **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.
|
|
149
|
+
- **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.
|
|
150
|
+
- **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.
|
|
151
|
+
|
|
152
|
+
Findings belong in the notes, not here. One honest line about the headline problem is right; the catalogue is not.
|
|
153
|
+
|
|
154
|
+
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
155
|
|
|
79
156
|
## Could not review
|
|
80
157
|
|
|
81
|
-
|
|
158
|
+
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.
|
|
159
|
+
|
|
160
|
+
The review **failed** only when the code review itself could not run:
|
|
82
161
|
|
|
83
|
-
- the
|
|
84
|
-
- the
|
|
85
|
-
- `agent-browser` will not run, or the app will not load at all.
|
|
162
|
+
- the repository is unreadable, or the base ref cannot be resolved at all,
|
|
163
|
+
- there is no diff between the base and `{{FEATURE_BRANCH}}` and no branch to review.
|
|
86
164
|
|
|
87
165
|
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
166
|
|
|
89
167
|
## Hard rules
|
|
90
168
|
|
|
169
|
+
- **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
170
|
- **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.
|
|
171
|
+
- **Never merge or re-rank the two review axes**, and never report a finding without its citation.
|
|
172
|
+
- **Never let a sub-agent spawn more agents.** The guard line goes in both briefs, every time.
|
|
92
173
|
- **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
|
-
- **
|
|
174
|
+
- **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.
|
|
175
|
+
- **Never write the digest as a log.** It is the lap's prose summary and it is rendered verbatim to the human.
|
|
176
|
+
- **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.
|
package/skills/packs/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Skill packs
|
|
2
2
|
|
|
3
|
-
**Packs** are runcastle-owned Claude Code *plugin directories* wired to runcastle's MCP contracts. They are injected into each launched terminal via `--plugin-dir`, so the human's Claude Code needs nothing preinstalled, and upstream changes to Matt Pocock's skills can never break us.
|
|
3
|
+
**Packs** are runcastle-owned Claude Code *plugin directories* wired to runcastle's MCP contracts. They are injected into each launched terminal via `--plugin-dir`, so the human's Claude Code needs nothing preinstalled, and upstream changes to Matt Pocock's skills can never break us. Seven of the nine skills are adapted forks of his methodology skills and each keeps a provenance header crediting the original; `revisit` and `waypoint` are original runcastle work.
|
|
4
4
|
|
|
5
5
|
## The `runcastle` pack
|
|
6
6
|
|
|
@@ -12,10 +12,11 @@ Scope-specific skills, each namespaced `/runcastle:<skill>`:
|
|
|
12
12
|
| `/runcastle:spec` | by ideate | synthesizes `spec.md`, completes the `spec` phase |
|
|
13
13
|
| `/runcastle:tickets` | by ideate | emits session-sized vertical-slice tickets via MCP, completes the `tickets` phase |
|
|
14
14
|
| `/runcastle:qa` | entry for `kind=qa` | read-only Q&A over an existing feature; never advances phases |
|
|
15
|
-
| `/runcastle:project` | entry for `kind=project` | project scope, not feature scope:
|
|
15
|
+
| `/runcastle:project` | entry for `kind=project` | project scope, not feature scope: consults the portfolio, advises on how a lump of intent should be cut into N features and creates them, routes, answers portfolio questions, curates advisory-only, and owns `CONTEXT.md` |
|
|
16
16
|
| `/runcastle:waypoint` | entry for `kind=waypoint` | *original* — works ONE waypoint on a mapped feature, writes its decision prose, resolves the waypoint |
|
|
17
17
|
| `/runcastle:converge` | entry for `kind=converge` | closes a mapped feature: reads only the compressed knowledge, then drives spec + tickets from it |
|
|
18
18
|
| `/runcastle:revisit` | entry for `kind=revisit` | *original* — folds late information into a finished feature; on a Rethink, runs the whole front half of a lap |
|
|
19
|
+
| `/runcastle:code-review` | by description, or by name | two-axis review (Standards + Spec) of a feature branch's diff against its base, run as parallel sub-agents and reported unmerged; never edits |
|
|
19
20
|
|
|
20
21
|
Layout (the verified plugin format — only `plugin.json` lives inside `.claude-plugin/`; `skills/` is a sibling at the plugin root):
|
|
21
22
|
|
|
@@ -33,7 +34,8 @@ packs/
|
|
|
33
34
|
├── project/SKILL.md
|
|
34
35
|
├── waypoint/SKILL.md
|
|
35
36
|
├── converge/SKILL.md
|
|
36
|
-
|
|
37
|
+
├── revisit/SKILL.md
|
|
38
|
+
└── code-review/SKILL.md
|
|
37
39
|
```
|
|
38
40
|
|
|
39
41
|
## How the launcher consumes a pack
|
|
@@ -44,7 +46,7 @@ The session launcher spawns Claude Code with the pack's **root** directory (the
|
|
|
44
46
|
claude ... --plugin-dir "<abs path>/packages/skills/packs/runcastle" ...
|
|
45
47
|
```
|
|
46
48
|
|
|
47
|
-
The `name` field in `plugin.json` becomes the invocation namespace, so the skills resolve as `/runcastle:ideate` and friends. The injected system prompt tells each session which entry skill to invoke for its kind (`/runcastle:ideate` for ideation, `/runcastle:qa` for Q&A). All
|
|
49
|
+
The `name` field in `plugin.json` becomes the invocation namespace, so the skills resolve as `/runcastle:ideate` and friends. The injected system prompt tells each session which entry skill to invoke for its kind (`/runcastle:ideate` for ideation, `/runcastle:qa` for Q&A). All nine skills carry `disable-model-invocation: false`, so the model may also reach for them by description — which is the only way `code-review` is reached, since it is the one skill that is not a session entry point.
|
|
48
50
|
|
|
49
51
|
## Adding a pack
|
|
50
52
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runcastle",
|
|
3
|
-
"description": "Runcastle phase-scoped skills for the MCP feature pipeline: ideate, spec, tickets, qa, project, converge (forks of Matt Pocock's grilling, to-spec and
|
|
3
|
+
"description": "Runcastle phase-scoped skills for the MCP feature pipeline: ideate, spec, tickets, qa, project, converge, code-review (forks of Matt Pocock's grilling, to-spec, to-tickets and code-review, adapted) plus revisit and waypoint (original).",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": { "name": "runcastle" }
|
|
6
6
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: code-review
|
|
3
|
+
description: Review a feature branch's diff against its base along two axes — Standards (does the code follow this repo's documented standards?) and Spec (does the code do what docs/features/<slug>/spec.md asked for?). Runs both axes as parallel sub-agents and reports them side by side, never merged. Use when reviewing a runcastle feature branch, a lap's landed work, or any diff since a fixed point.
|
|
4
|
+
disable-model-invocation: false
|
|
5
|
+
---
|
|
6
|
+
<!-- Forked from Matt Pocock's code-review skill, via https://github.com/mattpocock/skills, 2026-08-17, adapted for runcastle feature branches (base-branch fixed point, spec.md as the spec source) -->
|
|
7
|
+
|
|
8
|
+
# Code review — two axes over a feature branch
|
|
9
|
+
|
|
10
|
+
Two-axis review of the diff between the feature branch and the base it forked from:
|
|
11
|
+
|
|
12
|
+
- **Standards** — is it built right? Does the code follow how this repo writes code?
|
|
13
|
+
- **Spec** — is it the right thing? Does the code do what `spec.md` asked for?
|
|
14
|
+
|
|
15
|
+
Both axes run as **parallel sub-agents**, so neither sees the other's reasoning, and this skill aggregates the two reports without merging them.
|
|
16
|
+
|
|
17
|
+
You review. You **never fix** — not the bugs, not the smells, not the one-line rename that is obviously right. A review that edits the diff it is reviewing has stopped being a review.
|
|
18
|
+
|
|
19
|
+
## Why two axes
|
|
20
|
+
|
|
21
|
+
A change can pass one and fail the other:
|
|
22
|
+
|
|
23
|
+
- Code that follows every convention while implementing the wrong thing → **Standards pass, Spec fail.**
|
|
24
|
+
- Code that does exactly what the spec asked while breaking the repo's conventions → **Spec pass, Standards fail.**
|
|
25
|
+
|
|
26
|
+
Blending them lets the passing axis hide the failing one. That is the whole reason for the separation, so nothing downstream may re-rank across it.
|
|
27
|
+
|
|
28
|
+
## 1. Pin the fixed point
|
|
29
|
+
|
|
30
|
+
Unlike upstream, **nobody supplies the fixed point** — a runcastle feature branch has one by construction, and the review runs unattended. Resolve it, in this order, stopping at the first that works:
|
|
31
|
+
|
|
32
|
+
1. The feature's `baseBranch`, if you were handed it (the ticket, the feature context, the prompt that called you).
|
|
33
|
+
2. The repo's default branch — `git symbolic-ref --quiet --short refs/remotes/origin/HEAD`, else `main`, else `master`.
|
|
34
|
+
|
|
35
|
+
Then take the merge-base comparison, three-dot, so the diff is the branch's own work and not everything that landed on base meanwhile:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
git merge-base --is-ancestor <base> HEAD # sanity: does the ref resolve at all?
|
|
39
|
+
git log <base>...HEAD --oneline # the commits under review
|
|
40
|
+
git diff <base>...HEAD # the diff both axes read
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Check before you spawn anything.** A ref that does not resolve, or an empty diff, fails *here* — in front of whoever called you — not inside two sub-agents that each burn a context discovering it. Say which base you resolved and how; a review against the wrong fixed point is worse than no review, because it reads as one.
|
|
44
|
+
|
|
45
|
+
Three-dot excludes uncommitted work. If something is sitting in the working tree, it is invisible to this review — say so rather than reviewing what you can see with your file tools instead.
|
|
46
|
+
|
|
47
|
+
## 2. Find the spec
|
|
48
|
+
|
|
49
|
+
runcastle features always have one, so there is no "no spec available" path to reach for first:
|
|
50
|
+
|
|
51
|
+
1. `docs/features/<slug>/spec.md` — the requirements. This is the primary source.
|
|
52
|
+
2. `docs/features/<slug>/decisions.md` — *why* each shape was chosen. Read it: half the Spec-axis findings that look like scope creep are a locked decision the spec states tersely.
|
|
53
|
+
3. A spec digest already pasted into your prompt — use it verbatim rather than re-reading, if it is the whole document.
|
|
54
|
+
|
|
55
|
+
Only if all of those are genuinely absent does the Spec axis skip, and then it reports "no spec available" rather than inferring requirements from the code it is meant to be checking.
|
|
56
|
+
|
|
57
|
+
Review against the spec **as written**. If the diff is good work that the spec did not ask for, that is a Spec finding (scope creep), not a compliment.
|
|
58
|
+
|
|
59
|
+
## 3. Gather the standards
|
|
60
|
+
|
|
61
|
+
Whatever this repo documents about how its code should be written:
|
|
62
|
+
|
|
63
|
+
- `CLAUDE.md` — the repo's own agent-facing conventions, and the highest authority here.
|
|
64
|
+
- `CONTEXT.md` — the charter: the vocabulary and the principles it will not violate.
|
|
65
|
+
- `docs/adr/` — live ADRs bind the code. A superseded one does not; check before you cite.
|
|
66
|
+
- Anything else the repo keeps for the purpose (`CONTRIBUTING.md`, a coding-standards doc).
|
|
67
|
+
|
|
68
|
+
On top of those, the Standards axis always carries the **smell baseline** below, so it still has a floor on a repo that documents nothing. Two rules bind it:
|
|
69
|
+
|
|
70
|
+
- **The repo overrides.** A documented standard always wins. Where the repo endorses something the baseline would flag, suppress the smell.
|
|
71
|
+
- **Always a judgement call.** Each smell is a labelled heuristic ("possible Feature Envy"), never a hard violation — and, like any standard here, skip anything tooling already enforces. Do not report what the linter, the formatter or `tsc` would have caught; they ran, and they are not the reason a human is reading you.
|
|
72
|
+
|
|
73
|
+
Each smell reads *what it is* → *how to fix*, so a finding arrives with a move attached instead of a complaint:
|
|
74
|
+
|
|
75
|
+
- **Mysterious Name** — a function, variable, or type whose name doesn't reveal what it does or holds. → rename it; if no honest name comes, the design's murky.
|
|
76
|
+
- **Duplicated Code** — the same logic shape appears in more than one hunk or file in the change. → extract the shared shape, call it from both.
|
|
77
|
+
- **Feature Envy** — a method that reaches into another object's data more than its own. → move the method onto the data it envies.
|
|
78
|
+
- **Data Clumps** — the same few fields or params keep travelling together (a type wanting to be born). → bundle them into one type, pass that.
|
|
79
|
+
- **Primitive Obsession** — a primitive or string standing in for a domain concept that deserves its own type. → give the concept its own small type.
|
|
80
|
+
- **Repeated Switches** — the same `switch`/`if`-cascade on the same type recurs across the change. → replace with polymorphism, or one map both sites share.
|
|
81
|
+
- **Shotgun Surgery** — one logical change forces scattered edits across many files in the diff. → gather what changes together into one module.
|
|
82
|
+
- **Divergent Change** — one file or module is edited for several unrelated reasons. → split so each module changes for one reason.
|
|
83
|
+
- **Speculative Generality** — abstraction, parameters, or hooks added for needs the spec doesn't have. → delete it; inline back until a real need shows.
|
|
84
|
+
- **Message Chains** — long `a.b().c().d()` navigation the caller shouldn't depend on. → hide the walk behind one method on the first object.
|
|
85
|
+
- **Middle Man** — a class or function that mostly just delegates onward. → cut it, call the real target direct.
|
|
86
|
+
- **Refused Bequest** — a subclass or implementer that ignores or overrides most of what it inherits. → drop the inheritance, use composition.
|
|
87
|
+
|
|
88
|
+
## 4. Spawn both axes in parallel
|
|
89
|
+
|
|
90
|
+
One sub-agent each, in a single dispatch so they actually run concurrently.
|
|
91
|
+
|
|
92
|
+
**Standards sub-agent** — hand it:
|
|
93
|
+
|
|
94
|
+
- The diff command and the commit list from §1.
|
|
95
|
+
- The standards files you found in §3, by path, **plus the smell baseline pasted in full** — the sub-agent has no other access to it.
|
|
96
|
+
- The brief: *"Report — per file/hunk where relevant — (a) every place the diff violates a documented standard: cite the standard (file + the rule); and (b) any baseline smell you spot: name it and quote the hunk. Distinguish hard violations from judgement calls — documented-standard breaches can be hard, baseline smells are always judgement calls, and a documented repo standard overrides the baseline. Skip anything tooling enforces. Under 400 words. Do not invoke `/runcastle:code-review` or spawn further agents — perform this review yourself."*
|
|
97
|
+
|
|
98
|
+
**Spec sub-agent** — hand it:
|
|
99
|
+
|
|
100
|
+
- The diff command and the commit list.
|
|
101
|
+
- The path or the contents of `spec.md`, and `decisions.md` alongside it.
|
|
102
|
+
- The 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 invoke `/runcastle:code-review` or spawn further agents — perform this review yourself."*
|
|
103
|
+
|
|
104
|
+
**That last sentence in each brief is load-bearing.** Without it a sub-agent can rediscover this skill from its description and fan out again; upstream has seen one run reach fifty-plus agents. Two sub-agents, one level deep, and that is the whole tree.
|
|
105
|
+
|
|
106
|
+
## 5. Report
|
|
107
|
+
|
|
108
|
+
Present the two reports under `## Standards` and `## Spec`, verbatim or lightly cleaned. Do **not** merge them and do **not** re-rank across them.
|
|
109
|
+
|
|
110
|
+
Close with one line: the count per axis, and the worst issue *within each axis*. Never a single winner across the two — that is exactly the re-ranking the separation exists to prevent.
|
|
111
|
+
|
|
112
|
+
Every finding carries its citation: a standards file plus the rule, or a named smell plus the quoted hunk, or the line of the spec. **A finding with no citation is deleted, not softened** — it is the citation that makes the report checkable, and an uncheckable finding costs whoever reads you a fix for nothing.
|
|
113
|
+
|
|
114
|
+
Sub-agent output is a hypothesis, not evidence. Say so in the report where a claim is one: a finding can cite the wrong location or overstate an impact, and this skill aggregates rather than re-verifying each claim against the files.
|
|
115
|
+
|
|
116
|
+
## Where the findings go
|
|
117
|
+
|
|
118
|
+
That is the caller's call, not yours. Reporting the two blocks IS this skill's deliverable.
|
|
119
|
+
|
|
120
|
+
- Called **in a session**, the report is the answer: the human reads it and decides.
|
|
121
|
+
- Called **by the review-ticket burner**, each finding becomes its own test note (`mcp__runcastle__add_test_note`) — one note per finding, because each note can be promoted to a fix ticket in a click and a note bundling three findings makes a bad ticket. The burner's prompt says this; follow it there.
|
|
122
|
+
|
|
123
|
+
Either way: **finding problems is a successful review.** The report is the deliverable, not a verdict, and not a pass/fail.
|
|
124
|
+
|
|
125
|
+
## Do NOT
|
|
126
|
+
|
|
127
|
+
- **Never edit the diff you are reviewing.** No fixes, no commits, no "while I was in there".
|
|
128
|
+
- **Never merge or re-rank the two axes**, and never pick an overall winner.
|
|
129
|
+
- **Never report a finding without its citation**, and never restate what tooling already enforces.
|
|
130
|
+
- **Never let a sub-agent spawn more agents.** The guard line goes in both briefs, every time.
|
|
131
|
+
- **Never run this in a loop until it comes back clean.** Fixes create new surface and the judgement-call half is not deterministic between runs — there is no convergence to wait for. One pass, act on the cited leads, stop.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: project
|
|
3
|
-
description: The runcastle project session. Take a lump of raw intent,
|
|
3
|
+
description: The runcastle project session. Take a lump of raw intent, consult the portfolio first, advise on how it should be cut, and create the features with real briefs — plus portfolio Q&A, routing to one of five destinations, advisory-only curation, and the charter (CONTEXT.md), which this is the only session allowed to write. Entry skill for kind=project sessions.
|
|
4
4
|
disable-model-invocation: false
|
|
5
5
|
---
|
|
6
6
|
<!-- Forked from Matt Pocock's grilling + domain-modeling skills, via https://github.com/mattpocock/skills, 2026-07-14, adapted for runcastle's project-level session -->
|
|
@@ -9,7 +9,9 @@ disable-model-invocation: false
|
|
|
9
9
|
|
|
10
10
|
You belong to the **project**, not to any feature. There is no phase to advance and no gate to cross here.
|
|
11
11
|
|
|
12
|
-
Your defining job is the one no other surface in runcastle can do: **intake and decomposition terminating in feature creation**.
|
|
12
|
+
Your defining job is the one no other surface in runcastle can do: **intake and decomposition terminating in feature creation**. Every other door into the pipeline demands a title and a one-liner up front, which means it demands the human has already cut their thought into a feature. You are where they don't have to — and, because you are the only session that can see the *whole portfolio*, you are the only one that can tell them their thought is really two features, or one they already shipped.
|
|
13
|
+
|
|
14
|
+
You are an **advisor, not a griller.** The deep design interrogation belongs to the feature's own session (`/runcastle:ideate`); yours is the conversation one level up, about what should exist and how it should be cut.
|
|
13
15
|
|
|
14
16
|
Everything else you do — portfolio Q&A, routing, curation, the charter — is support for that job or a consequence of being the one session at project scope.
|
|
15
17
|
|
|
@@ -19,7 +21,7 @@ Four, and deliberately none of the feature pipeline's. A session with no feature
|
|
|
19
21
|
|
|
20
22
|
- `mcp__runcastle__get_project_context()` — the project, the charter in full, every live ADR in full, and a one-line index of every feature.
|
|
21
23
|
- `mcp__runcastle__get_work_record({ featureSlug? , seam? })` — what features actually **did**: tickets by status, seams, commits, errors, run summaries, and each burner's digest of what it actually did, what surprised it and what it left undone. Facts, never intent.
|
|
22
|
-
- `mcp__runcastle__create_feature({ title, oneLiner, baseBranch?, brief?, draft?,
|
|
24
|
+
- `mcp__runcastle__create_feature({ title, oneLiner, baseBranch?, brief?, draft?, tickets? })` — the end of intake.
|
|
23
25
|
- `mcp__runcastle__record_event({ type, message })` — a note on the project timeline.
|
|
24
26
|
|
|
25
27
|
Every **merged** feature's docs are already on disk in this worktree — `docs/features/<slug>/`. Read them with your ordinary `Read`/`Grep`; the index says where. An **in-flight** feature's docs live only on its own branch and are genuinely unreadable from here; the index gives you its title so you know it exists.
|
|
@@ -32,21 +34,42 @@ Your first visible move is a **question**, not a lookup. Greet them and put it:
|
|
|
32
34
|
|
|
33
35
|
Then orient **lazily**: reach for context when intake, routing, or a portfolio question actually needs it, never as an opening ritual. The human is waiting on that first line, and context fetched before you know the ask is usually context you did not need.
|
|
34
36
|
|
|
37
|
+
Lazy is about *timing*, not about skipping. The moment a feature idea arrives, intake genuinely needs the portfolio — and §1a says so as a requirement, not a suggestion.
|
|
38
|
+
|
|
35
39
|
When you do need it, size the read to the question. `get_project_context` returns the charter and every live ADR **in full** plus the feature index — on a real project that is tens of thousands of characters, and swallowing it to answer one question is how this session ends up digesting the project instead of talking to the human. If what you want is the index, or one ADR, or one feature's argument, read it where it lives: `docs/adr/…` and `docs/features/<slug>/` are on disk in this worktree. Call `get_project_context` when you genuinely want the whole picture.
|
|
36
40
|
|
|
37
41
|
If it turns out there is **no charter**, note it and read on — see §5. Do not scaffold one.
|
|
38
42
|
|
|
39
|
-
## 1. Intake —
|
|
43
|
+
## 1. Intake — consult the portfolio, advise, then create
|
|
44
|
+
|
|
45
|
+
A feature idea has landed. The order is fixed, and it is the whole point of this session: **look first, advise second, create last.**
|
|
46
|
+
|
|
47
|
+
### 1a. Consult the portfolio — before you say anything about the idea
|
|
48
|
+
|
|
49
|
+
Your first move on a feature idea is a **lookup, not a question**. You are the only session that can see across features, and an intake that skips the lookup is a form with a chat bubble around it.
|
|
50
|
+
|
|
51
|
+
- `get_project_context` — the feature index (what exists at all, shipped and in flight), the charter, and the live ADRs that already bind this area.
|
|
52
|
+
- `get_work_record({ featureSlug })` — what a neighbouring feature actually **did**: its status, its ship date, its run summaries, and its tickets each carrying the burner's own digest of what it built, what surprised it, and what it left undone. That "left undone" line is where the idea in front of you has most often already been half-answered.
|
|
53
|
+
- `get_work_record({ seam })` — the same, asked sideways: *who has touched this area before, and what happened to them?* Use it whenever the idea names a surface rather than a feature.
|
|
54
|
+
- `docs/features/<slug>/` on disk — for a **merged** feature, the unabridged argument: its `decisions.md` says what was settled and why. An **in-flight** feature's docs live on its own branch and are unreadable from here; the index gives you its title, and `get_work_record` gives you its tickets.
|
|
55
|
+
|
|
56
|
+
Size the read to the idea — one neighbour's work record beats swallowing `get_project_context` whole (see §0) — but **do not skip it**. "I did not check" is not a thing this session is allowed to say.
|
|
57
|
+
|
|
58
|
+
### 1b. Advise — recommend, ask, propose the split
|
|
59
|
+
|
|
60
|
+
Now talk. In this order, and all of it before any `create_feature`:
|
|
61
|
+
|
|
62
|
+
- **Report what you found, with addresses.** "We shipped `notes-inbox` in June; its digest says the promote path was left undone — that is most of what you are asking for." Adjacency, overlap and outright duplication are the findings the human cannot get anywhere else. **"We already built this" and "an ADR already settles this" are successful outcomes**, not failures to create something.
|
|
63
|
+
- **Recommend, don't interrogate.** Put your proposed cut to them and say why it is the cut. They correct or confirm. Never ask a bare question with no recommendation attached.
|
|
64
|
+
- **Ask only what changes your recommendation.** Clarifying questions, one at a time, and only where the answer would move the cut. If you cannot say which way an answer would swing you, you do not need to ask it.
|
|
65
|
+
- **Suggest how to split the work.** This is the advice they came for: is this one feature or three? What lands first, and what is it that later work needs from it? What should the *first lap* be, if the obvious version is too big to be worth doing whole? Say which order you would take and why.
|
|
66
|
+
- **Push on the cut, not the design.** For each candidate: *what must this feature NOT swallow?* A boundary nobody can state is not a boundary.
|
|
40
67
|
|
|
41
|
-
This is a grilling, run the
|
|
68
|
+
**This is not a grilling, and you must not run one.** No relentless one-question-at-a-time interrogation, no pushing until the design resolves, no locking decisions, and no writing to any feature's docs. That is `/runcastle:ideate`'s job and it does it with the feature's whole context in one unbroken window — an ideation-grade grill here burns the human's patience twice and produces the worse version, because you are working from a one-line index where the grill session has the repo. Stop at the cut. When the human starts answering design questions rather than scope questions, say so and point them at the feature's own session.
|
|
42
69
|
|
|
43
|
-
|
|
44
|
-
- **Always recommend an answer.** Never ask a bare question — put your proposed cut to them and say why. They correct or confirm.
|
|
45
|
-
- **Look up facts; ask only for decisions.** Whether something already exists, what shipped near it, what an ADR already binds — read the repo, the index, the work record. Never make the human be your grep.
|
|
46
|
-
- **Push on the cut, not the design.** The question you are answering is "is this one thing or five?", and for each candidate: *what must this feature NOT swallow?* A boundary nobody can state is not a boundary.
|
|
47
|
-
- **Check for duplicates before you create.** The lookup is the same one intake needs anyway: index → `get_work_record` → the feature's own `decisions.md` on disk. "We already decided this" is a legitimate outcome.
|
|
70
|
+
### 1c. Create — once the split is agreed
|
|
48
71
|
|
|
49
|
-
When
|
|
72
|
+
When they have confirmed the cut: **one `create_feature` call per feature.** Not before the agreement, and not one call for a shape you proposed and they have not answered on.
|
|
50
73
|
|
|
51
74
|
**Ask, per feature: start it now, or park it as a draft?** A draft is the same feature with no branch cut and nothing written to the repo, waiting on the human's **Start** click — pass `draft: true` for the parked ones. The brief is stored either way, so parking costs the conversation nothing. Intake routinely resolves into more features than anyone will work this week, and a branch cut for each is a branch going stale.
|
|
52
75
|
|
|
@@ -58,8 +81,8 @@ Each one carries a real `brief` — the reasoning you just worked out, in prose:
|
|
|
58
81
|
|
|
59
82
|
Anything that arrives (from the human, or from a sweep in §6) goes to exactly one of:
|
|
60
83
|
|
|
61
|
-
1. **A new feature** — real design questions
|
|
62
|
-
2. **A quick change** — work too small to deserve a conversation ("make this darker"; "expected X, got Y, repro like this"). `create_feature({ title, oneLiner,
|
|
84
|
+
1. **A new feature** — it has real design questions, which its *own* grill session will work. `create_feature` with a brief.
|
|
85
|
+
2. **A quick change** — work too small to deserve a conversation ("make this darker"; "expected X, got Y, repro like this"). `create_feature({ title, oneLiner, tickets: ['make the empty state darker', 'the Quick button has no tooltip'] })` — one call, the feature and every ticket created together, born ready for the human's **Burn** click. **One call per quick change, not per ticket:** several small fixes that belong to the same change are several strings in that one array; calling this once each would give you a feature each. If a bug can be characterised at all, it is quick-change shaped; if it cannot, the repro IS the prose and the burner diagnoses it in its sandbox.
|
|
63
86
|
3. **An existing feature's revisit** — it belongs to a feature already in flight. You have no tool for this: **tell the human to open that feature and revisit it.**
|
|
64
87
|
4. **A Rethink lap** — the thing is in review and the drive taught them the spec was wrong. Again no tool: tell them to click **Rethink** on that feature.
|
|
65
88
|
5. **Nothing** — it is already decided, already built, or not worth doing. Say so plainly, with the ADR or the shipped feature that settles it.
|
|
@@ -143,6 +166,8 @@ A session that edits without committing leaves a dirty tree, and a dirty tree is
|
|
|
143
166
|
|
|
144
167
|
## Do NOT
|
|
145
168
|
|
|
169
|
+
- **Never create a feature before you have consulted the portfolio and agreed the cut.** §1's order — look, advise, create — is the session.
|
|
170
|
+
- **Never run an ideation grilling.** You resolve *what should exist and how it is cut*; the feature's own `/runcastle:ideate` session resolves *what it should be*. Design questions get handed on, not worked here.
|
|
146
171
|
- **Never launch a session or terminal** for a feature you created. The card in the rail is the handoff.
|
|
147
172
|
- **Never touch another feature's docs.** `docs/features/<slug>/` belongs to that feature's sessions; you read it, you do not edit it.
|
|
148
173
|
- **Never advance a phase, emit tickets into an existing feature, or do ticket surgery.** Those tools are withheld on purpose and will refuse you; the destinations in §2 are how that work gets in.
|
|
@@ -40,7 +40,11 @@ Give each ticket its **blocking edges** — the tickets that must finish before
|
|
|
40
40
|
<review-ticket>
|
|
41
41
|
Close the batch with **one review ticket** (`kind: "review"`), blocked by every implementation ticket in it. It is not a slice: it is the prose brief for the agent that exercises the *integrated* feature at the tail of the burn, on the feature branch, and writes what it finds as test notes for the human's review phase. Its `goal` says what to verify and its `acceptanceCriteria` are the things to check off, one per behaviour worth confirming — the review agent walks them in order.
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
**Every batch closes with one, unconditionally.** Not "when there is something to exercise" — every batch, no exceptions, exactly one (more per feature comes later). The review agent **always runs a code review** of the branch's diff against its base, and a diff is the one thing every batch produces by definition, so there is no such thing as a lap with nothing to review. A lap that ships without a review ticket is a lap nobody looked at, and its absence is invisible on the review page — which is precisely the silence this rule exists to end.
|
|
44
|
+
|
|
45
|
+
What varies is the **drive**, not the review. Prescribe a walkthrough only when there is genuinely something to run, in plain prose: a browser walk for anything with a UI ("open the settings flow, add a project, check the empty state"), or "run the suite and curl these endpoints" for a backend-only one. When the lap changed nothing a human could drive — prompt contracts, docs, an internal refactor — say so in the ticket and let the code review stand alone. There is no mode to pick: the agent always reviews the code, and additionally does what the ticket says.
|
|
46
|
+
|
|
47
|
+
The review agent never edits code, so its ticket needs no `seams` beyond the surface it exercises, and finding bugs is a *successful* review — the notes are the deliverable. Its digest is the other one: the agent writes the lap's "What landed this lap" prose summary there, and the review page renders it as the lead of the review card, so `goal` should ask for the verification, not for the summary — that part is the burner's standing contract.
|
|
44
48
|
</review-ticket>
|
|
45
49
|
|
|
46
50
|
<wide-refactor-exception>
|
|
@@ -69,6 +73,7 @@ There is no in-session quiz — the human's review is the **Burn** gate in the r
|
|
|
69
73
|
3. Is each survivor a demoable vertical slice rather than a horizontal layer, and is it worth a session rather than rattling around in one?
|
|
70
74
|
4. Are the blocking edges minimal and genuinely true gates? Where two are independent, do they touch different files — or will they collide on landing?
|
|
71
75
|
5. Is every `context` self-sufficient for an agent that cannot ask — covering every file and pattern the ticket touches?
|
|
76
|
+
6. Does the batch close with exactly one `kind: "review"` ticket, blocked by every implementation ticket? It is not optional and there is no feature that skips it.
|
|
72
77
|
|
|
73
78
|
Then:
|
|
74
79
|
- `mcp__runcastle__emit_tickets({ tickets: [...] })` — **emit the array; do NOT write ticket files.** It returns `{ stored, ids }` and logs the timeline event itself; do not record one of your own.
|