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.
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
- Start every task with
jut status --jsonto get workspace state, stack structure, and change IDs. - For all mutations, always use
--json --status-after. - Use short IDs from
jut status --jsonoutput (short_idfield) to reference revisions. - After a successful
--status-after, do not run redundantjut status. - Use raw
jjfor interactive commands (split, resolve, diffedit, edit, rebase) — jut intentionally does not wrap these. - Never fabricate change IDs. Always read them from
jut status,jut log, orjut showoutput first. - jj has no staging area. The working copy IS the stage. Don't look for
add/stagecommands. - Build stacks, not monoliths. Multiple related changes should be multiple stacked commits, not one giant commit. Use
jut branch <name> --stackto chain them. - jj's working copy IS a commit. You describe it, evolve it, then
jut commit(orjj new) to start the next one. This is NOT git's "stage → commit" model.
Core Flow
# 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
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
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
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:
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
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
jut discard <file> # Restore file (discard changes)
jut discard <rev> # Abandon revision
Auto-detects whether target is a file path or revision ID.
Absorb
jut absorb # Auto-amend changes into the right commits
jut absorb --dry-run # Show plan without applying
Push, Pull & PR
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
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
{
"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
{
"revisions": [
{ "change_id": "...", "short_id": "...", "description": "...", "bookmarks": [...], ... }
]
}
jut pull --json
{
"fetched": true,
"rebased": true,
"merged_bookmarks": ["old-feature"],
"cleaned_bookmarks": [],
"conflicts": []
}
jut pr --json
{
"created": true,
"bookmark": "feature-x",
"pr_url": "https://github.com/user/repo/pull/42"
}
Task Recipes
Start new feature work
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)
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:
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):
# 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
jut push --json --status-after
jut pr --json
Amend a file into an older commit
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
jut rub <file> zz --json --status-after
# or equivalently:
jut discard <file> --json --status-after
Abandon a revision
jut rub <rev> zz --json --status-after
# or equivalently:
jut discard <rev> --json --status-after
Clean up after pull (delete merged bookmarks)
jut pull --clean --json --status-after
Undo a mistake
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)
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)
jj rebase -r <rev> -d <dest>
jut status --json # Refresh state
Resolve conflicts (drop to jj)
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 commitdescribes it and creates a new one. - Change IDs (reverse hex) are stable across rebases. Commit IDs change. Always prefer change IDs.
short_idfrom JSON output is the shortest unique prefix — use these for brevity.rubis positional:jut <source> <target>works without therubsubcommand.zzis the universal discard target forrub.--status-afterreturns 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 checkas routine preflight. - Run
jut skill checkwhen command behavior diverges from this skill. - If update available, recommend
jut skill check --update.
- Do not run
- 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.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
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".
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.
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".
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".
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".
linear
Read-only Linear issue access via the Linear GraphQL API.
Didn't find tool you were looking for?