runcastle 1.4.8 → 1.5.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.
Files changed (52) hide show
  1. package/bin/runcastle.js +10604 -7555
  2. package/drizzle/0045_nappy_adam_destine.sql +3 -0
  3. package/drizzle/0046_polite_nightcrawler.sql +20 -0
  4. package/drizzle/0046_project_scenario_runner.sql +1 -0
  5. package/drizzle/0047_clear_betty_brant.sql +3 -0
  6. package/drizzle/0048_new_northstar.sql +1 -0
  7. package/drizzle/0050_tan_manta.sql +1 -0
  8. package/drizzle/0051_ambiguous_talkback.sql +1 -0
  9. package/drizzle/0052_friendly_black_tarantula.sql +1 -0
  10. package/drizzle/0053_soft_karma.sql +12 -0
  11. package/drizzle/meta/0045_snapshot.json +1220 -0
  12. package/drizzle/meta/0046_snapshot.json +1227 -0
  13. package/drizzle/meta/0047_snapshot.json +1349 -0
  14. package/drizzle/meta/0048_snapshot.json +1356 -0
  15. package/drizzle/meta/0050_snapshot.json +1349 -0
  16. package/drizzle/meta/0051_snapshot.json +1356 -0
  17. package/drizzle/meta/0052_snapshot.json +1364 -0
  18. package/drizzle/meta/0053_snapshot.json +1283 -0
  19. package/drizzle/meta/_journal.json +63 -0
  20. package/hook-client.ts +110 -9
  21. package/index.js +10601 -7552
  22. package/package.json +1 -1
  23. package/skills/burner/implement-ticket.md +0 -1
  24. package/skills/burner/review-ticket.md +7 -5
  25. package/skills/burner/verify-fixes.md +4 -2
  26. package/skills/packs/README.md +24 -28
  27. package/skills/packs/runcastle/.claude-plugin/plugin.json +1 -1
  28. package/skills/packs/runcastle/skills/feature/SKILL.md +72 -0
  29. package/skills/packs/runcastle/skills/feature/references/design.md +132 -0
  30. package/skills/packs/runcastle/skills/feature/references/grilling.md +99 -0
  31. package/skills/packs/runcastle/skills/feature/references/planning-alone.md +70 -0
  32. package/skills/packs/runcastle/skills/{ideate → feature}/references/preview-starter.html +27 -17
  33. package/skills/packs/runcastle/skills/{ideate → feature}/references/preview.md +67 -8
  34. package/skills/packs/runcastle/skills/feature/references/rounds.md +91 -0
  35. package/skills/packs/runcastle/skills/feature/references/spec.md +54 -0
  36. package/skills/packs/runcastle/skills/feature/references/tickets.md +229 -0
  37. package/skills/packs/runcastle/skills/prepare/SKILL.md +19 -5
  38. package/skills/packs/runcastle/skills/prepare/references/recipes.md +6 -2
  39. package/skills/packs/runcastle/skills/project/SKILL.md +115 -14
  40. package/skills/packs/runcastle/skills/project/references/charter.md +3 -3
  41. package/skills/packs/runcastle/skills/project/references/health-sweeps.md +5 -5
  42. package/skills/packs/runcastle/skills/project/references/triage.md +21 -24
  43. package/skills/packs/runcastle/skills/qa/SKILL.md +1 -2
  44. package/web/assets/index-CT9k1kz0.css +1 -0
  45. package/web/assets/index-DVR7VbWW.js +186 -0
  46. package/web/index.html +2 -2
  47. package/skills/packs/runcastle/skills/ideate/SKILL.md +0 -128
  48. package/skills/packs/runcastle/skills/revisit/SKILL.md +0 -78
  49. package/skills/packs/runcastle/skills/spec/SKILL.md +0 -47
  50. package/skills/packs/runcastle/skills/tickets/SKILL.md +0 -119
  51. package/web/assets/index-CUoLAKY6.js +0 -188
  52. package/web/assets/index-bNgQedte.css +0 -1
package/web/index.html CHANGED
@@ -18,8 +18,8 @@
18
18
  document.documentElement.dataset.theme = t
19
19
  })()
20
20
  </script>
21
- <script type="module" crossorigin src="/assets/index-CUoLAKY6.js"></script>
22
- <link rel="stylesheet" crossorigin href="/assets/index-bNgQedte.css">
21
+ <script type="module" crossorigin src="/assets/index-DVR7VbWW.js"></script>
22
+ <link rel="stylesheet" crossorigin href="/assets/index-CT9k1kz0.css">
23
23
  </head>
24
24
  <body>
25
25
  <div id="root"></div>
