A local stacked-branch workflow manager for Git. Stackr organizes your branches into hierarchical stacks — parent-child relationships that let you work on dependent changes simultaneously, rebase entire chains with one command, and submit clean PRs that build on each other.
◉ feat-auth-ui ← ← you are here
│
│ ◯ feat-auth-tests
├─┘
◯ feat-auth-middleware
│
◯ feat-auth-models
│
◯ main (trunk)
Large features rarely fit in a single PR. Stacked branches let you:
- Ship incrementally — each branch is a reviewable, mergeable unit
- Work in parallel — start the next piece before the previous one merges
- Keep PRs small — reviewers see focused diffs, not 2000-line monsters
- Rebase safely —
sr syncupdates your entire stack when trunk moves
Stackr tracks the dependency graph so you don't have to remember which branch sits on which.
Requires Go 1.25.5+ and Git.
# Clone and install globally
git clone https://github.com/amustafa/stackr.git
cd stackr
make install # installs `sr` to your $GOPATH/bin
# Or build locally
make build # outputs to build/srStackr ships a zsh plugin at the repo root (stackr.plugin.zsh), so you can
install its shell integration through a plugin manager. With
antidote, add the repo to your ~/.zsh_plugins.txt:
amustafa/stackr
Then rebuild your plugin bundle (or restart your shell):
antidote updateThe plugin sets up both the worktree auto-cd wrapper (sr shell-hook) and zsh
completions (sr completion zsh) — but only if the sr binary is on your
$PATH. If stackr isn't installed yet, loading the plugin is a harmless
no-op, so the ordering of "install the binary" vs. "load the plugin" doesn't
matter. Install the binary with make link/make install (see
Development Setup), and the integration activates on your
next shell.
The plugin is oh-my-zsh compatible too — clone the repo into
$ZSH_CUSTOM/plugins/stackr and add stackr to your plugins=(...) list.
If you'd rather wire things up manually (or aren't on zsh), generate completions directly:
# Bash
sr completion bash > /etc/bash_completion.d/sr
# Zsh
sr completion zsh > "${fpath[1]}/_sr"
# Fish
sr completion fish > ~/.config/fish/completions/sr.fishAnd the worktree auto-cd wrapper (add to your .zshrc/.bashrc):
eval "$(sr shell-hook)"# Initialize stackr in your repo
cd my-project
sr init
# Create your first stacked branch
sr create feat-models -m "Add user model"
# Stack another branch on top
sr create feat-api -m "Add user API endpoints"
# See your stack
sr log
# ◉ feat-api ←
# │
# ◯ feat-models
# │
# ◯ main (trunk)
# Trunk moved? Sync everything
sr sync
# Push and open PRs for the whole stack
sr submit --stackStackr maintains a directed acyclic graph of your branches. Every tracked branch has exactly one parent (except trunk), and can have multiple children. This graph is stored alongside your git data and powers all stack operations.
┌── feat-c
feat-a ─┤
└── feat-b ── feat-d
Your main integration branch (main, master, etc.). Set during sr init. All stacks root here. View or switch to it with sr trunk.
Each tracked branch carries:
| Field | Purpose |
|---|---|
| Parent | Which branch this one builds on |
| Children | Branches that depend on this one |
| Revision | The git SHA at the branch tip |
| Description | A short objective for the branch |
| Context | Structured key-value entries (for humans and AI agents) |
| Frozen | If true, skipped during automatic restacking |
Context entries are structured metadata you attach to branches — design decisions, references, ticket links. They're especially useful for AI-assisted workflows where agents need to understand why a branch exists.
sr context set approach "Using JWT for stateless auth" \
--source file:internal/auth/jwt.go \
--ticket AUTH-456| Command | Description |
|---|---|
sr init |
Initialize stackr in a git repository |
sr config |
Show or modify stackr configuration |
sr config set <key> <value> |
Set a config value (e.g. trunk, remote) |
| Command | Alias | Description |
|---|---|---|
sr create [name] |
c |
Create a new branch on top of the current one |
sr track [branch] |
tr |
Start tracking an existing branch |
sr untrack [branch] |
utr |
Stop tracking a branch |
sr delete [branch] |
dl |
Delete a branch and reparent its children |
sr create flags:
-m, --message Commit message (creates an initial commit)
-a, --all Stage all tracked changes
-u, --untracked Stage tracked file changes
-p, --patch Interactive patch selection
-i, --insert Insert between current branch and its children
--desc Set branch description/objective
--worktree Create in a worktree instead of checking out
| Command | Alias | Description |
|---|---|---|
sr up [N] |
u |
Move N branches upstack (away from trunk) |
sr down [N] |
d |
Move N branches downstack (toward trunk) |
sr top |
t |
Jump to the top of the current stack |
sr bottom |
b |
Jump to the bottom of the current stack (first branch above trunk) |
sr checkout [branch] |
Switch to a tracked branch | |
sr trunk |
Show or switch to trunk |
| Command | Alias | Description |
|---|---|---|
sr log |
l |
Visualize the stack tree |
sr info [branch] |
Show branch details (parent, children, commits, context) | |
sr parent [branch] |
Show parent branch | |
sr children [branch] |
Show child branches |
sr log flags:
-a, --all Show all stacks, not just the current one
-l, --long Show commits for each branch
-r, --reverse Reverse order (trunk at bottom)
-s, --stack Show only the current stack
sr info flags:
-d, --diff Show full diff against parent
-s, --stat Show diff stat against parent
| Command | Alias | Description |
|---|---|---|
sr describe [text] |
desc |
Set or show the branch description/objective |
sr context set <key> <text> |
Add/update a context entry | |
sr context rm <key> |
Remove a context entry | |
sr context list |
List all context entries |
Context flags:
--source type:reference Source reference (repeatable)
--ticket ID Related ticket IDs (comma-separated or repeatable)
| Command | Description |
|---|---|
sr commit |
Commit with stackr context tracking |
sr commit flags:
-m, --message Commit message (required)
-a, --all Stage all tracked changes
-u, --untracked Stage tracked file changes
-p, --patch Interactive patch selection
--desc Update branch description
--context Commit context JSON blob (repeatable)
Context entries are structured JSON attached to individual commits:
sr commit -a -m "add validation" --context '{"key":"step-3","text":"Implementing zod validation","sources":[{"type":"file","reference":"codev/plans/1-validation.md"}]}'| Command | Alias | Description |
|---|---|---|
sr restack |
r |
Rebase the stack so branches are correctly ordered |
sr sync |
Fetch trunk, restack, and clean merged branches | |
sr absorb |
ab |
Distribute staged changes to the right stack commits |
sr split |
sp |
Split the current branch into multiple branches |
sr fold |
Merge the current branch into its parent | |
sr squash |
Combine commits within a branch | |
sr move |
Move a branch onto a new parent (prompts if --onto is omitted) |
|
sr reorder |
Reorder branches in a stack | |
sr pop |
Remove current branch and move to parent | |
sr freeze |
Mark a branch as frozen (skip during restack) | |
sr unfreeze |
Unfreeze a branch (resume restacking) |
sr sync flags:
--restack Restack after syncing (default: true)
-f, --force Force sync
-a, --all Sync all stacks
| Command | Alias | Description |
|---|---|---|
sr submit |
s |
Push branches and create/update PRs |
sr get [branch] |
Fetch a branch from remote and track it | |
sr push-meta |
Push stackr metadata to the remote | |
sr pull-meta |
Pull and merge stackr metadata from the remote |
sr submit flags:
-d, --draft Mark PRs as draft
-s, --stack Push all branches in the stack
-u, --update-only Only submit branches that already have a PR or remote branch
-f, --force Overwrite the remote when it has content you don't, without
prompting (the push is still lease-pinned)
--no-force Never force-push; fail instead
--dry-run Show what would be pushed, changing nothing
--pr-meta PR content as JSON, or a path to a file of JSON (repeatable)
--title PR title for a single PR (shorthand for --pr-meta)
--body PR body (used with --title)
--body-file Read PR body from file (used with --title)
--ai Launch Claude to generate and submit PR
--aiprepare Output PR context as JSON (for agents)
Writing PRs for a whole stack: --pr-meta. A submit pushes the current
branch and its downstack ancestors, creating a PR for each one that lacks it.
--title/--body can only describe one of those, so anything below the branch
you are standing on either gets prompted for interactively or is pushed without
a PR — a gap in the stack, where the PR above opens against a ref nobody
reviews.
--pr-meta addresses content per branch. Each entry is
{"branch", "title", "body" | "bodyFile", "draft"}, and a value is either
inline JSON or a path to a file of JSON, holding one object or an array:
# One file for the whole stack — the usual form for a generated submit.
sr submit --stack --pr-meta prs.json
# Inline, one branch at a time; repeatable.
sr submit \
--pr-meta '{"branch":"feat/api","title":"Add the endpoint","bodyFile":"/tmp/api.md"}' \
--pr-meta '{"branch":"feat/ui","title":"Wire up the form","bodyFile":"/tmp/ui.md","draft":true}'[
{ "branch": "feat/api", "title": "Add the endpoint", "bodyFile": "/tmp/api.md" },
{ "branch": "feat/ui", "title": "Wire up the form", "bodyFile": "/tmp/ui.md", "draft": true }
]"branch" may be omitted only when the submit creates exactly one PR — the one
case where there is nothing to be ambiguous about. With more than one, the same
entry could belong to either, and labelling a branch with another branch's
summary is worse than an error, because it survives review looking deliberate.
"draft" overrides --draft for that branch, so one entry can opt out of an
otherwise-draft submit.
The whole payload is validated before any PR is created: a file that is wrong about its third branch costs you a message, not two PRs and a half-built stack to unpick. Branch names are checked earlier still, before anything is pushed.
Force pushing is automatic — and safe. Restacking rewrites SHAs, so pushing a
stacked branch nearly always needs a force push. Rather than making you decide
whether -f is safe today, sr submit works it out per branch:
- If the remote still holds the commit stackr itself last pushed there, everything on it is yours and the force push is lossless. No prompt.
- Otherwise stackr compares content: a restack, squash, absorb, split or reorder contributes nothing new to the remote and still pushes silently.
- Only if the remote genuinely holds changes you don't have does it stop and ask — with the option to merge them in (squashed or cherry-picked), take theirs, or overwrite.
This all happens in a preflight that runs across every branch being submitted before anything is pushed, so you settle the whole stack in one go rather than discovering a conflict halfway through publishing it. Every force push pins its lease to the exact commit that was inspected, so if the remote moves while you're resolving, the push is rejected rather than overwriting it.
If preflight changed branches locally and you want them back, it prints a rollback id:
sr rollback # undo the most recent submit's local changes
sr rollback --list # see what's available
sr rollback 20260731-143022
(sr rollback restores branch tips. sr undo restores the branch graph —
who depends on whom — and can't put back a reset or a cherry-pick.)
Three submit modes:
- Programmatic — an agent gathers context with
sr submit --aiprepare, then callssr submit --pr-meta prs.jsononce for the whole stack - Bare interactive —
sr submitwith no flags presents a wizard (Push only / Create PR) - Agent interactive —
sr submit --aispawns a Claude session that generates and submits the PR autonomously
The --aiprepare JSON describes the whole job — one entry per branch the submit
acts on, each with its parent, description, recorded context entries, commits
and any existing PR, plus a needsPR flag on the gaps. It is the input side of
--pr-meta: the agent reads one payload describing every branch and writes one
back. What it does not carry is the diff.
The patch is the one unbounded thing in there, and most PR descriptions never
need it, so instead the JSON carries a diffCommand field: the git diff
invocation that prints this branch's changes against its parent. The agent runs
it only if it decides it has to see the code.
| Command | Description |
|---|---|
sr address-review |
Walk the stack and address PR review comments interactively |
sr address-review --aiprepare |
Output all unresolved comments as JSON (for agents) |
sr address-review --ai |
Launch Claude to address all comments autonomously |
Three address-review modes (same pattern as submit):
- Programmatic —
sr address-review --aiprepareoutputs JSON, agent makes changes and resolves threads - Bare interactive —
sr address-reviewwalks bottom-up, presents each comment, lets you edit/reply/skip - Agent interactive —
sr address-review --aispawns Claude with/goalto address everything autonomously
| Command | Description |
|---|---|
sr undo |
Undo the last stack mutation (the branch graph) |
sr rollback [id] |
Restore branch tips recorded before a submit changed them |
sr continue |
Continue after resolving a rebase conflict |
sr abort |
Abort an in-progress operation |
sr revert |
Revert a previous operation |
Turn a GitHub issue or Jira ticket into a working branch in one step. sr implement <ref> fetches the issue host-side, creates a new tracked branch
named <ref>-<title-slug>, records the issue linkage, and drives implementation.
| Command | Description |
|---|---|
sr implement 123 |
GitHub issue → new branch; implement here or spawn Claude |
sr implement PROJ-456 --worktree |
Jira ticket in a worktree |
sr implement 123 --sandbox |
implement in a disposable sandbox (implies --worktree) |
sr implement 123 --ai |
scaffold the branch and emit {branch, worktreePath, issueRef, prompt} as JSON |
Source detection is automatic: a number (123, #123) or GitHub issue URL
is a GitHub issue; a KEY-N (PROJ-456) or browse URL is a Jira ticket. Force
it with --source github|jira.
How it implements depends on context: inside a Claude session (or with
--ai) it scaffolds and hands the brief back as JSON — no nested agent; a bare
terminal spawns an interactive Claude on the brief; --sandbox runs it in a
container (attaching in a terminal, detached + JSON when driven by a skill).
Other flags: --branch <name> overrides the derived name, --parent <branch>
changes what it stacks on (default: current branch), --comments folds the
issue discussion into the brief. GitHub needs gh; Jira needs the jira CLI.
Run Claude with --dangerously-skip-permissions without exposing your host:
a disposable Docker container works on the branch's worktree, while your
~/.claude config, skills, and session history are shared so the work is
indistinguishable from a normal local session — and resumable.
| Command | Alias | Description |
|---|---|---|
sr sandbox [branch] [-- "<prompt>"] |
sb |
Launch (or reuse) a sandbox for a branch and attach |
sr sandbox attach [branch] |
Reattach; no arg opens a searchable picker | |
sr sandbox ls |
List this repo's sandboxes with status | |
sr sandbox watch [--notify] |
Live dashboard, or headless desktop notifications | |
sr sandbox stop <branch> |
Stop the container (relaunch resumes) | |
sr sandbox rm <branch> [--delete] |
Remove the container (--delete also removes the worktree) |
|
sr sandbox config [--ai] |
Edit sandbox config in a TUI, or let Claude help |
Highlights:
- Session continuity — the sandbox's Claude session appears under the same
~/.claude/projectsentry as the repo, so hostclaude --resumesees it. - Egress firewall by default — only an allowlist (Anthropic, GitHub,
registries) is reachable; the agent requests new domains, which you add to the
config and relaunch.
--network fullopts out. - No credentials in the sandbox — it records a proposed PR as a
prbranch context entry; runsr submiton the host to open the PR from it. - Attention —
sr sandbox ls/watchshow when a session is waiting on you.
Requires Docker. The base image is built once and reused; per-container differences are just bind mounts and the launch command.
| Command | Description |
|---|---|
sr rename |
Rename a branch |
sr modify |
Amend the current branch and restack descendants |
sr worktree |
Manage git worktrees |
sr claude install |
Install the stackr skill (with sandbox + implement lanes) for Claude Code |
sr shell-hook |
Print shell integration script |
sr completion |
Generate shell completion scripts |
Stackr is built around a development rhythm: plan the decomposition, build bottom-up, track decisions as you go, and submit in clean increments. This section describes how to use stackr effectively — whether you're working solo, with a team, or with AI agents.
Before writing code, decompose your feature into layers that build on each other. Each layer becomes a branch. The bottom of the stack should be the most foundational change — the thing everything else depends on.
# Bad: one giant branch
feat-auth (47 files, 2000 lines)
# Good: layered stack
feat-auth-models ← data structures, types
feat-auth-middleware ← request validation, token parsing
feat-auth-api ← endpoints that use the middleware
feat-auth-tests ← integration tests
Each branch should be independently reviewable. A reviewer looking at feat-auth-middleware should only need to understand feat-auth-models below it — not the entire feature.
Start at the bottom of your stack and work upward. When you create a new branch, give it a clear objective:
sr create feat-auth-models --desc "User and session types for JWT auth"Commit with sr commit instead of git commit to keep the graph in sync and attach reasoning:
sr commit -a -m "add session model" --context '{"key":"design","text":"Sessions are stateless JWTs, not DB-backed"}'When you're ready for review, submit the whole stack:
sr submit --stackReviewers should merge from the bottom up. As each PR merges, sr sync cleans it from the stack and rebases everything above.
Code says what changed. Context says why. Stackr has two levels:
Branch context — high-level decisions that apply to the whole branch:
sr context set approach "Stateless JWTs over DB sessions — latency constraint"
sr context set tradeoff "No revocation without a blocklist; acceptable for v1"Commit context — per-step reasoning attached to individual commits:
sr commit -a -m "add token rotation" --context '{"key":"step","text":"Refresh tokens rotate on use per OWASP guidelines","sources":[{"type":"url","reference":"https://cheatsheetseries.owasp.org/..."}]}'Both feed into PR description generation (sr submit --ai) and are visible via sr info. Both are lost on squash — persist anything that should outlive the branch to a file before squashing.
When reviewers leave comments on your stack, sr address-review walks from the bottom up, presents each unresolved thread, and lets you edit code, reply, and resolve — then commits, restacks, and moves to the next branch.
# Interactive walkthrough
sr address-review
# Let Claude handle everything
sr address-review --ai| Situation | Command |
|---|---|
| Trunk moved (someone merged) | sr sync |
| Reviewer requested changes mid-stack | sr checkout <branch>, fix, sr restack |
| Branch got too big | sr split |
| Branch is trivial, fold into parent | sr fold |
| Need to reorder the stack | sr reorder |
| Made a mistake | sr undo |
Stackr is designed for both human and AI-driven development. Every workflow command follows a three-mode pattern:
- Interactive — the bare command runs a terminal walkthrough for humans
- Programmatic —
--aiprepareoutputs JSON context,--title/--body/--contextaccept structured input. An agent already in a session composes these. - Autonomous —
--aispawns a Claude session that owns the workflow end-to-end
The agent workflow:
- Agent reads the branch state:
sr info,sr log - Agent sets context as it works:
sr context set,sr commit --context - Agent submits when ready:
sr submit --aiprepare→ craft a PR per branch →sr submit --pr-meta prs.json - Agent addresses review comments:
sr address-review --aiprepare→ make fixes → reply and resolve
Context entries are the key integration point. They let the agent explain its reasoning in a structured way that persists across sessions and feeds into PR generation.
For long-running branches or when you want to keep your main working tree clean:
sr create feat-experiment --desc "Try approach B" --worktree
# Creates .worktrees/feat-experiment/ — main working tree stays on current branchAdd a .stackr/hooks/post-worktree script to automate setup (copying .env, running direnv allow, etc.):
#!/bin/bash
# .stackr/hooks/post-worktree
cp .env "$1/.env"
cd "$1" && direnv allow# Start your day — sync with trunk
sr sync
# Start a new feature stack
sr create feat-data-model -m "Add data model"
# ... make changes, commit ...
# Stack the next piece on top
sr create feat-api -m "Add API layer"
# ... make changes, commit ...
# Stack the UI on top of that
sr create feat-ui -m "Add UI components"# Reviewer requested changes on feat-data-model
sr checkout feat-data-model
# ... make fixes, commit ...
# Restack everything above
sr restack# Push the entire stack and open PRs
sr submit --stack
# Or push just the current branch
sr submit
# Preview without pushing
sr submit --dry-run# Trunk moved (someone merged a PR)
sr sync
# Fetches trunk, rebases your stack, cleans up merged branches# If a restack hits a conflict, stackr pauses
# Fix the conflict in your editor, then:
git add <resolved-files>
sr continue
# Or bail out:
sr abortEvery tracked branch records the commit it was branched from — its base. The
range (base, branch] is what stackr replays when restacking, so the base is
what keeps a rebase from picking up commits that belong to the parent.
Stackr keeps bases alive behind refs/stackr/bases and repairs them
automatically where it safely can, including recovering one from the parent's
reflog. If a base is lost beyond recovery — usually after heavy raw-git surgery
plus an expired reflog — stackr stops rather than guess, because guessing
duplicates or drops commits:
cannot determine which commits belong to feat-3: recorded base 0000000 is
missing or is not an ancestor of the branch.
Point it at the commit the branch was created from:
# See where the branch actually diverged
git log --oneline <parent>..<branch>
# Re-point the base and restack in one step
sr restack --branch feat-3 --base <sha># Move a branch to a different parent
sr move --onto feat-auth
# Omit --onto to pick from a tree of the stacks. Branches you cannot move
# onto — your own dependents, your current parent — are shown greyed with
# the reason, rather than hidden.
sr move
# A move is a repoint plus a restack: the branch and everything stacked on
# it are replayed onto the new parent. Skip the replay with:
sr move --onto feat-auth --no-restack
# Insert a branch between two existing ones
sr create feat-middleware -i
# Merge a branch into its parent (fold up)
sr fold
# Split a branch that got too big
sr splitStackr ships a skill that teaches Claude how to use sr commands. Install it with:
sr claude installsr init also offers this automatically: after initializing, it checks whether
the stackr prompt is already reachable — either in the repo's CLAUDE.md or your
global one ($CLAUDE_CONFIG_DIR/CLAUDE.md, default ~/.claude/CLAUDE.md) — and,
if not, prompts to run the install for you. Already covered locally, it stays
silent; covered only globally, it says so and skips. In non-interactive mode it
prints a one-line hint instead of prompting.
This creates a single unified skill at .claude/skills/stackr/ — a SKILL.md covering the core workflow (branch creation, navigation, context tracking, conflict recovery, PR submission) plus progressive-disclosure lane files (sandbox.md, implement.md) that Claude reads on demand. Claude then uses sr via Bash instead of raw git. Upgrading from an older version folds the separate sr-sandbox / sr-implement skills into this one automatically.
It also writes an always-on prompt to .claude/prompts/stackr.md and imports it from CLAUDE.md via a small marker-delimited @-reference (@.claude/prompts/stackr.md). A skill only loads when its description matches the request, so this always-on prompt guarantees the essentials — prefer sr over raw git, stack sequential work, travel the stack with sr up/sr down — are in context even when the skill didn't trigger. The prompt lives in its own file (one source of truth) while CLAUDE.md keeps only a tiny import. It's idempotent, removed by sr claude uninstall, and leaves the rest of CLAUDE.md untouched. Pass --no-prompt-block to skip it, or --local to install into the current directory's .claude instead of the repo root (works even outside a git repo).
For programmatic workflows (agents already in a session), use the two-step submit:
# 1. Gather context for every branch the submit will act on
sr submit --aiprepare
# 2. Write a body file per branch, then one payload naming them all
cat > /tmp/prs.json <<'JSON'
[
{ "branch": "feat/api", "title": "Add auth middleware", "bodyFile": "/tmp/pr-api.md" },
{ "branch": "feat/ui", "title": "Gate the dashboard", "bodyFile": "/tmp/pr-ui.md" }
]
JSON
# 3. Create them all, bottom-up, in one command
sr submit --pr-meta /tmp/prs.jsonFor AI-driven submit, sr submit --ai spawns a Claude session that generates and submits the PR autonomously.
For addressing review comments across the stack:
# Gather all unresolved comments as JSON
sr address-review --aiprepare
# Walk through comments interactively
sr address-review
# Let Claude handle everything
sr address-review --aiStackr stores metadata in two tiers:
The branch graph, config, and PR info are stored as git objects behind a custom ref. This means:
- Metadata can travel with
git push/git fetchvia refspecs - Git's garbage collection manages cleanup
- Works correctly across worktrees (shared
.gitdirectory)
| Blob | Contents |
|---|---|
config.json |
Trunk branch name, remote name |
branches.json |
Complete branch graph with metadata |
pr_info.json |
PR numbers, URLs, and state per branch |
Per-machine state that shouldn't travel with push/pull:
| Path | Contents |
|---|---|
undo/snapshots/ |
Graph snapshots for rollback |
undo/events.json |
Undo event log |
rebase-state.json |
In-progress rebase state |
cmd/ CLI commands (Cobra)
internal/
context/ Session initialization, repo discovery
engine/ Core algorithms (create, restack, sync, submit, ...)
git/ Git command wrapper
graph/ Branch graph model and tree rendering
store/ Storage (git-ref shared + filesystem local)
ui/ Terminal UI (charmbracelet/bubbletea)
errors/ Sentinel error types
pkg/
version/ Build metadata (set via ldflags)
- Engine layer separates business logic from CLI concerns — each operation (create, sync, restack) lives in its own file under
internal/engine/ - Graph is the single source of truth — all operations read the graph, mutate it, and write it back. Undo works by snapshotting the graph before mutations.
- Git wrapper is explicit —
internal/git/exposes methods likeRebaseBranch,CommitsBetween,DiffStatrather than raw command execution, making the engine testable. - Two-tier storage — shared metadata (graph, config, PRs) lives in git objects behind
refs/stackr/data; local ephemeral data (undo, rebase state) stays on the filesystem under.git/.stackr/.
--cwd string Run as if started in this directory
--debug Print git commands as they run
--interactive Enable interactive prompts (default: true)
-q, --quiet Suppress non-essential output
--no-verify Skip git hooks
-v, --version Show version
make setup # Create .envrc from template
make build # Build to build/sr
make link # Symlink to $STACKR_LINK_DIR (from .envrc)
make install # Install to $GOPATH/bin/sr
make test # Run all tests
make clean # Remove build artifactsSee LICENSE for details.