| name | jut |
| version | 0.1.0 |
| description | 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. |
| author | Edmund Miller |
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 --json to get workspace state, stack structure, and change IDs.
- For all mutations, always use
--json --status-after.
- Use short IDs from
jut status --json output (short_id field) to reference revisions.
- After a successful
--status-after, do not run redundant jut status.
- Use raw
jj for 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, or jut show output first.
- jj has no staging area. The working copy IS the stage. Don't look for
add/stage commands.
- Build stacks, not monoliths. Multiple related changes should be multiple stacked commits, not one giant commit. Use
jut branch <name> --stack to chain them.
- 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
jut status --json
jut <command> --json --status-after
jj split -r <rev>
jj rebase -r <rev> -d <dest>
Command Reference
Inspection
jut status
jut status -f
jut status -v
jut log
jut log -n 50
jut log --all
jut diff
jut diff <rev>
jut show <rev>
jut show <rev> -v
Committing
jut commit -m "message"
jut commit
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>
jut branch <name> --stack
jut branch <name> --from <rev>
jut branch -l
jut branch -d <name>
jut branch --rename <old> <new>
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>
| 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
jut squash <rev>
jut squash <from> <into>
jut squash <from> <into> -m "x"
jut reword <rev> -m "new msg"
jut reword <rev>
Discard
jut discard <file>
jut discard <rev>
Auto-detects whether target is a file path or revision ID.
Absorb
jut absorb
jut absorb --dry-run
Push, Pull & PR
jut push
jut push <bookmark>
jut pull
jut pull --clean
jut pull --no-rebase
jut pull --dry-run
jut pr
jut pr <bookmark>
jut pr -m "title\nbody"
History
jut undo
jut oplog
jut oplog -n 20
jut oplog restore <op-id>
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
jut commit -m "implement feature" --json --status-after
Start stacked work (depends on current branch)
jut branch part-2 --stack --json --status-after
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
jut commit -m "add authentication" --json --status-after
jut branch profile --stack --json --status-after
jut commit -m "add user profiles" --json --status-after
jut branch settings --stack --json --status-after
jut commit -m "add settings page" --json --status-after
NOT THIS — one giant commit (git muscle memory):
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
jut rub <file> <rev> --json --status-after
Discard all changes to a file
jut rub <file> zz --json --status-after
jut discard <file> --json --status-after
Abandon a revision
jut rub <rev> zz --json --status-after
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
jut oplog --json
jut oplog restore <op-id> --json --status-after
Split a commit (drop to jj)
jut status --json
jj split -r <rev>
jut status --json
Rebase work (drop to jj)
jj rebase -r <rev> -d <dest>
jut status --json
Resolve conflicts (drop to jj)
jut status --json
jj resolve -r <rev>
jut status --json
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.