@wemuda/launchrail 1.14.0 → 1.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +2 -2
  2. package/assets/ralph.workflow.js +37 -24
  3. package/assets/skills/launchrail/launch/SKILL.md +4 -4
  4. package/assets/skills/launchrail/launch/workflow.md +3 -2
  5. package/assets/skills/launchrail/launch-browser-smoke/SKILL.md +36 -40
  6. package/assets/skills/launchrail/launch-implement/SKILL.md +4 -4
  7. package/assets/skills/launchrail/launch-loop-readiness/SKILL.md +79 -0
  8. package/assets/skills/launchrail/launch-project-alignment/SKILL.md +1 -1
  9. package/assets/skills/launchrail/launch-ralph/SKILL.md +7 -8
  10. package/assets/skills/launchrail/launch-ralph-implement/SKILL.md +5 -5
  11. package/dist/commands/add.js +6 -10
  12. package/dist/commands/add.js.map +1 -1
  13. package/dist/commands/doctor.js +55 -8
  14. package/dist/commands/doctor.js.map +1 -1
  15. package/dist/commands/init.js +0 -1
  16. package/dist/commands/init.js.map +1 -1
  17. package/dist/commands/verify.d.ts +3 -2
  18. package/dist/commands/verify.js +3 -2
  19. package/dist/commands/verify.js.map +1 -1
  20. package/dist/index.js +0 -15
  21. package/dist/index.js.map +1 -1
  22. package/dist/lib/browser-testing.d.ts +20 -3
  23. package/dist/lib/browser-testing.js +79 -79
  24. package/dist/lib/browser-testing.js.map +1 -1
  25. package/dist/lib/detect.d.ts +2 -0
  26. package/dist/lib/detect.js +3 -0
  27. package/dist/lib/detect.js.map +1 -1
  28. package/dist/lib/manifest.d.ts +6 -1
  29. package/dist/lib/manifest.js +17 -5
  30. package/dist/lib/manifest.js.map +1 -1
  31. package/dist/lib/migrations.js +76 -1
  32. package/dist/lib/migrations.js.map +1 -1
  33. package/dist/lib/readiness.d.ts +57 -0
  34. package/dist/lib/readiness.js +129 -0
  35. package/dist/lib/readiness.js.map +1 -0
  36. package/dist/lib/seeds.js +7 -6
  37. package/dist/lib/seeds.js.map +1 -1
  38. package/package.json +1 -1
  39. package/dist/commands/smoke.d.ts +0 -21
  40. package/dist/commands/smoke.js +0 -171
  41. package/dist/commands/smoke.js.map +0 -1
package/README.md CHANGED
@@ -60,9 +60,10 @@ From there, the day-to-day driver is not the CLI — it's the **`launch` skill**
60
60
  - **`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))
61
61
  - **`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
62
62
  - **`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
63
- - **`launch-browser-smoke`** — drives a real browser journey and leaves a traceable evidence bundle (with the browser-testing module)
63
+ - **`launch-browser-smoke`** — drives the running app in a real browser to see a just-built change working — one-off, from the shell with `agent-browser`, no test suite and no evidence bundle ([ADR-0034](https://github.com/wemuda/launchrail/blob/master/docs/adr/0034-browser-smoke-one-off-driving.md); with the browser-testing module)
64
64
  - **`launch-implement`** — the one door to building: `/launch-implement` drives ready tickets to verified merges through the Ralph loop (a ticket number builds just that one; "the next 5 of spec #2" scopes and caps a run)
65
65
  - **`launch-ralph`**, **`launch-ralph-implement`**, **`launch-code-review`**, **`launch-resolving-merge-conflicts`** — the verification-gated loop engine behind that door, installed by `init`
66
+ - **`launch-loop-readiness`** — checks and tunes a repo for the loop: measures the gates, sets the fast per-land gate, parallelizes the e2e specs, shares caches for parallel builders, narrows CI triggers, creates the tracker labels, adds hosted-session setup ([ADR-0033](https://github.com/wemuda/launchrail/blob/master/docs/adr/0033-loop-readiness.md)); `doctor` shows the same findings as warn-only readiness lines
66
67
 
67
68
  The CLI is the maintenance surface you return to between sessions:
68
69
 
@@ -73,7 +74,6 @@ npx @wemuda/launchrail sync # apply managed updates + run migra
73
74
  npx @wemuda/launchrail add browser-testing # enable a module
74
75
  npx @wemuda/launchrail doctor # repository and environment checks
75
76
  npx @wemuda/launchrail verify # deterministic verification gate
76
- npx @wemuda/launchrail smoke # scaffold a browser-smoke evidence bundle
77
77
  npx @wemuda/launchrail eject <module|file> # opt out of management (vendor mode: --all)
78
78
  ```
79
79
 
@@ -30,7 +30,7 @@ export const meta = {
30
30
  { title: 'Verify', detail: 'remote ground truth for every claimed land' },
31
31
  { title: 'Checkpoint', detail: 'the full gate on the base every N lands, with one bounded repair when red' },
32
32
  { title: 'Park', detail: 'comment failure history, label needs-info' },
33
- { title: 'Release', detail: 'final full gate and smoke, prune landed branches, the where-it-lives recap' },
33
+ { title: 'Release', detail: 'final full gate, prune landed branches, the where-it-lives recap' },
34
34
  ],
35
35
  }
36
36
 
