Skip to content
xiv

Stack workflows

Five workflows implement the xiv stack lifecycle: plan decides the stack, build implements it locally, push publishes it as stacked PRs, review settles those PRs, and amend edits the middle of the stack and re-flows everything above. They share one source of truth — the stack map, a JSON file in SMITHERS_HOME linking each Linear issue to its repo, branch, position, status, and PR (see /concepts/stack-map-and-triage) — and one rebase engine, jj, running colocated next to git. CLI reference: /commands/stack.

Loading diagram...

All five refuse to run without a stack map and say so: xiv stack plan first.

stack-plan (sp) — decide the stack

Triggered by xiv stack plan <source> --feature <slug>. Two tasks:

  1. plan (agent): fetches the Linear project or parent issue named by source, returns every sub-issue as a separate entry — { issueId, issueTitle, repo } — assigned to one of the configured repos. The source itself is never an entry.
  2. persist (deterministic): orders the entries, derives each branch name (feat/<issue-key-lowercase>, the same convention linear-implement uses), chains each entry's base onto the previous same-repo entry's branch (the first in each repo sits on that repo's trunk), and writes the stack map. All entries start status: "pending".

No code changes — planning only reads Linear and writes the map. Passing --skip-acceptance-review records "build without the local review step" as the feature-wide default on the map.

This is the single highest-leverage call in the lifecycle: it runs once per feature and every later command inherits its ordering — getting it wrong is a rebuild, not a bad paragraph. It therefore runs Claude Fable 5 at xhigh effort, deliberately off the XIV_TIER ladder (overridable via XIV_STACK_PLAN_MODEL / XIV_STACK_PLAN_EFFORT; see /concepts/model-tiers). When issue→repo assignment needs a human in the loop, the stack-plan skill resolves the plan interactively and persists it with xiv stack plan --plan <file> — deterministic, no planner agent at all.

stack-build (sb) — implement every entry locally

Triggered by xiv stack build --feature <slug> (per repo; --all-repos fans out one detached run per repo). For each entry in position order it renders a three-task sequence:

  1. build:base:<issue> (deterministic) — git checkout the entry's base branch, so the work stacks on the entry below.
  2. build:impl:<issue> — a full linear-implement subflow: fetch → gate → plan → implement/validate/review loop → finalize on feat/<issue-key>. skipAcceptanceReview and maxIterations are forwarded to every entry; the skip flag ORs with the map's plan-time default — a run flag can force skipping on, but never re-enables review for a map that opted out.
  3. build:record:<issue> (deterministic) — read-modify-write the live map: set status: "implemented", record the branch and head SHA, advance the repo's tip, and persist the subflow's knownGaps — the findings its review raised that the loop shipped without fixing. Push runs later, in its own process, and would otherwise have to re-review the diff to rediscover them.

A final build:summary task reports { feature, repo, built, pending, tip }.

Resumability is structural: all three tasks carry skipIf on the entry's map status (anything past pending/implementing counts as built), so a crash mid-stack resumes by skipping every already-built entry. Everything is local — no push, no PRs.

stack-push (spush) — publish as stacked PRs

Triggered by xiv stack push --feature <slug> --count N (default count 5, --draft default true). This is the only stack workflow that touches GitHub. Two phases:

Re-sync stale PRs first. An entry is stale when it has been published (status pushed or pr-open) but its recorded headSha no longer matches its pushedSha — i.e. a later stack-amend re-flowed the branch underneath the PR. For each: git push --force-with-lease, confirm the PR points at the new head, record the SHA. No comments, no reviewer pings.

Then open the next batch. The lowest contiguous run of implemented-but-unpublished entries (capped at count), bottom to top. Per entry:

  • push:describe:<issue> (agent, continueOnFail) reads that entry's own diffbase...branch where the base is the entry below — and writes the structured body (Summary / Test Plan / Known gaps). The recorded knownGaps from build are handed to it verbatim: they are the literal answer to "what's still wrong with this". If describing fails, the PR still opens with just the footer.
  • push:open:<issue> (agent) opens the PR titled <ISSUE>: <title>, based on the previous entry's branch (or the trunk once that entry is merged). The footer — Linear issue, "entry k of n" stack position, base branch — is composed deterministically, never asked of the agent, which would only risk garbling facts the map already holds.
  • push:open-record:<issue> (deterministic) sets status: "pr-open" with the PR number/URL and pushed SHA.

stack-review (sr) — settle the stack's PRs

Triggered by xiv stack review --feature <slug>. The stack-wide analogue of pr-review-loop: a signals → address loop over every open PR in the substack at once, capped at maxRounds (default 2, return-last on the cap), exiting when a snapshot reports clean: true — green (or absent) checks on every PR and no unaddressed human comment anywhere. If no entry has a PR yet, it short-circuits with "run xiv stack push first".

  • review:signals — one read-only snapshot per round across all PRs: CI status per PR plus findings from failing checks and unresolved human comments (all four comment surfaces; bot comments ignored; "unresolved" judged against the code at each PR's head). Not a poll — a round that finds only pending checks simply ends.
  • review:address — the jj cascade step, skipped when there is nothing actionable. Each finding is fixed in the branch that owns the code it refers to — normally the PR it was raised on, or lower if the fault was introduced lower; never downward into an unrelated PR. Mechanically: jj edit --ignore-immutable <branch>, fix the root cause (never loosen a check), let jj rebase the descendants, verify locally, then push the edited branch and every descendant the rebase rewrote (they get new SHAs even when their diffs did not change). Deliberate non-fixes go in skipped with a defensible reason.
  • review:report — final state: ready for a human to merge, or exactly which PRs are still open and why (escalating via smithers ask-human when not clean).

The cascade only ever moves the edited branch and the branches above it. Force-pushing a rewritten branch may dismiss prior approvals — expected, and honest: the fix shows up in the PR it belongs to.

stack-amend (sa) — edit the middle, re-flow the top

Triggered by xiv stack amend --feature <slug> -m "change" [--target <issue|branch>]. Local-only — it never pushes (the next stack-push re-syncs the now-stale PRs). Four tasks:

  1. amend:locate (agent, skipped when --target names a valid entry directly): given the change description and the stack listing, pick the lowest entry whose scope covers the change, so the fix lives at its root and re-flows up to every descendant.
  2. amend:edit (agent): jj edit <branch>, make the change — edits amend that commit directly, no git commit — then let jj auto-rebase every descendant, resolving any conflicts bottom-to-top until every descendant bookmark is conflict-free. Reports the new head SHA and jj change id for the target and each re-flowed branch, plus conflictsRemaining if a conflict could not be fully resolved.
  3. amend:record (deterministic): write the new SHAs/change ids back to the map. Because pushedSha is not updated, every re-flowed entry with an open PR now reads as stale — which is precisely what tells the next stack-push to re-sync it.
  4. amend:revalidate (agent): check out the substack's tip branch — which contains the whole feature — and run the repo's lint and tests, reporting pass/fail with a summary of every failure.

Inputs at a glance

WorkflowInput fields (defaults)
stack-plansource, feature, repoSlug, repos{}, stackMapPath, skipAcceptanceReview (false)
stack-buildstackMapPath, repo, skipAcceptanceReview (false), maxIterations (3)
stack-pushstackMapPath, count (5), repo, draft (true)
stack-reviewstackMapPath, repo, maxRounds (2)
stack-amendstackMapPath, message, target, repo

An omitted repo means "the whole map" for build/review/amend; the CLI resolves multi-repo features to one run per repo. Entry status advances pending → implementing → implemented → pushed → pr-open → merged.

Models and failure semantics

Beyond the planner (Fable 5 at xhigh) and the per-entry linear-implement subflows (the mixed pipeline described on that page), the stack workflows' agent tasks run on the autonomous pool — heavy Claude with git/gh/jj permissions and a hard never-merge system prompt — with amend:locate on the plain heavy Claude provider. Everything moves to codex under XIV_ENGINE=codex. See /concepts/model-tiers.

  • Every map mutation is a read-modify-write of the live file, so concurrent entries (and --all-repos fan-out) never clobber each other; results are recorded per entry, so any crash resumes without redoing finished work.
  • The only continueOnFail task is push:describe — its fallback is the bare footer body.
  • Nothing in the lifecycle merges a PR, posts a comment, or tags a reviewer. Publishing and fixing are the whole job; merging is a human's.