Phase 5: Every remaining command declares #520

Merged
mph merged 22 commits from IW-463-phase-05 into IW-463 2026-09-04 08:01:15 +00:00
Owner

Phase 05 implementation for IW-463.

Files

Browse changed files: https://code.iterative.works/iterative-works/iw-cli/-/blob/IW-463-phase-05/

Phase 05 implementation for IW-463. ### Files Browse changed files: https://code.iterative.works/iterative-works/iw-cli/-/blob/IW-463-phase-05/
mph added 18 commits 2026-09-04 06:28:50 +00:00
Records the cross-tier hit list for every refusal string the ten Phase 5
commands print today, before any migration starts (the lesson Phase 4's
red suite cost us). Confirms the group map is unchanged and checks off
the Step 0 and Confirm-before-writing tasks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Adds the CommandDeclaration for `version` (--verbose) and `projects`
(--json), migrating run() onto CommandArgParser.parse. Both now reject
--help and unknown flags through the shared parser instead of the old
args.contains scan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Both take Args.unit and now reject any argument by name instead of
silently ignoring it — a deliberate behaviour change (Risks 2), asserted
in the harness tests and recorded in the log. The Step 0 caller sweep
found no in-repo invocation passing either command an argument.

Closes Step 1 of Phase 5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
server, server start, server stop and server status each declare fully
(D5.7), replacing Server.usage and the hand-rolled args.head match with
CommandArgParser.dispatch. Rejection wording moves from "Unknown
command"/hand-rolled usage to the declared parser's MissingSubcommand/
UnknownSubcommand text.

The three subcommands' declared exit-1 causes describe the actual code
rather than phase-05-context.md's D5.7 prose, which does not match
stop's and status's real failure paths — recorded as a finding in
LESSONS.md and the log rather than editing the context document.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Declares `dashboard` (state-path/sample-data/dev), renames Dashboard.Args to
DashboardOptions per D5.5 and makes it private[commands], and deletes
Dashboard.parseArgs and printHelp. run() parses argv before touching
IW_DASHBOARD_JAR/HOME so --help needs neither.

Repairs the two rejection-text sites the Step 0 sweep named:
DashboardHarnessTest and dispatcher-mill-gate.bats, both moving from
"Unknown argument" to "Unknown flag: --bogus-flag.".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Declares `doctor`'s three switches (--env, --quality, --fix) into a
DoctorOptions case class. The --fix-implies-Quality / --fix-beats---env
precedence rule moves to Doctor.filterCategoryFor, a pure function beside
the command rather than a leaf (D5.2) — it depends on a sibling flag, not
on --env's own value. --fix and --env's `does` text names the interaction,
since the registry walk only checks non-blank, not complete (Risks 4).

DoctorCliFlags and its test are deleted; DoctorCliFlagsTest's three
behavioural cases move onto a new DoctorPrecedenceTest exercising
filterCategoryFor directly. Its fourth case ("ignores unrelated flags") is
not ported — that behaviour is exactly what this phase removes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Declares init's six flags into an InitOptions case class via Args.mapN's
arity-6 overload. --force is a switch; --tracker is Args.valued with an
empty-string "absent" sentinel and a closed-set leaf validator (D3.6);
--team/--repository/--team-prefix/--base-url are Args.resolved, matching
--state-path's "resolved elsewhere" shape from Step 3. --team-prefix's
TeamPrefixValidator stays a domain check after the parse, unchanged.

This widens accepted syntax to a superset: --tracker=X keeps working,
--tracker X now also works. Init.parseArg and its six call sites are
deleted. resolveTrackerType no longer needs Either, since the leaf
validator already restricts its input.

D5.1 held for init: no combine(leaves*) needed, six leaves compose
directly via Args.mapN.

Records a pre-existing hazard in LESSONS.md: Init's interactive
tracker-selection fallback recurses without a bound when a harness test's
FakePrompt queue runs dry, which hung the test JVM during RED; worked
around by queuing safety-net answers rather than fixed, which is out of
this phase's scope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Declares feedback's three leaves (<title> required operand, --description
resolved, --type valued) and deletes FeedbackParser's argv half
(parseFeedbackArgs, extractFlagValue). --type validates the string and
Feedback.toIssueType re-parses it, the same knowing double-parse
CiGateArgsDeclaration and PhaseMerge already carry (D5.3) — recorded as a
third call site in LESSONS.md's pending 2026-09-02 entry. The two length
limits stay in FeedbackParser as domain checks run after the parse.

