From 3e00a01bfb2cc8167a427e37f48169caa0cc0d71 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:49:20 +0100 Subject: [PATCH] docs: file itd-148 worktree-first discipline and plan it to spc-42 Files the worktree-first intent: the primary checkout becomes a read-only surface, every change starts in its own worktree, merged worktrees are removed on proof, and abandoned ones get a dossier sweep the human rules on. Decomposed per itd-84 (SPLIT confirmed: merged-cleanup consumed from itd-118, the coordination mechanism left to itd-33), revised under two adversarial reviews, planned at a live interview, and graded into the decomposition-calibration note. Assisted-by: Claude:claude-fable-5 --- ...n-its-own-worktree-the-primary-checkout.md | 202 ++++++++++++++++++ .../2026-08-15-decomposition-calibration.md | 32 +++ ...n-its-own-worktree-the-primary-checkout.md | 103 +++++++++ 3 files changed, 337 insertions(+) create mode 100644 .abcd/development/intents/planned/itd-148-every-change-starts-in-its-own-worktree-the-primary-checkout.md create mode 100644 .abcd/development/specs/open/spc-42-every-change-starts-in-its-own-worktree-the-primary-checkout.md diff --git a/.abcd/development/intents/planned/itd-148-every-change-starts-in-its-own-worktree-the-primary-checkout.md b/.abcd/development/intents/planned/itd-148-every-change-starts-in-its-own-worktree-the-primary-checkout.md new file mode 100644 index 00000000..a44a238c --- /dev/null +++ b/.abcd/development/intents/planned/itd-148-every-change-starts-in-its-own-worktree-the-primary-checkout.md @@ -0,0 +1,202 @@ +--- +id: itd-148 +slug: every-change-starts-in-its-own-worktree-the-primary-checkout +spec_id: spc-42 +kind: standalone +suggested_kind: null +reclassification_history: [] +builds_on: [itd-118, itd-33] +severity: major +impact: additive +--- + +# Every change starts in its own worktree: the primary checkout is a read-only surface, abcd blocks mutations there, and stale worktrees are swept with a dossier the human can decide on + +## Press Release + +> **abcd turns the primary checkout into a read-only surface: every change — a one-line docs fix included, peers present or not — begins in its own worktree, and abcd enforces the boundary mechanically.** The worktree verb family (CLI and plugin surface) creates and enters a per-change worktree, records the entry where every peer session sees it on its next prompt, and seeds the worktree with the protections the primary checkout has — the private name-guard layer included, pre-populated to a floor of four categories (home/user paths, machine hostnames, personal email, real surname), each value held on the machine and never committed. A host-hook backed by the binary refuses file writes and git mutations attempted in the primary checkout, so a session whose working directory silently reverts to the shared tree is stopped at its first recognisable write instead of contaminating a tree another session is about to move. A worktree whose change has provably merged is removed at session start with no ceremony; a worktree that went quiet without merging is never silently deleted — abcd names the candidates and the sweep hands the human a dossier per worktree (whether it holds uncommitted work, what the branch was about, the records it references, its last activity) plus a recommendation, so the decision is informed and one keystroke rather than archaeology. +> +> "I had two sessions going and asked one for a two-line docs fix, and it just did it — in its own worktree, visible to the other session, PR up, and days later the worktree was simply gone because the merge was provable," said Kira, an open-source maintainer running concurrent agents. "The worktree left over from an abandoned experiment didn't rot either: at my next session start they showed me it was clean, what it had been about, and a suggestion to remove. I pressed yes. I never had to reconstruct what a stale branch was." + +## Why This Matters + +The checkout-is-the-unit-of-isolation convention has been carried by vigilance, +and the ledger records vigilance failing safe rather than the setup holding: + +- iss-213: several agents in ONE worktree produced a false-green preflight + spanning two branch switches, and a `git rebase` silently rebased main. +- iss-2608230847432285 (major): per-agent worktrees did not isolate sessions + whose shell cwd silently reverted to the shared checkout. Two sessions wrote + into the primary tree believing they were in their worktrees, and only the + diff-you-did-not-make convention prevented a bad commit. That record asks in + terms for "whatever makes a session's tree unambiguous rather than + remembered". The mutation block in the primary checkout is that durable + form, aimed at the exact failure direction observed. +- The lint gates read the whole working tree, so any foreign work-in-progress + in a shared checkout fails `make preflight` in both directions; a per-change + worktree makes a gate's verdict describe the change it gates. +- itd-107 leaves open (its orchestration caveats) whether independent peer + sessions are kept apart by policy or by per-session worktrees; this intent + supplies that answer. + +Making the primary checkout read-only also gives it a positive role: it is the +always-current surface of the repository's state — status renders, rules +inspection, browsing — that is never mid-anything. + +Per-change worktree cost is negligible in this ecosystem: worktrees share the +object store, and Go's build cache is user-global. + +## What's In Scope + +- **The worktree verb family**, wired to CLI and plugin surface: create/enter + a per-change worktree; a status render answering which worktrees exist, who + is in them, and what each is about; the sweep. +- **The mutation block**: a host-hook (thin caller; the check lives in the + binary) that refuses file writes and git mutations in the primary checkout + of an abcd-managed repo, naming the worktree route instead. The check is + git-aware (resolved via the tree's git common directory, never a path + prefix, since worktrees physically live inside the primary checkout) and + honest about its rung: hook-level interception of the host's file-edit and + shell tools is a mitigation that catches recognisable writes, not a + filesystem guarantee. A git-level pre-commit backstop in the primary + checkout is the second layer, refusing commits there for humans and + non-hooked tools, subject to the same allow-set. +- **The read-only surface's allow-set, stated explicitly**: writes to the + local tier (`.abcd/.work.local/`) and other gitignored or untracked paths + (build output included), fetch and fast-forward of the default branch (what + keeps the surface current), and `git worktree` administration are permitted + in the primary checkout; tracked-file writes and history-moving operations + are what the block refuses. The ledger write of `abcd capture` joins the + allow-set as a narrow carve-out (a new timestamp-named file under the issue + ledger only, so concurrent captures cannot collide), paired with an + adoption path: a capture written in the primary checkout is uncommitted by + construction, and the sweep or the next change worktree adopts orphan + captures into a change so they reach the default branch rather than sitting + untracked. +- **Peer visibility as a mechanical duty** (the convention layer): worktree + entry and exit and record-id mints are recorded such that every peer + session in the repository sees them at its next hook fire (the + record-then-inject shape the rules loader already uses; delivery is + at-next-prompt, not instantaneous). The coordination mechanism — typed + claims, take or yield or escalate — remains itd-33's; this intent makes + today's AGENTS.md announce convention mechanical rather than remembered, + without adding the delivery channel itd-33 deliberately cut. +- **Sweep, merged half**: a worktree whose change has provably merged is + removed at session start or explicit sweep — never as a side effect of + other verbs, which keeps bare invocations zero-write — consuming itd-118's + tidy mechanics. Because this repo allows squash and rebase merges, which + rewrite the branch's shas out of existence, provably-merged means the + remote PR's recorded merge state where a PR exists, with patch-equivalence + as the local fallback; plain sha reachability is only the fast path, and a + worktree whose merge cannot be proven is demoted to the dossier path, never + guessed at. Operation is remote-optional (ruled at the planning interview): + without a forge, patch-equivalence alone decides, at the price of more + dossier demotions. +- **Sweep, abandoned half**: an unmerged worktree inactive past a threshold + is surfaced at session start and never auto-deleted. The sweep composes a + dossier per candidate — leading with whether the tree is dirty, then branch + subjects, diff summary, referenced records (iss-N or itd-N), last activity + — plus a recommendation whose vocabulary includes capture-or-commit-first + for dirty trees; removal is per-item human-confirmed, and `git worktree + remove` refusing a dirty tree without force is kept as the backstop. +- **Worktree protection seeding closes iss-370**: the guard layer is seeded + on detection, not only on creation — a hook fire that finds itself in a + worktree of an abcd-managed repo whose local tier lacks the name-guard + layer seeds the pointer to the primary checkout's store, so worktrees the + host created without abcd get the protection too. abcd-managed repos + pre-populate the private banlist to the four-category floor above, values + gathered by one-time setup prompt or from the user-level home, never + derived silently and never committed. +- **AGENTS.md Concurrent sessions rewrite** while this intent rewrites that + ground anyway: the scan-before-mutating rule restated by blast radius, with + the four git operations and shared build artefacts as examples rather than + the set (iss-2608230957104179). +- **Push-time gates lint the tree that ships**: the same machinery gives the + pre-push gate a clean worktree of HEAD to lint, so a working-tree and index + divergence cannot pass locally and fail CI (iss-2608210738378295). +- **Scaffolded to all abcd-managed repos** via the prepare and ahoy path. + +## What's Out of Scope + +- Post-merge residue mechanics (remote PR branch, local branch, tracking + ref): itd-118 owns them; this intent consumes them for the merged half of + the sweep. +- The coordination layer itself (claims, yield, escalation): that is itd-33, + whose revisit triggers have fired and which owes a SOTA sweep first + (iss-2608230943533581). This intent adds no delivery channel and no + agent-to-agent negotiation. +- Session-presence leases for two sessions in ONE checkout + (iss-2608220750029993): narrowed but not closed by this intent; it stays + open. +- The timestamp id mint for the sequential families: adr-45 ruling 3 already + adopts timestamp ids and schedules the itd and spc migration after the + captures family runs one release cycle (iss-2608210737260468). This intent + does not do that work and does not accelerate the recorded schedule (ruled + at the planning interview): the mint-visibility duty above is the standing + bridge until the migration arrives. The live collision evidence is + iss-2608221126066632 (its sibling iss-2608220150157512 was closed wontfix + in favour of the migration). + +## Acceptance Criteria + +> _Seeded by the drafting session and revised under two adversarial reviews; +> unconfirmed proposals until the planning interview walks them._ + +- **Given** a session in the primary checkout of an abcd-managed repo, + **when** it attempts a tracked-file write or a history-moving git operation + through a hooked surface, **then** the block refuses with a message naming + the worktree route, and read-only operations and allow-set writes are + untouched. +- **Given** abcd creates and enters a worktree, or a hook fire detects a + host-created worktree without the guard layer, **when** the session works + there, **then** the private name-guard layer is active in that worktree, + pre-populated to at least the four floor categories with values that appear + in no committed file, **and** the entry is recorded such that every live + peer session sees it at its next hook fire. +- **Given** a worktree whose change's merge is provable (remote PR merge + state, or patch-equivalence locally; squash and rebase merges therefore + covered), **when** a session starts or the sweep runs, **then** the + worktree and its tidy-work (per itd-118) are removed without prompting and + the removal is reported; a worktree whose merge cannot be proven is + surfaced on the dossier path instead, never removed on a guess. +- **Given** an unmerged worktree with no open PR and no activity for 14 days + (the default; repo-configurable), **when** a session starts, **then** it is named as a sweep candidate; **when** the + human runs the sweep, **then** each candidate presents its dossier (dirty + state first) and recommendation, and nothing is removed without per-item + confirmation. +- **Given** a record-id mint from any worktree while a peer session is live, + **when** the mint runs, **then** the family and checkout are recorded for + peer visibility at next hook fire: the mechanical form of today's + convention, standing until the timestamp migration retires the collision + class. +- **Given** a repo where no worktree exists and no peer runs, **when** a + read-only verb runs in the primary checkout, **then** nothing about its + behaviour or cost has changed, and no bare invocation acquires a write as + a side effect of the sweep. + +## Open Questions + +_None open. The drafting session's questions (capture carve-out, git-level +backstop, abandoned threshold, severity, adr-45 sequencing, remote-optional +merged proof) were each resolved with the maintainer at the 2026-08-26 +planning interview; the resolutions are folded into scope above._ + +## Audit Notes + +_Empty. Populated by intent-auditor when intent moves to shipped/._ + +## References + +- Resolved by shipping: iss-2608230847432285, iss-213, iss-370, + iss-2608230957104179, iss-2608210738378295. +- Adjacent intents: itd-118 (merged work leaves no residue: consumed), + itd-33 (coordination mechanism: this intent carries only the + record-then-inject visibility duty), itd-115 (merge without churn), + itd-107 (dispatches subagents into per-worktree isolation and asks the + independent-peers question this intent answers). +- Sequencing: adr-45 ruling 3 and iss-2608210737260468 (timestamp mint + schedule), with the open collision recurrence iss-2608221126066632 and the + wontfix sibling iss-2608220150157512. +- Host prior art: the Claude Code harness's own worktree isolation + (per-session worktrees under `.claude/worktrees/`, base-ref policy). This + intent is the host-agnostic, abcd-owned form of that behaviour. diff --git a/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md b/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md index 6a4022cd..3b74dd0d 100644 --- a/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md +++ b/.abcd/development/research/notes/2026-08-15-decomposition-calibration.md @@ -831,3 +831,35 @@ Per hand-run, append: errata: it proves a cited id resolves and proves nothing about whether the claim beside it is true, and a claim marked `verified` in leg 1 of that run was false. + +### 2026-08-26 — the worktree-first proposal (itd-148) + +- **Proposal:** "automate the worktree step": primary checkout read-only, + every change starts in a worktree, hard-block enforcement, sweep of stale + worktrees, cross-worktree agent communication, scaffolded to all + abcd-managed repos. Live interview session; two peer sessions active. +- **Initial routing:** seven parts — worktree-first discipline (capability | + new intent); mutation block (mechanism | same intent); all-repos scaffolding + (plumbing | the spec); merged-worktree removal (**duplicates itd-118** | + routed there, consumed via `builds_on`); abandoned-worktree dossier sweep + (capability | new intent, `builds_on: itd-118`); cross-worktree + communication (**overlaps itd-33** | flagged for the human — fold, layer, or + advance itd-33); timestamp mint (prerequisite | already + iss-2608210737260468, linked as blocker, not filed). Bundle candidates + surfaced from the ledger: iss-370, iss-2608230847432285 + iss-213, + iss-2608230957104179, iss-2608210738378295. +- **Confirmed routing:** adopted unchanged, with the itd-33 overlap resolved + as **layered** (announce-duty AC in the new intent; mechanism stays + itd-33's) and all four bundle candidates confirmed into scope. One addition + from the human at confirmation: the iss-370 seeding gains a four-category + pre-population floor for the private banlist (home/user paths, machine + hostnames, personal email, real surname — categories committed, values + never). +- **Verdict:** SPLIT, proposed and confirmed — the initial verdict survived + confirmation; filed as itd-148. +- **Notes:** no over-flags; no reversal candidates. The duplicate detection + (itd-118) came from a ledger grep before the table was built — the scan + step earns its place. The communication part shows the taxonomy gap for + "requirement whose mechanism lives in another draft": neither `refines` + nor `duplicates` fits; recorded here as *layered* pending the enum + question. diff --git a/.abcd/development/specs/open/spc-42-every-change-starts-in-its-own-worktree-the-primary-checkout.md b/.abcd/development/specs/open/spc-42-every-change-starts-in-its-own-worktree-the-primary-checkout.md new file mode 100644 index 00000000..5de6a267 --- /dev/null +++ b/.abcd/development/specs/open/spc-42-every-change-starts-in-its-own-worktree-the-primary-checkout.md @@ -0,0 +1,103 @@ +--- +id: spc-42 +slug: every-change-starts-in-its-own-worktree-the-primary-checkout +intent: itd-148 +--- +# every-change-starts-in-its-own-worktree-the-primary-checkout + +## Summary + +Delivers itd-148: the primary checkout of an abcd-managed repo becomes a +read-only surface, every change starts in a per-change worktree, and the +worktree population is swept — merged trees removed on proof, abandoned trees +surfaced with a dossier the human rules on. All behaviour lives in the +transport-agnostic core; the CLI and the plugin markdown surface are thin +front doors, and the enforcement hooks are thin callers into the binary. + +## Scope + +1. **Core worktree package** (`internal/core`): tree classification (primary + vs worktree, resolved via the git common directory, never a path prefix), + worktree create/enter metadata, sweep classification, dossier composition, + and the visibility ledger. +2. **Verbs**: `abcd worktree` (status render: which worktrees exist, who is + in them, what each is about), `abcd worktree new `, `abcd worktree + sweep`, and `abcd worktree check` (the hook entry point; exit code carries + the block verdict). Bare `abcd worktree` performs zero writes. +3. **Hooks**: a PreToolUse-shaped hook routing the host's file-edit and shell + tools through `worktree check`; a SessionStart contribution that surfaces + sweep candidates and performs proven-merge removals; a git pre-commit + backstop installed in the primary checkout via the ahoy defaults. +4. **Scaffolding**: the prepare/ahoy path installs the hooks and the + pre-populated private banlist floor into abcd-managed repos. +5. **Record changes**: the AGENTS.md Concurrent sessions rewrite (blast-radius + rule), and resolution of the bundled issues in the shipping changes + (iss-370, iss-213, iss-2608230847432285, iss-2608230957104179, + iss-2608210738378295). + +## Approach, by acceptance criterion + +**AC 1 — the mutation block with allow-set.** `worktree check` receives the +tool input (cwd, file path or command) and answers allow/refuse. Refusal only +when: the tree resolves to the primary checkout of an abcd-managed repo AND +the write targets a tracked file or the command is history-moving. The +allow-set is explicit: gitignored/untracked paths, `.abcd/.work.local/`, +fetch/fast-forward of the default branch, `git worktree` administration, and +the capture carve-out (a new timestamp-named file under +`.abcd/work/issues/open/` only). Shell interception is command parsing at the +guard-registry rung, stated as mitigation, not filesystem guarantee; the +pre-commit backstop (same check, commit-time) catches what parsing misses. +Refusal messages name the worktree route. Tests: table-driven check tests per +allow/refuse case; hook wiring exercised via the smoke harness. + +**AC 2 — guard seeding and peer visibility on entry.** Seeding runs on +detection, not only creation: any hook fire that finds itself in a worktree +of an abcd-managed repo whose local tier lacks the name-guard layer seeds the +pointer to the primary checkout's store. The floor's four categories +(home/user paths, machine hostnames, personal email, real surname) are +populated by one-time setup prompt or from the user-level home; values are +machine-local, never derived silently, never committed. Entry/exit events +append to a visibility ledger in the primary checkout's local tier; +peer-session hook fires inject unseen entries (the record-then-inject shape +the rules loader already uses). Delivery is at-next-prompt by design. + +**AC 3 — proven-merge removal.** Merge proof, in order: fast path, tip +reachable from the default branch; forge path, the remote PR's recorded merge +state where a PR exists; local fallback, patch-equivalence (`git cherry` +/ patch-id) of the branch's commits against the default branch. Squash and +rebase merges are covered by the forge and patch paths — sha reachability +alone cannot see them. Unprovable means dossier, never removal. Removal runs +at SessionStart or explicit sweep only, never as a side effect of other +verbs; residue tidy-work delegates to itd-118's mechanics. + +**AC 4 — the abandoned dossier.** Candidate: unmerged, no open PR, no +activity for 14 days (repo-configurable). The dossier leads with dirty state, +then branch subjects, diffstat, referenced record ids, last activity, and a +recommendation whose vocabulary includes capture-or-commit-first for dirty +trees. Removal is per-item human-confirmed; `git worktree remove` without +force is the dirty-tree backstop. Orphan-capture adoption rides the sweep: +uncommitted ledger files in the primary checkout are offered into a change. + +**AC 5 — mint visibility.** Sequential-family mints append a +family-and-checkout event to the same visibility ledger, injected to peers at +next hook fire — the mechanical form of the AGENTS.md convention, standing +until the adr-45 timestamp migration (schedule unchanged by this spec). + +**AC 6 — zero cost when idle.** Bare verbs stay zero-write; sweep writes are +confined to SessionStart and explicit sweep. A repo with no worktrees and no +peers takes one tree-classification stat call in the hook path and nothing +else. Read-only verbs are untouched by construction (the block sits on write +paths only), verified by tests asserting no writes from bare invocations. + +## Out of scope + +Coordination claims (itd-33), post-merge remote/branch residue mechanics +(itd-118, consumed), presence leases (iss-2608220750029993), the timestamp +migration itself (adr-45 schedule holds). + +## Delivery + +Staged PRs, each preflight-clean with tests watched fail first: (1) core +classification + `worktree check` + hooks; (2) seeding + visibility ledger; +(3) sweep (merged half, then dossier); (4) scaffolding + AGENTS.md rewrite + +bundled-issue resolutions with `Resolves:` trailers in their fixing changes.