diff --git a/AGENTS.md b/AGENTS.md index 416a5ef..ffe90ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,6 +17,7 @@ This repository contains reusable AI coding workflows that can be installed glob - **implement** — Story-to-code workflow (ingest, plan, revise, code, validate, publish, respond) - **kcs** — KCS Solution article workflow (gather, draft, validate, handoff) - **prd** — Requirements-to-PRD workflow (ingest, clarify, draft, revise, publish, respond) +- **rebase-stack** — Rebase a stacked-branch chain with conflict guidance, per-branch validation, and push (start, continue, validate, push) - **sizing** — Pre-cycle Feature sizing with T-shirt sizes and team effort breakdowns (ingest, assess, apply) - **skill-reviewer** — Meta-workflow that audits AI skill directories - **triage** — Bulk Jira bug triage with AI-driven categorization and HTML reports @@ -146,6 +147,7 @@ ai-workflows/ ├── implement/ ├── kcs/ ├── prd/ +├── rebase-stack/ ├── sizing/ ├── skill-reviewer/ │ ├── prompts/ diff --git a/README.md b/README.md index e8256ee..8056e77 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,9 @@ Reusable AI coding workflows a team member can install globally or per-project, - **KCS** -- KCS Solution article workflow: gather bug context from Jira, draft a KCS article in markdown, validate against the KCS Content Standard, and produce a handoff message for the support engineer. See [kcs/README.md](kcs/README.md). +- **Rebase Stack** -- Rebase a stacked-branch chain onto an updated base using `gh stack`, guide through conflict resolution, validate each branch, and push all updated branches. + See [rebase-stack/README.md](rebase-stack/README.md). + - **Sizing** -- Pre-cycle Feature sizing: ingest Features from Jira (single or batch by Fix Version), assess against a T-shirt size rubric (XS–XXL) with per-team effort breakdowns (DEV, QE, UX, UI, DOCS), and write results back to Jira. See [sizing/README.md](sizing/README.md). diff --git a/rebase-stack/README.md b/rebase-stack/README.md new file mode 100644 index 0000000..a15eade --- /dev/null +++ b/rebase-stack/README.md @@ -0,0 +1,121 @@ +# Rebase Stack Workflow + +Rebases a stacked-branch chain onto an updated base branch using `gh stack`, +guides you through conflict resolution if needed, validates each branch, and +pushes all updated branches. Creates any missing PRs afterwards, with +fork-aware targeting. + +## Phase Flow + +``` +/start ──── success ──────────────────────── /validate ──── /push ──── done + └─── conflict ──── (resolve) ──── /continue ──┐ + │ + conflict ──── (resolve) ────┘ + success ──────────────── /validate ──── /push ──── done +``` + +## Prerequisites + +| Tool | Purpose | +|------|---------| +| `git` | Rebase and push operations | +| `gh` (GitHub CLI) | Stack management, PR creation | +| `gh-stack` extension | `gh extension install github/gh-stack` | +| Remote access (`origin`) | Fetch and push | + +`/start` installs the extension automatically if it is not found. + +> **Note:** `gh-stack` is in private preview. The CLI extension installs for +> anyone, but the Stacked PRs feature (used by `gh stack submit`) requires +> enablement on the upstream repository. If `gh stack submit` exits with +> code 9, the repo does not have it enabled — the skill falls back to +> `gh pr create` automatically. + +## Stack Initialization + +`/start` detects whether your branches are already tracked by `gh stack`: + +- **Already tracked** (`gh stack view --json` exits 0): proceeds directly to + the rebase. +- **Not yet tracked** (exit code 2): discovers branches from the commit graph + (`git log --reverse --decorate origin/{base}..HEAD`), presents the + bottom-to-top list for your confirmation, then runs: + ```bash + gh stack init --base {base} branch-1 branch-2 branch-3 ... + ``` + `gh stack` adopts the existing branches and detects any open PRs automatically. + +You only need to initialize once per stack. Subsequent `/start` runs skip +this step. + +## Commands + +| Command | When to use | +|---------|-------------| +| `/start` | Begin the rebase. Takes an optional base branch (default: `main`). Installs `gh-stack` if needed, initializes the stack if not yet tracked, then rebases. Local only — does not push. | +| `/continue` | Resume after resolving a conflict mid-rebase. Repeat as needed. Local only — does not push. | +| `/validate` | After rebase completes: re-fetches to check remote currency, runs lint and unit tests on each branch independently, and shows a branch overview table. Does not push. | +| `/push` | After validation: asks for confirmation, pushes all branches atomically with `gh stack push`, then creates any missing PRs with fork-aware targeting. | + +## Usage Examples + +### Typical: rebase a stack onto updated main + +You have three branches stacked: `main → story-1 → story-2 → story-3 (HEAD)`. +Main has moved forward. Run: + +``` +/start +``` + +The agent checks for `gh-stack`, detects the stack (or initializes it), +then runs `gh stack rebase --remote origin`. On success it prompts you to +run `/validate`, which checks each branch and shows an overview table. Then +run `/push` to push all branches atomically with `gh stack push` after +confirmation. + +### With conflicts + +Same scenario, but `story-2` conflicts with a change on main: + +1. `/start` — rebase stops at `story-2`, shows the conflicting files +2. Resolve conflicts. For "old version vs evolved version" conflicts (the lower + branch was rebased and this branch has older copies): `git checkout --ours && git add `. + For genuine new-code conflicts: merge manually, then `git add `. Do not `git commit`. +3. `/continue` — rebase resumes; if clean, prompts you to run `/validate` +4. If another conflict appears, repeat steps 2–3 +5. `/validate` — shows branch overview table after per-branch lint/test +6. `/push` — pushes after confirmation, creates any missing PRs + +### Rebasing onto a branch other than main + +``` +/start feature/platform-upgrade +``` + +The agent fetches `origin/feature/platform-upgrade` and rebases onto it. + +### Fork-based workflow (no Stacked PRs) + +After `/push` completes the push, it tries `gh stack submit --auto`. If the +upstream repo does not have Stacked PRs enabled (exit code 9), it falls back +to `gh pr create` with `--head fork-owner:branch --repo upstream/repo`, setting +`Depends on: #N` in each PR body. All PRs target the upstream default branch. + +## Artifacts + +Local state written to `.artifacts/rebase-stack/` during the workflow (gitignored): + +| File | Written by | Purpose | How to clear | +|------|-----------|---------|--------------| +| `validation-cache` | `/validate` | `branch:sha` pairs for each branch that has passed validation. Persists across runs; stale entries are ignored automatically when SHAs change after a rebase. | `rm .artifacts/rebase-stack/validation-cache` to force full re-validation on the next `/validate` run. | +| `logs/{branch}.log` | `/validate` | Full lint and test output per branch. Kept on failure for inspection; deleted on success. | Inspect directly; deleted automatically on the next successful `/validate` run. | + +## Push Safety + +- Branches are pushed **atomically**: `gh stack push` uses `--force-with-lease + --atomic`, so if any remote branch was updated since the last fetch, the + entire push is rejected rather than partially applied. +- Protected branches (`main`, `master`, `develop`, PR targets) are never + pushed — only the stack branches above them. diff --git a/rebase-stack/SKILL.md b/rebase-stack/SKILL.md new file mode 100644 index 0000000..1ebb07f --- /dev/null +++ b/rebase-stack/SKILL.md @@ -0,0 +1,25 @@ +--- +name: rebase-stack +version: 0.1.0 +description: >- + Rebases a stacked-branch chain onto an updated base branch using gh-stack, + guides through conflict resolution if needed, validates each branch, and + pushes all updated branches. Creates PRs for any branch that lacks one, + with fork-aware targeting. Works on repos with or without Stacked PRs enabled. + Use when rebasing stacked stories or PRs onto main or another updated branch. + Activated by commands: /start, /continue, /validate, /push. +--- +# Rebase Stack Workflow + +## Quick Start + +1. If the user invoked `/start`, read `commands/start.md` and follow it. +2. If the user invoked `/continue`, read `commands/continue.md` and follow it. +3. If the user invoked `/validate`, read `commands/validate.md` and follow it. +4. If the user invoked `/push`, read `commands/push.md` and follow it. +5. Otherwise, read `skills/controller.md` and present the available phases to the user. + +If a step fails or produces unexpected output, stop and report to the user. +Always wait for the user before advancing to the next phase. + +For safety rules, see `guidelines.md`. diff --git a/rebase-stack/commands/continue.md b/rebase-stack/commands/continue.md new file mode 100644 index 0000000..14f50a6 --- /dev/null +++ b/rebase-stack/commands/continue.md @@ -0,0 +1,11 @@ +--- +name: rebase-stack:continue +description: Resume a rebase after conflict resolution, then force-push updated branches. +--- +# /continue + +Read `../skills/controller.md` and follow it. + +Dispatch the **continue** phase. Context: + +$ARGUMENTS diff --git a/rebase-stack/commands/push.md b/rebase-stack/commands/push.md new file mode 100644 index 0000000..884624d --- /dev/null +++ b/rebase-stack/commands/push.md @@ -0,0 +1,11 @@ +--- +name: rebase-stack:push +description: Push all stack branches atomically to origin and create any missing PRs. +--- +# /push + +Read `../skills/controller.md` and follow it. + +Dispatch the **push** phase. Context: + +$ARGUMENTS diff --git a/rebase-stack/commands/start.md b/rebase-stack/commands/start.md new file mode 100644 index 0000000..682f074 --- /dev/null +++ b/rebase-stack/commands/start.md @@ -0,0 +1,11 @@ +--- +name: rebase-stack:start +description: Verify gh-stack, detect or initialize the stack, then rebase it onto the updated base. +--- +# /start + +Read `../skills/controller.md` and follow it. + +Dispatch the **start** phase. Context: + +$ARGUMENTS diff --git a/rebase-stack/commands/validate.md b/rebase-stack/commands/validate.md new file mode 100644 index 0000000..6364959 --- /dev/null +++ b/rebase-stack/commands/validate.md @@ -0,0 +1,11 @@ +--- +name: rebase-stack:validate +description: Check remote currency, validate each branch, and show a push overview. +--- +# /validate + +Read `../skills/controller.md` and follow it. + +Dispatch the **validate** phase. Context: + +$ARGUMENTS diff --git a/rebase-stack/guidelines.md b/rebase-stack/guidelines.md new file mode 100644 index 0000000..43641c1 --- /dev/null +++ b/rebase-stack/guidelines.md @@ -0,0 +1,41 @@ +# Rebase Stack Guidelines + +## Safety Rules + +- **Push with `gh stack push --remote origin`.** It uses `--force-with-lease --atomic` + internally, so if the remote was updated since your last fetch the entire push is + rejected rather than partially applied. +- **Never push protected branches.** Do not push to `main`, `master`, `develop`, or any + branch that is the PR target — only push the stack branches above it. +- **Confirm before pushing.** Always present the full list of branches to be pushed and + wait for explicit user confirmation. +- **Stop on unexpected state.** If git or `gh stack` output is ambiguous or unexpected, + stop and report rather than guess. + +## Conflict Handling + +- When a conflict occurs, show the user the commit that caused it and the conflicting files, + then ask whether they want the agent to fix it or prefer to fix it themselves. Do not + attempt resolution before the user answers. +- **Conflict strategy:** When the conflict is "old version vs evolved version" (a lower + branch was rebased and this branch carries older copies of the same commits), take + `--ours`. In a rebase, `--ours` is the onto-branch (the lower branch's updated tip), + so it keeps the evolved version and discards the stale copy: + ```bash + git checkout --ours + git add + ``` + For conflicts involving genuinely new code unique to this branch, manually merge both sides. +- Instruct the user to resolve, `git add` the resolved files (do NOT `git commit`), + then run `/continue`. +- `gh stack rebase --continue` resumes from where the rebase paused — do not restart + from scratch. + +## PR Creation + +- After pushing, check which branches lack an open PR (use `gh stack view --json`). +- For repos with Stacked PRs enabled: try `gh stack submit --auto`. If it succeeds, done. +- For forks or repos where Stacked PRs are not enabled (exit code 9): create PRs manually + with `gh pr create`, setting `--head owner:branch` and `--repo upstream` for forks. +- All PRs in a fork-based workflow target the upstream default branch (PR Target), not + each other's branches. Use `Depends on: #N` in the PR body to express ordering. diff --git a/rebase-stack/skills/continue.md b/rebase-stack/skills/continue.md new file mode 100644 index 0000000..b946c34 --- /dev/null +++ b/rebase-stack/skills/continue.md @@ -0,0 +1,76 @@ +--- +name: continue +description: Resume a gh stack rebase after conflict resolution. +--- + +# Continue Rebase Stack + +## Process + +### Step 1: Verify Conflicts Are Resolved + +```bash +git diff --name-only --diff-filter=U +``` + +If any files are listed, conflicts are still unresolved. Tell the user to +resolve them and `git add` each file before running `/continue` again. + +### Step 2: Continue the Rebase + +```bash +gh stack rebase --continue --remote origin +``` + +Capture the full output. Two outcomes: + +--- + +**On conflict** — another commit in the stack conflicted. Show the user: + +1. The commit that caused the conflict: + ```bash + git rebase --show-current-patch 2>/dev/null | head -10 + ``` +2. The conflicting files: + ```bash + git diff --name-only --diff-filter=U + ``` + +Then ask: + +> A conflict was detected. How would you like to proceed? +> - **Fix it for me** — I'll attempt to resolve the conflicts and continue. +> - **I'll fix it** — Pause here; I'll resolve the conflicts manually and run `/continue` when ready. + +**Stop here.** Wait for the user's choice before doing anything. + +If the user asks you to fix it: examine each conflicting file. For files where +the conflict is "old version vs evolved version" (this branch carries a stale +copy of a commit that the lower branch already updated), apply `--ours` — in a +rebase, `--ours` is the onto-branch (the lower branch's updated tip): + +```bash +git checkout --ours +git add +``` + +For files with genuinely new code on both sides, show the conflict markers and +ask the user which version to keep before proceeding. + +Once all files are resolved and staged, run `gh stack rebase --continue --remote origin` automatically. + +If the user wants to fix it themselves: instruct them to resolve the conflicts, +then `git add ` (do **not** `git commit`), then run `/continue` again. + +--- + +**On success** — proceed to Step 3. + +--- + +### Step 3: Report and Hand Off + +Tell the user: + +> Rebase complete. Run `/validate` to validate each branch before pushing. diff --git a/rebase-stack/skills/controller.md b/rebase-stack/skills/controller.md new file mode 100644 index 0000000..1f75537 --- /dev/null +++ b/rebase-stack/skills/controller.md @@ -0,0 +1,46 @@ +--- +name: controller +description: Phase dispatcher for the rebase-stack workflow. +--- + +# Rebase Stack Controller + +## Phases + +1. **Start** (`/start`) — `start.md` + Verify the `gh-stack` extension, detect or initialize the stack, then rebase + it onto the updated base with `gh stack rebase`. Local-only — does not push. + On success, hands off to `/validate`. + +2. **Continue** (`/continue`) — `continue.md`. Repeatable. + Only needed when `/start` (or a previous `/continue`) stopped due to a + merge conflict. Resumes the rebase after the user resolves conflicts. + Local-only — does not push. On success, hands off to `/validate`. + +3. **Validate** (`/validate`) — `validate.md` + Re-fetches to check remote currency, validates lint and unit tests on each + branch independently, and shows a branch overview table. Does not push. + On success, hands off to `/push`. + +4. **Push** (`/push`) — `push.md` + Pushes all branches atomically with `gh stack push`, then creates any + missing PRs with fork-aware targeting. + +### Typical Flow + +```text +start → (success) → validate → push → done +start → (conflict) → [continue loop] → (success) → validate → push → done +``` + +## How to Execute a Phase + +1. Read `../guidelines.md` for safety rules before executing any phase. +2. Announce the phase to the user. +3. Read and follow the corresponding skill file. +4. Stop and wait for the user between phases — do not auto-advance. + +## Rules + +- **Never auto-advance.** Always wait for the user between phases. +- Stop and report on any unexpected git or `gh stack` state. Do not guess or continue past errors. diff --git a/rebase-stack/skills/push.md b/rebase-stack/skills/push.md new file mode 100644 index 0000000..a48e3c3 --- /dev/null +++ b/rebase-stack/skills/push.md @@ -0,0 +1,89 @@ +--- +name: push +description: Confirm, push all stack branches atomically, and create any missing PRs. +--- + +# Push Rebase Stack + +## Process + +### Step 1: Push + +```bash +gh stack push --remote origin +``` + +`gh stack push` uses `--force-with-lease --atomic` internally: if any remote +branch was updated since the last fetch in `/validate`, the entire push is +rejected rather than partially applied. + +If the exit code is non-0, stop immediately and report the exit code and +full stderr. Do not retry without re-running `/validate` (which re-fetches) +first. A lease rejection means a concurrent push occurred; any other failure +(e.g. exit 4 GitHub API error, exit 8 lock timeout) requires the user to +investigate before retrying. + +### Step 2: Create Missing PRs + +Using `{existing-pr}` from `/validate`, identify branches where the value is +`null`. If all branches already have open PRs, skip this step and report. + +**Detect repo topology:** + +```bash +gh repo view --json isFork,owner,parent --jq '{isFork: .isFork, owner: .owner.login, parent: .parent.nameWithOwner}' +``` + +Save `isFork`, `owner` (the fork owner login), and `parent` (upstream +`nameWithOwner`, e.g. `flightctl/flightctl`). + +**If `isFork` is `true`**: skip `gh stack submit` entirely and go straight to +manual PR creation below. `gh stack submit` targets the fork itself, not the +upstream, so it would create PRs on the wrong repository. + +**If `isFork` is `false`**: try Stacked PRs (works when the upstream has the +feature enabled): + +```bash +gh stack submit --auto +``` + +- Exit code 0: PRs created and linked as a stack. Done — skip manual creation. +- Any non-zero exit (including exit code 9): fall through to manual creation. + +**Manual PR creation** (always used for forks; fallback for direct clones): + +For each branch in `{push-list}` where `{existing-pr}` is `null`, in +bottom-to-top order: + +For a **fork**: +```bash +gh pr create \ + --head {owner}:{branch} \ + --base {pr-target} \ + --repo {parent} \ + --title "{branch-title}" \ + --draft \ + --body "Depends on: #{previous-pr-number}" +``` + +For a **direct clone**: +```bash +gh pr create \ + --head {branch} \ + --base {pr-target} \ + --title "{branch-title}" \ + --draft \ + --body "Depends on: #{previous-pr-number}" +``` + +Use `{pr-target}` from `/validate`. Omit the `Depends on:` line for the bottom +branch of the stack (it has no predecessor). Use the PR number returned by each +`gh pr create` call as `{previous-pr-number}` for the next branch. + +### Step 3: Report + +Summarize: +- Each branch pushed and its new tip commit +- Each PR created (number and URL) or already existing +- Any push rejection diff --git a/rebase-stack/skills/start.md b/rebase-stack/skills/start.md new file mode 100644 index 0000000..813b5fe --- /dev/null +++ b/rebase-stack/skills/start.md @@ -0,0 +1,139 @@ +--- +name: start +description: Verify gh-stack, detect or initialize the stack, then rebase onto the updated base. +--- + +# Start Rebase Stack + +## Process + +### Step 1: Verify gh-stack Extension + +```bash +gh extension list 2>/dev/null | grep -q 'github/gh-stack' +``` + +If not found, install it and configure git: + +```bash +gh extension install github/gh-stack +git config rerere.enabled true +``` + +If multiple remotes are configured (check with `git remote`), also run: + +```bash +git config remote.pushDefault origin +``` + +### Step 2: Verify Clean Working Tree + +```bash +git status +``` + +If there are unstaged changes or untracked files that could interfere, stop +and ask the user to stash or commit them first. + +### Step 3: Detect or Initialize the Stack + +Check if the current branch belongs to a tracked stack: + +```bash +gh stack view --json 2>&1; echo "exit:$?" +``` + +**If exit code 0**: stack already exists. Proceed to Step 4. + +**If exit code 2 ("not in a stack")**: adopt the existing branches. + +Discover branches from the commit graph. If the user provided a base branch, +use it; otherwise default to `main`: + +```bash +git log --reverse --oneline --decorate origin/{base}..HEAD +``` + +Read through the output and collect each local branch label the first time it +appears, in that order. Exclude `HEAD ->` aliases and `origin/` remote-tracking +refs. The resulting list is ordered bottom-to-top (closest to trunk first). + +Present the discovered list to the user and confirm the order before proceeding. +If the order is incorrect, ask the user to provide the correct branch list +(bottom to top) and use their provided order in the `gh stack init` call. + +Then initialize the stack: + +```bash +gh stack init --base {base} \ + {branch-1} \ + {branch-2} \ + ... +``` + +`gh stack` will report "Adopted N branches" and detect any existing PRs +automatically. Verify with: + +```bash +gh stack view --json +``` + +### Step 4: Rebase + +```bash +gh stack rebase --remote origin +``` + +Capture the full output. Two outcomes: + +--- + +**On success** — proceed to Step 5. + +--- + +**On conflict** — `gh stack rebase` will stop. Show the user: + +1. The commit that caused the conflict: + ```bash + git rebase --show-current-patch 2>/dev/null | head -10 + ``` +2. The conflicting files: + ```bash + git diff --name-only --diff-filter=U + ``` + +Then ask: + +> A conflict was detected. How would you like to proceed? +> - **Fix it for me** — I'll attempt to resolve the conflicts and continue. +> - **I'll fix it** — Pause here; I'll resolve the conflicts manually and run `/continue` when ready. + +**Stop here.** Wait for the user's choice before doing anything. + +If the user asks you to fix it: examine each conflicting file. For files where +the conflict is "old version vs evolved version" (this branch carries a stale +copy of a commit that the lower branch already updated), apply `--ours` — in a +rebase, `--ours` is the onto-branch (the lower branch's updated tip): + +```bash +git checkout --ours +git add +``` + +For files with genuinely new code on both sides, show the conflict markers and +ask the user which version to keep before proceeding. + +Once all files are resolved and staged, run `/continue` automatically. + +If the user wants to fix it themselves: instruct them to resolve the conflicts, +then `git add ` (do **not** `git commit`), then run `/continue`. + +--- + +### Step 5: Hand Off + +Tell the user: + +> Rebase onto `origin/{base}` complete. Run `/validate` to validate each +> branch before pushing. diff --git a/rebase-stack/skills/validate.md b/rebase-stack/skills/validate.md new file mode 100644 index 0000000..5336def --- /dev/null +++ b/rebase-stack/skills/validate.md @@ -0,0 +1,215 @@ +--- +name: validate +description: Verify the rebase is complete, check remote currency, validate each branch, and show a push overview. +--- + +# Validate Rebase Stack + +## Process + +### Step 1: Verify Rebase Is Complete + +```bash +git rebase --show-current-patch 2>&1 +``` + +If this outputs patch content (rather than an error saying no rebase is in +progress), a rebase is still ongoing. Tell the user to resolve any remaining +conflicts and run `/continue` before validating. + +### Step 2: Collect Stack State + +```bash +gh stack view --json +``` + +If the exit code is not 0, stop and report the exit code and stderr. Do not +proceed. For exit code 6 (branch belongs to multiple stacks), instruct the +user to run `gh stack checkout ` to resolve the disambiguation +before retrying. + +From the JSON: + +- Collect the `name` of every branch where `isMerged` is `false`, in the order + they appear in the `branches` array (bottom to top). Save this as + **`{push-list}`**. +- Note the `trunk` value. Save this as **`{pr-target}`** — used as the PR base + in `/push`. +- For each branch, note whether a `pr` object is present and its PR number if + so, or `null` if absent. Save this as **`{existing-pr}`** per branch — used + in `/push` to skip branches that already have an open PR. + +### Step 3: Refresh Remote and Detect Changes + +Fetch to refresh remote-tracking refs (needed for tip comparison and so that +`gh stack push --force-with-lease` has accurate data): + +```bash +git fetch origin +``` + +For each branch in `{push-list}`, compare local and remote tip SHAs: + +```bash +git rev-parse {branch} +git rev-parse origin/{branch} 2>/dev/null || echo "(not on remote)" +``` + +Record whether the local tip differs from `origin/{branch}`. Save this as +**`{tip-changed}`** per branch (`true` / `false`). Branches not yet on the +remote count as changed. + +There is no need to block on remote-ahead commits — after a rebase the remote +naturally holds the pre-rebase history, which diverges from the new local tips. +`gh stack push --force-with-lease --atomic` provides the real safety net: if +someone pushes to origin between now and `/push`, the entire push is rejected. + +### Step 4: Determine Validation Range + +Each branch will become an independent PR that CI runs against on its own. +Because branches are cumulative, a lower branch changing affects the state of +every branch above it — even if those upper branches' own commits didn't move. + +The cache (`.artifacts/rebase-stack/validation-cache`) records every branch +that has passed validation as `branch:sha` lines. It persists across runs. +After a rebase, branch SHAs change and the stale cache entries are ignored +automatically — no explicit clear is needed. + +```bash +cat .artifacts/rebase-stack/validation-cache 2>/dev/null +``` + +Scan `{push-list}` bottom-to-top. For each branch: + +```bash +git rev-parse {branch} +``` + +Find `{first-to-validate}` — the lowest branch where either: +- the branch is not in the cache, or +- its current SHA differs from the cached SHA. + +**All branches strictly below `{first-to-validate}`** are skipped and marked +`— skip (already validated)`. Do not apply the cache check to branches at or +above `{first-to-validate}` — they must be validated unconditionally because +their cumulative base has changed. + +If every branch is cached with a matching SHA: skip all validation, mark +every branch `— skip (already validated)`, and proceed to Step 8. + +### Step 5: Set Up Worktrees + +Discover the validation commands once from the project's `AGENTS.md`, +`CLAUDE.md`, or `Makefile` (lint and unit tests at minimum). If none of +those sources document the commands, ask the user before proceeding. + +Get the currently checked-out branch in the main working tree: + +```bash +git branch --show-current +``` + +For each branch in the validation range, set up its work directory: + +- If the branch is the currently checked-out branch in the main working tree: + use the repo root as `{work-dir}` for this branch. No worktree needed. +- Otherwise, create a worktree (branch name with `/` replaced by `-`) and + save its path as `{work-dir}` for this branch: + + ```bash + mkdir -p .artifacts/rebase-stack/worktrees + git worktree add .artifacts/rebase-stack/worktrees/{branch-safe-name} {branch} + # {work-dir} = .artifacts/rebase-stack/worktrees/{branch-safe-name} + ``` + + If `git worktree add` fails for any reason other than "already checked out + in the main working tree": stop, report the error, and ask the user to run + `git worktree list` to inspect and `git worktree remove --force` to clean up + any stale entries before retrying. + +### Step 6: Run Validation + +Always capture full output to a log file — never pipe to `tail`, `head`, or +any truncating filter. On pass, show a one-line summary. On failure, report +the log file path so the user can inspect it; do not dump the full contents +into the chat. + +For each branch in the validation range, bottom-to-top: + +```bash +mkdir -p .artifacts/rebase-stack/logs +(cd {work-dir} && {lint command} && {unit test command}) > .artifacts/rebase-stack/logs/{branch-safe-name}.log 2>&1 +exit_code=$? +``` + +- Exit 0: show a one-line pass summary and append to cache: + ```bash + echo "{branch}: ✓ pass" + echo "{branch}:$(git rev-parse {branch})" >> .artifacts/rebase-stack/validation-cache + ``` +- Non-zero: report the failure and log path, then proceed to Step 7: + ``` + {branch}: ✗ fail — see .artifacts/rebase-stack/logs/{branch-safe-name}.log + ``` + +### Step 7: Clean Up and Report + +Remove log files created in Step 6 on success only — on failure they are +kept for the user to inspect: + +```bash +# only if all branches passed: +rm -f .artifacts/rebase-stack/logs/{branch-safe-name}.log +# repeat for each branch in the validation range +``` + +Remove all worktrees created in Step 5 (always, regardless of outcome): + +```bash +git worktree remove --force .artifacts/rebase-stack/worktrees/{branch-safe-name} +# repeat for each worktree created (skip branches that used the main working tree) +``` + +**On any failure:** the cache already contains all branches that passed. +Tell the user: + +> Validation failed on `{failing-branch}`. +> Full output: `.artifacts/rebase-stack/logs/{branch-safe-name}.log` +> Fix the issue, then run `/validate` again. +> Branches that already passed will be skipped automatically. + +**Stop here.** Do not proceed to Step 8. + +**On success:** the cache is kept — subsequent `/validate` runs will skip +already-validated branches automatically. + +### Step 8: Build Push Overview + +For each branch in `{push-list}`, get its new local tip: + +```bash +git log --oneline -1 {branch} +``` + +And the current remote tip for comparison (remote-tracking ref was just +refreshed by the fetch in Step 3): + +```bash +git log --oneline -1 origin/{branch} 2>/dev/null || echo "(not on remote)" +``` + +Present the overview as a table. Use `✓ pass` for validated branches, +`— skip` for branches skipped because they and everything below them were +unchanged: + +``` +Branch Validation New tip (local) Remote tip (before push) +──────────────────────────────────────────────────────────────────────────────────────── +story-1 — skip abc1234 EDM-4100: add routing abc1234 (same) +story-2 ✓ pass def5678 EDM-4200: add handler e3f1a2b EDM-4200: ... (old) +story-3 (HEAD) ✓ pass ghi9012 EDM-4300: add test cba3210 EDM-4300: ... (old) +``` + +Then tell the user: + +> All branches validated. Run `/push` to push and create any missing PRs.