Agent skill

jut

Jujutsu version control through jut, a human and agentic framework around jj. Use for: check status, view changes, commit work, create branches, push, pull, create PRs, squash commits, reword messages, absorb changes, undo operations, view history. Complements jj — use jut for opinionated workflows, drop into raw jj for everything else.

Stars 53
Forks 5

Install this agent skill to your Project

npx add-skill https://github.com/edmundmiller/dotfiles/tree/main/packages/jut/skill

SKILL.md

jut — Jujutsu CLI Skill

Use jut as the primary interface for jj version control. jut is a thin opinionated layer — not a replacement. Drop into raw jj for anything jut doesn't cover.

Non-Negotiable Rules

  1. Start every task with jut status --json to get workspace state, stack structure, and change IDs.
  2. For all mutations, always use --json --status-after.
  3. Use short IDs from jut status --json output (short_id field) to reference revisions.
  4. After a successful --status-after, do not run redundant jut status.
  5. Use raw jj for interactive commands (split, resolve, diffedit, edit, rebase) — jut intentionally does not wrap these.
  6. Never fabricate change IDs. Always read them from jut status, jut log, or jut show output first.
  7. jj has no staging area. The working copy IS the stage. Don't look for add/stage commands.
  8. Build stacks, not monoliths. Multiple related changes should be multiple stacked commits, not one giant commit. Use jut branch <name> --stack to chain them.
  9. jj's working copy IS a commit. You describe it, evolve it, then jut commit (or jj new) to start the next one. This is NOT git's "stage → commit" model.

Core Flow

bash
# 1. Understand workspace state
jut status --json

# 2. Perform mutations with structured feedback
jut <command> --json --status-after

# 3. For complex operations, drop into jj
jj split -r <rev>
jj rebase -r <rev> -d <dest>

Command Reference

Inspection

bash
jut status                       # Workspace state: stacks, bookmarks, files
jut status -f                    # Include file-level details
jut status -v                    # Verbose: author + timestamps
jut log                          # Revision history (default: 20)
jut log -n 50                    # More revisions
jut log --all                    # All revisions
jut diff                         # Working copy diff
jut diff <rev>                   # Diff of specific revision
jut show <rev>                   # Revision details
jut show <rev> -v                # With inline diff

Committing

bash
jut commit -m "message"          # Describe @ + create new empty change
jut commit                       # Opens editor for message

jj's model: the working copy is always a commit. jut commit describes it and creates a new empty change on top — like jj commit.

Branching

bash
jut branch <name>                # Create branch from trunk + new change
jut branch <name> --stack        # Stack: branch from @ (dependent work)
jut branch <name> --from <rev>   # Branch from specific revision
jut branch -l                    # List branches (same as status)
jut branch -d <name>             # Delete branch
jut branch --rename <old> <new>  # Rename branch

The Rub Primitive

rub is the universal "combine two things" verb (from GitButler). It replaces several jj commands based on what SOURCE and TARGET are:

bash
jut rub <source> <target>        # Or: jut <source> <target>
SOURCE → TARGET Action jj equivalent
file → revision Amend file into commit jj squash --into <rev> <file>
file → zz Discard file changes jj restore <file>
revision → revision Squash into target jj squash --from <src> --into <tgt>
revision → zz Abandon revision jj abandon <rev>

zz is the discard target — the trash can.

Squash & Reword

bash
jut squash                       # Squash @ into parent
jut squash <rev>                 # Squash <rev> into its parent
jut squash <from> <into>         # Squash <from> into <into>
jut squash <from> <into> -m "x"  # With new message
jut reword <rev> -m "new msg"    # Edit commit message
jut reword <rev>                 # Opens editor

Discard

bash
jut discard <file>               # Restore file (discard changes)
jut discard <rev>                # Abandon revision

Auto-detects whether target is a file path or revision ID.

Absorb

bash
jut absorb                       # Auto-amend changes into the right commits
jut absorb --dry-run             # Show plan without applying

Push, Pull & PR