The title becomes a required operand, so an unquoted multi-word title
(iw feedback Fix the bug) is now Rejection.UnexpectedArgument — a
user-visible change alongside issue create's equivalent (Risks 3),
release-note item.

FeedbackHarnessTest's --help-files-no-issue case is asserted on the
tracker's empty call list, not exit code alone; proved RED by a temporary
mutation routing HelpRequested through createForgejoIssue, then reverted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Declares issue's parent (<issue-id> optional operand, IssueTracking
group) and issue create (--title required, --description resolved).
Deletes IssueCreateParser.scala (whole file, IssueCreateRequest with it)
and IssueCreateParserTest.scala; the 422 and missing-value scenarios move
to IssueHarnessTest. Deletes Issue.printCreateHelp and its --help/-h
guard; CommandHelp.render replaces both. Corrects CommandGroup
.IssueTracking's doc comment now that a declaration binds to it.

The 422 the issue is named for closes with no special-casing: the inline
form --title=--wip: x creates the issue with that exact title (asserted
on the recorded tracker call, not exit code); the quoted form
--title "--wip: x" is a named MissingValue with no request sent
(asserted on the empty tracker call list) — Michal's 2026-09-03 ruling
on Open questions 1(a), unchanged.

Finding: D5.6 says issue's run should use CommandArgParser.dispatch, but
dispatch treats any non-flag bareword as an attempted subcommand once a
declaration carries one (verified with a throwaway probe test), so
routing through dispatch would turn every "iw issue <id>" into
UnknownSubcommand and break issue fetching outright. Issue.run keeps its
existing manual head-token check for "create" instead; create is still a
full declaration for help/registry purposes. Recorded in the log for
Michal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
D5.6's instruction to route issue's run through CommandArgParser.dispatch
could not be followed as written: dispatch treated any unmatched leading
bareword as an attempted subcommand once a declaration carried one, with
no fallback to the parent's own operand, so iw issue TEST-1 would break.
Michal approved widening dispatch instead of leaving issue's hand-rolled
if in place: an unmatched bareword now falls back to ToParent when the
parent's Args declares at least one operand leaf (a repeated flag does
not count, since it is matched by name, not position). Confirmed the
other four subcommand-carrying parents (gate, server, config,
review-state) carry no operand leaf and so keep UnknownSubcommand
unchanged, proved by dedicated CommandArgParserTest cases. Issue.run now
dispatches through the shared function like every other parent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Adds the ten remaining top-level Subject entries to CommandDeclarationTest
(version, projects, register, project-context, server, dashboard, doctor,
feedback, init, issue), expanding the walk to 34 declarations across 25
top-level subjects with server's three subcommands and issue create
folded in automatically. Confirms by rg that all four ad-hoc argv parsers
are gone and nothing in core/ reads argv except CommandArgParser and
ReviewStateCliParser (ReviewState's own combine()-sanctioned reads of the
already-parsed args are pre-existing Phase 3 work, not a violation).
Checks the PhasePrOptions leaf/field invariant by hand across the six
authorship subjects and gives their does/whenToReachForIt text a human
read, confirming doctor's --env/--fix interaction wording carries the
sibling-flag interaction the registry walk cannot verify on its own.
Closes out the Step 0 rejection-text sweep with no break outside the
recorded hit list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Adds test/version.bats, the phase's one E2E wiring round trip (LESSONS
16): iw version --bogus names the flag, exits 1, prints no version line.
Given as a new one-case file rather than folding into projects.bats,
matching the per-command convention every other wiring case in test/
already follows. Confirms test/dispatcher-mill-gate.bats and
test/issue-create.bats both pass, no doctor --help case was added (the
Phase 1 intercept still answers first by design), and nothing touches
iw-run, iw-bootstrap, docs/, .iw/commands/ or any // USAGE: line.

./iw ./test read twice: the first run's exit 1 was three pre-existing
TmuxAdapterTest/TmuxAdapterSendKeysTest failures unrelated to this
phase's ten commands, root-caused to deterministic session names racing
on the one real shared tmux server rather than a regression (isolated
rerun green, full suite immediately green again with no code change in
between) and filed as a new LESSONS entry rather than fixed here. The
reliable run this phase's changes are judged on: exit 0.

