@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 +2 -1
- package/assets/agents-docs/issue-tracker-github.md +39 -4
- package/assets/agents-docs/issue-tracker-gitlab.md +4 -0
- package/assets/agents-docs/issue-tracker-linear.md +4 -0
- package/assets/agents-docs/issue-tracker-local.md +1 -1
- package/assets/ralph.workflow.js +109 -26
- package/assets/skills/launchrail/launch/SKILL.md +9 -7
- package/assets/skills/launchrail/launch/workflow.md +14 -9
- package/assets/skills/launchrail/launch-design-handoff/SKILL.md +34 -0
- package/assets/skills/launchrail/launch-design-validation/SKILL.md +4 -4
- package/assets/skills/launchrail/launch-implement/SKILL.md +7 -4
- package/assets/skills/launchrail/launch-ralph/SKILL.md +33 -14
- package/assets/skills/launchrail/launch-spec/SKILL.md +6 -3
- package/assets/skills/launchrail/launch-tickets/SKILL.md +5 -5
- package/assets/skills/launchrail/launch-wayfinder/SKILL.md +1 -1
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.js +4 -21
- package/dist/commands/init.js.map +1 -1
- package/dist/lib/manifest.d.ts +1 -4
- package/dist/lib/manifest.js +4 -8
- package/dist/lib/manifest.js.map +1 -1
- package/dist/lib/migrations.js +24 -0
- package/dist/lib/migrations.js.map +1 -1
- package/dist/lib/seeds.js +8 -2
- package/dist/lib/seeds.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
48
|
-
- **Blocking**:
|
|
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
|
|
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
|
package/assets/ralph.workflow.js
CHANGED
|
@@ -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.
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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
|
-
'
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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: ['
|
|
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}")
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ||
|
|
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.
|
|
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
|
|
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
|
-
|
|
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` (`
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
##
|
|
67
|
+
## Deliberate skips
|
|
66
68
|
|
|
67
|
-
|
|
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
|
|
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
|
-
|
|
78
|
+
## Stage-skipping
|
|
72
79
|
|
|
73
|
-
|
|
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
|
|
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
|
|
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,
|
|
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.
|