Skip to content

refactor: collapse the workspace primitives into internal/herd - #22

Merged
xico42 merged 1 commit into
mainfrom
chore/refactor-packages
Jul 17, 2026
Merged

refactor: collapse the workspace primitives into internal/herd#22
xico42 merged 1 commit into
mainfrom
chore/refactor-packages

Conversation

@xico42

@xico42 xico42 commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Why

A deleted worktree could leave its agent's tmux session and process tree alive, orphaned against a directory that no longer existed. That bug was not isolated — it was one of at least four instances of a single structural cause.

The workspace domain (a worktree together with its sessions and its project) was split across internal/session, internal/worktree, and internal/project: three packages that could not see each other. session.Service was the only core service constructed without cfg, so it could not know the active profile, and every profile decision had to leak upward into its callers. The profile-blind literal semconv.SessionName("", …) appeared nine times, and each occurrence was a latent miss — running markers never showed under a profile, .herd templates rendered a different session name depending on which command created the worktree, and the kill loop in worktree.Delete was dead code that either missed or no-oped. The enforced boundary even forced worktree to smuggle session logic in through the raw tmux client, without the profile and therefore wrongly. The boundary caused the defect.

What

  • Collapse the primitives into internal/herd. One domain package owns projects, worktrees, and sessions together, because they are one thing: a workspace. Herd carries cfg, the active profile, and the exec-boundary runners; Ref is identity that always carries the profile, obtained only from h.Ref(project, branch) or Workspace.Ref and never built by hand. Because every operation now flows through a profile-aware Ref, the class of defect is closed at the source instead of patched at each call site — you can no longer create a session you cannot address.
  • Rehouse git exec into internal/git as pure mechanism (a WorktreeRunner + CloneRunner + Runner union with the porcelain parsers) that never sees cfg or the profile. filecopy and herdtemplate stay outside herd because they never needed the profile — exactly why they were never implicated in the profile bugs.
  • Thin the front ends. cmd/ and internal/tui/ parse input, call one herd operation, and render the result. A single error translator replaces the scattered os.Exit calls in RunE, and the TUI humanizes herd's sentinel errors instead of surfacing raw ones.
  • Pre-upgrade session compatibility. herd recovers and self-heals projects from pre-upgrade sessions that predate the @codeherd_project marker, matching live sessions on their stored canonical name.
  • Coverage contract. A real-tmux integration matrix exercises every operation with the profile both off and on, pinning behaviour the old split could never guarantee.

The design and per-plan handoff notes are in docs/superpowers/specs/ and docs/superpowers/plans/.

Notes

The original symptom fix (75177ee) is preserved as its own commit; this refactor sits on top of it and eliminates the underlying class of defect.

🤖 Generated with Claude Code

A deleted worktree could leave its agent's tmux session and process tree
alive, orphaned against a directory that no longer existed. That bug was
not isolated — it was one of at least four instances of a single
structural cause. The workspace domain (a worktree together with its
sessions and its project) was split across internal/session,
internal/worktree, and internal/project: three packages that could not
see each other. session.Service was the only core service constructed
without cfg, so it could not know the active profile, and every profile
decision had to leak upward into its callers. The profile-blind literal
semconv.SessionName("", …) appeared nine times, and each occurrence was
a latent miss — running markers never showed under a profile, .herd
templates rendered a different session name depending on which command
created the worktree, and the kill loop in worktree.Delete was dead code
that either missed or no-oped. The enforced boundary even forced
worktree to smuggle session logic in through the raw tmux client,
without the profile and therefore wrongly. The boundary caused the
defect.

This folds the three primitives into one domain package, internal/herd,
that owns projects, worktrees, and sessions together, because they are
one thing: a workspace. Herd carries cfg, the active profile, and the
exec-boundary runners; Ref is identity that always carries the profile,
obtained only from h.Ref(project, branch) or Workspace.Ref and never
built by hand. Because every operation now flows through a profile-aware
Ref, the class of defect is closed at the source instead of patched at
each call site — you can no longer create a session you cannot address.

Git execution moves out into internal/git as pure mechanism — a
WorktreeRunner + CloneRunner + Runner union with the porcelain parsers —
that never sees cfg or the profile. filecopy and herdtemplate stay
outside herd because they never needed the profile, which is exactly why
they were never implicated in the profile bugs. Needing cfg, the
profile, or identity to decide something puts code in herd; not needing
them keeps it in a support package.

With the domain consolidated, cmd/ and internal/tui/ become thin front
ends that parse input, call one herd operation, and render the result. A
single error translator replaces the scattered os.Exit calls in RunE,
and the TUI humanizes herd's sentinel errors instead of surfacing raw
ones. herd also recovers and self-heals projects from pre-upgrade
sessions that predate the @codeherd_project marker, matching live
sessions on their stored canonical name. A real-tmux integration matrix
exercises every operation with the profile both off and on, pinning
behaviour the old split could never guarantee.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@xico42
xico42 force-pushed the chore/refactor-packages branch from c024217 to 3c4ac2f Compare July 17, 2026 20:37
@xico42
xico42 merged commit 8663fe3 into main Jul 17, 2026
7 checks passed
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