plugins/sjawhar/skills/using-jj/SKILL.md
Use when performing ANY version control operation, starting a work session, checking repo state, or orienting to a codebase. This user uses jj instead of git — NEVER use git commands. Triggers on: commit, push, pull, branch, checkout, rebase, merge, diff, log, status, stash, reset, cherry-pick, bookmark, workspace, conflict resolution, 'what's the repo state', 'are other agents working here', 'what branches exist', 'starting work', 'orient me'.
npx skillsauth add sjawhar/dotfiles using-jjInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This user uses jj (Jujutsu) instead of git. Never use git commands unless explicitly told to. If you're thinking git commit, git push, git checkout, git rebase, etc. — STOP and use the jj equivalent from this skill.
jj-agent-statusjj-agent-status gives you a complete repo orientation in one command — where you are, what needs attention, who else is working here, and what branches exist. Useful when starting a session, checking for other agents, or triaging repo state. Not needed for routine operations like push, describe, or rebase.
jj-agent-status # Quick orientation (auto-deep for <15 bookmarks)
jj-agent-status --deep # Add trunk distance per branch (+N)
jj-agent-status --deep --branches # Full detail with trunk distance
jj-agent-status --json # Machine-readable JSONL
jj-agent-status --help # See all options
Example output:
@ uzpy on nywr [sami] — 9 files
default@
files: session.ts, bus/index.ts, serve.ts...
🤖 AGENTS:
reskin@ → workable-route-merge: reskin ralph v2-3 (2h8m) ⚠️ editing @ would rebase them
⚡ NEEDS ATTENTION:
6 undescribed changes (31 files)
5 divergent
1 need push: feat/memory-telemetry
📦 5 BRANCHES (13 changes with work)
1password-reskin-ralph +8 tsqm 2026-03-28 fix: subtle borders...
fix/sse-backpressure ⚡ +1 xsmw 2026-03-27 fix: add SSE backpressure...
TRUNK: pyxl [dev]
This tells you:
*), divergence (⚡), agents (🤖), and trunk distance (+N)jj-agent-status combines jj log, jj status, jj workspace list, and oc ps into one view. Reach for it when you need the big picture, not for every jj interaction.
jj agent-logAlways use jj agent-log instead of jj log when you need to inspect revision history. It outputs one JSON object per line (JSONL) with no graph, which is far easier to parse than the default human-readable graph.
jj agent-log # default revset, JSONL
jj agent-log -r 'ancestors(@, 5)' # scoped revset
jj agent-log -r 'bookmarks()' # all bookmarked changes
Each line is a valid JSON object:
{"change":"nywr","commit":"28e998","parents":["xnrv","xqou"],"bookmarks":["sami"],"empty":false,"conflict":false,"divergent":false,"immutable":true,"desc":"sami: octopus merge"}
Fields: change (stable ID for commands), commit (hex, changes on rewrite), parents (topology), bookmarks (local only, * suffix = unsynced), workspace (present only if a working copy is here), empty/conflict/divergent/immutable (boolean flags), desc (first line or null).
Use jj log (without agent-) only when you need to show the user the human-readable graph, or with -T builtin_log_compact for a one-off human-readable view from within an agent environment.
jj command auto-snapshots the working copy. There is no git add.qzmzpxyl) are stable across rewrites. Commit IDs (hex) change when the commit is modified. Prefer change IDs to refer to things.@ = working copy change. Not like git HEAD — it represents what's on disk right now, including uncommitted work. @- is its parent.@ unless asked.jj op log. You can inspect any previous state with --at-op and restore with jj op restore. Run jj st > /dev/null frequently to create snapshot recovery points./0, /4 suffixes). This is usually fine — resolve by squashing the copies together.If a jj command doesn't do what you expected, STOP. Do not chain jj undo → retry → jj undo → retry.
Every jj operation (including undo) writes to a shared operation log. Undo loops create operation churn that causes divergent commits across all workspaces. One agent running 10 undo/redo cycles in 5 minutes can corrupt the history for every other workspace.
When something goes wrong:
jj-agent-status to understand your current stateRed flags — STOP and ask the user:
jj undo for the second time/0, /4 suffixes on change IDs (divergent commits)jj log shows something unexpected and you're not sure whyjj op restore to an earlier stateAll changes accumulate in the working copy change (@). Don't create new commits for fixes — just make changes and push again.
@ — all file changes are auto-capturedjj git push (see Pushing Changes)To modify a change that already has a description, do NOT make changes in @, describe @, then squash. This opens an interactive editor that fails in agent contexts.
Option 1: Edit the target directly (preferred)
jj edit <change_id> # Move @ to the change you want to modify
# Make your changes directly
jj new # Create new empty change when done
Option 2: Squash without describing
# Make changes in @ — do NOT run jj describe
jj squash # Content moves to @-, parent keeps its description
| Task | Command |
|------|---------|
| Status | jj status |
| Log (human-readable) | jj log |
| Log (agent — JSONL, no graph) | jj agent-log |
| Diff of current change | jj diff |
| Diff of specific change | jj diff -r <rev> |
| Show current change | jj log -r @ |
| Describe current change | jj describe -m "message" |
| Create new empty change | jj new |
| New change on specific parent | jj new <rev> |
| New change with message | jj new -m "message" |
| Insert change before current | jj new -B @ |
| Edit an existing change | jj edit <rev> |
| Move to next/prev change | jj next --edit / jj prev --edit |
| Squash @ into parent | jj squash |
| Squash interactively (TUI) | jj squash -i |
| Redistribute edits to ancestors | jj absorb (see Gotchas) |
| Abandon a change | jj abandon <rev> |
| Undo last operation | jj undo |
| Redo undone operation | jj redo |
| Rebase (default: branch) | jj rebase -o <dest> (defaults to -b @) |
| Rebase revisions only | jj rebase -r <rev> -o <dest> |
| Rebase revision + descendants | jj rebase -s <rev> -o <dest> |
| Rebase whole branch | jj rebase -b <rev> -o <dest> |
| Insert revision after target | jj rebase -r <rev> -A <target> |
| Insert revision before target | jj rebase -r <rev> -B <target> |
| Create merge commit | jj rebase -s <rev> -o <parent1> -o <parent2> |
| List bookmarks | jj bookmark list |
| Create/move bookmark to @ | jj bookmark set <name> |
| Push | jj git push |
| Fetch | jj git fetch |
| Update stale workspace | jj workspace update-stale |
jj rebase moves revisions to different parents while preserving their diffs. The behavior varies significantly depending on which source flag you use.
-r (revisions only) -- extracts and re-parents children
Rebases ONLY the specified revisions. Descendants are re-parented onto the revision's OLD parents, filling the "hole". The revision is "extracted" from the graph.
jj rebase -r K -o M
BEFORE AFTER
M K'
| |
| L M
| | => |
| K | L' <-- L was re-parented from K to J (K's old parent)
|/ |/
J J
Use -r when you want to move a commit without bringing its descendants. Common for rewriting octopus merge parents.
-s (source + descendants) -- moves subtree intact
Rebases the specified revision AND all its descendants. The whole subtree moves together.
jj rebase -s M -o O
BEFORE AFTER
O N'
| |
| N M'
| | |
| M => O
| | |
| | L | L
| |/ | |
| K | K
|/ |/
J J
Use -s when you want to transplant a whole feature branch. Multiple -s arguments make each a direct child of dest (flattening).
-b (branch) -- moves everything not already on dest
Rebases the whole "branch" relative to the destination: the set (dest..rev):: -- meaning revisions that aren't ancestors of dest, plus ALL their descendants.
Equivalent to: jj rebase -s 'roots(dest..rev)' -o dest
jj rebase -b M -o O (same result if you said -b L or -b K)
BEFORE AFTER
O N'
| |
| N M'
| | |
| M | L'
| | => |/
| | L K'
| |/ |
| K O
|/ |
J J
Use -b when rebasing after a fetch -- it moves your whole branch onto the updated trunk. This is the default when no flag is specified (jj rebase -o dest implies -b @).
| Flag | Behavior |
|------|----------|
| -o/--onto (alias -d) | Place onto targets. Existing descendants of targets unaffected. |
| -A/--insert-after | Like -o, but also rebases targets' existing descendants onto the rebased revisions. |
| -B/--insert-before | Rebases onto targets' parents, then rebases targets and their descendants onto the rebased revisions. |
-A and -B can be combined to splice revisions into a specific location in the graph.
Repeat -o to create a merge:
jj rebase -s L -o K -o M # L now has parents K and M
jj rebase -r @ -o A -o B -o C # Reset @'s parents to A, B, C (octopus merge)
# Rebase current branch onto updated trunk (most common)
jj rebase -o trunk()
# Rebase all local branches onto trunk
jj rebase -s 'all:roots(trunk()..@)' -o trunk()
# Reset a merge commit's parents (e.g., drop branches from octopus)
jj rebase -r <merge> -o <parent1> -o <parent2> -o <parent3>
# Extract a commit from middle of chain (descendants stay)
jj rebase -r <middle> -o <new-parent>
# Move whole feature branch onto new base
jj rebase -b <branch-tip> -o <new-base>
Revsets are a functional query language for selecting commits. Most commands accept -r <revset>.
| Revset | Meaning |
|--------|---------|
| @ | Working copy change |
| @- | Parent of @ |
| @+ | Child of @ |
| trunk() | Main/master/trunk on remote |
| root() | Root commit (zzzzzzzz) |
| mine() | Changes authored by current user |
| heads(all()) | All branch heads |
| ::x | Ancestors of x |
| x:: | Descendants of x |
| x..y | Range between x and y |
| ancestors(x, depth) | Ancestors with depth limit |
| description(substring:x) | Changes with x in description |
| bookmarks() | Changes with bookmarks |
| remote_bookmarks() | Changes with remote bookmarks |
Rebase all branches onto updated trunk:
jj rebase -s 'all:roots(trunk()..@)' -o trunk()
The all: prefix is required when a revset resolves to multiple revisions (confirms you intended multiple results).
jj conflict markers differ from git:
<<<<<<< / >>>>>>> — start/end of conflict+++++++ — start of a snapshot (full content of one side)%%%%%%% — start of a diff (changes to apply to the snapshot)To resolve: edit the file to remove all markers, keeping the correct content. Resolving a parent conflict auto-resolves descendants via automatic rebasing.
Before pushing, ALWAYS run jj bookmark list to see what bookmarks actually exist.
| Action | Command |
|--------|---------|
| Push all tracked bookmarks | jj git push |
| Push specific bookmark | jj git push --bookmark <name> |
| Create new remote branch | jj git push --named <name>=@ |
--named does not require --allow-newCommon mistake: Labels ending with @ in jj log output (e.g. default@, my-workspace@) are workspace markers, NOT bookmarks. Only names in the bookmark position (without trailing @) are actual bookmarks. Always verify with jj bookmark list.
jj bookmark set <name> to move themtug aliasThis user has a custom alias: jj tug moves the closest bookmark to @. Use it to update a bookmark to point at the current change before pushing.
You may be in a jj workspace (not the default workspace). Check with jj workspace list.
This user uses colocated repositories (jj + git coexist). A .git folder is present and tools like gh work fine. However, always use jj commands instead of git — git operations can desync the jj state.
In non-default workspaces:
jj workspace update-stalejj log -r @ to confirm your working copy is where you expectMultiple jj workspaces share one operation log and one commit store. Every jj command you run — including jj st, jj undo, jj rebase — writes to that shared log. Other Claude sessions in other workspaces see your operations and vice versa.
Consequences:
/0, /4 suffixes)@ (or its ancestors) makes that workspace stale — this only matters when workspaces share lineage, not when they're on independent branchesRules for parallel workspaces:
jj workspace update-stale before doing anything elsejj git fetch && jj rebase -o mainWhen resolving conflicts after rebase:
jj log to see what divergedjj absorb — merge workflow onlyAbsorb is for merge workflows — when @ sits on top of a merge of multiple branches and you want to distribute fixes back to whichever branch owns each line. It uses blame to route each changed line to the ancestor that last modified it.
Do NOT use absorb to rewrite historical commits. To fix a specific ancestor commit, use:
jj edit <change_id> → make changes → jj new (preferred)jj squash --into <change_id> -- <paths> (for routing specific files)How it works:
jj absorb [--from=@] [--into=mutable()]
1. Diff @'s tree against @'s parent tree (what you changed)
2. Annotate each line of the PARENT tree via blame → find which ancestor last touched it
3. Assign each diff hunk to the ancestor that owns those lines
4. Rewrite destination commits (3-way merge the hunks in)
5. Rebase @ (remove absorbed hunks) and all descendants
What absorb CANNOT route (stays in @):
What can go wrong:
Always verify after absorb:
jj diff # Check what's left in @
jj log -r ::@ # Check for (conflict) markers on ancestors
jj squash opens editor when both changes have descriptionsWhen both @ and @- have non-empty descriptions, jj squash opens an interactive editor to combine them. This always fails in agent/non-TTY contexts.
If you already described @ and need to squash:
jj squash -m "description" — set the final description directlyjj squash -u — keep the destination's description, discard source'sBut the real fix is to not get into this state — see "Modifying Existing Changes" above.
jj split opens an interactive TUI by defaultjj split with no arguments opens an interactive TUI to choose which changes go into the new commit. This times out in agent bash sessions.
To split non-interactively, pass file paths or a revset:
jj split <path> [<path>...] — move named files into the first commitjj split -r <rev> <path> — split a specific revisionPer the global Commits AGENTS.md rule, prefer not splitting at all — one commit per PR is the default.
jj diff in non-TTY / agent contextsStandard jj diff uses word-level diffs that concatenate old and new text without ANSI color codes in non-TTY output. This makes diffs unreadable — e.g. my-org/aboreturn-value is actually [deleted:ab][added:return-value] rendered without color.
Always use --git for verification in agent/piped contexts:
jj diff --git # Standard unified diff format (readable without color)
jj diff --git -r <rev> # For a specific revision
jj diff --git --stat # Summary of changed files
development
Use when searching flights, hotels, or rental cars; comparing fares across flexible dates; discovering cheap destinations from a fixed origin; or hunting hidden-city ticketing deals. Trigger on multi-city itineraries, fare calendars, "where can I fly cheaply", price-sensitive trip planning, or any time the user wants a sanity-check against Google Flights pricing — Skiplagged surfaces hidden-city deals other engines deliberately hide.
development
Search the web via Ceramic Search (lexical/keyword-based). Use when looking up current events, recent news, time-sensitive facts, specific people/products/companies, technical docs, or any topic requiring fresh web results. Triggers on "search the web", "look up", "find recent", "latest news", "current", or when built-in knowledge is likely stale.
tools
Use when reading WhatsApp messages, searching conversations, sending messages, listing chats, or interacting with WhatsApp workspaces
tools
Watch CI status, fix failures, and merge when green