diff --git a/skills/operational-rigor/SKILL.md b/skills/operational-rigor/SKILL.md index 63cd099..b33f8d5 100644 --- a/skills/operational-rigor/SKILL.md +++ b/skills/operational-rigor/SKILL.md @@ -281,21 +281,68 @@ When rigor conflicts with finishing sooner, rigor wins. be READ — but never as a merge preview: a three-way merge applies the branch's changes from the MERGE-BASE, so base-side work the branch never touched survives the merge even though the two-dot shows it as - deletions (those deletions materialize only under `reset --hard`, - re-applying the branch as a patch, or otherwise overwriting the base - with the branch tip — which is why non-empty never justifies - re-applying). Match the read to the action: what a merge would - actually change is the branch's own delta from the merge-base - (`git diff $(git merge-base ) `); what - deleting the branch would lose is its unlanded work — the two-dot - ADDITION side, or `git log ..` (`unprobed` — - contributor incident as shape; see Provenance). + deletions. Survives is not untouched — a branch-side rename of an + enclosing directory still relocates such a file or conflicts on it. + Those deletions materialize under `git reset --hard `, a + DELETION-AWARE sync of the tip tree (`rsync --delete`; a plain + recursive copy leaves base-only files in place), or piping the two-dot + diff itself into `git apply` — which is why non-empty never justifies + re-applying. They do NOT materialize under + `git format-patch --stdout $(git merge-base ).. + | git am` onto the base, which replays the branch's own commits and + leaves base-only files alone (that pipeline is the runnable form: + bare `format-patch` writes `*.patch` into cwd and bare `git am` then + waits on stdin). Match the read to the action: a real merge preview is + `git merge-tree --write-tree `. Read its EXIT STATUS + first — 0 clean, 1 conflicted, anything else an error whose output is + unspecified (it refuses unrelated histories outright). On 0 and 1 its + first stdout line is the OID of the merged tree, written either way, + with conflicted-file info following on 1. Diff that OID against the + base to read the merge's net change. `git diff + $(git merge-base ) ` is the branch's + CONTRIBUTION, + not the merge result — where a merge-base exists it is the same + computation as the three-dot form rejected above, but only the + longhand degrades: on unrelated histories `git merge-base` prints + nothing, the substitution empties, and the command silently becomes a + working-tree diff, which `...` never does. What deleting + the branch would lose is its unlanded work, and the two-dot ADDITION + side does not measure it: once the base has moved on, that side also + carries the branch's older copy of base-side edits, which deleting the + branch does not lose — inconclusive for the same tip-to-tip reason as + the deletion side. Use two COMPLEMENTARY reads instead, never as + equivalents: `git log ..` enumerates the branch's unique + COMMITS, and `git diff ...` shows its net CONTENT since + the merge-base. They diverge — a commit plus its revert leaves the log + non-empty and the three-dot diff empty. Mind the dots on the log: the + two-dot `..` (or `^ `) is the one you + want; `git log ...` is the SYMMETRIC difference and + lists base-side commits too, recreating the very over-report this + paragraph exists to stop (the materialization set, the merge-tree + preview, the addition-side over-report and the merge-base longhand + were verified against + fixtures 2026-08-28; the squash and empty-two-dot claims above and the + incident shape stay `unprobed` — contributor incident; see + Provenance). - **A torn-down worktree can make git act on the ENCLOSING repo instead - of failing** (`unprobed` — contributor incident as shape; see - Provenance). The usual teardown fails LOUDLY: a removed linked - worktree is a dead cwd, sibling paths stop resolving, and commands - there die with "not a git repository" (`git worktree prune` itself - only drops admin entries whose directory is already missing). The + of failing** (prune, repair, and the `--show-toplevel` rebind case + verified against fixtures 2026-08-28; the incident shape stays + `unprobed` — contributor incident; see Provenance). Teardown normally + fails LOUDLY, in one of two ways: delete the worktree directory while + it is still your cwd and git dies with "Unable to read current working + directory" before it looks for a repository at all; delete only the + `.git` pointer somewhere OUTSIDE the main checkout and the walk-up + finds nothing, so it dies with "not a git repository". `git worktree + prune` does not + create the silent case below, but it does CLOSE the exit from it: it + drops the admin entry of any UNLOCKED worktree whose `.git` pointer + file is missing — its directory still fully present or not ("gitdir + file points to non-existent location"); `git worktree lock` is what + holds an entry through a prune. A bare `git worktree repair` run from + the main checkout rewrites the missing pointer from that admin entry, + so the exit works only until prune removes the entry; the + `repair ` form also restores it but exits 1 with an `error:` + line, so its status reads as a failure it is not. The silent case is narrower and worse: the worktree's `.git` pointer file is gone while its directory path still resolves INSIDE the main checkout's tree — git resolves its repository by walking up from cwd, @@ -308,9 +355,12 @@ When rigor conflicts with finishing sooner, rigor wins. notice. So the trigger is positional, not observational: from any long-lived session working in a linked worktree that cleanup could have touched, before the first commit, push, or PR after a merge or - cleanup event, re-verify identity. `git rev-parse --show-toplevel` is - the decisive check — a rebound checkout can be sitting on the very - branch name you expect, so `--abbrev-ref HEAD` alone can false-pass. + cleanup event, re-verify identity. Decide it on `git rev-parse + --show-toplevel` COMPARED against the worktree path you expect: it + does not error in this failure, it succeeds and prints the enclosing + checkout, so reading it without comparing proves nothing. And a + rebound checkout can be sitting on the very branch name you expect, + so `--abbrev-ref HEAD` alone can false-pass. ❌ a create-PR command issued from a session's own already-torn-down worktree directory would have acted on the main checkout — wrong tree, wrong branch — under this session's name; the staged-diff read @@ -1424,11 +1474,60 @@ shipped after the branch's base. The incident's original reading — "a late merge would have silently regressed them" — was REFUTED on 2026-08-28 pre-merge review by execution (a three-way merge preserves base-side work the branch never touched; the two-dot deletion side -materializes only under reset/re-apply/overwrite), which is why the -amendment now reads the two-dot as a re-apply hazard and routes merge -and delete decisions to the merge-base delta and the addition side -respectively. Both bullets ship `unprobed` per the covenant; their -probes join the standing #115 queue. +materializes when the base checkout is overwritten by the tip or the +two-dot diff is itself applied, not under `format-patch` + `am`), which +is why the amendment now reads the two-dot as a re-apply hazard, routes +the merge preview to `git merge-tree --write-tree`, and reads delete +risk off `git log ..` plus `git diff ...` — +treating the two-dot ADDITION side as inconclusive for the same +tip-to-tip reason as its deletion side (result 11). + +Both bullets' GIT MECHANICS were probed on 2026-08-28 against throwaway +fixtures (git 2.50.1), after a two-family prose review of the same text +returned findings but ran nothing. Results, each corrected above: +(1) `git worktree prune -v` removed the admin entry of an UNLOCKED +worktree whose directory was fully populated and whose `.git` pointer +file alone was deleted ("gitdir file points to non-existent location"); +a locked worktree in the same state survived. The old text said prune +only drops entries whose directory is already missing. (2) `git diff +$(git merge-base A B) B` was byte-identical (`cmp`) to `git diff A...B`; +on unrelated histories `git merge-base` exited 1 printing nothing; and on +a branch that renamed an enclosing directory the merge conflicted and +relocated a base-only file, which that diff did not show and +`git merge-tree --write-tree` did (merged-tree OID on the first stdout +line, then the conflicted path and a CONFLICT message, exit 1; a clean +pair printed the OID alone and exited 0). (3) `format-patch +$(git merge-base A B)..B` + `am` +onto the base left the base-only file in place. (4) A force-push updated +the ref and left the pushing tree untouched, so it is not a materializing +action. (5) `git rev-parse --show-toplevel` exited 0 printing the +enclosing checkout in the rebind case. (6) With the pointer file deleted +and the admin entry present, a bare `git worktree repair` from the main +checkout restored the pointer and exited 0, and `repair ` restored +it too but exited 1 with an `error:` line; after `prune` removed the +entry, both failed and the pointer stayed gone. (7) Of the overwrite +actions, `git reset --hard ` and `rsync -a --delete` of the tip +tree each removed the base-only file, and piping `git diff +` into `git apply` did too, but a plain `cp -R` of the tip tree +left it in place — so only a deletion-aware overwrite materializes. +(8) Deleting a linked worktree directory while it was the cwd produced +"fatal: Unable to read current working directory", not "not a git +repository". (9) `format-patch --stdout ... | git am` is the runnable +form; bare `format-patch` wrote `0001-*.patch` into cwd and bare +`git am` read stdin. (10) `merge-tree --write-tree` on unrelated +histories exited 128 ("refusing to merge unrelated histories"), outside +its documented 0/1. Probed 2026-08-29: (11) with the base moved on (a +base-side edit the branch predates), the two-dot ADDITION side carried +the branch's older copy of that file alongside its genuine new work, +while `git log ..` contained only the branch's unique +commit and `git diff ...` only its net new content — so +the addition side over-reports what deleting the branch loses, by the +same mechanism that makes the deletion side inconclusive. The two reads +are not equivalent: a commit plus its revert left the log listing two +commits and the three-dot diff empty. (12) `git log ...` +(three dots) listed the BASE-side commit as well. +The incident SHAPE of both bullets remains contributor-reported +and ships `unprobed`; those probes stay on the standing #115 queue. Stable behavioral rules; the environment-specific facts to re-verify now travel with the rules that cite them — the external-systems set in