Skip to content

Backend architecture: providers, presenters, and the herdr backend#79

Open
ewilazarus wants to merge 4 commits into
mainfrom
next
Open

Backend architecture: providers, presenters, and the herdr backend#79
ewilazarus wants to merge 4 commits into
mainfrom
next

Conversation

@ewilazarus

Copy link
Copy Markdown
Owner

Summary

Refactors git-workspace so worktree lifecycle, project setup, and presentation are independent concerns, then builds the first real backend on top of the seam:

  • WorktreeProvider owns git worktree creation/discovery/removal (NativeGitProvider, HerdrWorktreeProvider)
  • WorkspacePreparer owns setup (assets, env, fingerprints, hooks) — backend-agnostic by construction
  • WorkspacePresenter owns how a worktree is surfaced (HerdrPresenter)
  • WorkspaceService orchestrates the lifecycle state machine over provider/preparer/presenter/state store

New features

  • git workspace prepare [path] [--force] — prepares any worktree of the repository, wherever and however it was created (workspace discovered via the worktree's git metadata). The primitive external hosts and CI invoke; reset is now a deprecated alias for prepare --force.
  • Per-worktree lifecycle state under .workspace/.state/ (schema v1, atomic writes): failed setups are remembered and retried by the next up/prepare instead of silently ignored; doctor reports failures with the retry command and flags stale state.
  • Per-worktree operation locks: concurrent mutations fail fast; lock contention exits 75 (EX_TEMPFAIL) so callers can distinguish busy from broken.
  • Herdr backend (contract captured against a live herdr 0.7.4 server): inside a verified herdr session, plain up creates the worktree through herdr, prepares it, and opens/focuses it as a herdr workspace; down closes the workspace, rm removes through herdr. Selection via --backend/--provider/--presenter, the [workspace] manifest table, or auto-detection (--backend native opts out).
  • Thin herdr plugin (integrations/herdr/): runs git workspace prepare on herdr's worktree.created/worktree.opened events plus prepare/reprepare actions — no config parsing, no state, everything delegates to the CLI.

Breaking changes

  • prune --apply now runs on_detach/on_teardown hooks per worktree (skip-and-report on failure, exit 1)
  • The CLI exits non-zero on errors (previously printed the error but exited 0)
  • Failed preparation on up preserves the worktree, records the failure, and prints a retry command

Testing

  • 909 tests (unit + integration), ruff and ty clean
  • Reusable provider/presenter contract tests (tests/contracts/) that future implementations must pass
  • Herdr is exercised through a fake executable backed by real git — CI never needs herdr installed
  • Verified live against a real herdr session: up --backend herdr, flagless auto-detected rm, and plugin manifest linking (zero warnings)

Rollout note

After merging and releasing, upgrade the installed binary (currently 0.7.0 lacks prepare) before enabling the plugin with herdr plugin link <repo>/integrations/herdr.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EVBc3r3WrJeXzaogVD6GwX

ewilazarus and others added 4 commits July 18, 2026 18:38
Separate worktree lifecycle, preparation, and presentation into
independently testable components behind a backend seam:

- WorktreeProvider protocol + NativeGitProvider owning the full worktree
  creation chain; reusable provider contract tests for future providers
- WorkspacePreparer extracted from operations.py (assets, env,
  fingerprints, hooks); backend-agnostic by construction
- WorkspacePresenter protocol + capabilities; no implementations yet,
  seam covered by fakes
- WorkspaceService orchestrating the lifecycle state machine over
  provider/preparer/presenter/state store
- Injectable CommandRunner; git.py migrated (hooks keep shell=True
  intentionally); tests inject fakes instead of patching subprocess
- Minimal per-worktree state (schema v1) under .workspace/.state/ with
  atomic writes; failed setup is now remembered and retried
- flock-based per-worktree operation locks, fail-fast
- New 'git workspace prepare [path]' incl. worktrees created by other
  tools anywhere on disk (.workspace discovered as sibling of the git
  common dir)
- doctor: flag failed preparations and stale state files

BREAKING: prune --apply now runs detach/teardown hooks (continues past
failures, exits 1 with summary); CLI exits non-zero on errors; reset is
deprecated in favor of 'prepare --force'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVBc3r3WrJeXzaogVD6GwX
Implement spec phase 4 against the real herdr 0.7.x CLI contract
(captured by probing a live server):

- subprocesses/herdr.py: JSON-envelope plumbing for worktree
  list/create/open/remove and workspace focus/close; errors are parsed
  from the payload since herdr can report them with exit code 0
- HerdrWorktreeProvider: creation via 'herdr worktree create --no-focus',
  discovery via list entries (open_workspace_id -> provider_id), removal
  by workspace id with a git fallback for unpresented worktrees and
  explicit partial-failure reporting
- HerdrPresenter: open (natively idempotent), find by canonical path,
  focus (opens when absent; stale ids raise PresentationNotFoundError),
  close (idempotent, tolerates already-closed), full capabilities
- Backend resolution: presets native/herdr/auto; auto selects herdr only
  inside a verified context (HERDR_ENV=1 + reachable session socket) --
  the executable existing is never enough; explicit kinds override
  presets, CLI --backend overrides manifest [workspace] settings
- CLI: up gains --backend/--provider/--presenter/--focus; detached up
  never steals focus; prepare and reset pin the native provider so
  preparation stays dependency-free for CI and external hosts
- Tests: fake herdr executable backed by real git (CI needs no herdr);
  provider and new presenter contracts pass against it; root conftest
  scrubs HERDR_* env so auto-detection stays inert in the suite
- Verified live: up --backend herdr and flagless rm inside a real herdr
  session (create, present, close, remove; branch preserved)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVBc3r3WrJeXzaogVD6GwX
A herdr plugin (integrations/herdr/) that closes the reverse loop: when
herdr creates or opens a worktree, the plugin runs 'git workspace
prepare <path>' so assets and setup hooks apply regardless of which side
initiated the worktree.

Contract discovered by probing herdr 0.7.4's plugin system live:
herdr-plugin.toml manifest (id/min_herdr_version/platforms required),
dotted event names (worktree.created/opened/removed), commands resolved
relative to the plugin root, payload delivered via
HERDR_PLUGIN_EVENT_JSON / HERDR_PLUGIN_CONTEXT_JSON env vars, execution
captured in 'herdr plugin log'.

- events: worktree.created + worktree.opened -> prepare (idempotent)
- actions: prepare / reprepare for the current worktree
- non-git-workspace repos are skipped silently (herdr fires events for
  every repo it manages); failures raise a herdr notification with the
  retry command
- deliberately thin per spec: no config parsing, no direct hooks, no
  state; everything delegates to git-workspace
- lock contention now exits 75 (EX_TEMPFAIL) instead of 1 so the plugin
  can skip benignly while 'up --backend herdr' is still preparing the
  worktree it just created via herdr
- tested without herdr: hook driven directly with captured payload
  shapes against real workspaces; fake herdr gained 'notification show'
- manifest validated against real herdr: links with zero warnings

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVBc3r3WrJeXzaogVD6GwX
- Features, TOC, and commands cover backends, prepare, and lifecycle state
- New 'Preparing worktrees' section: prepare semantics, --force as the
  reset replacement, state tracking and failure retry, lock exit code 75
- Hook table reflects reality: on_setup runs on preparation, detach and
  teardown hooks now also run on prune --apply
- Pruning section no longer claims hooks are skipped (they run since the
  lifecycle fix), documents skip-and-report failure handling
- Doctor checks table gains failed-preparation and stale-state rows
- Detached mode notes presentations open without stealing focus
- Development layout updated to the providers/presenters/backends tree,
  with the contract-test entry points for new implementations

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVBc3r3WrJeXzaogVD6GwX
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant