runcastle 1.4.4 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,12 +22,14 @@ Everything happens in this one window. **Never compact. Never `/clear`. Never su
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
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.
25
+ - `mcp__runcastle__complete_phase` — the phase boundaries (§4), and the ones `/runcastle:spec` and `/runcastle:tickets` cross after you.
26
26
  - `mcp__runcastle__emit_tickets` — used by `/runcastle:tickets`, not by you directly.
27
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
28
 
29
29
  ## 1. Grill relentlessly
30
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
+
31
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.
32
34
 
33
35
  - **One question at a time.** Wait for the answer before the next. Asking several at once is bewildering.
@@ -38,7 +40,20 @@ Interview the human about every aspect of the feature until you reach a genuinel
38
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.
39
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`.
40
42
 
41
- Do not move on until the human confirms you have reached shared understanding.
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.
42
57
 
43
58
  ## 2. Lock decisions incrementally
44
59
 
@@ -50,24 +65,64 @@ The moment a decision locks, append it to `docs/features/<slug>/decisions.md`
50
65
  **Why:** <the reason / the trade-off chosen over the alternatives>
51
66
  ```
52
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
+
53
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.
54
90
 
55
- ## 3. Complete ideation
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.
56
99
 
57
- When the human confirms shared understanding and the open questions are answered or explicitly deferred:
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:
58
111
 
59
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.
60
113
 
61
- ## 4. Spec → tickets — stay in this window
114
+ ## 5. Spec → tickets — stay in this window
62
115
 
63
116
  Invoke `/runcastle:spec` (it writes `spec.md` and completes the `spec` phase); when it returns, invoke `/runcastle:tickets`.
64
117
 
65
118
  Do not open a new session for these. They run here, on top of everything you just learned.
66
119
 
67
- ## 5. Close — hand off to the human
120
+ ## 6. Close — hand off to the human
68
121
 
69
122
  `/runcastle:tickets` ends by emitting tickets to the runcastle store. When it returns, your job is **done**. Tell the human plainly:
70
123
 
71
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.
72
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
+
73
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.)
@@ -0,0 +1,274 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>[Feature title] preview</title>
7
+ <style>
8
+ /* ---- runcastle theme: dark is the default, light is a full peer.
9
+ The app sets data-theme on <html>; opened as a file, the OS setting decides.
10
+ Token names and values are the app's own (apps/web/src/theme.css): keep them in step. ---- */
11
+ :root{
12
+ color-scheme:dark;
13
+ --color-canvas:#0a0c11;--color-surface:#10131a;--color-surface-raised:#161a24;--color-surface-hover:#1a1e29;--color-surface-selected:#212636;--color-surface-inset:#0c0e14;
14
+ --color-border:#222837;--color-border-subtle:rgb(200 215 255 / 0.06);--color-border-strong:#333a4d;
15
+ --color-text:#eceef4;--color-text-secondary:#a2a8b9;--color-text-tertiary:#8a91a5;--color-text-disabled:#4b5163;--color-icon:#8990a4;
16
+ --color-primary:#eceef4;--color-on-primary:#0a0c11;
17
+ --color-accent:#4f8ef7;--color-accent-text:#7aaafa;--color-accent-subtle:#16213a;
18
+ --color-success:#46b67a;--color-warning:#e2a53b;--color-danger:#f06363;--color-danger-subtle:#2a1515;
19
+ --color-phase-ideation:#ec7cb5;--color-phase-spec:#35c2b8;--color-phase-implementation:#f08a4b;
20
+ --color-phase-planning:var(--color-phase-spec);--color-phase-building:var(--color-phase-implementation);
21
+ --color-phase-review:var(--color-accent);--color-phase-shipped:var(--color-success);
22
+ --color-scrim:rgb(0 0 0 / 0.6);
23
+ --font-sans:"Geist Variable","Segoe UI Variable",system-ui,-apple-system,sans-serif;
24
+ --font-mono:"Geist Mono Variable","Cascadia Code",ui-monospace,Consolas,monospace;
25
+ --radius-sm:4px;--radius-md:6px;--radius-lg:10px;--ease-app:cubic-bezier(.22,.61,.24,1);
26
+ }
27
+ :root[data-theme="light"]{
28
+ color-scheme:light;
29
+ --color-canvas:#f4f6f9;--color-surface:#ffffff;--color-surface-raised:#ffffff;--color-surface-hover:#eef1f6;--color-surface-selected:#e4e8f1;--color-surface-inset:#f4f6f9;
30
+ --color-border:#dfe3ec;--color-border-subtle:rgb(22 30 50 / 0.07);--color-border-strong:#cdd2de;
31
+ --color-text:#161a24;--color-text-secondary:#4c5366;--color-text-tertiary:#5c6377;--color-text-disabled:#a4aab9;--color-icon:#6a7185;
32
+ --color-primary:#161a24;--color-on-primary:#ffffff;
33
+ --color-accent:#2f6fe4;--color-accent-text:#2463d6;--color-accent-subtle:#ecf2fe;
34
+ --color-success:#1e8e53;--color-warning:#a86a06;--color-danger:#c42828;--color-danger-subtle:#fdeeee;
35
+ --color-phase-ideation:#c13d7e;--color-phase-spec:#0b7f78;--color-phase-implementation:#b44f0c;
36
+ --color-scrim:rgb(22 26 36 / 0.28);
37
+ }
38
+ @media (prefers-color-scheme:light){
39
+ :root:not([data-theme]){
40
+ color-scheme:light;
41
+ --color-canvas:#f4f6f9;--color-surface:#ffffff;--color-surface-raised:#ffffff;--color-surface-hover:#eef1f6;--color-surface-selected:#e4e8f1;--color-surface-inset:#f4f6f9;
42
+ --color-border:#dfe3ec;--color-border-subtle:rgb(22 30 50 / 0.07);--color-border-strong:#cdd2de;
43
+ --color-text:#161a24;--color-text-secondary:#4c5366;--color-text-tertiary:#5c6377;--color-text-disabled:#a4aab9;--color-icon:#6a7185;
44
+ --color-primary:#161a24;--color-on-primary:#ffffff;
45
+ --color-accent:#2f6fe4;--color-accent-text:#2463d6;--color-accent-subtle:#ecf2fe;
46
+ --color-success:#1e8e53;--color-warning:#a86a06;--color-danger:#c42828;--color-danger-subtle:#fdeeee;
47
+ --color-phase-ideation:#c13d7e;--color-phase-spec:#0b7f78;--color-phase-implementation:#b44f0c;
48
+ --color-scrim:rgb(22 26 36 / 0.28);
49
+ }
50
+ }
51
+
52
+ /* ---- the fixed parts of every preview ---- */
53
+ *{box-sizing:border-box}
54
+ html{scroll-behavior:smooth}
55
+ body{margin:0;background:var(--color-surface);color:var(--color-text);font:13px/20px var(--font-sans);-webkit-font-smoothing:antialiased}
56
+ .page{max-width:1040px;margin:0 auto;padding:40px 32px 72px}
57
+ h1{margin:0;font-size:22px;line-height:28px;font-weight:600;letter-spacing:-.02em}
58
+ h2{margin:0 0 4px;font-size:16px;line-height:24px;font-weight:600;letter-spacing:-.01em}
59
+ h3{margin:0;font-size:13px;line-height:20px;font-weight:500}
60
+ p{margin:0}
61
+ .lede{margin-top:8px;max-width:620px;font-size:14px;line-height:22px;color:var(--color-text-secondary);text-wrap:pretty}
62
+ .meta{display:flex;flex-wrap:wrap;gap:4px 14px;margin-top:10px;font-size:12px;line-height:16px;color:var(--color-text-tertiary)}
63
+ .hint{color:var(--color-text-tertiary);font-size:12px;line-height:16px}
64
+ .mono{font-family:var(--font-mono);font-size:12px}
65
+ section{margin-top:44px}
66
+ .sub{margin-top:28px}
67
+ .sub > .hint{margin:2px 0 12px}
68
+ svg.i{width:16px;height:16px;flex:none;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
69
+ button{font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer}
70
+ :focus-visible{outline:2px solid var(--color-accent);outline-offset:2px;border-radius:var(--radius-sm)}
71
+ .jump{display:flex;flex-wrap:wrap;gap:2px;margin-top:20px;padding-bottom:12px;border-bottom:1px solid var(--color-border-subtle)}
72
+ .jump a{padding:4px 10px;border-radius:var(--radius-md);color:var(--color-text-secondary);text-decoration:none;transition:background .12s var(--ease-app)}
73
+ .jump a:hover{background:var(--color-surface-hover);color:var(--color-text)}
74
+ .jump a b{font-weight:400;color:var(--color-text-tertiary);font-variant-numeric:tabular-nums}
75
+
76
+ /* a decision's number, wherever that decision is visible: <a class="pin" data-d="3">3</a> */
77
+ .pin{display:inline-grid;place-items:center;min-width:18px;height:18px;padding:0 4px;border-radius:var(--radius-sm);
78
+ background:var(--color-accent-subtle);color:var(--color-accent-text);font:500 11px/1 var(--font-mono);text-decoration:none;vertical-align:1px;cursor:pointer}
79
+ .pin:hover{background:var(--color-accent);color:#fff}
80
+
81
+ /* a segmented toggle, for switching a figure between states */
82
+ .seg{display:inline-flex;gap:2px;padding:2px;border-radius:var(--radius-md);background:var(--color-surface-inset)}
83
+ .seg button{height:24px;padding:0 10px;border-radius:var(--radius-sm);color:var(--color-text-secondary);transition:background .12s var(--ease-app)}
84
+ .seg button:hover{color:var(--color-text)}
85
+ .seg button[aria-pressed="true"]{background:var(--color-surface-selected);color:var(--color-text)}
86
+ .dot{display:inline-block;width:6px;height:6px;border-radius:999px;background:var(--color-text-disabled);flex:none}
87
+ .dot.ok{background:var(--color-success)}.dot.no{background:var(--color-danger)}.dot.live{background:var(--color-accent)}
88
+
89
+ /* the framed panel a mocked screen sits in; inside it, use the project's own styling */
90
+ .screen{border:1px solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;background:var(--color-surface);position:relative}
91
+
92
+ /* decided / left out / under the hood */
93
+ .dlist{border-top:1px solid var(--color-border-subtle);margin-top:12px}
94
+ .dlist details{border-bottom:1px solid var(--color-border-subtle)}
95
+ .dlist summary{display:flex;align-items:center;gap:12px;min-height:40px;padding:6px 8px;cursor:pointer;list-style:none;border-radius:var(--radius-md)}
96
+ .dlist summary::-webkit-details-marker{display:none}
97
+ .dlist summary:hover{background:var(--color-surface-hover)}
98
+ .dlist summary svg{margin-left:auto;color:var(--color-icon);transition:transform .18s var(--ease-app)}
99
+ .dlist details[open] summary svg{transform:rotate(90deg)}
100
+ .dlist details p{padding:0 8px 12px 42px;color:var(--color-text-secondary);max-width:700px}
101
+ .dlist details.flash summary{background:var(--color-accent-subtle)}
102
+ .tag{display:inline-flex;align-items:center;height:18px;padding:0 6px;border-radius:var(--radius-sm);background:var(--color-surface-inset);color:var(--color-text-tertiary);font-size:11px;flex:none}
103
+ .dlist summary .tag{margin-left:auto}.dlist summary .tag + svg{margin-left:0}
104
+ /* check first: built from the C list, absent when it is empty */
105
+ .cf{margin-top:12px}
106
+ .cf-item{border:1px solid var(--color-border);border-left:2px solid var(--color-warning);border-radius:var(--radius-md);padding:8px 10px;margin-top:8px;background:var(--color-surface-raised)}
107
+ .cf-item .t{display:flex;align-items:center;gap:8px;font-weight:500}
108
+ .cf-item dl{display:grid;grid-template-columns:86px 1fr;gap:2px 10px;margin:6px 0 0;color:var(--color-text-secondary)}
109
+ .cf-item dt{color:var(--color-text-tertiary);font-size:12px}
110
+ .cf-item dd{margin:0}
111
+ .out2{display:grid;grid-template-columns:1fr 1fr;gap:8px 32px;margin-top:12px}
112
+ .out2 h3{margin-bottom:6px;color:var(--color-text-tertiary);font-weight:400;font-size:12px}
113
+ .out2 li{display:flex;align-items:center;gap:10px;min-height:28px;color:var(--color-text-secondary)}
114
+ .out2 ul{margin:0;padding:0;list-style:none}
115
+ .hood > summary{display:flex;align-items:center;gap:8px;cursor:pointer;list-style:none}
116
+ .hood > summary::-webkit-details-marker{display:none}
117
+ .hood > summary svg{color:var(--color-icon);transition:transform .18s var(--ease-app)}
118
+ .hood[open] > summary svg{transform:rotate(90deg)}
119
+ .hood > summary h2{margin:0}
120
+ @media (max-width:760px){
121
+ .page{padding:28px 16px 56px}.out2{grid-template-columns:1fr}
122
+ }
123
+
124
+ /* ---- the show's own styles go below this line ---- */
125
+ </style>
126
+ </head>
127
+ <body>
128
+ <svg width="0" height="0" style="position:absolute" aria-hidden="true">
129
+ <symbol id="i-chev" viewBox="0 0 16 16"><path d="m6 3.500 4.500 4.500L6 12.500"/></symbol>
130
+ </svg>
131
+
132
+ <div class="page">
133
+
134
+ <!-- 1 · what this is -->
135
+ <header id="what">
136
+ <h1>[Feature title]</h1>
137
+ <p class="lede">[One or two sentences: what this feature is, in the human's words.]</p>
138
+ <div class="meta"><span>Lap 1</span><span><span data-dcount></span> decisions</span><span class="mono">docs/features/[slug]/preview.html</span></div>
139
+ <nav class="jump" aria-label="Parts of this preview">
140
+ <a href="#show">The show</a><a href="#decided">Decided <b data-dcount></b></a><a href="#left-out">Left out</a><a href="#hood">Under the hood</a>
141
+ </nav>
142
+ </header>
143
+
144
+ <!-- 2 · the show -->
145
+ <section id="show">
146
+ <h2>The show</h2>
147
+ <!-- THE SHOW IS FREE-FORM. Replace this comment with whatever explains how the
148
+ feature will work: screens, clickable prototypes, skeletons, flows, before
149
+ and after, scenarios. There is no template. Pin a decision's number where
150
+ it is visible: <a class="pin" data-d="3">3</a>. Diagrams and flows use the
151
+ runcastle tokens above; a mocked screen uses the project's own styling
152
+ inside a .screen panel, wrapped in a screen block.
153
+
154
+ Example screen block (copy it, rename it in kebab case, uncomment the
155
+ panel; delete it if the show has no screen):
156
+ -->
157
+ <!-- screen:example-name -->
158
+ <!--
159
+ <div class="screen">
160
+ ... the mocked screen, in the project's own styling ...
161
+ </div>
162
+ -->
163
+ <!-- /screen:example-name -->
164
+ </section>
165
+
166
+ <!-- 3 · what was decided (built from the list in the script below) -->
167
+ <section id="decided">
168
+ <h2>What was decided</h2>
169
+ <p class="hint">Open one for the reason.</p>
170
+ <div class="dlist" id="dlist"></div>
171
+ </section>
172
+
173
+ <!-- 4 · what is left out -->
174
+ <section id="left-out">
175
+ <h2>What is left out</h2>
176
+ <div class="out2">
177
+ <div><h3>Later laps</h3><ul>
178
+ <li><i class="dot"></i>[Deferred to a later lap]</li>
179
+ </ul></div>
180
+ <div><h3>Not this feature</h3><ul>
181
+ <li><i class="dot"></i>[Out of scope]</li>
182
+ </ul></div>
183
+ </div>
184
+ </section>
185
+
186
+ <!-- 5 · under the hood (collapsed by default) -->
187
+ <section id="hood-s">
188
+ <details class="hood" id="hood">
189
+ <summary><svg class="i"><use href="#i-chev"/></svg><h2>Under the hood</h2></summary>
190
+ <div class="sub">
191
+ <!-- [The technical shape, for a reader who wants it. Draw it where you can.] -->
192
+ </div>
193
+ </details>
194
+ </section>
195
+
196
+ </div>
197
+
198
+ <script>
199
+ (function () {
200
+ var $ = function (s, r) { return (r || document).querySelector(s) }
201
+ var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)) }
202
+
203
+ // every decision in decisions.md, by number and title, in order: [title, reason, mark?]
204
+ // mark is 'assumed' for a decision made without the human; a grilled feature has none
205
+ var D = [
206
+ ['[Decision 1 title]', '[Why, in a sentence]']
207
+ ]
208
+ // check first, a self-planned feature only (at most five): [decisionNumber, alternative, whyUnsure, tag?]
209
+ // leave it empty for a grilled feature, and no group is drawn
210
+ var C = []
211
+ var list = $('#dlist')
212
+ D.forEach(function (d, i) {
213
+ var el = document.createElement('details')
214
+ el.id = 'd' + (i + 1)
215
+ el.innerHTML = '<summary><span class="pin">' + (i + 1) + '</span><span></span><svg class="i"><use href="#i-chev"/></svg></summary><p></p>'
216
+ $('summary span:nth-child(2)', el).textContent = d[0]
217
+ $('p', el).textContent = d[1]
218
+ if (d[2]) {
219
+ var mark = document.createElement('span')
220
+ mark.className = 'tag'
221
+ mark.textContent = d[2]
222
+ $('summary svg', el).before(mark)
223
+ }
224
+ list.appendChild(el)
225
+ })
226
+ if (C.length) {
227
+ var cf = document.createElement('div')
228
+ cf.className = 'cf'
229
+ cf.innerHTML = '<h3>Check first</h3><p class="hint">At most five. The ones most likely to be wrong for you.</p>'
230
+ C.forEach(function (c) {
231
+ var item = document.createElement('div')
232
+ item.className = 'cf-item'
233
+ item.innerHTML = '<div class="t"><a class="pin" data-d="' + c[0] + '">' + c[0] + '</a><span></span></div>' +
234
+ '<dl><dt>Alternative</dt><dd></dd><dt>Why unsure</dt><dd></dd></dl>'
235
+ $('.t span', item).textContent = D[c[0] - 1][0]
236
+ if (c[3]) {
237
+ var tag = document.createElement('span')
238
+ tag.className = 'tag'
239
+ tag.textContent = c[3]
240
+ $('.t', item).appendChild(tag)
241
+ }
242
+ var dd = $$('dd', item)
243
+ dd[0].textContent = c[1]
244
+ dd[1].textContent = c[2]
245
+ cf.appendChild(item)
246
+ })
247
+ list.before(cf)
248
+ }
249
+ $$('[data-dcount]').forEach(function (el) { el.textContent = String(D.length) })
250
+
251
+ // a pin opens its decision; in-page links scroll by script, because hash
252
+ // navigation is unreliable inside the app's sandboxed frame
253
+ function go(el) { el.scrollIntoView({ behavior: 'smooth', block: 'center' }) }
254
+ document.addEventListener('click', function (e) {
255
+ var pin = e.target.closest('a.pin[data-d]')
256
+ if (pin) {
257
+ e.preventDefault()
258
+ var d = $('#d' + pin.dataset.d)
259
+ d.open = true; go(d); d.classList.add('flash')
260
+ setTimeout(function () { d.classList.remove('flash') }, 1400)
261
+ return
262
+ }
263
+ var a = e.target.closest('.jump a')
264
+ if (a) {
265
+ e.preventDefault()
266
+ var t = $(a.getAttribute('href'))
267
+ if (t.tagName === 'DETAILS') t.open = true
268
+ t.scrollIntoView({ behavior: 'smooth', block: 'start' })
269
+ }
270
+ })
271
+ })()
272
+ </script>
273
+ </body>
274
+ </html>
@@ -0,0 +1,98 @@
1
+ # The plan preview — the page the human looks at
2
+
3
+ Load this when the decisions are locked and the preview step (§3 of the ideate
4
+ skill) arrives, or when a decision edit at planning means the preview has to be
5
+ redrawn. Otherwise it costs this conversation nothing.
6
+
7
+ The preview is the one page the human reads the feature by. They confirm the
8
+ plan by looking at it, and when tired it is the only thing they read before
9
+ Burn. Draw it for that reader.
10
+
11
+ ## The file
12
+
13
+ - **Exactly one file, `preview.html`, at the root of the feature's docs dir**:
14
+ `docs/features/<slug>/preview.html`. Several screens or flows are sections of
15
+ that one page, never more files.
16
+ - **Start from `./preview-starter.html`.** Copy it to that path and fill it in.
17
+ It carries runcastle's theme tokens for both themes and the styles and markup
18
+ for the fixed parts; do not restyle them.
19
+ - **Throwaway explorations** may go under `docs/features/<slug>/prototypes/`.
20
+ They are never shown; the app shows only `preview.html`.
21
+
22
+ ## Five parts, in this order
23
+
24
+ 1. **What this is.** One or two sentences.
25
+ 2. **The show.** Whatever explains how the feature will work. **It is free, with
26
+ no template**: screens, clickable prototypes, skeletons, flows, before and
27
+ after, scenarios. Choose the form that explains *this* feature — work with no
28
+ screen gets flows, use cases, before and after, or a clickable skeleton.
29
+ 3. **What was decided.** Every decision in `decisions.md` by its number and
30
+ title, with the reason available on demand (the starter's `D` list builds
31
+ this). Where a decision is visible in the show, pin its number on that spot:
32
+ `<a class="pin" data-d="3">3</a>`.
33
+ **A self-planned feature** (§1a of the ideate skill) opens this part with a
34
+ **Check first** group of at most five decisions, the ones the planner picked.
35
+ Each entry shows the decision's number and title, its alternative, and why it
36
+ is uncertain (the starter's `C` list builds this). The remaining decisions
37
+ follow in the list, each showing that it was assumed (a third element on its
38
+ `D` entry). **A grilled feature has neither**: no group and no assumed marks.
39
+ 4. **What is left out.** Out of scope, and anything deferred to a later lap.
40
+ 5. **Under the hood.** The technical shape for a reader who wants it,
41
+ **collapsed by default**, drawn where it can be.
42
+
43
+ ## Visual first
44
+
45
+ **A short sentence beside a picture, never paragraphs, detail behind a click.**
46
+ If a part reads as prose, draw it instead: a flow, a before and after, a
47
+ toggled state, a list of dots.
48
+
49
+ ## The theme rule
50
+
51
+ - **The page and every diagram and flow are runcastle-themed**, through the
52
+ starter's tokens, in light or dark to match the app.
53
+ - **A mocked screen uses the styling of the project it belongs to**, inside the
54
+ framed `.screen` panel. A prototype of another product's UI can only be judged
55
+ in that product's look.
56
+
57
+ ## Screen blocks
58
+
59
+ Each screen in the show is a **screen block**: the lines between two markers,
60
+
61
+ ```html
62
+ <!-- screen:<name> -->
63
+ <div class="screen"> … </div>
64
+ <!-- /screen:<name> -->
65
+ ```
66
+
67
+ with `<name>` in kebab case and unique in the file. A ticket that builds that
68
+ screen names the block, and its burn agent reads only those lines — so put
69
+ everything the screen needs to be understood between the markers, and nothing
70
+ else. Delete the starter's `example-name` block if the show has no screen.
71
+
72
+ ## Inline only
73
+
74
+ **Styles, scripts and images are inline. The file makes no network request** —
75
+ no `<link>`, no `@import`, no external `src` or `url(`; images are `data:` URIs
76
+ or inline SVG. This is enforced, not a preference: the app renders the preview
77
+ in a sandboxed frame that runs scripts and refuses every fetch, external image
78
+ and font, form post and popup. Anything hosted elsewhere simply does not load.
79
+ In-page links scroll by script (the starter does this), because hash navigation
80
+ is unreliable inside that frame.
81
+
82
+ ## Sizing
83
+
84
+ **There is no minimum.** A small feature's show can be a single picture and the
85
+ other parts a line each. Reading target, as guidance and not a limit: **about a
86
+ minute for a small feature, a few minutes for a large one.**
87
+
88
+ ## Self-check, before you tell the human to look
89
+
90
+ - Every decision in `decisions.md` appears in **What was decided**, by its number
91
+ and its title, in order — none missing, none renumbered.
92
+ - For a self-planned feature, the **Check first** group holds at most five
93
+ decisions, each with its alternative and why it is uncertain, and every
94
+ decision carrying an `**Assumed.**` line in `decisions.md` shows as assumed.
95
+ For a grilled feature, `C` is empty and no decision is marked.
96
+ - Every pin points at the decision the spot actually shows.
97
+ - The five parts are in order; **Under the hood** is closed.
98
+ - The file is still fully inline.
@@ -16,7 +16,9 @@ That is the ordinary revisit, and it is what the moves below describe. The one e
16
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
+ - **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.
19
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.
20
22
  4. **Ticket surgery.** Walk the ticket list against the new reality:
21
23
  - Stale but still needed → `mcp__runcastle__update_ticket({ id, ...changed fields })` (pending/failed only).
22
24
  - No longer needed → `mcp__runcastle__cancel_ticket({ id, reason })` (pending/failed only).
@@ -35,7 +37,7 @@ Your kickoff line reads `PLAN LAP <n> FROM REVIEW`. The human burned the last la
35
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.
36
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.
37
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.
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
+ 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.
39
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.
40
42
 
41
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:
@@ -71,6 +71,12 @@ And width only counts if it lands: parallel tickets touching the same files coll
71
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
72
  </wide-refactor-exception>
73
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
+
74
80
  ## 2. Build each ticket to the schema
75
81
 
76
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).