bash
jut push                         # Push all bookmarks
jut push <bookmark>              # Push specific bookmark
jut pull                         # Fetch + rebase onto trunk
jut pull --clean                 # Also delete merged bookmarks
jut pull --no-rebase             # Fetch only
jut pull --dry-run               # Show plan
jut pr                           # Create PR for current bookmark (via gh)
jut pr <bookmark>                # Create PR for specific bookmark
jut pr -m "title\nbody"         # With message

History

bash
jut undo                         # Undo last operation
jut oplog                        # View operation history
jut oplog -n 20                  # More operations
jut oplog restore <op-id>        # Restore to previous state

JSON Output Shapes

All commands support --json (or -j). Key shapes:

jut status --json

json
{
  "trunk": { "change_id": "...", "short_id": "...", "bookmarks": ["main"], ... },
  "stacks": [
    {
      "bookmarks": ["feature-x"],
      "revisions": [
        {
          "change_id": "abc123...",
          "commit_id": "def456...",
          "short_id": "abc",
          "description": "add feature x",
          "bookmarks": ["feature-x"],
          "is_empty": false,
          "is_working_copy": true,
          "is_conflicted": false,
          "is_immutable": false,
          "parent_change_ids": ["..."],
          "author": "user@example.com",
          "timestamp": "2026-02-10 13:00",
          "files": [{ "status": "M", "path": "src/main.rs" }]
        }
      ]
    }
  ],
  "working_copy": null,
  "uncommitted_files": [],
  "shared_base": []
}

jut log --json

json
{
  "revisions": [
    { "change_id": "...", "short_id": "...", "description": "...", "bookmarks": [...], ... }
  ]
}

jut pull --json

json
{
  "fetched": true,
  "rebased": true,
  "merged_bookmarks": ["old-feature"],
  "cleaned_bookmarks": [],
  "conflicts": []
}

jut pr --json

json
{
  "created": true,
  "bookmark": "feature-x",
  "pr_url": "https://github.com/user/repo/pull/42"
}

Task Recipes

Start new feature work

bash
jut pull --clean --json --status-after
jut branch my-feature --json --status-after
# ... make changes ...
jut commit -m "implement feature" --json --status-after

Start stacked work (depends on current branch)

bash
jut branch part-2 --stack --json --status-after
# ... make changes ...
jut commit -m "part 2" --json --status-after

Multi-part feature (the critical pattern)

DO THIS — three stacked commits, each reviewable independently:

bash
jut branch auth --json --status-after
# ... write auth code ...
jut commit -m "add authentication" --json --status-after
jut branch profile --stack --json --status-after
# ... write profile code ...
jut commit -m "add user profiles" --json --status-after
jut branch settings --stack --json --status-after
# ... write settings code ...
jut commit -m "add settings page" --json --status-after

NOT THIS — one giant commit (git muscle memory):

bash
# WRONG: dumping everything into one commit
# ... write all files ...
jj describe -m "add auth, profiles, and settings"

Each logical unit of work should be its own commit in a stack. This enables independent review, selective rollback, and clean history.

Ship a feature

bash
jut push --json --status-after
jut pr --json

Amend a file into an older commit

bash
jut status --json                          # Find the file and target revision short_id
jut rub <file> <rev> --json --status-after  # Amend file into that commit

Discard all changes to a file

bash
jut rub <file> zz --json --status-after
# or equivalently:
jut discard <file> --json --status-after

Abandon a revision

bash
jut rub <rev> zz --json --status-after
# or equivalently:
jut discard <rev> --json --status-after

Clean up after pull (delete merged bookmarks)

bash
jut pull --clean --json --status-after

Undo a mistake

bash
jut undo --json --status-after
# If you need to go further back:
jut oplog --json                           # Find the operation
jut oplog restore <op-id> --json --status-after

Split a commit (drop to jj)

bash
jut status --json    # Get the revision ID
jj split -r <rev>    # Interactive split in jj
jut status --json    # Refresh state

Rebase work (drop to jj)

bash
jj rebase -r <rev> -d <dest>
jut status --json    # Refresh state

Resolve conflicts (drop to jj)

bash
jut status --json          # See conflicted revisions (is_conflicted: true)
jj resolve -r <rev>        # Interactive merge tool
jut status --json          # Verify resolution

