Skip to content

Make the refused --worktree flag pairs unrepresentable - #167

Merged
adamw merged 3 commits into
masterfrom
worktree-agent-a5b1a4380c1b51005
Sep 11, 2026
Merged

Make the refused --worktree flag pairs unrepresentable#167
adamw merged 3 commits into
masterfrom
worktree-agent-a5b1a4380c1b51005

Conversation

@adamw

@adamw adamw commented Aug 28, 2026

Copy link
Copy Markdown
Member

--worktree cannot be combined with --skip-branch or with --keep-changes.
Both pairs were refused at runtime, but nothing stopped code from building one:
OrcaArgs carried three independent Flag fields, and the shell's FlowFlags
three independent Booleans, built at four launch sites.

The three flags now become one value once argv is parsed:

enum RunTarget:
  case NewBranch(uncommitted: Uncommitted)      // Stash | Keep
  case CurrentBranch(uncommitted: Uncommitted)
  case Worktree

Worktree carries neither a branch mode nor an Uncommitted, so the five legal
combinations are the only ones that can be written.

What is now impossible

  • The refusal inside flow() is gone. It was there because an OrcaArgs
    built by hand had never been through the parser. Such an OrcaArgs can no
    longer hold a refused pair, so there is nothing left to check.
  • A launch path cannot be handed a refused pair. FlowFlags holds a
    RunTarget, so the four sites that build one (menu run, resume, flow
    authoring, orca run) cannot spawn a flow child that would refuse its own
    argv.
  • One type, not two. The shell had its own three-case RunTarget for the
    menu; it is folded into the shared one rather than left as a second enum
    meaning the same thing.
  • One home for the flag spelling. RunTarget.toArgv renders the flags the
    shell appends after --, next to the @arg annotations that parse them back.

What is still representable, and why

mainargs builds a case class straight from argv, so raw Flag fields have to
exist at the parse boundary. They moved to RawArgs, private to package orca, whose only consumer is OrcaArgs.parse. orca run has the same
boundary — its own mainargs @main flags — and converts them the same way.
Both conversions go through RunTarget.from, the single place a refused pair is
worded and rejected. So argv can still say --worktree --skip-branch; nothing
past those two conversions can.

One derived pair of booleans is left: DirtyTreeFacts(skipBranch, keepChanges),
the dirty-tree policy's input, now filled from target.skipBranch /
target.keepChanges. It has no worktree field, so it cannot express a refused
pair either.

ParserForClass[OrcaArgs] is no longer given, since OrcaArgs is no longer the
parser's type. Flow scripts keep writing flow(OrcaArgs(args)).

No behaviour change

Same flags, same refusal wording and precedence (--worktree --keep-changes
still names only those two), same exit codes, same argv passed to the flow
child.

sbt scalafmtAll clean, sbt test green: 2238 tests, 0 failures.

adamw added 3 commits August 28, 2026 10:30
`--worktree` cannot be combined with `--skip-branch` or `--keep-changes`.
Until now three independent `Flag` fields on `OrcaArgs` could hold either
pair, so the refusal had to be repeated at runtime: once in
`OrcaArgs.parse`, and again in `flow()` for an `OrcaArgs` built by hand.

The flags now become one `RunTarget` — `NewBranch(Uncommitted)`,
`CurrentBranch(Uncommitted)` or `Worktree`, which carries neither a
branch mode nor an `Uncommitted`. The five legal states are the only ones
that can be written, so the check in `flow()` is gone.

mainargs still needs raw `Flag` fields to parse argv, so those live on
`RawArgs`, private to the package. `OrcaArgs.parse` is its only consumer
and converts through `RunTarget.from` — the single place a refused pair
is worded and rejected.

`RunTarget.toArgv` renders the flags back, next to the parser that reads
them, so the shell no longer spells them out itself.
`FlowFlags` carried the same three independent booleans as `OrcaArgs`,
built at four launch sites, so any of them could hand the flow child a
combination it would refuse after the spawn. It now carries a
`RunTarget`, which cannot express one.

The shell's own `RunTarget` (menu-only, same three destinations) is
dropped for the shared type. `orca run` converts its argv flags with
`RunTarget.from` and refuses a bad pair before resolving anything, as
before — but now that conversion is the only way to reach a launch path,
rather than a check a call site could skip.
Review follow-ups on the two commits before this one:

- `flow()` asked `args.target == RunTarget.Worktree`; it now matches, so a
  new case cannot silently take the in-checkout path.
- `--skip-branch --keep-changes` is the one combination of two flags orca
  allows, and it is now its own constructor — so it gets a parse test.
- ADR 0018 named the three `OrcaArgs` fields the flags used to become.
@adamw
adamw merged commit 1f7d625 into master Sep 11, 2026
6 checks passed
@adamw
adamw deleted the worktree-agent-a5b1a4380c1b51005 branch September 11, 2026 07:35
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