Wrap-up records D5.1-D5.7's outcomes, the four-parser inventory
correction (LESSONS lesson 4, corrected in place), the registry's
25-top-level/34-declaration count, both release-note items together,
and the note for Phase 6 that IssueTracking renders as a group of one.
The dev-docs #183 cross-repo question is left open for Michal, box
unchecked as instructed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Two full-suite runs on an unchanged tree failed with different test sets,
all start_dev_server timeouts; each file passes in isolation. Diagnosed as
contention between dashboard-dev-mode.bats's real dev servers and
bloop-cleanup.bash's machine-wide bloop kill, not a Phase 5 regression.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Four combined runs, four different failure sets; every tier and every named
file green in isolation. The flaky set is exactly the BATS files that spawn
scala-cli, which shares one bloop server that bloop-cleanup.bash kills
machine-wide.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Fixes the six warnings Michal approved from review-phase-05-20260904-080458.md
(seven counting the required-parent-operand CommandArgParserTest case already
RED-proved in the tree):

- Init.scala: --tracker's "" absent-sentinel becomes Option[String] via
  Args.valued(...).map(Option.when), keeping the closed-set validator.
  Args.resolved was considered and rejected -- it carries no validator and
  would have silently dropped D5.4's check against Constants.TrackerTypeValues.
- Init.scala: resolveTrackerType's doc comment now states the real mechanism
  (None means absent, Some means already validated) instead of a false claim.
- Feedback.scala: typeArgs now maps to FeedbackParser.IssueType at the leaf,
  so FeedbackOptions carries the domain type and validateAndSubmit no longer
  re-parses it.
- Feedback.scala: exit-1 documentation now lists the missing <title> operand
  and an invalid --type among its causes.
- InitHarnessTest.scala: dropped the stale "--help has no handling" comment
  and asserts the Usage line the Help branch actually prints.
- DoctorHarnessTest.scala: --help test asserts the Usage line, not only
  the absence of check output.
- Dropped this phase's four phase-number citations from evergreen comments
  in CommandDeclaration.scala, FeedbackParser.scala, Issue.scala and
  Doctor.scala, leaving earlier phases' citations untouched.

./mill core.compile and ./iw ./test unit (186/186 core.test, 241/241
dashboard.test) green throughout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Widen dev-docs #183 rather than open a second issue: mis-spelled
review-state flags, the non-existent --phase/--step/--branch block in three
workflow documents, and stray arguments to the ten commands this phase made
strict all share one root cause.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
chore(IW-463): register Phase 5 code review artifact
Some checks failed
CI / contract (pull_request) Has been skipped
CI / format (pull_request) Successful in 8s
CI / lint (pull_request) Successful in 8s
CI / compile (pull_request) Successful in 1m15s
CI / dashboard-build (pull_request) Successful in 1m2s
CI / test (pull_request) Has been cancelled
33836a55af
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
chore(IW-463): update review-state for phase 05
Some checks failed
CI / contract (pull_request) Has been skipped
CI / format (pull_request) Successful in 8s
CI / lint (pull_request) Successful in 13s
CI / compile (pull_request) Successful in 1m23s
CI / dashboard-build (pull_request) Successful in 1m2s
CI / test (pull_request) Has been cancelled
0dcfe0f233
chore(IW-463): update review-state for phase 05
Some checks failed
CI / contract (pull_request) Has been skipped
CI / format (pull_request) Successful in 10s
CI / lint (pull_request) Successful in 8s
CI / compile (pull_request) Successful in 1m15s
CI / dashboard-build (pull_request) Successful in 1m0s
CI / test (pull_request) Has been cancelled
7b437b74e7
chore(IW-463): update review-state for phase 05
Some checks failed
CI / contract (pull_request) Has been skipped
CI / format (pull_request) Successful in 7s
CI / lint (pull_request) Successful in 11s
CI / compile (pull_request) Successful in 1m20s
CI / dashboard-build (pull_request) Successful in 1m12s
CI / test (pull_request) Has been cancelled
ac18753d30
chore(IW-463): update review-state for phase 05
All checks were successful
CI / contract (pull_request) Has been skipped
CI / format (pull_request) Successful in 7s
CI / lint (pull_request) Successful in 7s
CI / compile (pull_request) Successful in 1m13s
CI / dashboard-build (pull_request) Successful in 1m0s
CI / test (pull_request) Successful in 10m7s
0ff7c2c8fb
mph merged commit ece33b2561 into IW-463 2026-09-04 08:01:15 +00:00
mph deleted branch IW-463-phase-05 2026-09-04 08:01:16 +00:00
Sign in to join this conversation.
No description provided.