@@ -1,128 +0,0 @@
1
- ---
2
- name: ideate
3
- description: The runcastle ideation session. Grill the human relentlessly about a feature, lock decisions incrementally into decisions.md, then drive spec and tickets out of the same unbroken context. Entry skill for kind=ideation sessions.
4
- disable-model-invocation: false
5
- ---
6
- <!-- Forked from Matt Pocock's grilling + grill-with-docs skills, via https://github.com/mattpocock/skills, 2026-07-14, adapted for runcastle -->
7
-
8
- # Ideate — runcastle's ideation session
9
-
10
- You are the front of the runcastle pipeline. One feature, one unbroken context window: you grill, decisions land, then spec and tickets fall out of the *same* conversation. After you, the human's only job is to click **Burn**. Do it right.
11
-
12
- ## Context hygiene is the whole game
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: narrow, don't compact. (Matt Pocock's rule; it is load-bearing here.)
15
-
16
- ## 0. Orient
17
-
18
- 1. Call `mcp__runcastle__get_feature_context`. It opens with a header — the feature (`slug`, `title`, `oneLiner`), current `phase` and `lap`, `annotatedModels`, `burnConcurrency` — then any tickets as summary rows (`seq`, `title`, `status`, `goal`, …; a ticket's context, acceptance criteria and digest come from `mcp__runcastle__get_ticket({ seq })`), then the canonical docs inlined in `docs[]` (`brief.md`, and any `decisions.md` — **this may be a resumed session; read what is already locked and do not re-ask it**), and an index of every other doc in `moreDocs[]` (fetch one with `mcp__runcastle__read_feature_doc({ relPath })`). A canonical doc too large to inline is listed in `notInlined` with its `relPath` and `absPath` instead — **read it before acting**, with `read_feature_doc` or off disk.
19
- 2. Decisions live at `docs/features/<slug>/decisions.md` in this worktree. The injected system prompt carries the slug and paths; trust `get_feature_context` for the live state.
20
-
21
- **Your tools**, and there are not many — this session's real work is the conversation:
22
-
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, 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 (§4), and the ones `/runcastle:spec` and `/runcastle:tickets` cross after you.
26
- - `mcp__runcastle__emit_tickets` — used by `/runcastle:tickets`, not by you directly.
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.
28
-
29
- ## 1. Grill relentlessly
30
-
31
- This is the **grill path**: the human is here and answers. When your briefing says no human is present, take the **self-planning path** in "Planning without a human" below instead; the rules of this section do not bind it.
32
-
33
- Interview the human about every aspect of the feature until you reach a genuinely shared understanding. Walk each branch of the design tree, resolving dependencies between decisions one at a time.
34
-
35
- - **One question at a time.** Wait for the answer before the next. Asking several at once is bewildering.
36
- - **Always recommend an answer.** Never ask a bare question — put your recommended answer to it and say why. The human corrects or confirms.
37
- - **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.
38
- - **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?").
39
- - **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. 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.
41
- - **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
-
43
- On the grill path, do not move on until the human confirms you have reached shared understanding. That confirmation happens on the preview (§3), not in chat alone.
44
-
45
- ## 1a. Planning without a human
46
-
47
- This is the **self-planning path**. It applies only when your briefing says no human is present; with a human here, §1 stands and this section does not. You still look up facts, model the domain and park scope creep as §1 says. What changes is who answers.
48
-
49
- - **One turn.** Do the whole plan in this one turn. Never end the turn to ask a question or to report progress: nobody will reply, and ending the turn ends the run.
50
- - **Frontier rounds.** Work in rounds. A round is every open question that does not depend on an unanswered one. Write each as you would put it to the human, with your recommended answer and the main alternative, then accept your own recommendations and record them. Then ask what those answers opened: that is the next round. Write each round to `decisions.md` as it is decided (§2), not at the end, so a run that dies keeps what it had.
51
- - **Depth rule.** Record a decision only if it changes what the feature is or does for its users, or the human might reasonably overturn it. Anything an engineer would settle while building (wording, internal formats, test coverage) is not a question; it goes to the spec as detail.
52
- - **Exactly one challenger.** When a round would be empty, run one separate agent: the Agent tool in Claude Code, `spawn_agent` in Codex. Give it only the brief and `decisions.md`. Ask it for (a) design branches never asked, within the depth rule, and (b) decisions it would have made differently, with its reason. Do not tell it what you expect to hear. Ask its branches as one more round and decide them, with whatever they open. A decision it disagrees with is marked challenged with its reason (§2), and your own answer stands. There is no second pass, and no requirement that the pass come back empty. If you have no tool that runs a separate agent, challenge your own plan as a separate pass and write in `decisions.md` that the challenge was not independent.
53
- - **A refused tool is not a wait.** If a tool is refused, do not retry it and do not wait: decide on what you know, and treat that decision as a candidate to check first.
54
- - **A timed-out tool is not a refused one.** A refusal is runcastle's answer; a timeout is no answer, and the call may have landed anyway. Retry it once, checking first that it did not land: `complete_phase` is safe to repeat (a step already reported comes back ok with a note), but before repeating `emit_tickets`, `update_ticket` or `cancel_ticket`, read `list_tickets` and send only what is missing. If the retry times out too, carry on and say so in the closing message. Never leave `complete_phase({ phase: "tickets" })` on a timeout: until it lands, Burn is not armed.
55
- - **Check first.** Pick at most five decisions for the human to check first: guesses about the human's intent that the brief did not settle, decisions that force several others, and decisions the challenger disagreed with. They open the preview's decisions part (`./references/preview.md`).
56
- - **Straight through.** Then draw the preview and run its self-check (§3, steps 1 and 2), but do not tell the human to look and do not wait: complete ideation (§4), run `/runcastle:spec`, then `/runcastle:tickets` (§5). End with one closing message (§6) that covers the plan only: the plan was made without the human, how many decisions it holds, and which are to check first, plus anything that stopped you doing the job (a refused or failed tool the plan needed). Leave out the state of tools and connectors the plan did not need.
57
-
58
- ## 2. Lock decisions incrementally
59
-
60
- The moment a decision locks, append it to `docs/features/<slug>/decisions.md` — **immediately, one at a time, never batched at the end.** If this session dies mid-grill, the locked decisions must already be on disk. Append entries in this shape:
61
-
62
- ```markdown
63
- ## <n>. <short decision title>
64
- **Decision:** <the choice, in the project's domain vocabulary>
65
- **Why:** <the reason / the trade-off chosen over the alternatives>
66
- ```
67
-
68
- A decision you made without the human (the self-planning path, §1a) keeps that shape and adds one line naming the main alternative it was chosen over. A decision the challenger disagreed with adds a further line with its reason:
69
-
70
- ```markdown
71
- ## <n>. <short decision title>
72
- **Decision:** <the choice>
73
- **Why:** <the reason>
74
- **Assumed.** Alternative: <the main alternative>.
75
- **Challenged:** <the challenger's reason>.
76
- ```
77
-
78
- For example:
79
-
80
- ```markdown
81
- ## 4. Tags and dates are left out of the PDF
82
- **Decision:** The PDF carries the title and body only.
83
- **Why:** The brief asks for the note, not its filing.
84
- **Assumed.** Alternative: a small footer with tags and the last-edited date.
85
- ```
86
-
87
- When the human later confirms or changes such a decision, the session doing the edit removes its `**Assumed.**` and `**Challenged:**` lines; from then on it reads as any other decision.
88
-
89
- 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.
90
-
91
- ## 3. Draw the preview, then wait for the human to look
92
-
93
- **The preview is what the human confirms shared understanding on.** It is one self-contained page, `docs/features/<slug>/preview.html`, that shows the feature and every decision; the human reads the feature by looking at it.
94
-
95
- 1. **Draw it.** When the decisions are locked, read `./references/preview.md` (the rules) and write `preview.html` from `./references/preview-starter.html`.
96
- 2. **Check it against `decisions.md`.** Every decision appears by its number and title. Fix the preview before anyone sees it.
97
-
98
- On the self-planning path (§1a) steps 1 and 2 are the whole of this section: go straight on to §4. Steps 3 to 5 are the grill path.
99
-
100
- 3. **Tell the human to look, then wait.** Say the preview is on the feature's **Overview**, under the Burn row, and that the "Preview ready" notice in Chat leads there. Then stop and wait for their answer.
101
- 4. **A correction is a decision edit plus a redraw.** Edit the decision in `decisions.md` and redraw the preview in the same move, then ask them to look again.
102
- 5. **Only after the human confirms**, complete ideation (§4).
103
-
104
- **Never skip the preview on your own judgement** — not because the feature is small, not because the grill went quickly. A small feature gets a small preview. **If the human says to skip it**, write no file and go straight on to §4.
105
-
106
- **The redraw rule.** While the feature is at planning, any later edit to `decisions.md` — in this session or after it — updates `preview.html` in the same move, before anything else.
107
-
108
- ## 4. Complete ideation
109
-
110
- On the grill path, when the human confirms shared understanding on the preview and the open questions are answered or explicitly deferred. On the self-planning path (§1a), as soon as the preview passes its check, without waiting:
111
-
112
- `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.
113
-
114
- ## 5. Spec → tickets — stay in this window
115
-
116
- Invoke `/runcastle:spec` (it writes `spec.md` and completes the `spec` phase); when it returns, invoke `/runcastle:tickets`.
117
-
118
- Do not open a new session for these. They run here, on top of everything you just learned.
119
-
120
- ## 6. Close — hand off to the human
121
-
122
- `/runcastle:tickets` ends by emitting tickets to the runcastle store. When it returns, your job is **done**. Tell the human plainly:
123
-
124
- > Tickets are in the runcastle UI. Review the ticket cards and click **Burn** to start the AFK agents. That is the next step — I will stop here.
125
-
126
- On the self-planning path (§1a), the closing message also says that the plan was made without the human, how many decisions it holds, and which are to check first, by number and title. It covers the plan only. Report anything that stopped you doing the job: a tool the plan needed that was refused or failed, and what you decided without it. Do not report the state of tools and connectors the plan did not need, such as MCP servers that failed to connect or connectors waiting to be authorised: the session inherits the human's own, and they are not part of the plan. Then end the turn; the run is done.
127
-
128
- Do **not** start burning. Burn and Merge are the only two clicks in the whole pipeline, and both belong to the human. (You do not write code here either — that rule is in your injected prompt and enforced by the edit guard, not restated as advice.)
@@ -1,78 +0,0 @@
1
- ---
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, 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
- disable-model-invocation: false
5
- ---
6
-
7
- # Revisit
8
-
9
- The human came back: they remembered a constraint, changed their mind, or learned something that the grilling/spec didn't capture. This session's job is to make the **record** and the **ticket queue** match the new reality — nothing more. The pipeline does not move; whatever phase the feature is in, it stays in.
10
-
11
- That is the ordinary revisit, and it is what the moves below describe. The one exception is a **lap**, and your kickoff line is what tells you: a lap briefing opens this same session with a bigger job — see **Lap mode** below, and work that section instead. Go by the line you were given, never by a guess from the feature's state.
12
-
13
- ## Order of operations
14
-
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, 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
- 3. **Docs.** Capture the change as decision prose:
18
- - Append to `docs/features/<slug>/decisions.md` under a dated `## Revisited <date>` heading — never rewrite old decisions, supersede them ("Supersedes: <old decision>").
19
- - **Clear the assumed mark.** A decision the session made without the human carries an `**Assumed.**` line, and perhaps a `**Challenged:**` line. When the human confirms or changes such a decision, remove both lines from it in the same edit. Decisions the human did not touch keep theirs.
20
- - If `spec.md` exists and the change touches it, amend the affected sections in place.
21
- - **Redraw the preview.** When the feature is at `planning` and `docs/features/<slug>/preview.html` exists, update `preview.html` in the same move as the decisions edit, before anything else, following `../ideate/references/preview.md`, check-first group included: a decision that lost its assumed mark leaves that group. The human reads the plan off the preview, so a preview that disagrees with `decisions.md` misleads them at Burn.
22
- 4. **Ticket surgery.** Walk the ticket list against the new reality:
23
- - Stale but still needed → `mcp__runcastle__update_ticket({ id, ...changed fields })` (pending/failed only).
24
- - No longer needed → `mcp__runcastle__cancel_ticket({ id, reason })` (pending/failed only).
25
- - New work required → `mcp__runcastle__emit_tickets({ tickets })` (batch, same shape as ideation).
26
- - `done` work now wrong → emit a NEW ticket that corrects it. Never touch done/burning tickets.
27
- 5. **Close.** `mcp__runcastle__record_event({ type: "feature.revisited", message: "<one-line gist of what changed>" })`, then tell the human what state you left things in — especially whether a re-Burn is needed (pending tickets exist and the feature is at `building` with no run behind them).
28
-
29
- ## Lap mode (another lap over the same feature)
30
-
31
- 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:
32
-
33
- 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.
34
- 2. **Read the evidence before you plan.** Not after, and not instead of the interview. Your inputs, in reading order:
35
- - **The previous lap's review evidence.** Your injected prompt names it by absolute path, and `get_feature_context` → `reviewEvidence` carries the same paths: `digestPath` is the review agent's own account of what it found, `dir` holds that pass's screenshots and `walkthrough.webm`, and `status` is how the pass itself ended — the review outcome. It reaches you through nothing else: the directory is host scratch space outside the repo, and the context payload's ticket rows carry no `digest` — a ticket's own digest is one `get_ticket({ seq })` away. An empty `reviewEvidence` means the previous lap finished no review pass — say that plainly rather than implying you read one.
36
- - **What the human wrote down**: the previous lap's section of `docs/features/<slug>/test-notes.md` — it is in `moreDocs[]`, so read it with `mcp__runcastle__read_feature_doc({ relPath: "test-notes.md" })` (or off disk) — and the `## Later laps` section of `spec.md`, which is inlined in `docs[]`. **Both may be absent** — that is not an error and not something to go hunting for: interview the human from scratch instead. What did they hit, what surprised them, what do they want instead now they've used it?
37
- - **What the docs say is not demonstrable yet.** Grep `spec.md` and this feature's tickets for `not demonstrable`, `do not demo` and `later laps`, and tell the human what you found **before** you offer a test drive. A human was once handed a lap-1 build whose own plan said "no UI, nobody should demo this before ticket X"; the drive taught nothing and cost them the time to find that out. Say which parts are drivable and which are not, then offer the drive.
38
- 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.
39
- 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.
40
- 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`. A lap planned from review does not redraw the preview: leave `preview.html` as it is.
41
- 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.
42
-
43
- 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:
44
-
45
- - **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.
46
- - **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.
47
- - **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").
48
-
49
- 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.
50
- 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.
51
- 8. **Hand to Burn.** Tell the human in a line or two what this lap does, then:
52
-
53
- > Lap `<n>` is specced and carded. Review the ticket cards and click **Burn** — I'll stop here.
54
-
55
- 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.
56
-
57
- ### Stand on the failure
58
-
59
- Every bug the human reports gets exactly one of three answers before you card a fix for it:
60
-
61
- - **Reproduced** — you drove it and saw the failure. Say what you did and what happened.
62
- - **Traced** — you found it in the code. Name the file and line, and what there is wrong.
63
- - **Unreproduced** — you could not do either. Say so plainly, and then make reproducing it the **first** acceptance criterion of the ticket that fixes it, so the burner stands on the failure before it changes anything.
64
-
65
- Never card a fix for a failure nobody has stood on: a ticket written from a one-line report and a guess sends an agent to change the code it guessed at, and the next drive hits the same bug. This is what the human's report buys — it is the report, not a diagnosis.
66
-
67
- ## Do NOT
68
-
69
- - **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
- - **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
-
72
- ## Scope check
73
-
74
- If what the human brings is not an amendment but a whole new capability, say so — and park it rather than losing it:
75
-
76
- > That reads like a new feature, not a revision of this one. I'll park it as a draft so it isn't lost; you can start it from the runcastle UI when you want it, and it gets grilled there.
77
-
78
- `mcp__runcastle__create_feature({ title, oneLiner, brief, draft: true })` — a parked draft, no branch cut and nothing written, with the `brief` carrying why you deferred it and what it must not swallow. `draft: true` is the only door open to you; a full create belongs to the project session.
@@ -1,47 +0,0 @@
1
- ---
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.
4
- disable-model-invocation: false
5
- ---
6
- <!-- Forked from Matt Pocock's to-spec skill, via https://github.com/mattpocock/skills, 2026-07-14, adapted for runcastle -->
7
-
8
- # Spec
9
-
10
- Turn the ideation conversation plus `docs/features/<slug>/decisions.md` into a spec at `docs/features/<slug>/spec.md`. **Do NOT interview the human** — you are inside the unbroken ideation window; synthesize what you already know. Do not compact or clear.
11
-
12
- ## Process
13
-
14
- 1. **Load state.** `mcp__runcastle__get_feature_context` for the feature, the locked `decisions.md`, and any existing `spec.md` (this may be a re-run). A canonical doc too large to inline arrives in `notInlined` instead of `docs[]` — read it (`mcp__runcastle__read_feature_doc({ relPath })` or the file at its `absPath`) before writing a word; tickets arrive as summary rows with their `goal`, and `mcp__runcastle__get_ticket({ seq })` holds a ticket's context, acceptance criteria and digest. Explore the codebase for its current state if you have not already; use the project's domain vocabulary throughout and respect any ADRs in the area you touch.
15
-
16
- 2. **Sketch the seams** — the public boundaries the feature will be tested at. Prefer *existing* seams to new ones; use the *highest* seam possible; the fewer across the codebase, the better — the ideal is one. If new seams are needed, propose them at the highest point you can. These seams flow straight into the tickets and the burner, so get them right here.
17
-
18
- 3. **Write `spec.md`** with exactly these sections:
19
-
20
- ```markdown
21
- # <Feature title>
22
-
23
- ## Problem
24
- The problem the user faces, from the user's perspective.
25
-
26
- ## Approach
27
- The solution from the user's perspective, then the shape of it: modules built/modified, their interfaces, schema/API/contract decisions, key interactions. Prose, not a file-by-file plan.
28
-
29
- ## Seams
30
- The interfaces to test at (from step 2) — each named, with what it lets you observe. Flag which are existing vs. new.
31
-
32
- ## Out of scope
33
- What this feature explicitly does not cover.
34
-
35
- ## Open questions
36
- Anything still unresolved or deliberately deferred.
37
-
38
- ## Later laps
39
- Optional — include it when this is a thin lap 1. Scope consciously deferred
40
- to a later lap; it seeds the next lap's session alongside the test notes.
41
- ```
42
-
43
- Do **not** hardcode specific file paths or code snippets — they go stale. Exception: if the ideation produced a snippet that pins a decision more precisely than prose can (a state machine, reducer, schema, type shape), inline the decision-rich parts and note it came from that discussion.
44
-
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
-
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.
@@ -1,119 +0,0 @@
1
- ---
2
- name: tickets
3
- description: Break the spec into vertical slices sized to earn a fresh sandboxed agent session and land inside it, and emit them via MCP. Then complete the tickets phase. Invoked by /runcastle:ideate.
4
- disable-model-invocation: false
5
- ---
6
- <!-- Forked from Matt Pocock's to-tickets skill, via https://github.com/mattpocock/skills, 2026-07-14, adapted for runcastle -->
7
-
8
- # Tickets
9
-
10
- Break the work into **tickets** — vertical slices, each cutting end-to-end through every layer it needs, each big enough to be worth a whole agent session and small enough to finish inside one — and emit them to the runcastle store via `mcp__runcastle__emit_tickets`. You are still in the unbroken ideation window: synthesize from the spec (`docs/features/<slug>/spec.md`). Do not compact or clear. If `get_feature_context` lists a canonical doc in `notInlined`, read it before you slice. Its existing tickets are summary rows (`seq`, `title`, `status`, `goal`, …); `mcp__runcastle__get_ticket({ seq })` holds one ticket's context, acceptance criteria and digest.
11
-
12
- ## The one thing that makes a runcastle ticket different
13
-
14
- Each ticket is executed by a **single fresh agent, alone, in a sandbox, with ~100k tokens and NO way to ask a follow-up question.** It sees only what you put in the ticket plus the code already in the repo. So — *inverting* the usual "avoid file paths, they go stale" rule — the ticket's `context` field **must be concrete and self-sufficient**: name the files to touch, the existing patterns to copy, the type/schema shapes, the gotchas, the commands to run and verify with. A ticket that assumes the agent can ask "where does X live?" is a broken ticket. Over-specify context; under-specify nothing.
15
-
16
- ## The second thing: a ticket costs ~10 minutes before it does any work — and pays it twice if it overruns
17
-
18
- That fresh agent is fresh *every time*. Each ticket gets its own container: cold build, a full dependency install (measured 70–507s, ~2.5 min average), then an agent that re-reads the spec, the docs digest, and every file your `context` names — reading the repo was 16% of all agent time across 42 measured attempts. **This overhead is paid per ticket and is the same whether the ticket is twenty lines or a day's work.** Splitting one coherent piece of work into three tickets triples it, and adds two seams where a new agent has to reconstruct what the previous one had just finished learning.
19
-
20
- But that meter runs both ways. A ticket too big to finish in one pass gets no cheap second wind: an iteration is not a retry (ADR-0008) — the container is rebuilt, the install runs again, and a brand-new agent re-reads everything from the last commit, having lost every conclusion its predecessor reached. An overstuffed ticket pays the exact overhead it was merged to avoid, on top of the orientation it already paid once, and it pays it *after* the work is half-done rather than before.
21
-
22
- So size a ticket to **earn its container and still land in one pass**: substantial, with room left at the end for the tests, the self-review and the commits — not the smallest slice you can name, and not the largest you can imagine fitting. Fine-grained slicing still matters — it just belongs *inside* a ticket, as the `acceptanceCriteria` list. The burner works red→green one criterion at a time and commits each green slice, so one ticket with six criteria produces six small commits and orients once; six one-criterion tickets produce the same commits and orient six times.
23
-
24
- Expect a **handful of substantial tickets** for a feature — roughly 4–8, each worth a session. A dozen thin ones means you sliced by layer or by file, not by behaviour. Two or three giant ones means you will pay back the containers you saved, with interest, when they overrun.
25
-
26
- ## 1. Draft vertical slices
27
-
28
- <vertical-slice-rules>
29
- - Each slice cuts a narrow but COMPLETE path through every layer it needs (schema, API, UI, tests) — vertical, NOT a horizontal slice of one layer.
30
- - A completed slice is demoable or verifiable on its own.
31
- - Each slice is worth one fresh ~100k-token agent session working alone and **lands inside it** — the orientation at the front and the tests, self-review and commits at the back are fixed tax, so leave them room instead of filling the window to the brim.
32
- - **Merge when a merge test holds.** Two drafts are one ticket if they touch the same files, share a type or module, or the second one's first act would be re-reading what the first just wrote. Same for the obvious siblings of one behaviour: create/edit/delete of a resource, an endpoint and the UI that calls it, code and the tests that cover it. Two drafts that share none of that are not one ticket merely because both would fit.
33
- - **Split only for a real reason:** (a) a genuine gate — the later work cannot be written until the earlier work's interface exists; (b) the combined work would not land in one session; (c) a prefactor, which is its own slice done first ("make the change easy, then make the easy change"); (d) parallel width, under the rule below; (e) the wide-refactor sequence below.
34
- - **Not reasons to split:** one ticket per file, layer, endpoint or component; keeping each ticket easy; keeping diffs small; separating tests from the code they test.
35
- - **Parallelism pays when the halves are file-disjoint.** Tickets with no blocking edge burn concurrently, each on its own temp branch, landing one at a time (ADR-0002) — so two substantial slices touching *different* files buy real wall-clock for one container. Two touching the *same* files race to land, and the loser fails on a merge conflict: that width costs a re-burn, which is worse than the container it bought. Overlapping files → merge.
36
- </vertical-slice-rules>
37
-
38
- Give each ticket its **blocking edges** — the tickets that must finish before it can start. Every ticket carries them: the field is required, so a ticket with no blockers gets an empty array and can start immediately.
39
-
40
- <review-ticket>
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
-
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). Whatever the lap turned out to be, there is always a review the agent can run: a diff is the one thing every batch produces by definition. 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
- The server says so too, and the rule is yours to keep either way: a batch with no `kind: "review"` ticket comes back from `complete_phase({ phase: "tickets" })` as a **warning**, and the human sees the same sentence in the Burn dialog — it never refuses, so nothing but you stops the lap shipping unlooked-at. `emit_tickets` does still refuse a batch outright when a ticket is *titled* like the review but carries another kind — set the kind (or retitle it) and re-emit.
46
-
47
- The agent reviews in **exactly one of two modes**, never both — a browser **Drive** of the app against your acceptance criteria, or **Gates**: the project's verify commands plus a two-axis read of the diff. Reviews that attempted both ran long or died having delivered neither, so the burner prompt makes the agent choose up front and spend the whole review inside the choice.
48
-
49
- Your job is to make that choice obvious. Prescribe a walkthrough only when there is genuinely a surface to walk, in plain prose ("open the settings flow, add a project, check the empty state"), and write the `acceptanceCriteria` as the steps of that walk. When the lap changed nothing a human could drive — prompt contracts, docs, an internal refactor, a backend-only change — say so in the ticket, and write criteria the gates-and-diff mode can actually check off. Do not ask for both; do not ask for a browser walk of something with no UI.
50
-
51
- 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.
52
- </review-ticket>
53
-
54
- <parallelism-budget>
55
- The other thing that is true of the batch as a whole: **budget the parallelism explicitly, before you emit.** The burn runs up to `burnConcurrency` tickets at once — the project's setting, served to you in `get_feature_context` as `burnConcurrency` — but a ticket cannot start until every ticket in its `blockedBy` has landed, so the lap's wall-clock is set by its **critical path**: the longest chain of blocking edges through the batch. A chain of N tickets is N sequential sandboxes however wide the pool is. Measured on a real project at `burnConcurrency: 3`, three consecutive laps whose tickets each chained end to end came in at **1.09x, 1.16x and 1.00x** — the width was there all three times and was never once spent.
56
-
57
- So, after slicing and before the self-check below, **say it out loud in the session**, in these terms:
58
-
59
- - the **critical path** — the longest chain of implementation tickets, named in order, and its length;
60
- - **how many tickets can run at once**, stated against this project's `burnConcurrency`;
61
- - whether that clears the bar: a batch of T implementation tickets at concurrency C wants a chain no longer than roughly **T / C**. Longer than that and the pool sits idle for most of the burn.
62
-
63
- **When it does not clear the bar, reslice.** The usual cause is habit rather than necessity — tickets sequenced because they *feel* ordered, or because one "sets up" the other. **A blocking edge is true only when the later ticket reads the earlier one's output**: a file it writes, a type or interface it defines, a schema it migrates, a stub it fills. "Related", "same area", "logical reading order", "easier to review in sequence" are not blocking edges — drop them and those tickets burn side by side. Where the edge is genuinely real, the fix runs the other way: merge the two into one ticket (the merge test above has usually already said so), or pull the shared foundation forward into the blocker so everything else goes wide behind it.
64
-
65
- Two things are not chain problems. The **review ticket** is blocked by every implementation ticket by definition and is always the tail: measure the critical path over the implementation tickets and add it. The **expand → migrate → contract** sequence below is the shape this rule is asking for — its migrate batch is the wide step.
66
-
67
- And width only counts if it lands: parallel tickets touching the same files collide on landing (the file-disjoint rule above), so a reslice that buys width by overlapping files has bought a re-burn instead.
68
- </parallelism-budget>
69
-
70
- <wide-refactor-exception>
71
- A **wide refactor** — one mechanical change (rename a column, retype a shared symbol) whose blast radius breaks thousands of call sites at once — cannot land green as a vertical slice. Sequence it **expand → migrate → contract** instead. Expand: add the new form beside the old so nothing breaks (one ticket). Migrate: move call sites over in batches sized by blast radius (per package/dir), each batch a ticket blocked by the expand, CI green throughout because the old form still exists. Contract: delete the old form once no caller remains, blocked by every migrate batch. If even the batches cannot stay green alone, let them share an integration branch that all block a final integrate-and-verify ticket — green is promised only there.
72
- </wide-refactor-exception>
73
-
74
- <preview-screen-block>
75
- When the feature has a `preview.html` and a ticket **builds a screen that appears in it as a screen block**, point that ticket at **one named screen block**, never the file. Its **context** names the path (`docs/features/<slug>/preview.html`) and the block name, and tells the burn agent to find the two markers, `<!-- screen:<name> -->` and `<!-- /screen:<name> -->`, and **read only the lines between them**. Say plainly that the block shows the approved look and behaviour and **is not the implementation**: build it with the project's real components and never copy the mock's markup. The mock's styles live in the page head and are deliberately not read. **Never tell an agent to read the whole file** — the rest repeats docs it already has.
76
-
77
- A ticket with no screen does not mention the preview.
78
- </preview-screen-block>
79
-
80
- ## 2. Build each ticket to the schema
81
-
82
- Emit an array of `TicketInput`. Number them from 1 in the array in **dependency order (blockers first)**; `blockedBy` holds those numbers (they are the seq numbers the store assigns).
83
-
84
- Each ticket:
85
- - **title** — short, descriptive, in the domain vocabulary.
86
- - **goal** — the end-to-end behaviour this ticket makes work, from the user's/caller's perspective. Not a layer list.
87
- - **context** — everything the sandboxed agent needs and cannot ask for: file paths to touch, existing patterns/modules to follow, type/schema shapes, gotchas, how to run and verify. Be concrete (see above).
88
- - **acceptanceCriteria** — `string[]`, each independently *verifiable* (a behaviour you can observe, a command that passes). These are what the burner works red-green against, one at a time, committing each green one — so this is where the fine granularity lives. A substantial ticket having four to eight criteria is right, not a smell; a list running well past that is a ticket that will not land in one pass.
89
- - **seams** — `string[]`, the public interfaces to test at (carry them from the spec's Seams section; prefer existing, prefer the highest, prefer one).
90
- - **blockedBy** — `number[]`, **required**, the seq numbers (from your 1..N ordering) of the tickets that *genuinely* gate this one. Only true gates; pass `[]` when there are none — omitting it fails validation.
91
- - **kind** — `"implementation"` (the default, omit it) or `"review"` for the one review ticket described above.
92
- - **model** — optional, and usually omitted. See below.
93
-
94
- ### Assigning a model (only from the annotated roster)
95
-
96
- `mcp__runcastle__get_feature_context` returns `annotatedModels` in its header: `{ id, runtime, note }` for the models the human wrote a use-case note against, and only those. That list is the *entire* set of ids you may put in a ticket's `model`.
97
-
98
- - **Empty list → never set `model`.** The human annotated nothing, so there is no stated reason to prefer one model over another. Omit the field on every ticket.
99
- - **Non-empty → set it only where a note actually fits the ticket.** The note is the human's own vocabulary for what that model is good at ("UI/UX taste", "mechanical refactors"); assign it when this ticket's work is plainly that, and leave the field unset when nothing in the list speaks to it. Unset is the good default — it falls through to the model the project already burns on.
100
- - **Never invent an id**, never copy one from these docs or from memory, and never write a model that is not in `annotatedModels` — the store rejects it and the whole batch fails to emit.
101
-
102
- The human sees the assignment on the ticket card and can change or clear it before Burn, so a wrong-but-reasonable choice is cheap; a fabricated id is not.
103
-
104
- ## 3. Self-check, then emit
105
-
106
- There is no in-session quiz — the human's review is the **Burn** click in the runcastle UI, reading these cards. So make them right before you emit. Check yourself, in this order:
107
-
108
- 1. **Can any two of these be one ticket?** Go pair by pair. Same files, shared type, sibling behaviours, or one whose blocker is the only thing it waits on and together they'd still land in one session → merge them. Do this first, because merging changes the rest.
109
- 2. **Would any one of these overrun?** A criteria list longer than one agent can take red→green — with tests, self-review and commits still to come — costs a whole second container to finish. Split it where its files stop overlapping.
110
- 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?
111
- 4. Are the blocking edges minimal and genuinely true gates — each one a ticket that reads its blocker's output? State the critical path and the width it leaves against `burnConcurrency`, and reslice if the chain runs longer than roughly total / concurrency. Where two are independent, do they touch different files — or will they collide on landing?
112
- 5. Is every `context` self-sufficient for an agent that cannot ask — covering every file and pattern the ticket touches?
113
- 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.
114
-
115
- Then:
116
- - `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. Emit the **whole batch, fully enriched, in one call** — large payloads are supported, so a batch of long `context` fields is fine. Never emit placeholder contexts ("Context follows via `update_ticket`.") to enrich ticket by ticket afterwards: a Burn landing mid-enrichment burns agents on the placeholders.
117
- - `mcp__runcastle__complete_phase({ phase: "tickets" })`. It comes back `ok: true` with `waitingOn: "human burn"` and a `warnings` array — the things the human will otherwise read in the Burn dialog (no review ticket in the batch, earlier-lap defects still un-dispositioned, no `spec.md` on disk). Nothing refuses, so anything in that array is yours to fix now, while you are still the session that can: fix it and call again.
118
-
119
- Return control to **the session skill that invoked you** — `/runcastle:ideate` — and let it close out the session. 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.