Phase 6: One index, one truth #535

Merged
mph merged 27 commits from IW-463-phase-06 into IW-463 2026-09-05 17:54:56 +00:00
Owner

Phase 6 of IW-463. The declarations become the only place a caller can read a command fact from, and they become reachable from a terminal.

iw.core.commands.Help renders three things from CommandRegistry: the grouped index, one command's full help, and a parse rejection that names the offending flag. iw --list becomes a seven-group index of 25 commands. The comment-header scrape and all 25 // USAGE: lines are deleted.

Read this before merging

Do not squash Steps 3-5. The rollback target is a single commit:

Commit Step
a5d0ed1e classpath resolution, additive
e3a53bfc the deletions — the rollback target
61089174 the plugin/project fallback

Reverting e3a53bfc alone leaves the classpath resolution and the fallback standing but unreached. Reverting one squashed commit takes them with it and leaves the launcher unable to answer any help request.

Wait for CI / test. It is the job that packages a release and runs test/bootstrap.bats — the only proof that the classpath tier every consumer install uses works. The other checks going green does not cover it.

Accepted regression, one release cycle

iw --describe kanon/implement used to print a scraped Purpose:/Usage:. It now prints a name plus a label. Plugin and project commands have no declaration until Phase 7 gives their owners a command index. This is deliberate: keeping the scrape for plugins would keep the drift this issue exists to remove.

What the review changed

Two rounds, six reviewers. Iteration 1 found 5 critical; iteration 2, over the fixes, found 0.

The most serious: the phase as first written re-created the defect this issue exists to remove, one layer up. It moved the command roster from filesystem discovery to a hand-kept registry while leaving iw-run resolving built-ins by file existence, with no gate between them. core/test/CommandRegistryTest.scala now diffs the two sets in both directions, and reads the launcher's own name rule out of iw-run rather than restating it.

Also closed: a filename in a cloned repository (build me.scala, or report.md.scala by accident) emptied the whole command index; six assertions across the phase could not fail; and IW_HELP_CLASSPATH was accepted unvalidated into java -cp.

