Worktree commands have one orchestration, shared by the CLI and the dashboard #477
Labels
No labels
agent-ready
bug
contract
documentation
duplicate
enhancement
good first issue
help wanted
invalid
needs-human
needs-input
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
iterative-works/iw-cli#477
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Worktree commands have two orchestrators, and only one of each pair is
maintained. The CLI and the dashboard each re-derive the sequence of a
command — the order of steps, the checks, and what runs alongside them — from
the shared pieces in
core. The pieces are shared; the orchestration is not.The two mutating dashboard endpoints solve this in opposite, ad-hoc ways:
/api/worktrees/create,CaskServer.scala:1224) re-wires thesteps in-process.
WorktreeCreationService.createcallsIssueId.parse,builds a
WorktreePath, then invokescreateWorktree,createTerminalWorkspaceandregisterWorktreeas injected lambdas./api/worktrees/:issueId/remove,CaskServer.scala:1361)spawns the CLI.
WorktreeRemovalServicebuildsSeq("rm", key, "--path", …, "--no-prompt")and hands it toIwCli.run(
WorktreeRemovalService.scala:64-70,CaskServer.scala:1373), then readsan exit code.
Neither reuses
Start.runorRm.run, because both are CLI entry points:argv in, prose out through
env.console,CommandResultback(
core/commands/Start.scala:24). A web handler needs typed inputs and typedfailures, so the create path invented its own
WorktreeCreationErrorADT andthe remove path settled for exit codes.
What has already drifted
WorktreeCreationServiceis behindiw startin three ways:WorktreeCreationService.scala:73-76hardcodesWorktreeKey.Issue;Start.scala:122buildsWorktreeKey.Effort(slug).An effort worktree cannot be created from the browser at all.
Start.scala:194,248,271runsenv.hooks.runSessionHooks.The dashboard path has no equivalent, so a worktree created from the browser
silently skips every project session hook.
:82isfullPath = s"../${worktreePath.directoryName}", under the comment"For now, we construct a relative path string that will be resolved by the
caller."
Each divergence appeared because a change landed in
Start.scalaand nothingforced the copy to follow.
Two justifications for the subprocess that do not hold
Checked while analysing IW-476, so they are not re-litigated later:
.iw/config.conffilesacross the local project tree read
version = latest, and~/.local/share/iw/versionsholds exactly one directory (latest), so everyproject resolves the same binary.
iw-bootstrapis an installer/launcher —read a version, download to a shared cache if absent,
exec iw-run— not aper-project component boundary.
iw rm. Overstated inIwCli.scala's scaladoc.Rm.scala:222-233usesos.dynamicPwd.withValue,a scoped thread-local restored even when a hook throws, so concurrent server
requests could not corrupt one another.
What does survive is crash isolation for hook code: a project hook that
hangs or calls
System.exitwould take the server with it.IwClicontainsthat with a 2-minute timeout. That is a narrow concern about hooks, not a
reason for a whole command to live behind a process boundary.
Outcome
dashboard. A change to what
iw startdoes reaches the browser with nosecond edit.
browser run the project's session hooks.
iw startand the dashboard produce identical worktrees, including theherdr sidebar label (the divergence IW-476 accepted rather than papered over).
Design sketch
Extract each command's orchestration into a function with typed input and a
typed result — the functional core the project already claims — and give it two
shells:
Start.run/Rm.runrender the typed result to a console and aCommandResult.Then
WorktreeCreationServiceandWorktreeRemovalServicecollapse into thinadapters, and
WorktreeCreationErrorbecomes the shared failure type ratherthan a dashboard-only invention.
Hook execution is the one step that may keep an isolation boundary. Decide
during the work whether it stays in-process (with a timeout) or is the only
thing that is still spawned.
Scope
core/commands/Start.scala,core/commands/Rm.scaladashboard/jvm/src/application/WorktreeCreationService.scala(+ its 17 tests)dashboard/jvm/src/application/WorktreeRemovalService.scaladashboard/jvm/src/infrastructure/IwCli.scala— kept, reduced, or removeddashboard/jvm/src/CaskServer.scala:1224,1361MainProjectServiceis read-only derivation and is out of scope. The twomutating endpoints above are the whole dashboard command surface today; the
same rule should govern any command the dashboard drives later.
Notes
not thread its new workspace label through
WorktreeCreationService,because that orchestrator is dissolved here. Until this lands, a worktree
created from the browser carries the long
<project>-<key>herdr label whileone created by
iw startcarries the short key — two conventions in the samesidebar. That is the accepted cost recorded in IW-476's analysis.
CaskServer.scala:1279will passsessionNametwice after IW-476, which isa greppable marker of the unconverted path.
Intake: needs-human
Workflow: waterfall
Reason: G4 the analysis proposes a change to a document a human owns: a paragraph in
CLAUDE.md"Dashboard layers" (analysis.md:378, Documentation Requirements)Analysis: project-management/issues/IW-477/analysis.md on branch IW-477