When to Use jj Directly

jut intentionally skips these — use raw jj:

Command Why
jj split Interactive editor — can't improve on it
jj edit <rev> Trivial one-liner
jj rebase Complex revset args — wrapping loses flexibility
jj resolve Interactive merge tool
jj diffedit Interactive editor
jj next / jj prev Trivial navigation
jj new Covered by jut commit and jut branch
jj describe Covered by jut reword
jj abandon Covered by jut discard
jj bookmark (advanced) jut branch covers common cases
jj config Config management, not a repo operation

Read-only jj commands are always fine alongside jut (jj log, jj evolog, jj show, jj diff).

Notes

  • jj has no staging area. Every change is immediately part of the working copy commit.
  • The working copy (@) is always a revision. jut commit describes it and creates a new one.
  • Change IDs (reverse hex) are stable across rebases. Commit IDs change. Always prefer change IDs.
  • short_id from JSON output is the shortest unique prefix — use these for brevity.
  • rub is positional: jut <source> <target> works without the rub subcommand.
  • zz is the universal discard target for rub.
  • --status-after returns the full workspace state after mutation — eliminates a round-trip.
  • jut and jj coexist freely. No setup/teardown. Switch between them at will.
  • Keep skill version checks low-noise:
    • Do not run jut skill check as routine preflight.
    • Run jut skill check when command behavior diverges from this skill.
    • If update available, recommend jut skill check --update.
  • For deeper command syntax and flags, see references/reference.md.
  • For workspace model and jj concepts, see references/concepts.md.
  • For end-to-end workflow patterns, see references/examples.md.

Expand your agent's capabilities with these related and highly-rated skills.

edmundmiller/dotfiles

zbench

Benchmark interactive zsh performance with zsh-bench and track regressions. Use when benchmarking shell startup, comparing zsh latency after config changes, investigating slow shell, or running git bisect on performance. Trigger phrases: "benchmark zsh", "shell is slow", "zbench", "zsh-bench", "shell startup time", "profile zsh", "zsh performance".

53 5
Explore
edmundmiller/dotfiles

nix-rebuild

Rebuild nix-darwin/NixOS system after dotfiles changes. Use when config files managed by Nix (lazygit, ghostty, etc.) need to be regenerated, or after editing any .nix file in the dotfiles repo.

53 5
Explore
edmundmiller/dotfiles

hass-config-flow

Interact with Home Assistant via the REST API on a NixOS host. Use when adding integrations, querying entities, managing config flows, creating API tokens, or automating HA setup programmatically. Also covers identifying device protocols (Matter, Zigbee, Thread, HomeKit) from the device registry. Trigger phrases: "add HA integration", "configure home assistant", "query HA entities", "create HA token", "HA REST API", "pair homekit", "set up matter in HA", "add spotify to HA", "is this device zigbee or thread", "what protocol is this device", "move devices to ZHA", "identify matter devices".

53 5
Explore
edmundmiller/dotfiles

hass-declarative

Manage Home Assistant automations, scenes, and scripts declaratively via NixOS modules. Covers adding/editing/removing entities in the domain-based Nix structure, the ensureEnabled wrapper (initial_state enforcement), the sweep service that cleans orphaned entities, entity identity (IDs, slugs, unique_ids), the eval test assertions, and the build-time manifest. Trigger phrases: "add HA automation", "new scene", "new script", "remove automation", "declarative HA", "sweep unmanaged", "entity drift", "ghost entity", "orphaned automation", "HA domain file", "eval-automations test", "hass assertion", "ensureEnabled", "initial_state".

53 5
Explore
edmundmiller/dotfiles

agenix-secrets

Create, edit, and wire up agenix-encrypted secrets in this dotfiles repo. Use when adding API keys, tokens, credentials, passwords, or any sensitive values to NixOS host configs. Trigger phrases: "add a secret", "encrypt with agenix", "new age secret", "hide this value", "agenix secret".

53 5
Explore
edmundmiller/dotfiles

linear

Read-only Linear issue access via the Linear GraphQL API.

53 5
Explore

Didn't find tool you were looking for?

Be as detailed as possible for better results