Core Concepts

Design principles

SDF is built around three principles:

Shell out, don't reimplement. SDF delegates to git, gh, and claude CLIs. It is pure orchestration — no custom git library, no custom GitHub API client. This means you get the behavior of the tools you already know and trust.

PR descriptions are the source of truth. Since .sdf/ is local-only, the authoritative record of stack structure and intent lives in the PR descriptions on GitHub. SDF can reconstruct stack topology from the PR graph via sdf fetch, making the workflow resilient to lost local state.

Fail loudly, recover clearly. When something goes wrong, SDF leaves the repository in a known state and tells you exactly what to run to recover. It never silently swallows errors.


Understanding SDF's model makes every command feel obvious. There are two things to know: stacks and nodes.


Stacks

A stack is a named, linear chain of branches that all descend from a common base (usually main). Each branch in the chain becomes a pull request that depends on the PR above it.

main
 └─ users-feature/db-schema      ← PR #142
     └─ users-feature/repository  ← PR #143
         └─ users-feature/controller ← PR #144

A repository can have multiple independent stacks — one for each feature being developed in parallel. Each stack has its own JSON file in .sdf/stacks/<name>.json, stored locally on your machine.

Stack JSON

{
  "stack_id": "users-feature",
  "base": "main",
  "nodes": [
    { "branch": "users-feature/db-schema",   "pr": 142, "status": "open",   "base_tip": "abc123", "nav_hash": "e3b0c4..." },
    { "branch": "users-feature/repository",  "pr": 143, "status": "open",   "base_tip": "def456" },
    { "branch": "users-feature/controller",  "pr": 144, "status": "draft",  "base_tip": "ghi789" }
  ]
}

The base_tip records the commit hash at the tip of the parent branch at the time of the last sync. This allows SDF to detect when a parent has moved and a rebase is needed.

The optional nav_hash is a SHA-256 hash of the PR stack navigation section. SDF uses it to avoid redundant API calls when updating PR descriptions — if the hash hasn't changed, the description update is skipped.


Nodes

A node is one branch (and its corresponding PR) within a stack. Nodes are ordered — the first node's parent is the stack base, and each subsequent node's parent is the node above it.

Node status

Each node tracks its PR status. The possible values are:

StatusMeaning
openThe PR is open and under review.
draftThe PR is a draft, not yet ready for review.
mergedThe PR has been merged into its base.
closedThe PR was closed without merging.

Merged nodes stay in the stack

When a node's PR merges, sdf sync does not remove it from the stack. Instead, the node remains in the stack JSON with status "merged". The ParentBranch() function skips over merged nodes when computing the effective parent for downstream branches — so downstream nodes rebase onto the stack base (or the nearest non-merged ancestor) automatically.

This means merged PRs are shown as completed in the stack topology rather than silently disappearing.


Configuration

SDF uses a two-tier configuration system:

  • Global config — ~/.config/sdf/config.json, user-specific defaults
  • Repo config — .sdf/config.json, per-repo overrides

Repo config takes precedence over global config on a field-by-field basis. Run sdf config show to see the effective merged config.

{
  "branch_prefix": {
    "enabled": true,
    "scope": "users-feature",
    "separator": "/"
  },
  "pr_title": {
    "conventional_commits": true,
    "ticket_pattern": ""
  },
  "sync": {
    "with_content": false
  }
}
FieldTypeDescription
branch_prefix.enabledboolWhether new branches are auto-prefixed with the stack ID (default true).
branch_prefix.scopestringScope used as branch prefix and conventional commit scope. If empty, falls back to the stack ID.
branch_prefix.separatorstringCharacter between prefix and branch name (default "/").
pr_title.conventional_commitsboolGenerate PR titles with conventional commit format using scope (default true).
pr_title.ticket_patternstringRegex to extract a ticket ID from the branch name for PR titles.
sync.with_contentboolWhether sdf sync updates PR titles and descriptions (default false).

Repository layout

.sdf/                                       ← local-only (gitignored)
  config.json                               ← repo config
  stacks/
    users-feature.json                      ← stack topology
    auth-feature.json                       ← another stack
  local.json                                ← ephemeral state (sync progress, etc.)

The entire .sdf/ directory is local-only — it is listed in .gitignore and is never pushed to the remote. Each developer's machine maintains its own copy of the stack metadata. PR descriptions on GitHub serve as the shared source of truth for stack structure and context.


Stack resolution

When you run a command that operates on a stack (sdf sync, sdf status, sdf branch, etc.), SDF determines which stack to use with this logic:

  1. Explicit --stack flag — always takes priority
  2. Positional argument — e.g. sdf sync my-feature
  3. Current branch — if your checked-out branch belongs to a stack, that stack is used
  4. Single stack — if there's only one stack in the repo, it's used automatically

If none of these resolve to a stack (e.g. you're on main with multiple stacks and didn't specify one), SDF reports an error listing the available stacks. With shell completions installed, you can tab-complete stack names directly — useful when working with multiple stacks.


Local state

The file .sdf/local.json stores ephemeral state that should not be shared between developers:

  • Sync progress — when a sync is paused for manual conflict resolution, the paused branch, resume index, and original branch are saved here. sdf sync --continue reads this to resume.
  • Split sessions — maps stack names to Claude session IDs so split refinement can resume across invocations.

This file is automatically created when needed and can be safely deleted — SDF recreates it as necessary.