runcastle 1.4.2 → 1.4.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/bin/runcastle.js +4556 -3932
- package/drizzle/0042_retire_mapped_ideation.sql +3 -0
- package/drizzle/0042_review_gate_run.sql +1 -0
- package/drizzle/0044_icy_daimon_hellstrom.sql +8 -0
- package/drizzle/meta/0042_snapshot.json +1131 -0
- package/drizzle/meta/0044_snapshot.json +1197 -0
- package/drizzle/meta/_journal.json +21 -0
- package/index.js +4553 -3929
- package/package.json +2 -2
- package/pty-host.cjs +8 -0
- package/skills/burner/implement-ticket.md +5 -3
- package/skills/burner/review-ticket.md +14 -9
- package/skills/burner/verify-fixes.md +3 -1
- package/skills/packs/README.md +10 -17
- package/skills/packs/runcastle/.claude-plugin/plugin.json +1 -1
- package/skills/packs/runcastle/skills/code-review/SKILL.md +1 -1
- package/skills/packs/runcastle/skills/ideate/SKILL.md +8 -25
- package/skills/packs/runcastle/skills/project/SKILL.md +28 -10
- package/skills/packs/runcastle/skills/qa/SKILL.md +2 -6
- package/skills/packs/runcastle/skills/revisit/SKILL.md +8 -10
- package/skills/packs/runcastle/skills/spec/SKILL.md +2 -2
- package/skills/packs/runcastle/skills/tickets/SKILL.md +2 -2
- package/web/assets/index-BAbR9IXb.css +1 -0
- package/web/assets/index-CW8ioVQ6.js +188 -0
- package/web/favicon.svg +7 -5
- package/web/index.html +2 -2
- package/skills/burner/research-waypoint.md +0 -45
- package/skills/packs/runcastle/skills/converge/SKILL.md +0 -54
- package/skills/packs/runcastle/skills/waypoint/SKILL.md +0 -59
- package/web/assets/index-D7diTU3w.css +0 -1
- package/web/assets/index-Ih8W7ADS.js +0 -187
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runcastle",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.4",
|
|
4
4
|
"description": "Burn tickets into shipped features with Claude Code.",
|
|
5
5
|
"license": "FSL-1.1-ALv2",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"drizzle-orm": "0.45.2",
|
|
39
39
|
"hono": "4.12.30",
|
|
40
40
|
"nanoid": "6.0.0",
|
|
41
|
-
"node-pty": "
|
|
41
|
+
"node-pty": "1.2.0-beta.15",
|
|
42
42
|
"simple-git": "^3.36.0",
|
|
43
43
|
"zod": "4.4.3"
|
|
44
44
|
}
|
package/pty-host.cjs
CHANGED
|
@@ -102,7 +102,15 @@ function handleSpawn(msg) {
|
|
|
102
102
|
|
|
103
103
|
send({ t: 'ready', pid: proc.pid })
|
|
104
104
|
|
|
105
|
+
// node-pty 1.2 on win32 starts the ConPTY child only once its output worker has
|
|
106
|
+
// connected, so `proc.pid` is still 0 above. It is set before the first output
|
|
107
|
+
// arrives, so re-announce it then.
|
|
108
|
+
let pidAnnounced = proc.pid > 0
|
|
105
109
|
proc.onData((d) => {
|
|
110
|
+
if (!pidAnnounced && proc.pid > 0) {
|
|
111
|
+
pidAnnounced = true
|
|
112
|
+
send({ t: 'ready', pid: proc.pid })
|
|
113
|
+
}
|
|
106
114
|
const buf = typeof d === 'string' ? Buffer.from(d, 'utf8') : d
|
|
107
115
|
send({ t: 'data', d: buf.toString('base64') })
|
|
108
116
|
})
|
|
@@ -52,7 +52,7 @@ Whatever the commands are, spend them well — a full suite on a monorepo is min
|
|
|
52
52
|
- **Report a gate that cannot run.** If a configured verify command cannot run 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
53
|
|
|
54
54
|
- **Capture, then read.** Redirect a full 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
|
-
- **Run the whole thing rarely.** Targeted runs (single file, single pattern) while you work; the full suite once
|
|
55
|
+
- **Run the whole thing rarely.** Targeted runs (single file, single pattern) while you work; the full suite once at the end — *after* your work is committed (see "How to work", step 4) — plus once more only if that run found something you then fixed.
|
|
56
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
57
|
|
|
58
58
|
## How to work
|
|
@@ -73,11 +73,13 @@ Whatever the commands are, spend them well — a full suite on a monorepo is min
|
|
|
73
73
|
|
|
74
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
75
|
|
|
76
|
-
|
|
76
|
+
**Commit before the long final checks.** The full typecheck and test suite 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 in all of its iterations. So once the targeted tests for your change pass, commit it *before* you start the full typecheck and test suite. If those checks then find something, fix it in a follow-up commit. A death during the long checks must never cost the work itself.
|
|
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 full typecheck and test suite:
|
|
77
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.
|
|
78
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.
|
|
79
81
|
|
|
80
|
-
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.
|
|
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.
|
|
81
83
|
|
|
82
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:
|
|
83
85
|
- **What was done** — past tense, what you actually built, *including where it deviated* from the approach the ticket described.
|
|
@@ -7,7 +7,7 @@ Every implementation ticket in this burn has landed on `{{FEATURE_BRANCH}}`, whi
|
|
|
7
7
|
You review it in **exactly one of two modes**, and you pick which before you do anything else:
|
|
8
8
|
|
|
9
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** —
|
|
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
11
|
|
|
12
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
13
|
|
|
@@ -39,6 +39,12 @@ Its `goal` says what to verify and its `acceptanceCriteria` say how you will kno
|
|
|
39
39
|
|
|
40
40
|
{{DRIVE_AVAILABILITY}}
|
|
41
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
|
+
|
|
42
48
|
## Feature context
|
|
43
49
|
|
|
44
50
|
{{FEATURE_BRIEF}}
|
|
@@ -81,7 +87,7 @@ The URL is **not** ready when `start` returns — the dev server has to print it
|
|
|
81
87
|
|
|
82
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.
|
|
83
89
|
|
|
84
|
-
**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:
|
|
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.
|
|
85
91
|
|
|
86
92
|
**Walk the app with `agent-browser`.** Its core loop, which you repeat:
|
|
87
93
|
|
|
@@ -107,15 +113,13 @@ Work the ticket's `acceptanceCriteria` one at a time: reach the part of the app
|
|
|
107
113
|
|
|
108
114
|
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.
|
|
109
115
|
|
|
110
|
-
### 2b. Gates mode — the
|
|
116
|
+
### 2b. Gates mode — the server's gate results, then the diff
|
|
111
117
|
|
|
112
118
|
No app, no browser, no drive slot: this mode needs only the repository.
|
|
113
119
|
|
|
114
|
-
**
|
|
115
|
-
|
|
116
|
-
{{GATE_NOTES}}
|
|
120
|
+
**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.
|
|
117
121
|
|
|
118
|
-
A gate that
|
|
122
|
+
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.
|
|
119
123
|
|
|
120
124
|
**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.
|
|
121
125
|
|
|
@@ -208,7 +212,7 @@ So write **prose**, roughly 10–15 lines, for a reader who has none of your con
|
|
|
208
212
|
|
|
209
213
|
- **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".
|
|
210
214
|
- **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.
|
|
211
|
-
- **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.
|
|
215
|
+
- **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.
|
|
212
216
|
- **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.
|
|
213
217
|
|
|
214
218
|
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.
|
|
@@ -243,7 +247,8 @@ When that happens: run the step 4 cleanup for whatever you got as far as startin
|
|
|
243
247
|
- **Never let a sub-agent spawn more agents.** The guard line goes in both briefs, every time.
|
|
244
248
|
- **Never leave the drive — or the recorder — running.** Stop both on every path, including the one where you gave up on the drive.
|
|
245
249
|
- **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.
|
|
246
|
-
- **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
|
|
250
|
+
- **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.
|
|
251
|
+
- **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.
|
|
247
252
|
- **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.
|
|
248
253
|
- **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.
|
|
249
254
|
- **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.
|
|
@@ -38,7 +38,9 @@ In **Drive mode**, call `mcp__runcastle__review_drive({ action: "start" })`, sta
|
|
|
38
38
|
|
|
39
39
|
{{DRIVE_INSTRUCTIONS}}
|
|
40
40
|
|
|
41
|
-
In **Gates mode**, read each fix diff against its finding and
|
|
41
|
+
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.
|
|
42
|
+
|
|
43
|
+
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.
|
|
42
44
|
|
|
43
45
|
{{GATE_NOTES}}
|
|
44
46
|
|
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. Most of the pack's skills are adapted forks of his methodology skills and each keeps a provenance header crediting the original; `revisit` and `
|
|
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. Most of the pack's skills are adapted forks of his methodology skills and each keeps a provenance header crediting the original; `revisit` and `prepare` are original runcastle work.
|
|
4
4
|
|
|
5
5
|
## The `runcastle` pack
|
|
6
6
|
|
|
@@ -13,8 +13,6 @@ Scope-specific skills, each namespaced `/runcastle:<skill>`:
|
|
|
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
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
|
-
| `/runcastle:waypoint` | entry for `kind=waypoint` | *original* — works ONE waypoint on a mapped feature, writes its decision prose, resolves the waypoint |
|
|
17
|
-
| `/runcastle:converge` | entry for `kind=converge` | closes a mapped feature: reads the compressed knowledge — `map.md`, `decisions.md`, and the `research/*.md` deliverables the research waypoints produced — then drives spec + tickets from it |
|
|
18
16
|
| `/runcastle:revisit` | entry for `kind=revisit` | *original* — folds late information into a finished feature; on an Iterate from review, runs the whole front half of a lap |
|
|
19
17
|
| `/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 |
|
|
20
18
|
|
|
@@ -36,8 +34,6 @@ packs/
|
|
|
36
34
|
│ └── references/ # loaded on demand by SKILL.md, never up front
|
|
37
35
|
│ ├── charter.md
|
|
38
36
|
│ └── health-sweeps.md
|
|
39
|
-
├── waypoint/SKILL.md
|
|
40
|
-
├── converge/SKILL.md
|
|
41
37
|
├── revisit/SKILL.md
|
|
42
38
|
└── code-review/SKILL.md
|
|
43
39
|
```
|
|
@@ -83,8 +79,8 @@ another session's procedure — `project/SKILL.md` says "**Never run an ideation
|
|
|
83
79
|
grilling**" while `ideate`'s description sits in the same list offering exactly
|
|
84
80
|
that.
|
|
85
81
|
|
|
86
|
-
The
|
|
87
|
-
`
|
|
82
|
+
The five **session-entry** skills — `ideate`, `qa`, `revisit`, `project`,
|
|
83
|
+
`prepare` — used to carry `disable-model-invocation:
|
|
88
84
|
true` for that reason, on the build-era understanding that explicit invocation
|
|
89
85
|
kept working (CC-INTEGRATION-NOTES §1). It no longer does: Claude Code now
|
|
90
86
|
hard-blocks the **Skill tool** for such a skill even when the prompt names it,
|
|
@@ -103,10 +99,10 @@ live: **per-session settings**. The launcher launches each session with
|
|
|
103
99
|
prompt states the policy in one line. Same layering as the edit guard: the
|
|
104
100
|
prompt says it, the harness enforces it. `chat` keeps both of its openings
|
|
105
101
|
(`ideate` + `revisit`); `qa` — whose session kind was retired — is denied
|
|
106
|
-
everywhere; `drive-fix` has no entry skill and denies all
|
|
102
|
+
everywhere; `drive-fix` has no entry skill and denies all five.
|
|
107
103
|
|
|
108
104
|
The chained/reached skills — `spec` and `tickets` (invoked by name from inside
|
|
109
|
-
`ideate`
|
|
105
|
+
`ideate`) and `code-review` (reached by description) — are in no
|
|
110
106
|
deny list.
|
|
111
107
|
|
|
112
108
|
The rule, for anything added later: **a session-entry skill goes into
|
|
@@ -132,15 +128,14 @@ The standing convention across this pack:
|
|
|
132
128
|
rule lives there, backed by `launcher/edit-guard.ts`, and is *not* restated in
|
|
133
129
|
any skill).
|
|
134
130
|
- **Procedures belong to the skill.** One skill owns each procedure in full;
|
|
135
|
-
`revisit/SKILL.md` §Lap mode is the whole lap procedure, `ideate/SKILL.md` §
|
|
136
|
-
is the whole
|
|
131
|
+
`revisit/SKILL.md` §Lap mode is the whole lap procedure, `ideate/SKILL.md` §1
|
|
132
|
+
is the whole grilling procedure.
|
|
137
133
|
- **Where a skill names a fact the prompt also names, it points instead of
|
|
138
134
|
restating** — the shape is `ideate/SKILL.md`'s "The injected system prompt
|
|
139
135
|
carries the slug and paths; trust `get_feature_context` for the live state."
|
|
140
136
|
- **Where two skills need the same procedure, the second delegates by
|
|
141
|
-
reference** —
|
|
142
|
-
|
|
143
|
-
differently.
|
|
137
|
+
reference** — "grill the way `/runcastle:ideate` §1 does" rather than a
|
|
138
|
+
second copy that ages differently.
|
|
144
139
|
- **A rule the hook enforces is stated once, as enforced, not as advice.** The
|
|
145
140
|
`qa` kind's write refusals are server-side; `qa/SKILL.md` says so rather than
|
|
146
141
|
asking nicely.
|
|
@@ -148,9 +143,7 @@ The standing convention across this pack:
|
|
|
148
143
|
A corollary that cost real sessions: **never tell a session to do something a
|
|
149
144
|
hook will deny.** An agent that believes a skill and hits a hard deny has no path
|
|
150
145
|
forward and improvises — ADR-0007 §6 documents one aborting and emitting a
|
|
151
|
-
ticket instead.
|
|
152
|
-
was pointed at `docs/features/<slug>/prototypes/`, the one path the edit guard
|
|
153
|
-
lets a talk session write code into.
|
|
146
|
+
ticket instead.
|
|
154
147
|
|
|
155
148
|
## Adding a pack
|
|
156
149
|
|
|
@@ -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,
|
|
3
|
+
"description": "Runcastle phase-scoped skills for the MCP feature pipeline: ideate, spec, tickets, qa, project, code-review (forks of Matt Pocock's grilling, to-spec, to-tickets and code-review, adapted) plus revisit and prepare (original).",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": { "name": "runcastle" }
|
|
6
6
|
}
|
|
@@ -138,4 +138,4 @@ Either way: **finding problems is a successful review.** The report is the deliv
|
|
|
138
138
|
- **Never merge or re-rank the two axes**, and never pick an overall winner.
|
|
139
139
|
- **Never report a finding without its citation**, and never restate what tooling already enforces.
|
|
140
140
|
- **Never let a sub-agent spawn more agents.** The guard line goes in both briefs, every time.
|
|
141
|
-
- **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
|
|
141
|
+
- **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 clean state to wait for. One pass, act on the cited leads, stop.
|
|
@@ -11,7 +11,7 @@ You are the front of the runcastle pipeline. One feature, one unbroken context w
|
|
|
11
11
|
|
|
12
12
|
## Context hygiene is the whole game
|
|
13
13
|
|
|
14
|
-
Everything happens in this one window. **Never compact. Never `/clear`. Never suggest either.** Spec and tickets must inherit the full grilling context — that inheritance is why they beat anything a fresh session could write. If you feel context pressure, you grilled too wide:
|
|
14
|
+
Everything happens in this one window. **Never compact. Never `/clear`. Never suggest either.** Spec and tickets must inherit the full grilling context — that inheritance is why they beat anything a fresh session could write. If you feel context pressure, you grilled too wide: narrow, don't compact. (Matt Pocock's rule; it is load-bearing here.)
|
|
15
15
|
|
|
16
16
|
## 0. Orient
|
|
17
17
|
|
|
@@ -21,9 +21,8 @@ Everything happens in this one window. **Never compact. Never `/clear`. Never su
|
|
|
21
21
|
**Your tools**, and there are not many — this session's real work is the conversation:
|
|
22
22
|
|
|
23
23
|
- `mcp__runcastle__get_feature_context` / `mcp__runcastle__read_feature_doc` / `mcp__runcastle__list_tickets` / `mcp__runcastle__get_ticket` — the record.
|
|
24
|
-
- `mcp__runcastle__create_feature({ title, oneLiner, brief, draft: true })` — park scope creep as a draft feature (§1). `draft: true` is the only door open to you; a full create belongs to the project session.
|
|
25
|
-
- `
|
|
26
|
-
- `mcp__runcastle__complete_phase` — the phase boundaries (§4), and the ones `/runcastle:spec` and `/runcastle:tickets` cross after you.
|
|
24
|
+
- `mcp__runcastle__create_feature({ title, oneLiner, brief, draft: true, dependsOn? })` — park scope creep as a draft feature (§1), with `dependsOn: [<this feature's slug>]` when it builds on this one. `draft: true` is the only door open to you; a full create belongs to the project session.
|
|
25
|
+
- `mcp__runcastle__complete_phase` — the phase boundaries (§3), and the ones `/runcastle:spec` and `/runcastle:tickets` cross after you.
|
|
27
26
|
- `mcp__runcastle__emit_tickets` — used by `/runcastle:tickets`, not by you directly.
|
|
28
27
|
- `mcp__runcastle__record_event` — the feature timeline. Most state changes already emit their own event; only reach for this when the timeline would otherwise have no record at all.
|
|
29
28
|
|
|
@@ -35,10 +34,9 @@ Interview the human about every aspect of the feature until you reach a genuinel
|
|
|
35
34
|
- **Always recommend an answer.** Never ask a bare question — put your recommended answer to it and say why. The human corrects or confirms.
|
|
36
35
|
- **Look up facts; ask only for decisions.** If a fact is discoverable in the codebase — how something currently works, what a type looks like, whether a pattern already exists — read/grep the repo and find it. Never make the human be your grep. *Decisions* are theirs: put each one to them and wait.
|
|
37
36
|
- **Model the domain as you go** (forked from domain-modeling): challenge terms that conflict with existing usage, sharpen fuzzy words to one canonical meaning ("you said 'account' — Customer or User?"), invent concrete edge-case scenarios that force precision, and cross-check claims against the actual code ("your code cancels whole Orders, but you just said partial — which is right?").
|
|
38
|
-
- **Probe the size early.** Within your first few questions, ask enough about scope to judge whether this is one-session-sized or map-sized — how many independent areas it touches, what has to be researched or prototyped before decisions can lock, how wide the design tree looks. Make that call up front, not thirty questions deep into one corner. If it smells map-sized, go to §3 *now* rather than rabbit-holing first.
|
|
39
37
|
- **Ask the slicing question: how sure are you this is what you want?** Put it to them early, plainly, with your recommendation attached. **Sure, and small** → spec the whole thing; it is one lap and will likely merge on lap 1 — the old linear flow, verbatim. **Unsure, or large** → recommend a thin **lap 1**: a walking skeleton of the uncertain part, or one sub-feature slice they can actually test-drive, with the consciously deferred scope parked in the spec's `## Later laps` section (from there it seeds the next lap's session alongside the test notes). **Say the recommendation out loud and let them answer** — this is a judgment you offer, not a rule you enforce, and the human decides. There is no mode and no flag: how many laps a feature takes is a description of how it went.
|
|
40
|
-
- **Park scope creep instead of swallowing it.** When something surfaces that is plainly its own feature, say so and offer to park it: `mcp__runcastle__create_feature({ title, oneLiner, brief, draft: true })` creates a parked draft — no branch, nothing written — with the `brief` carrying why you deferred it and what it must not swallow. Then get back to this feature. Drafts are the only thing you may create here; a full create belongs to the project session.
|
|
41
|
-
- **
|
|
38
|
+
- **Park scope creep instead of swallowing it.** When something surfaces that is plainly its own feature, say so and offer to park it: `mcp__runcastle__create_feature({ title, oneLiner, brief, draft: true })` creates a parked draft — no branch, nothing written — with the `brief` carrying why you deferred it and what it must not swallow. When the parked scope builds on this feature, also pass `dependsOn: [<this feature's slug>]` so it cannot be started before this one merges. Then get back to this feature. Drafts are the only thing you may create here; a full create belongs to the project session.
|
|
39
|
+
- **A feature bigger than expected gets a thin lap 1.** The project session sizes features to fit one ideation window before they reach you, so this is rare. When a feature still turns out bigger than this window mid-grill, do not grind on and do not split it yourself: put the slicing question to the human again, recommend a thin lap 1, and park the rest under the spec's `## Later laps`.
|
|
42
40
|
|
|
43
41
|
Do not move on until the human confirms you have reached shared understanding.
|
|
44
42
|
|
|
@@ -54,34 +52,19 @@ The moment a decision locks, append it to `docs/features/<slug>/decisions.md`
|
|
|
54
52
|
|
|
55
53
|
Keep `decisions.md` free of implementation minutiae — it is the record of *what was decided and why*, the raw material spec and tickets are built from. `complete_phase` checkpoints the file into git for you; you just write it. The file is the record — do not narrate it onto the timeline as you go.
|
|
56
54
|
|
|
57
|
-
## 3.
|
|
58
|
-
|
|
59
|
-
Some features are too big for one unbroken context. The tells: you are rabbit-holing into one corner while whole branches sit untouched; decisions keep hanging on material nobody has read yet (a prototype to build, a dependency to research, an area to grill on its own); the design tree has grown wider than you can hold and converge in this session. When that happens, **do not compact and do not grind on** — chart a map instead:
|
|
60
|
-
|
|
61
|
-
**Ask before you chart.** Escalation is a one-way door — there is no un-map path once charted. So the instant you judge a feature map-sized, put it to the human and *wait for a yes*: "This looks bigger than one session — I'd like to chart it into a waypoint map instead of grinding on here. Chart it?" **Never call `escalate_to_map` without that explicit confirmation.** If they say no, keep grilling. Only on a yes do the steps below run:
|
|
62
|
-
|
|
63
|
-
1. `mcp__runcastle__escalate_to_map({ destination: "<the one-line north star>", notes: "<what's locked / constraints so far>" })`. This flips the feature to *mapped* and scaffolds `docs/features/<slug>/map.md`. If it warns that the feature is already mapped, that is fine — the map already exists; just proceed to chart the first batch.
|
|
64
|
-
2. `mcp__runcastle__emit_waypoints({ waypoints: [...] })` for the **first batch** — the questions worth branching now. Each waypoint: `title`, `type` (`grilling` | `research` | `prototype` | `task`), `question` (what that session must answer), and `blockedBy` (1-based positions within this batch, and/or ids of already-stored waypoints) for ordering. Charting the first frontier is enough; later sessions branch the map further (any mapped session may `emit_waypoints`).
|
|
65
|
-
3. `mcp__runcastle__record_event({ type: "map.charted", message: "<n> waypoints charted" })`.
|
|
66
|
-
4. Tell the human plainly:
|
|
67
|
-
|
|
68
|
-
> This feature is bigger than one session. I've charted it into a map — its first waypoints are in the runcastle UI. Work them from there (each opens its own session); converge when the frontier clears. I'll stop here.
|
|
69
|
-
|
|
70
|
-
5. **End the session.** Charting is one session's work — do **not** claim or work a waypoint yourself (claiming is never agent-driven), and do **not** carry on grilling. Stop.
|
|
71
|
-
|
|
72
|
-
## 4. Converge
|
|
55
|
+
## 3. Complete ideation
|
|
73
56
|
|
|
74
57
|
When the human confirms shared understanding and the open questions are answered or explicitly deferred:
|
|
75
58
|
|
|
76
59
|
`mcp__runcastle__complete_phase({ phase: "ideation" })` → returns `{ ok: true, nextPhase, nextStep }`. It does not refuse and it does not move the feature: ideation, spec and tickets are three steps inside **Planning**, and this records the milestone on the timeline. `nextStep` is the next step still missing its artifact, read off disk rather than stored — so if `decisions.md` is not written yet it comes back as `"ideation"` again, which is your cue to write it. It logs its own timeline event; do not add one.
|
|
77
60
|
|
|
78
|
-
##
|
|
61
|
+
## 4. Spec → tickets — stay in this window
|
|
79
62
|
|
|
80
63
|
Invoke `/runcastle:spec` (it writes `spec.md` and completes the `spec` phase); when it returns, invoke `/runcastle:tickets`.
|
|
81
64
|
|
|
82
65
|
Do not open a new session for these. They run here, on top of everything you just learned.
|
|
83
66
|
|
|
84
|
-
##
|
|
67
|
+
## 5. Close — hand off to the human
|
|
85
68
|
|
|
86
69
|
`/runcastle:tickets` ends by emitting tickets to the runcastle store. When it returns, your job is **done**. Tell the human plainly:
|
|
87
70
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: project
|
|
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.
|
|
3
|
+
description: The runcastle project session. Take a lump of raw intent, consult the portfolio first, shape-grill it and 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 -->
|
|
@@ -11,25 +11,26 @@ You belong to the **project**, not to any feature. There is no feature state to
|
|
|
11
11
|
|
|
12
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
13
|
|
|
14
|
-
You are
|
|
14
|
+
You are a **shape griller, not a design griller.** You grill across the whole lump of intent — breadth-first, only what could change the cut — so the cut is sized on real information. 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.
|
|
15
15
|
|
|
16
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.
|
|
17
17
|
|
|
18
18
|
## Your tools
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Ten, and deliberately none of the feature pipeline's. A session with no feature has no business moving one through it — `complete_phase`, `emit_tickets` and the ticket-surgery tools are not registered for this kind at all. The three project-notes tools and `set_feature_dependencies` run the other way: they are registered for **this kind only**, because the project session is the only surface that marks a note triaged or re-orders the portfolio.
|
|
21
21
|
|
|
22
22
|
- `mcp__runcastle__get_project_context()` — the project row, the charter (`CONTEXT.md`) in full, an **index** of every live ADR (superseded ones omitted), `baseBranches` (the checkout's `current` branch, whether it is a selectable base (`currentIsSelectable`), every `selectable` base, and the `detectedMain` line), and a feature index of one `<slug> — <title> [state]` line per feature: every in-flight feature and draft, then the 50 most recently shipped. Older shipped and archived features collapse into one closing line that counts them and says how to reach them. ADR bodies and one-liners are *not* inlined.
|
|
23
23
|
- `mcp__runcastle__read_adr({ relPath })` — one ADR in full, from the index. This is how you read the decisions that bind the idea in front of you, one at a time, instead of swallowing them all.
|
|
24
24
|
- `mcp__runcastle__read_feature_brief({ slug })` — one feature's title, one-liner, status/phase/lap and brief: off disk for a shipped feature, from the database for an in-flight or draft one. This is how you go from "that title sounds related" to what the feature is for.
|
|
25
25
|
- `mcp__runcastle__get_work_record({ featureSlug? , seam? , seq? })` — what features actually **did**. Send exactly one of `featureSlug` or `seam`. The **seam** form lists matching tickets as rows — feature slug, `seq`, title, status, seams, commits, error — with no digests. The **featureSlug** form tells the whole story: 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; a digest too large to fit is moved out and its ticket marked `digestNotInlined: true`. **`featureSlug` + `seq`** returns that one ticket with its digest. Facts, never intent.
|
|
26
|
-
- `mcp__runcastle__create_feature({ title, oneLiner, baseBranch?, brief?, draft?, tickets? })` — the end of intake.
|
|
26
|
+
- `mcp__runcastle__create_feature({ title, oneLiner, baseBranch?, brief?, draft?, tickets?, dependsOn? })` — the end of intake. `dependsOn` (feature slugs, drafts only) records merge order (§1c).
|
|
27
|
+
- `mcp__runcastle__set_feature_dependencies({ slug, dependsOn })` — replaces a draft's whole merge-order set (§1c).
|
|
27
28
|
- `mcp__runcastle__record_event({ type, message })` — a note on the project timeline.
|
|
28
29
|
- `mcp__runcastle__list_project_notes()` — the project's **open notes**, oldest first: id, text, createdAt, and for a note with a screenshot its absolute host path (`Read` it) plus a ready-made attachment sentence to paste into a ticket string. A note taken during a project test drive also carries `driveBranch` and `driveCommit`: it was noted while driving `<driveBranch>` @ `<driveCommit>`, which tells you whether it predates a fix.
|
|
29
30
|
- `mcp__runcastle__triage_project_note({ noteIds, outcome, featureId? })` — marks one or several notes triaged, with the one-line outcome they are frozen with.
|
|
30
31
|
- `mcp__runcastle__update_project_note({ noteId, text })` — rewrites an open note's text.
|
|
31
32
|
|
|
32
|
-
**What the feature index makes readable.** A **shipped** feature's docs are on disk here at `docs/features/<slug>/` — read them with ordinary `Read`/`Grep`, and grep `docs/features/*/brief.md` for the older ones the index collapsed into its closing line. An **in-flight** one's docs are on an unmerged branch, but its index line is `<slug> — <title> [in flight: <phase>, lap N, X pending, Y burning
|
|
33
|
+
**What the feature index makes readable.** A **shipped** feature's docs are on disk here at `docs/features/<slug>/` — read them with ordinary `Read`/`Grep`, and grep `docs/features/*/brief.md` for the older ones the index collapsed into its closing line. An **in-flight** one's docs are on an unmerged branch, but its index line is `<slug> — <title> [in flight: <phase>, lap N, X pending, Y burning]` and `read_feature_brief({ slug })` returns its brief. **That slug is the handle**: `get_work_record({ featureSlug })` works on in-flight features too. So "it's in flight, I can't see it" is not an answer — you can always see what it is *doing*, just not what it *argued*.
|
|
33
34
|
|
|
34
35
|
**Three procedures load on demand**, beside this file — read one only when that job actually arrives: `./references/charter.md` (writing or amending `CONTEXT.md` and project ADRs — §5), `./references/health-sweeps.md` (running a sweep — §6) and `./references/triage.md` (triaging the project's jotted notes — **Notes triage**).
|
|
35
36
|
|
|
@@ -66,17 +67,24 @@ Your first move on a feature idea is a **lookup, not a question**. You are the o
|
|
|
66
67
|
|
|
67
68
|
Size the read to the idea — but **do not skip it**. "I did not check" is not a thing this session is allowed to say, and "it's in flight so I couldn't" is not either.
|
|
68
69
|
|
|
69
|
-
### 1b. Advise —
|
|
70
|
+
### 1b. Advise — report, shape-grill, propose the split
|
|
70
71
|
|
|
71
72
|
Now talk. In this order, and all of it before any `create_feature`:
|
|
72
73
|
|
|
73
74
|
- **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.
|
|
75
|
+
- **Shape-grill before you propose the cut.** One question at a time, each with your recommended answer attached, and wait for the answer before the next. Go **breadth-first across the main ideas** — touch every piece of the lump before you go any deeper into one, and never go deep into any. Ask only what could change the cut:
|
|
76
|
+
- **the pieces** — what separate things are in this lump;
|
|
77
|
+
- **the core** — which piece is the one the rest exists for;
|
|
78
|
+
- **the uncertainty** — where the human is unsure, and what they would want to test-drive first;
|
|
79
|
+
- **the width** — roughly how wide each piece is, including whether it needs research or a prototype before its design can lock.
|
|
80
|
+
|
|
81
|
+
If you cannot say which way an answer would swing the cut, you do not need to ask it. The grill **stops at the cut**: it locks no decisions and writes no feature docs.
|
|
74
82
|
- **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.
|
|
75
|
-
- **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.
|
|
76
83
|
- **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.
|
|
84
|
+
- **Cut until every feature fits one ideation window.** Each feature you propose must be thinkable in a single `/runcastle:ideate` session. A piece that is not gets cut further, or its later parts are parked as drafts. Big intent never reaches ideation as one feature — you are the size gate.
|
|
77
85
|
- **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.
|
|
78
86
|
|
|
79
|
-
**This is not a
|
|
87
|
+
**This is a shape grill, not a design grill, and you must not run a design grill.** No data shapes, no UX detail, no edge cases, 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 — a design 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.
|
|
80
88
|
|
|
81
89
|
### 1c. Create — once the split is agreed
|
|
82
90
|
|
|
@@ -84,6 +92,8 @@ When they have confirmed the cut: **one `create_feature` call per feature.** Not
|
|
|
84
92
|
|
|
85
93
|
**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.
|
|
86
94
|
|
|
95
|
+
**State the cut's order as `dependsOn`, not as brief prose.** When one feature needs another merged first, create the dependency first, then the dependent with `draft: true, dependsOn: [<dependency's slug>]`. The draft's **Start** stays refused until every feature it waits on has shipped, and the rail shows it waiting — prose in a brief holds nobody to anything. Only drafts can wait. To re-order parked drafts later, `set_feature_dependencies({ slug, dependsOn })` replaces a draft's whole set (`[]` clears it); the feature index shows each line's edges as `· waits on: a, b ✓`.
|
|
96
|
+
|
|
87
97
|
**State the base you will cut from; do not ask for it as a matter of course.** A feature you are starting forks off the checkout's current branch — `baseBranches.current` — because that is the branch the human chose to work on. **Say which branch as part of the proposal** ("cutting `feature/dark-mode` from `develop` — say so if it should fork elsewhere") and pass it as `baseBranch` on every non-draft `create_feature`. Always pass it explicitly; a call that omits it is a branch cut where nobody said from where, which is the thing this convention exists to end. There is no routine "which branch?" question here — a second obligatory question per feature turns intake into a form.
|
|
88
98
|
|
|
89
99
|
Three departures from that assumption, and only three:
|
|
@@ -94,7 +104,15 @@ Three departures from that assumption, and only three:
|
|
|
94
104
|
|
|
95
105
|
**Drafts pass no base.** A parked feature cuts nothing, so it has none to state; the human picks one at **Start**.
|
|
96
106
|
|
|
97
|
-
Each one carries a real `brief` — the
|
|
107
|
+
Each one carries a real `brief` — what the shape grill learned, in prose, so ideation starts warm:
|
|
108
|
+
|
|
109
|
+
- **purpose** — why this feature exists and what it is for;
|
|
110
|
+
- **must not swallow** — the boundary you pushed on;
|
|
111
|
+
- **understood** — what the grill already established about it;
|
|
112
|
+
- **open** — what is still unsure, and what the human wants to test-drive first;
|
|
113
|
+
- **ordering** — where it sits relative to its sibling features from the same cut: what must merge before it, and what waits on it. State the order in prose; nothing enforces it.
|
|
114
|
+
|
|
115
|
+
It is written into `brief.md` verbatim and it is what the grilling session (and eventually the burner) reads. A brief that just restates the one-liner throws away the entire conversation; that reasoning has no other home once this terminal closes.
|
|
98
116
|
|
|
99
117
|
**You never launch what you create.** No terminal, no session, no "shall I start on it?". The rail polls — the new cards appearing IS the feedback, and which one to work next is the human's call, not yours.
|
|
100
118
|
|
|
@@ -166,7 +184,7 @@ A session that edits without committing leaves a dirty tree, and a dirty tree is
|
|
|
166
184
|
## Do NOT
|
|
167
185
|
|
|
168
186
|
- **Never create a feature before you have consulted the portfolio and agreed the cut.** §1's order — look, advise, create — is the session.
|
|
169
|
-
- **Never run
|
|
187
|
+
- **Never run a design grilling.** Shape-grill for the cut, and stop there. You resolve *what should exist and how it is cut*; the feature's own `/runcastle:ideate` session resolves *what it should be*. Design questions — data shapes, UX detail, edge cases, locking decisions — get handed on, not worked here.
|
|
170
188
|
- **Never launch a session or terminal** for a feature you created. The card in the rail is the handoff.
|
|
171
189
|
- **Never touch another feature's docs.** `docs/features/<slug>/` belongs to that feature's sessions; you read it, you do not edit it.
|
|
172
190
|
- **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.
|
|
@@ -17,9 +17,9 @@ That is not a limitation to apologise for: it is the contract that makes this se
|
|
|
17
17
|
|
|
18
18
|
## Your tools
|
|
19
19
|
|
|
20
|
-
- `mcp__runcastle__get_feature_context` — the feature, its `phase` and `lap`, its canonical docs inlined in `docs[]` (brief,
|
|
20
|
+
- `mcp__runcastle__get_feature_context` — the feature, its `phase` and `lap`, its canonical docs inlined in `docs[]` (brief, decisions, spec), an index of everything else in `moreDocs[]` (with `relPath` and byte counts), and the tickets as summary rows (`seq`, `title`, `status`, `goal`, …). A canonical doc too large to inline is listed in `notInlined` instead — read it before you answer from it.
|
|
21
21
|
- `mcp__runcastle__get_ticket({ seq })` — one ticket in full: its context, acceptance criteria, and the burner's digest of what it actually did.
|
|
22
|
-
- `mcp__runcastle__read_feature_doc({ relPath })` — one of the `moreDocs[]` entries in full: test notes,
|
|
22
|
+
- `mcp__runcastle__read_feature_doc({ relPath })` — one of the `moreDocs[]` entries in full: test notes, anything else the feature's sessions left behind.
|
|
23
23
|
- `mcp__runcastle__list_tickets({ status? })` — the ticket list on its own, when the question is about the queue and you do not need the whole feature.
|
|
24
24
|
- `mcp__runcastle__record_event({ type: "qa.note", message: "..." })` — a one-line note on the feature timeline. The only write you have, and the timeline is its only record, so use it when something surfaces that the record should keep: a clarification worth remembering, a discovered discrepancy between the docs and the code.
|
|
25
25
|
- Your ordinary `Read`/`Grep` over the repo in this worktree.
|
|
@@ -41,7 +41,3 @@ A Q&A session is the wrong place to change the feature. If the conversation turn
|
|
|
41
41
|
- a **new feature**, if it is really its own capability — created from the project session or the UI.
|
|
42
42
|
|
|
43
43
|
Say which one and why, then `record_event` a one-line note capturing the request so it is not lost. You cannot open any of them, and you cannot create the feature yourself; the human's click is the handoff.
|
|
44
|
-
|
|
45
|
-
## Branching the map
|
|
46
|
-
|
|
47
|
-
If the feature is **mapped** and the conversation surfaces a genuine open question that the map does not carry, you may chart it: `escalate_to_map`, `emit_waypoints` and `resolve_waypoint` stay open to this kind on purpose — any session may branch the map (SPEC §13.3). Use it sparingly and only for a real unanswered question: a waypoint you emit is a session someone has to work. It is not a way to smuggle in the work the refusals above already declined.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: revisit
|
|
3
|
-
description: Fold late-arriving information into a feature whose sessions are finished — amend the docs, then reconcile tickets (update/cancel/emit); an ordinary revisit never moves the feature. Also the lap session: digest the test drive, amend decisions + spec, emit the lap's tickets and
|
|
3
|
+
description: Fold late-arriving information into a feature whose sessions are finished — amend the docs, then reconcile tickets (update/cancel/emit); an ordinary revisit never moves the feature. Also the lap session, planned at review from the Start-lap door: digest the test drive, amend decisions + spec, emit the lap's tickets and hand back to the human's Burn click. Entry skill for kind=revisit sessions.
|
|
4
4
|
disable-model-invocation: false
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -13,11 +13,10 @@ That is the ordinary revisit, and it is what the moves below describe. The one e
|
|
|
13
13
|
## Order of operations
|
|
14
14
|
|
|
15
15
|
1. **Listen first.** The human opens with what changed. If this terminal resumed the previous conversation, use that context; do not re-grill what is already settled. Ask only the questions the NEW information raises.
|
|
16
|
-
2. **Context.** Call `mcp__runcastle__get_feature_context` — the feature, its `phase` and `lap`, the canonical docs inlined in `docs[]` (brief,
|
|
16
|
+
2. **Context.** Call `mcp__runcastle__get_feature_context` — the feature, its `phase` and `lap`, the canonical docs inlined in `docs[]` (brief, decisions, spec), an index of everything else in `moreDocs[]` (test notes and any other doc the feature's sessions left behind; fetch one with `mcp__runcastle__read_feature_doc({ relPath })`), and every ticket as a summary row with its id, `seq`, status and `goal` — its context, acceptance criteria and digest come from `mcp__runcastle__get_ticket({ seq })`. A canonical doc too large to inline is listed in `notInlined` instead; read it before acting. The injected system prompt carries the slug and paths; trust `get_feature_context` for the live state. When all you need is ticket ids to operate on, `mcp__runcastle__list_tickets({ status? })` is the cheap call — it returns the queue without the docs.
|
|
17
17
|
3. **Docs.** Capture the change as decision prose:
|
|
18
18
|
- Append to `docs/features/<slug>/decisions.md` under a dated `## Revisited <date>` heading — never rewrite old decisions, supersede them ("Supersedes: <old decision>").
|
|
19
19
|
- If `spec.md` exists and the change touches it, amend the affected sections in place.
|
|
20
|
-
- If the feature is mapped, keep `map.md` honest (destination/out-of-scope).
|
|
21
20
|
4. **Ticket surgery.** Walk the ticket list against the new reality:
|
|
22
21
|
- Stale but still needed → `mcp__runcastle__update_ticket({ id, ...changed fields })` (pending/failed only).
|
|
23
22
|
- No longer needed → `mcp__runcastle__cancel_ticket({ id, reason })` (pending/failed only).
|
|
@@ -27,7 +26,7 @@ That is the ordinary revisit, and it is what the moves below describe. The one e
|
|
|
27
26
|
|
|
28
27
|
## Lap mode (another lap over the same feature)
|
|
29
28
|
|
|
30
|
-
Your kickoff line reads `LAP <n> REVIEW
|
|
29
|
+
Your kickoff line reads `PLAN LAP <n> FROM REVIEW`. The human burned the last lap, test-drove the branch, and clicked **Start lap `<n>`** on the review page with what it taught them: the code was right, the *spec* wasn't. That click is the start of the lap — you are already in it, so never send the human off to start one. Nothing looped the feature backwards to get you here and nothing can — **Planning → Building → Review → Shipped** runs one way only — so the feature stands at **review**, still on lap `<n-1>`, while you plan. The human's **Burn** click is what starts lap `<n>`: it moves the pending tickets you emit onto it and takes the feature through building. One trip round is a lap, and this session is the whole front half of lap `<n>` — you carry it from what-the-drive-taught to ticket cards waiting on that click, in this one conversation:
|
|
31
30
|
|
|
32
31
|
1. **Context.** `mcp__runcastle__get_feature_context` — the feature (including its `lap`), the docs, `reviewEvidence`, and the full ticket history across every lap as summary rows (title, status, `goal`, `error`); each ticket's own `lap` is what separates them, and `mcp__runcastle__get_ticket({ seq })` gives one ticket's context, acceptance criteria and digest. A canonical doc listed in `notInlined` must be read before you plan. Skim what the last lap actually landed and what failed before you interview.
|
|
33
32
|
2. **Read the evidence before you plan.** Not after, and not instead of the interview. Your inputs, in reading order:
|
|
@@ -37,21 +36,21 @@ Your kickoff line reads `LAP <n> REVIEW ITERATION`. The human burned the last la
|
|
|
37
36
|
3. **Never re-emit a promoted note.** Notes the human already promoted from the review checklist are tickets in *this* lap and arrive as ids in your context. However well such a note reads as a ticket, if its id is in front of you the work is already carded — emitting it again gives the burner the same job twice.
|
|
38
37
|
4. **Grill, briefly.** A small lap is a short grilling: a few questions, one at a time, each with your recommended answer attached. Prune and promote `## Later laps` entries with the human as part of that conversation — what the drive taught is usually what decides which deferred scope this lap picks up and which stays parked.
|
|
39
38
|
5. **Amend the docs.** Append this lap's learning to `docs/features/<slug>/decisions.md` under a `## Lap <n>` heading (the convention) — never rewrite old decisions, supersede them. Then amend `spec.md` in place: the sections this lap changes, plus the pruned `## Later laps`.
|
|
40
|
-
6. **Emit this lap's tickets, dispositioning the defects as you do.** `mcp__runcastle__emit_tickets({ tickets })` — only the work this lap will burn; the rest stays in `## Later laps`. Every ticket that answers a bug the human reported obeys **Stand on the failure** below. Reconcile stale pending tickets with `update_ticket` / `cancel_ticket`; `done` work that is now wrong gets a NEW ticket, as always.
|
|
39
|
+
6. **Emit this lap's tickets, dispositioning the defects as you do.** `mcp__runcastle__emit_tickets({ tickets })` — only the work this lap will burn; the rest stays in `## Later laps`. They land `pending`, and the Burn click carries them onto lap `<n>`. Every ticket that answers a bug the human reported obeys **Stand on the failure** below. Reconcile stale pending tickets with `update_ticket` / `cancel_ticket`; `done` work that is now wrong gets a NEW ticket, as always.
|
|
41
40
|
|
|
42
|
-
The defects an **earlier** lap's review left open arrive in your context as `openDefects` (`carriedDefects` are the ones a lap already parked — agenda, not obligation). Each open one needs exactly one of three answers. Nothing refuses over the ones you leave:
|
|
41
|
+
The defects an **earlier** lap's review left open arrive in your context as `openDefects` (`carriedDefects` are the ones a lap already parked — agenda, not obligation). Each open one needs exactly one of three answers. Nothing refuses over the ones you leave: the human reads them back as warnings in the Burn dialog, so finishing this triage is yours, not a gate's:
|
|
43
42
|
|
|
44
43
|
- **Link** — this lap is carding the work that fixes it: emit that ticket with `originFindingId: "<the defect's id>"`. Nothing else to do; the burn closes the finding itself when the ticket lands.
|
|
45
44
|
- **Carry** — nobody is answering it this lap: `mcp__runcastle__resolve_finding({ findingId, disposition: "carry" })`. It leaves the open count, stays visible, and only the human reopens it.
|
|
46
45
|
- **Close as addressed** — this lap's work already answers it and no ticket names it: `mcp__runcastle__resolve_finding({ findingId, disposition: "addressed", note: "<what addressed it>" })`. The note is required and is your attestation, so make it specific ("lap 2's ticket 7 rewrote the endpoint").
|
|
47
46
|
|
|
48
47
|
Judge each against what you and the human just settled — you are the only party that knows whether this lap's tickets touch a defect, which is why this is yours and not a dialog. If one genuinely is not this lap's business, carry it; do not close what you have not established.
|
|
49
|
-
7. **
|
|
48
|
+
7. **Do NOT call `complete_phase`.** Its steps belong inside Planning, and this feature is at review: there is nothing to report, and the Burn click is what starts the lap. Before you hand over, check what the Burn dialog would warn about and fix what you can while you are still the session that can — un-dispositioned defects from move 6, no review ticket in the batch, a `spec.md` this lap changed but you have not amended.
|
|
50
49
|
8. **Hand to Burn.** Tell the human in a line or two what this lap does, then:
|
|
51
50
|
|
|
52
51
|
> Lap `<n>` is specced and carded. Review the ticket cards and click **Burn** — I'll stop here.
|
|
53
52
|
|
|
54
|
-
If this lap turns out to be genuinely big — whole branches of design reopened, decisions hanging on material nobody has read — say so and
|
|
53
|
+
If this lap turns out to be genuinely big — whole branches of design reopened, decisions hanging on material nobody has read — say so and recommend a thinner lap: card the part the human can test-drive now and defer the rest into the spec's `## Later laps`, rather than grinding it all out here.
|
|
55
54
|
|
|
56
55
|
### Stand on the failure
|
|
57
56
|
|
|
@@ -65,8 +64,7 @@ Never card a fix for a failure nobody has stood on: a ticket written from a one-
|
|
|
65
64
|
|
|
66
65
|
## Do NOT
|
|
67
66
|
|
|
68
|
-
- **Never call `complete_phase
|
|
69
|
-
- **Never resolve or reopen waypoints.** If a resolved waypoint's answer is now wrong, record the superseding decision in `decisions.md` — the map's history stays intact.
|
|
67
|
+
- **Never call `complete_phase`** — not in an ordinary revisit and not in Lap mode. Neither has planning steps of its own to report; the human's clicks move the feature.
|
|
70
68
|
- **Code changes ride tickets** — the burner (or a human) implements them, never this session. (The no-code rule itself is in your injected prompt and enforced by the edit guard.)
|
|
71
69
|
|
|
72
70
|
## Scope check
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spec
|
|
3
|
-
description: Synthesize the ideation conversation and decisions.md into docs/features/<slug>/spec.md, then complete the spec phase. No interview — pure synthesis. Invoked by /runcastle:ideate
|
|
3
|
+
description: Synthesize the ideation conversation and decisions.md into docs/features/<slug>/spec.md, then complete the spec phase. No interview — pure synthesis. Invoked by /runcastle:ideate.
|
|
4
4
|
disable-model-invocation: false
|
|
5
5
|
---
|
|
6
6
|
<!-- Forked from Matt Pocock's to-spec skill, via https://github.com/mattpocock/skills, 2026-07-14, adapted for runcastle -->
|
|
@@ -44,4 +44,4 @@ Turn the ideation conversation plus `docs/features/<slug>/decisions.md` into a s
|
|
|
44
44
|
|
|
45
45
|
4. **Report the step.** `mcp__runcastle__complete_phase({ phase: "spec" })`. It does not refuse and it does not move the feature — spec is a step inside **Planning** — but it answers with `nextStep`, the next step still missing its artifact. If that comes back `"spec"`, `spec.md` is not on disk where the server looks: write it and call again. It logs its own timeline event and checkpoints `spec.md` into git; do not add an event of your own.
|
|
46
46
|
|
|
47
|
-
Return control to **the session skill that invoked you** — `/runcastle:ideate
|
|
47
|
+
Return control to **the session skill that invoked you** — `/runcastle:ideate`. It invokes `/runcastle:tickets` next. Do **not** invoke a session skill yourself to hand back: you are already inside one, and loading another session's entry skill would drop a whole procedure this session is not running into the window.
|