feat(dev): lab — per-instance worktrees + unified identity #139

Merged
dominik.polakovics merged 1 commit from afk/134 into main 2026-06-08 10:35:17 +02:00

What

Slice 1/3 of #133. Make every manual lab instance run in its own git worktree (today only AFK runs are isolated), and unify session identity on <project>~<label> with slots removed.

  • Identity — drop slots (allocateSlot/takenSlots/Slot). Session name is <project>~<label>; parseSessionName splits on the first ~. Kind (parseAFKLabel), branch (afk/<N> | lab/<label>), worktree dir (<project>-<N> | <project>-<label>), and the rendered identity (AFK #N, or <label> · 15:30 / 15:30) all derive from the label. AFK Start is re-expressed through the shared worktreePath/instanceBranch derivation but behaves identically.
  • Start — synchronous, fail-loud worktree creation off freshly-fetched origin/<default> with rollback (mirrors AFK's teardownClaim) and a git-op timeout. No fallback base: a repo with no usable origin (or a failing fetch) fails Start with the git cause and leaves nothing behind.
  • Stop (manual) — one guarded teardown (decideTeardown): a dirty worktree keeps worktree + branch; a clean one removes the worktree and deletes the branch only if merged into origin/<default>. AFK Stop is unchanged this slice — reaper unification is the next slice.
  • Docsdocs/adr/0017-lab-per-instance-worktrees.md (full design); a focused superseding note on ADR-0007; CONTEXT.md Instance entry; default.nix version bump.

Acceptance

  • Two concurrent instances of one project run in separate worktrees, never sharing a tree/index/branch.
  • Identity is <project>~<label>; allocateSlot/takenSlots/Slot gone; parseSessionName splits on the first ~.
  • Manual rows render label · 15:30 (time-only when unlabelled); AFK rows still render AFK #N.
  • Clean manual Stop removes the worktree, keeps the branch only if unmerged; dirty keeps both.
  • A repo with no usable origin shows a clear Start error and leaves nothing behind.
  • ADR added; ADR-0007/0013 still accurate; CONTEXT.md "Instance" updated.
  • go test ./... passes (also under -race); gofmt/go vet clean; fw dry-build green.

Closes #134

## What Slice 1/3 of #133. Make every **manual** lab instance run in its own git worktree (today only AFK runs are isolated), and unify session identity on `<project>~<label>` with slots removed. - **Identity** — drop slots (`allocateSlot`/`takenSlots`/`Slot`). Session name is `<project>~<label>`; `parseSessionName` splits on the first `~`. Kind (`parseAFKLabel`), branch (`afk/<N>` | `lab/<label>`), worktree dir (`<project>-<N>` | `<project>-<label>`), and the rendered identity (`AFK #N`, or `<label> · 15:30` / `15:30`) all derive from the label. AFK Start is re-expressed through the shared `worktreePath`/`instanceBranch` derivation but behaves identically. - **Start** — synchronous, fail-loud worktree creation off freshly-fetched `origin/<default>` with rollback (mirrors AFK's `teardownClaim`) and a git-op timeout. No fallback base: a repo with no usable origin (or a failing fetch) fails Start with the git cause and leaves nothing behind. - **Stop (manual)** — one guarded teardown (`decideTeardown`): a dirty worktree keeps worktree + branch; a clean one removes the worktree and deletes the branch only if merged into `origin/<default>`. **AFK Stop is unchanged this slice** — reaper unification is the next slice. - **Docs** — `docs/adr/0017-lab-per-instance-worktrees.md` (full design); a focused superseding note on ADR-0007; `CONTEXT.md` **Instance** entry; `default.nix` version bump. ## Acceptance - [x] Two concurrent instances of one project run in separate worktrees, never sharing a tree/index/branch. - [x] Identity is `<project>~<label>`; `allocateSlot`/`takenSlots`/`Slot` gone; `parseSessionName` splits on the first `~`. - [x] Manual rows render `label · 15:30` (time-only when unlabelled); AFK rows still render `AFK #N`. - [x] Clean manual Stop removes the worktree, keeps the branch only if unmerged; dirty keeps both. - [x] A repo with no usable origin shows a clear Start error and leaves nothing behind. - [x] ADR added; ADR-0007/0013 still accurate; CONTEXT.md "Instance" updated. - [x] `go test ./...` passes (also under `-race`); `gofmt`/`go vet` clean; fw dry-build green. Closes #134
Every manual lab instance used to spawn in the project's main checkout, so
two concurrent instances stomped each other's working tree, index, and
branch; only AFK runs were isolated. Make every instance — manual and AFK —
run in its own git worktree forked from freshly-fetched origin/<default>,
and unify session identity on <project>~<label> (slots removed).

- Identity: drop slots (allocateSlot/takenSlots/Slot); session name is
  <project>~<label>; parseSessionName splits on the first ~. Branch, worktree
  dir, and the rendered "<label> · 15:30" / "AFK #N" identity all derive from
  the label.
- Start: synchronous, fail-loud worktree creation off origin/<default> with
  rollback and a git-op timeout; no fallback base — a repo with no usable
  origin fails Start cleanly, leaving nothing behind.
- Stop (manual): one guarded teardown — dirty keeps worktree+branch; clean
  removes the worktree and deletes the branch only if merged into
  origin/<default>. AFK Stop is unchanged this slice (reaper unification is
  the next slice).
- Docs: ADR-0017 (full design) + a superseding note on ADR-0007; CONTEXT.md
  Instance entry; default.nix version bump.

go test ./... passes (also under -race). Slice 1/3 of #133.
Author
Owner

This was generated by AI while landing a PR.

Validation: PASS — landing now.

  • Verification signal: the repo's pre-commit dry-build gates eval, but lab Go is not eval-gated, so I ran the project's own checks on afk/134: gofmt -l clean, go vet clean, go build clean, go test -race ./... green (39.8s).
  • Conventions: Conventional-Commits title ✓; no secrets.yaml / stateVersion changes; vendorHash = null (stdlib-only, no hash risk); Closes #134 present.
  • Diff review: the slot → <project>~<label> identity unification is internally consistent; the new git.go seam (WorktreeDirty, BranchMerged with correct merge-base --is-ancestor exit-code handling, 60s timeout on the one network op) is sound.

Merging via merge-commit. #140 (stacked on afk/134) will be retargeted to main next.

> *This was generated by AI while landing a PR.* **Validation: PASS** — landing now. - **Verification signal:** the repo's pre-commit dry-build gates *eval*, but lab Go is not eval-gated, so I ran the project's own checks on `afk/134`: `gofmt -l` clean, `go vet` clean, `go build` clean, `go test -race ./...` green (39.8s). - **Conventions:** Conventional-Commits title ✓; no `secrets.yaml` / `stateVersion` changes; `vendorHash = null` (stdlib-only, no hash risk); `Closes #134` present. - **Diff review:** the slot → `<project>~<label>` identity unification is internally consistent; the new `git.go` seam (`WorktreeDirty`, `BranchMerged` with correct `merge-base --is-ancestor` exit-code handling, 60s timeout on the one network op) is sound. Merging via merge-commit. `#140` (stacked on `afk/134`) will be retargeted to `main` next.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/nixos!139
No description provided.