What this changes for everyone after it

  • test/bootstrap.bats was silently skipping all five cases — a bats skip reports as ok — so every earlier phase's green suite was green with the release path untested. It now runs, and both CI workflows package a release first, gated by IW_REQUIRE_RELEASE=1.
  • Adding a commands/*.scala file now requires a CommandRegistry.topLevel row, or core.test goes red.

Both are documented in docs/testing.md.

Still open for the reviewer

  • One unchecked task (phase-06-tasks.md:414): extending CoreJarTest/DashboardJarTest. The argument for leaving it is that the classpath choice is made in Bash, so no falsifiable assertion exists in those Scala files. Accept or reject it.
  • Two structural findings deferred to a new tracker issue and recorded in LESSONS.md: the command-name grammar exists in thirteen literal copies, two of which already disagree; and ExternalCommand, a value object with a validating constructor, sits in the presentation package.
  • LESSONS.md carries several Decision: _pending_ entries awaiting triage.

Verification

IW_REQUIRE_RELEASE=1 ./iw ./test — exit 0, 244 ok, 0 failures, 0 skips.

Latency, recorded not asserted: release --list and <cmd> --help both 0.23-0.24 s; dev checkout both 0.35-0.38 s. analysis.md's 0.09 s does not reproduce — the JVM alone costs 0.21-0.26 s. <cmd> --help fell from 0.95 s under scala-cli.

Review packet: project-management/issues/IW-463/review-packet-phase-06.md

🤖 Generated with Claude Code

https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa

Phase 6 of IW-463. The declarations become the **only** place a caller can read a command fact from, and they become reachable from a terminal. `iw.core.commands.Help` renders three things from `CommandRegistry`: the grouped index, one command's full help, and a parse rejection that names the offending flag. `iw --list` becomes a seven-group index of 25 commands. The comment-header scrape and all 25 `// USAGE:` lines are deleted. ## Read this before merging **Do not squash Steps 3-5.** The rollback target is a single commit: | Commit | Step | |---|---| | `a5d0ed1e` | classpath resolution, additive | | **`e3a53bfc`** | **the deletions — the rollback target** | | `61089174` | the plugin/project fallback | Reverting `e3a53bfc` alone leaves the classpath resolution and the fallback standing but unreached. Reverting one squashed commit takes them with it and leaves the launcher unable to answer any help request. **Wait for `CI / test`.** It is the job that packages a release and runs `test/bootstrap.bats` — the only proof that the classpath tier every consumer install uses works. The other checks going green does not cover it. ## Accepted regression, one release cycle `iw --describe kanon/implement` used to print a scraped `Purpose:`/`Usage:`. It now prints a name plus a label. Plugin and project commands have no declaration until Phase 7 gives their owners a command index. This is deliberate: keeping the scrape for plugins would keep the drift this issue exists to remove. ## What the review changed Two rounds, six reviewers. Iteration 1 found **5 critical**; iteration 2, over the fixes, found **0**. The most serious: the phase as first written re-created the defect this issue exists to remove, one layer up. It moved the command roster from filesystem discovery to a hand-kept registry while leaving `iw-run` resolving built-ins by file existence, with no gate between them. `core/test/CommandRegistryTest.scala` now diffs the two sets in both directions, and reads the launcher's own name rule out of `iw-run` rather than restating it. Also closed: a filename in a cloned repository (`build me.scala`, or `report.md.scala` by accident) emptied the whole command index; six assertions across the phase could not fail; and `IW_HELP_CLASSPATH` was accepted unvalidated into `java -cp`. ## What this changes for everyone after it - `test/bootstrap.bats` was silently skipping all five cases — a bats skip reports as `ok` — so every earlier phase's green suite was green with the release path untested. It now runs, and both CI workflows package a release first, gated by `IW_REQUIRE_RELEASE=1`. - Adding a `commands/*.scala` file now requires a `CommandRegistry.topLevel` row, or `core.test` goes red. Both are documented in `docs/testing.md`. ## Still open for the reviewer - One unchecked task (`phase-06-tasks.md:414`): extending `CoreJarTest`/`DashboardJarTest`. The argument for leaving it is that the classpath choice is made in Bash, so no falsifiable assertion exists in those Scala files. Accept or reject it. - Two structural findings deferred to a new tracker issue and recorded in `LESSONS.md`: the command-name grammar exists in thirteen literal copies, two of which already disagree; and `ExternalCommand`, a value object with a validating constructor, sits in the presentation package. - `LESSONS.md` carries several `Decision: _pending_` entries awaiting triage. ## Verification `IW_REQUIRE_RELEASE=1 ./iw ./test` — exit 0, **244 ok, 0 failures, 0 skips**. Latency, recorded not asserted: release `--list` and `<cmd> --help` both 0.23-0.24 s; dev checkout both 0.35-0.38 s. `analysis.md`'s 0.09 s does not reproduce — the JVM alone costs 0.21-0.26 s. `<cmd> --help` fell from 0.95 s under scala-cli. Review packet: `project-management/issues/IW-463/review-packet-phase-06.md` 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
mph added 25 commits 2026-09-05 16:58:11 +00:00
Records the three baselines the phase is measured against (108-line --list,
the Phase 1 placeholder answering `review-state update --help`, and the thin
core.jar failing to load any command object), the thirteen-suite BATS hit list
with its five process-level failures and its four vacuous assertions, and
Michal's three rulings of 2026-09-05.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
CommandRegistry.topLevel becomes the one list of the 25 top-level commands in
production code, with lookup by name and the ordered group walk. CommandIndex
renders it: seven headings in CommandGroup's own order, one line per top-level
command, and a long sentence wrapped under a hanging indent rather than
truncated — a truncated sentence is a second description nobody can edit at its
source.

CommandDeclarationTest is re-pointed at the registry in the same commit, per
D6.1. Its 25-entry roster becomes a side table of the two facts a declaration
cannot state (canResume, waitsOnABudget) keyed by name, guarded in both
directions: a registered command with no row fails at construction, and a row
naming no registered command fails the coverage test.

Nothing outside core/ changes; nothing is deleted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
iw.core.commands.Help resolves a name against the registry and descends through
CommandArgParser.dispatch while the declaration carries subcommands, so
`--command review-state update --help` reaches update's own declaration by
exactly the tokens that reach it when the command runs — no second resolution
rule. The walk is pure and its answer carries the declaration it reached, so the
subcommand test asserts the dispatch rather than a string the parent also
renders.

Three exit codes for the launcher to read: 0 rendered, 1 refused, 2 not a help
request after all. The last is a safety valve, reached only when a parse
consumed the help token as a flag's value.

Nothing is wired to the launcher yet, and nothing is deleted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Additive: nothing is deleted and the phase remains reversible.

mill_classpath is a separate helper beside mill_jar_path, not a widening of it —
core.runClasspath returns an array where core.jar returns a scalar, its entries
carry two prefix forms, and three of them are not jars. Nothing is filtered:
java -cp ignores a missing entry at no cost, and a filter would add one stat per
entry to every help request. A contract case pins all three facts, none of which
Mill documents.

ensure_help_classpath mirrors ensure_core_jar's three tiers, resolving the
release assembly rather than the thin build/iw-core.jar — that jar carries no
scala class at all and every command object fails to load from it.

resolve_command_target lifts describe_command's name validation, namespace
decision and file resolution, with all nine of its error messages, into one
function placed above ensure_core_jar's call site. Only describe_command's
built-in branch routes through Help; the plugin and project branches keep the
scrape until Step 5.

The five bare-temp-dir suites take IW_HELP_CLASSPATH from one shared helper that
resolves it once per bats run and caches it, so no setup() queries Mill.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
This is the phase's one irreversible commit and the target a revert aims at. It
carries nothing else: no rename, no reformat, no unrelated repair. Do NOT squash
it with the commits either side of it.

parse_command_header is deleted, with all fourteen of its call sites.
list_commands renders the index through Help --index and then names the plugin
and project commands it discovers; describe_command loses both its rendering
tails; the Phase 1 intercept is replaced, for built-in commands only, by an
exact-token pre-filter that resolves the target and hands its own argv to the
declaration's parser. Plugin and project commands keep a stop — deleting it for
them would let `iw kanon/implement --help` run kanon's command, which is the
defect the intercept exists to prevent. Step 5 gives that stop its wording.

Bare `iw` and `iw --help`/`-h` render the index instead of six lines pointing at
it. All 25 `// USAGE:` lines leave commands/*.scala, together with the
explanatory notes under three of them; every `// PURPOSE:` is untouched, per
D6.7, because the published skill build reads those lines as command summaries
until Phase 8 rewires it.

Every assertion this commit breaks is repaired in it. Three of them inverted
rather than changed wording: help-intercept case 8 (the help path now must
resolve a classpath, so its subject becomes "no scala-cli and no command run"),
issue-create's "names no per-command flags" (rendered help must now name them),
and cases 7a/7b, whose `!= *"was not run"*` halves would have gone vacuous the
moment that string left iw-run. Vacuous is worse than red, so both now assert
the absence of what the two live stops actually print.

A revert of this commit alone, after Steps 5-7 land, un-deletes
parse_command_header while the rewritten list_commands no longer calls it: dead
Bash, not a broken tool.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Deliberately a separate commit from the deletions, so the fallback's own tests
fail for one commit and are seen to fail.

Help gains --external <name>, repeated after --index and standing alone for one
command. ExternalCommand reads a name as a plugin's, a project's, or neither,
and CommandIndex owns both the index's trailing sections and the four-line
answer one external command's help request gets — so Phase 7 replaces one
renderer instead of hunting prose through Bash. Both section headings are
reproduced verbatim: eleven assertions across three suites read those two
strings, four of them negatively, and all eleven still pass unchanged.

The two fail-if-run fixtures are the guard against the one way this phase could
re-open a merged defect: `iw testplugin/loud --help` must not reach kanon-shaped
plugin code. Both cases assert the absence of the fixture's own output first, so
a regression report carries the evidence. Proved fallible by making the
pre-filter never fire: both cases then failed on the fixture's output, not on
the exit code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
iw-bootstrap's `update` branch keeps its intercept and its placeholder becomes
two lines of real prose, per D6.5(b). This is the one accepted, documented
exception to "one source of truth": `update` is answered before any release is
downloaded, so there is no jar to render from and no iw-run to delegate to, and
it has neither a command script nor a declaration.

docs/testing.md's shim example drops its `// USAGE:` line and its
`// PURPOSE: <one-line summary>` now reads as the shim's own purpose, with a
paragraph stating D6.7's rule: the declaration is the only copy of a command's
usage.

The log records D6.1-D6.11's outcomes, the four latency numbers measured end to
end through the real launcher, the plugin/project description regression that
must be named in the PR description, the corrected BATS estimate, the
six-groups-versus-seven note a reviewer reading the Gherkin will need, and that
Risks 4's --no-daemon retry is now higher priority and still not implemented.

Two lessons filed, both pending: package-release.sh's stale 0.1.0 default makes
every bootstrap.bats case skip while reporting `ok`, and the unit-tier guard
Testing (c) asks for cannot be made falsifiable — that task is left unchecked
with its reason rather than checked on a test that cannot fail.

#202 stays closed (superseded by #463 on 2026-08-27) and now carries a comment
naming the invocation that works.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
The tasks.md index flip is phase-merge every other phase. Marking it
before the pull request exists claims a state that is not true yet, and
status that rots is the thing LESSONS.md forbids. The Story 5 mapping
row, which records what the phase delivers, stays as written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Cases 7a and 7b forbade a sentence no code prints, so both passed
unconditionally. Correcting the wording was not enough: with an exact-token
pre-filter neither argv reaches resolve_command_target, so no stop wording is
reachable on any path, and Help and the command refuse an unknown flag in the
same words — absence of wording cannot say which one answered.

The pre-filter now emits a trace line, and both cases assert its absence under
IW_TRACE=1. Mutating the match to a substring turns both red on that exact
assertion; the mutation is reverted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
C1: `command_names_in` offers only names `resolve_command_target` accepts,
so the listing and the resolver agree on what a command name is, and
`Help.answer` drops an external name it cannot read instead of refusing the
whole index. Either fix alone survives `.iw/commands/build me.scala`; both
are kept because the launcher and the renderer answer the question from
different sides.

C2: `CommandRegistryTest` reconciles `CommandRegistry.topLevel` against
`commands/*.scala` in both directions, by the launcher's own name rule.

C3: the two negatives in `issue-create.bats` and `feedback.bats` now pin
strings the success paths actually print.

C4: `dashboard --help` proves the server was not started by the absence of
the `mill_jar_path dashboard.assembly` trace, not by two strings this path
cannot produce.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
C5: `scripts/package-release.sh` defaults to the VERSION file, so the
obvious invocation builds the tarball `test/bootstrap.bats` looks for;
`IW_REQUIRE_RELEASE=1` turns that suite's skip into a failure; and the test
job packages a tarball before running the suites, with the flag set. Tier 2
of `ensure_help_classpath` — the tier every consumer install uses — is now
exercised on every code run instead of by five silent skips.

W12: the mill stub moves to `$INSTALL_DIR/mill`, the only mill the launcher
runs, so its marker can be created and the two absence assertions can fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
W1: `CommandRegistry.byGroup` and its two tests are gone — a second grouping
walk with a different ordering rule, read by nothing in production.
W2: `CommandIndexTest` reads `Help.indexEntries` instead of rebuilding it.
W3: `help-intercept.bats` asserts the `[iw-run] help_intercept` trace is
present for an exact `--help` token, so cases 7a and 7b's absences mean
something.
W4: `IW_HELP_CLASSPATH` is held to the bar its two siblings apply — every
entry an absolute path to a .jar or a directory of classes.
W6: the unreachable `exit 0` after `list_commands` is gone.
W7: the four flag assertions pin `name + does`, which only `flagsSection`
emits, instead of a string the usage line already carries.
W11: the shared classpath helper always resolves through Mill, so no run can
render help from a stale jar.
W13: the write-only `TARGET_PLUGIN` is gone.

Also: the inert `// USAGE:` lines are dropped from the fixtures whose tests
do not read them, `namesUnder` takes the render it reads, `docs/testing.md`
states the shim's one-line PURPOSE exception, and the help path guards on
`command -v java` as the launcher already does for scala-cli.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
W5: ten comments now describe the code as it is. Every load-bearing fact is
kept — the heading strings eleven assertions read, the eager-list
measurement, the reason the bats classpath is cached — and only the account
of what used to be there is dropped.

W14: the three comments dated to "Phase 7" state the condition instead: an
external command has no declaration because its owner ships no command
index. One greppable `TODO(IW-463)` in `CommandIndex.fallback` carries the
pointer. The three bats comments carrying the same date are rewritten too.

W8: `Answer.NotAHelpRequest` IS reachable from the launcher, contrary to the
review's reading. `CommandArgParser.looksLikeFlag` recognises a `--` prefix
only, so `-h` is a legal value for a flag: `iw phase-commit --title -h`
fires the pre-filter, leaves Help with exit 2, and dispatches the command.
The exit-2 arm stays; the comment and the test that read as coverage of it
now use argv the launcher can produce.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
The same CLAUDE.md rule as 4ab6dbfb, in three bats comments the review did
not list. Each states what the case pins now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
test/bootstrap.bats is the only suite that exercises the release tier of
ensure_help_classpath. An inherited IW_HELP_CLASSPATH sent all five cases
down tier 1 instead, so setup() clears it.

export_help_classpath now requires BATS_RUN_TMPDIR rather than falling back
to a fixed path that would outlive the run and serve a stale classpath.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Help exits 2 when a help token turns out to be a flag's value. Nothing
proved iw-run acted on it: replacing the guard with an unconditional
exit left the whole suite green. Case 7d dispatches 'phase-commit
--title -h' and reads the command's own refusal and exit code.

ensure_help_classpath's three-tier block sat above help_classpath_is_usable,
which left the predicate carrying a contract it does not have and
ensure_help_classpath undocumented. Each function now carries its own block.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
The roster gate restated iw-run's command-name regex in Scala, so a
loosened rule in the launcher would leave the gate passing against its
own stricter copy. CommandRegistryTest now reads the rule out of
command_names_in.

CommandIndex.render keeps input order, so a group's rendered sequence is
the hand-typed order of CommandRegistry.topLevel. One assertion pins the
Phase lifecycle group's sequence.

An assert in HelpTest ANDed two conditions under one message; split so a
failure names which half broke.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
package-release.sh took an unvalidated version into rm -rf and mkdir -p,
so a '/..' sequence walked out of release/. It now requires X.Y.Z, the
same rule release-publish.sh applies.

release.yml ran ./iw ./test with no tarball and no IW_REQUIRE_RELEASE, so
the job that cuts the release skipped all five bootstrap cases and
reported ok. It packages from VERSION first — the name test/bootstrap.bats
builds — and requires the tarball. The tag-derived packaging step is
unchanged.

The ci.yml packaging step gets the 15-minute budget every other build step
in the file has.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
docs/testing.md now covers IW_REQUIRE_RELEASE=1 (what it does, that both
workflows set it, that packaging runs first) and the commands/-to-registry
roster gate.

The dev-gate suite's --help case named VITE_DEV_URL as its subject, but the
variable is read only inside the dashboard JVM, which the help path never
starts. The assertion it made is already made by dashboard-jar-launch.bats
and help-intercept.bats, and it paid a dashboard.assembly build for it.

Three comments narrated history; two credited a negative assertion with
evidence the exit code and usage line actually carry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
The test runner is an iw command, so a BATS suite it starts inherits
IW_CORE_JAR and the launcher makes no Mill query. The trace assertion
passed standalone and failed under ./iw ./test. The command's own refusal
and exit code prove the fall-through in either environment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
Iteration 1: 5 critical, 14 warnings. Iteration 2 over the fixes: 0
critical, 9 warnings, all closed.

The finding worth keeping: the phase as first written re-created the
defect this issue exists to remove, one layer up. It moved the roster from
filesystem discovery to a hand-kept registry and left the launcher
resolving by file existence, with no gate. That is now gated, and the gate
reads the launcher's own rule rather than restating it.

Six assertions across the phase could not fail. Two silent coverage holes
closed, one of them created by the first fix pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WehHdHp9MRKJkrhGFiKNxa
One index, one truth
All checks were successful
CI / changes (pull_request) Successful in 2s
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 1m20s
CI / dashboard-build (pull_request) Successful in 1m2s
CI / test (pull_request) Successful in 10m40s
414268f153
- iw.core.commands.Help renders the grouped index, one command's help, and a parse rejection, from the declarations alone
- the comment-header scrape and all 25 // USAGE: lines are deleted; e3a53bfc is the rollback target and Steps 3-5 must not be squashed
- CommandRegistryTest gates the registry against commands/ in both directions, reading the launcher's own name rule out of iw-run
- a filename in a cloned repository can no longer empty the command index
- test/bootstrap.bats had been skipping all five cases; it now runs, and both CI workflows package a release first
- plugin and project commands degrade to name-only until Phase 7 restores rich descriptions
chore(IW-463): update review-state for phase 06
All checks were successful
CI / changes (pull_request) Successful in 2s
CI / contract (pull_request) Has been skipped
CI / format (pull_request) Successful in 7s
CI / lint (pull_request) Successful in 11s
CI / test (pull_request) Successful in 10m42s
CI / compile (pull_request) Successful in 1m16s
CI / dashboard-build (pull_request) Successful in 1m3s
05b9ac0308
chore(IW-463): update review-state for phase 06
All checks were successful
CI / changes (pull_request) Successful in 2s
CI / format (pull_request) Successful in 7s
CI / lint (pull_request) Successful in 7s
CI / contract (pull_request) Has been skipped
CI / compile (pull_request) Successful in 1m16s
CI / dashboard-build (pull_request) Successful in 1m2s
CI / test (pull_request) Successful in 10m32s
9c99d6b5ea
mph merged commit dcbe0bcd96 into IW-463 2026-09-05 17:54:56 +00:00
mph deleted branch IW-463-phase-06 2026-09-05 17:54:56 +00:00
Sign in to join this conversation.
No description provided.