Skip to content

The Run owns its start, tested in-process behind a seam (#388) - #389

Merged
JacobStephens2 merged 2 commits into
mainfrom
issue-388
Oct 5, 2026
Merged

JacobStephens2 merged 2 commits into
mainfrom
issue-388

Conversation

@JacobStephens2

@JacobStephens2 JacobStephens2 commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Summary

The Run's start, from the Tickets read to the Claim ended, now runs in the Run module's own code. It is generic over a private seam, run::Outside. The Launch directory is opened before the seam, and the production adapter is built from it.

 run_to_end
   BaseFix::new
   run
     LaunchDirectory::open_for_run          # Origin match, open issue, git identity
+    LaunchAndGitHub { directory, Logs::of_run, claim: None, .. }
+    start(outside, issue, asks, started_by, directory.checked_out())
-    github::tickets / logs::started / branch::select / base_branch / pull
-    interrupt::requested / claim::make / Logs::of_run / Delivery
+      outside.tickets        unless a child Run
+      outside.started        as a Run or a Spec run
+      refuse parallel       on an issue with no sub-issues
+      outside.select
+      refuse all closed     on a fresh selection
+      selection.pr_base(given, checked_out, |line| outside.step(line))
+      outside.base_branch / outside.pull
+      outside.interrupted   -> FailedRun { interrupted: true }
+      outside.make_claim    unless a child Run
+      outside.worktree(Fresh | Continuation, branch, base)
+      outside.spec_run | outside.deliver(fresh or continuation prompt)
+      outside.end_claim(goal reached, or none)
  • The worktree. It is the seam's associated type. In production it is a real Worktree. In the fake it is the Checkout it was created by, so the hand-off is tested without git.

  • The Claim. The production adapter makes the Claim and keeps it. The rules decide whether a Claim is made and with which goal it is ended.

  • The Session logs. They are built when the adapter is built, so no unit test needs the process-wide logs.

  • Issue branch selection. Selection::pr_base now always takes the progress line as a callback. The variant that wrote straight to the progress lines had no callers left, so it is gone.

  • The tests. 25 unit tests in src/run.rs cover what started the Run, the work recorded as started, the refusals, the Base branch, the pull, the interrupt, the Claim and the hand-off. Seven end-to-end tests that each checked only one of those rules are removed:

    • three in tests/continuation.rs: the two continuation prompts, and the checked-out Base branch without an open PR;
    • three in tests/spec_run.rs: parallel on an issue with no sub-issues, every Ticket closed with no Spec branch, and an issue with no sub-issues being an ordinary Run.

    The end-to-end tests for each wiring path stay. They cover the preflight refusals changing no label, the Claim being made before the worktree, a Claim that can't be made, the fresh prompt in a sibling worktree, an open PR's base whatever is checked out or on a detached HEAD, and the pull.

Evidence

  • Before: the order of the start had no unit test. It could only be checked by driving the binary against the fake gh, git and claude.
    After: cargo test --bin thirdshift run:: passes. One of the new tests, for example:

    a_run_its_command_started_makes_the_claim_before_the_worktree_and_ends_it
      calls == [Tickets, Started{Run}, Select, BaseBranch(None), Interrupted,
                MakeClaim, Worktree(Fresh, issue-7, main),
                Deliver(Fresh, main, fresh prompt), EndClaim(ReadyForReview)]
    

    The full cargo test run passes: 961 tests, 0 failed. cargo clippy --all-targets reports no warnings, and cargo fmt --check is clean.

Merge Danger

Door: two-way

The interface of the Run module is unchanged, and the seam is private. Seen from the command line, nothing changes: the requests, git effects, progress lines, logs, refusals and outcomes are the same.

Blast Radius: every Run

Every Command that does work goes through this start: a Run, a Spec run, and an Architect run's or a Pickup run's dispatch. A mistake in the order of the steps would show up there, for example a Claim made before a refusal. The remaining end-to-end tests check the wiring for each of these paths.

Unaddressed findings

Standards

  • The interface is wide. LaunchAndGitHub has 13 methods, most of them one-line pass-throughs. These are the 12 methods the spec listed, plus ending the Claim, and the sibling seams look the same.
  • Whether a Claim was made is tracked in two places. start guards on a claims bool, and the adapter also holds an Option<Claim>. The bool is the rule under test. The Option is where the adapter keeps the Claim it made, which the spec asks for.
  • base is passed on each call. It goes to worktree, spec_run and deliver rather than being kept in the adapter. The rules settle the Base branch, so it can't be a field of the adapter, which is built before it is settled.
  • start<O: Outside> instead of impl Outside. delivery.rs uses the same generic form, and start_in_worktree names O too.
  • The fake uses one bool per failure. The sibling fakes use a Fails enum. Here each failure is a single step with nothing to share, so the bools read more plainly.
  • Some test assertions still use positions. The ones left check the last calls, as in "ends with EndClaim(None)". The position is the point there: nothing comes after.
  • start_by, run_on and run_asked overlap. Each is a shorter call for the most common case.
  • Selection::pr_base now requires the line callback. It has one production caller.

Spec

  • The API of pr_base changed. The spec says to use the existing callback variant. Keeping the old wrapper would have left dead code that clippy warns about.
  • The open PR's base on a detached HEAD is still its own end-to-end test. It is not merged into the checked-out case. A detached HEAD goes through a different path in the Launch directory, and the spec's own list names both cases.

Closes #388

🤖 Generated with Claude Code

JacobStephens2 and others added 2 commits October 4, 2026 21:58
The Run's start, from the Tickets read to the Claim ended, runs generic over a
private seam, with a production adapter over the opened Launch directory,
GitHub, Issue branch selection, the logs, the Claim, the worktree, the Spec run
and the Delivery, and a scripted fake. Each rule has a unit test, and the
end-to-end tests that only checked one of them through a whole Run are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Name the worktree's checkout a Continuation as CONTEXT.md does, pick it and
the prompt in one match, assert the start's calls by their order rather than
their positions, and leave the kept end-to-end tests where they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit 1149ad7 into main Oct 5, 2026
13 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-388 branch October 5, 2026 02:09
@JacobStephens2 JacobStephens2 mentioned this pull request Oct 5, 2026
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.

The Run owns its start, tested in-process behind a seam

1 participant