diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index ca57886b..0b37f366 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -78,7 +78,7 @@ { "name": "bitwarden-delivery-tools", "source": "./plugins/bitwarden-delivery-tools", - "version": "3.2.0", + "version": "3.3.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." }, { diff --git a/README.md b/README.md index b9a6acb0..98987712 100644 --- a/README.md +++ b/README.md @@ -4,24 +4,24 @@ A curated collection of plugins for AI-assisted development at Bitwarden. Enable ## Available Plugins -| Plugin | Version | Description | -| ------------------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [bitwarden-ai-telemetry](plugins/bitwarden-ai-telemetry/) | 1.1.0 | Claude Code hooks emitting metadata-only AI-usage telemetry (identity, git-linkage, MCP) via OTLP | -| [bitwarden-tech-lead](plugins/bitwarden-tech-lead/) | 3.0.1 | Tech lead for technical planning, architecture coherence, and surfacing patterns to Technical Strategy Ideas | -| [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.2.0 | Delivery lifecycle skills: initiative funnel navigation, work transitions, architectural judgment, commits, PRs, preflight, labeling, Jira ticket 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 | -| [bitwarden-init](plugins/bitwarden-init/) | 1.2.2 | Initialize and enhance CLAUDE.md files with Bitwarden's standardized template format | -| [bitwarden-product-analyst](plugins/bitwarden-product-analyst/) | 0.1.7 | Product analyst agent for creating comprehensive Bitwarden requirements documents from multiple sources, and writing user-facing release notes | -| [bitwarden-security-engineer](plugins/bitwarden-security-engineer/) | 2.0.0 | Application security engineering: vulnerability triage, threat modeling, and secure code analysis | -| [bitwarden-software-engineer](plugins/bitwarden-software-engineer/) | 1.0.0 | Software engineer agent for a Bitwarden product team. Implements stories, tasks, and bugs with code quality, performance, security, and team comms in mind. | -| [bitwarden-testing-tools](plugins/bitwarden-testing-tools/) | 1.1.0 | Testing tools for analyzing and improving test quality across Bitwarden's repositories. | -| [claude-config-validator](plugins/claude-config-validator/) | 2.0.2 | Validates Claude Code configuration files for security, structure, and quality | -| [claude-retrospective](plugins/claude-retrospective/) | 1.1.1 | Analyze Claude Code sessions to identify successful patterns and improvement opportunities | +| Plugin | Version | Description | +| ------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [bitwarden-ai-telemetry](plugins/bitwarden-ai-telemetry/) | 1.1.0 | Claude Code hooks emitting metadata-only AI-usage telemetry (identity, git-linkage, MCP) via OTLP | +| [bitwarden-tech-lead](plugins/bitwarden-tech-lead/) | 3.0.1 | Tech lead for technical planning, architecture coherence, and surfacing patterns to Technical Strategy Ideas | +| [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.3.0 | Delivery lifecycle skills: initiative funnel navigation, 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 | +| [bitwarden-init](plugins/bitwarden-init/) | 1.2.2 | Initialize and enhance CLAUDE.md files with Bitwarden's standardized template format | +| [bitwarden-product-analyst](plugins/bitwarden-product-analyst/) | 0.1.7 | Product analyst agent for creating comprehensive Bitwarden requirements documents from multiple sources, and writing user-facing release notes | +| [bitwarden-security-engineer](plugins/bitwarden-security-engineer/) | 2.0.0 | Application security engineering: vulnerability triage, threat modeling, and secure code analysis | +| [bitwarden-software-engineer](plugins/bitwarden-software-engineer/) | 1.0.0 | Software engineer agent for a Bitwarden product team. Implements stories, tasks, and bugs with code quality, performance, security, and team comms in mind. | +| [bitwarden-testing-tools](plugins/bitwarden-testing-tools/) | 1.1.0 | Testing tools for analyzing and improving test quality across Bitwarden's repositories. | +| [claude-config-validator](plugins/claude-config-validator/) | 2.0.2 | Validates Claude Code configuration files for security, structure, and quality | +| [claude-retrospective](plugins/claude-retrospective/) | 1.1.1 | Analyze Claude Code sessions to identify successful patterns and improvement opportunities | ## Usage diff --git a/plugins/bitwarden-delivery-tools/.claude-plugin/plugin.json b/plugins/bitwarden-delivery-tools/.claude-plugin/plugin.json index 0ff3c2d1..15e80564 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.2.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.3.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" @@ -16,6 +16,7 @@ "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 58d67790..7030969e 100644 --- a/plugins/bitwarden-delivery-tools/CHANGELOG.md +++ b/plugins/bitwarden-delivery-tools/CHANGELOG.md @@ -5,6 +5,19 @@ 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.3.0] - 2026-09-09 + +### Added + +- **`stacking-pull-requests` skill** — carries Bitwarden's per-PR conventions across a chain of dependent pull requests: layer planning, per-layer gates walked bottom to top, one whole-stack submission preview, lower-layer feedback, and merging. `gh stack` mechanics are delegated to GitHub's `gh-stack` skill and extension, and Step 0 falls back to a single-branch PR whenever either is unusable for the current run. +- `perform-preflight`: a Stacked Branches section covering the current layer, with its own stack detection. It stops when a commit is about to land on a layer that already has an open pull request, since that rebase belongs to `stacking-pull-requests` Step 5. +- `committing-changes`: `description` gains a stack boundary so stack-level requests route to `stacking-pull-requests`. The body stays stack-agnostic, since a layer is a branch and the existing "first commit on a branch" rule already applies per layer. + +### Changed + +- `creating-pull-request`: routes chain requests to `stacking-pull-requests`, and accepts a single pull request back from it when the stack path is unavailable. Its review gate runs per layer when that skill drives it. +- `applying-pr-conventions`: invoked once per layer by the stack path. + ## [3.2.0] - 2026-09-09 ### Added diff --git a/plugins/bitwarden-delivery-tools/README.md b/plugins/bitwarden-delivery-tools/README.md index 3dd79624..f0f6d08d 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 @@ -41,7 +41,8 @@ Any agent (tech-lead, software-engineer, shepherds, others) can compose these sk | `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 | +| `perform-preflight` | "preflight", "self review" | Pre-commit quality gate checklist, plus 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 @@ -55,7 +56,8 @@ Several skills in this plugin reference tools or skills provided by sibling plug - **`bitwarden-atlassian-tools`** — provides the Jira/Confluence MCP tools used by `navigating-the-initiative-funnel`, and the `filing-jira-tickets` skill plus its opt-in Jira write tools that `filing-breakdown-tasks` hands off to. - **`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. +- **`gh-stack`** — GitHub's [`gh-stack`](https://github.com/github/gh-stack) owns the `gh stack` surface that `stacking-pull-requests` delegates to. It needs both the extension (`gh extension install github/gh-stack`) and the `gh-stack` skill from that repo's `skills/gh-stack/`, since the extension install places only the binary. Missing either, stack requests fall back to a single-branch PR. +- **`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. Also supplies `Skill(addressing-code-review-comments)`, which `stacking-pull-requests` uses for lower-layer feedback and treats as optional. ## Installation @@ -83,6 +85,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/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 ba675b39..0896fc17 100644 --- a/plugins/bitwarden-delivery-tools/skills/creating-pull-request/SKILL.md +++ b/plugins/bitwarden-delivery-tools/skills/creating-pull-request/SKILL.md @@ -1,6 +1,6 @@ --- name: creating-pull-request -description: 'Open a pull request from a branch in a Bitwarden repository. Use whenever the user wants a branch turned into a pull request, however they phrase it — "create a PR", "open a PR", "ship a draft", "ship it", "ready for review", "put it up for review", "get this in front of reviewers", "send it over to the team", "throw together a pull request", "wrap this branch up". Use it too when the request asks to open the PR and also asks what the title or t: label should be, and when the user says the title and body are already settled and only the PR needs opening — the gate, the preview, and the submission still apply. Runs the required local code-review gate, resolves the base branch, takes the title, template body, and ai-review label from applying-pr-conventions, confirms a full submission preview, then pushes and runs gh pr create. Not for composing a title, body, or label when no PR is being opened (that is applying-pr-conventions), 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. Use whenever the user wants a branch turned into a pull request, however they phrase it — "create a PR", "open a PR", "ship a draft", "ship it", "ready for review", "put it up for review", "get this in front of reviewers", "send it over to the team", "throw together a pull request", "wrap this branch up". Use it too when the request asks to open the PR and also asks what the title or t: label should be, and when the user says the title and body are already settled and only the PR needs opening — the gate, the preview, and the submission still apply. Runs the required local code-review gate, resolves the base branch, takes the title, template body, and ai-review label from applying-pr-conventions, confirms a full submission preview, then pushes and runs gh pr create. Not for composing a title, body, or label when no PR is being opened (that is applying-pr-conventions), a chain of dependent pull requests (stacking-pull-requests), conceptual questions ("how do PRs work"), or managing existing PRs (status, merging, addressing comments).' --- # Creating a Pull Request @@ -13,6 +13,8 @@ None of the three is cheap to undo. A PR opened on unreviewed work buries the re 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. The reverse direction is the exception: an invocation stating the stack path was already tried and is unavailable for this run is that skill handing a single ordinary pull request _here_, so run the whole workflow, gate included. Handing it back is the loop that exception exists to prevent. + ### Step 1 — Confirm preflight, then run the code-review gate A PR opened on broken work, or on work that skipped review, wastes reviewer time and buries the real problem under comment threads. Settle preflight first, then run the review. @@ -38,6 +40,8 @@ If preflight cannot be made to pass, stop and report the failure rather than ope - `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 the base branch resolved in 1b (not just uncommitted changes); there is no PR yet +**Scoping to one layer of a stack.** When `stacking-pull-requests` drives this per layer, scope the review to that layer or it re-reviews every layer below. Only `Substantial` can be scoped: use its commit-range mode with `..`. Name `Substantial` as the layer-scoped option when asking, 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 AI-assisted review section (Step 2) and surface it in the Step 3 preview. Never skip on your own initiative. After the review: @@ -48,7 +52,9 @@ After the review: 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. -**This gate runs on every entry.** There is no caller exemption. A skill that wants the title, body, and label rather than this whole workflow should invoke `Skill(applying-pr-conventions)` directly, which is what `force-multiplier` does; `force-multiplier` is therefore a peer of this workflow rather than a caller of it. +**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. + +Otherwise the gate runs on every entry, including the stack-fallback case above, which bailed before its own gate. A skill that wants the title, body, and label rather than this whole workflow should invoke `Skill(applying-pr-conventions)` directly, which is what `force-multiplier` does; `force-multiplier` is therefore a peer of this workflow rather than a caller of it. ### Step 2 — Compose the title, body, and label @@ -121,7 +127,7 @@ 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 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. +- 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. diff --git a/plugins/bitwarden-delivery-tools/skills/perform-preflight/SKILL.md b/plugins/bitwarden-delivery-tools/skills/perform-preflight/SKILL.md index 5436c6af..31f29324 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,44 @@ 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** — the `gh stack view --json` payload just fetched carries it per layer; read the current branch's entry rather than making a second call. If it has one **and a commit is about to land on it**, that fix 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. A commit is what this stop is about; a gate-only pass over a layer strands nothing. 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. + +**Two invocations are not that case, and neither stops.** `stacking-pull-requests` Step 5 does commit on the layer, but it then restacks everything above behind its own confirmation listing every pull request that gets force-pushed — it is the sanctioned route for this fix, and stopping here would block the one path allowed to take it. Step 2 commits nothing: it walks the stack gating each layer, and on a stack already on GitHub every layer legitimately has an open pull request, so stopping would halt that walk on the bottom layer. Run the section normally when the invocation states either one. The claim has to be explicit — an invocation that merely happens to sit on a layer with an open pull request, with a commit behind it, is the case the stop exists for. + +- 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. The assertion has to carry the two things the absent payload would have supplied: the parent branch, which the rebase checkbox needs, and whether this layer already has an open pull request, which both the stop above and the On Failure branch turn on. Missing either, report the rebase checkbox unverifiable rather than guessing — treating an unstated pull request as absent would restack the layers above on an unverified premise, and "nothing to force-push" is the one claim this section cannot afford to assume. +- 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 +- [ ] 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. + +**On the rebase checkbox.** `` 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. + +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. + +**When the layer has no pull request yet**, 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. If `gh-stack` is not resolvable, report the checkbox failed and stop rather than improvising the rebase by hand: this section's entry gate only proves the extension is present, and `Skill(stacking-pull-requests)` Step 0 calls extension-present-skill-absent the likeliest combination. + +**When it already has one**, do not fix it here — and that holds even when Step 5 is driving. The rebase rewrites and force-pushes every layer above, which needs the kind of confirmation `stacking-pull-requests` Step 5 takes before its own restack. But Step 5 rebases the layers _above_ a fix; it has no step that rebases this layer onto its parent, so a layer that has drifted from its parent is outside its flow too. Report it and stop, and let the user decide — this is the one branch no caller is sanctioned for. + +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..128d41af --- /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 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`. An install counts immediately: `gh extension list` reflects it in this run. +- **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. Fall back to `Glob` over those two paths only if the name is absent there, and only to tell _not installed_ apart from _installed but not yet loaded_ — a `Glob` hit is never grounds for continuing. Do not probe by invoking `Skill(gh-stack)`: invoking a missing skill is itself an error, inside a gate whose job is to fail cleanly into the fallback. `gh extension install` fetches only a binary and never places the skill, so it installs separately; the reference above has that procedure and the commit to verify the copy against. + +**The condition for continuing is that both are usable in this run** — extension present, skill resolvable. An install does not satisfy that for the skill: Claude Code discovers skills at session start, so a freshly copied `SKILL.md` is on disk but unresolvable until a restart, and the available-skills listing stays fixed for the session while `Glob` finds the file. If either is missing, name which one and its remedy, and ask whether to install it. + +**The fallback covers every outcome that leaves the skill unresolvable for this run** — the user declines, an install fails, or an install succeeds. In all three the change ships as a single pull request: hand off to `Skill(creating-pull-request)`, stating 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 report a decline when it was installed — say the install succeeded and that a restart gets the stack path next run. Never continue into Step 1 on an unresolvable skill: that delegates `gh stack init`, navigation, rebasing, and the merge to something not loaded, which is the half-attempted stack this step forbids, after the user consented to an install and after the layers are planned. + +**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`. Exit `0` means the current branch is in a stack; treat anything else as not in one, and require that a successful payload names the current branch rather than trusting the code alone. Do not run it through a pipe when you need that status — the shell reports the last command's exit code, not `gh`'s. `installing-gh-stack.md` has what each status means; the one worth catching here is exit `9`, the repository not having 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. Use `init` explicitly rather than reaching for `gh stack add` — since v0.1.1 `add` on a non-stack branch offers to initialize a stack interactively, and a prompt nobody answers hangs the run. `` 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. **Say that this step is gating and committing nothing.** On a stack already on GitHub every layer has an open pull request, and that section stops on a pull request with a commit behind it — so state that there is none, or the walk halts on the bottom layer. 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, the resolved `t:` label the title's prefix will produce, and a label choice — and nothing else; it does not review, preview, push, or create. Keep the `t:` value per layer: Step 4's preview prints it per row and has no other source for it. 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: