refactor: collapse the workspace primitives into internal/herd - #22
Merged
Conversation
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
force-pushed
the
chore/refactor-packages
branch
from
July 17, 2026 20:37
c024217 to
3c4ac2f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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, andinternal/project: three packages that could not see each other.session.Servicewas the only core service constructed withoutcfg, so it could not know the active profile, and every profile decision had to leak upward into its callers. The profile-blind literalsemconv.SessionName("", …)appeared nine times, and each occurrence was a latent miss — running markers never showed under a profile,.herdtemplates rendered a different session name depending on which command created the worktree, and the kill loop inworktree.Deletewas dead code that either missed or no-oped. The enforced boundary even forcedworktreeto smuggle session logic in through the raw tmux client, without the profile and therefore wrongly. The boundary caused the defect.What
internal/herd. One domain package owns projects, worktrees, and sessions together, because they are one thing: a workspace.Herdcarriescfg, the active profile, and the exec-boundary runners;Refis identity that always carries the profile, obtained only fromh.Ref(project, branch)orWorkspace.Refand never built by hand. Because every operation now flows through a profile-awareRef, 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.internal/gitas pure mechanism (aWorktreeRunner+CloneRunner+Runnerunion with the porcelain parsers) that never seescfgor the profile.filecopyandherdtemplatestay outsideherdbecause they never needed the profile — exactly why they were never implicated in the profile bugs.cmd/andinternal/tui/parse input, call oneherdoperation, and render the result. A single error translator replaces the scatteredos.Exitcalls inRunE, and the TUI humanizesherd's sentinel errors instead of surfacing raw ones.herdrecovers and self-heals projects from pre-upgrade sessions that predate the@codeherd_projectmarker, matching live sessions on their stored canonical name.The design and per-plan handoff notes are in
docs/superpowers/specs/anddocs/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