@@ -93,8 +93,8 @@ const POLICY = {
93
93
  // counts as a real failure.
94
94
  resyncs: A.resyncs ?? 2,
95
95
  // Run the FULL verification gate on the base after this many lands (0 = only at release).
96
- // Every land already passed the fast gate on the merged tree; the full suite (browser
97
- // journeys included) is paid once per checkpoint instead of once per ticket, and a red
96
+ // Every land already passed the fast gate on the merged tree; the full suite (e2e
97
+ // specs included) is paid once per checkpoint instead of once per ticket, and a red
98
98
  // checkpoint has at most this many suspects.
99
99
  checkpointEvery: A.checkpointEvery ?? 5,
100
100
  // A sha the caller vouches for: a previous run verified the base green at exactly this
@@ -139,6 +139,15 @@ const PREFLIGHT_SCHEMA = {
139
139
  base: { type: 'string', description: "the run's integration base: the declared target branch when one is set, else the default branch" },
140
140
  defaultBranch: { type: 'string', description: 'the repository default branch name' },
141
141
  targetCreated: { type: 'boolean', description: 'true when a declared target branch was missing from the remote and was created from the default branch tip' },
142
+ baseOnRemote: {
143
+ type: 'string',
144
+ description:
145
+ 'the base tip as `git ls-remote --heads origin <base>` reports it on the LIVE remote (after `git fetch --prune`), or "" when the base is absent from origin. NEVER inferred from `git branch -r` or a local refs/remotes/origin/<base> tracking ref — those go stale and make a local-only base look present. green requires this non-empty and equal to headSha.',
146
+ },
147
+ basePushed: {
148
+ type: 'boolean',
149
+ description: 'true when the base existed only locally (a session-pinned working branch with unpushed commits) and preflight pushed it to origin so the run can land onto it',
150
+ },
142
151
  issueTracker: { type: 'string', description: 'issueTracker from .launchrail.yml (github | linear | none)' },
143
152
  trackerAccess: {
144
153
  type: 'string',
@@ -153,7 +162,6 @@ const PREFLIGHT_SCHEMA = {
153
162
  items: { type: 'string' },
154
163
  description: 'other verbatim local commands (typecheck, lint, unit tests) implementers should use',
155
164
  },
156
- browserTesting: { type: 'boolean', description: '.launchrail.yml modules.browser-testing' },
157
165
  pushedBranches: {
158
166
  type: 'array',
159
167
  items: {
@@ -299,7 +307,6 @@ const RELEASE_SCHEMA = {
299
307
  properties: {
300
308
  verified: { type: 'boolean', description: 'the full verification gate is green on the final base (run now, or already proven at this exact tip)' },
301
309
  headSha: { type: 'string' },
302
- smokeBundle: { type: 'string', description: 'path of the smoke evidence bundle, when one was produced' },
303
310
  prunedBranches: { type: 'array', items: { type: 'string' }, description: 'the landed ralph/* branches deleted from the remote' },
304
311
  summary: { type: 'string' },
305
312
  failures: { type: 'array', items: { type: 'string' } },
@@ -316,7 +323,7 @@ Architecture decisions: read the registry index (docs/adr/README.md) and open on
316
323
  Tracker access from this environment: ${pre.trackerAccess}
317
324
  Blocking edges live on tickets as "Blocked by: #n" lines.
318
325
  Verbatim local commands: install: ${pre.installCommand}${pre.localCommands.length > 0 ? ` ; ${pre.localCommands.join(' ; ')}` : ''}
319
- Two gates. The FAST gate is ${pre.fastGateCommand} — run it before every hand-off; it must exit 0. The FULL gate (${pre.verifyCommand}) belongs to the loop, which runs it on the base at its checkpoints — do not spend your turn on the whole suite; run only the slow test files your change touches (a browser journey you edited).
326
+ Two gates. The FAST gate is ${pre.fastGateCommand} — run it before every hand-off; it must exit 0. The FULL gate (${pre.verifyCommand}) belongs to the loop, which runs it on the base at its checkpoints — do not spend your turn on the whole suite; run only the slow test files your change touches (an e2e spec you edited).
320
327
  Several implementers share this machine — run single test files while iterating and save full runs for the gate.`
321
328
  }
322
329
 
@@ -339,7 +346,7 @@ Implement ticket #${ticket.number} ("${ticket.title}") through to a pushed, fast
339
346
  ${retry}${resyncNote}
340
347
  Steps, in order:
341
348
  1. Dependency gate: before anything else, confirm every ticket on this ticket's "Blocked by" line is CLOSED with its work landed on ${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.
342
- 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.
349
+ 2. Read the ticket and everything it links (spec sections, ADRs, designs). 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.
343
350
  3. Label the ticket ralph:building so a lost session leaves a trace.
344
351
  4. Branch and push immediately. ${adopt} From here on the pushed branch is your checkpoint: commit and push after every green step (a passing test slice, a finished subtask) — a session that dies keeps everything up to its last push, and its successor resumes from there instead of rebuilding. The pushes are also the loop's liveness signal.
345
352
  5. Implement by invoking the launch-ralph-implement skill — it owns the per-ticket contract: TDD, commit-and-push cadence, the fast gate, browser smoke for user-facing changes, self-review via launch-code-review, commit conventions.
@@ -404,7 +411,7 @@ Failures: ${(cp.failures ?? []).join(' | ') || cp.summary}
404
411
 
405
412
  Repair the base through to a pushed, green branch:
406
413
  1. Branch from a fresh fetch and push at once: \`git fetch origin ${pre.base} && git checkout -b ralph/repair-${k}-<short-slug> origin/${pre.base} && git push -u origin HEAD\`. Never check out ${pre.base} itself in this worktree.
407
- 2. Reproduce the failure — the failing test files first, the full gate if needed — and find the root cause among the listed landings (\`git log ${cp.greenSha || ''}..${cp.headSha}\`): an integration break between two tickets, a migration-number collision, a journey a landing invalidated. Fix it properly; regenerate colliding migrations with the project's migration tool.
414
+ 2. Reproduce the failure — the failing test files first, the full gate if needed — and find the root cause among the listed landings (\`git log ${cp.greenSha || ''}..${cp.headSha}\`): an integration break between two tickets, a migration-number collision, an e2e spec a landing invalidated. Fix it properly; regenerate colliding migrations with the project's migration tool.
408
415
  3. Make the fast gate green, then the FULL gate (${pre.verifyCommand}) green — this repair lands under the full gate. Self-review via the launch-code-review skill, commit conventionally, push after every green step.
409
416
  4. Hand off: report status "ready" with branch, headSha, and a commitTitle like "fix(<scope>): <what>". Then STOP — the loop lands it. Never push to ${pre.base} yourself.
410
417
  Report "failed" with the one fact a human must know if the base cannot be repaired without losing behavior.
@@ -418,20 +425,15 @@ function releasePrompt(pre, opts) {
418
425
  : opts.needsGate
419
426
  ? `2. Run the install command (${pre.installCommand}), then the FULL verification gate: ${pre.verifyCommand}. Report the actual exit code.`
420
427
  : `2. The full gate already passed at exactly this tip (${opts.greenSha}) during the run — report verified: true without re-running it, unless the tip you synced differs, in which case run ${pre.verifyCommand}.`
421
- const smokeStep =
422
- !opts.baseRed && opts.smoke
423
- ? `
424
- 3. The browser-testing module is enabled and tickets landed: start the app (node scripts/dev.mjs --background), scaffold an evidence bundle (npx @wemuda/launchrail smoke), and drive the smoke journeys from docs/testing/smoke-journeys.md per the launch-browser-smoke skill. Report the bundle path. A journey you could not complete is a failure, never a pass.`
425
- : ''
426
428
  const pruneStep =
427
429
  opts.prune.length > 0
428
430
  ? `
429
- 4. Prune the remote branches of the verified-landed tickets, and ONLY these: ${opts.prune.join(', ')} (\`git push origin --delete <branch>\` each; a branch already gone is fine). Never delete any other branch. Report prunedBranches.`
431
+ 3. Prune the remote branches of the verified-landed tickets, and ONLY these: ${opts.prune.join(', ')} (\`git push origin --delete <branch>\` each; a branch already gone is fine). Never delete any other branch. Report prunedBranches.`
430
432
  : ''
431
433
  return `Release verification for a finished Ralph loop run, in THIS checkout. Fix nothing.
432
434
  1. Sync a fresh ${pre.base} (\`git fetch origin ${pre.base}\`, \`git checkout ${pre.base}\`, \`git merge --ff-only origin/${pre.base}\`) and record its head sha.
433
- ${gateStep}${smokeStep}${pruneStep}
434
- verified means: the full verification gate is green on the final base${!opts.baseRed && opts.smoke ? ' AND no smoke journey failed' : ''}.`
435
+ ${gateStep}${pruneStep}
436
+ verified means: the full verification gate is green on the final base.`
435
437
  }
436
438
 
437
439
  const graphPrompt = (pre) => `List the open, ready tickets for a Ralph loop run. Change nothing on the tracker.
@@ -691,13 +693,13 @@ async function drive(pre, ticket, pushed) {
691
693
  // ---------------------------------------------------------------------------
692
694
  phase('Preflight')
693
695
  const pre = await agent(
694
- `Preflight for a Ralph loop run in THIS checkout — the loop lands tickets here (one land at a time), while builders use their own worktrees. Report actual state; fix nothing — the permitted mutations are creating the declared integration branch (step 2) and syncing this checkout onto the base.
696
+ `Preflight for a Ralph loop run in THIS checkout — the loop lands tickets here (one land at a time), while builders use their own worktrees. Report actual state; fix nothing — the permitted mutations are publishing the declared integration base to origin (step 2: creating it from the default branch tip, or pushing a base that exists only locally) and syncing this checkout onto it.
695
697
  1. Read .launchrail.yml (issueTracker, testing commands, modules) and AGENTS.md (verbatim commands).
696
- 2. Identify the repo (git remote) and its default branch; report the default branch name as defaultBranch. ${
698
+ 2. Identify the repo (git remote) and its default branch; report the default branch name as defaultBranch. Prune stale remote-tracking refs first: \`git fetch --prune origin\` — a leftover refs/remotes/origin/<base> from an earlier fetch otherwise makes a local-only base look present. Then judge the base's presence ON THE LIVE REMOTE with \`git ls-remote --heads origin <base>\`, NEVER \`git branch -r\` or a local tracking ref: a base that is not actually on origin passes on a stale ref here and then fails every land at \`git fetch origin <base>\`. ${
697
699
  POLICY.target
698
- ? `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.`
699
- : `This run lands onto 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.`
700
- } Sync the base INTO THIS CHECKOUT: the tree must be clean (\`git status --porcelain\` shows no modified or staged files — untracked files are fine; a dirty tree is not green: say so, stash nothing); \`git fetch origin\`, check out the base (tracking origin) and \`git merge --ff-only origin/<base>\` — a local base that has diverged from origin is not green (say "push or reset it first"). Report the base name as base and its tip as headSha.
700
+ ? `This run's integration base is the branch "${POLICY.target}". If \`git ls-remote --heads origin ${POLICY.target}\` returns it, that is the base. If it is ABSENT from origin: when a local "${POLICY.target}" branch holds commits not yet on origin (a session-pinned working branch), those commits ARE the base — push it (\`git push -u origin ${POLICY.target}\`) and report basePushed: true; otherwise mint it from the default branch's tip (\`git branch ${POLICY.target} origin/<default> && git push -u origin ${POLICY.target}\`; no force, the default branch itself is never touched) and report targetCreated: true. If you can neither push nor create it, report not green with "push the base first: ${POLICY.target} exists only locally". A missing DEFAULT branch is still not green — do not guess.`
701
+ : `This run lands onto the default branch (trunk) — that branch is the base. If \`git ls-remote --heads origin <default>\` does not return it, report not green and say the base is missing — do not guess another branch.`
702
+ } Sync the base INTO THIS CHECKOUT: the tree must be clean (\`git status --porcelain\` shows no modified or staged files — untracked files are fine; a dirty tree is not green: say so, stash nothing); with origin now carrying the base, check out the base (tracking origin) and \`git merge --ff-only origin/<base>\` — a local base that has diverged from origin is not green (say "push or reset it first"). Report the base name as base and its tip as headSha, then re-run \`git ls-remote --heads origin <base>\` and report the sha it returns as baseOnRemote — green REQUIRES baseOnRemote non-empty and equal to headSha (origin carries the base at exactly the tip this checkout builds against).
701
703
  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.
702
704
  4. List in-flight work from previous sessions: \`git ls-remote --heads origin 'ralph/*'\` — report every branch with its sha as pushedBranches (the loop adopts them; delete nothing).
703
705
  5. Run the project's install command (report it verbatim as installCommand). ${
@@ -706,7 +708,7 @@ const pre = await agent(
706
708
  : 'Then run the FULL verification gate'
707
709
  }: 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.
708
710
  Report verifyCommand as "npx @wemuda/launchrail verify" and fastGateCommand as "npx @wemuda/launchrail verify --fast" (the fast tier: testing.checkCommand, else the unit command — never e2e).
709
- green means: base synced in this checkout AND (the full gate exited 0 OR it was skipped as known green).`,
711
+ green means: the base is confirmed on origin (baseOnRemote non-empty and equal to headSha — proven by \`git ls-remote\`, never inferred from a local tracking ref) AND synced in this checkout AND (the full gate exited 0 OR it was skipped as known green).`,
710
712
  { label: 'preflight', phase: 'Preflight', schema: PREFLIGHT_SCHEMA },
711
713
  )
712
714
  if (!pre) throw new Error('preflight agent died — refusing to start')
@@ -715,6 +717,18 @@ if (!pre.green) {
715
717
  // can only end with unverifiable results.
716
718
  return { refused: true, reason: 'preflight not green', failures: pre.failures }
717
719
  }
720
+ // The green verdict must be grounded in the LIVE remote, not inferred locally: a stale
721
+ // refs/remotes/origin/<base> tracking ref makes a local-only base look present, preflight
722
+ // reports green, and then EVERY land fails at `git fetch origin <base>` (with canary, no
723
+ // land ever succeeds and the whole run is wasted). Refuse unless preflight confirmed the
724
+ // base on origin — via `git ls-remote` — at exactly the tip this checkout builds against.
725
+ if (!pre.baseOnRemote || pre.baseOnRemote !== (pre.headSha ?? '')) {
726
+ return {
727
+ refused: true,
728
+ reason: `preflight reported green but did not confirm the base ${pre.base} on origin at the checkout tip (git ls-remote --heads origin ${pre.base} → ${pre.baseOnRemote || 'absent'}, headSha ${pre.headSha || 'unset'}) — refusing to dispatch; every land would fail at git fetch origin ${pre.base}`,
729
+ failures: pre.failures,
730
+ }
731
+ }
718
732
  if ((pre.issueTracker ?? 'none') === 'none') {
719
733
  return { refused: true, reason: 'no issue tracker configured (.launchrail.yml issueTracker: none) — Ralph needs tickets' }
720
734
  }
@@ -734,7 +748,7 @@ phase('Graph')
734
748
  log(
735
749
  `Base green at ${pre.headSha ?? pre.base} on ${pre.base}${pre.skippedGate ? ' (known green — gate skipped)' : ''}. ` +
736
750
  (POLICY.target
737
- ? `Consolidating onto ${pre.base}${pre.targetCreated ? ' (created from the default branch tip)' : ''}; ${pre.defaultBranch || 'the default branch'} stays untouched. `
751
+ ? `Consolidating onto ${pre.base}${pre.targetCreated ? ' (created from the default branch tip)' : pre.basePushed ? ' (pushed to origin from local-only commits)' : ''}; ${pre.defaultBranch || 'the default branch'} stays untouched. `
738
752
  : `Trunk mode — each ticket lands on ${pre.base}. `) +
739
753
  (POLICY.only.length > 0
740
754
  ? `Scoped to ${POLICY.only.map((n) => `#${n}`).join(', ')}.`
@@ -943,7 +957,6 @@ const release = await agent(
943
957
  baseRedReason,
944
958
  needsGate: baseTip !== lastGreenSha,
945
959
  greenSha: lastGreenSha,
946
- smoke: Boolean(pre.browserTesting) && landed.length > 0,
947
960
  prune: landed.map((s) => s.branch).filter(Boolean),
948
961
  }),
949
962
  { label: 'release-verification', phase: 'Release', schema: RELEASE_SCHEMA },
@@ -15,7 +15,7 @@ Read `.launchrail.yml` (`origin`, `modules`, `issueTracker`) first — it is the
15
15
 
16
16
  | # | Stage | Owner (invoke / run) | Done when |
17
17
  |---|---|---|---|
18
- | 0 | Setup | `npx @wemuda/launchrail init` | Manifest + lockfile committed; `doctor` green (`docs/agents/` is seeded by init) |
18
+ | 0 | Setup | `npx @wemuda/launchrail init`; then `launch-loop-readiness` to tune the repo for the implementation loop (optional — recommended once for an existing codebase) | Manifest + lockfile committed; `doctor` green (`docs/agents/` is seeded by init) — its `ralph …` readiness lines are advice, never a gate |
19
19
  | 1 | Vision | `launch-vision-creation` — via `launch-project-alignment` when `origin: existing` | `docs/vision.md` exists and is real (not the bare template) |
20
20
  | 2 | Visual exploration | Claude Design | Exploration artifacts linked from `docs/vision.md` |
21
21
  | 3 | Discovery | `launch-discovery` | Landscape map committed under `docs/research/` (`discovery-*.md`) |
@@ -26,7 +26,7 @@ Read `.launchrail.yml` (`origin`, `modules`, `issueTracker`) first — it is the
26
26
  | 8 | Design validation | `launch-design-validation` (fidelity chosen inside the skill) | The spec carries a `## Design validation` section (a recorded skip counts) |
27
27
  | 9 | Tickets | `launch-tickets` † | Tracker has `ready-for-agent` tickets with `Blocked by: #n` edges |
28
28
  | 10 | Implementation | `/launch-implement` † — drives the Ralph loop | The ready frontier is drained; PRs merged and verified |
29
- | 11 | Verification | `npx @wemuda/launchrail verify` · `launch-browser-smoke` | The gate is green; smoke evidence where behavior is user-facing |
29
+ | 11 | Verification | `npx @wemuda/launchrail verify` · `launch-browser-smoke` | The gate is green; user-facing behavior seen working in a real browser |
30
30
  | 12 | Release | The project's release setup | The release is cut |
31
31
 
32
32
  † User-typed (`disable-model-invocation`): prepare the handoff — inputs committed, exact fully-argumented command handed over, resume when the artifact lands (see the conductor rules). Never call one and get refused, and never reverse-engineer it.
@@ -50,7 +50,7 @@ A feature that arrives **design-first** — a dropped zip or folder of Claude De
50
50
  ## Running it
51
51
 
52
52
  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.
53
- 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.
53
+ 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). When `doctor` warns on its `ralph …` readiness lines (fast gate, e2e specs, CI triggers, hosted setup, commands), offer `launch-loop-readiness` once — it measures and tunes the repo for the loop — and move on; readiness never gates the rail. 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.
54
54
  3. **Confirm the read — with the banner.** Render the rail banner for the position you detected, then say why — which artifacts you found and which you didn't. Ambiguous signals (template-only vision, several specs) are questions, not guesses.
55
55
  4. **Route.** Invoke the owner — call the Skill tool with its 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.
56
56
  5. **Close every transition with the banner.** When a stage finishes or a handoff is prepared, re-render the banner — the just-closed artifact under Done, the next mover under Now, the `➤` line carrying the one next action (the exact fully-argumented command when the stage is user-typed). Add a sentence on what the next stage does and whether it's optional here, and that any stage is reachable by keyword — a bare stage name reads as a turnstile; explain, don't gate.
@@ -61,7 +61,7 @@ Case-insensitive direct jumps:
61
61
 
62
62
  - `status` / `where` — render the rail banner for the detected position (with the evidence) and stop.
63
63
  - `next` — detect the frontier and drive it (the default).
64
- - `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`).
64
+ - `setup` / `init` — 0 · `readiness` / `tune` / `optimize` — `launch-loop-readiness` · `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`).
65
65
  - `feature` / `size` — size a described feature (recommend a path; route on request).
66
66
 
67
67
  Unrecognized keyword → show this list and ask.
@@ -51,6 +51,7 @@ A planning session (a grill, a wayfinder ticket, an interview) closes with the b
51
51
  ## Prerequisites
52
52
 
53
53
  - The repository is initialized (`npx @wemuda/launchrail init`) and healthy (`npx @wemuda/launchrail doctor`). Init writes the workflow skills, the implementation loop's materials, *and* the `docs/agents/` configuration (issue-tracker conventions and domain-doc rules, seeded from the manifest's answers) — there is no separate install or setup step on the golden path.
54
+ - Before the first `/launch-implement` on an existing codebase, `launch-loop-readiness` (stage 0, optional) measures the verification gates and tunes the repository for the implementation loop — a fast per-land gate, parallel e2e specs, shared caches for parallel builders, CI triggers, tracker labels, hosted-session setup, verbatim commands ([ADR-0033](https://github.com/wemuda/launchrail/blob/master/docs/adr/0033-loop-readiness.md)). `doctor`'s `ralph …` readiness lines say when it is worth running; they warn, never fail.
54
55
 
55
56
  ## Stages
56
57
 
@@ -66,7 +67,7 @@ A planning session (a grill, a wayfinder ticket, an interview) closes with the b
66
67
  | 8 | Design validation | Launchrail `design-validation` skill | Spec (+ Claude Design at the top fidelity) | Revised spec with `## Design validation` section |
67
68
  | 9 | Tickets | `launch-tickets` † | Validated spec | Tickets in the tracker: `ready-for-agent` label, `Blocked by: #n` edges |
68
69
  | 10 | Implementation | `/launch-implement` † → the Ralph loop | Ready tickets | PRs merged and verified; the frontier drained |
69
- | 11 | Verification | `npx @wemuda/launchrail verify` · Launchrail `browser-smoke` skill | Merged work | The gate green; smoke evidence where behavior is user-facing |
70
+ | 11 | Verification | `npx @wemuda/launchrail verify` · `launch-browser-smoke` | Merged work | The gate green; user-facing behavior seen working in a real browser |
70
71
  | 12 | Release | The project's release setup | Verified base | The release cut |
71
72
 
72
73
  † **User-typed by design** — `disable-model-invocation`: only the user can start these. `launch-wayfinder`/`launch-spec` and `launch-tickets` publish to the tracker; `/launch-implement` spawns agents and merges PRs. A conductor prepares the handoff instead of calling them — see the conductor rules.
@@ -77,7 +78,7 @@ Stage notes:
77
78
  - **Stage 4 converges far enough to build, not exhaustively.** The grill's stopping rule is build-safety — the decisions the next slice depends on are locked or safely defaulted — never an empty question tree: product design is generative, and "everything answered before implementation" is not a reachable state ([ADR-0029](https://github.com/wemuda/launchrail/blob/master/docs/adr/0029-planning-interaction-contract.md)). "Nothing silently assumed" still holds, but it means every open question is *labeled and parked*, not answered. And 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.
78
79
  - **‡ 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.
79
80
  - **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.
80
- - **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 --fast` gating every land and the full `launchrail verify` (+ browser smoke where enabled) at the loop's checkpoints and release ([ADR-0032](https://github.com/wemuda/launchrail/blob/master/docs/adr/0032-ralph-lean-local-gate-loop.md)).
81
+ - **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 --fast` gating every land, the full `launchrail verify` at the loop's checkpoints and release, and — where the browser-testing module is enabled — each implementer's one-off browser smoke of its user-facing ticket ([ADR-0034](https://github.com/wemuda/launchrail/blob/master/docs/adr/0034-browser-smoke-one-off-driving.md)) ([ADR-0032](https://github.com/wemuda/launchrail/blob/master/docs/adr/0032-ralph-lean-local-gate-loop.md)).
81
82
 
82
83
  ## Sizing the work in the delivery loop
83
84
 
@@ -1,49 +1,45 @@
1
1
  ---
2
2
  name: launch-browser-smoke
3
- description: Drive the running app through its defined smoke journeys in a real browser and produce a Launchrail evidence bundle. Use when user-facing work needs verification beyond deterministic tests, when the user asks to smoke-test the app, or before declaring user-facing work done in a project with the browser-testing module enabled (.launchrail.yml modules.browser-testing).
3
+ description: Drive the running app in a real browser to see a just-built change working — a one-off, agent-driven check of the feature, not a test suite and not an evidence bundle. Use before declaring user-facing work done when .launchrail.yml has modules.browser-testing enabled, or when the user asks to smoke-test or click through the app.
4
4
  ---
5
5
 
6
- # Browser smoke testing
6
+ # Browser smoke — see the change working
7
7
 
8
- Drive the real application through its user journeys in a browser and record evidence. Agentic smoke testing supplements deterministic tests — it never replaces them, and it never substitutes assertion for evidence.
8
+ A browser smoke is you driving the real stack in a real browser to check that what you just built looks right and works. It is **one-off**: it lives for this change and ends in a verdict, not in a test file, a journeys catalogue, or a committed report ([ADR-0034](https://github.com/wemuda/launchrail/blob/master/docs/adr/0034-browser-smoke-one-off-driving.md)). Deterministic coverage is the other lane — `npx @wemuda/launchrail verify` with the unit command and the thin Playwright e2e specs — and a smoke never grows it by accident.
9
9
 
10
10
  ## Preconditions
11
11
 
12
12
  1. `.launchrail.yml` has `modules.browser-testing: true`. If not, stop and suggest `npx @wemuda/launchrail add browser-testing`.
13
- 2. Deterministic checks pass first: run `node scripts/verify.mjs`. If verify fails, fix that before smoke testing — smoke runs on top of a green build.
14
- 3. The app is running. Start it with `node scripts/dev.mjs` (use `--background` in cloud or CI sessions; logs land in `.launchrail/state/dev.log`). In a fresh clone, run `node scripts/setup.mjs` first.
15
-
16
- ## Run contract
17
-
18
- 1. **Collect the journeys.** Read `docs/testing/smoke-journeys.md` (sections headed `## Journey:`) plus any journeys defined in the ticket or spec under verification. Each journey has a start point, steps, and verify checks.
19
- 2. **Scaffold the evidence bundle.** Run `npx @wemuda/launchrail smoke` (add `--url <url>` for a preview environment). It confirms the app responds and creates `artifacts/verification/<run-id>/` containing `meta.json`, a `summary.md` skeleton, and `screenshots/` + `traces/` directories. If it reports the app unreachable, start the app — do not skip the journey.
20
- 3. **Drive each journey in a real browser** — Playwright MCP, browser tools, or a Playwright script, whichever is available. The browser-testing module seeds a Playwright MCP server (`.mcp.json`); approve it once in Claude Code to drive the browser interactively, or fall back to a Playwright script in headless CI. Follow the steps as a user would: click, type, navigate. Try realistic variations and obvious edge cases, and watch the console and network panel as you go.
21
- 4. **Capture evidence while testing, not afterwards:**
22
- - Screenshots of each key state → `screenshots/`
23
- - Console errors and warnings → `console.log`
24
- - Failed or unexpected requests → `network-errors.json`
25
- - Playwright traces where available → `traces/`
26
- 5. **Apply the standard checks to every journey:**
27
- - No uncaught console errors
28
- - No failed API requests
29
- - The success state is visible
30
- - Data remains after refresh
31
-
32
- ## When you find a real bug
33
-
34
- 1. Record the precise reproduction in the evidence bundle.
35
- 2. Add or update a deterministic test that fails on the bug.
36
- 3. Fix the bug.
37
- 4. Prove the deterministic test passes.
38
- 5. Re-run the affected journey.
39
- 6. Keep the trace or screenshot that shows the failure.
40
-
41
- This turns exploratory findings into permanent regression coverage instead of forgotten discoveries.
42
-
43
- ## Completing the run
44
-
45
- - Fill in `summary.md` completely: journey outcomes, standard checks, evidence references, deviations, newly added tests, remaining blockers. Check only boxes you actually verified.
46
- - Record any deviation from the spec or design in `deviations.md` next to the summary.
47
- - A journey you could not complete is a failure or a blocker, never a pass.
48
- - Never mark a journey passed while it has unexplained console or network errors.
49
- - The committed record is `summary.md`, `deviations.md`, and `meta.json`; bulky evidence stays local or becomes a CI artifact.
13
+ 2. The fast gate is green: `npx @wemuda/launchrail verify --fast`. A smoke runs on top of a green build; a red one is fixed first.
14
+ 3. The app runs from **this** worktree: `node scripts/dev.mjs --background` (in a fresh clone, `node scripts/setup.mjs` first). Other builders may share the machine — pass `--port <n>` with a port nobody else uses. The script writes the URL to `.launchrail/state/dev.url` and the pid to `.launchrail/state/dev.pid`; read the URL from there rather than assuming `testing.appUrl`.
15
+ 4. The driver is `agent-browser`, installed by `scripts/setup.mjs`: `npx agent-browser --version` must answer. If it does not, run setup; if it still cannot install, use the fallback below.
16
+
17
+ ## The run
18
+
19
+ 1. **Decide what to check.** From the ticket's acceptance criteria and your diff, write three to six steps for *this change*: where to start, what to click or type, what must be visible at the end. They live in your head and your handoff — never in a repository file.
20
+ 2. **Drive them, one session per ticket.** `export AGENT_BROWSER_SESSION=<branch-or-ticket>` keeps your browser apart from other builders'. Then, from the shell:
21
+ - `npx agent-browser open <url>` — start on the page the change lives on.
22
+ - `npx agent-browser snapshot -i` — the interactive elements with refs (`@e1`, `@e2`, …).
23
+ - `npx agent-browser click @e3`, `fill @e5 "text"`, `type`, `press Enter` — act as a user would.
24
+ - `npx agent-browser wait --load networkidle`, `wait --text "Saved"`, `wait <selector>` — **after every action that triggers a request or opens a modal, wait before the next snapshot**; a snapshot taken too early is the usual false failure.
25
+ - `npx agent-browser screenshot <path>` — and look at the image yourself (open it with the Read tool). Layout, copy, empty states, and wrong-but-rendering are what a script cannot judge; that judgment is the point of this lane.
26
+ - `npx agent-browser errors`, `console`, `network requests` — after each step, not only at the end.
27
+ 3. **Click around beyond the happy path.** The obvious wrong input, the empty state, a reload, the back button. You are looking for what the ticket did not spell out.
28
+ 4. **The standard checks, every time:** no uncaught exceptions, no failed requests, the success state visible in a screenshot you looked at, the data still there after a reload.
29
+ 5. **Design:** compare the screen to a design only when the ticket or spec names one (a `docs/design/` handoff, a mockup). Never invent a comparison.
30
+
31
+ ## When something is wrong
32
+
33
+ 1. Fix it.
34
+ 2. Add the regression test at the **cheapest seam that would have caught it** — a unit or integration test first. A new Playwright spec under `tests/e2e/` only when the ticket asks for one or the behavior exists only in a real browser; the e2e lane stays thin, and a smoke never becomes a spec by default.
35
+ 3. Re-drive the steps that failed.
36
+
37
+ ## Done
38
+
39
+ - `npx agent-browser close`, and stop the app you started: `kill $(cat .launchrail/state/dev.pid)`.
40
+ - Your handoff states, in a few lines, what you drove and what you saw — that is the record. Nothing is committed for the smoke: no journeys file, no `artifacts/` bundle, no screenshots in the repo.
41
+ - A smoke you could not drive is a failure, not a pass. Never report one you did not run.
42
+
43
+ ## Fallback — no driver
44
+
45
+ If `agent-browser` cannot be installed here, write a throwaway `playwright-core` script under `.launchrail/state/` (gitignored) that drives the same steps and prints what it saw, and run it with the project's Playwright. It stays there — it never moves under `tests/`, and it is no substitute for looking at the screenshots.
@@ -18,13 +18,13 @@ From `.launchrail.yml`: `issueTracker` and the `testing` commands. From the argu
18
18
  - **a count** — "the next 5", "max 5" → the loop with a merge cap. Don't hand-pick which five: the cap is a stop condition, and the frontier decides the order — the loop stops after that many *verified merges* and leaves the rest ready;
19
19
  - **a spec, slice, or epic reference** — "spec #2's tickets", "the rest of slice 1" → resolve it to explicit numbers against the live tracker: the open tickets that belong to it (a "Part of: #n" line, the spec issue's ticket list, or a label), plus any open in-set blockers so the scope stays dependency-closed. Combinations compose: "the next 5 of spec #2" → that spec's tickets *and* a cap of 5.
20
20
 
21
- **Resolve the integration target with the scope.** Every multi-ticket run **consolidates by default** (ADR-0022, ADR-0026): its per-ticket branches are landed onto one integration branch by the loop's own local gate, the default branch stays untouched, and the run ends by *offering* one release PR `<target> → <default>` for the user to review and merge — the one place cloud CI runs — never merging to the default branch on its own. You name that branch before anything launches, scope-native: the branch the user named if they gave one ("collect spec #44 on `spec/44-mvp`"); else **the session's designated working branch** when the environment pinned one at start — a hosted session's `claude/...` branch exists to receive exactly this run, so use it rather than minting a `spec/*` twin beside it (ADR-0028); else the scope's own name when it maps to a spec, epic, or slice (`spec/<n>-<slug>`); else a generated fallback for an ad-hoc frontier (`launch/frontier-<today>`, or `launch/tickets-<n>-<m>` for a handful of loose numbers). A named target the remote lacks is created from the default branch's tip by the run's preflight. **Trunk** — each ticket merged straight to the default branch, live the moment it lands — is now the explicit opt-in: choose it only when the user asks in so many words ("land each ticket on master as it goes"), and never when the environment forbids pushing to the default branch. A pinned-branch environment ("develop on this branch; no unprompted PRs") constrains the *deliverable*, which consolidation already honors — the default branch stays untouched, the release PR stays offered-only. It never constrains the *engine*: the user typing `/launch-implement` is the explicit go-ahead for the loop's own mechanics — per-ticket `ralph/*` branches, pushed as they grow and landed on the target by the loop, no PR among them — and is never a reason to drop to sequential in-session building. Whichever target, restate it in the echo — a choice the user should recognize, never silent.
21
+ **Resolve the integration target with the scope.** Every multi-ticket run **consolidates by default** (ADR-0022, ADR-0026): its per-ticket branches are landed onto one integration branch by the loop's own local gate, the default branch stays untouched, and the run ends by *offering* one release PR `<target> → <default>` for the user to review and merge — the one place cloud CI runs — never merging to the default branch on its own. You name that branch before anything launches, scope-native: the branch the user named if they gave one ("collect spec #44 on `spec/44-mvp`"); else **the session's designated working branch** when the environment pinned one at start — a hosted session's `claude/...` branch exists to receive exactly this run, so use it rather than minting a `spec/*` twin beside it (ADR-0028); else the scope's own name when it maps to a spec, epic, or slice (`spec/<n>-<slug>`); else a generated fallback for an ad-hoc frontier (`launch/frontier-<today>`, or `launch/tickets-<n>-<m>` for a handful of loose numbers). A named target the remote lacks is put on origin by the run's preflight — created from the default branch's tip, or pushed as-is when it exists only locally with committed work (a pinned session's branch), the presence check made against the live remote, never a stale tracking ref (ADR-0035). **Trunk** — each ticket merged straight to the default branch, live the moment it lands — is now the explicit opt-in: choose it only when the user asks in so many words ("land each ticket on master as it goes"), and never when the environment forbids pushing to the default branch. A pinned-branch environment ("develop on this branch; no unprompted PRs") constrains the *deliverable*, which consolidation already honors — the default branch stays untouched, the release PR stays offered-only. It never constrains the *engine*: the user typing `/launch-implement` is the explicit go-ahead for the loop's own mechanics — per-ticket `ralph/*` branches, pushed as they grow and landed on the target by the loop, no PR among them — and is never a reason to drop to sequential in-session building. Whichever target, restate it in the echo — a choice the user should recognize, never silent.
22
22
 
23
23
  **Resolve prose to data before anything launches.** The loop's inputs are ticket numbers and policy values (`only`, `max`, `width`, `target`) — the workflow takes them as JSON args and refuses a natural-language string by design. Translating the user's words into that scope, against live tracker state, is *your* job, and it ends with an echo before any dispatch: "Scope: #14, #15, #19 — the remaining slice-1 tickets; #19 builds after #14. Cap: none. Target: consolidate on `spec/2-checkout` (`master` untouched; one release PR offered at the end). Engine: the `ralph` workflow." A misread scope corrected here costs a sentence; corrected after launch it costs a run. That echo is your one pre-launch report — resolve the scope, repair setup (Step 2), and route (Step 3) without narrating the checks in between; surface a step only when it fails or changes the scope.
24
24
 
25
25
  ## Step 2 — Repair setup, don't gatekeep
26
26
 
27
- If the loop's materials are missing — `modules.ralph` off in the manifest, or `.claude/workflows/ralph.js` absent — run `npx @wemuda/launchrail sync` (additive and idempotent; its migration installs them) and say what it did. Never answer the user's "build this" with "first go run a command" for anything this skill can run itself. What you cannot repair, report precisely: no tracker configured (`issueTracker: none`), or an empty verification contract (no `testing` commands — `verify` fails on an empty contract and the loop refuses a start it cannot gate). An unset `testing.checkCommand` is not a gap — the fast gate falls back to the unit command — but say once that naming a quicker lint/typecheck/unit gate there makes every land cheaper.
27
+ If the loop's materials are missing — `modules.ralph` off in the manifest, or `.claude/workflows/ralph.js` absent — run `npx @wemuda/launchrail sync` (additive and idempotent; its migration installs them) and say what it did. Never answer the user's "build this" with "first go run a command" for anything this skill can run itself. What you cannot repair, report precisely: no tracker configured (`issueTracker: none`), or an empty verification contract (no `testing` commands — `verify` fails on an empty contract and the loop refuses a start it cannot gate). An unset `testing.checkCommand` is not a gap — the fast gate falls back to the unit command — but when `doctor` warns on its `ralph …` readiness lines (fast gate, e2e specs, CI triggers, hosted setup, commands), say once that `/launch-loop-readiness` measures and fixes them, and proceed; readiness never gates a build.
28
28
 
29
29
  ## Step 3 — Route by scope
30
30
 
@@ -33,7 +33,7 @@ If the loop's materials are missing — `modules.ralph` off in the manifest, or
33
33
  **One ticket:** build it here, watchable, under the same contract a Ralph dispatch carries (kept textually parallel with `launch-ralph` — change one, change both). A single ticket has nothing to consolidate, so its base is the **default branch** and its one CI-gated PR merges there — consolidation-by-default is a multi-ticket policy (use a different base only if the user names one, or the session pins a designated branch — then that is the base). Two deliberate divergences from a loop dispatch: a single ticket's PR *is* the review checkpoint, so it keeps the PR and its cloud CI, and you are the session, not a subagent, so you run that merge gate yourself — waiting on CI here is fine:
34
34
 
35
35
  1. **Dependency gate:** every ticket on the `Blocked by:` line is closed with its work merged. An open blocker stops you before any code — name it and offer to build it first.
36
- 2. Read the ticket and everything it links (spec sections, ADRs, journeys), plus `AGENTS.md`/`CLAUDE.md`.
36
+ 2. Read the ticket and everything it links (spec sections, ADRs, designs), plus `AGENTS.md`/`CLAUDE.md`.
37
37
  3. Label the ticket `ralph:building`; branch `ralph/<n>-<short-slug>` from a fresh sync of the base resolved above.
38
38
  4. Implement by calling the Skill tool with **`launch-ralph-implement`** — it owns TDD, the push cadence, the gates (the full `verify` before a PR of your own), browser smoke for user-facing changes, self-review, and commit conventions. Never paraphrase it inline.
39
39
  5. Pre-PR sync: merge the latest base; resolve conflicts by calling the Skill tool with `launch-resolving-merge-conflicts`; re-run the gate if anything changed.
@@ -44,6 +44,6 @@ If the loop's materials are missing — `modules.ralph` off in the manifest, or
44
44
  ## Ground rules
45
45
 
46
46
  - **Only the user starts this.** Conductors and other skills hand over the command (`/launch-implement`); they never invoke it. The engines behind it inherit the same rule — reaching them through this door *is* the explicit user start.
47
- - **Nothing is done until the gates are green** — `npx @wemuda/launchrail verify --fast` on every land, the full `npx @wemuda/launchrail verify` at the loop's checkpoints and once more on the final base when a run ends (and before a single ticket's PR). Where `modules.browser-testing` is enabled and the change is user-facing, a `launch-browser-smoke` journey is part of done.
47
+ - **Nothing is done until the gates are green** — `npx @wemuda/launchrail verify --fast` on every land, the full `npx @wemuda/launchrail verify` at the loop's checkpoints and once more on the final base when a run ends (and before a single ticket's PR). Where `modules.browser-testing` is enabled and the change is user-facing, a `launch-browser-smoke` run — the change seen working in a real browser — is part of done.
48
48
  - **Report evidence, not assertions:** landing commits (PR numbers in single-ticket mode), issues closed, checkpoint and verify outcomes — and what was held, parked, or punted, with why.
49
49
  - **Every loop run ends with the campaign recap** (the `launch-ralph` close-out): where the work lives — target branch and head SHA — the ticket → landing-commit table, held, parked, and stuck tickets, punted follow-ups in one list, and the single next step. By default that step is *offering* the one release PR `<target> → <default>` (where cloud CI runs, once), opened only when the user says so; in the opt-in trunk mode it is nothing — every ticket is already live on the default branch. Under the recap, render the rail banner ([`workflow.md`](../launch/workflow.md)'s phase view) — Build done for this scope, verification/release as what remains — so even the build door closes with "where we are and where we're going".
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: launch-loop-readiness
3
+ description: Check and tune a repository for the implementation loop — measure the verification gates, then set the fast per-land gate, parallelize the e2e specs, share caches for parallel builders, narrow CI triggers, create the tracker labels, add hosted-session setup, and document the verbatim commands. Measured first, applied with one confirmation, never a gate on the rail. Run once before the first /launch-implement on an existing codebase, or whenever doctor's `ralph …` readiness lines warn.
4
+ ---
5
+
6
+ # Loop readiness — tune the repo for the implementation loop
7
+
8
+ The Ralph loop is only as fast as the repository lets it be. Every land runs the fast gate, every fifth land runs the full gate, every builder starts in a fresh worktree, every push can wake a CI runner, and a hosted session starts from an empty container. A repo tuned for humans typing `pnpm test` once an hour wastes tokens and wall-clock when three fresh-context builders hammer it all night. This skill measures that waste and removes it — without weakening a single test.
9
+
10
+ It is advice, never a gate: `/launch-implement` runs whether or not this skill ever ran. `npx @wemuda/launchrail doctor` shows the same findings as warn-only `ralph …` lines; this skill is where they get fixed with numbers behind them.
11
+
12
+ ## Contract
13
+
14
+ - **Measure before proposing.** Every finding carries a number — seconds cold, seconds warm, e2e spec count — from a run you made, not an estimate.
15
+ - **Propose in impact order, with the expected saving**, then apply everything reversible in **one confirmation round** (the interaction contract in [`workflow.md`](../launch/workflow.md): reversible implementation details are yours, the user confirms only what touches their testing methodology — which e2e specs stay serial, what CI still runs).
16
+ - **Never weaken coverage.** No deleted, skipped, or loosened tests; no lowered assertions; latency-sensitive e2e specs stay serial. Speed comes from tiering, parallelism, caching, and not running the same suite twice.
17
+ - **Idempotent.** Re-running on a tuned repo reports "ready" and changes nothing.
18
+ - **One commit** at the end, in the project's convention (`chore: tune the repo for the implementation loop`), listing what changed.
19
+
20
+ ## Step 1 — Inventory (read-only)
21
+
22
+ 1. `npx @wemuda/launchrail doctor` — collect every `ralph …` line; they are the deterministic half of this checklist.
23
+ 2. `.launchrail.yml` `testing.*` (unit, check, e2e, dev) and `AGENTS.md`'s Commands section: which commands exist verbatim, which are missing.
24
+ 3. The package manager and its install command (frozen-lockfile form); the scripts (`test`, `lint`, `typecheck`, `build`, `check`); the monorepo tool (turbo, nx, workspaces) and its cache configuration; the test runners and their configs (vitest/jest workers and cache, Playwright `workers`, `fullyParallel`, `projects`, `retries`).
25
+ 4. CI: every workflow's triggers and jobs; whether the required check is the same command the loop runs locally.
26
+ 5. Data: the migration tool and its generate/renumber command; whether tests need a database, ports, or external services, and how they isolate per run.
27
+ 6. Tracker: the labels the loop uses (`ready-for-agent`, `ralph:building`, `needs-info`, `spec`) exist — check with the tracker tools named in `docs/agents/issue-tracker.md`.
28
+ 7. `.claude/settings.json`: hooks (the Ralph guard, any `SessionStart`), permissions; the seeded `scripts/setup.mjs` when the browser-testing module is on.
29
+
30
+ ## Step 2 — Measure
31
+
32
+ Run, and time, in this order — twice each, so cold and warm are both known:
33
+
34
+ - `npx @wemuda/launchrail verify --fast` (the per-land gate).
35
+ - `npx @wemuda/launchrail verify` (the checkpoint and release gate); note the per-package breakdown the runner prints and, with e2e specs, their count and their share of the time.
36
+ - The install command in a fresh worktree (`git worktree add ../readiness-probe HEAD`, install, then remove it) — what every builder pays before its first test.
37
+
38
+ Write the numbers down; they anchor every proposal and the closing card.
39
+
40
+ ## Step 3 — The catalogue
41
+
42
+ Work through these; each names the check, the fix, and why it matters to the loop.
43
+
44
+ 1. **The fast gate** — `testing.checkCommand` unset, or slower than ~2 minutes warm. Set it to lint + typecheck + the unit suites without e2e specs (on turbo: `turbo run lint typecheck test --filter=!<web-package>` plus the web package's unit runner). Every land and every hand-off runs this; the e2e specs move to the checkpoints.
45
+ 2. **E2e specs** — a global `workers: 1` or `fullyParallel: false`. Split: a `serial` Playwright project holding the latency-asserting specs (matched by directory or a `@serial` tag) and a `parallel` project for the rest; run them as two invocations from the e2e command (`playwright test --project=parallel --workers=4 && playwright test --project=serial --workers=1`). Which specs are latency-sensitive is the user's call — ask with the list in hand. Target: the full suite in a fraction of its serial time with identical assertions.
46
+ 3. **Caches for parallel builders** — each builder starts in a fresh worktree with an empty cache. Enable the monorepo tool's shared cache: turbo remote cache, or `TURBO_CACHE_DIR` pointing at a path outside the worktree (persist it for hosted sessions via `$CLAUDE_ENV_FILE` in the SessionStart hook); a shared vitest `cacheDir`; the package manager's content-addressable store (pnpm has one by default). Unchanged packages then cost nothing at the builder's gate and at the lander's.
47
+ 4. **CI triggers** — a workflow that runs on every push. The loop pushes `ralph/*` on every green step and the integration branch on every land; each would start a run nobody waits on. Trigger on `pull_request` and pushes to the default branch only, and add a `concurrency` group with `cancel-in-progress` so a release PR's re-pushes do not queue behind each other. Cloud CI runs once, on the release PR — make sure the required check there is the same full gate the loop ran at release.
48
+ 5. **Tracker labels** — create any of `ready-for-agent`, `ralph:building`, `needs-info`, `spec` that are missing, with the descriptions from `docs/agents/issue-tracker.md`. A run refuses or mislabels without them.
49
+ 6. **Hosted-session setup** — no `SessionStart` hook. Hosted sessions (Claude Code on the web) start from a fresh container: add `.claude/hooks/session-start.sh` that exits early unless `$CLAUDE_CODE_REMOTE` is `true`, runs the install command, and — with the browser-testing module — `node scripts/setup.mjs` so the pinned browser build is present (a version mismatch here is a preflight refusal in the field). Register it additively under `hooks.SessionStart` in `.claude/settings.json` (merge; the Ralph guard's `PreToolUse` entry stays). Synchronous first; offer async mode only if the user wants faster session starts. Validate it once with `CLAUDE_CODE_REMOTE=true` before committing.
50
+ 7. **Verbatim commands** — `AGENTS.md`'s Commands section still holds the seeded TODO, or lacks any of install, fast gate, full gate, dev, and the migration generate/renumber command. Builders and the pre-land sync read these verbatim; fill them in from the inventory, and mirror the test ones into `.launchrail.yml` `testing.*`.
51
+ 8. **Test isolation for parallel builders** — fixed ports, a shared database or schema, temp files at fixed paths. Three builders run the suites at once on one machine: use ephemeral ports (`0`), per-run database names or schemas, and `os.tmpdir()`-based paths. A flake that only appears at width 3 is this.
52
+ 9. **Worktree hygiene** — configs or scripts that assume the checkout path (absolute paths, `..` walks out of the repo), generated files that are not ignored, install steps that write outside the repo. Builders work in `git worktree`s; anything path-bound breaks there first.
53
+ 10. **Unattended runs** — remind, once, that an unattended run launches in a non-prompting permission mode (the guard hook warns at launch), and that any MCP or CLI the loop needs (the tracker tools) must be reachable from the session that launches it.
54
+
55
+ ## Step 4 — Confirm, apply, re-measure
56
+
57
+ Present the proposals as one list — finding, fix, expected saving — and ask one round of at most three questions, only about what needs the user's judgment (which e2e specs stay serial; whether CI may be narrowed; anything touching production or secrets). Then apply everything approved, run `doctor` again, re-run the Step 2 timings, and commit.
58
+
59
+ ## Step 5 — The readiness card
60
+
61
+ Close with a card the user can act on without scrolling back:
62
+
63
+ ```
64
+ Loop readiness — <project>
65
+ Fast gate: <before> → <after> (warm <n>s) · <command>
66
+ Full gate: <before> → <after> · e2e specs <n> (<parallel>/<serial>)
67
+ Builder cold start: install <n>s · first fast gate <n>s
68
+ Changed: <one line per change, with the file>
69
+ Left for you: <anything that needs a human — secrets, remote cache login, CI settings>
70
+ ```
71
+
72
+ Then hand back: when reached through `launch`, close with the rail banner from [`workflow.md`](../launch/workflow.md) — stage 0 under Done, the next stage under Now.
73
+
74
+ ## What this skill does not do
75
+
76
+ - It never starts the loop, and never blocks it — `/launch-implement` runs without it.
77
+ - It never touches product artifacts (vision, specs, tickets) or the managed Launchrail files.
78
+ - It never deletes, skips, or weakens a test, and never lowers an assertion to buy speed.
79
+ - It never changes what CI verifies, only when it runs; a project's required checks stay required.
@@ -39,7 +39,7 @@ How each Launchrail artifact shows up in an existing project, and what to do:
39
39
  | Architecture decisions (`docs/adr/`) | ADRs beyond the template; or de-facto decisions in code/docs | Note stage 6 as largely satisfied | Note as a gap; capture load-bearing existing decisions as ADRs later |
40
40
  | MVP spec (`docs/specs/`) | Spec docs, PRDs, design docs | Note stage 7 as partial/satisfied | Real gap — owned by the spec stage |
41
41
  | Tickets | The tracker in `.launchrail.yml` (issues/backlog) | Note stage 9 as partial | Real gap — owned by `launch-tickets` |
42
- | Verification | Test suite, CI config, Playwright | Wire `testing` commands in `.launchrail.yml`; note stage 11 partial | Note as a gap |
42
+ | Verification | Test suite, CI config, Playwright | Wire `testing` commands in `.launchrail.yml`; note stage 11 partial; recommend `launch-loop-readiness` before the first `/launch-implement` — it measures the gates and tunes the repo for the loop | Note as a gap |
43
43
 
44
44
  ## What this skill does not do
45
45