IW-463: Help must be able to guide an agent #541
No reviewers
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
iterative-works/iw-cli!541
Loading…
Reference in a new issue
No description provided.
Delete branch "IW-463"
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?
Closes #463. Supersedes and closes #439 and #202, both carried in full.
Summary
iwhelp could not guide anyone — not a person at a shell, not an agent driving theCLI. Four failures, fixed together across eight phases:
ran, or failed on an unrelated error.
iw phase-merge --helpmerged pull request#434 — a destructive action performed in answer to a request for documentation.
iw --helpprinted six lines and redirected;iw --listprinted 34 flatPurpose:/Usage:pairs scraped from source comments.phase-pr's three flags were all typedDURwith no meanings, no defaults and no exit codes — and exit 3 ("the wait is unfinished,
call me again") lived only in a skill's text.
already disagreed.
Every command now carries a
CommandDeclaration. The parser, the rendered--help, thegrouped index,
docs/commands/and the published skill are all derived from it. The countof independent copies of any flag's meaning is one.
Changes
phase-prfully self-describing, end to endissue create422 fixedHelpentry point, grouped index, the comment scrape deleted (closes #202)iw command-index, manifest schema, rich listingdocs/commands/and the published skill generated from the registry229 files, +35523 / -2314.
Behaviour changes callers will notice
now exit 1 naming the token.
iw issue create --titleneeds quotes for a multi-word title. The old parser joinedevery bare token with spaces, which is what produced the bare 422 on a
---leadingvalue.
--title "Fix the bug"now, not--title Fix the bug.iw feedbacktakes the same treatment — the title is a declared operand.iw --list,iw --helpand bareiwrender the grouped index, not the old six lines.Testing
core/test/, 241 indashboard/. Green.test/bootstrap.batsagainst an extracted release tarball,which is the only place the release-path help classpath is exercised.
the counts and the mutations are recorded per phase in
implementation-log.md.The
.git-hooks/pre-commitscalafix step added in the final commit ran on that commitand passed.
Review
Start with
project-management/issues/IW-463/review-packet.md— goals, per-storyacceptance criteria, a reading order for entry points, two diagrams, the test summary and
files changed by area.
Lessons
LESSONS.mdwas triaged before this PR: 22 dropped, 17 reduced toFOLLOW-UPS.md, 22sorted for decision of which five were walked. Three promotions landed in this branch —
docs/launcher-performance.md, a newdocs/testing.mdsection, and the scalafix step in.git-hooks/pre-commit. Seventeen entries keep_pending_decisions by deliberate call:the ground they cover is already captured elsewhere.
Poznámky k vydání
Viz
project-management/issues/IW-463/release-notes.md.Merged with main
mainis merged in (3ab9cd4c). Seven files conflicted, all of them places wheremain and this branch had independently fixed the same thing:
IssuePoller.close()— both bounded the wait aftershutdownNow. Kept thisbranch's body, which also restores the interrupt flag, under the name
PullRequestPolleralready uses; dropped the duplicate constant the automergeleft behind.
CaskServerTest— complementary, so both were kept. This branch stops theserver before a test deletes what its pollers write; main's
removeStateDirtolerates whatever a save in flight left behind.
FakeCommandEnv.write— the fixture now carries both a per-path failure mapand a global write result. The specific answer wins, then the general one.
InitHarnessTest— both sides kept; this branch's four help and rejectioncases move onto main's config read-back helpers, which replaced the fixture's
config capability.
Init.scala,Doctor.scala,PullRequestPollerTest— call site,imports and imports.
Two things followed from the merge rather than from either side, and are worth a
reviewer's eye:
dashboard/, so this branch's poller andserver-stop tests were linted for the first time. Three violations: a
@volatile varin each poller test became anAtomicBoolean, and anAtomicReferenceseeded withnullbecame one seeded withNone— the idiomthe same file already uses for
startupError.ScalafixGatethe single definition of the lint argv. Thepre-commit step this branch adds had hardcoded a different string, which would
have been a fifth disagreeing copy of exactly the kind this issue removes. It
now runs the same command as pre-push, CI and
./iw ./test, andScalafixGateTestpins the pre-commit hook alongside the other three.Verified on the merged tree:
./iw ./test unit0,./iw ./test compile0,scalafix --check0,scala-cli fmt --check0.🤖 Generated with Claude Code
https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Issue: IW-463 Phase: 1 - A help request never acts This commit marks the checkpoint for phase 1 context generation. Use ag-revert IW-463 1 to return to this point. 🤖 Generated with Claude CodeA help request never acts Artifacts: - phase-01-tasks.md 🤖 Generated with Claude CodeIssue: IW-463 Phase: 2 - phase-pr describes itself This commit marks the checkpoint for phase 2 context generation. Use ag-revert IW-463 2 to return to this point. 🤖 Generated with Claude Codephase-pr describes itself Artifacts: - phase-02-tasks.md 🤖 Generated with Claude CodeRescued from a commit the batch run left unreachable. The entry diagnoses why phase 2's gate never converged: each exit-3 resume commits and pushes a review-state update, which cancels the CI run it is waiting for. 🤖 Generated with Claude CodeA wait that uses up its whole budget leaves through exit 1, but every poll that reached that point had read Running, so nothing failed. Exit 1 named only red CI, an unreadable forge and a bad argument, and told the caller to fix it and run again — which times out again on the same budget. Found by re-running the agent-legibility gate with an isolated agent, which answered both required questions correctly and then reported that the help never says what happens when the budget runs out. - exit 1 names the spent budget and says to raise --timeout - CommandDeclarationTest gains a content rule over the subject list, so phase-merge and ci-gate are covered when they declare in phase 4 - PhasePrHarnessTest pins the in-loop routing, which no test covered; the existing budget test exercises only the pre-poll path - Builder.register refuses a duplicate flag name, which otherwise loses the caller's value silently - CommandHelpTest guards the blank line before every section header 🤖 Generated with Claude CodeIssue: IW-463 Phase: 3 - Declarations that cannot be forgotten This commit marks the checkpoint for phase 3 context generation. Use ag-revert IW-463 3 to return to this point. 🤖 Generated with Claude CodeIssue: IW-463 Phase: 4 - Worktree and phase lifecycle declare This commit marks the checkpoint for phase 4 context generation. Use ag-revert IW-463 4 to return to this point. 🤖 Generated with Claude CodeWorktree and phase lifecycle declare Artifacts: - phase-04-tasks.md 🤖 Generated with Claude CodeDeclares the three phase commands. phase-advance: both flags Args.resolved from the branch, per the context's worked recipe. phase-commit: --title required, --items/--issue-id/--phase-number Args.resolved; the inline usage string goes. phase-merge composes the Step 2 shared Args[CiGateConfig] plus --max-retries (validated with PhaseMergeArgs.parseMaxRetries), --issue-id and --phase-number; CiGateArgs.parseConfig is no longer called from this file. Declares exit 3 (it reaches CommandResult.resume) and exit 1 naming --timeout in both meaning and caller action, matching PhasePr's exit 1 text for a spent budget. Adds FakeGit.getCurrentBranchCallList so a --help harness case can prove no git call was made. LESSONS: running a new --help/unknown-flag harness test against an unmigrated gated command (phase-merge, before this commit) can hang the JVM forever rather than fail an assertion, because FakeClock's sleep is a no-op and its now never advances without an explicit call — the poll loop spins with no exit condition. Two Mill daemon processes for this worktree were left in that spin state and had to be killed by PID; the daemon's own state directory (out/mill-daemon) was left corrupt afterward ("Worker wire broken") and had to be removed to let a fresh daemon start. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxaRecorded at the moment found while sweeping the repo for start's --effort/<issue-id> callers: test/help-intercept.bats:207 escaped a literal "iw start" search because the binary name is quoted separately from the subcommand ("$PROJECT_ROOT/iw" start), which is this project's own BATS idiom throughout. The stale assertion only surfaced when ./iw ./test ran the BATS suite, not from ./iw ./test unit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxaDeclares `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_01WehHdHp9MRKJkrhGFiKNxaIssue: IW-463 Phase: 7 - Plugins describe themselves This commit marks the checkpoint for phase 7 context generation. Use ag-revert IW-463 7 to return to this point. Review found eight wrong citations, three internal contradictions and three wrong counts; all corrected and re-verified against the tree. Two items are surfaced for decision, not settled: D7.2 proposes an amendment to DECISION 2, and the project-command half is out of scope until CLARIFY 1 is answered. Risks 7 recommends splitting the phase. 🤖 Generated with Claude CodeIssue: IW-463 Phase: 7 - Plugins describe themselves All five open questions answered by Michal on 2026-09-05. Zero CLARIFY markers remain; each is now a dated decision with its reasoning. - D7.2 reader token --command-index <dir>, --external stays the roster - D7.8 object IwCommands in lib/, applied symmetrically via .iw/lib/ - D7.9 digest covers lib/ as well as commands/ - D7.10 one mismatch warning per invocation on stderr - D7.11 eighth CommandGroup case, Authoring - D7.12 the generator is named command-index - D7.13 this repository ships no manifest of its own here DECISION 2 in analysis.md gains a dated amendment recording the three points that supersede it. The phase ships whole; the split was declined. Estimate revised to 9-12h against tasks.md's 4-6h, accepted as a known deviation, and tasks.md is left unamended per the Phase 3-6 precedent. 🤖 Generated with Claude CodePlugins describe themselves Artifacts: - phase-07-tasks.md 🤖 Generated with Claude CodeHelp.answer's scaladoc documents the external form in prefix order, [--command-index <dir>] --external <name>, but the match arms accept only the suffix order. Both launcher sites emit suffix order, so the code is the correct side and the comment is wrong. Recorded pending, proposed home "new tracker issue": the fix touches a file Phase 8 opens only under one of its open routes. 🤖 Generated with Claude CodeIssue: IW-463 Phase: 8 - Generated docs and skill This commit marks the checkpoint for phase 8 context generation. Use ag-revert IW-463 8 to return to this point. Two rounds of review over the draft. Corrections that changed the plan: - analysis.md:817 says docs/commands/*.md, plural, so the page roster is decided (A), not an open three-way question. - build-iw-cli-skills rule 1 discovers commands by listing commands/*.scala. Nobody had named it. It is a second roster, and is now in scope. - README.md:65-77 is a further hand-kept copy carrying syntax lines for ten registry commands. No story named it; it gets its own decision, D8.10. - Story 6's "~0.09 s per help call" does not reproduce: 0.23-0.38 s measured. The conclusion survives, because there are only 26 commands. - The skill's .input-manifest hashes none of core/commands/, where the facts have lived since Phase 2, so incremental builds are blind to a changed does. D8.2 (generator mechanism) and D8.3 (the prose in review-state.md that no declaration holds) block implementation. D8.1 needs a one-line confirm. D8.6 and D8.10 are low-stakes. D8.4, D8.5, D8.7, D8.8 and D8.9 are taken. Deleting SKILL.md's "Current Known State" block loses nothing the retained rules need: its two load-bearing bullets are already stated by rule 1 of the iw-command-creation section, which stays out of scope. Estimate 6.5-8h against tasks.md's 4-6h, a known deviation; tasks.md is left unamended per the Phase 3-7 precedent. 🤖 Generated with Claude CodeIssue: IW-463 Phase: 8 - Generated docs and skill All five open questions answered by Michal on 2026-09-06. Zero CLARIFY markers remain; each is now a dated decision with its reasoning. - D8.1 option A - the pages that exist today, not one per command - D8.2 route (a) - a 27th command, iw command-docs [--check] - D8.3 option (c) - a generated page plus a hand-written companion - D8.6 option (b) - a docs/commands/* code arm in change-scope.sh - D8.10 option (a) - strip the syntax column from README.md:65-77 D8.2 makes command-docs the 27th registry entry, so it needs its own declaration, shim and group, and the roster gate covers it. The document now declares 26 as the inherited count and 27 as the post-phase count, and every occurrence is one or the other deliberately. D8.3 splits review-state.md into a generated page plus docs/review-state-cards.md, and rm.md plus docs/worktree-removal.md. The companions sit at docs/ root, which lets D8.6 treat docs/commands/ wholesale as code. The prose must land before the regeneration. The split adds a companion map inside command-docs - the only hand-kept thing this phase adds. It holds links, not facts, and a test asserts each named companion exists. Estimate 6.75-8.5h against tasks.md's 4-6h, a known deviation; tasks.md is left unamended per the Phase 3-7 precedent. 🤖 Generated with Claude CodeGenerated docs and skill 118 marked tasks across the context's six approach steps, plus 8 fallibility mutations, 25 acceptance boxes and an acceptance-to-task map covering all 25. Three departures from a literal reading of the context, each recorded in the file: - The two change-scope.sh verdict cases go into test/change-scope.bats, which already holds 21 cases, rather than into the new suite. - docs/testing.md becomes mandatory, not conditional: scope item 8 and acceptance box A8 both require the authoring sentence. - The companion map stays in CommandDocsCommand and the renderer takes the source path and the companion link as parameters, so the link text has one copy. No task copies a command count into code; each reads CommandRegistry. Artifacts: - phase-08-tasks.md 🤖 Generated with Claude CodeSorted all 61 LESSONS.md entries (60 headed, plus one orphan block at :605 that carries a Decision with no heading of its own). None held an APPROVED or REJECTED decision. Drop 22 status, working notes consumed by this issue's own phases, fixes already committed on this branch, or duplicates of a filed issue Follow-up 17 work rather than knowledge; one line each in project-management/issues/IW-463/FOLLOW-UPS.md Already fixed elsewhere 0 the one entry claiming it was stale:10d52e8f,407811a0,4ab6dbfband055007b5are all ancestors of HEAD since the 2026-09-05 main merge Decide 22 five walked with Michal, seventeen retained Promotions applied, each cited in its entry's Decision line: docs/launcher-performance.md new — the measured per-invocation cost of scala-cli vs a plain JVM, and which classpath to use in a release and in a dev checkout docs/testing.md new section: a green unit tier does not mean ./iw loads that code; do not run a bare ./mill while ./iw ./test is running .git-hooks/pre-commit scalafix --check moved forward from pre-push, so a mechanism built from a banned construct fails at the commit that writes it One walked entry was rejected as already delivered by docs/testing.md's "Asserting a rejection". Seventeen Decide entries keep _pending_ decisions by Michal's call: the ground they cover is already captured elsewhere. Nothing was removed from LESSONS.md. Promoted entries were rewritten into the capture shape with a Surface line naming what delivers them at the trigger. Also carries the completion-flow artifacts generated in the same session: review-packet.md for the whole feature branch, and release-notes.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxaSeven conflicts, all where main and this branch had fixed the same things. IssuePoller.close() Both bounded the wait after shutdownNow. Kept this branch's body — it also restores the interrupt flag — under the name PullRequestPoller already uses, and dropped the duplicate CloseWait the automerge left beside CloseTimeout. CaskServerTest Complementary, so both were kept: this branch stops the server before a test deletes what its pollers write; main's removeStateDir tolerates whatever a save in flight left behind. PullRequestPollerTest Import union. FakeCommandEnv.write Both a per-path failure map and a global write result now live on the fixture. The specific answer wins, then the general one. InitHarnessTest Both sides kept: this branch's four help and rejection cases, main's config read-back helpers. The four cases move onto those helpers, because main replaced the fixture's config capability. Init.scala Took this branch's call site, which already carried the Left/Right match, and dropped configPath to match main's writeConfig signature. Doctor.scala Took this branch's declaration imports, less ConfigSerializer — main routed every command's config access through ProjectConfigFile and pins it with an invariant test. Three lint violations followed from the merge rather than from either side: main widened the scalafix gate to dashboard/, so this branch's poller and server-stop tests were linted for the first time. A @volatile var in each poller test became an AtomicBoolean, and an AtomicReference seeded with null became one seeded with None, the idiom the same file already uses for startupError. main also made ScalafixGate the single definition of the lint argv. The pre-commit scalafix step added on this branch hardcoded a different string, so it now runs the same command as pre-push, CI and ./iw ./test, and ScalafixGateTest pins the pre-commit hook alongside the other three. Verified on the merged tree: ./iw ./test unit 0, ./iw ./test compile 0, scalafix --check 0, scala-cli fmt --check 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa