"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."
Resources
2Install
npx skillscat add edmundmiller/dotfiles/jut Install via the SkillsCat registry.
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 diffCommitting
jut commit -m "message" # Describe @ + create new empty change
jut commit # Opens editor for messagejj'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 branchThe 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 editorDiscard
jut discard <file> # Restore file (discard changes)
jut discard <rev> # Abandon revisionAuto-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 applyingPush, 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 messageHistory
jut undo # Undo last operation
jut oplog # View operation history
jut oplog -n 20 # More operations
jut oplog restore <op-id> # Restore to previous stateJSON 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-afterStart stacked work (depends on current branch)
jut branch part-2 --stack --json --status-after
# ... make changes ...
jut commit -m "part 2" --json --status-afterMulti-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-afterNOT 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 --jsonAmend 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 commitDiscard all changes to a file
jut rub <file> zz --json --status-after
# or equivalently:
jut discard <file> --json --status-afterAbandon a revision
jut rub <rev> zz --json --status-after
# or equivalently:
jut discard <rev> --json --status-afterClean up after pull (delete merged bookmarks)
jut pull --clean --json --status-afterUndo 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-afterSplit a commit (drop to jj)
jut status --json # Get the revision ID
jj split -r <rev> # Interactive split in jj
jut status --json # Refresh stateRebase work (drop to jj)
jj rebase -r <rev> -d <dest>
jut status --json # Refresh stateResolve conflicts (drop to jj)
jut status --json # See conflicted revisions (is_conflicted: true)
jj resolve -r <rev> # Interactive merge tool
jut status --json # Verify resolutionWhen 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.