eos-commit-mine
Commit ONLY your own changes when they're entangled — intermixed in the same files — with another session's (or the user's) uncommitted work-in-progress. Produces a clean, single-purpose commit of jus
SKILL.md.claude/skills/eos-commit-mine/SKILL.md
GitHubEmptyOS Commit-Mine
Commit only your own hunks when your edits are intermixed, in the same files, with a parallel session's uncommitted WIP — leaving that WIP byte-exact and uncommitted for its owner.
git add <file> stages the whole file, bundling the other session's WIP
into your commit. git add -p (patch mode) is interactive and not supported in
this harness. This skill is the deterministic, non-interactive way to get a
clean single-purpose commit anyway, with a byte-exact safety net for the WIP.
Origin: shipped the publish SSG-gap + ranked-search work while a parallel
publish-site session had an uncommitted "Editions band" WIP in the same
builder.py / templates.py. Ran this dance twice; second occurrence → skill.
When to use
All of these hold:
git status --shortshows a file you edited that also carries changes you didn't make (another Claude session, the user, a linter, a background track).- You want a commit of just your work, and the other changes to stay uncommitted (their owner will commit them).
git add <file>would wrongly bundle both.
When NOT to use
- Your changes are in their own files → just
git add <those files>and commit. No entanglement, no skill. - The parallel changes are yours too and ready → commit both together
(
git add <files>); simpler and honest. - You're unsure whose the other changes are → stop and ask the user (per CLAUDE.md §Git: "decline to commit unfamiliar uncommitted changes"). This skill assumes you've already established the split; it does not decide ownership for you.
The problem shape
working tree file = HEAD + YOUR changes + THEIR uncommitted WIP
└── want to commit ──┘ └── leave alone ──┘
Goal: a commit = HEAD + YOUR changes, and a working tree left at
HEAD + YOUR changes + THEIR WIP (so git diff HEAD afterwards = THEIR WIP
only), byte-identical to what it was before.
Method — index-only (preferred; never removes their WIP from disk)
Both methods below reset the file with git checkout HEAD -- F and rely on a
backup to put their WIP back. That opens a window in which their uncommitted
work exists only in your scratchpad — if anything dies in between, it is
gone, because uncommitted work is in no git object. git apply --cached writes
to the index only, so the working tree is never touched, there is no backup,
no restore, and no window.
git diff -- F > "$SP/full.patch" # working tree vs index/HEAD
# …build "$SP/mine.patch" (see step 3 below for the classifier)
git apply --cached --check "$SP/mine.patch" && git apply --cached "$SP/mine.patch"
git add <your-own-untouched-files>
Because the working tree still carries their WIP, you cannot test your isolated change there. Materialise exactly what is staged somewhere else and test it there — also non-destructive:
rm -rf "$SP/idx" && mkdir -p "$SP/idx"
git checkout-index -a --prefix="$SP/idx/"
(cd "$SP/idx" && python -m pyflakes <your files> && python -m pytest <your test> -q)
This is the step that catches a split which applies cleanly but does not stand alone — an import you kept whose only user was in their hunks, or a manifest entry left pointing at a method that landed on their side.
Then commit, and confirm the split held:
git diff --cached | grep -ci "<THEIRS-marker>" # MUST be 0 before committing
git diff --stat -- F # their WIP, still unstaged
Selecting hunks: prefer @@ identity over markers
The classifier below keys on marker strings, and markers mis-assign more often than they look like they will: most lines of a feature never mention the feature's name. A first pass keyed on "every added line in this hunk carries their marker" classified zero of their hunks as theirs, because their code was mostly plumbing with neutral names.
Worse, the classifier's fallback for a hunk carrying neither marker is
WARN neutral hunk kept — it keeps it as yours. That is fail-open: an
unrecognised hunk of theirs lands silently in your commit.
When you can enumerate your own hunks (you just wrote them), select by @@
header instead — it needs no guessing and cannot fail open:
KEEP = ("@@ -18,7", "@@ -38,6", "@@ -378,7") # your hunks, read off the diff
...
cur, take = [line], line.startswith(KEEP)
Print every hunk header with its added-line count first and decide by eye; on a handful of hunks that is faster and safer than tuning markers.
Method — patch-classifier (when you must reset the working tree)
Use when your hunks and theirs live in different regions and you need the file itself reset — otherwise prefer the index-only method above. Fully deterministic; the backup + md5 check is the safety net.
Let $SP = your scratchpad dir. For each entangled file F:
1. Back up the entangled files (ground truth for restore)
cp F "$SP/BAK_F"
md5sum F "$SP/BAK_F" # confirm identical
2. Diff vs HEAD and classify hunks
git diff HEAD -- F > "$SP/full.patch"
grep -nE "^@@|^\+" "$SP/full.patch" | head -60 # eyeball which hunks are yours vs theirs
Pick marker strings unique to each side (identifiers, comments, CSS class prefixes, function names). Yours: things only your change introduces. Theirs: things only their WIP introduces.
3. Build a mine-only patch
Run a classifier that keeps hunks whose added lines carry YOUR markers, drops
hunks with only THEIR markers, and for any mixed hunk (both markers in one
@@ block) strips their added lines and fixes the + count in the header:
import sys, re
full, out = sys.argv[1], sys.argv[2]
lines = open(full, encoding="utf-8").read().split("\n")
THEIRS = [ ... ] # marker substrings unique to the parallel WIP
MINE = [ ... ] # marker substrings unique to your change
def has(ms, t): return any(m in t for m in ms)
o=[]; i=0; n=len(lines)
while i < n:
if lines[i].startswith("diff --git"):
hdr=[lines[i]]; i+=1
while i<n and not lines[i].startswith(("@@","diff --git")): hdr.append(lines[i]); i+=1
hunks=[]
while i<n and lines[i].startswith("@@"):
hh=lines[i]; i+=1; body=[]
while i<n and not lines[i].startswith(("@@","diff --git")): body.append(lines[i]); i+=1
hunks.append((hh,body))
kept=[]
for hh,body in hunks:
added="\n".join(l for l in body if l.startswith("+") and not l.startswith("+++"))
if has(THEIRS,added) and has(MINE,added): # mixed → strip theirs, fix count
nb=[]; removed=0
for l in body:
if l.startswith("+") and not l.startswith("+++") and has(THEIRS,l) and not has(MINE,l):
removed+=1; continue
nb.append(l)
m=re.match(r"@@ -(\d+),(\d+) \+(\d+),(\d+) @@(.*)", hh)
a1,a2,b1,b2,tail=m.groups()
kept.append((f"@@ -{a1},{a2} +{b1},{int(b2)-removed} @@{tail}", nb))
elif has(MINE,added): kept.append((hh,body))
elif has(THEIRS,added): continue # drop
else: sys.stderr.write("WARN neutral hunk kept: "+hh+"\n"); kept.append((hh,body))
if kept:
o.extend(hdr)
for hh,body in kept: o.append(hh); o.extend(body)
else: i+=1
open(out,"w",encoding="utf-8",newline="\n").write("\n".join(o).rstrip("\n")+"\n")
Sanity-check the result: grep -c <THEIRS-marker> mine.patch must be 0;
grep -c <MINE-marker> mine.patch must be > 0.
4. Reset the file to HEAD, apply mine-only, verify
git checkout HEAD -- F
git apply --check "$SP/mine.patch" && git apply "$SP/mine.patch"
python -m py_compile F # or the relevant fast check
# run the offline/unit test that proves your change (best: a test that exercises it)
5. Commit only your files
git status --short --untracked-files=no | grep "^[MA]" # confirm ONLY your files staged
git add F [other-of-your-files]
git commit -m "..." # end with the Co-Authored-By trailer per CLAUDE.md
git log -1 --stat --oneline # verify HEAD is yours
6. Restore their WIP byte-for-byte + verify the split
cp "$SP/BAK_F" F
md5sum F "$SP/BAK_F" # MUST match → their WIP restored exactly
# remaining diff vs your new commit must contain ZERO of your markers:
git diff -- F | grep -cE "<MINE-marker>" && echo "!! MINE LEAKED" || echo "CLEAN"
git diff -- F | grep -cE "<THEIRS-marker>" # > 0 → their WIP intact + uncommitted
python -m py_compile F # combined tree (yours committed + their WIP) still compiles
Method — backup + re-apply via Edit (when hunks are messy/moving)
Use when the parallel WIP is large, evolving, or genuinely interleaved with your hunks so the classifier can't split cleanly. The backup restore is byte-exact, so their WIP integrity is guaranteed regardless:
cp F "$SP/BAK_F"(their WIP + yours).git checkout HEAD -- F.- Re-apply only your edits with the Edit tool, in dependency order (your anchors must exist in the HEAD version; apply an earlier edit first if a later edit anchors on text you added).
- Verify (py_compile + your test) →
git add F→ commit. cp "$SP/BAK_F" F→ working tree = your-commit + their WIP again.git diff HEAD -- F→ must be their WIP only (no MINE markers). If a stray MINE line appears, your re-apply drifted — the working tree is still correct (BAK restored), but inspect/amend the commit.
Load-bearing safety rules
- Never touch
data/*.db*,emptyos.toml,apps/personal/(CLAUDE.md §Daemon/Git). This skill only rewrites tracked source files you edited. - The backup is ground truth.
cp BAK Frestores the parallel WIP exactly; the md5 check proves it. If anything goes sideways,cp BAK Freturns the working tree to its pre-skill state losslessly. - Two hard post-conditions, both must pass before you're done:
git diff -- F(vs your new commit) contains 0 of your markers.md5sum F == md5sum BAK_Fafter restore.
- Verify your committed tree runs (py_compile + a test) before restoring the WIP — a broken commit is the real risk, and the WIP restore hides it.
- Confirm HEAD is yours (
git log -1) after committing — a parallel session may have moved HEAD under you between steps; the diff/checkout are relative to whatever HEAD is, which stays correct, but verify the commit landed as yours.
Anti-patterns
git add -A/git add ./git add <entangled-file>— bundles their WIP. The whole reason this skill exists.- Hand-editing
@@line counts without recomputing — a wrong count makesgit applyreject or silently corrupt. Only the mixed-hunk case needs a count fix, and the classifier does it. - Skipping the md5 restore check — you might leave the parallel WIP subtly altered, which is exactly the "don't touch parallel work" rule this protects.
- Using this when your changes are in separate files — over-engineering; just
git addyour files.
Cross-references
- CLAUDE.md §Git / Version Control — "stage/commit only files scoped to the
current task; never
git add -A; verify HEAD is yours." .claude/rules/environment.md§Parallel-session staging — the rule this operationalises at hunk granularity.- memory
feedback_parallel_commit_race_steals_staged— the failure mode (parallel auto-add bundling unrelated files) this prevents.