mirror of https://github.com/garrytan/gstack.git
feat(land): enqueue-and-return default + first-time trunk.io onboarding
On a merge queue, /land now enqueues the PR and returns by default, so you can
/land a stack of ready PRs and walk away while the queue lands them. --watch
opts into the blocking poll (and is what /land-and-deploy uses, since it deploys
the result). No-queue repos still merge synchronously.
Also: /land explains in plain English what a merge queue is and what it'll do
before submitting (teacher mode on first encounter), and when no queue is
configured it offers to set trunk.io up and hand-holds the whole flow. The
onboarding lives in one shared {{MERGE_QUEUE_SETUP}} resolver included by both
/land and /setup-deploy (DRY).
Atomic .tmpl + gen:skill-docs regen. The postfail-ordering test is updated in
the same commit because 4.3 was renamed (Wait -> enqueue/watch); it now matches
the section number so the rename stays bisect-clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f05e61a0bf
commit
9e49d4f812
|
|
@ -1126,7 +1126,14 @@ Continue to Step 2.
|
|||
|
||||
The entire "land" half — pre-flight, CI wait, VERSION-drift check, the pre-merge
|
||||
readiness gate, and the actual merge through the right regime (none / GitHub native
|
||||
merge queue / trunk.io merge queue) — is owned by the `/land` skill. Run it now:
|
||||
merge queue / trunk.io merge queue) — is owned by the `/land` skill. Run it now.
|
||||
|
||||
**Run `/land` as if invoked with `--watch`.** `/land`'s default for a queue regime is
|
||||
enqueue-and-return (hand the PR to the queue and come back) — but the deploy and revert
|
||||
steps below need the *completed* merge and its SHA, so here you MUST block until the PR
|
||||
actually lands. Take `/land`'s `--watch` branch at its Step 4.3 (`gstack-merge wait`, then
|
||||
Step 5 `write-state`), not the enqueue-and-return branch. If the PR ejects or times out in
|
||||
the queue, `/land` STOPs and so do you — there is nothing to deploy.
|
||||
|
||||
Read the `/land` skill file at `~/.claude/skills/gstack/land/SKILL.md` using the Read tool.
|
||||
|
||||
|
|
|
|||
|
|
@ -308,7 +308,14 @@ Continue to Step 2.
|
|||
|
||||
The entire "land" half — pre-flight, CI wait, VERSION-drift check, the pre-merge
|
||||
readiness gate, and the actual merge through the right regime (none / GitHub native
|
||||
merge queue / trunk.io merge queue) — is owned by the `/land` skill. Run it now:
|
||||
merge queue / trunk.io merge queue) — is owned by the `/land` skill. Run it now.
|
||||
|
||||
**Run `/land` as if invoked with `--watch`.** `/land`'s default for a queue regime is
|
||||
enqueue-and-return (hand the PR to the queue and come back) — but the deploy and revert
|
||||
steps below need the *completed* merge and its SHA, so here you MUST block until the PR
|
||||
actually lands. Take `/land`'s `--watch` branch at its Step 4.3 (`gstack-merge wait`, then
|
||||
Step 5 `write-state`), not the enqueue-and-return branch. If the PR ejects or times out in
|
||||
the queue, `/land` STOPs and so do you — there is nothing to deploy.
|
||||
|
||||
{{INVOKE_SKILL:land}}
|
||||
|
||||
|
|
|
|||
197
land/SKILL.md
197
land/SKILL.md
|
|
@ -793,6 +793,9 @@ When the user types `/land`, run this skill.
|
|||
- `/land` — auto-detect the PR from the current branch
|
||||
- `/land #123` — land a specific PR number
|
||||
- `/land --fast` — skip the soft-warning confirmation when there are no blockers. `--fast` NEVER skips a real blocker (failing CI, merge conflict, failing free tests, an unconfirmed merge SHA). It only spares you the "warnings present, proceed?" prompt when everything that matters is green.
|
||||
- `/land --watch` — for a **queue** regime (trunk / GitHub native), block and watch until the PR actually lands, instead of the default **enqueue-and-return**. Use it when you want to sit and confirm this one PR landed. (Combine freely, e.g. `/land #123 --fast --watch`.)
|
||||
|
||||
**Default for a merge queue is enqueue-and-return.** If the repo uses a queue, `/land` hands the PR to the queue, tells you where to watch, and returns — so you can `/land` a whole stack of ready PRs and walk away while the queue lands them. `--watch` opts into blocking. A no-queue repo always merges synchronously (there's nothing to queue).
|
||||
|
||||
## Non-interactive philosophy — with one critical gate
|
||||
|
||||
|
|
@ -814,8 +817,8 @@ This is a **mostly automated** workflow. The user said `/land`, which means DO I
|
|||
## Voice & Tone
|
||||
- **Narrate what's happening now.** "Checking CI status..." not silence.
|
||||
- **Explain why before a gate.** "A merge to main can't be undone without a revert, so I check X first."
|
||||
- **Be specific.** "Your repo uses the trunk.io merge queue — I'll enqueue and watch it" not "merging."
|
||||
- **First run = teacher mode**; subsequent runs = brief status updates.
|
||||
- **Be specific.** "Your repo uses the trunk.io merge queue — I'll enqueue this PR and the queue will land it" not "merging."
|
||||
- **First run = teacher mode** (explain what a merge queue is and what enqueue-and-return means before doing it); subsequent runs = brief status updates.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -1081,10 +1084,137 @@ Resolution order (platform-agnostic rule — the project owns its config, gstack
|
|||
It returns `{"regime":"none|github|trunk","source":"...","base":"..."}`. Detection uses the queue's own GitHub status check (`Trunk Merge Queue (<base>)` → trunk), branch-protection merge queue (→ github), and `.trunk/trunk.yaml` `merge:` as a secondary signal. A bare `.trunk/` directory is NOT treated as trunk (the `trunk check` linter uses the same dir).
|
||||
3. **Ask once, then persist** — if there is no config AND detection returns `none` but the user expected a queue (or detection is ambiguous), ask via AskUserQuestion which regime to use, then write a `## Merge Configuration` section to CLAUDE.md so we never ask again. (You can also point them at `/setup-deploy`, which writes this section.)
|
||||
|
||||
Tell the user which regime you'll use and why: e.g. "Your repo uses the trunk.io merge queue (detected from the `Trunk Merge Queue (main)` check). I'll enqueue the PR and watch the queue."
|
||||
Tell the user which regime you'll use and why: e.g. "Your repo uses the trunk.io merge queue (detected from the `Trunk Merge Queue (main)` check)."
|
||||
|
||||
Record the start timestamp.
|
||||
|
||||
### 4.1a: Explain what's about to happen (queue regimes)
|
||||
|
||||
If the regime is `github` or `trunk`, **before submitting**, tell the user plainly what a
|
||||
merge queue is and what `/land` will do. Full version on the first encounter for this repo
|
||||
(no `## Merge Configuration` was present), one line on repeats. Gloss "merge queue,"
|
||||
"enqueue," and "optimistic merge" on first use.
|
||||
|
||||
First-encounter script (adapt to the detected regime):
|
||||
|
||||
> "Heads up on how this lands. Your repo uses a **merge queue** (a system that merges
|
||||
> PRs for you instead of you clicking merge). So I won't merge right now — I'll **enqueue**
|
||||
> this PR (hand it to the queue) and return. The queue tests it and lands it on `<base>`
|
||||
> on its own, in parallel with other queued PRs, and **optimistically** (a later PR that
|
||||
> already contains this change can rescue it from a flaky test). The point: you can run
|
||||
> `/land` on a whole stack of ready PRs and walk away — they'll all make it onto `<base>`
|
||||
> without you babysitting. I'll tell you where to watch. (Want me to block and watch this
|
||||
> one land instead? Re-run with `/land --watch`.)"
|
||||
|
||||
Repeat-encounter: "Enqueuing to the {trunk/GitHub} queue — it'll land on `<base>`. (`--watch` to block.)"
|
||||
|
||||
### 4.1b: Offer the merge queue (no-queue repos, first time)
|
||||
|
||||
If the regime resolved to `none` AND there was no `## Merge Configuration` (i.e. we did
|
||||
not detect a queue and the user never configured one), surface the option once — don't
|
||||
force it. Use AskUserQuestion:
|
||||
|
||||
- **Re-ground:** "This repo merges directly (no merge queue), so I'll squash-merge this PR
|
||||
now. If you regularly have several PRs ready at once, trunk.io's merge queue can land
|
||||
them all in parallel without you merging each one by hand and waiting. Want me to set it
|
||||
up? It's a one-time setup and I'll walk you through every step."
|
||||
- **RECOMMENDATION:** Choose A if you often juggle multiple ready PRs; choose B to just
|
||||
merge this one now.
|
||||
- A) Walk me through setting up the trunk.io merge queue first (Completeness: 10/10)
|
||||
- B) Just merge this PR directly now — maybe later (Completeness: 7/10)
|
||||
|
||||
**If A:** Run the hand-held onboarding below, then re-resolve the regime (it should now be
|
||||
`trunk`) and continue. **If B:** continue with the `none` path. Either way, do not re-ask on
|
||||
later runs (the choice, or the written `## Merge Configuration`, settles it).
|
||||
|
||||
### Set up a merge queue with trunk.io (first-time, hand-held)
|
||||
|
||||
**What a merge queue is, in plain English.** Normally you merge one PR, wait for
|
||||
it to land, merge the next, wait again — babysitting a line of PRs into the base
|
||||
branch one at a time. A **merge queue** flips that: you *enqueue* each ready PR
|
||||
and walk away. Trunk tests them (in parallel, and **optimistically** — a later PR
|
||||
that already contains an earlier change can rescue it from a flaky failure) and
|
||||
**lands them on the base branch for you**, in a safe order. You queue ten PRs in
|
||||
a row, close your laptop, and they all make it onto the base branch without you.
|
||||
|
||||
That is exactly the workflow this unlocks: `/land` on each PR, then go do
|
||||
something else.
|
||||
|
||||
**Before you start:** this needs a trunk.io account (the free tier covers small
|
||||
teams) and admin access to the GitHub repo. It's a one-time setup. I'll walk each
|
||||
step and explain *why*, and verify what I can with `gh`.
|
||||
|
||||
**Step 1 — Create / sign in to trunk.io.**
|
||||
Open https://app.trunk.io and sign in with GitHub. *(Why: the queue config and
|
||||
dashboard live in Trunk's web app, not in your repo — there's no `trunk.yaml`
|
||||
merge section to commit.)*
|
||||
|
||||
**Step 2 — Install the Trunk GitHub App on this repo.**
|
||||
In app.trunk.io → **Merge Queue** → **Create New Queue** → install the GitHub
|
||||
App, select this repo, approve permissions. *(Why: the App is what lets the
|
||||
`trunk-io` bot test on throwaway branches and push the final merge. Mandatory —
|
||||
nothing works without it.)*
|
||||
Verify the App can see the repo:
|
||||
```bash
|
||||
gh api "/repos/<owner>/<repo>/installation" --jq '.app_slug' 2>/dev/null || echo "App not detected yet"
|
||||
```
|
||||
|
||||
**Step 3 — Create a queue for this repo + base branch.**
|
||||
In the same flow, pick this repo and target branch `<base>`, click **Create
|
||||
Queue**. *(Why: a queue is scoped to one branch — you're queuing merges into
|
||||
`<base>`.)*
|
||||
|
||||
**Step 4 — Adjust branch protection (3 changes).**
|
||||
In GitHub → Settings → Branches → the `<base>` rule:
|
||||
- **Allow the `trunk-io` bot to push to the protected branch.** *(Why: Trunk's
|
||||
bot performs the actual merge; without push rights it can't land anything.)*
|
||||
- **Disable "Require branches to be up to date before merging."** *(Why: Trunk
|
||||
tests each PR against the others in the queue, so GitHub's own up-to-date gate
|
||||
would fight it.)*
|
||||
- **Exclude `trunk-merge/*` and `trunk-temp/*` from protection.** *(Why: those
|
||||
are the throwaway branches Trunk tests on; protecting them blocks testing.)*
|
||||
|
||||
**Step 5 — Turn on the optimizations that make "queue many, walk away" real.**
|
||||
In app.trunk.io → your repo → Merge Queue → Settings, enable:
|
||||
- **Optimistic Merge Queue** + **Pending Failure Depth ≥ 1** — keeps testing
|
||||
later PRs while an earlier one is in "pending failure," and auto-recovers when a
|
||||
later PR proves the failure was a flake. *(Why: one flaky PR doesn't stall the
|
||||
whole line.)*
|
||||
- **Parallel** — non-overlapping PRs test in independent lanes at the same time.
|
||||
*(Why: throughput; ten unrelated PRs don't go one-at-a-time.)*
|
||||
- **Batching** — lands compatible PRs together with auto-bisection on failure.
|
||||
*(Why: fewer CI runs, and a bad PR doesn't eject the whole batch.)*
|
||||
- **Merge Method** — pick Squash / Merge Commit / Rebase to match your repo. *(Why:
|
||||
it controls what the landed commit looks like; `/land` handles all three.)*
|
||||
|
||||
**Step 6 — Pick how PRs get enqueued.**
|
||||
The simplest works immediately: commenting **`/trunk merge`** on a PR. `/land`
|
||||
uses that by default — zero extra auth, because the GitHub App is already
|
||||
installed. *(Optional upgrades: set an "enqueue by label" name in the web UI, run
|
||||
`trunk login` to use the `trunk` CLI, or set `$TRUNK_API_TOKEN` for the REST
|
||||
API — `/land` will prefer those when present.)*
|
||||
|
||||
**Step 7 — Persist the choice so I never ask again.**
|
||||
I'll write `Merge queue: trunk` into a `## Merge Configuration` section of
|
||||
CLAUDE.md. *(Why: `/land` reads it and skips detection from then on.)*
|
||||
|
||||
**Step 8 — Verify end-to-end.**
|
||||
Open any test PR and run `/land`. You should see a **`Trunk Merge Queue
|
||||
(<base>)`** check appear, move Queued → Testing → Merged, and the PR land on
|
||||
`<base>` without you touching GitHub:
|
||||
```bash
|
||||
gh pr checks <test-pr> --json name,state | grep -i "Trunk Merge Queue" || echo "no queue check yet — recheck Steps 2-4"
|
||||
```
|
||||
|
||||
Full docs: https://docs.trunk.io/merge-queue/getting-started
|
||||
|
||||
Once this is done, the payoff: queue up all your ready PRs with `/land`, walk
|
||||
away, and trunk lands them on `<base>` for you.
|
||||
|
||||
When the onboarding completes, write `Merge queue: trunk` into a `## Merge Configuration`
|
||||
section of CLAUDE.md (create the section if absent) so `/land` never has to ask again, then
|
||||
re-run `gstack-merge detect` to confirm, and continue with the `trunk` regime.
|
||||
|
||||
### 4.2: Submit
|
||||
|
||||
```bash
|
||||
|
|
@ -1143,25 +1273,46 @@ gh pr view --json autoMergeRequest -q .autoMergeRequest
|
|||
|
||||
**Hard rule: never call `gh pr merge` a second time** after a non-zero exit. Server state is authoritative.
|
||||
|
||||
### 4.3: Wait for it to land
|
||||
### 4.3: Enqueue-and-return (default) or watch until landed
|
||||
|
||||
```bash
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)"
|
||||
```
|
||||
|
||||
**Regime `none`** — the direct squash in 4.2 already merged synchronously. Go straight to Step 5 (write-state confirms the SHA).
|
||||
|
||||
**Regime `github` / `trunk`, DEFAULT (no `--watch`)** — confirm the queue actually picked the PR up, then return; the queue lands it:
|
||||
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-merge confirm-enqueue --regime <regime> --pr <NNN> --base <base> --slug "$SLUG"
|
||||
```
|
||||
|
||||
- **exit 0 / `ENQUEUED=...`** — the PR is in the queue and will land on `<base>` on its own. Do NOT run Step 5 (there is no merge SHA yet — `confirm-enqueue` wrote a lightweight `last-enqueue.json`). Go to Step 6's **enqueue summary**, and surface the `WATCH_CHECK` / `WATCH_DASHBOARD` lines from the output so the user knows where to look.
|
||||
- **`TRUNK_ENQUEUE_TIMEOUT`** — **STOP.** "I posted `/trunk merge` but Trunk never picked it up. Confirm the Trunk GitHub App is installed on this repo and 'GitHub commands' is enabled (run `/land` on a no-queue repo, or `/setup-deploy`, for the setup walkthrough), then run `/land` again."
|
||||
- **`ENQUEUE_UNCONFIRMED`** (github) — **STOP.** "GitHub auto-merge didn't enable on the PR. Check the repo's merge-queue / auto-merge settings, then run `/land` again."
|
||||
|
||||
**Regime `github` / `trunk` WITH `--watch`** — block until it actually lands:
|
||||
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-merge wait --regime <regime> --pr <NNN> --base <base>
|
||||
```
|
||||
|
||||
The helper polls the uniform landing signal (`gh pr view state` + the merge-queue status check) and prints progress. For the trunk regime it first confirms the PR was actually picked up (the `Trunk Merge Queue (<base>)` check appears) — a posted `/trunk merge` comment is silently inert if the GitHub App isn't installed.
|
||||
|
||||
Handle the exit:
|
||||
- **exit 0 / `LAND_STATUS=landed`** — it merged. Continue to Step 5.
|
||||
The helper polls the uniform landing signal (`gh pr view state` + the merge-queue status check). For trunk it first confirms pickup (the `Trunk Merge Queue (<base>)` check appears) — a posted `/trunk merge` comment is silently inert if the GitHub App isn't installed. Handle the exit:
|
||||
- **`LAND_STATUS=landed`** — continue to Step 5.
|
||||
- **`LAND_STATUS=ejected`** — the queue rejected the PR (a CI check failed on the merge candidate, or a conflict with another queued PR). **STOP.** "The merge queue ejected this PR: {reason}. Check the queue page — usually a check failed on the merge commit. Fix and run `/land` again."
|
||||
- **`LAND_STATUS=closed`** — **STOP.** "The PR was closed without merging."
|
||||
- **`TRUNK_ENQUEUE_TIMEOUT`** — **STOP.** "I posted `/trunk merge` but Trunk never picked it up. Confirm the Trunk GitHub App is installed on this repo and 'GitHub commands' is enabled, then run `/land` again."
|
||||
- **`TRUNK_ENQUEUE_TIMEOUT`** — **STOP.** (same guidance as above.)
|
||||
- **timeout** — **STOP.** "The merge has been pending for {duration}. Something may be stuck — check the GitHub Actions tab and the merge-queue page."
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Confirm landing and write the handoff
|
||||
|
||||
**Run this step only when the PR actually merged** — i.e. the `none` regime, or a `--watch`
|
||||
run that returned `LAND_STATUS=landed`. In the default enqueue-and-return path you already
|
||||
returned at 4.3 with the PR sitting in the queue (no merge SHA yet), so skip to Step 6's
|
||||
**enqueue summary**.
|
||||
|
||||
A merge isn't done until the commit is on the base branch with a known SHA. This is also the **handoff** the deploy half needs (its `git revert` and deploy-workflow match both need the merge SHA), so `/land` writes it as a file, not just a log line.
|
||||
|
||||
```bash
|
||||
|
|
@ -1184,9 +1335,30 @@ and prints a human echo: `LANDED: pr=#NNN sha=<oid> regime=<regime> base=<branch
|
|||
|
||||
---
|
||||
|
||||
## Step 6: Land summary
|
||||
## Step 6: Summary
|
||||
|
||||
When run standalone, print a short summary (skip the deploy framing — there is none here):
|
||||
### Enqueue summary (default queue path — the PR is in the queue, not yet landed)
|
||||
|
||||
When 4.3 returned `ENQUEUED=...`, print this and stop — the queue does the rest:
|
||||
|
||||
```
|
||||
ENQUEUED
|
||||
════════
|
||||
PR: #<number> — <title>
|
||||
Branch: <head> → <base>
|
||||
Regime: <github / trunk>
|
||||
Status: In the merge queue — it'll land on <base> automatically
|
||||
Watch: <WATCH_CHECK> (and <WATCH_DASHBOARD> for trunk)
|
||||
|
||||
VERDICT: ENQUEUED — no action needed; the queue will land it.
|
||||
```
|
||||
|
||||
Then tell the user, in plain English: "You don't need to wait. Queue up your other ready
|
||||
PRs with `/land` the same way and walk away — the queue lands them all on `<base>`. To
|
||||
block and watch one land instead, run `/land --watch`." (Skip the walk-away pitch if this
|
||||
was invoked by `/land-and-deploy`.)
|
||||
|
||||
### Land summary (none regime, or `--watch` that landed)
|
||||
|
||||
```
|
||||
LAND REPORT
|
||||
|
|
@ -1212,6 +1384,7 @@ Then suggest the natural next step: "Want to deploy and verify this in productio
|
|||
- **Never skip CI.** Failing or pending checks gate the merge.
|
||||
- **Never call `gh pr merge` twice** after a non-zero exit — server state is authoritative (see 4.2). Related: cli/cli#3442, cli/cli#13380.
|
||||
- **Trunk owns the trunk path.** In the trunk regime, never run `gh pr merge` and never pass `--delete-branch`.
|
||||
- **Landing means a SHA on the base branch.** Don't report success until `write-state` confirms it (Step 5). A null SHA silently kills the deploy half's revert.
|
||||
- **A merge queue means enqueue-and-return, not babysit.** For a queue regime the default is to enqueue and return so the user can `/land` a whole stack and walk away; only `--watch` blocks. Never block-by-default on a queue — it defeats the point of the queue.
|
||||
- **Landing means a SHA on the base branch.** In the `none` path or a `--watch` run, don't report success until `write-state` confirms it (Step 5). A null SHA silently kills the deploy half's revert. (In enqueue-and-return there is intentionally no SHA yet — that's why `/land-and-deploy` always runs `/land --watch`.)
|
||||
- **Detect, don't assume.** Resolve the regime from config → live detection → ask-once-and-persist. The same `gstack-merge detect` is what `/land-and-deploy`'s dry-run uses, so the two never disagree.
|
||||
- **Narrate the journey.** The user should always know what just happened, what's happening now, and what's next.
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ When the user types `/land`, run this skill.
|
|||
- `/land` — auto-detect the PR from the current branch
|
||||
- `/land #123` — land a specific PR number
|
||||
- `/land --fast` — skip the soft-warning confirmation when there are no blockers. `--fast` NEVER skips a real blocker (failing CI, merge conflict, failing free tests, an unconfirmed merge SHA). It only spares you the "warnings present, proceed?" prompt when everything that matters is green.
|
||||
- `/land --watch` — for a **queue** regime (trunk / GitHub native), block and watch until the PR actually lands, instead of the default **enqueue-and-return**. Use it when you want to sit and confirm this one PR landed. (Combine freely, e.g. `/land #123 --fast --watch`.)
|
||||
|
||||
**Default for a merge queue is enqueue-and-return.** If the repo uses a queue, `/land` hands the PR to the queue, tells you where to watch, and returns — so you can `/land` a whole stack of ready PRs and walk away while the queue lands them. `--watch` opts into blocking. A no-queue repo always merges synchronously (there's nothing to queue).
|
||||
|
||||
## Non-interactive philosophy — with one critical gate
|
||||
|
||||
|
|
@ -64,8 +67,8 @@ This is a **mostly automated** workflow. The user said `/land`, which means DO I
|
|||
## Voice & Tone
|
||||
- **Narrate what's happening now.** "Checking CI status..." not silence.
|
||||
- **Explain why before a gate.** "A merge to main can't be undone without a revert, so I check X first."
|
||||
- **Be specific.** "Your repo uses the trunk.io merge queue — I'll enqueue and watch it" not "merging."
|
||||
- **First run = teacher mode**; subsequent runs = brief status updates.
|
||||
- **Be specific.** "Your repo uses the trunk.io merge queue — I'll enqueue this PR and the queue will land it" not "merging."
|
||||
- **First run = teacher mode** (explain what a merge queue is and what enqueue-and-return means before doing it); subsequent runs = brief status updates.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -331,10 +334,55 @@ Resolution order (platform-agnostic rule — the project owns its config, gstack
|
|||
It returns `{"regime":"none|github|trunk","source":"...","base":"..."}`. Detection uses the queue's own GitHub status check (`Trunk Merge Queue (<base>)` → trunk), branch-protection merge queue (→ github), and `.trunk/trunk.yaml` `merge:` as a secondary signal. A bare `.trunk/` directory is NOT treated as trunk (the `trunk check` linter uses the same dir).
|
||||
3. **Ask once, then persist** — if there is no config AND detection returns `none` but the user expected a queue (or detection is ambiguous), ask via AskUserQuestion which regime to use, then write a `## Merge Configuration` section to CLAUDE.md so we never ask again. (You can also point them at `/setup-deploy`, which writes this section.)
|
||||
|
||||
Tell the user which regime you'll use and why: e.g. "Your repo uses the trunk.io merge queue (detected from the `Trunk Merge Queue (main)` check). I'll enqueue the PR and watch the queue."
|
||||
Tell the user which regime you'll use and why: e.g. "Your repo uses the trunk.io merge queue (detected from the `Trunk Merge Queue (main)` check)."
|
||||
|
||||
Record the start timestamp.
|
||||
|
||||
### 4.1a: Explain what's about to happen (queue regimes)
|
||||
|
||||
If the regime is `github` or `trunk`, **before submitting**, tell the user plainly what a
|
||||
merge queue is and what `/land` will do. Full version on the first encounter for this repo
|
||||
(no `## Merge Configuration` was present), one line on repeats. Gloss "merge queue,"
|
||||
"enqueue," and "optimistic merge" on first use.
|
||||
|
||||
First-encounter script (adapt to the detected regime):
|
||||
|
||||
> "Heads up on how this lands. Your repo uses a **merge queue** (a system that merges
|
||||
> PRs for you instead of you clicking merge). So I won't merge right now — I'll **enqueue**
|
||||
> this PR (hand it to the queue) and return. The queue tests it and lands it on `<base>`
|
||||
> on its own, in parallel with other queued PRs, and **optimistically** (a later PR that
|
||||
> already contains this change can rescue it from a flaky test). The point: you can run
|
||||
> `/land` on a whole stack of ready PRs and walk away — they'll all make it onto `<base>`
|
||||
> without you babysitting. I'll tell you where to watch. (Want me to block and watch this
|
||||
> one land instead? Re-run with `/land --watch`.)"
|
||||
|
||||
Repeat-encounter: "Enqueuing to the {trunk/GitHub} queue — it'll land on `<base>`. (`--watch` to block.)"
|
||||
|
||||
### 4.1b: Offer the merge queue (no-queue repos, first time)
|
||||
|
||||
If the regime resolved to `none` AND there was no `## Merge Configuration` (i.e. we did
|
||||
not detect a queue and the user never configured one), surface the option once — don't
|
||||
force it. Use AskUserQuestion:
|
||||
|
||||
- **Re-ground:** "This repo merges directly (no merge queue), so I'll squash-merge this PR
|
||||
now. If you regularly have several PRs ready at once, trunk.io's merge queue can land
|
||||
them all in parallel without you merging each one by hand and waiting. Want me to set it
|
||||
up? It's a one-time setup and I'll walk you through every step."
|
||||
- **RECOMMENDATION:** Choose A if you often juggle multiple ready PRs; choose B to just
|
||||
merge this one now.
|
||||
- A) Walk me through setting up the trunk.io merge queue first (Completeness: 10/10)
|
||||
- B) Just merge this PR directly now — maybe later (Completeness: 7/10)
|
||||
|
||||
**If A:** Run the hand-held onboarding below, then re-resolve the regime (it should now be
|
||||
`trunk`) and continue. **If B:** continue with the `none` path. Either way, do not re-ask on
|
||||
later runs (the choice, or the written `## Merge Configuration`, settles it).
|
||||
|
||||
{{MERGE_QUEUE_SETUP}}
|
||||
|
||||
When the onboarding completes, write `Merge queue: trunk` into a `## Merge Configuration`
|
||||
section of CLAUDE.md (create the section if absent) so `/land` never has to ask again, then
|
||||
re-run `gstack-merge detect` to confirm, and continue with the `trunk` regime.
|
||||
|
||||
### 4.2: Submit
|
||||
|
||||
```bash
|
||||
|
|
@ -393,25 +441,46 @@ gh pr view --json autoMergeRequest -q .autoMergeRequest
|
|||
|
||||
**Hard rule: never call `gh pr merge` a second time** after a non-zero exit. Server state is authoritative.
|
||||
|
||||
### 4.3: Wait for it to land
|
||||
### 4.3: Enqueue-and-return (default) or watch until landed
|
||||
|
||||
```bash
|
||||
{{SLUG_EVAL}}
|
||||
```
|
||||
|
||||
**Regime `none`** — the direct squash in 4.2 already merged synchronously. Go straight to Step 5 (write-state confirms the SHA).
|
||||
|
||||
**Regime `github` / `trunk`, DEFAULT (no `--watch`)** — confirm the queue actually picked the PR up, then return; the queue lands it:
|
||||
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-merge confirm-enqueue --regime <regime> --pr <NNN> --base <base> --slug "$SLUG"
|
||||
```
|
||||
|
||||
- **exit 0 / `ENQUEUED=...`** — the PR is in the queue and will land on `<base>` on its own. Do NOT run Step 5 (there is no merge SHA yet — `confirm-enqueue` wrote a lightweight `last-enqueue.json`). Go to Step 6's **enqueue summary**, and surface the `WATCH_CHECK` / `WATCH_DASHBOARD` lines from the output so the user knows where to look.
|
||||
- **`TRUNK_ENQUEUE_TIMEOUT`** — **STOP.** "I posted `/trunk merge` but Trunk never picked it up. Confirm the Trunk GitHub App is installed on this repo and 'GitHub commands' is enabled (run `/land` on a no-queue repo, or `/setup-deploy`, for the setup walkthrough), then run `/land` again."
|
||||
- **`ENQUEUE_UNCONFIRMED`** (github) — **STOP.** "GitHub auto-merge didn't enable on the PR. Check the repo's merge-queue / auto-merge settings, then run `/land` again."
|
||||
|
||||
**Regime `github` / `trunk` WITH `--watch`** — block until it actually lands:
|
||||
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-merge wait --regime <regime> --pr <NNN> --base <base>
|
||||
```
|
||||
|
||||
The helper polls the uniform landing signal (`gh pr view state` + the merge-queue status check) and prints progress. For the trunk regime it first confirms the PR was actually picked up (the `Trunk Merge Queue (<base>)` check appears) — a posted `/trunk merge` comment is silently inert if the GitHub App isn't installed.
|
||||
|
||||
Handle the exit:
|
||||
- **exit 0 / `LAND_STATUS=landed`** — it merged. Continue to Step 5.
|
||||
The helper polls the uniform landing signal (`gh pr view state` + the merge-queue status check). For trunk it first confirms pickup (the `Trunk Merge Queue (<base>)` check appears) — a posted `/trunk merge` comment is silently inert if the GitHub App isn't installed. Handle the exit:
|
||||
- **`LAND_STATUS=landed`** — continue to Step 5.
|
||||
- **`LAND_STATUS=ejected`** — the queue rejected the PR (a CI check failed on the merge candidate, or a conflict with another queued PR). **STOP.** "The merge queue ejected this PR: {reason}. Check the queue page — usually a check failed on the merge commit. Fix and run `/land` again."
|
||||
- **`LAND_STATUS=closed`** — **STOP.** "The PR was closed without merging."
|
||||
- **`TRUNK_ENQUEUE_TIMEOUT`** — **STOP.** "I posted `/trunk merge` but Trunk never picked it up. Confirm the Trunk GitHub App is installed on this repo and 'GitHub commands' is enabled, then run `/land` again."
|
||||
- **`TRUNK_ENQUEUE_TIMEOUT`** — **STOP.** (same guidance as above.)
|
||||
- **timeout** — **STOP.** "The merge has been pending for {duration}. Something may be stuck — check the GitHub Actions tab and the merge-queue page."
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Confirm landing and write the handoff
|
||||
|
||||
**Run this step only when the PR actually merged** — i.e. the `none` regime, or a `--watch`
|
||||
run that returned `LAND_STATUS=landed`. In the default enqueue-and-return path you already
|
||||
returned at 4.3 with the PR sitting in the queue (no merge SHA yet), so skip to Step 6's
|
||||
**enqueue summary**.
|
||||
|
||||
A merge isn't done until the commit is on the base branch with a known SHA. This is also the **handoff** the deploy half needs (its `git revert` and deploy-workflow match both need the merge SHA), so `/land` writes it as a file, not just a log line.
|
||||
|
||||
```bash
|
||||
|
|
@ -434,9 +503,30 @@ and prints a human echo: `LANDED: pr=#NNN sha=<oid> regime=<regime> base=<branch
|
|||
|
||||
---
|
||||
|
||||
## Step 6: Land summary
|
||||
## Step 6: Summary
|
||||
|
||||
When run standalone, print a short summary (skip the deploy framing — there is none here):
|
||||
### Enqueue summary (default queue path — the PR is in the queue, not yet landed)
|
||||
|
||||
When 4.3 returned `ENQUEUED=...`, print this and stop — the queue does the rest:
|
||||
|
||||
```
|
||||
ENQUEUED
|
||||
════════
|
||||
PR: #<number> — <title>
|
||||
Branch: <head> → <base>
|
||||
Regime: <github / trunk>
|
||||
Status: In the merge queue — it'll land on <base> automatically
|
||||
Watch: <WATCH_CHECK> (and <WATCH_DASHBOARD> for trunk)
|
||||
|
||||
VERDICT: ENQUEUED — no action needed; the queue will land it.
|
||||
```
|
||||
|
||||
Then tell the user, in plain English: "You don't need to wait. Queue up your other ready
|
||||
PRs with `/land` the same way and walk away — the queue lands them all on `<base>`. To
|
||||
block and watch one land instead, run `/land --watch`." (Skip the walk-away pitch if this
|
||||
was invoked by `/land-and-deploy`.)
|
||||
|
||||
### Land summary (none regime, or `--watch` that landed)
|
||||
|
||||
```
|
||||
LAND REPORT
|
||||
|
|
@ -462,6 +552,7 @@ Then suggest the natural next step: "Want to deploy and verify this in productio
|
|||
- **Never skip CI.** Failing or pending checks gate the merge.
|
||||
- **Never call `gh pr merge` twice** after a non-zero exit — server state is authoritative (see 4.2). Related: cli/cli#3442, cli/cli#13380.
|
||||
- **Trunk owns the trunk path.** In the trunk regime, never run `gh pr merge` and never pass `--delete-branch`.
|
||||
- **Landing means a SHA on the base branch.** Don't report success until `write-state` confirms it (Step 5). A null SHA silently kills the deploy half's revert.
|
||||
- **A merge queue means enqueue-and-return, not babysit.** For a queue regime the default is to enqueue and return so the user can `/land` a whole stack and walk away; only `--watch` blocks. Never block-by-default on a queue — it defeats the point of the queue.
|
||||
- **Landing means a SHA on the base branch.** In the `none` path or a `--watch` run, don't report success until `write-state` confirms it (Step 5). A null SHA silently kills the deploy half's revert. (In enqueue-and-return there is intentionally no SHA yet — that's why `/land-and-deploy` always runs `/land --watch`.)
|
||||
- **Detect, don't assume.** Resolve the regime from config → live detection → ask-once-and-persist. The same `gstack-merge detect` is what `/land-and-deploy`'s dry-run uses, so the two never disagree.
|
||||
- **Narrate the journey.** The user should always know what just happened, what's happening now, and what's next.
|
||||
|
|
|
|||
|
|
@ -36,8 +36,10 @@ import { generateMakePdfSetup } from './make-pdf';
|
|||
import { generateTasksSectionEmit, generateTasksSectionAggregate } from './tasks-section';
|
||||
import { SECTION, SECTION_INDEX } from './sections';
|
||||
import { generateRedactTaxonomyTable, generateRedactInvocationBlock } from './redact-doc';
|
||||
import { generateMergeQueueSetup } from './merge-queue-setup';
|
||||
|
||||
export const RESOLVERS: Record<string, ResolverValue> = {
|
||||
MERGE_QUEUE_SETUP: generateMergeQueueSetup,
|
||||
SLUG_EVAL: generateSlugEval,
|
||||
SLUG_SETUP: generateSlugSetup,
|
||||
REDACT_TAXONOMY_TABLE: generateRedactTaxonomyTable,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
import type { TemplateContext } from './types';
|
||||
|
||||
/**
|
||||
* {{MERGE_QUEUE_SETUP}} — the authoritative, teacher-mode trunk.io merge-queue
|
||||
* onboarding. Included by BOTH /setup-deploy (## Merge Configuration) and
|
||||
* /land's first-time branch so the guide lives in exactly one place (DRY) and
|
||||
* /land can hand-hold inline without making the user stop and run another skill.
|
||||
*
|
||||
* Grounded in a full read of docs.trunk.io/merge-queue (2026-05): config is
|
||||
* server-side (app.trunk.io), the GitHub App is mandatory, trunk posts a
|
||||
* "Trunk Merge Queue (<base>)" status check, and the `/trunk merge` PR comment
|
||||
* enqueues with zero extra auth.
|
||||
*/
|
||||
export function generateMergeQueueSetup(_ctx: TemplateContext): string {
|
||||
return `### Set up a merge queue with trunk.io (first-time, hand-held)
|
||||
|
||||
**What a merge queue is, in plain English.** Normally you merge one PR, wait for
|
||||
it to land, merge the next, wait again — babysitting a line of PRs into the base
|
||||
branch one at a time. A **merge queue** flips that: you *enqueue* each ready PR
|
||||
and walk away. Trunk tests them (in parallel, and **optimistically** — a later PR
|
||||
that already contains an earlier change can rescue it from a flaky failure) and
|
||||
**lands them on the base branch for you**, in a safe order. You queue ten PRs in
|
||||
a row, close your laptop, and they all make it onto the base branch without you.
|
||||
|
||||
That is exactly the workflow this unlocks: \`/land\` on each PR, then go do
|
||||
something else.
|
||||
|
||||
**Before you start:** this needs a trunk.io account (the free tier covers small
|
||||
teams) and admin access to the GitHub repo. It's a one-time setup. I'll walk each
|
||||
step and explain *why*, and verify what I can with \`gh\`.
|
||||
|
||||
**Step 1 — Create / sign in to trunk.io.**
|
||||
Open https://app.trunk.io and sign in with GitHub. *(Why: the queue config and
|
||||
dashboard live in Trunk's web app, not in your repo — there's no \`trunk.yaml\`
|
||||
merge section to commit.)*
|
||||
|
||||
**Step 2 — Install the Trunk GitHub App on this repo.**
|
||||
In app.trunk.io → **Merge Queue** → **Create New Queue** → install the GitHub
|
||||
App, select this repo, approve permissions. *(Why: the App is what lets the
|
||||
\`trunk-io\` bot test on throwaway branches and push the final merge. Mandatory —
|
||||
nothing works without it.)*
|
||||
Verify the App can see the repo:
|
||||
\`\`\`bash
|
||||
gh api "/repos/<owner>/<repo>/installation" --jq '.app_slug' 2>/dev/null || echo "App not detected yet"
|
||||
\`\`\`
|
||||
|
||||
**Step 3 — Create a queue for this repo + base branch.**
|
||||
In the same flow, pick this repo and target branch \`<base>\`, click **Create
|
||||
Queue**. *(Why: a queue is scoped to one branch — you're queuing merges into
|
||||
\`<base>\`.)*
|
||||
|
||||
**Step 4 — Adjust branch protection (3 changes).**
|
||||
In GitHub → Settings → Branches → the \`<base>\` rule:
|
||||
- **Allow the \`trunk-io\` bot to push to the protected branch.** *(Why: Trunk's
|
||||
bot performs the actual merge; without push rights it can't land anything.)*
|
||||
- **Disable "Require branches to be up to date before merging."** *(Why: Trunk
|
||||
tests each PR against the others in the queue, so GitHub's own up-to-date gate
|
||||
would fight it.)*
|
||||
- **Exclude \`trunk-merge/*\` and \`trunk-temp/*\` from protection.** *(Why: those
|
||||
are the throwaway branches Trunk tests on; protecting them blocks testing.)*
|
||||
|
||||
**Step 5 — Turn on the optimizations that make "queue many, walk away" real.**
|
||||
In app.trunk.io → your repo → Merge Queue → Settings, enable:
|
||||
- **Optimistic Merge Queue** + **Pending Failure Depth ≥ 1** — keeps testing
|
||||
later PRs while an earlier one is in "pending failure," and auto-recovers when a
|
||||
later PR proves the failure was a flake. *(Why: one flaky PR doesn't stall the
|
||||
whole line.)*
|
||||
- **Parallel** — non-overlapping PRs test in independent lanes at the same time.
|
||||
*(Why: throughput; ten unrelated PRs don't go one-at-a-time.)*
|
||||
- **Batching** — lands compatible PRs together with auto-bisection on failure.
|
||||
*(Why: fewer CI runs, and a bad PR doesn't eject the whole batch.)*
|
||||
- **Merge Method** — pick Squash / Merge Commit / Rebase to match your repo. *(Why:
|
||||
it controls what the landed commit looks like; \`/land\` handles all three.)*
|
||||
|
||||
**Step 6 — Pick how PRs get enqueued.**
|
||||
The simplest works immediately: commenting **\`/trunk merge\`** on a PR. \`/land\`
|
||||
uses that by default — zero extra auth, because the GitHub App is already
|
||||
installed. *(Optional upgrades: set an "enqueue by label" name in the web UI, run
|
||||
\`trunk login\` to use the \`trunk\` CLI, or set \`$TRUNK_API_TOKEN\` for the REST
|
||||
API — \`/land\` will prefer those when present.)*
|
||||
|
||||
**Step 7 — Persist the choice so I never ask again.**
|
||||
I'll write \`Merge queue: trunk\` into a \`## Merge Configuration\` section of
|
||||
CLAUDE.md. *(Why: \`/land\` reads it and skips detection from then on.)*
|
||||
|
||||
**Step 8 — Verify end-to-end.**
|
||||
Open any test PR and run \`/land\`. You should see a **\`Trunk Merge Queue
|
||||
(<base>)\`** check appear, move Queued → Testing → Merged, and the PR land on
|
||||
\`<base>\` without you touching GitHub:
|
||||
\`\`\`bash
|
||||
gh pr checks <test-pr> --json name,state | grep -i "Trunk Merge Queue" || echo "no queue check yet — recheck Steps 2-4"
|
||||
\`\`\`
|
||||
|
||||
Full docs: https://docs.trunk.io/merge-queue/getting-started
|
||||
|
||||
Once this is done, the payoff: queue up all your ready PRs with \`/land\`, walk
|
||||
away, and trunk lands them on \`<base>\` for you.`;
|
||||
}
|
||||
|
|
@ -917,6 +917,95 @@ section if it exists, or append it. Keep it separate from Deploy Configuration s
|
|||
`/land` reads the `Merge queue:` line to pick its submit path. If you skip this section,
|
||||
`/land` falls back to live detection and asks once.
|
||||
|
||||
**If the user chose `trunk` and the queue isn't set up yet** (no `Trunk Merge Queue (<base>)`
|
||||
check on recent PRs — check with `gh pr checks` or `gh api`), walk them through the
|
||||
one-time onboarding below before writing `Merge queue: trunk`. If they chose `none` or
|
||||
`github`, skip the onboarding.
|
||||
|
||||
### Set up a merge queue with trunk.io (first-time, hand-held)
|
||||
|
||||
**What a merge queue is, in plain English.** Normally you merge one PR, wait for
|
||||
it to land, merge the next, wait again — babysitting a line of PRs into the base
|
||||
branch one at a time. A **merge queue** flips that: you *enqueue* each ready PR
|
||||
and walk away. Trunk tests them (in parallel, and **optimistically** — a later PR
|
||||
that already contains an earlier change can rescue it from a flaky failure) and
|
||||
**lands them on the base branch for you**, in a safe order. You queue ten PRs in
|
||||
a row, close your laptop, and they all make it onto the base branch without you.
|
||||
|
||||
That is exactly the workflow this unlocks: `/land` on each PR, then go do
|
||||
something else.
|
||||
|
||||
**Before you start:** this needs a trunk.io account (the free tier covers small
|
||||
teams) and admin access to the GitHub repo. It's a one-time setup. I'll walk each
|
||||
step and explain *why*, and verify what I can with `gh`.
|
||||
|
||||
**Step 1 — Create / sign in to trunk.io.**
|
||||
Open https://app.trunk.io and sign in with GitHub. *(Why: the queue config and
|
||||
dashboard live in Trunk's web app, not in your repo — there's no `trunk.yaml`
|
||||
merge section to commit.)*
|
||||
|
||||
**Step 2 — Install the Trunk GitHub App on this repo.**
|
||||
In app.trunk.io → **Merge Queue** → **Create New Queue** → install the GitHub
|
||||
App, select this repo, approve permissions. *(Why: the App is what lets the
|
||||
`trunk-io` bot test on throwaway branches and push the final merge. Mandatory —
|
||||
nothing works without it.)*
|
||||
Verify the App can see the repo:
|
||||
```bash
|
||||
gh api "/repos/<owner>/<repo>/installation" --jq '.app_slug' 2>/dev/null || echo "App not detected yet"
|
||||
```
|
||||
|
||||
**Step 3 — Create a queue for this repo + base branch.**
|
||||
In the same flow, pick this repo and target branch `<base>`, click **Create
|
||||
Queue**. *(Why: a queue is scoped to one branch — you're queuing merges into
|
||||
`<base>`.)*
|
||||
|
||||
**Step 4 — Adjust branch protection (3 changes).**
|
||||
In GitHub → Settings → Branches → the `<base>` rule:
|
||||
- **Allow the `trunk-io` bot to push to the protected branch.** *(Why: Trunk's
|
||||
bot performs the actual merge; without push rights it can't land anything.)*
|
||||
- **Disable "Require branches to be up to date before merging."** *(Why: Trunk
|
||||
tests each PR against the others in the queue, so GitHub's own up-to-date gate
|
||||
would fight it.)*
|
||||
- **Exclude `trunk-merge/*` and `trunk-temp/*` from protection.** *(Why: those
|
||||
are the throwaway branches Trunk tests on; protecting them blocks testing.)*
|
||||
|
||||
**Step 5 — Turn on the optimizations that make "queue many, walk away" real.**
|
||||
In app.trunk.io → your repo → Merge Queue → Settings, enable:
|
||||
- **Optimistic Merge Queue** + **Pending Failure Depth ≥ 1** — keeps testing
|
||||
later PRs while an earlier one is in "pending failure," and auto-recovers when a
|
||||
later PR proves the failure was a flake. *(Why: one flaky PR doesn't stall the
|
||||
whole line.)*
|
||||
- **Parallel** — non-overlapping PRs test in independent lanes at the same time.
|
||||
*(Why: throughput; ten unrelated PRs don't go one-at-a-time.)*
|
||||
- **Batching** — lands compatible PRs together with auto-bisection on failure.
|
||||
*(Why: fewer CI runs, and a bad PR doesn't eject the whole batch.)*
|
||||
- **Merge Method** — pick Squash / Merge Commit / Rebase to match your repo. *(Why:
|
||||
it controls what the landed commit looks like; `/land` handles all three.)*
|
||||
|
||||
**Step 6 — Pick how PRs get enqueued.**
|
||||
The simplest works immediately: commenting **`/trunk merge`** on a PR. `/land`
|
||||
uses that by default — zero extra auth, because the GitHub App is already
|
||||
installed. *(Optional upgrades: set an "enqueue by label" name in the web UI, run
|
||||
`trunk login` to use the `trunk` CLI, or set `$TRUNK_API_TOKEN` for the REST
|
||||
API — `/land` will prefer those when present.)*
|
||||
|
||||
**Step 7 — Persist the choice so I never ask again.**
|
||||
I'll write `Merge queue: trunk` into a `## Merge Configuration` section of
|
||||
CLAUDE.md. *(Why: `/land` reads it and skips detection from then on.)*
|
||||
|
||||
**Step 8 — Verify end-to-end.**
|
||||
Open any test PR and run `/land`. You should see a **`Trunk Merge Queue
|
||||
(<base>)`** check appear, move Queued → Testing → Merged, and the PR land on
|
||||
`<base>` without you touching GitHub:
|
||||
```bash
|
||||
gh pr checks <test-pr> --json name,state | grep -i "Trunk Merge Queue" || echo "no queue check yet — recheck Steps 2-4"
|
||||
```
|
||||
|
||||
Full docs: https://docs.trunk.io/merge-queue/getting-started
|
||||
|
||||
Once this is done, the payoff: queue up all your ready PRs with `/land`, walk
|
||||
away, and trunk lands them on `<base>` for you.
|
||||
|
||||
### Step 5: Verify
|
||||
|
||||
After writing, verify the configuration works:
|
||||
|
|
|
|||
|
|
@ -220,6 +220,13 @@ section if it exists, or append it. Keep it separate from Deploy Configuration s
|
|||
`/land` reads the `Merge queue:` line to pick its submit path. If you skip this section,
|
||||
`/land` falls back to live detection and asks once.
|
||||
|
||||
**If the user chose `trunk` and the queue isn't set up yet** (no `Trunk Merge Queue (<base>)`
|
||||
check on recent PRs — check with `gh pr checks` or `gh api`), walk them through the
|
||||
one-time onboarding below before writing `Merge queue: trunk`. If they chose `none` or
|
||||
`github`, skip the onboarding.
|
||||
|
||||
{{MERGE_QUEUE_SETUP}}
|
||||
|
||||
### Step 5: Verify
|
||||
|
||||
After writing, verify the configuration works:
|
||||
|
|
|
|||
|
|
@ -292,12 +292,12 @@ export const E2E_TOUCHFILES: Record<string, string[]> = {
|
|||
// bin/gstack-merge (lib/merge.ts), so those are dependencies of every
|
||||
// land-and-deploy E2E — a change to the land skill or the merge helper must
|
||||
// re-run the composition path.
|
||||
'land-and-deploy-workflow': ['land-and-deploy/**', 'land/**', 'bin/gstack-merge', 'lib/merge.ts', 'scripts/gen-skill-docs.ts'],
|
||||
'land-and-deploy-first-run': ['land-and-deploy/**', 'land/**', 'bin/gstack-merge', 'lib/merge.ts', 'scripts/gen-skill-docs.ts', 'bin/gstack-slug'],
|
||||
'land-and-deploy-workflow': ['land-and-deploy/**', 'land/**', 'bin/gstack-merge', 'lib/merge.ts', 'scripts/resolvers/merge-queue-setup.ts', 'scripts/gen-skill-docs.ts'],
|
||||
'land-and-deploy-first-run': ['land-and-deploy/**', 'land/**', 'bin/gstack-merge', 'lib/merge.ts', 'scripts/resolvers/merge-queue-setup.ts', 'scripts/gen-skill-docs.ts', 'bin/gstack-slug'],
|
||||
'land-and-deploy-review-gate': ['land-and-deploy/**', 'land/**', 'bin/gstack-review-read'],
|
||||
'canary-workflow': ['canary/**', 'browse/src/**'],
|
||||
'benchmark-workflow': ['benchmark/**', 'browse/src/**'],
|
||||
'setup-deploy-workflow': ['setup-deploy/**', 'bin/gstack-merge', 'lib/merge.ts', 'scripts/gen-skill-docs.ts'],
|
||||
'setup-deploy-workflow': ['setup-deploy/**', 'bin/gstack-merge', 'lib/merge.ts', 'scripts/resolvers/merge-queue-setup.ts', 'scripts/gen-skill-docs.ts'],
|
||||
|
||||
// Sidebar agent
|
||||
'sidebar-navigate': ['browse/src/server.ts', 'browse/src/sidebar-agent.ts', 'browse/src/sidebar-utils.ts', 'extension/**'],
|
||||
|
|
|
|||
|
|
@ -39,13 +39,15 @@ describe("PR #1620 post-failure PR-state check in /land template", () => {
|
|||
expect(readTmpl()).toMatch(/### 4\.2a: Post-failure PR-state check/);
|
||||
});
|
||||
|
||||
test("post-failure check comes before the wait step", () => {
|
||||
test("post-failure check comes before the landing step (4.3)", () => {
|
||||
const body = readTmpl();
|
||||
const postfail = body.indexOf("### 4.2a: Post-failure PR-state check");
|
||||
const wait = body.indexOf("### 4.3: Wait for it to land");
|
||||
// 4.3 is the landing step (enqueue-and-return by default, or --watch). Match
|
||||
// the section number, not its title, so D4's rename doesn't break the order check.
|
||||
const landing = body.indexOf("### 4.3:");
|
||||
expect(postfail).toBeGreaterThan(-1);
|
||||
expect(wait).toBeGreaterThan(-1);
|
||||
expect(postfail).toBeLessThan(wait);
|
||||
expect(landing).toBeGreaterThan(-1);
|
||||
expect(postfail).toBeLessThan(landing);
|
||||
});
|
||||
|
||||
test("Universal invariant + upstream gh bug references", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue