@wemuda/launchrail 1.10.1 → 1.12.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.
@@ -74,6 +74,17 @@ Run `gh issue view <number> --comments`.
74
74
 
75
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
76
 
77
+ ## The spec's milestone (rollup view)
78
+
79
+ The spec issue and its tickets are also bundled under a **GitHub milestone** named for the feature, so the tracker shows one progress rollup — the milestone's bar fills as tickets close. The milestone is a **rollup, not the spec's home**: the `spec`-labelled issue stays canonical ([ADR-0025](https://github.com/wemuda/launchrail/blob/master/docs/adr/0025-spec-home-follows-tracker.md)), and the milestone's description holds only a one-line goal and a link back to that issue.
80
+
81
+ - **Create it** (`launch-spec`, once per spec): `gh api --method POST repos/{owner}/{repo}/milestones -f title='<feature>' -f description='<one-line goal> — spec: <spec-issue-url>'`. The response's `number` is the milestone id.
82
+ - **Put an issue in it**: pass `--milestone '<feature>'` to `gh issue create`, or `gh issue edit <n> --milestone '<feature>'` after the fact. `launch-spec` adds the spec issue; `launch-tickets` adds every ticket to the same milestone.
83
+ - **Find the milestone a spec already carries** (`launch-tickets`): `gh issue view <spec> --json milestone --jq .milestone.title`.
84
+ - **Progress** is computed by GitHub from closed-vs-open issues — no upkeep.
85
+
86
+ An issue belongs to at most one milestone, so each ticket rolls up to exactly one spec. Milestones carry no labels and take no part in the implementation loop's frontier — they are purely the human-facing rollup, never a substitute for the `spec` issue or the native sub-issue links.
87
+
77
88
  ## Wayfinding operations
78
89
 
79
90
  Used by `launch-wayfinder`. The **map** is a single issue with **child** issues as tickets.
@@ -44,6 +44,17 @@ Run `glab issue view <number> --comments`.
44
44
 
45
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
46
 
47
+ ## The spec's milestone (rollup view)
48
+
49
+ The spec issue and its tickets are also bundled under a **GitLab milestone** named for the feature, so the tracker shows one progress rollup as tickets close. The milestone is a **rollup, not the spec's home**: the `spec`-labelled issue stays canonical ([ADR-0025](https://github.com/wemuda/launchrail/blob/master/docs/adr/0025-spec-home-follows-tracker.md)), and the milestone description holds only a one-line goal and a link back to that issue.
50
+
51
+ - **Create it** (`launch-spec`, once per spec): a project milestone — `glab api --method POST "projects/:id/milestones" -f title='<feature>' -f description='<one-line goal> — spec: <spec-issue-url>'` (or make it in the GitLab UI). A group-level milestone works too where the feature spans projects.
52
+ - **Put an issue in it**: pass `--milestone '<feature>'` to `glab issue create`, or `glab issue update <n> --milestone '<feature>'` after the fact. `launch-spec` adds the spec issue; `launch-tickets` adds every ticket to the same milestone.
53
+ - **Find the milestone a spec already carries** (`launch-tickets`): `glab issue view <spec> -F json` and read its `milestone`.
54
+ - **Progress** is computed by GitLab from closed-vs-open issues — no upkeep.
55
+
56
+ An issue belongs to at most one milestone, so each ticket rolls up to exactly one spec. Milestones carry no labels and take no part in the implementation loop's frontier — they are purely the human-facing rollup, never a substitute for the `spec` issue.
57
+
47
58
  ## Wayfinding operations
48
59
 
49
60
  Used by `launch-wayfinder`. The **map** is a single issue with **child** issues as tickets.
@@ -44,6 +44,16 @@ Fetch the issue by its identifier (`ENG-123`) including comments.
44
44
 
45
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
46
 
47
+ ## The spec's rollup (milestone)
48
+
49
+ Linear's analog of a milestone is a **project milestone** inside a project — or, if the feature is big enough to stand alone, a **project** of its own. Either way it bundles the spec issue with its tickets so Linear shows one progress rollup for the feature. It is a **rollup, not the spec's home**: the `spec`-labelled issue stays canonical ([ADR-0025](https://github.com/wemuda/launchrail/blob/master/docs/adr/0025-spec-home-follows-tracker.md)).
50
+
51
+ - **Create it** (`launch-spec`): `save_milestone` for a project milestone named for the feature under the configured project (create the project with `save_project` first if the feature warrants its own).
52
+ - **Attach issues**: set each issue's project / project-milestone via `save_issue` — `launch-spec` for the spec issue, `launch-tickets` for every ticket.
53
+ - **Find the rollup a spec already carries** (`launch-tickets`): read the spec issue's project milestone via `get_issue`.
54
+
55
+ Linear computes the rollup's progress from its issues' states. If no project is configured (the field above is blank), skip the milestone — a bare Linear issue has nowhere to hang one.
56
+
47
57
  ## Wayfinding operations
48
58
 
49
59
  Used by `launch-wayfinder`. The **map** is a single issue with **child** issues as tickets.
@@ -15,6 +15,7 @@ Issues and specs for this repo live as markdown files in `.scratch/`.
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
18
+ - No milestones: the `.scratch/<feature-slug>/` directory is itself the feature's bundle (spec at `docs/specs/<feature-slug>.md`, tickets alongside), and progress is just how many of its issue files are closed — there is nothing to create when a skill mentions the spec's milestone
18
19
 
19
20
  ## Labels
20
21
 
@@ -11,7 +11,7 @@ export const meta = {
11
11
  name: 'ralph',
12
12
  description: 'Autonomous Ralph loop: implement ready tickets with fresh-context subagents, verification-gated',
13
13
  whenToUse:
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
+ '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). In a session pinned to a designated working branch (hosted sessions), the front door passes that branch as the target (ADR-0028). 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.',
15
15
  phases: [
16
16
  { title: 'Preflight', detail: 'read project config, resolve the integration target, run the verification gate' },
17
17
  { title: 'Graph', detail: 'list ready tickets and their blocking edges, verbatim' },
@@ -61,7 +61,8 @@ const POLICY = {
61
61
  // Integration target. The front door consolidates by DEFAULT (ADR-0026): it resolves a
62
62
  // scope-native branch name and passes it here, so the campaign collects on that branch and
63
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
64
+ // target -> default. A session pinned to a designated working branch passes that
65
+ // branch here (ADR-0028) — the pin re-targets the run, it never changes the engine. '' is the explicit trunk opt-in: each ticket PR merged straight into
65
66
  // the default branch; a bare launch with no target is therefore trunk mode — non-default.
66
67
  target: A.target ?? '',
67
68
  // Canary: hold width at 1 until the run's first verified merge proves the plumbing
@@ -18,7 +18,7 @@ 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 PRs collect onto one integration branch, the default branch stays untouched, and the run ends by *offering* one release PR `<target> → <default>` for the user to review and merge — 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 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. 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 PRs collect onto one integration branch, the default branch stays untouched, and the run ends by *offering* one release PR `<target> → <default>` for the user to review and merge — 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 and their PRs, every one landing on the target — 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
 
@@ -30,11 +30,11 @@ If the loop's materials are missing — `modules.ralph` off in the manifest, or
30
30
 
31
31
  **The frontier (or any multi-ticket scope):** the engine is the `ralph` workflow (`.claude/workflows/ralph.js`) — launch it with the resolved scope and target as JSON args, e.g. `{ only: [14, 15, 19], max: 5, target: 'spec/2-checkout' }` (`canary: true` on a project's first run), then supervise it per the `launch-ralph` skill, which owns the policies (width, attempts, cap, deferrals, the merge gate, remote-verified merges) and the supervisor's contract. Orchestrating dispatches by hand under that skill instead is the exception, chosen out loud in the echo: the user asked to watch each dispatch, the Workflow tool is unavailable here, or the run is a targeted intervention (one parked ticket). One engine, one shape — a session that invents its own fan-out is not running the loop.
32
32
 
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 consolidation branch only if the user names one). One deliberate divergence: you are the session, not a subagent, so you also run the merge gate yourself — waiting on CI here is fine:
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). One deliberate divergence: you are the session, not a subagent, so you also run the 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
36
  2. Read the ticket and everything it links (spec sections, ADRs, journeys), plus `AGENTS.md`/`CLAUDE.md`.
37
- 3. Label the ticket `ralph:building`; branch `ralph/<n>-<short-slug>` from a fresh sync of the base (the default branch, unless the user named a consolidation branch).
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 the **`launch-ralph-implement`** contract — TDD, the `verify` gate, browser smoke for user-facing changes, self-review, commit conventions. Name the skill; don't paraphrase it.
39
39
  5. Pre-PR sync: merge the latest base; resolve conflicts with `launch-resolving-merge-conflicts`; re-run the gate if anything changed.
40
40
  6. Open a PR titled from the ticket with `Closes #<n>`; adopt an existing `ralph/<n>-*` branch or PR rather than opening a second.
@@ -13,11 +13,11 @@ You are the orchestrator. **You do not write code. You do not read diffs. You do
13
13
 
14
14
  One agent implementing a whole backlog in a single session degrades — context fills with diffs and half-remembered state, and quality drops with every ticket. This loop inverts that: every ticket gets a fresh-context implementer subagent that owns the build through an open PR, the loop's merge gate lands it, and nothing anyone reports is trusted until the remote confirms it.
15
15
 
16
- This skill is the loop's contract and its supervisor. The loop itself runs as the deterministic `ralph` workflow (`.claude/workflows/ralph.js`, installed by init; `launchrail sync` restores it) — **the workflow is the engine for every multi-ticket run**, launched with the resolved scope and integration target as JSON args and then supervised per this skill; its script state cannot be compacted away. Orchestrating dispatches from this session instead is the exception, and it is chosen out loud — name the engine and why before anything dispatches: the user asked to watch each dispatch, the Workflow tool is unavailable in this environment, or this is a targeted intervention (one parked ticket, re-run watchably). A hand-rolled fan-out that is neither is not the loop. The two forms share one policy block: change a policy here, change it in the workflow too (ADR-0005, field-revised by ADR-0010, ADR-0022, and ADR-0026).
16
+ This skill is the loop's contract and its supervisor. The loop itself runs as the deterministic `ralph` workflow (`.claude/workflows/ralph.js`, installed by init; `launchrail sync` restores it) — **the workflow is the engine for every multi-ticket run**, launched with the resolved scope and integration target as JSON args and then supervised per this skill; its script state cannot be compacted away. Orchestrating dispatches from this session instead is the exception, and it is chosen out loud — name the engine and why before anything dispatches: the user asked to watch each dispatch, the Workflow tool is unavailable in this environment, or this is a targeted intervention (one parked ticket, re-run watchably). A hand-rolled fan-out that is neither is not the loop. And the exception swaps only who orchestrates, never the shape: fresh context per ticket, per-ticket PRs into the target, and the loop-owned gate survive every engine. An environment rule about branches or PRs re-targets the run (see Integration target) — it never selects this exception and never licenses sequential in-session implementation. The two forms share one policy block: change a policy here, change it in the workflow too (ADR-0005, field-revised by ADR-0010, ADR-0022, and ADR-0026).
17
17
 
18
18
  ## Policies
19
19
 
20
- - **Integration target: declared, singular, restated.** Every run merges its per-ticket PRs into exactly one base, named before anything dispatches and again in the close-out. **Consolidation** (the default, ADR-0026): one integration branch (e.g. `spec/44-mvp`) collects the whole campaign and the default branch is never touched; the front door names it scope-native — the scope's own spec/epic/slice name when it maps to one, else a generated `launch/*` fallback — and passes it as the `target` arg. The run ends by *offering* one release PR `<target> → <default>` — opened only when the user says so. **Trunk** (the explicit opt-in): the repository's default branch — each verified merge is immediately on mainline, and when the run ends there is nothing left to integrate; select it only when the user asks for per-ticket merges to the default branch, and never when the environment forbids pushing there. A named target missing from the remote is created from the default branch's tip before preflight verifies it; a missing *default* branch stays a refusal. In consolidation mode `Closes #n` never auto-fires (auto-close only triggers from the default branch), so the explicit post-merge close is load-bearing, not belt-and-suspenders.
20
+ - **Integration target: declared, singular, restated.** Every run merges its per-ticket PRs into exactly one base, named before anything dispatches and again in the close-out. **Consolidation** (the default, ADR-0026): one integration branch (e.g. `spec/44-mvp`) collects the whole campaign and the default branch is never touched; the front door names it scope-native — the session's designated working branch when the environment pinned one at start, else the scope's own spec/epic/slice name when it maps to one, else a generated `launch/*` fallback — and passes it as the `target` arg. A pinned-branch session (ADR-0028) changes only that name: the user's start authorizes the loop's mechanics, per-ticket `ralph/*` branches and PRs included, and the pin's real demands — default branch untouched, release PR offered not opened — are exactly what consolidation already does. The run ends by *offering* one release PR `<target> → <default>` — opened only when the user says so. **Trunk** (the explicit opt-in): the repository's default branch — each verified merge is immediately on mainline, and when the run ends there is nothing left to integrate; select it only when the user asks for per-ticket merges to the default branch, and never when the environment forbids pushing there. A named target missing from the remote is created from the default branch's tip before preflight verifies it; a missing *default* branch stays a refusal. In consolidation mode `Closes #n` never auto-fires (auto-close only triggers from the default branch), so the explicit post-merge close is load-bearing, not belt-and-suspenders.
21
21
  - **Width: 3** implementers at once. Width multiplies conflict rate and shared-machine load, not just throughput — use 1 until a run has landed tickets cleanly on this project (the workflow's `canary: true` encodes exactly that). Cut a batch below width when its tickets would obviously collide (same module, same files); when in doubt, narrow. Tickets that add DB migrations are a known collision: two parallel implementers both claim the next migration number — serialize them, or expect the second to renumber at pre-PR sync.
22
22
  - **Cap: none** by default. The user may bound a run ("the next 5"): stop once that many merges have been *verified*, keeping every batch within the remainder so the run cannot overshoot. Failed and deferred dispatches never consume the cap — their slots go to other tickets. Hitting the cap ends the run cleanly: the rest of the frontier stays ready (reported, never parked), and close-out runs as usual.
23
23
  - **Attempts: 2** — retry a failed ticket once with a fresh context, then park it.
@@ -21,6 +21,8 @@ This skill takes the current conversation context and codebase understanding and
21
21
  - **A real tracker (GitHub, GitLab, Linear)** → publish the spec as an issue labeled **`spec`**. That issue is stage 7's canonical artifact — do not also commit a `docs/specs/` file. Never label it `ready-for-agent`: that label marks implementable tickets, the implementation loop computes its frontier from it, and it cannot tell prose from work.
22
22
  - **Local mode (`local`, or no tracker)** → commit the spec under `docs/specs/` (`<feature-slug>.md`). There is no external store, so the committed file is the canonical artifact. It is project-owned.
23
23
 
24
+ On a real tracker, also bundle the spec under a **milestone** named for the feature: create the milestone, put the spec issue in it, and set its description to a one-line goal plus a link back to the spec issue. That milestone is the rollup `launch-tickets` hangs every ticket on, so the whole feature reads as one progress bar — a *view*, not the spec's home; the `spec`-labelled issue stays canonical. See `docs/agents/issue-tracker.md` for the exact per-tracker commands; local mode has no milestone (the feature's files are its bundle).
25
+
24
26
  The spec then flows on: design validation (stage 8) revises it in place, and `launch-tickets` (stage 9) breaks it into tickets that reference it.
25
27
 
26
28
  <spec-template>
@@ -62,7 +62,7 @@ Iterate until the user approves the breakdown.
62
62
  Publish the approved tickets. **How** depends on the tracker configured in `docs/agents/issue-tracker.md` — the tickets are the same either way, only the shape of the blocking edges changes:
63
63
 
64
64
  - **Local files** → write one file per ticket under `.scratch/<feature-slug>/issues/<NN>-<slug>.md`, numbered from `01` in dependency order (blockers first). Each file's "Blocked by" lists the numbers/titles it depends on. Use the per-ticket file template below — one ticket per file, never a single combined file.
65
- - **A real issue tracker (GitHub, GitLab, Linear, …)** → publish one issue per ticket in dependency order (blockers first) so each ticket's edges reference real identifiers. Wire every blocking edge and any parent as the tracker's **native relationship** — GitHub/GitLab issue dependencies and sub-issues, Linear's blocked-by and sub-issue relations — the canonical, UI-visible form the implementation loop's frontier reads. The body's `Parent` / `Blocked by` lines then only mirror it, becoming the gate itself only on a tracker that lacks the native relationship. When the spec is itself a `spec`-labeled issue on the tracker ([ADR-0025](https://github.com/wemuda/launchrail/blob/master/docs/adr/0025-spec-home-follows-tracker.md)), it **is** the parent — link each ticket to it through that same native sub-issue relation. `docs/agents/issue-tracker.md` carries the exact per-tracker API. Apply the **`ready-for-agent`** label unless instructed otherwise — the tickets are agent-grabbable by construction. Only tickets wear that label: the spec issue keeps its `spec` label, or the loop will dispatch the document as work.
65
+ - **A real issue tracker (GitHub, GitLab, Linear, …)** → publish one issue per ticket in dependency order (blockers first) so each ticket's edges reference real identifiers. Wire every blocking edge and any parent as the tracker's **native relationship** — GitHub/GitLab issue dependencies and sub-issues, Linear's blocked-by and sub-issue relations — the canonical, UI-visible form the implementation loop's frontier reads. The body's `Parent` / `Blocked by` lines then only mirror it, becoming the gate itself only on a tracker that lacks the native relationship. When the spec is itself a `spec`-labeled issue on the tracker ([ADR-0025](https://github.com/wemuda/launchrail/blob/master/docs/adr/0025-spec-home-follows-tracker.md)), it **is** the parent — link each ticket to it through that same native sub-issue relation. `docs/agents/issue-tracker.md` carries the exact per-tracker API. Apply the **`ready-for-agent`** label unless instructed otherwise — the tickets are agent-grabbable by construction. Only tickets wear that label: the spec issue keeps its `spec` label, or the loop will dispatch the document as work. Assign every ticket to the **same milestone the spec issue carries** — read it off the spec issue (`docs/agents/issue-tracker.md` has the per-tracker command) — so the feature rolls up as one progress bar; a tracker without milestones (local) has nothing to assign.
66
66
 
67
67
  Work the **frontier**: any ticket whose blockers are all done. For a purely linear chain that means top to bottom.
68
68
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wemuda/launchrail",
3
- "version": "1.10.1",
3
+ "version": "1.12.0",
4
4
  "description": "Launchrail — initialize, inspect, update, and validate repositories using the Launchrail development system.",
5
5
  "license": "MIT",
6
6
  "type": "module",