jj
Jujutsu (jj) version control workflow, commands, and best practices. Use when working with version control in jj-enabled repos. Covers commits, bookmarks, workspaces, and safe push patterns.
Best use case
jj is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Jujutsu (jj) version control workflow, commands, and best practices. Use when working with version control in jj-enabled repos. Covers commits, bookmarks, workspaces, and safe push patterns.
Teams using jj should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/jj/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How jj Compares
| Feature / Agent | jj | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Jujutsu (jj) version control workflow, commands, and best practices. Use when working with version control in jj-enabled repos. Covers commits, bookmarks, workspaces, and safe push patterns.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Jujutsu (jj) Version Control
## Overview
**CRITICAL**: Always use `jj` commands instead of `git` for ALL version control operations in jj-enabled repos. Never use raw git commands directly.
Jujutsu is a Git-compatible VCS with automatic snapshots, mutable history, and conflict-free parallel work. This dotfiles repo is jj-initialized with full git coexistence.
## Quick Reference Card
```
STATUS: jj status (jj s)
DIFF: jj diff (jj d)
LOG: jj log (jj l)
NEW COMMIT: jj new -m "message"
DESCRIBE: jj describe -m "msg" (jj dm "msg")
SQUASH: jj squash -m "message"
FETCH: jj git fetch (jj g fetch)
PUSH: jj git push (jj push)
REBASE: jj rebase -d main (jj rb -d main)
BOOKMARK: jj bookmark set main -r @ (jj main)
```
---
## Decision Trees
### "I need to start new work"
```
Need to start new work?
│
├─▶ Standard workflow (RECOMMENDED):
│ └─▶ jj new -m "feat: description"
│ └─▶ Work in default workspace
│
├─▶ Need isolation? (⚠️ WIP - workspace scripts not stable)
│ └─▶ For now: just use jj new in default workspace
│ └─▶ Workspace scripts under development
│
└─▶ Multiple parallel features?
└─▶ jj new main -m "Feature A"
└─▶ jj new main -m "Feature B"
└─▶ Use jj edit <change-id> to switch
```
### "I need to save/commit my work"
```
Need to save work?
│
├─▶ Just want to record progress (local)?
│ └─▶ jj describe -m "work in progress"
│ (or just keep working - jj auto-snapshots)
│
├─▶ Ready to finalize commit message?
│ └─▶ jj describe -m "feat(scope): detailed message
│
│ - What changed
│ - Why it changed"
│
├─▶ Want to squash into parent?
│ └─▶ jj squash -m "combined message"
│
└─▶ Want to split into multiple commits?
└─▶ jj split
(interactive - opens editor)
```
### "I need to push to remote"
```
Ready to push?
│
├─▶ Check what will be pushed:
│ └─▶ jj log -r 'main@origin::main'
│
├─▶ Nothing to push (main == main@origin)?
│ └─▶ First move main to current: jj bookmark set main -r @
│
├─▶ Remote is ahead (commits on origin we don't have)?
│ └─▶ jj git fetch
│ └─▶ jj rebase -d main@origin
│ └─▶ Then try push again
│
└─▶ Ready to push?
└─▶ ASK USER FIRST - Never push without consent!
└─▶ jj git push --bookmark main
```
### "I have conflicts"
```
Conflicts detected?
│
├─▶ See conflict markers:
│ └─▶ jj status (shows conflicted files)
│ └─▶ Look for <<<<<<< markers in files
│
├─▶ Resolve conflicts:
│ └─▶ Edit files to resolve
│ └─▶ Remove conflict markers
│ └─▶ jj status (verify resolved)
│
├─▶ Want to use a merge tool?
│ └─▶ jj resolve <file>
│
└─▶ Want to abort and try different approach?
└─▶ jj op log (find pre-conflict state)
└─▶ jj op restore <op-id>
```
### "Something went wrong"
```
Need to recover?
│
├─▶ Undo last operation:
│ └─▶ jj undo
│
├─▶ See operation history:
│ └─▶ jj op log
│ └─▶ jj op restore <op-id>
│
├─▶ Abandon current change:
│ └─▶ jj abandon
│
├─▶ Discard uncommitted edits:
│ └─▶ jj restore
│
└─▶ Find lost commit:
└─▶ jj evolog (shows change evolution)
└─▶ jj op log (shows all operations)
```
---
## Configuration
### User's Custom Aliases
These aliases are configured and available:
| Alias | Expands To | Purpose |
|-------|-----------|---------|
| `jj b` | `jj bookmark` | Bookmark management |
| `jj d` | `jj diff` | Show diff |
| `jj dm "msg"` | `jj desc -m "msg"` | Describe with message |
| `jj dv` | `jj desc` | Describe (opens editor) |
| `jj g` | `jj git` | Git subcommand |
| `jj l` | `jj log` | Show log |
| `jj ll` | `jj log -T builtin_log_compact_full_description` | Log with full descriptions |
| `jj main` | `jj bookmark move main --to @` | Move main to current |
| `jj push` | `jj git push` | Push to remote |
| `jj rb` | `jj rebase` | Rebase |
| `jj s` | `jj status` | Status |
| `jj tug` | (moves closest bookmark to parent) | Pull bookmark down |
### Workflow Aliases (Multi-Command)
These aliases use `jj util exec` to chain multiple commands:
| Alias | Usage | Purpose |
|-------|-------|---------|
| `jj up` | `jj up [branch]` | Fetch + rebase onto origin (default: main) |
| `jj feat` | `jj feat "msg"` | Fetch + new commit from main@origin |
| `jj feat-here` | `jj feat-here "msg"` | New commit from current (no fetch) |
| `jj pr-fix` | `jj pr-fix ["msg"]` | New commit on PR branch + confirm push |
| `jj fixup` | `jj fixup` | Squash into parent + confirm push |
**Example workflows:**
```bash
# Start a new feature
jj up # Sync with origin/main first
jj feat "feat: add user auth" # Create new commit from main@origin
jj bookmark create user-auth # Create bookmark for PR
# Work on existing PR
jj up # Sync with origin
# ... make changes ...
jj pr-fix "fix: address review feedback" # New commit, asks to push
# Quick fix on existing PR
# ... make small changes ...
jj fixup # Squash into parent, asks to push
```
### User's Custom Revsets
| Revset | Meaning |
|--------|---------|
| `trunk()` | `main@origin` |
| `current_work` | Work between trunk and @, used as default log |
| `stack()` | Ancestors of reachable mutable commits |
| `stack(x)` | Stack at specific revision |
| `stack(x, n)` | Stack with depth limit |
| `closest_bookmark(to)` | Find nearest bookmark ancestor |
### UI Settings
- **Pager**: delta (with syntax highlighting)
- **Graph style**: curved
- **Editor**: neovim
- **Signing**: SSH with 1Password integration
- **Default command**: `jj log` (runs when just typing `jj`)
---
## Revset Syntax Reference
Revsets are jj's query language for selecting commits.
### Basic Selectors
| Revset | Meaning |
|--------|---------|
| `@` | Current working copy commit |
| `@-` | Parent of @ |
| `@--` | Grandparent of @ |
| `root()` | Repository root commit |
| `heads()` | All head commits |
| `main` | Bookmark named "main" |
| `main@origin` | Remote tracking bookmark |
| `abc123` | Commit/change ID (prefix match) |
### Operators
| Operator | Meaning | Example |
|----------|---------|---------|
| `::x` | Ancestors of x (inclusive) | `::main` |
| `x::` | Descendants of x (inclusive) | `@::` |
| `x::y` | Range from x to y | `main::@` |
| `x-` | Parent of x | `main-` |
| `x+` | Children of x | `main+` |
| `x \| y` | Union (x or y) | `main \| @` |
| `x & y` | Intersection (x and y) | `heads() & main::` |
| `x ~ y` | Difference (x but not y) | `all() ~ immutable()` |
| `!x` | Negation (not x) | `!immutable()` |
### Functions
| Function | Returns |
|----------|---------|
| `all()` | All commits |
| `none()` | Empty set |
| `heads(x)` | Commits in x with no descendants in x |
| `roots(x)` | Commits in x with no ancestors in x |
| `ancestors(x)` | All ancestors of x |
| `descendants(x)` | All descendants of x |
| `reachable(x, y)` | Commits reachable from x through y |
| `connected(x)` | Ancestors and descendants connecting x |
| `parents(x)` | Direct parents of x |
| `children(x)` | Direct children of x |
| `mutable()` | Non-immutable commits |
| `immutable()` | Immutable commits (usually pushed) |
| `bookmarks()` | Commits with bookmarks |
| `bookmarks(pattern)` | Commits matching bookmark pattern |
| `remote_bookmarks()` | Commits with remote bookmarks |
| `tags()` | Commits with tags |
| `git_head()` | Git HEAD |
| `empty()` | Empty commits |
| `conflict()` | Commits with conflicts |
| `author(pattern)` | Commits by author |
| `description(pattern)` | Commits with matching description |
| `file(pattern)` | Commits touching file |
### Common Revset Patterns
```bash
# What will I push?
jj log -r 'main@origin::main'
# My recent work
jj log -r '@::'
# Unpushed work on any bookmark
jj log -r 'bookmarks() ~ remote_bookmarks()'
# Find commits with "fix" in message
jj log -r 'description("fix")'
# Commits I authored
jj log -r 'author("seth")'
# Commits touching specific file
jj log -r 'file("home/programs/ai")'
# All workspace bookmarks
jj log -r 'bookmarks(glob:"ws/*")'
```
---
## Template Language
Templates control output formatting with `-T` flag.
### Common Template Variables
| Variable | Type | Description |
|----------|------|-------------|
| `commit_id` | CommitId | Full commit hash |
| `change_id` | ChangeId | jj change ID |
| `description` | String | Commit message |
| `author` | Signature | Author info |
| `committer` | Signature | Committer info |
| `working_copies` | String | Working copy info |
| `bookmarks` | List | Bookmarks pointing here |
| `tags` | List | Tags pointing here |
| `git_head` | Bool | Is this git HEAD? |
| `empty` | Bool | Is commit empty? |
| `conflict` | Bool | Has conflicts? |
| `root` | Bool | Is root commit? |
### Template Methods
```
commit_id.short() # Short hash
commit_id.short(8) # 8-char hash
change_id.shortest() # Shortest unique prefix
description.first_line() # First line only
author.name() # Author name
author.email() # Author email
author.timestamp() # Commit time
```
### Template Syntax
```bash
# Simple template
jj log -T 'change_id.short() ++ " " ++ description.first_line() ++ "\n"'
# Conditional
jj log -T 'if(empty, "(empty) ") ++ description.first_line()'
# With labels (for colors)
jj log -T 'label("commit_id", commit_id.short())'
# Check boolean
jj log -r @ --no-graph -T 'if(empty, "true", "false")'
```
---
## Workspace Scripts (⚠️ WORK IN PROGRESS)
> **WARNING**: These workspace scripts are under active development and not fully
> functional yet. Do NOT rely on them for production work. Use standard jj commands
> (`jj new`, `jj describe`, etc.) in the default workspace until these are stable.
Four custom scripts for AI agent workspace management:
### jj-ws-claim
Claim or create a workspace for isolated work.
```bash
# Basic usage
jj-ws-claim <task-id>
# With options
jj-ws-claim <task-id> --base <revision> --json
# Example
jj-ws-claim hs-memory-leaks
# Creates: .workspaces/hs-memory-leaks/
# Creates bead task if doesn't exist
```
**What it does:**
1. Creates `.workspaces/<task-id>/` directory
2. Creates jj workspace pointing to main repo
3. Creates bead task for tracking (if `bd` available)
4. Uses mkdir-based locking for concurrent safety
**Exit codes:**
- 0: Success
- 1: Invalid arguments
- 2: Not in jj repository
- 3: Workspace creation failed
- 4: Already in non-default workspace
### jj-ws-complete
Complete work in a workspace and clean up.
```bash
# Complete current workspace
jj-ws-complete
# Complete specific workspace
jj-ws-complete <workspace-name>
# Options
jj-ws-complete -r # Rebase if parallel branch
jj-ws-complete --no-merge # Don't merge to main
jj-ws-complete --no-cleanup # Keep workspace directory
jj-ws-complete --reason "text" # Close reason for bead
```
**What it does:**
1. Creates `ws/<workspace-name>` bookmark for tracking
2. Merges work to main (moves main bookmark)
3. Closes associated bead task
4. Cleans up workspace directory
**Exit codes:**
- 0: Success
- 1: Invalid arguments
- 2: Not in jj repository
- 3: In default workspace (nothing to complete)
- 4: Workspace not found
### jj-ws-push
Review and push completed workspace work.
```bash
# List all completed workspace work
jj-ws-push --list
# Push specific bookmark
jj-ws-push ws/<name>
# Push all workspace bookmarks
jj-ws-push --all
# Force (skip confirmation)
jj-ws-push -f ws/<name>
```
**What it does:**
1. Lists ws/* bookmarks (completed workspace work)
2. Shows diff/log for review
3. Requires explicit confirmation before push
4. Moves main and pushes to origin
### jj-ws-status
Get current workspace status (useful for agents).
```bash
# Human readable
jj-ws-status
# Machine readable
jj-ws-status --json
```
**Returns:**
- Workspace name and path
- Commit info (empty, conflicts, description)
- Associated bead task
- All available workspaces
### Workspace Lifecycle (⚠️ WIP)
```
⚠️ NOT YET STABLE - Use standard jj workflow for now
┌─────────────────┐
│ jj-ws-claim │ Create isolated workspace
└────────┬────────┘
│
▼
┌─────────────────┐
│ Work in │ Make changes, jj describe
│ workspace │
└────────┬────────┘
│
▼
┌─────────────────┐
│ jj-ws-complete │ Creates ws/* bookmark, merges to main
└────────┬────────┘
│
▼
┌─────────────────┐
│ jj-ws-push │ User reviews and pushes (requires consent!)
└─────────────────┘
```
**Current Recommended Workflow (Stable):**
```
jj new -m "description" → work → jj describe → jj bookmark set main -r @ → ask user to push
```
---
## Command Reference
### Core Commands
| Command | Purpose | Example |
|---------|---------|---------|
| `jj status` | Show working copy changes | `jj s` |
| `jj diff` | Show current change diff | `jj d` |
| `jj log` | Show revision history | `jj l` |
| `jj show` | Show commit details | `jj show @-` |
| `jj new` | Create new change | `jj new -m "feat: add X"` |
| `jj describe` | Update commit message | `jj dm "message"` |
| `jj edit` | Switch to editing a change | `jj edit abc123` |
| `jj abandon` | Discard a change | `jj abandon @` |
### History Manipulation
| Command | Purpose | Example |
|---------|---------|---------|
| `jj squash` | Merge current into parent | `jj squash -m "msg"` |
| `jj split` | Split change into multiple | `jj split` |
| `jj rebase` | Move change to new parent | `jj rb -d main` |
| `jj absorb` | Auto-distribute fixes | `jj absorb` |
| `jj duplicate` | Copy changes | `jj duplicate @` |
| `jj parallelize` | Make revisions siblings | `jj parallelize x y` |
### Bookmarks (like git branches)
| Command | Purpose | Example |
|---------|---------|---------|
| `jj bookmark list` | List bookmarks | `jj b list` |
| `jj bookmark set` | Move bookmark | `jj b set main -r @` |
| `jj bookmark create` | Create bookmark | `jj b create feat -r @` |
| `jj bookmark delete` | Delete bookmark | `jj b delete feat` |
| `jj bookmark move` | Move bookmark | `jj main` (alias) |
### Git Integration
| Command | Purpose | Example |
|---------|---------|---------|
| `jj git fetch` | Fetch from remote | `jj g fetch` |
| `jj git push` | Push to remote | `jj push` |
| `jj git clone` | Clone git repo | `jj git clone <url>` |
| `jj git init` | Init in git repo | `jj git init --colocate` |
| `jj git export` | Export to .git | `jj git export` |
| `jj git import` | Import from .git | `jj git import` |
### Recovery
| Command | Purpose | Example |
|---------|---------|---------|
| `jj undo` | Undo last operation | `jj undo` |
| `jj redo` | Redo undone operation | `jj redo` |
| `jj op log` | Show operation history | `jj op log` |
| `jj op restore` | Restore to past state | `jj op restore <id>` |
| `jj evolog` | Show change evolution | `jj evolog` |
| `jj restore` | Restore file content | `jj restore <file>` |
### Workspaces
| Command | Purpose | Example |
|---------|---------|---------|
| `jj workspace list` | List workspaces | `jj workspace list` |
| `jj workspace add` | Create workspace | See scripts above |
| `jj workspace forget` | Remove workspace | `jj workspace forget ws` |
| `jj workspace root` | Show workspace root | `jj workspace root` |
| `jj workspace update-stale` | Update stale ws | Auto with config |
### File Operations
| Command | Purpose | Example |
|---------|---------|---------|
| `jj file list` | List tracked files | `jj file list` |
| `jj file show` | Show file at rev | `jj file show @- file.txt` |
| `jj file chmod` | Change permissions | `jj file chmod x script.sh` |
| `jj sparse` | Sparse checkout | `jj sparse set --add dir/` |
---
## Common Workflows
### Daily Work Pattern
```bash
# 1. Start day - fetch latest
jj git fetch
# 2. Check if behind
jj log -r 'main@origin::main'
# 3. Rebase if needed
jj rebase -d main@origin
# 4. Start new work
jj new -m "feat: today's work"
# 5. Work... (changes auto-tracked)
# 6. Describe when ready
jj describe -m "feat(scope): what I did
Detailed description here."
# 7. Move main bookmark
jj bookmark set main -r @
# 8. Push (with user consent)
jj git push
```
### Parallel Features
```bash
# Create feature branches off main
jj new main -m "Feature A" # Now at feature-a change
jj new main -m "Feature B" # Creates new change off main
# Switch between them
jj edit <change-id-a> # Work on A
jj edit <change-id-b> # Work on B
# Merge both to main when done
jj rebase -r <change-id-a> -d main
jj bookmark set main -r <change-id-a>
jj rebase -r <change-id-b> -d main
jj bookmark set main -r <change-id-b>
```
### Sync with Remote (Before Push)
```bash
# Fetch latest
jj git fetch
# Check divergence
jj log -r 'main@origin..main' # Local-only commits
jj log -r 'main..main@origin' # Remote-only commits
# If remote is ahead, rebase
jj rebase -d main@origin
# Then push
jj git push --bookmark main
```
### Clean Up History
```bash
# Squash multiple small commits into one
jj squash --from <start> --into <target>
# Split a big commit
jj split # Interactive, choose files
# Reword any commit
jj describe -r <rev> -m "new message"
```
---
## Safety Rules
### NEVER Do
1. **NEVER push without explicit user consent**
2. **NEVER use git commands directly** (use jj equivalents)
3. **NEVER assume push will succeed** (always fetch first)
4. **NEVER force push** without extreme caution
### ALWAYS Do
1. **ALWAYS start work with `jj new`** - creates clean change
2. **ALWAYS check `jj log -r 'main@origin::main'`** before pushing
3. **ALWAYS ask user** before any push operation
4. **ALWAYS use headless/non-interactive flags** (see below)
---
## Interactive vs Headless Commands (CRITICAL)
**CRITICAL**: Many jj commands open an editor by default. AI agents MUST use headless flags to avoid hanging.
### Commands That Open Editor (AVOID)
| Command | Opens Editor | Headless Alternative |
|---------|-------------|---------------------|
| `jj describe` | YES - opens $EDITOR | `jj describe -m "message"` |
| `jj squash` | YES - opens $EDITOR | `jj squash -m "message"` |
| `jj split` | YES - interactive | **NO HEADLESS** - ask user |
| `jj commit` | YES - opens $EDITOR | `jj commit -m "message"` |
| `jj resolve` | YES - opens merge tool | **NO HEADLESS** - ask user |
### Always Use These Patterns
```bash
# WRONG - will hang waiting for editor
jj describe
jj squash
jj commit
# RIGHT - provide message inline
jj describe -m "feat: add feature X"
jj squash -m "combine: cleanup commits"
jj commit -m "feat: complete feature"
```
### Commands That Are Safe (Non-Interactive)
These commands never open an editor:
```bash
jj status # Safe
jj diff # Safe
jj log # Safe
jj new -m "msg" # Safe (with -m)
jj abandon # Safe
jj git fetch # Safe
jj git push # Safe
jj bookmark set # Safe
jj rebase # Safe
jj edit # Safe
jj undo # Safe
jj op log # Safe
jj op restore # Safe
```
### When Interactive is Required
Some commands have no headless mode. For these, **ask the user**:
```bash
# jj split - no headless mode
# Ask user: "I need to split this commit. Can you run `jj split` interactively?"
# jj resolve - needs merge tool
# Ask user: "There are conflicts. Can you resolve them with `jj resolve <file>`?"
```
### Editor Timeout Prevention
If you accidentally run an interactive command:
- The command will hang waiting for editor
- Use Ctrl+C to abort
- Re-run with `-m` flag
---
## Transparency Protocol
When using jj in sessions, provide:
### Inline Explanations
For each jj command, explain:
- What the command does
- Why you're running it
- Expected outcome
```
Running `jj git fetch` - This pulls the latest commits from origin
without modifying the working copy. Needed to check if main is ahead
before attempting to push.
```
### End-of-Session Summary
```markdown
## jj Commands Used This Session
| Command | Purpose |
|---------|---------|
| `jj new -m "feat: ..."` | Started new unit of work |
| `jj git fetch` | Pulled latest from remote |
| `jj describe -m "..."` | Updated commit message |
| `jj bookmark set main -r @` | Moved main to current |
| `jj git push --bookmark main` | Pushed to origin (with consent) |
```
---
## Beads Integration
### ID Correlation Patterns
Beads tasks and jj work should be correlated through consistent naming:
| Bead Task ID | jj Bookmark | jj Workspace | Commit Reference |
|--------------|-------------|--------------|------------------|
| `.dotfiles-abc` | `ws/abc` | `.workspaces/abc/` | `Task: .dotfiles-abc` |
| `.dotfiles-fix-lsp` | `ws/fix-lsp` | `.workspaces/fix-lsp/` | `Task: .dotfiles-fix-lsp` |
### Finding Correlated Work
```bash
# From bead task → find jj work
bd show .dotfiles-abc # Get task details
jj log -r 'description(".dotfiles-abc")' # Find commits referencing it
jj bookmark list | grep -i abc # Find related bookmarks
# From jj bookmark → find bead task
jj log -r 'ws/abc' --no-graph -T 'description' # Get commit description
bd show .dotfiles-abc # Look up task by ID extracted from commit
# From workspace → find both
jj-ws-status --json # Shows associated task ID
bd show $(jj-ws-status --json | jq -r '.task.id') # Get task details
```
### Workspace Scripts Auto-Correlation (⚠️ WIP)
> **Note**: Workspace scripts are under development. For now, manually correlate
> tasks with commits using the patterns below.
The `jj-ws-*` scripts (when stable) will automatically correlate:
```bash
# jj-ws-claim creates both (WIP)
jj-ws-claim fix-lsp
# Creates: .workspaces/fix-lsp/
# Creates: .dotfiles-fix-lsp (bead task)
# jj-ws-complete references task in close (WIP)
jj-ws-complete
# Closes bead task with: "Completed in workspace fix-lsp [bookmark: ws/fix-lsp]"
# jj-ws-status shows correlation (WIP)
jj-ws-status --json | jq '.task'
# {"id": ".dotfiles-fix-lsp", "status": "in_progress", "title": "..."}
```
**Current Manual Workflow:**
```bash
# 1. Create bead task
bd create fix-lsp -t task -p P2
# 2. Start jj work with reference
jj new -m "fix(lsp): address issue
Task: .dotfiles-fix-lsp"
# 3. Complete and close manually
jj describe -m "fix(lsp): resolved issue
Closes: .dotfiles-fix-lsp"
bd close .dotfiles-fix-lsp
```
### Standard Commit References
Always include task reference in commit messages:
```bash
# Short reference (in commit subject)
jj describe -m "feat(lsp): add diagnostic filtering (.dotfiles-abc)"
# Full reference (in commit body)
jj describe -m "feat(lsp): add diagnostic filtering
Implemented severity-based filtering for LSP diagnostics.
Added configuration for per-language rules.
Task: .dotfiles-abc
Closes: .dotfiles-abc"
```
### Finding Orphaned Work
```bash
# Commits without task references
jj log -r 'all() ~ description("dotfiles-")'
# Workspace bookmarks without closed tasks
for bm in $(jj bookmark list | grep '^ws/' | awk '{print $1}'); do
task_id=".dotfiles-${bm#ws/}"
if bd show "$task_id" 2>/dev/null | grep -q "open"; then
echo "Open task for $bm: $task_id"
fi
done
# Bead tasks without jj work
bd list --status=open | while read task; do
if ! jj log -r "description(\"$task\")" --no-graph 2>/dev/null | head -1 | grep -q .; then
echo "No commits for: $task"
fi
done
```
### Link in Both Directions
**Starting work (current stable workflow):**
```bash
# 1. Create bead task
bd create fix-lsp -t task -p P2 -d "Fix LSP diagnostic flooding"
# Creates: .dotfiles-fix-lsp
# 2. Start jj work with task reference
jj new -m "fix(lsp): address diagnostic flooding
Task: .dotfiles-fix-lsp"
# 3. Update task status
bd update .dotfiles-fix-lsp --status in_progress
```
**Completing work (current stable workflow):**
```bash
# 1. Describe final commit
jj describe -m "fix(lsp): implement diagnostic filtering
Added severity-based filtering for LSP diagnostics.
Closes: .dotfiles-fix-lsp"
# 2. Move main bookmark
jj bookmark set main -r @
# 3. Close bead task
bd close .dotfiles-fix-lsp --reason "Implemented in $(jj log -r @ --no-graph -T 'change_id.short(8)')"
# 4. ASK USER before pushing
# "Ready to push. Run: jj git push --bookmark main"
```
---
## Known Issues and Limitations
### jj Limitations
1. **No interactive rebase** - use `jj squash`, `jj split` instead
2. **No staging area** - all changes auto-tracked (feature, not bug)
3. **Conflicts stored in tree** - unlike git stash conflicts
4. **Immutable after push** - can't rewrite pushed commits easily
### Common Gotchas
| Issue | Cause | Solution |
|-------|-------|----------|
| "change is immutable" | Commit was pushed | Create new commit instead |
| Bookmark disappeared | Moved unexpectedly | `jj op log` + `jj op restore` |
| Working copy conflict | Auto-merge failed | Edit files, remove markers |
| "no description" warning | Empty commit message | Use `jj describe` |
| Workspace stale | Another workspace changed repo | `jj workspace update-stale` |
### GitHub Actions Interaction
The dotfiles repo has a GitHub Action that updates flake.lock on Sundays. Always:
```bash
jj git fetch # Get flake.lock updates
jj rebase -d main@origin # Rebase onto updated main
```
---
## Self-Discovery Patterns
### Finding Commands
```bash
# List all commands
jj help
# Help for specific command
jj help <command>
jj describe --help
# Search command help
jj help | grep -i <keyword>
```
### Exploring Configuration
```bash
# List all config
jj config list
# Show specific config
jj config get <key>
# Config file location
jj config path --user
jj config path --repo
```
### Inspecting Repository State
```bash
# All changes
jj log -r 'all()'
# All bookmarks
jj bookmark list
# Operation history
jj op log
# Change evolution
jj evolog
# Workspace state
jj-ws-status --json
```
### Revset Debugging
```bash
# Test revset (dry run)
jj log -r '<revset>' --no-graph
# Count matches
jj log -r '<revset>' --no-graph | wc -l
# Show IDs only
jj log -r '<revset>' --no-graph -T 'change_id.short()'
```
### Official Resources
- **jj book**: https://martinvonz.github.io/jj/latest/
- **GitHub**: https://github.com/martinvonz/jj
- **Discord**: https://discord.gg/dkmfj3aGQN
---
## Directory Structure
```
~/.dotfiles/ # Main workspace (default)
├── .jj/ # jj data directory
│ ├── repo/ # Repository data
│ │ ├── store/ # Object store
│ │ └── op_store/ # Operation store
│ ├── working_copy/ # Working copy state
│ └── workspace-ops.lock/ # Concurrent op lock (created by scripts)
│
├── .workspaces/ # Secondary workspaces (gitignored)
│ └── <workspace-name>/ # Created by jj-ws-claim
│ ├── .jj/ # Points to main repo
│ └── (files) # Working copy
│
├── bin/ # Custom scripts
│ ├── jj-ws-claim # Create workspace
│ ├── jj-ws-complete # Complete workspace
│ ├── jj-ws-push # Push workspace work
│ └── jj-ws-status # Get workspace status
│
└── _docs/ # Research/documentation
├── jj-workspace-conventions.md # Naming conventions
└── jj-workspaces-research.md # Implementation research
```
---
## Quick Troubleshooting
### "Change is immutable"
```bash
# Can't modify pushed commits
# Solution: create new change instead
jj new -m "fix: corrected version"
```
### "Bookmark moved unexpectedly"
```bash
jj op log # Find when it moved
jj op restore <op-id> # Restore previous state
```
### "Working copy is stale"
```bash
# Usually auto-resolves with auto-update-stale=true
jj workspace update-stale
```
### "Conflicts in working copy"
```bash
jj status # See conflicted files
# Edit files to resolve <<<<<<< markers
jj status # Verify resolved
```
### "Can't push - remote ahead"
```bash
jj git fetch
jj log -r 'main..main@origin' # See what's new
jj rebase -d main@origin # Rebase onto remote
jj git push # Now push
```
### Lost Work
```bash
jj op log # Find operation before loss
jj op restore <op-id> # Restore
# Or find via evolution log
jj evolog # Shows all versions of current change
```Related Skills
writing-clearly-and-concisely
Apply Strunk's timeless writing rules to ANY prose humans will read - documentation, commit messages, error messages, explanations, reports, or UI text. Makes your writing clearer, stronger, and more professional.
web-search
Web search using DuckDuckGo (free, unlimited). Falls back to pi-web-access extension for content extraction.
web-browser
Interact with web pages using agent-browser CLI. MUST run 'browser connect 9222' FIRST to use existing browser with authenticated sessions.
tmux
Remote control tmux sessions for interactive CLIs (python, gdb, etc.) by sending keystrokes and scraping pane output.
ticket-worker
Work on a single tk ticket end-to-end. Use when the user says 'work on ticket X' or when spawned by work-tickets.sh.
ticket-creator
Create and refine tickets for the tk ticket system. Use when the user says 'create tickets for X', 'refine ticket X', 'break this into tickets', 'seed tickets from plan', or anything about creating or refining tk tickets.
tell
Delegate tasks to other agents - pi sessions or external agents (claude, opencode, aider). Non-blocking with task tracking and completion notifications.
task-pipeline
Structured workflow for research → plan → tickets → work. Use when starting or continuing a task with /task, /plan, or /tickets commands.
preview
Display code, diffs, images, and other content in a tmux pane or popup. Auto-detects nvim/megaterm for floating popups.
mcpctl
Manage MCP server configurations — add, remove, list, inspect, troubleshoot. Use when asked to "add mcp server", "remove mcp", "list mcp servers", "mcp status", "configure mcp", "troubleshoot mcp", or any MCP server management task.
handoff
Save session state for later pickup. Use /handoff when context is degrading, /pickup to resume in a new session.
github
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.