Repository navigation
Description
What to build
A Run, an Architect run and a Pickup run all start from the Launch directory. Each needs the same facts about it: its origin and the GitHub repository that names, the git identity an agent commits with, the branch checked out there, the Base branch and its checks on origin, and the optional launch.pull fast-forward. Today the passes get these from the Launch directory module, but a Run works them out again for itself, using the pre-flight checks, its own read of the checked-out branch, the Base-branch fallback in Issue branch selection, and the pull, which lives in the Run module and which the Architect run borrows from there.
Deepen the Launch directory module so that it is the one module every command opens its Launch directory through. Every rule about the Launch directory then lives behind one interface:
- Origin match and the open-issue check for a Run.
- The git identity.
- The checked-out branch.
- The rule that a named branch, or else the checked-out branch, is the Base branch, and that a detached HEAD fails.
- The checks that the Base branch exists on
originand that no local copy of it is ahead. - The
launch.pullfast-forward. - The repository lock for passes.
The pre-flight checks module is absorbed and deleted.
Nothing a user sees changes. Every message, its order and its exit code stay the same, as do what is checked before any work, what a Command log and the Activity log record, and the launch.pull behaviour.
Acceptance criteria
-
A Run, a Ticket's Run and a Base fix included, opens its Launch directory through the Launch directory module. That opening makes the Origin match, the open-issue check and the git-identity check, in today's order and with today's messages. It also reads the checked-out branch, so the Run no longer reads it itself.
-
An origin that is not on GitHub still fails a Run as an
origin mismatch, not as "is not a GitHub repository". That second message stays the passes' own. -
The Run's Base branch is settled by the Launch directory module, from the branch named for it, or else the checked-out branch, and checked on origin there.
- The branch named for it is the open pull request's base in a Continuation that has one, or else the Base branch a dispatching Architect run or Pickup run gave it.
- Issue branch selection now says only which branch, if any, names the Base branch: the open pull request's base in a Continuation, with its "continuing … so the Base branch is …" progress line unchanged. It no longer has a detached-HEAD fallback of its own.
-
The detached-HEAD rule exists once. A Run's message stays
HEAD is detached; check out the branch the work should be based on. An Architect run's or Pickup run's message keeps its, or name it with base <branch>hint, since only those commands takebase. -
The
launch.pullfast-forward is part of the Launch directory module's interface. Both the Run and the Architect run call it there, and the Run module no longer exports it. Each keeps its behaviour, including when it happens: an Architect run pulls only once past its skip checks, and its dispatched Run pulls again, a no-op. -
The pass start, with its preflight, Base branch and repository lock, keeps its interface and its skip on
AlreadyRunning, built on the same opening as a Run's. -
The pre-flight checks module is gone. No module outside the Launch directory module runs
giton the Launch directory to readremote.origin.url, the checked-out branch, the git identity, the Base branch's presence on origin or whether it is ahead. -
The Launch directory module has unit tests through its interface, against a temporary repository with a bare origin beside it, as the git module's tests set one up. A
url.<bare>.insteadOf https://github.com/<owner>/<repo>.gitsetting letsoriginname a GitHub repository. The tests cover:- a detached HEAD, with and without a named branch, and both messages;
- a named branch winning over the checked-out one;
- a Base branch missing on origin;
- a local Base branch ahead of origin;
- the pull fast-forwarding only when the Base branch is the checked-out one;
- a pull that can't fast-forward being a warning that leaves the checkout alone.
The open-issue check needs
gh, so it stays covered end to end. -
The existing end-to-end tests pass unchanged, in particular
tests/preflight.rs,tests/launch_pull.rs,tests/continuation.rs,tests/numbered_branches.rs,tests/architect_run.rs,tests/pickup_run.rsandtests/command_logs.rs.cargo clippyis clean. -
CONTEXT.mdand the ADRs need no change. Launch directory is already a glossary term, and the module is named for it.
Blocked by
None (can start immediately).
Decisions
- The Launch directory module is the deepened module, and the pre-flight checks are absorbed into it. The pre-flight checks module only works on the Launch directory, so it is a shallow module beside the deep one. Deleting it concentrates its rules where the passes already take them. Why: the deletion test passes. The checked-out branch read, the detached-HEAD rule and the
remote.origin.urlread are each written twice today, so after the change they exist once. - The module opens the Launch directory in two ways, one for a Run and one for a pass, over one private opening. A Run's opening first makes the Origin match and checks that the issue is open. A pass's opening fails on an origin that isn't on GitHub, then settles its Base branch and takes the repository lock, as it does now. Why: the two commands differ only in what they're checked against: an Issue URL, or nothing and a lock. Both must keep their exact messages, and the Origin match must stay a Run's first check, before any GitHub request or Command log.
- A Run's Base branch is settled in two steps: Issue branch selection says what names it, then the Launch directory module settles and checks it. Why: a Run can only know its Base branch after Issue branch selection, because a Continuation's open pull request names it (ADR-0002, not reopened). The checked-out fallback and the detached-HEAD failure are Launch-directory facts, so they move into the Launch directory module. The pull-request rule, and its progress line, stay with Issue branch selection.
- The detached-HEAD message keeps its two wordings, chosen inside the module by which opening was used. Why:
base <branch>is accepted only byarchitectandpickup, as the help says, so a Run must not suggest it. The tests check both wordings exactly. Keeping them inside one module keeps the rule in one place, without putting the choice on callers. - The
launch.pullfast-forward moves into the Launch directory module as its own step, separate from settling the Base branch. Why: it only touches the Launch directory. An Architect run must pull after its skip checks and after it records the start of its work, not when it opens, so the pull can't be folded into the opening. The double call, from an Architect run and then the Run it dispatches, is kept: the second is a no-op, and this change is behaviour-preserving. - The module can be opened on any directory, not only the current one, for its tests. The commands open the current directory. Why: the current directory is process-wide, and Rust runs unit tests in parallel. A constructor that takes a directory lets the module's tests run through its interface without changing the current directory.
- No trait or seam for git. Why: git is a local-substitutable dependency. A temporary repository with a bare origin is a real git, as the git and worktree modules' tests already use, so a second adapter would be hypothetical.
- The Launch directory module's facts are read-only to callers. That covers the git handle,
origin, the repository and the checked-out branch. The Run no longer runs its ownsymbolic-ref. Why: a caller that needs one of these facts reads it from the module, so the module stays the one place that knows how each is found. - The open-issue check stays in the opening, though it calls GitHub. Why: it is part of "may a Run start from here", it is checked before any work today, and splitting it out would add a step for every caller to remember. It is covered end to end by the fake
gh. CONTEXT.mdand the ADRs are unchanged. Why: Launch directory and Origin match are already glossary terms, and this refactor decides nothing hard to reverse.
Architecture review
Strength: Strong · Dependencies: local-substitutable (git in a temporary repository)
Files: src/launch.rs, src/preflight.rs, src/run.rs, src/branch.rs, src/architect.rs, src/pickup.rs
Before
flowchart LR
Run --> Preflight[Pre-flight checks]
Run -.reads checked-out branch.-> Git[(Launch directory git)]
Run --> Selection[Issue branch selection]
Selection -.detached-HEAD fallback.-> Git
Run --> Pull[pull, in Run module]
Architect[Architect run] --> Launch[Launch directory module]
Pickup[Pickup run] --> Launch
Architect -.borrows.-> Pull
Launch --> Preflight
Launch -.reads checked-out branch.-> Git
Preflight --> Git
classDef leak stroke:#dc2626,stroke-width:2px;
class Selection,Pull leak
After
flowchart LR
Run --> Launch
Run --> Selection[Issue branch selection]
Architect[Architect run] --> Launch
Pickup[Pickup run] --> Launch
subgraph Launch[Launch directory module]
direction LR
open[origin + Origin match + identity + checked out] --> base[Base branch + checks on origin]
base --> pull[launch.pull]
open --> lock[repository lock]
end
Launch --> Git[(Launch directory git)]
classDef deep stroke-width:3px;
class Launch deep
Problem: A Run works out its Launch directory facts outside the Launch directory module, across the pre-flight checks, Run and Issue branch selection, so the checked-out branch read, the detached-HEAD rule and the origin read each exist twice.
Solution: Every command opens its Launch directory through one deepened Launch directory module that owns the Origin match, the identity, the Base branch rule and checks, and the pull. The pre-flight checks module is deleted.
Wins
- Locality: Launch directory rules in one module
- Leverage: one opening, three commands
- Delete the shallow pre-flight checks module
- Base branch rules unit-tested at last
- Run module sheds Launch directory work
Activity