| name | jj-uncommit-to-working-copy |
| description | In a jj-colocated git repo, take a jj commit (default `@-`, the parent of the working copy) and fold its changes into the working copy as undescribed changes — equivalent to "uncommitting" so they re-appear as unstaged changes in `git status`. NEVER calls `git reset`, `git checkout`, or any other mutating git command. Use when: the user says "uncommit", "put changes back as unstaged", "undo last jj commit", "move commit into working copy", "unstage from jj", or has a described jj commit they want to re-stage / re-split / amend differently.
|
| license | MIT |
| metadata | {"source":"pi-agent-dashboard","version":"1.0"} |
Uncommit a jj revision back into the working copy
Folds the changes from a target jj revision into @ (the working copy
commit) and clears @'s description. The result: the target commit's
diff is now in the working copy, with no description, and git status
shows the files as unstaged. The original target commit is squashed
(becomes empty) and its descendants are rebased automatically by jj.
⚠️ This skill never invokes git reset, git checkout, git stash,
or any other mutating git command. All operations go through jj.
The git working tree updates because jj is colocated.
When to use this
- The user has a described jj commit they want to redo / re-split.
- An agent committed too eagerly; user wants to amend differently.
- The user wants
git diff / git status to reflect the changes
unstaged, e.g. so they can git add -p selectively, or hand the
diff to a different tool.
If the user wants to rewrite the description without moving files,
use jj describe instead. If they want to drop the changes, use
jj abandon. This skill is specifically for "put changes back where I
can edit/restage them".
Refusal preconditions
test -d .jj && test -d "$(jj workspace root)/.git" || \
{ echo "Not a jj-colocated repo. Aborting."; exit 1; }
[ -z "$(jj resolve --list 2>/dev/null)" ] || \
{ echo "Unresolved conflicts. Run 'jj resolve' first."; exit 1; }
git diff --cached --quiet || \
{ echo "Git index is dirty. See refusal message below."; exit 1; }
Why a dirty git index is fatal here
Identical to jj-workspace-fold-back: jj has no concept of staging.
If jj squash runs while the index has staged blobs, the staged
content is left pointing at a tree jj never saw. The next git commit
creates a git-only commit and history bifurcates.
When the index is dirty, do not auto-fix. Tell the user:
The git index has staged changes. In a jj-colocated repo, the staging
area is invisible to jj — staged content cannot be folded back. Pick one:
❌ Don't: git stash (forbidden — corrupts jj history)
✓ Safe: git reset Clears the index. Files on disk are
untouched. jj's view is unchanged because
jj never reads the index.
✓ jj way: jj new -m "WIP" Set the current work aside as a real
jj change. Then start fresh on top.
(`jj edit <change-id>` returns to it later.)
Then exit the skill. Re-invoke after the user has resolved it.
Default flavor: uncommit @- into @
This is the most common case — the user's last described commit is
@- and they want it back as working-copy changes.
TARGET="${1:-@-}"
PRE_OP="$(jj op log -T 'id.short() ++ "\n"' --limit 1 --no-pager | head -1)"
TARGET_REV="$(jj log -r "$TARGET" --no-graph --no-pager -T 'change_id.short()' --limit 1 2>/dev/null)"
WC_REV="$(jj log -r '@' --no-graph --no-pager -T 'change_id.short()' --limit 1)"
if [ -z "$TARGET_REV" ]; then
echo "Revision '$TARGET' not found. Aborting."; exit 1
fi
if [ "$TARGET_REV" = "$WC_REV" ]; then
echo "Target is the working copy itself. Use 'jj describe -m \"\"' to clear the description, or pick a different revision."
exit 1
fi
BOOKMARKS_AT_TARGET="$(jj bookmark list -r "$TARGET" -T 'name ++ "\n"' --no-pager 2>/dev/null | tr -d '\n' | sed 's/$//')"
if [ -n "$BOOKMARKS_AT_TARGET" ]; then
echo "Warning: bookmarks at $TARGET — squashing will move them."
echo " $BOOKMARKS_AT_TARGET"
echo "Continue anyway? (Ctrl-C to abort)"
read -r _
fi
if ! jj squash --from "$TARGET" --into @; then
echo "Squash failed. Restoring pre-op state."
jj op restore "$PRE_OP"
exit 1
fi
jj describe -m '' --no-edit 2>/dev/null || jj describe -m ''
echo "Uncommitted $TARGET into working copy."
echo
echo "Working copy now contains:"
jj diff -r @ --summary
echo
echo "git status sees:"
git status --short
Variants
Uncommit a specific revision (not @-)
bash uncommit-to-wc.sh xnmsxzos
bash uncommit-to-wc.sh 'description("feat: rail")'
The default target is @- but any single-revision revset works. If the
revset matches multiple revisions the script aborts with an error so the
user can narrow it.
Uncommit a range of commits (mode: range)
Use only when the user explicitly asks for "uncommit the last N
commits" or "uncommit everything since main".
RANGE="${1:?usage: bash uncommit-range-to-wc.sh '<revset>'}"
PRE_OP="$(jj op log -T 'id.short() ++ "\n"' --limit 1 --no-pager | head -1)"
for REV in $(jj log -r "$RANGE" --reversed --no-graph --no-pager -T 'change_id.short() ++ "\n"'); do
if ! jj squash --from "$REV" --into @; then
echo "Squash of $REV failed; rolling back."
jj op restore "$PRE_OP"; exit 1
fi
done
jj describe -m ''
Caveat: range mode collapses all descriptions. The user is opting in
to lose them. If they want to keep them, they should split the work
manually (jj split).
After successful uncommit
The original target commit is empty (or rebased away if abandon-empty
is on). The working copy now carries those changes, undescribed. Common
next steps the user might want:
git add -p then git commit (creates a git-only commit; jj will
observe it on next snapshot — fine, this is the standard colocated
workflow).
jj split to carve the working copy into multiple new described
commits.
jj describe -m "..." to give the working copy a description.
Do not run any of the above automatically — the whole point of this
skill is to hand control back to the user.
Recovery
jj op restore <op-id> undoes any operation. jj op log lists recent
operations with their ids. The skill captures PRE_OP before any
mutation, so a single jj op restore "$PRE_OP" returns the repo to
its pre-skill state — including any rebased descendants.
Why this exists separately from jj-workspace-fold-back
jj-workspace-fold-back ships work outward to origin; this skill
moves work inward to the working copy. Opposite directions, both
needed, kept separate so neither bloats with the other's edge cases.