diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 3c4f0d24..43cc5c3e 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -78,8 +78,8 @@ { "name": "bitwarden-delivery-tools", "source": "./plugins/bitwarden-delivery-tools", - "version": "3.1.0", - "description": "Delivery lifecycle skills for Bitwarden initiatives — initiative funnel navigation, work transitions, architectural judgment, commits, pull requests, preflight checks, change labeling, and Jira ticket filing." + "version": "3.2.0", + "description": "Delivery lifecycle skills for Bitwarden initiatives — initiative funnel navigation, work transitions, architectural judgment, commits, pull requests, stacked pull requests, preflight checks, change labeling, and Jira ticket filing." }, { "name": "bitwarden-designer", diff --git a/.cspell.json b/.cspell.json index 8ff75412..8d30ffba 100644 --- a/.cspell.json +++ b/.cspell.json @@ -90,6 +90,7 @@ "mcp", "MECE", "metacharacters", + "mktemp", "modelcontextprotocol", "msword", "MVVM", diff --git a/README.md b/README.md index 9c0299b1..f8217a6b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A curated collection of plugins for AI-assisted development at Bitwarden. Enable | [bitwarden-shepherd](plugins/bitwarden-shepherd/) | 1.0.1 | Champion of a technical strategy — shepherds a TSI through evaluation into the funnel, then through to adoption | | [bitwarden-atlassian-tools](plugins/bitwarden-atlassian-tools/) | 2.7.0 | Atlassian access via MCP server with deep Jira issue research skill and opt-in Jira write tools | | [bitwarden-code-review](plugins/bitwarden-code-review/) | 2.1.0 | Autonomous code review agent following Bitwarden engineering standards with GitHub integration | -| [bitwarden-delivery-tools](plugins/bitwarden-delivery-tools/) | 3.1.0 | Delivery lifecycle skills: initiative funnel navigation, work transitions, architectural judgment, commits, PRs, preflight, labeling, Jira ticket filing | +| [bitwarden-delivery-tools](plugins/bitwarden-delivery-tools/) | 3.2.0 | Delivery lifecycle skills: initiative funnel, work transitions, architectural judgment, commits, PRs and stacks, preflight, labeling, Jira filing | | [bitwarden-designer](plugins/bitwarden-designer/) | 0.1.0 | Product designer persona: Code of Conduct and 30/60/90 critique, critique facilitation; dispatches into bitwarden-design-tools | | [bitwarden-design-tools](plugins/bitwarden-design-tools/) | 0.1.0 | Design toolkit: content style guide, Figma Dev Mode MCP, Bitwarden brand application, handoff prep, Design System governance, Product and Design Jira | | [bitwarden-devops-engineer](plugins/bitwarden-devops-engineer/) | 0.3.0 | DevOps engineering assistant: workflow compliance linting, action security auditing, and org-wide CI/CD remediation | diff --git a/plugins/bitwarden-delivery-tools/.claude-plugin/plugin.json b/plugins/bitwarden-delivery-tools/.claude-plugin/plugin.json index 653cbf7a..e65024a2 100644 --- a/plugins/bitwarden-delivery-tools/.claude-plugin/plugin.json +++ b/plugins/bitwarden-delivery-tools/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "bitwarden-delivery-tools", - "version": "3.1.0", - "description": "Delivery lifecycle skills for Bitwarden initiatives — initiative funnel navigation, work transitions, architectural judgment, commits, pull requests, preflight checks, change labeling, and Jira ticket filing.", + "version": "3.2.0", + "description": "Delivery lifecycle skills for Bitwarden initiatives — initiative funnel navigation, work transitions, architectural judgment, commits, pull requests, stacked pull requests, preflight checks, change labeling, and Jira ticket filing.", "author": { "name": "Bitwarden", "url": "https://github.com/bitwarden" @@ -15,6 +15,8 @@ "work-transition", "commit", "pull-request", + "pr-conventions", + "stacked-pull-requests", "preflight", "labeling", "jira" diff --git a/plugins/bitwarden-delivery-tools/CHANGELOG.md b/plugins/bitwarden-delivery-tools/CHANGELOG.md index 98df5280..8e42108d 100644 --- a/plugins/bitwarden-delivery-tools/CHANGELOG.md +++ b/plugins/bitwarden-delivery-tools/CHANGELOG.md @@ -5,6 +5,37 @@ All notable changes to the `bitwarden-delivery-tools` plugin will be documented The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.2.0] - 2026-09-04 + +### Added + +- **`stacking-pull-requests` skill** — Bitwarden's per-PR conventions across a chain of dependent pull requests: layer planning, per-layer gates, a stack-level submission preview, lower-layer feedback, and merging. `gh stack` mechanics are delegated to GitHub's `gh-stack` skill and extension, with an availability check and a single-branch fallback +- `references/submitting-a-stack.md` — the two paths that carry a title, body, and `t:` label onto every layer; `gh stack link` is passed `--base` so the bottom layer's base is not silently rewritten to the repository default branch +- **`applying-pr-conventions` skill** — the title, template body, and `ai-review` label a Bitwarden pull request needs, composed for one PR and returning those three values only. `creating-pull-request`, `stacking-pull-requests`, and `force-multiplier` all invoke it instead of reaching into each other: the stack path calls it per layer, the fan-out once at its pilot +- `references/installing-gh-stack.md`, holding the install detail out of the always-loaded file +- `skills/stacking-pull-requests/evals/` — 20-case trigger eval, runner, and README. The runner denies the mutating tools, loads no MCP servers, and runs each subprocess in a temp directory, since the query set is imperative. No baseline is committed +- `perform-preflight`: Stacked Branches section covering the current layer, with its own stack-detection test; a skipped section is reported rather than passing silently. It stops when the layer already has an open pull request, since that fix belongs to `stacking-pull-requests` Step 5 — the check sits here because `committing-changes` runs this checklist before staging, so a direct commit onto a lower layer passes through it +- `stacking-pull-requests` Step 1 registers the planned layers with `gh stack init`, so submission does not fail after every layer has been gated and previewed +- `stacking-pull-requests` Step 5 confirms before rebasing layers above a fix, and Step 6 confirms before both the draft flip and the merge +- `perform-preflight`: a failed rebase checkbox on a layer with an open pull request is reported rather than auto-fixed, since the fix force-pushes every layer above; before submission it is fixed in place, rebasing the layer and restacking the layers above it + +### Changed + +- `creating-pull-request`: routes to `stacking-pull-requests` on the user's intent to open a chain, and never when that skill is the caller. Its title, body, and label steps moved to `applying-pr-conventions`, so the workflow is now gate → conventions → preview → submit and its steps renumber 1–6 to 1–4 +- `creating-pull-request`: Step 1b names the base ref instead of hardcoding `origin/HEAD`, and keys it on the same trunk-or-release-branch distinction its push step uses for `--base`, so a branch cut from `rc` is not reviewed against trunk with every already-merged release commit in the diff. Only the `Substantial` path can be scoped to one layer, or to an explicit range on a release-cut branch +- `creating-pull-request`: the review gate has no exemption left. `force-multiplier` was the only caller that skipped it and no longer enters this workflow at all, so the gate runs on every entry; the single remaining caller edge is `stacking-pull-requests` running Step 1b alone, per layer +- `creating-pull-request`: its push step passes `--base ` for a branch cut from `rc`, `hotfix-rc`, or another release branch, which `gh pr create` would otherwise point at the repository default branch +- `force-multiplier`: collects conventions from `applying-pr-conventions` at its pilot target rather than walking `creating-pull-request`, so there is no per-PR preview, no `gh pr create`, and no review gate to suppress mid-campaign +- `committing-changes`: `description` gains a stack boundary so stack-level requests route to `stacking-pull-requests`. The body stays stack-agnostic — a layer is a branch, so the existing "first commit on a branch" rule already applies per layer — and the open-pull-request guard lives in `perform-preflight`, which this skill already runs before staging +- Stack detection reads `gh stack view --json`'s exit code and requires a payload naming the current branch; `stacking-pull-requests` Step 0 owns what each status means +- `creating-pull-request`: stack exclusion added to `description`; its eval baseline is stale and must be re-recorded after this version ships, since that runner takes no `--plugin-dir` and loads the installed cache +- `committing-changes` gains a single-layer eval case guarding its new description boundary. The stack boundary cannot be measured from these skills' own eval sets — correct routing goes to `stacking-pull-requests`, which then invokes them per layer, and the runner matches its token anywhere in the response — so it is measured from `stacking-pull-requests/evals/` only. Its runner requires `--plugin-dir`, so that baseline can be recorded from the branch; the `committing-changes` and `creating-pull-request` baselines take no such flag and must wait until after this version ships +- `stacking-pull-requests`: the stack root is `` rather than `` in `gh stack init`, the Step 4 preview, and both commands in `submitting-a-stack.md`, so a stack rooted on `rc` or a release branch is not registered and submitted against the default branch +- `stacking-pull-requests` Step 0 gates on whether the tooling is usable in this run rather than on install status: a skill installed mid-session is on disk but unresolvable until Claude Code restarts, so that outcome reports the install and takes the single-branch fallback instead of continuing into the stack path +- `creating-pull-request` no longer detects which skill is calling it. The three-signal protocol and the six-row caller table collapse to two documented edges on the review gate — run only that step (`stacking-pull-requests`, per layer) or skip it on a named caller that owns the review (`force-multiplier`) — plus the reverse-direction handoff when the stack path is unavailable for a run, which now covers a mid-session install as well as a decline. `references/caller-integration.md` is deleted +- `stacking-pull-requests` Step 2 walks the layers bottom to top, the order under which a lower layer's rebase is re-gated on every layer above it +- `plugin.json`: description and `stacked-pull-requests` keyword added; marketplace and README catalog entries follow + ## [3.1.0] - 2026-08-19 ### Added diff --git a/plugins/bitwarden-delivery-tools/README.md b/plugins/bitwarden-delivery-tools/README.md index 8565c857..23a22c63 100644 --- a/plugins/bitwarden-delivery-tools/README.md +++ b/plugins/bitwarden-delivery-tools/README.md @@ -1,6 +1,6 @@ # Bitwarden Delivery Tools -Delivery lifecycle skills for Bitwarden initiatives — from routing work through the Software Initiative Funnel and running cross-team work transitions, down to the day-to-day mechanics of committing, opening pull requests, running preflight checks, and labeling changes. +Delivery lifecycle skills for Bitwarden initiatives — from routing work through the Software Initiative Funnel and running cross-team work transitions, down to the day-to-day mechanics of committing, opening pull requests and stacks of them, running preflight checks, and labeling changes. ## Overview @@ -33,14 +33,16 @@ Any agent (tech-lead, software-engineer, shepherds, others) can compose these sk ### Mechanics -| Skill | Triggers | Purpose | -| ------------------------ | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -| `committing-changes` | "commit", "stage changes" | Default-branch check, commit message format, staging best practices | -| `creating-pull-request` | "create PR", "open PR" | PR title/body format, draft workflow, AI review labels | -| `filing-breakdown-tasks` | "tickets from tasks.md", "file the epic and stories" | Turn a breakdown's `tasks.md` into epic + child ticket drafts for `filing-jira-tickets` to file | -| `force-multiplier` | "across all repos", "in bulk" | Fan one change across many repos or monorepo projects as isolated, piloted draft PRs | -| `labeling-changes` | "label", "change type" | Conventional commit type keywords, CI label mapping | -| `perform-preflight` | "preflight", "self review" | Pre-commit quality gate checklist | +| Skill | Triggers | Purpose | +| ------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------- | +| `applying-pr-conventions` | Invoked by the PR workflows, rarely direct | Composes one PR's title/type-prefix, template body, and label; never reviews, previews, or submits | +| `committing-changes` | "commit", "stage changes" | Default-branch check, commit message format, staging best practices | +| `creating-pull-request` | "create PR", "open PR" | Single-branch PR workflow: review gate, conventions, submission preview, `gh pr create` | +| `filing-breakdown-tasks` | "tickets from tasks.md", "file the epic and stories" | Turn a breakdown's `tasks.md` into epic + child ticket drafts for `filing-jira-tickets` to file | +| `force-multiplier` | "across all repos", "in bulk" | Fan one change across many repos or monorepo projects as isolated, piloted draft PRs | +| `labeling-changes` | "label", "change type" | Conventional commit type keywords, CI label mapping | +| `perform-preflight` | "preflight", "self review" | Pre-commit quality gate checklist, including the per-layer checks for a stacked branch | +| `stacking-pull-requests` | "stack these PRs", "stacked diffs" | Bitwarden conventions across a chain of dependent PRs; mechanics delegated to `gh-stack` | ## Design Principle @@ -56,6 +58,20 @@ Several skills in this plugin reference tools or skills provided by sibling plug - **`bitwarden-security-engineer`** — provides `Skill(bitwarden-security-context)`, referenced from `architecting-solutions`. - **`bitwarden-code-review`** — provides `/bitwarden-code-review:code-review-local` and `Skill(performing-multi-agent-code-review)`, the code-review gate `creating-pull-request` runs before opening a PR. If it is absent, `creating-pull-request` prompts you to install it rather than skip the review. +`stacking-pull-requests` also invokes `Skill(addressing-code-review-comments)` from `bitwarden-code-review` when handling feedback on a lower layer, and treats it as optional. + +It additionally depends on two pieces of tooling outside this marketplace, both from GitHub's [`gh-stack`](https://github.com/github/gh-stack) repository, which owns the `gh stack` command surface. Install both — with only the extension, every stack request takes the single-branch fallback. + +1. The CLI extension: + + ```bash + gh extension install github/gh-stack --pin v0.1.0 + ``` + +2. The `gh-stack` skill, from that repository's `skills/gh-stack/` directory. `gh extension install` fetches a platform binary only and never places the skill, so install it as a Claude skill separately. + +`stacking-pull-requests` checks for both up front and falls back to a single-branch PR rather than improvising the commands. The documented flag sets were verified against `gh-stack` v0.1.0, the first release containing `gh stack merge`. It is pre-1.0 and still moving. + ## Installation ```bash @@ -82,6 +98,10 @@ Commit these changes Create a PR for this branch ``` +``` +Split this into a stack of dependent PRs +``` + ``` Run preflight before I commit ``` diff --git a/plugins/bitwarden-delivery-tools/skills/applying-pr-conventions/SKILL.md b/plugins/bitwarden-delivery-tools/skills/applying-pr-conventions/SKILL.md new file mode 100644 index 00000000..23b9a713 --- /dev/null +++ b/plugins/bitwarden-delivery-tools/skills/applying-pr-conventions/SKILL.md @@ -0,0 +1,101 @@ +--- +name: applying-pr-conventions +description: "Compose the three conventions a Bitwarden pull request needs — a title carrying the conventional commit type prefix that drives the t: label, a body following the repo's PR template, and an AI review label decision. Mostly invoked by another delivery skill: once for a single branch, once per layer of a stack, or once at the pilot target of a fan-out. Returns those three values and nothing else — it never runs a code review, never shows a submission preview, and never pushes or creates a pull request. Not for actually opening a pull request (that is creating-pull-request), a chain of dependent pull requests (stacking-pull-requests), or a fan-out across repositories (force-multiplier)." +--- + +# Applying Bitwarden's Pull Request Conventions + +Bitwarden pull requests depend on three signals that are easy to forget and hard to fix after submission: + +- the **conventional commit type prefix** in the title (CI reads it to apply the `t:` label), +- the **repo's PR template** (reviewers use its sections to orient), +- the **AI review label** (routes the PR to specific automation). + +Missing any one is silent — CI will not reject the PR, and the reviewer just becomes confused. + +This skill produces those three values for one pull request. It does not decide when they are used. Reviewing, previewing, pushing, and creating belong to whichever workflow invoked it, so the same conventions apply identically to a single branch, to every layer of a stack, and to every target of a fan-out. + +**What you get back:** a title string, a body string, and a label choice. Hand all three to the caller. Do not run `git push`, `gh pr create`, or `gh pr edit` from here, and do not show a submission preview — the caller's preview is the catch-net, and a second one here either duplicates it or contradicts it. + +## 1 — Determine the change type and propose the title + +The title must follow this exact format: + +``` +[PM-XXXXX] : +``` + +The `:` prefix is what CI scans (lowercased) to assign the `t:` label. Without it the PR ships with no type label and triage cannot filter it. Read `${CLAUDE_PLUGIN_ROOT}/references/change-type-labels.md` to pick the keyword. + +If the Jira ticket key is not in the branch name or recent conversation, ask the user. Do not leave `PM-XXXXX` as a placeholder — a real ticket key is required for tracking links to resolve. + +**Show the proposed title to the user before continuing.** This is the first chance to catch typos, a missing prefix, or the wrong ticket key. + +## 2 — Read the repo's PR template and draft the body + +Always read `.github/PULL_REQUEST_TEMPLATE.md` from the target repo before drafting. Even with a body draft in mind, the template's sections are what reviewers expect to scan. Skipping this is a common failure mode — PRs ship with improvised bodies missing sections reviewers depend on. + +If the template exists: + +- use its sections verbatim as the body structure, +- fill each section based on the actual change, +- keep section headers (e.g. `## 🎟️ Tracking`, `## 📔 Objective`) — they are load-bearing for reviewer scanning, +- delete sections that do not apply (Screenshots with no UI change, for example), unless the template comments say to leave them. + +If no template exists, fall back to: + +```markdown +## 🎟️ Tracking + + + +## 📔 Objective + + + +## 📸 Screenshots + + +``` + +**The caller may hand you review outcomes to record.** A skipped code review, and every deferred CRITICAL or IMPORTANT finding, goes in the Objective section. Write whatever the caller passes; do not go looking for review results yourself, and do not assert a review happened when the caller said nothing about one. + +## 3 — Ask about the AI review label + +Use the `AskUserQuestion` tool: + +- **Question**: "Would you like to add an AI review label to this PR?" +- **Options**: `ai-review`, `ai-review-vnext`, `No label` + +Return the answer to the caller. **Ask this once per decision, not once per invocation** — see below. + +## Applying these across more than one pull request + +Two callers do this, and they do it differently. `stacking-pull-requests` invokes this **once per layer**, because each layer states its own position in the chain and picks its own type keyword. `force-multiplier` invokes it **once, at its pilot target**, then replicates the confirmed pattern across the fan-out without invoking again — it cannot answer an interactive prompt dozens of times, and the whole point of its pilot is to settle one pattern. + +For the per-layer case: + +- **Compose the title and body per unit.** +- **Ask the label question once**, on the first invocation, and have the caller carry the answer into the rest. A caller that asks per layer is asking the same question N times and can end up with a stack whose layers disagree. +- **Take the ticket key as given** across the set. One ticket normally spans a whole stack, and the key repeats on every layer. +- **Choose the type keyword per unit.** It is a property of what that layer or target actually changes, not of the set — a stack often mixes `feat` and `refactor`. + +If the caller has already settled the label for the set, skip step 3 entirely rather than re-asking. + +## Common failure modes + +These are what a caller's submission preview is built to catch. Recognizing them helps while composing: + +- **Title with no type prefix** → `[PM-12345] Add autofill for passkeys` ships with no `t:` label. Include `feat:`, `fix:`, etc. +- **Generic body replacing the template** → reviewers expect the template's sections. Read the template even when the body feels obvious. +- **`PM-XXXXX` left as a placeholder** → tracking links will not resolve. +- **The label answer getting dropped** between here and submission — return it explicitly rather than assuming the caller remembers it. + +Recovery after submission is awkward: the title is permanent in the merge commit, and labels feed downstream filtering and automation. + +## Related skills + +- `Skill(creating-pull-request)` — the single-branch workflow that reviews, previews, and submits around these conventions. +- `Skill(stacking-pull-requests)` — applies them per layer of a stack, with one whole-stack preview. +- `Skill(force-multiplier)` — applies them once at a pilot target, then replicates across a fan-out. +- `Skill(labeling-changes)` — the conventional commit keyword reference behind step 1. diff --git a/plugins/bitwarden-delivery-tools/skills/committing-changes/SKILL.md b/plugins/bitwarden-delivery-tools/skills/committing-changes/SKILL.md index c3117764..7984f8e2 100644 --- a/plugins/bitwarden-delivery-tools/skills/committing-changes/SKILL.md +++ b/plugins/bitwarden-delivery-tools/skills/committing-changes/SKILL.md @@ -1,6 +1,6 @@ --- name: committing-changes -description: Git commit conventions and workflow for Bitwarden repositories. Use when committing code, writing commit messages, or preparing changes for commit. Triggered by "commit", "git commit", "commit message", "prepare commit", "stage changes". +description: Git commit conventions and workflow for Bitwarden repositories. Use when committing code, writing commit messages, or preparing changes for commit. Triggered by "commit", "git commit", "commit message", "prepare commit", "stage changes". Not for planning, gating, submitting, or merging a stack of dependent pull requests (that is stacking-pull-requests). --- # Git Commit Conventions diff --git a/plugins/bitwarden-delivery-tools/skills/committing-changes/evals/README.md b/plugins/bitwarden-delivery-tools/skills/committing-changes/evals/README.md index 72ab1707..70b0ea70 100644 --- a/plugins/bitwarden-delivery-tools/skills/committing-changes/evals/README.md +++ b/plugins/bitwarden-delivery-tools/skills/committing-changes/evals/README.md @@ -9,7 +9,7 @@ Two eval sets. Run the one that matches what changed. ## Trigger eval -Does the skill activate on the right phrasings and stay silent on near-misses? `trigger-eval.json` holds 13 queries; `run_real_eval.py` runs them (see `../../creating-pull-request/evals/run_real_eval.py` for why this runner exists instead of the skill-creator harness); `baseline.json` is the last known-good run. Requires Python 3.10+ and an authenticated `claude` CLI. +Does the skill activate on the right phrasings and stay silent on near-misses? `trigger-eval.json` holds 14 queries; the `stacking-pull-requests` boundary is not measurable from here, since a correctly-routed stack phrasing invokes this skill per layer and the runner matches its token anywhere in the response; `run_real_eval.py` runs them (see `../../creating-pull-request/evals/run_real_eval.py` for why this runner exists instead of the skill-creator harness); `baseline.json` is the last known-good run — **stale as of the stack-scope description change**: it holds 13 results against the current 14 queries. Regenerate before relying on the diff below — and note this runner takes no `--plugin-dir`, so it loads the installed plugin cache. Record the baseline after this version ships, not before, or it captures the description the change replaced. Requires Python 3.10+ and an authenticated `claude` CLI. ```bash python3 run_real_eval.py --eval-set trigger-eval.json --runs-per-query 3 \ diff --git a/plugins/bitwarden-delivery-tools/skills/committing-changes/evals/trigger-eval.json b/plugins/bitwarden-delivery-tools/skills/committing-changes/evals/trigger-eval.json index 0eacd65f..bdb7344a 100644 --- a/plugins/bitwarden-delivery-tools/skills/committing-changes/evals/trigger-eval.json +++ b/plugins/bitwarden-delivery-tools/skills/committing-changes/evals/trigger-eval.json @@ -50,5 +50,9 @@ { "query": "create a new branch for the PM-33210 work before I start coding, nothing to commit yet", "should_trigger": false + }, + { + "query": "commit what I have on this layer before I start the next one", + "should_trigger": true } ] diff --git a/plugins/bitwarden-delivery-tools/skills/creating-pull-request/SKILL.md b/plugins/bitwarden-delivery-tools/skills/creating-pull-request/SKILL.md index 872c5a7c..3f618ef3 100644 --- a/plugins/bitwarden-delivery-tools/skills/creating-pull-request/SKILL.md +++ b/plugins/bitwarden-delivery-tools/skills/creating-pull-request/SKILL.md @@ -1,7 +1,7 @@ --- name: creating-pull-request -description: Open a pull request from a branch in a Bitwarden repository — pick the conventional commit type prefix that drives the t: label, fill in the repo's PR template, choose an ai-review label, and confirm a full submission preview before running gh pr create. -when_to_use: Use when the user is ready to open a pull request from a branch — phrasings like "create a PR", "open a PR", "ship a draft", "put it up for review", "ready for review", or "ship it". Also use when drafting a PR title or body, picking the conventional commit type prefix, or choosing the t: or ai-review label for a PR being opened (takes precedence over labeling-changes in PR-creation contexts). Do not use for conceptual questions ("how do PRs work") or managing existing PRs (status, merging, addressing comments). +description: "Open a pull request from a branch in a Bitwarden repository — pick the conventional commit type prefix that drives the t: label, fill in the repo's PR template, choose an ai-review label, and confirm a full submission preview before running gh pr create. Not for a chain of dependent pull requests (that is stacking-pull-requests)." +when_to_use: 'Use when the user is ready to open a pull request from a branch — phrasings like "create a PR", "open a PR", "ship a draft", "put it up for review", "ready for review", or "ship it". Also use when drafting a PR title or body, picking the conventional commit type prefix, or choosing the t: or ai-review label for a PR being opened (takes precedence over labeling-changes in PR-creation contexts). Do not use for a chain of dependent pull requests (that is stacking-pull-requests), conceptual questions ("how do PRs work"), or managing existing PRs (status, merging, addressing comments).' --- # Creating a Pull Request @@ -16,7 +16,11 @@ Missing any one of these is silent — CI won't reject the PR, and the reviewer ## Workflow -Follow these steps in order. Each one produces information the next step needs, and the preview in Step 5 depends on all of them. +Follow these steps in order. Each one produces information the next step needs, and the preview in Step 3 depends on all of them. + +**First, is this one pull request or a stack?** A request for a chain of dependent pull requests belongs to `Skill(stacking-pull-requests)` — hand off. + +One exception, and it is the reverse direction: an invocation stating the stack path was already tried and is unavailable for this run — declined, the install failed, or the skill installed but is not resolvable until a restart — is `stacking-pull-requests` handing a single ordinary pull request _here_. Run the whole workflow, gate included; nobody owns the review on that path. Handing it back is the loop this exception exists to prevent. ### Step 1 — Confirm preflight, then run the code-review gate @@ -36,73 +40,37 @@ If preflight cannot be made to pass, stop and report the failure rather than ope - **Question**: "How deep is this change? (sets review depth)" - **Options**: - `Standard` — a typical feature, fix, docs, or config change: run `/bitwarden-code-review:code-review-local` (tell it to review the current branch's changes; there is no PR yet) - - `Substantial` — architectural, cross-cutting, or security-touching: run `Skill(performing-multi-agent-code-review)`, telling it to review the full branch diff against `origin/HEAD` (not just uncommitted changes); there is no PR yet + - `Substantial` — architectural, cross-cutting, or security-touching: run `Skill(performing-multi-agent-code-review)`, telling it to review the full branch diff against the base ref (not just uncommitted changes); there is no PR yet + +The base ref is `origin/HEAD` for a branch cut from trunk, and the release branch itself — `origin/rc`, `origin/hotfix-rc` — for a branch cut from one; Step 4 keys `--base` on the same distinction. `Standard` resolves `origin/HEAD` unconditionally, so on a release-cut branch its three-dot diff takes the merge base with trunk and sweeps in every already-merged commit that release branch carries. Use `Substantial` with the explicit range there, or record in the PR body that the review was not base-scoped. When `stacking-pull-requests` is driving this per layer, the review has to be scoped to that layer or it re-reviews every layer below. Only `Substantial` can be scoped: use its commit-range mode with the explicit range `..`. `Standard` resolves `origin/HEAD` itself and takes no base ref, so on any layer above the bottom its diff reaches below the layer. Say so when asking — name `Substantial` as the layer-scoped option — and if the user picks `Standard` anyway, record in that layer's PR body that the review was not layer-scoped. -Present only these two options; do not add a skip option. Honor a skip only if the user volunteers one unprompted, then record it in the PR body's Objective section (Step 3) and surface it in the Step 5 preview. Never skip on your own initiative. +Present only these two options; do not add a skip option. Honor a skip only if the user volunteers one unprompted, then record it in the PR body's Objective section (Step 2) and surface it in the Step 3 preview. Never skip on your own initiative. After the review: -- Address every CRITICAL and IMPORTANT finding, or record why each is deferred in the PR body's Objective section (Step 3). +- Address every CRITICAL and IMPORTANT finding, or record why each is deferred in the PR body's Objective section (Step 2). - On the `Substantial` path only, you may re-run `Skill(performing-multi-agent-code-review)` with a different `--model-*` value for the highest-risk changes (auth, crypto, data handling, migrations); findings vary by model, so a second pass can catch what the first missed. Optional, never required. - If a review path wrote output into the repo, remove it before pushing so it never lands in a commit — but only files this run created (a `??` in `git status --porcelain`). For example, `code-review-local` writes `review-summary.md` and `review-inline-comments.md` to the working-directory root; the multi-agent path writes outside the repo and needs no cleanup. Never delete a tracked file of the same name. Each review path checks its own prerequisites and reports what to install if something is missing. If a path can't run, install what it reports or fall back to the other path and note the limitation in the PR body. If neither path is available, stop and prompt the user to install `bitwarden-code-review` (`/plugin install bitwarden-code-review@bitwarden-marketplace`) before continuing. Never silently skip the review. -When `creating-pull-request` runs as a step inside another delivery skill's workflow (for example a bulk campaign or a prototype PR), that workflow owns whether and how a review runs; skip this gate. Today neither in-plugin caller runs a review — wiring it in is a tracked follow-up. - -### Step 2 — Determine change type and propose the title - -The title must follow this exact format: - -``` -[PM-XXXXX] : -``` - -The `:` prefix is what CI scans (lowercased) to assign the `t:` label. Without it, the PR ships with no type label and triage can't filter it. Read `${CLAUDE_PLUGIN_ROOT}/references/change-type-labels.md` to pick the right keyword. - -If the Jira ticket key isn't in the branch name or recent conversation, ask the user. Don't leave `PM-XXXXX` as a placeholder — a real ticket key is required for tracking links to resolve. - -**Show the proposed title to the user before continuing.** This is the first chance for them to catch typos, a missing prefix, or the wrong ticket key. +**One thing another delivery skill may do with this step:** run 1b alone. `stacking-pull-requests` does, once per layer, because the review gate is per pull request and a stack has N of them. It runs `perform-preflight` itself per layer, so do not also run 1a, and return after 1b rather than continuing into Step 2. -### Step 3 — Read the repo's PR template +Otherwise the gate runs on every entry, including the stack-fallback case above — that path bailed before its own gate, so nobody owns the review. -Always read `.github/PULL_REQUEST_TEMPLATE.md` from the target repo before drafting the body. Even when you have a body draft in mind, the template's sections are what other reviewers expect to scan. Skipping this is a common failure mode — PRs ship with improvised bodies that miss sections reviewers depend on. - -If the template exists: - -- use its sections verbatim as the body structure, -- fill each section based on the actual change, -- keep section headers (e.g. `## 🎟️ Tracking`, `## 📔 Objective`) — they're load-bearing for reviewer scanning, -- delete sections that don't apply (Screenshots with no UI change, for example), unless the template comments say to leave them. - -If no template exists, fall back to: - -```markdown -## 🎟️ Tracking - - - -## 📔 Objective - - - -## 📸 Screenshots - - -``` +Skills wanting the title, body, and label rather than this gate should invoke `Skill(applying-pr-conventions)` directly; it is a peer of this workflow, not a step inside it. That is the whole of `force-multiplier`'s use, which is why it no longer enters here at all and why there is no longer a way to skip this gate. -### Step 4 — Ask about the AI review label +### Step 2 — Compose the title, body, and label -Use the `AskUserQuestion` tool to ask: +Invoke `Skill(applying-pr-conventions)`. It owns all three: the `[PM-XXXXX] : ` title and the type keyword behind the `t:` label, the body built from the repo's `.github/PULL_REQUEST_TEMPLATE.md`, and the `ai-review` label question. -- **Question**: "Would you like to add an AI review label to this PR?" -- **Options**: `ai-review`, `ai-review-vnext`, `No label` +Pass it what Step 1 produced, since it does not go looking for review results itself: the review path taken, any skip the user volunteered, and every deferred CRITICAL or IMPORTANT finding. Those go into the body's Objective section. -Capture the answer. You'll surface it in Step 5 and pass it on the command line in Step 6. +Carry back the title, the body, and the label choice — Step 3's preview shows all three and Step 4 submits them. -### Step 5 — Show the full submission preview, then confirm +### Step 3 — Show the full submission preview, then confirm -This is the most important step in this workflow. **Before running any `git push` or `gh pr create`, show the user a single preview block containing every decision made above.** This is the catch-net for failure modes like title typos, missing type prefix, body drifting from the template, or the AI review label getting dropped between Step 4 and submission. +This is the most important step in this workflow. **Before running any `git push` or `gh pr create`, show the user a single preview block containing every decision made above.** This is the catch-net for failure modes like title typos, missing type prefix, body drifting from the template, or the AI review label getting dropped between Step 2 and submission. Use this exact format: @@ -129,14 +97,14 @@ Then use the `AskUserQuestion` tool to confirm: - **Question**: "Submit this PR as previewed?" - **Options**: - - `Submit as shown` — proceed to Step 6 with the previewed values + - `Submit as shown` — proceed to Step 4 with the previewed values - `Edit title or body` — apply the requested edit, then redisplay the preview and re-ask - - `Change ai-review label` — re-run the Step 4 label question, then redisplay the preview and re-ask + - `Change ai-review label` — re-run the label question from `Skill(applying-pr-conventions)`, then redisplay the preview and re-ask - `Cancel` — stop without pushing or creating the PR -Only continue to Step 6 when the user selects `Submit as shown`. The recap is non-negotiable — some failures (title in the merge commit, label-driven automation routing) are painful to undo once the PR is live, so a visible chance to catch issues at submission time pays for itself many times over. +Only continue to Step 4 when the user selects `Submit as shown`. The recap is non-negotiable — some failures (title in the merge commit, label-driven automation routing) are painful to undo once the PR is live, so a visible chance to catch issues at submission time pays for itself many times over. -### Step 6 — Push and create +### Step 4 — Push and create Push the branch and run `gh pr create` with the confirmed values. Pass the body via `--body-file`, not `--body`: the body carries model- and review-generated text (derived from untrusted repo content), and interpolating it into a double-quoted shell argument would let backticks or `$(…)` execute. Write it to a temp file and hand `gh` the path: @@ -152,18 +120,19 @@ gh pr create --draft \ Defaults that hold unless the user said otherwise: - create as **draft** — only skip `--draft` if the user explicitly asked for a ready-for-review PR, -- include `--label` only if the user picked a label in Step 4 (omit it for "No label"), -- multiple labels can be passed by repeating `--label`. +- include `--label` only if a label was picked in Step 2 (omit it for "No label"), +- multiple labels can be passed by repeating `--label`, +- omit `--base` for a branch cut from trunk, and pass `--base ` when the branch was cut from `rc`, `hotfix-rc`, or another release branch — with no `--base`, `gh pr create` targets the repository default branch and silently points the PR at trunk. Stack layers are submitted by `Skill(stacking-pull-requests)`, which passes each layer's `--base` itself; this step does not run per layer. After `gh pr create` returns, post the PR URL back to the user. ## Common Failure Modes -These are what the Step 5 preview is built to prevent. Recognizing them helps when adjusting the draft mid-workflow: +These are what the Step 3 preview is built to prevent. Recognizing them helps when adjusting the draft mid-workflow: - **Title with no type prefix** → `[PM-12345] Add autofill for passkeys` ships with no `t:` label. Include `feat:`, `fix:`, etc. - **Generic body replacing the template** → reviewers expect the template's sections. Read the template even when the body feels obvious. -- **Label answer dropped between Step 4 and Step 6** → the recap surfaces it; if it's missing there, it's about to be missing on the PR. -- **`PM-XXXXX` left as a placeholder** → tracking links won't resolve. Catch in Step 2 or Step 5. +- **Label answer dropped between Step 2 and Step 4** → the recap surfaces it; if it's missing there, it's about to be missing on the PR. +- **`PM-XXXXX` left as a placeholder** → tracking links won't resolve. Catch in Step 2 or Step 3. If any of these slip past the preview, recovery is awkward — the title is permanent in the merge commit, and labels feed downstream filtering and automation. diff --git a/plugins/bitwarden-delivery-tools/skills/creating-pull-request/evals/README.md b/plugins/bitwarden-delivery-tools/skills/creating-pull-request/evals/README.md index 8b67e85f..2e60805b 100644 --- a/plugins/bitwarden-delivery-tools/skills/creating-pull-request/evals/README.md +++ b/plugins/bitwarden-delivery-tools/skills/creating-pull-request/evals/README.md @@ -9,8 +9,11 @@ The upstream `skill-creator` harness measures triggering by registering a tempor ## Files - `trigger-eval.json` — 20-query test set: 10 should-trigger natural-language phrasings ("package this up into a PR", "ship a draft", "get this in front of reviewers", etc.) and 10 should-not-trigger near-misses against sibling delivery skills (`committing-changes`, `labeling-changes`, `perform-preflight`) and against existing-PR management queries. + + The `stacking-pull-requests` boundary is **not** measurable here. Correct routing for a stack phrasing is `Skill(stacking-pull-requests)`, which then invokes this skill once per layer — and the runner counts a trigger when the token appears anywhere in the response, so a correctly-routed query scores as a false trigger. That boundary is measured from the other side, in `stacking-pull-requests/evals/`. + - `run_real_eval.py` — runner. Spawns parallel `claude -p` subprocesses, parses streamed tool-use events, computes per-query trigger rates. -- `baseline.json` — last known-good run. Diff against this to spot regressions on future description changes. +- `baseline.json` — last known-good run. **Stale**: the skill's `description` and `when_to_use` both changed without a re-run, so the recorded rates predate the current routing. Regenerate before relying on the diff below — and note this runner takes no `--plugin-dir`, so it loads the installed plugin cache. Record the baseline after this version ships, not before, or it captures the frontmatter the change replaced. ## Running diff --git a/plugins/bitwarden-delivery-tools/skills/force-multiplier/SKILL.md b/plugins/bitwarden-delivery-tools/skills/force-multiplier/SKILL.md index a2380d86..9dfad95a 100644 --- a/plugins/bitwarden-delivery-tools/skills/force-multiplier/SKILL.md +++ b/plugins/bitwarden-delivery-tools/skills/force-multiplier/SKILL.md @@ -3,7 +3,7 @@ name: force-multiplier description: Apply one intent across many targets at once — a fleet of repositories across the Bitwarden ecosystem, or many projects inside a monorepo — as N consistent, idempotent, reviewable draft PRs. when_to_use: Use when the user wants the same change made everywhere — phrasings like "across all repos", "every repo", "for every project", "fleet-wide", "org-wide", "enterprise-wide", "company-wide", "in bulk", "mass update", or "roll this out everywhere". argument-hint: " [--scope multi-repo|monorepo] [--dry-run] [--no-pilot]" -allowed-tools: "Bash, Read, Write, Edit, Glob, Grep, Agent, Skill(perform-preflight), Skill(committing-changes), Skill(labeling-changes), Skill(creating-pull-request)" +allowed-tools: "Bash, Read, Write, Edit, Glob, Grep, Agent, Skill(perform-preflight), Skill(committing-changes), Skill(labeling-changes), Skill(applying-pr-conventions)" --- # Force Multiplier @@ -55,9 +55,9 @@ Force Multiplier is the **cross-target** layer. Per-target intelligence lives in - `Skill(perform-preflight)` — the quality gate before any commit. - `Skill(committing-changes)` — the commit message format. - `Skill(labeling-changes)` — the conventional type keyword that drives the `t:` label. -- `Skill(creating-pull-request)` — the draft-PR workflow, template, and `ai-review` label. +- `Skill(applying-pr-conventions)` — the title format, template body, and `ai-review` label. -Of these, `creating-pull-request` is **interactive** — it prompts per PR, which you cannot answer dozens of times. Resolve it at **PILOT**: walk it once to lock the title format, body template, and labels, then replicate that confirmed pattern non-interactively across the fan-out as draft PRs. +Of these, `applying-pr-conventions` is **interactive** — it proposes a title and asks the label question, which you cannot answer dozens of times. Resolve it at **PILOT**: invoke it once to lock the title format, body template, and label, then replicate that confirmed pattern non-interactively across the fan-out as draft PRs. It composes conventions and nothing else, so it neither reviews nor submits, and there is no gate to suppress. Do not invoke `Skill(creating-pull-request)` per target: it ends in a per-PR preview and its own `gh pr create`, and its review gate would stop mid-campaign to prompt for a review this workflow cannot answer. ## Safety defaults (non-negotiable unless explicitly overridden) diff --git a/plugins/bitwarden-delivery-tools/skills/force-multiplier/references/campaign-spec.md b/plugins/bitwarden-delivery-tools/skills/force-multiplier/references/campaign-spec.md index 4b706a1a..6fdc83f5 100644 --- a/plugins/bitwarden-delivery-tools/skills/force-multiplier/references/campaign-spec.md +++ b/plugins/bitwarden-delivery-tools/skills/force-multiplier/references/campaign-spec.md @@ -44,7 +44,7 @@ The shape of the change as it is delivered. Confirmed once on the pilot, then re - `branch` — a deterministic branch name template (same input → same branch, so re-runs are no-ops). - `title` — follows `Skill(committing-changes)` / `Skill(labeling-changes)` format (`[TICKET] : `), so CI applies the right `t:` label. `[TICKET]` is a placeholder — substitute a real tracking-issue key, never the literal word. -- `body` — fills the target repo's `.github/PULL_REQUEST_TEMPLATE.md` per `Skill(creating-pull-request)`. +- `body` — fills the target repo's `.github/PULL_REQUEST_TEMPLATE.md` per `Skill(applying-pr-conventions)`. - `labels` — the `ai-review` choice and any others, confirmed at pilot. - `draft` — `true` by default. diff --git a/plugins/bitwarden-delivery-tools/skills/perform-preflight/SKILL.md b/plugins/bitwarden-delivery-tools/skills/perform-preflight/SKILL.md index 5436c6af..feb6b4f5 100644 --- a/plugins/bitwarden-delivery-tools/skills/perform-preflight/SKILL.md +++ b/plugins/bitwarden-delivery-tools/skills/perform-preflight/SKILL.md @@ -1,6 +1,6 @@ --- name: perform-preflight -description: Quality gate checklist to run before committing or creating a PR. Use when finishing implementation, checking work quality, or preparing to commit. Triggered by "preflight", "self review", "ready to commit", "check my work", "quality gate". +description: Quality gate checklist to run before committing or creating a PR, with a section covering the current branch when it is one layer of a stack. Use when finishing implementation, checking work quality, or preparing to commit. Triggered by "preflight", "self review", "ready to commit", "check my work", "quality gate". Gating a whole stack layer by layer belongs to stacking-pull-requests, which calls this per layer. --- # Preflight Checklist @@ -31,6 +31,32 @@ Run this checklist before committing or creating a PR. Consult the repo's CLAUDE - [ ] Dependency injection and error handling follow repo convention - [ ] String resources added to the correct location (if applicable) +## Stacked Branches + +Only applies when the current branch is one layer of a stack. Test that rather than assume it, since preflight is often invoked directly rather than from the stack workflow. Run `gh stack view --json`, then: + +**Before anything else, check whether this layer already has an open pull request.** If it does, a fix on it belongs to `Skill(stacking-pull-requests)` Step 5, which rebases and force-pushes every layer above behind a confirmation listing them. Say so and stop rather than gating a commit that would strand those layers. This is the case that reaches here unattended: `Skill(committing-changes)` runs this checklist before staging, so a commit aimed at a lower layer passes through this section whether or not the stack workflow is driving. + +- Do this section only if `gh stack view --json` exits `0` **and** its payload names the current branch. Any non-zero status skips it, and do not try to interpret which one you got — `Skill(stacking-pull-requests)` Step 0 owns what each status means and the install offer that follows. +- **Say when you skipped, and why.** A silent skip is indistinguishable from three satisfied checkboxes, and `stacking-pull-requests` Step 2 requires this section per layer. Report one of: + - `Stacked Branches: skipped, gh stack view --json exited ` + - `Stacked Branches: skipped, gh stack view --json exited 0 but its payload does not name this branch` +- **Leave the checkboxes unchecked on a skip.** A bare `exited 0` with ticks reads as a clean run to a human and to Step 2 alike. +- **A caller may assert this branch is a layer**, in which case run the section on that assertion instead of the probe. It must supply the parent branch with the assertion, since the rebase checkbox takes its parent from the payload that is absent on this path; without one, report that checkbox unverifiable rather than guessing. +- Do not pipe `gh stack view --json` when you need that status. The shell reports the last command's exit code, not `gh`'s, so a pipe silently turns the gate on the wrong process. + +A stack merges bottom-to-top and all-or-nothing, so a layer that is red on its own blocks every layer above it. + +- [ ] This layer builds, lints, and passes its tests with only the layers below it present +- [ ] This layer is rebased on its parent — `git merge-base --is-ancestor "" HEAD` exits 0, where `` is the layer immediately below this one in the `gh stack view --json` payload captured above. That name is repository data, so validate it against `^[A-Za-z0-9_][A-Za-z0-9._/-]*$` before composing the command and quote it as shown; a branch name is legal git syntax and can still be a shell payload. Report the checkbox unverifiable if it fails the pattern. On the bottom layer, whose parent is the stack base, report this unverified rather than substituting that base: the trunk-drift paragraph below deliberately keeps it off the checklist, and checking it here would reintroduce it +- [ ] This layer references no code that lands in a layer above it + +The last item has no command behind it; it is a read-through of what this layer calls. Report it as checked-by-inspection rather than verified. + +How far trunk has moved is deliberately not a checkbox. Trunk advances constantly, so gating a commit on it would fire most of the time and force a full-stack rebase and force-push, invalidating reviews on lower layers. + +See `Skill(stacking-pull-requests)` for the surrounding workflow, for walking the rest of the stack, and for whether the `gh-stack` tooling is available at all. + ## On Failure -If any check fails, fix the issue before proceeding. For test failures, diagnose the root cause rather than skipping. For lint/format failures, run the repo's auto-fix command if available. If a check cannot be resolved, flag it to the user with the specific failure output. +If any check fails, fix the issue before proceeding — with one conditional exception. A failed **rebase checkbox** depends on whether the layer already has an open pull request. Before submission, when it does not, rebase it on its parent and restack the layers above it onto the result — `Skill(gh-stack)` has that command. There is nothing to force-push and no review to invalidate, but the branches above are still rooted on the pre-rebase tip and are stranded until they are restacked. Once it does, do not fix it here: the rebase rewrites and force-pushes every layer above, which belongs to `Skill(stacking-pull-requests)` Step 5 behind the confirmation it requires listing every affected PR. Report it and stop in that case. For test failures, diagnose the root cause rather than skipping. For lint/format failures, run the repo's auto-fix command if available. If a check cannot be resolved, flag it to the user with the specific failure output. diff --git a/plugins/bitwarden-delivery-tools/skills/stacking-pull-requests/SKILL.md b/plugins/bitwarden-delivery-tools/skills/stacking-pull-requests/SKILL.md new file mode 100644 index 00000000..7006dfad --- /dev/null +++ b/plugins/bitwarden-delivery-tools/skills/stacking-pull-requests/SKILL.md @@ -0,0 +1,133 @@ +--- +name: stacking-pull-requests +description: 'Break one change into a stack of dependent pull requests in a Bitwarden repository — plan the layers, carry the ticket key, the conventional-commit type prefix that drives the t: label, the PR template, and the ai-review label onto every layer, gate each layer, submit the chain, address review feedback on a lower layer, and merge the stack. Triggered by "stack these PRs", "stacked diffs", "split this into dependent PRs", "comment on the bottom PR of my chain", "land the whole chain". Not for a single-branch pull request (that is creating-pull-request) or one change fanned across many repositories (that is force-multiplier).' +--- + +# Stacking Pull Requests + +A stack turns one large change into a chain of branches, each rooted on the one below it, where every layer is its own pull request reviewed against its parent rather than against trunk. Reviewers see one concern at a time. + +The mechanics belong to GitHub: `Skill(gh-stack)` documents the full `gh stack` command surface, its non-interactive flags, and the traps that hang an agent. This skill does not restate that surface. Aside from the availability and state checks in Step 0, the stack registration in Step 1, and the merge in Step 6 — whose flags come from `Skill(gh-stack)`, not from here — the only `gh stack` commands written down here are the submission sequence in `${CLAUDE_PLUGIN_ROOT}/skills/stacking-pull-requests/references/submitting-a-stack.md`, which stays usable on its own; navigation, rebasing, and conflict handling need `Skill(gh-stack)`. What this skill owns is the part GitHub's tooling knows nothing about: Bitwarden's per-PR conventions, which a stack multiplies by the number of layers, and which `gh stack submit` cannot carry at all. + +## Step 0 — Confirm stacks are available + +Stacks depend on an external, pre-1.0 extension that is not installed by default, plus the skill that ships with it. Check both before planning anything, because a plan built on unavailable tooling wastes the planning. + +- **Extension.** `gh extension list` must show a `github/gh-stack` row. If it is absent, offer `gh extension install github/gh-stack --pin v0.1.0`. Pin it: the extension is pre-1.0, the flag sets in the references were verified against v0.1.0, and this is the component that runs with live `git` and `gh` write credentials. +- **Skill.** A `gh-stack` skill must be resolvable — from a plugin, a `--plugin-dir` load, or a `SKILL.md` at `.claude/skills/gh-stack/` or `~/.claude/skills/gh-stack/`. Check the available-skills listing for the name, and only fall back to `Glob` over those two paths if it is absent there. Do not probe by invoking `Skill(gh-stack)` to see what happens: invoking a missing skill is itself an error, inside a gate whose whole job is to fail cleanly into the fallback. `gh extension install` fetches only a platform binary and never places the skill, so it has to be installed separately; `${CLAUDE_PLUGIN_ROOT}/skills/stacking-pull-requests/references/installing-gh-stack.md` has that procedure, including the commit the copy must be verified against. Extension present and skill absent is therefore the likeliest combination, so offer the procedure — and note that installing the skill does not make it resolvable until Claude Code restarts, so a successful install still leaves this run without it. + +The condition for continuing is that both are usable **in this run**: the extension present, and the skill resolvable. Install status is not the same thing — the extension may have been installed a moment ago, since `gh extension list` reflects it immediately, but the skill cannot be. If either is missing, name which one and its remedy, and ask whether to install it. + +**A skill installed just now does not become resolvable in this session.** Claude Code discovers skills at session start, so a `SKILL.md` copied into `.claude/skills/gh-stack/` is on disk but unresolvable until a restart — and the re-check above cannot see the difference, because the available-skills listing is fixed for the session while the `Glob` fallback finds the file. Do not read a successful install as the skill being present. Say the install succeeded and tell the user to restart Claude Code and re-run to get the stack path. Continuing into Step 1 delegates `gh stack init`, navigation, rebasing, and the merge to a skill that is not loaded, which is the half-attempted stack this step forbids — and it lands after the user has consented to an install and after the layers are planned. + +**The fallback covers every outcome that leaves the skill unresolvable for this run** — the user declines, an install fails, or the skill install succeeds. In all three the change ships as a single pull request: hand off to `Skill(creating-pull-request)` and state in the handoff that the stack path was already tried and is unavailable for this run, so it proceeds as one PR instead of routing back here. Do not say it was declined when it was installed; the handoff signal is the same either way. Say plainly that the change is shipping as one PR. Never half-attempt a stack. + +**Existing stack state.** Once both are usable in this run, invoke `Skill(gh-stack)` and have it report the current stack with `gh stack view --json`. Read the exit code: `0` means the current branch is in a stack, and anything else means treat it as not in one. Do not run it through a pipe when you need that status — the shell reports the last command's exit code, not `gh`'s. Exit `2` covers "not in a stack" but also "not a git repository" and a detached HEAD, so require that a successful payload names the current branch rather than trusting the code alone. Exit `9` is different and worth catching here: it means the repository does not have stacked PRs enabled, which otherwise surfaces only at submit, after every layer has been planned, gated, and titled. Treat it as unavailable tooling and take the single-branch fallback. + +## Step 1 — Plan the layers before writing code + +Layer boundaries are a design decision, and retrofitting them onto a finished branch is the expensive way to discover that. Decide the chain first. + +Two properties make a layer a real layer: + +- **Independently reviewable.** It is one coherent concern a reviewer can judge without the layers above it. +- **Independently green.** It builds, lints, and passes its tests with only the layers below it present. A layer that is red on its own is not reviewable and cannot merge, since `gh stack merge` lands the chain bottom-to-top. + +Foundational work goes at the bottom, consumers above it. If code in one layer depends on code in another, the dependency belongs in the same layer or a lower one — never a higher one. + +**Don't stack when:** the change fits comfortably in one PR; the layers cannot be made independently green; or the work is the same change repeated across many repositories, which is a fan-out rather than a chain and belongs to `Skill(force-multiplier)`. + +Confirm the planned layers with the user before creating branches. Name each layer's concern and the Jira ticket the stack serves. One ticket normally spans the whole stack. + +**Then build the layers.** Step 2 assumes every planned layer already exists as a branch with its commits on it, and three entry shapes reach it: + +- **Nothing written yet.** Create each branch on the one below it and write the layers bottom-up. +- **Already in a stack**, which is what exit `0` in Step 0 reports. The layers exist; add to them rather than rebuilding, and see the submission reference for appending a layer to a stack already on GitHub. +- **One oversized branch already written**, which is how most requests here arrive ("split this into dependent PRs", "I want stacked diffs instead of one 2000-line review"). Decide which existing commits belong to which layer before moving anything, then redistribute them. Whether a commit is foundation or consumer is the planning judgment this skill owns; the branch and rebase mechanics are `Skill(gh-stack)`'s. + +**Register the layers as a stack.** On the first and third shapes the branches are not yet a stack as far as `gh stack` is concerned, and `gh stack push` in the submission sequence exits `2` without one — after every layer has been gated, titled and previewed, which is the late failure Step 0 exists to prevent. Validate every branch name against the allowlist in `${CLAUDE_PLUGIN_ROOT}/skills/stacking-pull-requests/references/submitting-a-stack.md` before composing the command — two of the three shapes take names from the repository rather than names you chose. Then `gh stack init --base "" "" "" …` covers both: it adopts branches that already exist and creates the ones that do not. `` is the branch layer 1 was cut from — trunk usually, but `rc`, `hotfix-rc`, or another release branch when the stack is rooted there. Name it explicitly rather than letting it default: every command that takes a base takes this one, and a stack registered against the repository default branch submits its bottom layer at trunk. Confirm with `gh stack view --json` before moving on. + +Each layer's first commit takes the full format from `Skill(committing-changes)`. + +## Step 2 — Gate every layer, not just the top + +Both delivery gates are per-PR, so in a stack they run per layer. + +- `Skill(perform-preflight)` on each layer, including its Stacked Branches section. That skill checks the layer it is on; this step owns walking the stack, so check out each layer in turn and run it there. Walk bottom to top. A failed rebase checkbox on a lower layer is fixed by rebasing it and restacking everything above, which rewrites those layers' commits. Going upward means each higher layer is gated after that rewrite; top-down gates it before, so its tests, review path and deferred findings describe commits the restack replaced, and Step 4's preview renders that stale record as current. +- The code-review gate from `Skill(creating-pull-request)` Step 1b, per layer. Layer scoping is the wrinkle: both review paths default to `origin/HEAD`, so on layer 3 they re-review layers 1 and 2. That step's own base-ref paragraph is authoritative on which path can be scoped to a single layer and what to record when neither can — follow it rather than a second copy here. + +**Record what each gate produced.** Step 4's preview prints a review path and a deferred-finding count per layer, and nothing else carries them: `creating-pull-request` writes those into its own Step 3 preview, which never runs per layer. So as each layer's gate finishes, note the path taken (`Standard`, `Substantial`, or a user-volunteered skip), every deferred CRITICAL or IMPORTANT finding, and whatever `perform-preflight` reported for its Stacked Branches section — a skip there has to reach the preview, or a layer whose stack checks never ran renders as `preflight: pass`. Carry the review path and the deferred findings into that layer's PR body, and all three into its preview row — the preflight report has a slot only in the preview. + +**Tell the reviewer where the layer sits.** A lower layer legitimately adds code with no caller yet, because the caller lands above it. State the layer's position and what lands above it in the review request, otherwise a reviewer reasonably reads incomplete-by-design work as dead code. + +## Step 3 — Give every layer its Bitwarden conventions + +Each layer is a pull request, so each layer needs the same conventions a single-branch PR does. Invoke `Skill(applying-pr-conventions)` **once per layer** to compose them. + +That skill produces a title, a body, and a label choice, and nothing else — it does not review, preview, push, or create. Which is what makes it safe to call in a loop: this step collects N sets of conventions, Step 4 previews them together, and the submission reference creates the pull requests. Do not route layers through `Skill(creating-pull-request)` instead. That workflow ends in a per-PR preview and its own `gh pr create`, so per layer it would ask the user to authorize a submission the stack has not been previewed for, then push outside the submission sequence and create pull requests that sequence tries to create again. + +Two things this step carries across the loop rather than asking per layer: + +- **The label.** One decision for the whole stack, applied to every layer. Ask it on the first layer and pass the answer into the rest; `applying-pr-conventions` skips its label question when the caller has already settled it. +- **The ticket key.** One ticket normally spans the stack, so the key repeats on every layer. + +The type keyword is the opposite — chosen per layer, since a stack often mixes `feat` and `refactor`. Each layer's body also states its own position in the chain, which is the other reason the body cannot be composed once and reused. + +**`gh stack submit` cannot carry any of this.** Its only flags are `--auto`, `--open`, and `--remote`; there is no title, body, or label flag, and `--auto` generates titles that carry no type prefix. A stack submitted that way ships N pull requests with no `t:` label and no template body. `${CLAUDE_PLUGIN_ROOT}/skills/stacking-pull-requests/references/submitting-a-stack.md` has the two paths that do work and the commands for each. + +## Step 4 — Preview the whole stack, then submit + +`creating-pull-request` shows one submission preview per PR. For a stack, show one preview covering every layer, because a wrong base is the failure this catch-net exists for and it is only visible when the chain is laid out together: + +``` +═══════════════════════════════════════ + STACK SUBMISSION PREVIEW +═══════════════════════════════════════ +Target repo: +Stack base: (trunk, or the release branch layer 1 was cut from) +Draft: (Step 6 runs `gh pr ready` on every draft before merging) +AI review: + + 1. base: → t: