@wemuda/launchrail 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -38,7 +38,7 @@ Launchrail structures development as two movements. The **foundation** runs once
38
38
  <img src="https://github.com/wemuda/launchrail/raw/master/assets/how-launchrail-works.png" alt="How Launchrail works — the foundation runs once per project (Vision → Visual exploration → Discovery research → Complexity grill → Technical research → Architecture decisions); the delivery loop then repeats once per slice (Specify features into tickets → Ralph loop → Verification) before looping back for the next slice." width="880" />
39
39
  </p>
40
40
 
41
- The skills are Launchrail's own complete, `launch-*` prefixed set ([ADR-0020](https://github.com/wemuda/launchrail/blob/master/docs/adr/0020-independent-skill-set.md)); the methodology of several stages is inspired by [Matt Pocock's skills](https://github.com/mattpocock/skills) — see [Credits](#credits). The full stage contract — inputs, artifacts, composition rules, and per-mode rigor — lives in [the workflow doc](https://github.com/wemuda/launchrail/blob/master/packages/cli/assets/skills/launchrail/launch/workflow.md).
41
+ The skills are Launchrail's own complete, `launch-*` prefixed set ([ADR-0020](https://github.com/wemuda/launchrail/blob/master/docs/adr/0020-independent-skill-set.md)); the methodology of several stages is inspired by [Matt Pocock's skills](https://github.com/mattpocock/skills) — see [Credits](#credits). The full stage contract — inputs, artifacts, composition rules, and deliberate-skip rules — lives in [the workflow doc](https://github.com/wemuda/launchrail/blob/master/packages/cli/assets/skills/launchrail/launch/workflow.md).
42
42
 
43
43
  What makes projects **updatable** instead of copy-once-and-rot is the second half of the system: shared capabilities are *shipped as managed files and kept current on `sync`*, shared standards are *synchronized*, product knowledge stays *locally owned*, and reusable lessons are *deliberately promoted upstream*.
44
44
 
@@ -55,6 +55,7 @@ npx @wemuda/launchrail init
55
55
  From there, the day-to-day driver is not the CLI — it's the **`launch` skill** inside Claude Code: open the project and run `/launch`. Invoke it (or just ask "what's next?") and it reads your committed artifacts, works out where the project is — no vision yet, mid-grill, spec validated, tickets ready — and runs or routes to the next stage's owner. Give it a stage name (`launch design-validation`) to jump straight there. The skills carry the rest of the workflow too:
56
56
 
57
57
  - **`launch-project-alignment`** — the on-ramp for an existing codebase: infer a vision from the code, interview only the gaps, inventory the design system, then join the loop
58
+ - **`launch-design-handoff`** — the design→code on-ramp: drop a Claude Design export (a zip, a folder, artboards) and it becomes a committed handoff package under `docs/design/` — read against the code and design system, gaps recorded as the grill's agenda — then sized into the loop ([ADR-0024](https://github.com/wemuda/launchrail/blob/master/docs/adr/0024-design-handoff-onramp.md))
58
59
  - **`launch-vision-creation`**, **`launch-discovery`**, **`launch-grill`**, **`launch-research`** — vision, then the divergent landscape scan, the grill (the convergent interview that runs both as the foundation's complexity grill and per-feature before speccing, keeping the glossary and ADRs honest as it goes), and primary-source research
59
60
  - **`launch-wayfinder`**, **`launch-spec`**, **`launch-tickets`**, **`launch-design-validation`** — break big work into decision maps, synthesize the spec, validate it visually, and cut tracer-bullet tickets with blocking edges
60
61
  - **`launch-browser-smoke`** — drives a real browser journey and leaves a traceable evidence bundle (with the browser-testing module)
@@ -17,7 +17,7 @@ Issues and specs for this repo live as GitHub issues. Use the `gh` CLI for all o
17
17
  - **Apply / remove labels**: `gh issue edit <number> --add-label "..."` / `--remove-label "..."`
18
18
  - **Close**: `gh issue close <number> --comment "..."`
19
19
 
20
- Infer the repo from `git remote -v` — `gh` does this automatically when run inside a clone.
20
+ Infer the repo from `git remote -v` — `gh` does this automatically when run inside a clone. In `gh api` paths you can write the `{owner}/{repo}` placeholders literally and `gh` fills them from the current clone.
21
21
 
22
22
  GitHub shares one number space across issues and PRs, so a bare `#42` may be either — resolve with `gh pr view 42` and fall back to `gh issue view 42`.
23
23
 
@@ -31,21 +31,56 @@ The Launchrail workflow's label vocabulary — the skills quote these exact stri
31
31
  - **`ralph:building`** — claimed by an implementer; removed when its PR merges.
32
32
  - **`wayfinder:map`** / **`wayfinder:<type>`** — a wayfinder map and its decision tickets (see below).
33
33
 
34
+ ## Relationships — native, not prose
35
+
36
+ GitHub renders issue relationships in its own UI: **blocking dependencies** and **parent/child sub-issues** both show in the issue's sidebar, so the frontier is visible at a glance without opening a body. **These native relationships are the canonical representation — create them, don't just describe them in prose.** A body line is the fallback only where the native relationship isn't available; when the two ever disagree, the native relationship wins.
37
+
38
+ Both APIs key off an issue's numeric **database id**, _not_ its `#number` and _not_ its GraphQL `node_id`. Resolve it once per issue and reuse it:
39
+
40
+ ```bash
41
+ gh api repos/{owner}/{repo}/issues/<n> --jq .id # the database id to pass as issue_id / sub_issue_id below
42
+ ```
43
+
44
+ ### Blocking (`blocked by`)
45
+
46
+ - **Add an edge**: `gh api --method POST repos/{owner}/{repo}/issues/<blocked>/dependencies/blocked_by -F issue_id=<blocker-db-id>` — issue `<blocked>` is now blocked by the issue whose database id is `<blocker-db-id>`.
47
+ - **Read the gate**: an issue carries `issue_dependencies_summary.blocked_by`, the count of its still-**open** blockers — `gh api repos/{owner}/{repo}/issues/<n> --jq .issue_dependencies_summary.blocked_by`. `> 0` means blocked; `0` means every blocker is closed and the edge is clear. List them with `gh api repos/{owner}/{repo}/issues/<n>/dependencies/blocked_by`.
48
+ - **Remove an edge**: `gh api --method DELETE repos/{owner}/{repo}/issues/<blocked>/dependencies/blocked_by/<blocker-db-id>`.
49
+ - **Fallback** (dependencies unavailable): a `**Blocked by:** #<n>, #<n>` line at the top of the blocked issue's body. A ticket is unblocked when every blocker it names is closed.
50
+
51
+ ### Parent / child (sub-issues)
52
+
53
+ - **Link a child**: `gh api --method POST repos/{owner}/{repo}/issues/<parent>/sub_issues -F sub_issue_id=<child-db-id>` — the child now nests under the parent and rolls up in its progress. The child must live in the same repo owner as the parent.
54
+ - **Read**: `gh api repos/{owner}/{repo}/issues/<n>/parent` and `gh api repos/{owner}/{repo}/issues/<n>/sub_issues`.
55
+ - **Fallback** (sub-issues unavailable): a `Part of #<parent>` line at the top of the child body, plus a task list in the parent.
56
+
57
+ ## Issue ↔ PR linkage (Development)
58
+
59
+ Tie every implementation PR to its ticket so the work closes the loop automatically — this is what fills an issue's **Development** section and closes it on merge:
60
+
61
+ - Put a **closing keyword** in the PR body — `Closes #<n>` (`Fixes` / `Resolves` work too). GitHub links the PR under the issue's **Development** section, and **merging the PR into the repository's default branch closes the issue**.
62
+ - **Auto-close fires only from the default branch.** A PR merged into a consolidation / integration branch (not the default) still links but does **not** auto-close — close the issue explicitly after the merge (`gh issue close <n>`). Squash-merges can also miss the trigger even on the default branch; read the issue back on the remote and close it explicitly if it's still open.
63
+ - **One ticket, one PR.** Adopt an existing `<n>`-scoped branch or PR rather than opening a second — the linkage should point at a single PR.
64
+
34
65
  ## When a skill says "publish to the issue tracker"
35
66
 
36
- Create a GitHub issue.
67
+ Create a GitHub issue. When the ticket declares blocking edges or a parent, wire them as the **native relationships above** (blocking dependency, sub-issue) — the canonical, UI-visible form the implementation loop's frontier reads — falling back to a body line only where the native relationship isn't available.
37
68
 
38
69
  ## When a skill says "fetch the relevant ticket"
39
70
 
40
71
  Run `gh issue view <number> --comments`.
41
72
 
73
+ ## Specs and their tickets
74
+
75
+ The stage-7 spec ([ADR-0025](https://github.com/wemuda/launchrail/blob/master/docs/adr/0025-spec-home-follows-tracker.md)) is itself an issue here — created by `launch-spec`, labelled **`spec`**, never `ready-for-agent`. There is no `docs/specs/` file; the issue is the canonical spec. When `launch-tickets` breaks it down, each ticket is a **GitHub sub-issue of the spec** (`gh api` on the sub-issues endpoint), or carries `Part of #<spec>` at the top of its body where sub-issues aren't enabled. Design validation revises the spec issue in place (its `## Design validation` section lives in the issue body).
76
+
42
77
  ## Wayfinding operations
43
78
 
44
79
  Used by `launch-wayfinder`. The **map** is a single issue with **child** issues as tickets.
45
80
 
46
81
  - **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `gh issue create --label wayfinder:map`.
47
- - **Child ticket**: an issue linked to the map as a GitHub sub-issue (`gh api` on the sub-issues endpoint). Where sub-issues aren't enabled, add the child to a task list in the map body and put `Part of #<map>` at the top of the child body. Labels: `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
48
- - **Blocking**: GitHub's **native issue dependencies** — the canonical, UI-visible representation. Add an edge with `gh api --method POST repos/<owner>/<repo>/issues/<child>/dependencies/blocked_by -F issue_id=<blocker-db-id>`, where `<blocker-db-id>` is the blocker's numeric **database id** (`gh api repos/<owner>/<repo>/issues/<n> --jq .id`, _not_ the `#number` or `node_id`). GitHub reports `issue_dependencies_summary.blocked_by` (open blockers only — the live gate). Where dependencies aren't available, fall back to a `Blocked by: #<n>, #<n>` line at the top of the child body. A ticket is unblocked when every blocker is closed.
82
+ - **Child ticket**: an issue linked to the map as a **sub-issue** (see Relationships → Parent / child), labelled `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Where sub-issues aren't enabled, add the child to a task list in the map body and put `Part of #<map>` at the top of the child body. Once claimed, the ticket is assigned to the driving dev.
83
+ - **Blocking**: the native **`blocked by` dependency** from Relationships — the canonical, UI-visible gate; GitHub reports open blockers as `issue_dependencies_summary.blocked_by`. Fall back to a `Blocked by: #<n>` body line only where dependencies aren't available.
49
84
  - **Frontier query**: list the map's open children (`gh issue list --state open`, scoped to the map's sub-issues / task list), drop any with an open blocker (`issue_dependencies_summary.blocked_by > 0`, or an open issue in the `Blocked by` line) or an assignee; first in map order wins.
50
85
  - **Claim**: `gh issue edit <n> --add-assignee @me` — the session's first write.
51
86
  - **Resolve**: `gh issue comment <n> --body "<answer>"`, then `gh issue close <n>`, then append a context pointer (gist + link) to the map's Decisions-so-far.
@@ -40,6 +40,10 @@ Create a GitLab issue.
40
40
 
41
41
  Run `glab issue view <number> --comments`.
42
42
 
43
+ ## Specs and their tickets
44
+
45
+ The stage-7 spec ([ADR-0025](https://github.com/wemuda/launchrail/blob/master/docs/adr/0025-spec-home-follows-tracker.md)) is itself an issue here — created by `launch-spec`, labelled **`spec`**, never `ready-for-agent`. There is no `docs/specs/` file; the issue is the canonical spec. When `launch-tickets` breaks it down, each ticket carries `Part of #<spec>` at the top of its description (on tiers with native epics, the spec may be an epic holding the tickets instead). Design validation revises the spec issue in place (its `## Design validation` section lives in the issue description).
46
+
43
47
  ## Wayfinding operations
44
48
 
45
49
  Used by `launch-wayfinder`. The **map** is a single issue with **child** issues as tickets.
@@ -40,6 +40,10 @@ Create a Linear issue in the team above.
40
40
 
41
41
  Fetch the issue by its identifier (`ENG-123`) including comments.
42
42
 
43
+ ## Specs and their tickets
44
+
45
+ The stage-7 spec ([ADR-0025](https://github.com/wemuda/launchrail/blob/master/docs/adr/0025-spec-home-follows-tracker.md)) is itself an issue here — created by `launch-spec`, labelled **`spec`**, never `ready-for-agent`. There is no `docs/specs/` file; the issue is the canonical spec. When `launch-tickets` breaks it down, each ticket is a **sub-issue of the spec** (Linear's native parent/child). Design validation revises the spec issue in place (its `## Design validation` section lives in the issue description).
46
+
43
47
  ## Wayfinding operations
44
48
 
45
49
  Used by `launch-wayfinder`. The **map** is a single issue with **child** issues as tickets.
@@ -11,7 +11,7 @@ Issues and specs for this repo live as markdown files in `.scratch/`.
11
11
  ## Conventions
12
12
 
13
13
  - One feature per directory: `.scratch/<feature-slug>/`
14
- - The spec is committed under `docs/specs/` (the rail's stage-7 artifact); `.scratch/<feature-slug>/spec.md` may hold a working copy
14
+ - The spec is committed under `docs/specs/<feature-slug>.md` — the rail's stage-7 artifact and its single home in local mode (there is no external tracker to hold it)
15
15
  - Implementation issues are one file per ticket at `.scratch/<feature-slug>/issues/<NN>-<slug>.md`, numbered from `01` — never a single combined tickets file
16
16
  - Ticket state is recorded as a `Status:` line near the top of each issue file, using the label vocabulary below
17
17
  - Comments and conversation history append to the bottom of the file under a `## Comments` heading
@@ -3,21 +3,23 @@
3
3
  //
4
4
  // The Ralph loop as a deterministic workflow: the plan, the frontier bookkeeping,
5
5
  // and every intermediate report live in script variables — not in any context window —
6
- // so long or wide runs cannot compact away their own state. The watchable, checkpointed
7
- // variant of the same loop is the launch-ralph skill; the two share one policy block,
8
- // and a policy change belongs in both places (ADR-0005, field-revised by ADR-0010).
6
+ // so long or wide runs cannot compact away their own state. This is the engine for
7
+ // every multi-ticket run (ADR-0022); the launch-ralph skill carries the same policy
8
+ // block as the supervisor's contract and the declared-exception watchable mode, and
9
+ // a policy change belongs in both places (ADR-0005, field-revised by ADR-0010, ADR-0022).
9
10
  export const meta = {
10
11
  name: 'ralph',
11
12
  description: 'Autonomous Ralph loop: implement ready tickets with fresh-context subagents, verification-gated',
12
13
  whenToUse:
13
- 'Run the Ralph implementation loop over the ticket backlog when the dependency graph is wide or the run is long. Scope a run via args: { only: [9, 10], width: 2 }, just [9, 10], or { max: 5 } to stop after 5 verified merges ("the next five" — the frontier picks which, in dependency order). Args must be JSON — resolve any natural-language scope to ticket numbers and a cap before launching. For a watchable, checkpointed run (or when something is already going wrong), use the launch-ralph skill instead.',
14
+ 'The engine for any multi-ticket Ralph run. Scope a run via args: { only: [9, 10], width: 2 }, just [9, 10], or { max: 5 } to stop after 5 verified merges ("the next five" — the frontier picks which, in dependency order). The front door consolidates by DEFAULT (ADR-0026): it passes { target: "spec/44-mvp" } to collect the campaign onto that branch (default branch untouched; released later by one offered PR). Omitting target is the explicit trunk opt-in — each ticket merged straight into the default branch. { canary: true } holds width at 1 until the first verified merge. Args must be JSON — resolve any natural-language scope to ticket numbers, a cap, and a target before launching. For a watchable run (an explicit user ask, or a targeted intervention), use the launch-ralph skill instead — and say why.',
14
15
  phases: [
15
- { title: 'Preflight', detail: 'read project config, sync the base, run the verification gate' },
16
+ { title: 'Preflight', detail: 'read project config, resolve the integration target, run the verification gate' },
16
17
  { title: 'Graph', detail: 'list ready tickets and their blocking edges, verbatim' },
17
- { title: 'Build', detail: 'one fresh-context implementer per ticket, merge included' },
18
+ { title: 'Build', detail: 'one fresh-context implementer per ticket, handing off at PR-open' },
19
+ { title: 'Gate', detail: 'per-ticket merge gate: CI wait, squash-merge, explicit close' },
18
20
  { title: 'Verify', detail: 'remote ground truth for every claimed merge' },
19
21
  { title: 'Park', detail: 'comment failure history, label needs-info' },
20
- { title: 'Release', detail: 'final verification gate and evidence summary' },
22
+ { title: 'Release', detail: 'final verification gate and the where-it-lives recap' },
21
23
  ],
22
24
  }
23
25
 
@@ -52,8 +54,19 @@ const POLICY = {
52
54
  max: A.max ?? 0,
53
55
  // Parallel implementers. Width also caps local build concurrency — several implementers
54
56
  // share one machine, and fanning out test runs buys backpressure, not speed. Use 1 until
55
- // a run has landed tickets cleanly on this project.
57
+ // a run has landed tickets cleanly on this project — or pass canary: true, which does it
58
+ // for you. Tickets that add DB migrations collide on the next migration number when run
59
+ // in parallel; the pre-PR sync renumbers, but serializing them is cheaper.
56
60
  width: A.width ?? 3,
61
+ // Integration target. The front door consolidates by DEFAULT (ADR-0026): it resolves a
62
+ // scope-native branch name and passes it here, so the campaign collects on that branch and
63
+ // the default branch is never touched — the run ends by offering ONE release PR
64
+ // target -> default. '' is the explicit trunk opt-in: each ticket PR merged straight into
65
+ // the default branch; a bare launch with no target is therefore trunk mode — non-default.
66
+ target: A.target ?? '',
67
+ // Canary: hold width at 1 until the run's first verified merge proves the plumbing
68
+ // end to end (branch, PR, CI, merge gate, close). For a project's first campaign.
69
+ canary: A.canary ?? false,
57
70
  // Tries per ticket: 1 attempt + 1 retry with a fresh context, then park. Deferrals
58
71
  // (a declared blocker had not landed yet) hand their attempt back, capped separately.
59
72
  attempts: A.attempts ?? 2,
@@ -84,12 +97,14 @@ where it left off — do not start over. Never open a second PR for the same tic
84
97
  const PREFLIGHT_SCHEMA = {
85
98
  type: 'object',
86
99
  additionalProperties: false,
87
- required: ['green', 'base', 'trackerAccess', 'verifyCommand', 'localCommands', 'failures'],
100
+ required: ['green', 'base', 'defaultBranch', 'trackerAccess', 'verifyCommand', 'localCommands', 'failures'],
88
101
  properties: {
89
102
  green: { type: 'boolean', description: 'base is synced and the verification gate passed' },
90
103
  headSha: { type: 'string', description: 'commit sha the gate ran against' },
91
104
  repo: { type: 'string', description: 'owner/name from the git remote, or empty' },
92
- base: { type: 'string', description: 'default branch name' },
105
+ base: { type: 'string', description: "the run's integration base: the declared target branch when one is set, else the default branch" },
106
+ defaultBranch: { type: 'string', description: 'the repository default branch name' },
107
+ targetCreated: { type: 'boolean', description: 'true when a declared target branch was missing from the remote and was created from the default branch tip' },
93
108
  issueTracker: { type: 'string', description: 'issueTracker from .launchrail.yml (github | linear | none)' },
94
109
  trackerAccess: {
95
110
  type: 'string',
@@ -145,7 +160,9 @@ const BUILD_SCHEMA = {
145
160
  properties: {
146
161
  status: {
147
162
  type: 'string',
148
- enum: ['merged', 'already-done', 'blocked', 'ci-red', 'ci-timeout', 'conflict', 'verify-failed', 'failed'],
163
+ enum: ['pr-open', 'merged', 'already-done', 'blocked', 'conflict', 'verify-failed', 'failed'],
164
+ description:
165
+ '"pr-open" is the normal hand-off (the loop owns CI and merge); "merged" only when an adopted PR turned out to be merged already (idempotency)',
149
166
  },
150
167
  pr: { type: 'integer', description: 'PR number, when one was opened or adopted' },
151
168
  mergeCommit: { type: 'string' },
@@ -162,6 +179,24 @@ const BUILD_SCHEMA = {
162
179
  },
163
180
  }
164
181
 
182
+ const GATE_SCHEMA = {
183
+ type: 'object',
184
+ additionalProperties: false,
185
+ required: ['status', 'summary'],
186
+ properties: {
187
+ status: {
188
+ type: 'string',
189
+ enum: ['merged', 'ci-failed', 'ci-timeout', 'not-mergeable', 'failed'],
190
+ },
191
+ mergeCommit: { type: 'string' },
192
+ issueClosed: { type: 'boolean' },
193
+ summary: {
194
+ type: 'string',
195
+ description: 'on merged: the API facts; on failure: the failing check or conflicting files, enough for a fresh implementer to act on',
196
+ },
197
+ },
198
+ }
199
+
165
200
  const VERIFY_SCHEMA = {
166
201
  type: 'object',
167
202
  additionalProperties: false,
@@ -211,24 +246,32 @@ Start clean: delete the failed ralph/${ticket.number}-* branch first, re-sync th
211
246
  : ''
212
247
  return `${preamble(pre)}
213
248
 
214
- Implement ticket #${ticket.number} ("${ticket.title}") end to end — merge included. You own it alone; assume no knowledge of any other session. Other implementers are working on other tickets against the same base right now, so ${pre.base} will move under you. That is expected.
249
+ Implement ticket #${ticket.number} ("${ticket.title}") through to an open PR. You own the build alone; assume no knowledge of any other session. Other implementers are working on other tickets against the same base right now, so ${pre.base} will move under you. That is expected.
215
250
  ${retry}
216
251
  Steps, in order:
217
252
  1. Dependency gate: before anything else, confirm every ticket on this ticket's "Blocked by" line is CLOSED with its work merged into ${pre.base}. If any blocker is still open, do NOT build on a missing dependency — report status "blocked", name the open blocker in "failure", and stop. That is a deferral, not a failure; the loop retries you after the blocker lands.
218
- 2. Read the ticket and everything it links (spec sections, ADRs, journeys). Report status "already-done" if it is already closed.
253
+ 2. Read the ticket and everything it links (spec sections, ADRs, journeys). If the tracker tool truncates the body (long code spans are a known trigger), fetch the full text by another route — the tracker's search API, the spec file in the repo — and never implement from a truncated ticket. Report status "already-done" if the ticket is already closed.
219
254
  3. Label the ticket ralph:building so a lost session leaves a trace.
220
255
  4. Branch from a fresh sync of ${pre.base}: ralph/${ticket.number}-<short-slug>.
221
256
  5. Implement by invoking the launch-ralph-implement skill — it owns the per-ticket contract: TDD, the verification gate, browser smoke for user-facing changes, self-review via /code-review, commit conventions.
222
- 6. Pre-PR sync: merge the latest ${pre.base} into your branch. Conflicts are ordinary work — resolve them with the launch-resolving-merge-conflicts skill and re-run the verification gate if anything changed.
223
- 7. Open a PR titled from the ticket, with "Closes #${ticket.number}" in the body. Never open a second PR if one already exists — adopt it. Opening against an up-to-date base means CI tests the state that will actually land.
224
- 8. Wait for CI if the repository has it, spacing polls with the Monitor tool or a background sleep — never a foreground sleep, never a busy loop; treat ~20 minutes as the budget and report status "ci-timeout" beyond it. Fix what your branch broke and push. If a failure reproduces on ${pre.base} itself, report "ci-red" and stop — that is systemic, not this ticket's problem.
225
- 9. Immediately before merging, re-sync with ${pre.base} once more (retry up to 3 times if the base keeps moving), then squash-merge. Squash-merge does not reliably fire "Closes" — read the issue back, close it explicitly if it is still open, and remove the ralph:building label. Never push to ${pre.base} directly; the PR is the only door.
257
+ 6. Pre-PR sync: merge the latest ${pre.base} into your branch. Conflicts are ordinary work — resolve them with the launch-resolving-merge-conflicts skill. If ${pre.base} gained DB migrations since you branched, regenerate yours to follow them with the project's migration tool — never hand-edit the migration journal. Re-run the verification gate if anything changed.
258
+ 7. Open a PR against ${pre.base}, titled from the ticket, with "Closes #${ticket.number}" in the body. Never open a second PR if one already exists — adopt it. Opening against an up-to-date base means CI tests the state that will actually land. Then report status "pr-open" with the PR number and STOP: the CI wait, the merge, and the issue close belong to the loop's merge gate, not to you — a subagent cannot wait on CI (a background sleep will not resume you). Never push to ${pre.base} directly; the PR is the only door.
226
259
 
227
260
  ${INTEGRITY}
228
261
 
229
262
  ${IDEMPOTENCY}
230
263
 
231
- Report honestly via the schema: "merged" only after the squash-merge API call succeeded; "blocked" when a declared blocker had not landed; "verify-failed" when the verification gate would not go green; "conflict" when a conflict was too ambiguous to resolve without losing behavior (say which files and why); "ci-red" / "ci-timeout" / "failed" otherwise, with a summary a fresh retry can act on. List deliberately-out-of-scope discoveries in "punted".`
264
+ Report honestly via the schema: "pr-open" once the PR exists against ${pre.base}; "merged" only when an adopted PR turned out to be already merged; "blocked" when a declared blocker had not landed; "verify-failed" when the verification gate would not go green; "conflict" when a conflict was too ambiguous to resolve without losing behavior (say which files and why); "failed" otherwise, with a summary a fresh retry can act on. List deliberately-out-of-scope discoveries in "punted".`
265
+ }
266
+
267
+ function gatePrompt(pre, ticket, build) {
268
+ return `You are the merge gate for ticket #${ticket.number}: PR #${build.pr} is open against ${pre.base}.
269
+ Tracker access from this environment: ${pre.trackerAccess}
270
+ You own the CI wait, the squash-merge, and the tracker bookkeeping — and nothing else. You never write code, never push commits, never repair a failing branch; a failing PR is reported, not fixed here.
271
+ 1. Wait for CI on the PR, if the repository has it. Space checks with the Monitor tool — NEVER a bare background sleep (it will not resume you) and never a busy loop. Treat ~20 minutes as the budget; beyond it report status "ci-timeout".
272
+ 2. CI green (or absent): check mergeability against ${pre.base} — the base may have moved since CI started. Mergeable: squash-merge via the tracker API; if the base moves between check and merge, re-check and retry up to 3 times. A real conflict is status "not-mergeable" — name the conflicting files if the API reports them.
273
+ 3. Merged: read issue #${ticket.number} back and close it explicitly if it is still open — "Closes #n" only auto-fires from the default branch${POLICY.target ? ', and this run does not merge there' : ', and squash-merge does not reliably fire it even there'} — then remove the ralph:building label. Report status "merged" with the merge commit sha.
274
+ 4. CI failed on the PR: report status "ci-failed" with the failing check and a summary a fresh implementer can act on. Fix nothing.`
232
275
  }
233
276
 
234
277
  function verifyPrompt(pre, ticket, build) {
@@ -308,17 +351,39 @@ async function drive(pre, ticket) {
308
351
  s.failures.push(`still blocked after ${s.defers} deferrals: ${build.failure ?? build.summary}`)
309
352
  return { ticket, ok: false }
310
353
  }
311
- if (build.status !== 'merged') {
354
+ if (build.status !== 'pr-open' && build.status !== 'merged') {
312
355
  s.failures.push(`[attempt ${s.attempts}] ${build.status}: ${build.failure ?? build.summary}`)
313
356
  return { ticket, ok: false }
314
357
  }
315
358
  if (!build.pr) {
316
- s.failures.push(`[attempt ${s.attempts}] reported merged but returned no PR number`)
359
+ s.failures.push(`[attempt ${s.attempts}] reported ${build.status} but returned no PR number`)
317
360
  return { ticket, ok: false }
318
361
  }
362
+ let mergeCommit = build.mergeCommit
363
+ if (build.status === 'pr-open') {
364
+ // The loop owns the merge gate (ADR-0022): an implementer cannot wait on CI (a
365
+ // subagent's background sleep never resumes it), and a single gate owner keeps
366
+ // merge ordering sane. A failing gate hands the ticket back as a failed attempt;
367
+ // the fresh retry adopts the PR via the idempotency clause, repairs, hands off again.
368
+ const gate = await agent(gatePrompt(pre, ticket, build), {
369
+ label: `gate:#${ticket.number}`,
370
+ phase: 'Gate',
371
+ schema: GATE_SCHEMA,
372
+ effort: 'low',
373
+ })
374
+ if (!gate) {
375
+ s.failures.push('gate agent died (infrastructure)')
376
+ return { ticket, ok: false, dead: true }
377
+ }
378
+ if (gate.status !== 'merged') {
379
+ s.failures.push(`[attempt ${s.attempts}] PR #${build.pr} ${gate.status}: ${gate.summary}`)
380
+ return { ticket, ok: false }
381
+ }
382
+ mergeCommit = gate.mergeCommit || mergeCommit
383
+ }
319
384
  // Nothing is trusted from a report — a claimed merge is checked against the remote
320
385
  // by a separate, cheap agent with tracker access only.
321
- const verdict = await agent(verifyPrompt(pre, ticket, build), {
386
+ const verdict = await agent(verifyPrompt(pre, ticket, { pr: build.pr, mergeCommit }), {
322
387
  label: `verify:#${ticket.number}`,
323
388
  phase: 'Verify',
324
389
  schema: VERIFY_SCHEMA,
@@ -328,7 +393,7 @@ async function drive(pre, ticket) {
328
393
  if (verdict?.merged && verdict.issueClosed) {
329
394
  s.status = 'merged'
330
395
  s.pr = build.pr
331
- s.mergeCommit = verdict.mergeCommit || build.mergeCommit
396
+ s.mergeCommit = verdict.mergeCommit || mergeCommit
332
397
  return { ticket, ok: true }
333
398
  }
334
399
  // Merged-but-issue-open fails verification too: the retry adopts the merged PR (the
@@ -363,9 +428,13 @@ function frontier(tickets, closedBefore) {
363
428
  // ---------------------------------------------------------------------------
364
429
  phase('Preflight')
365
430
  const pre = await agent(
366
- `Preflight for a Ralph loop run in this repository. Fix nothing; report actual state.
431
+ `Preflight for a Ralph loop run in this repository. Report actual state; fix nothing — the one permitted mutation is creating the declared integration branch in step 2.
367
432
  1. Read .launchrail.yml (issueTracker, testing commands, modules) and AGENTS.md (verbatim commands).
368
- 2. Identify the repo (git remote) and the default/base branch; sync it fresh (clean tree). If the base branch does not exist on the remote, report not green and say the base is missing — do not guess another branch.
433
+ 2. Identify the repo (git remote) and its default branch; report the default branch name as defaultBranch. ${
434
+ POLICY.target
435
+ ? `This run consolidates onto the integration branch "${POLICY.target}" — that branch is the base. If it does not exist on the remote, create it from the default branch's tip (no force; the default branch itself is never touched) and report targetCreated: true. A missing DEFAULT branch is still not green — do not guess.`
436
+ : `This run merges into the default branch (trunk) — that branch is the base. If it does not exist on the remote, report not green and say the base is missing — do not guess another branch.`
437
+ } Sync the base fresh (clean tree) and report its name as base.
369
438
  3. Determine how the tracker is reachable from THIS environment: check whether the CLI the project docs assume (e.g. gh) is installed; if not, name the concrete substitute available here (e.g. GitHub MCP tools) as an instruction future agents can follow.
370
439
  4. Run the project's install command, then the verification gate: npx @wemuda/launchrail verify. Report the actual exit codes, not the reassuring summary line. An empty verification contract failing the gate is a refusal condition, not something to work around.
371
440
  green means: base synced AND the verification gate exited 0.`,
@@ -384,11 +453,14 @@ if ((pre.issueTracker ?? 'none') === 'none') {
384
453
  phase('Graph')
385
454
  log(
386
455
  `Base green at ${pre.headSha ?? pre.base} on ${pre.base}. ` +
456
+ (POLICY.target
457
+ ? `Consolidating onto ${pre.base}${pre.targetCreated ? ' (created from the default branch tip)' : ''}; ${pre.defaultBranch || 'the default branch'} stays untouched. `
458
+ : `Trunk mode — each ticket merges into ${pre.base}. `) +
387
459
  (POLICY.only.length > 0
388
460
  ? `Scoped to ${POLICY.only.map((n) => `#${n}`).join(', ')}.`
389
461
  : 'No scope — building the whole ready frontier.') +
390
462
  (POLICY.max > 0 ? ` Stopping after ${POLICY.max} verified merge(s).` : '') +
391
- ` Width ${POLICY.width}, ${POLICY.attempts} attempts per ticket.`,
463
+ ` Width ${POLICY.width}${POLICY.canary ? ' (canary: width 1 until the first verified merge)' : ''}, ${POLICY.attempts} attempts per ticket.`,
392
464
  )
393
465
  let graph = await agent(graphPrompt(pre), { label: 'read-graph', phase: 'Graph', schema: GRAPH_SCHEMA, model: 'haiku', effort: 'low' })
394
466
  if (!graph) throw new Error('graph agent died — refusing to start')
@@ -424,7 +496,10 @@ while (rounds < POLICY.maxRounds) {
424
496
  const ready = frontier(tickets, closedBefore)
425
497
  if (ready.length === 0) break
426
498
  rounds += 1
427
- const batch = ready.slice(0, Math.min(POLICY.width, capLeft))
499
+ // Canary: the first verified merge proves the plumbing end to end (branch, PR, CI,
500
+ // merge gate, explicit close); until it lands, dispatch one ticket at a time.
501
+ const width = POLICY.canary && mergedCount() === 0 ? 1 : POLICY.width
502
+ const batch = ready.slice(0, Math.min(width, capLeft))
428
503
  log(`round ${rounds}: dispatching ${batch.map((t) => `#${t.number}`).join(', ')} (${ready.length} unblocked)`)
429
504
  const results = await parallel(batch.map((t) => () => drive(pre, t)))
430
505
  const landed = results.filter((r) => r?.ok)
@@ -495,10 +570,18 @@ verified means: the verification gate exited 0${pre.browserTesting && merged.len
495
570
  { label: 'release-verification', phase: 'Release', schema: RELEASE_SCHEMA },
496
571
  )
497
572
 
573
+ // The recap is part of the contract (ADR-0022): where the work lives and the one
574
+ // next step, as data — the supervisor relays it, never reconstructs it.
575
+ const mode = POLICY.target ? 'consolidation' : 'trunk'
498
576
  return {
499
577
  rounds,
500
578
  verified: release?.verified ?? false,
501
579
  maxReached,
580
+ target: { mode, base: pre.base, defaultBranch: pre.defaultBranch ?? '', headSha: release?.headSha ?? '' },
581
+ nextStep:
582
+ mode === 'consolidation'
583
+ ? `All campaign work is on ${pre.base}; ${pre.defaultBranch || 'the default branch'} is untouched. Release it with one PR ${pre.base} -> ${pre.defaultBranch || 'the default branch'} — offer it, and open it only when the user says so.`
584
+ : `Every merged ticket is live on ${pre.base}; nothing is left to integrate.`,
502
585
  release,
503
586
  merged: merged.map((s) => ({ ticket: s.ticket.number, title: s.ticket.title, pr: s.pr, mergeCommit: s.mergeCommit })),
504
587
  parked: parked.map((s) => ({ ticket: s.ticket.number, title: s.ticket.title, failures: s.failures })),
@@ -9,7 +9,7 @@ One command for the whole rail. You are the **conductor**, not a stage: find whe
9
9
 
10
10
  ## The stage map
11
11
 
12
- Read `.launchrail.yml` (`mode`, `origin`, `modules`, `issueTracker`) first — it is the source of truth for configuration; `npx @wemuda/launchrail status` for what's installed and current.
12
+ Read `.launchrail.yml` (`origin`, `modules`, `issueTracker`) first — it is the source of truth for configuration; `npx @wemuda/launchrail status` for what's installed and current.
13
13
 
14
14
  | # | Stage | Owner (invoke / run) | Done when |
15
15
  |---|---|---|---|
@@ -20,7 +20,7 @@ Read `.launchrail.yml` (`mode`, `origin`, `modules`, `issueTracker`) first — i
20
20
  | 4 | Complexity grill | `launch-grill` | Grill constraints committed under `docs/research/` |
21
21
  | 5 | Technical research | `launch-research`, fed the grill constraints | Research notes committed under `docs/research/` |
22
22
  | 6 | Architecture decisions | ADRs (`docs/adr/0000-template.md`) | `docs/adr/NNNN-*.md` beyond the template |
23
- | 7 | MVP specification | `launch-wayfinder` / `launch-spec` † | A spec exists under `docs/specs/` |
23
+ | 7 | MVP specification | `launch-wayfinder` / `launch-spec` † | A spec exists — a `spec`-labeled issue on the tracker, or a `docs/specs/` file in local mode (read `docs/agents/issue-tracker.md`) |
24
24
  | 8 | Design validation | `launch-design-validation` (fidelity chosen inside the skill) | The spec carries a `## Design validation` section (a recorded skip counts) |
25
25
  | 9 | Tickets | `launch-tickets` † | Tracker has `ready-for-agent` tickets with `Blocked by: #n` edges |
26
26
  | 10 | Implementation | `/launch-implement` † — drives the Ralph loop | The ready frontier is drained; PRs merged and verified |
@@ -41,12 +41,14 @@ Once the foundation exists (a real vision, ADRs beyond the template) and the use
41
41
  | **Semi** | Self-contained feature, some design surface, a handful of tickets | grill → `launch-spec` → design validation *(optional)* → `launch-tickets` |
42
42
  | **Small** | Well-understood change, little or no design surface, one or few tickets | grill → `launch-tickets` |
43
43
 
44
- Judgment calls: the grill here is feature-scoped (same `launch-grill`, narrower brief); discovery earns a place only when the feature opens genuinely new tech territory — a vendor category or storage engine the project hasn't used; design validation is for real UI surface; a genuine architecture decision gets an ADR before tickets. Between two sizes pick the smaller — it's cheaper to add a stage than to over-plan a small change. `mode` calibrates on top: `spike` may drop `launch-spec` and design validation (record the skip); `high-rigor` bumps one notch. Every size ends at `/launch-implement`.
44
+ Judgment calls: the grill here is feature-scoped (same `launch-grill`, narrower brief); discovery earns a place only when the feature opens genuinely new tech territory — a vendor category or storage engine the project hasn't used; design validation is for real UI surface; a genuine architecture decision gets an ADR before tickets. Between two sizes pick the smaller — it's cheaper to add a stage than to over-plan a small change. Every size ends at `/launch-implement`.
45
+
46
+ A feature that arrives **design-first** — a dropped zip or folder of Claude Design artboards, "here is the prototype of X" — routes through `launch-design-handoff` before sizing: it commits the package under `docs/design/<slug>/` and proposes a size; sizing then consumes its `handoff.md` as the feature brief, the grill takes the doc's open questions as its agenda, the spec cites the package as its UX/UI reference, and design validation usually becomes a recorded skip citing it.
45
47
 
46
48
  ## Running it
47
49
 
48
50
  1. **Did the user name a stage or a feature?** A stage keyword (below): sanity-check its inputs exist, offer the earlier stage if one is missing, but honor the jump if they insist — then invoke or hand off and stop. A new feature on a founded project: size it (above) and run the path.
49
- 2. **Otherwise orient, then find the frontier.** A cheap read-only look first: `git status`, current branch, recent commits — is something already in flight for the stage you're about to start? If the tracker is configured and reachable, read the live discussion on relevant tickets and PRs, not just titles; skip what isn't there (orientation sharpens routing, never gates it). Then close stage-0 gaps yourself without asking (init, commit init output, `sync` — it seeds `docs/agents/` too). Walk stages 1 → 12 and stop at the first whose "done when" fails, skipping only what `mode` permits. `origin: existing` with no real vision → route to `launch-project-alignment`, not a blank vision.
51
+ 2. **Otherwise orient, then find the frontier.** A cheap read-only look first: `git status`, current branch, recent commits — is something already in flight for the stage you're about to start? If the tracker is configured and reachable, read the live discussion on relevant tickets and PRs, not just titles; skip what isn't there (orientation sharpens routing, never gates it). Then close stage-0 gaps yourself without asking (init, commit init output, `sync` — it seeds `docs/agents/` too). Walk stages 1 → 12 and stop at the first whose "done when" fails, skipping only what the vision's non-goals record as deliberately skipped. `origin: existing` with no real vision → route to `launch-project-alignment`, not a blank vision.
50
52
  3. **Confirm the read.** Say where you think the project is and why — which artifacts you found and which you didn't. Ambiguous signals (template-only vision, several specs) are questions, not guesses.
51
53
  4. **Route.** Invoke the owner by exact name, or prepare the handoff for a user-typed stage (†). For stage 7, name the authoritative inputs in order; if the stack isn't stood up yet, tell it to name its seams but leave harness mechanics to the foundation work. For stage 10, hand over `/launch-implement` — never start it yourself.
52
54
  5. **Leave an explained map.** Current stage, the next one with a sentence on what it does and whether it's optional here, then the rest of the arc to the destination — and that any stage is reachable by keyword. A bare stage name reads as a turnstile; explain, don't gate.
@@ -57,11 +59,11 @@ Case-insensitive direct jumps:
57
59
 
58
60
  - `status` / `where` — report the detected stage and stop.
59
61
  - `next` — detect the frontier and drive it (the default).
60
- - `setup` / `init` — 0 · `align` / `adopt` — the existing-project on-ramp · `vision` — 1 · `explore` — 2 · `discovery` / `landscape` — 3 · `grill` — 4 · `research` — 5 · `deep-research` — 3→5 · `adr` / `architecture` — 6 · `spec` — 7 · `design-validation` / `validate` — 8 · `tickets` — 9 · `implement` / `build` / `ralph` / `loop` — hand over `/launch-implement` · `verify` / `smoke` — 11 · `release` — 12.
62
+ - `setup` / `init` — 0 · `align` / `adopt` — the existing-project on-ramp · `vision` — 1 · `explore` — 2 · `discovery` / `landscape` — 3 · `grill` — 4 · `research` — 5 · `deep-research` — 3→5 · `adr` / `architecture` — 6 · `spec` — 7 · `design-validation` / `validate` — 8 · `tickets` — 9 · `implement` / `build` / `ralph` / `loop` — hand over `/launch-implement` · `verify` / `smoke` — 11 · `release` — 12 · `handoff` / `design-handoff` — the design→code on-ramp (`launch-design-handoff`).
61
63
  - `feature` / `size` — size a described feature (recommend a path; route on request).
62
64
 
63
65
  Unrecognized keyword → show this list and ask.
64
66
 
65
- ## Mode calibration
67
+ ## Deliberate skips
66
68
 
67
- `mode` calibrates rigor, not stage order: `spike` may skip stages 2–5 and 8 when the vision's non-goals record it (don't nag); `standard-mvp` skips nothing silently; `high-rigor` skips nothing, wants an ADR per stage-6 decision, and design validation covers error and edge states. When a stage looks skipped, check the vision's non-goals before deciding — and if you can't tell, ask.
69
+ Skip nothing silently. A stage may be skipped only when the vision's non-goals record the skip — then honor it and don't nag. When a stage looks skipped, check the vision's non-goals before deciding — and if you can't tell, ask.
@@ -23,7 +23,7 @@ Two commands cover the whole rail:
23
23
  | 4 | Complexity grill | `launch-grill` | Vision + exploration + discovery | Grill constraints in `docs/research/` |
24
24
  | 5 | Technical research | `launch-research` | **Grill constraints** | Research notes in `docs/research/` |
25
25
  | 6 | Architecture decisions | ADRs (seeded template) | Research | `docs/adr/NNNN-*.md` |
26
- | 7 | MVP specification | `launch-wayfinder` / `launch-spec` † | Vision, ADRs, research | `docs/specs/` |
26
+ | 7 | MVP specification | `launch-wayfinder` / `launch-spec` † | Vision, ADRs, research | A `spec`-labeled issue on the tracker (or `docs/specs/` in local mode) ‡ |
27
27
  | 8 | Design validation | Launchrail `design-validation` skill | Spec (+ Claude Design at the top fidelity) | Revised spec with `## Design validation` section |
28
28
  | 9 | Tickets | `launch-tickets` † | Validated spec | Tickets in the tracker: `ready-for-agent` label, `Blocked by: #n` edges |
29
29
  | 10 | Implementation | `/launch-implement` † → the Ralph loop | Ready tickets | PRs merged and verified; the frontier drained |
@@ -34,8 +34,9 @@ Two commands cover the whole rail:
34
34
 
35
35
  Stage notes:
36
36
 
37
- - **Stages 3 → 4 → 5 are one arc** (`deep-research`): discovery *diverges* — it maps the real option space for the vision's hard parts (all the auth vendors, not one) and never picks winners; the grill *converges* — it narrows that landscape into constraints; research de-risks what survives. Don't collapse discovery into the grill outside `spike` mode: a grill with no discovery narrows whatever stack was assumed upstream, the exact failure discovery exists to prevent ([ADR-0015](https://github.com/wemuda/launchrail/blob/master/docs/adr/0015-discovery-research-stage.md)).
37
+ - **Stages 3 → 4 → 5 are one arc** (`deep-research`): discovery *diverges* — it maps the real option space for the vision's hard parts (all the auth vendors, not one) and never picks winners; the grill *converges* — it narrows that landscape into constraints; research de-risks what survives. Don't collapse discovery into the grill unless the vision's non-goals record the skip: a grill with no discovery narrows whatever stack was assumed upstream, the exact failure discovery exists to prevent ([ADR-0015](https://github.com/wemuda/launchrail/blob/master/docs/adr/0015-discovery-research-stage.md)).
38
38
  - **Stage 4 ends in a committed file, always.** `launch-grill` closes its interview by writing the surviving constraints to `docs/research/` — the conversation alone never closes the stage, and the skill treats the committed doc as part of its own contract.
39
+ - **‡ The stage-7 spec's home follows the tracker** ([ADR-0025](https://github.com/wemuda/launchrail/blob/master/docs/adr/0025-spec-home-follows-tracker.md)), exactly as stage-9 tickets do. On a real tracker (GitHub, GitLab, Linear) the spec **is** a `spec`-labeled issue and no `docs/specs/` file is written; in local mode (`local`, or no tracker) it is a committed `docs/specs/<slug>.md` file. Detection is therefore tracker-aware — read `docs/agents/issue-tracker.md` to know where to look. `ready-for-agent` still marks tickets only; the spec issue wears `spec` so the loop never dispatches prose as work.
39
40
  - **Stage 8 scales to the spec's design surface** through a fidelity ladder ([ADR-0016](https://github.com/wemuda/launchrail/blob/master/docs/adr/0016-design-validation-fidelity-ladder.md)): recorded skip, flow diagrams, screen mockups, or Claude Design. The level choice lives inside `design-validation` (recommend, user confirms). It exists to catch "specified but wrong on screen" while the finding still costs a spec edit rather than re-cut tickets — that's why it precedes stage 9 and is not stage 11, which checks the *built* product. Even a skip is recorded through the skill, so the gate stays artifact-based.
40
41
  - **Stage 10 is one door.** `/launch-implement` drives the Ralph loop ([ADR-0017](https://github.com/wemuda/launchrail/blob/master/docs/adr/0017-implementation-loop-provider.md) as amended by [ADR-0020](https://github.com/wemuda/launchrail/blob/master/docs/adr/0020-independent-skill-set.md)). Launchrail owns both edges of the loop: `ready-for-agent` tickets with `Blocked by: #n` edges in, `launchrail verify` (+ browser smoke where enabled) gating every merge.
41
42
 
@@ -49,6 +50,14 @@ The stages above take a fresh project to its first release. After that, the deli
49
50
 
50
51
  Every size ends the same way: `/launch-implement`, gated by `launchrail verify`. Sizing changes *how many* planning stages a feature needs, never *who owns* them.
51
52
 
53
+ A feature may arrive **design-first**: as a Claude Design prototype dropped into the session rather than a described idea. That arrival goes through the design handoff on-ramp (below) before sizing — the committed `handoff.md` then serves as the feature brief sizing consumes.
54
+
55
+ ## Returning from Claude Design
56
+
57
+ The stages drive Claude Design code→design (stages 2 and 8). The delivery loop also runs the reverse trip: design work done *in* Claude Design — a tweak, the next few pages, a redesign — comes back as files, typically a zip of artboards. The Launchrail `design-handoff` skill owns that arrival ([ADR-0024](https://github.com/wemuda/launchrail/blob/master/docs/adr/0024-design-handoff-onramp.md)): it reads the prototype against the current code and design system, asks only the questions documenting needs, and commits a **handoff package** under `docs/design/<feature-slug>/` — the prototype verbatim plus a distilled `handoff.md`, both project-owned, in the same accumulating `docs/design/` home as earlier handoffs.
58
+
59
+ From there the normal sizing paths apply, with two design-first twists: the handoff doc's open questions become the feature grill's agenda (the handoff feeds the grill, as the grill feeds research), and the spec cites `docs/design/<feature-slug>/` as its UX/UI reference — so design validation typically becomes a recorded skip citing the package, recorded through the `design-validation` skill as always. Like alignment, this is an on-ramp onto the same rail, not a second workflow; the handoff skill routes and never starts implementation.
60
+
52
61
  ## Adopting an existing project
53
62
 
54
63
  When `.launchrail.yml` records `origin: existing`, stage 1 is reached through the Launchrail `project-alignment` skill: it inventories what the codebase already has, infers a draft vision from the code, interviews only the gaps, and detects the existing design system as the baseline for stages 2 and 8, then hands to `vision-creation` to commit ([ADR-0013](https://github.com/wemuda/launchrail/blob/master/docs/adr/0013-existing-project-alignment.md)). Alignment is an on-ramp onto the same rail, not a second workflow.
@@ -64,14 +73,10 @@ The contract for `launch`, `/launch-implement`, and any agent driving the rail.
64
73
  - **`ready-for-agent` marks tickets, never specs.** The implementation loop's frontier is every open issue wearing that label, and it cannot tell prose from work — a spec or research note published to the tracker takes a different label (e.g. `spec`), or the loop will dispatch the document as work. Relabel before anyone starts the loop.
65
74
  - **Implementation is never started unprompted.** Stage 10 belongs to the user: conductors hand over `/launch-implement` and explain; they do not launch it.
66
75
  - **Everything the workflow produces is project-owned.** Vision, research, ADRs, specs, tickets — Launchrail tooling never overwrites them.
67
- - **Setup gaps are action, not conversation.** Known, additive fixes (commit untracked init output, run `init` when the manifest is missing, `sync` when loop materials are absent) get applied and reported; questions are saved for product artifacts, where intent is genuinely unknowable. Init owns installs — never improvise a dependency install from the web.
68
-
69
- ## Stage-skipping by project mode
76
+ - **Setup gaps are action, not conversation.** Known, additive fixes (commit untracked init output, run `init` when the manifest is missing, `sync` when loop materials are absent, record the project's test command as `testing.unitCommand` in `.launchrail.yml` once a real test runner exists — init detects it where it can and otherwise leaves it null) get applied and reported; questions are saved for product artifacts, where intent is genuinely unknowable. Init owns installs — never improvise a dependency install from the web.
70
77
 
71
- The manifest's `mode` calibrates rigor, not stage order:
78
+ ## Stage-skipping
72
79
 
73
- - `spike` — stages 2–5 and 8 may be skipped deliberately; record the skip in the vision's non-goals.
74
- - `standard-mvp` — the default path; skip nothing silently.
75
- - `high-rigor` — no skips; ADRs for every stage-6 decision, and design validation covers error and edge states, not just happy paths.
80
+ Skip nothing silently. A stage may be skipped deliberately — a short experiment might drop visual exploration, discovery, research, or design validation — but only when the vision's non-goals record the skip. A recorded skip is honored without nagging.
76
81
 
77
82
  When a stage looks skipped, check the vision's non-goals before deciding whether it's deliberate — and if you still can't tell, ask.
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: launch-design-handoff
3
+ description: The design→code on-ramp — take a Claude Design prototype dropped into the session (a .zip export, an extracted folder, artboard/HTML files, or a published canvas link) and turn it into a committed handoff package under docs/design/, read against the current codebase and design system, then route it into the delivery loop — normally a short feature grill, a spec that references the designs, and tickets. Use when the user drops design files or a zip from Claude Design and wants them documented or implemented — "here is the prototype of feature X" — or asks to hand designs back from Claude Design into code.
4
+ ---
5
+
6
+ # Design handoff — from Claude Design back into the loop
7
+
8
+ The rail drives Claude Design in two places — visual exploration (stage 2) and design validation at the top fidelity (stage 8). The delivery loop also runs the reverse trip: design work happens *in* Claude Design — a tweak, the next few pages, a redesign — and comes back as files. This skill is the **on-ramp for that return trip**: it turns a dropped prototype into a committed, readable handoff package and routes the work into the loop at the right size. It builds nothing itself — it gets the design intent into the repository as an artifact the planning stages can gate on and reference, then hands over.
9
+
10
+ ## Ground rules
11
+
12
+ - **The package is committed, and everything in it is project-owned.** A dropped zip has no durable link and the session is ephemeral — so unlike stage 8's validation evidence, which stays linked ([ADR-0016](https://github.com/wemuda/launchrail/blob/master/docs/adr/0016-design-validation-fidelity-ladder.md)), a handoff prototype is the input of record for implementation and lands in the repo ([ADR-0024](https://github.com/wemuda/launchrail/blob/master/docs/adr/0024-design-handoff-onramp.md)). Launchrail tooling never rewrites `docs/design/`.
13
+ - **Read the artboards, don't just file them.** Claude Design artboards are self-contained HTML: real layout, spacing, colors, type, and copy. The handoff doc is written from what the files actually say, cross-checked against what the user said the drop is.
14
+ - **Interview only what documenting needs; leave alignment to the grill.** This skill asks just enough to file the package truthfully — scope, intentional-vs-accidental design-system divergence, load-bearing copy. Everything the prototype is silent on (missing states, behavior rules, edge cases) is *recorded as open questions* that become the feature grill's agenda, not interviewed twice.
15
+ - **Design-system deltas are findings, not fixes.** Where the prototype's tokens diverge from the project's existing design system, ask whether that is an intentional restyle or an accident. Never silently normalize the prototype, and never silently fork the design system.
16
+ - **Route, don't build.** The skill ends at a committed package plus a recommended path. Planning stages keep their owners; implementation stays behind the user-typed `/launch-implement`.
17
+
18
+ ## Process
19
+
20
+ 1. **Take intake.** Accept whatever arrived: a `.zip` (unpack in a scratch directory — never straight into the repo), an extracted folder, loose artboard/HTML/image exports, or a published canvas link (export what is reachable; if the session can't reach it, ask for the zip). Strip packaging junk (`__MACOSX/`, `.DS_Store`, editor caches). Confirm scope in one line: which feature this is, and whether it reads as a tweak, new pages, or a redesign.
21
+ 2. **Inventory and read.** For each artboard/screen: name it, read its markup, and note layout, components, states shown, and copy. Identify components repeated across screens, and the assets (images, fonts) the screens actually reference — anything shipped but unused stays out of the package and is noted as excluded.
22
+ 3. **Diff against the code.** Classify each screen against the current implementation: **new** (no counterpart), **changed** (a counterpart exists — enumerate the deltas: layout, components, tokens, copy), or **context** (unchanged, included for orientation only). Extract the tokens the prototype actually uses (palette, type scale, spacing, radii) and compare them with the project's design-system baseline (theme/tokens/Tailwind config, component library — the baseline `launch-project-alignment` records). Express deltas in the project's vocabulary where one exists.
23
+ 4. **Ask the documenting questions.** Settle only what the package can't be filed without: confirm the scope reading, rule on each design-system divergence (intentional or accident), and flag copy that reads load-bearing (is it real or placeholder?). Everything else the prototype doesn't show — empty/loading/error states, responsive behavior, permissions, motion, validation rules — goes into the handoff doc's **Open questions**, addressed to the grill.
24
+ 5. **Commit the package** under `docs/design/<feature-slug>/`:
25
+ - `prototype/` — the design files as received (after the junk strip), verbatim. Never edit an artboard; it is the designer's record.
26
+ - `handoff.md` — the distilled contract: date and source; scope (tweak / extension / redesign) in a paragraph; the screen inventory (per screen: file, intent, new/changed/context with deltas, states shown, states missing); the component map (prototype component → existing code component, or "new"); token/design-system deltas with the intentional-vs-accident ruling; assets kept and excluded; **Open questions** (the grill's agenda); and the recommended path. Record the precedence rule: implementation follows the prototype for look and layout, `handoff.md` for behavior, states, and everything the pixels don't show.
27
+
28
+ One slug per feature; use the same `docs/design/` root as earlier handoffs (visual identity, prior features) so design references accumulate in one place. Iterating on the same feature revises the same slug — replace `prototype/`, revise `handoff.md` in place with a dated revision note — never a blind overwrite of settled answers, and never a second competing package for one feature.
29
+ 6. **Route.** Recommend a size with a one-line reason and let the user confirm (sizing is `launch`'s call; this is the on-ramp's proposal):
30
+ - **Tweak** — token/copy/spacing changes to existing screens → short feature grill → `launch-tickets`, prototype as the visual reference.
31
+ - **New pages / a feature on the existing system** (the common case) → short feature grill *fed this handoff doc, its open questions as the agenda* → `launch-spec`, naming `docs/design/<feature-slug>/` as the design reference the spec cites for UX/UI → `launch-tickets`. Because the spec is written *from* the prototype, design validation usually becomes a recorded skip citing this package — recorded through `launch-design-validation` as always, so the gate stays artifact-based.
32
+ - **Redesign / new surface area** → `launch-wayfinder` and the full large-feature path.
33
+
34
+ Then hand to `/launch`, naming the committed `handoff.md` as the input. Never start `/launch-implement` yourself — every path ends there by the user's hand. See [`workflow.md`](../launch/workflow.md) for the stage contract.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: launch-design-validation
3
- description: Validate an approved spec visually before implementation — at a confirmed fidelity level (recorded skip, flow-diagram artifact, screen-mockup artifact, or Claude Design), feed the findings back into a revised spec, and produce a handoff note for ticket creation. Use when a spec in docs/specs/ is drafted and the user wants design validation, a visual review, or pre-implementation sign-off.
3
+ description: Validate an approved spec visually before implementation — at a confirmed fidelity level (recorded skip, flow-diagram artifact, screen-mockup artifact, or Claude Design), feed the findings back into a revised spec, and produce a handoff note for ticket creation. Use when a spec is drafted (a spec-labeled issue on the tracker, or a docs/specs/ file in local mode) and the user wants design validation, a visual review, or pre-implementation sign-off.
4
4
  ---
5
5
 
6
6
  # Design validation
@@ -30,9 +30,9 @@ Every level answers the same question — does the specified behavior survive co
30
30
 
31
31
  ## Process
32
32
 
33
- 1. **Locate the spec.** Find the spec under `docs/specs/` (ask if there are several). Read it plus `docs/vision.md` and any grill/research artifacts it references, so validation happens against the product's constraints rather than in a vacuum.
33
+ 1. **Locate the spec.** Find the spec in its tracker-appropriate home — a `spec`-labeled issue on the tracker, or a file under `docs/specs/` in local mode (read `docs/agents/issue-tracker.md`; ask if there are several). Read it plus `docs/vision.md` and any grill/research artifacts it references, so validation happens against the product's constraints rather than in a vacuum.
34
34
  2. **Extract the flows to validate.** From the spec, list the user-facing journeys it implies — entry point, steps, decision points, end state. Confirm the list with the user; three to six flows is the useful range for an MVP.
35
- 3. **Choose the level — recommend, then confirm.** Read the spec's design surface and the manifest's `mode`, recommend one level with a one-line reason, and let the user confirm or override across all four. Mode is **advisory, never a gate**: `spike` leans toward a recorded skip, `high-rigor` leans toward mockups or Claude Design with error and edge states covered — but the user owns the call. Never pick silently.
35
+ 3. **Choose the level — recommend, then confirm.** Read the spec's design surface, recommend one level with a one-line reason, and let the user confirm or override across all four. The recommendation is **advisory, never a gate**: little or no design surface leans toward a recorded skip; a large or risky surface leans toward mockups or Claude Design with error and edge states covered — but the user owns the call. Never pick silently.
36
36
  4. **Run the level.**
37
37
  - **Recorded skip** — go straight to step 7 and write the section as a recorded skip: date, what was assessed, why nothing needed driving.
38
38
  - **Flow diagrams** — build one artifact page of flow/state diagrams covering the confirmed flows, including the decision points and terminal states the spec claims.
@@ -41,4 +41,4 @@ Every level answers the same question — does the specified behavior survive co
41
41
  5. **Harvest findings.** For each flow record: what the design confirmed, what it contradicted in the spec, and what the spec turned out to be silent on. Ambiguities count as findings. Findings at a low level are also a signal — if the diagrams alone surface deep uncertainty, recommend re-running a flow at a higher level before revising.
42
42
  6. **Revise the spec.** Apply the accepted findings to the spec in place. If a finding invalidates an ADR, update or supersede that ADR in the same change. Note rejected findings and why in the handoff note, so the question does not resurface every review.
43
43
  7. **Write the handoff note** at the end of the spec (section `## Design validation`) with: date, the level that ran, flows validated, links to the artifact pages / design artifacts, accepted changes, rejected findings with reasons, and open questions. This section is the evidence that validation happened — the ticket stage (`launch-tickets`) reads the spec as validated only if it is present.
44
- 8. **Hand off.** Confirm with the user that the revised spec is approved, commit it (respect the project's commit conventions), and point them at ticket creation as the next stage. See [`workflow.md`](../launch/workflow.md) for the full stage order.
44
+ 8. **Hand off.** Confirm with the user that the revised spec is approved, save it in place — commit the file, or update the spec issue on the tracker — respecting the project's commit conventions, and point them at ticket creation as the next stage. See [`workflow.md`](../launch/workflow.md) for the full stage order.