Skip to content

refactor(tui): adopt command shapes in utility group (FEAT-018) - #5525

Merged
Hmbown merged 10 commits into
Hmbown:mainfrom
aboimpinto:feat/FEAT-018-adopt-command-shapes-in-tui-utility-group
Aug 23, 2026
Merged

refactor(tui): adopt command shapes in utility group (FEAT-018)#5525
Hmbown merged 10 commits into
Hmbown:mainfrom
aboimpinto:feat/FEAT-018-adopt-command-shapes-in-tui-utility-group

Conversation

@aboimpinto

@aboimpinto aboimpinto commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

FEAT-018 converts the complete TUI utility command group to the external command shapes introduced by FEAT-014 and hosted by FEAT-015. The seven command files remain under codewhale-tui; this PR changes their execution boundary without physically moving them.

This PR:

  • registers /attach, /automation, /jobs, /mcp, /network, /task, and /update through RegisterCommand<CommandResult> and the existing ContextualCommand::from_contract bridge;
  • removes concrete App and executable TUI-helper access from every utility handler;
  • adds narrowly scoped presentation, atomic media-attachment, and canonical operation-digest capabilities to codewhale-command-contract;
  • keeps /jobs, /network, and /update argument-only (CommandHandler::Pure), while /attach, /automation, /mcp, and /task declare and receive only their exact minimum external facets;
  • removes utility from the migration frontier only after the whole group is portable;
  • preserves the existing registry, precedence, aliases, messages, localization, actions, persistence behavior, and safety checks.

Tracking: EPIC-005 / EPIC-006 / FEAT-018 in #5316.

No-Issue: FEAT-018 is tracked in umbrella #5316, which must remain open for the remaining decomposition FEATs.

Dependency boundary

The intended dependency direction remains acyclic:

future codewhale-commands utility group
                 |
                 v
codewhale-command-contract ---> codewhale-core / other acyclic leaf crates
                 ^
                 |
codewhale-tui implements the host facets

Utility handlers no longer name or call concrete App, TUI services, TUI state, localization enums, composer helpers, work-runtime helpers, config-persistence helpers, or other executable TUI behavior. The concrete implementations remain on the TUI-owned adapter side and expose only external trait objects to handlers. codewhale-command-contract has no dependency on codewhale-tui, enforced by the existing boundary gate.

Temporary FEAT-037 compatibility references

This PR intentionally does not move shared command outcome/action ownership. The following TUI-owned data-only references remain temporarily:

  • CommandResult in all seven utility modules;
  • AppAction in automation.rs, jobs.rs, mcp.rs, and task.rs;
  • AutomationAction in automation.rs;
  • ShellJobAction in jobs.rs;
  • McpUiAction in mcp.rs.

These types describe handler outputs; they provide no access to App or executable TUI behavior. They are therefore the bounded compatibility exception defined by FEAT-018, not a reverse executable dependency and not circular-dependency debt.

The mandatory retirement sequence is:

  1. FEAT-018 through FEAT-035 remove executable TUI dependencies while command files remain in TUI.
  2. FEAT-036 moves localization ownership behind its compatibility path.
  3. FEAT-037 moves CommandResult, AppAction, and only the proven action payload data into external ownership, retaining compatibility re-exports as needed.
  4. FEAT-016 bootstraps codewhale-commands after those ownership prerequisites are external.
  5. FEAT-038 physically moves the utility group only after the temporary data references are gone.

Keeping that ownership move out of FEAT-018 is deliberate: one FEAT remains one independently reviewable structural PR, without mixing command rewiring, shared-type relocation, and physical file movement.

Capability additions

  • CommandPresentationContext: stable translation keys with exact named replacements and safe failure behavior.
  • CommandMediaContext: validates and inserts media atomically, returning only a portable receipt.
  • CommandWorkspaceContext::operation_digest: delegates session-aware capture and canonical digest formatting to the host.
  • CommandCapabilities: each contextual registration declares its exact minimum external facet set; the dispatcher exposes only those facets, rejects empty contextual declarations, and pure handlers build no host bundle.
  • CommandContexts / ContextParts: add presentation and media slots without a command-specific envelope, host escape hatch, or second registry.

Review hardening

Commit 643b76c5f incorporates the first-production-slice review findings: exact capability declarations are enforced by the external contract; undeclared facets remain absent; missing required facets return safe command errors; malformed localized placeholder contracts fall back through the authoritative English catalog; the nine-facet comment is current; and the attachment validation rationale is retained beside the moved validation block. The same invariant is now recorded for FEAT-019 through FEAT-046 and the EPIC-006 final gate. Commit e4aad4c06 repairs the Rust 1.98 strict-Clippy diagnostics exposed after rebasing onto current main, without changing the FEAT-018 behavior or dependency boundary. The complete GitHub Actions matrix is green on this head.

Scope and behavior

  • Only groups/utility is migrated.
  • No command file moves out of codewhale-tui.
  • No shared result/action type moves in this PR.
  • No localization system extraction occurs.
  • Names, aliases, usage strings, registry order, discovery, returned actions, user messages, network redaction, attachment validation, operation-digest semantics, and bounded updater output remain unchanged.

Testing

  • cargo fmt --all -- --check
  • cargo test -p codewhale-command-contract --lib --locked — 10 passed
  • cargo test -p codewhale-tui --lib --locked — 10,865 passed, 13 ignored in the review-hardening gate
  • Focused FEAT-018 public-dispatch tests — 3 passed
  • TUI command-adapter tests — 17 passed
  • Utility-group tests — 37 passed
  • python3 scripts/test_check_command_crate_boundaries.py — 8 passed
  • python3 scripts/check-command-crate-boundaries.py
  • python3 scripts/test_check_command_migration_manifest.py — 54 passed
  • python3 scripts/check-command-migration-manifest.py --baseline-ref origin/main
  • python3 scripts/test_ci_migration_wiring.py — 11 passed
  • Strict workspace Clippy with -D warnings — 0 warnings
  • cargo test --workspace --all-features --locked — 13,074 passed in the final serialized gate
  • Release build for codewhale-cli and codewhale-tui
  • git diff --check

Checklist

  • All seven utility registrations use the portable contract path
  • Zero concrete-App utility handlers
  • Zero executable TUI dependencies in utility handlers
  • Exact least-capability declarations; undeclared facets unavailable; pure handlers build no host bundle
  • Exact temporary FEAT-037 data references documented
  • utility removed from both migration-frontier representations
  • No physical group move or shared-type ownership move
  • No manual UI verification required for this structural change

Paulo Aboim Pinto

@aboimpinto
aboimpinto marked this pull request as ready for review August 20, 2026 14:15
@aboimpinto
aboimpinto requested a review from Hmbown as a code owner August 20, 2026 14:15

@Lstarsky0 Lstarsky0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went at the preservation claim first. Bare App is gone from all seven handlers — 31 references down to 0 — while AppAction and the rest stay exactly where the body says they do. Comparing string literals outside the test modules, the messages that left the handlers land verbatim in contract.rs: Attachment not found:, Attachment is not a file:, the unsupported-type sentence, both operation-digest strings. attach_media also keeps the original check order, so "not found" still prints the pre-canonicalize path and the other two print the canonical one. /automation's --confirm parsing is byte-identical apart from the parameter swap.

One thing isn't preserved. Base rendered the MCP recommendations with chained .replace("{restart_command}", …), which tolerates a locale entry whose placeholders don't line up — you get that locale's text, possibly with a literal {restart_command} sitting in it. apply_named_replacements (contract.rs:550) instead requires the placeholder set to equal the supplied names exactly; missing, extra and duplicate all return None, and the handler then falls back to a hardcoded English string. So a translator who drops {restart_command} from mcp_recommendations_safety turns that whole line English instead of leaving a visible artifact. kimi_import_and_new_mcp_recommendations_have_complete_locale_parity (localization.rs:4548) does cover those keys, but only for non-empty and different-from-English — placeholder parity is asserted for the Automation* and Coordination* packs (:3917) and the permission messages, not for these. So nothing in CI catches the drift, and the four fallback strings are hand-copies of the en catalog with nothing holding them to it.

Second one is about the shape rather than this diff. CommandContexts makes every facet an Option, but the only production builder — CommandContextBundle::contexts() at contract.rs:648 — sets all nine unconditionally, so None never happens and each migrated handler pays for it with .expect("… facet"). Five of those now. Nothing connects what a command declares it needs to what the host actually filled in; it's convention held in place by there being exactly one builder. This is FEAT-018 of about twenty, so whatever it settles is what the rest inherit.

Small: the doc comment right above that builder still says "Owns seven facet objects" (contract.rs:630) — this PR makes it nine. And the five-line comment explaining why an image is validated at command time rather than at send time didn't travel with the block into MediaAdapter::attach_media.

@aboimpinto

aboimpinto commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for tracing both behavior preservation and the boundary shape so carefully. Addressed in 643b76c5f.

The weak full-envelope convention is now an enforced least-capability contract:

  • CommandHandler::Contextual carries a contract-owned CommandCapabilities declaration.
  • /attach receives only WORKSPACE | MEDIA; /automation and /mcp receive only PRESENTATION; /task receives only WORKSPACE.
  • /jobs, /network, and /update remain Pure, and the dispatcher does not construct a host bundle on that branch.
  • CommandContextBundle::contexts populates only declared slots. There is deliberately no public ALL shortcut.
  • Empty contextual declarations are rejected during registration, and a missing required facet returns a safe command error instead of panicking.
  • Focused tests assert every declaration, restricted envelope exposure, empty-declaration rejection, and safe missing-facet behavior.

On localization: the existing kimi_import_and_new_mcp_recommendations_have_complete_locale_parity test already compares message_placeholders(translated) with message_placeholders(english_value) for these MCP recommendation keys (localization.rs:4608-4609), so shipped complete locale packs were already guarded against placeholder drift. Even so, the runtime path is now stronger: if a selected locale entry has a malformed placeholder contract, rendering retries through the authoritative tr(Locale::En, message_id) catalog entry rather than a hand-copied fallback string.

I also updated the stale seven-facet comments to nine and restored the rationale for validating image bytes at command time beside the moved validation block.

The strengthened invariant has been added to EPIC-006 and FEAT-019 through FEAT-046 so later migration slices and physical moves preserve the same pattern. Local verification is green: contract 10/10, TUI library 10,865 passed with 13 ignored, strict workspace Clippy with zero warnings, plus boundary 8/8, migration 54/54, CI-wiring 11/11, and live architecture gates.

The temporary TUI-owned result/action data references are unchanged and remain explicitly assigned to FEAT-037 before the FEAT-038 physical utility-group move.

Paulo Aboim Pinto

Paulo Aboim Pinto added 10 commits August 22, 2026 13:38
…mand contract

- CommandPresentationContext: stable-key translation with named replacements (D3)
- CommandMediaContext + MediaAttachmentReceipt: atomic composer/media attach (D4)
- CommandWorkspaceContext::operation_digest: session-aware canonical digest (D5)
- Envelope: presentation + media optional slots with duplicate-slot asserts (D7)
- Contract tests: object safety, transport, translation failures, media atomicity, digest, duplicate-slot rejection (9 total)

Generated with Claude Code
…(D3)

Review fix: unknown translation key now fails with a generic safe error in the
contract test double and the assertion verifies the raw key is not echoed.

Generated with Claude Code
- PresentationAdapter: stable-key translation with named replacement validation and English fallback (D3)
- MediaAdapter: atomic media validation + composer insertion, portable receipt (D4)
- WorkspaceAdapter::operation_digest: session-aware canonical digest with no-active/failure semantics (D5)
- Bundle carries presentation + media adapters; construction performs no eager work (D7)
- 16 contract adapter tests green; boundary + migration gates PASS

Generated with Claude Code
Lib build flagged unused Locale import; moved to the cfg(test) module where it is used.

Generated with Claude Code
…ct registration

- /automation, /mcp: Contextual handlers consuming presentation facet for localized text (D3/D6)
- /attach: Contextual handler resolving paths via workspace facet + atomic media facet (D4/D6)
- /task: Contextual handler with workspace operation_digest for digest (D5/D6)
- /jobs, /network, /update: Pure argument-only handlers (D6)
- /network uses codewhale_config leaf APIs + reqwest::Url host parse; no TUI persistence/network_policy helpers
- mod.rs bridges all seven via ContextualCommand::from_contract; FunctionCommand removed from utility
- FEAT-015 transitional test updated: utility excluded from legacy-only assertion
- 35 utility tests + full TUI lib 10857/0 green

Generated with Claude Code
…ity dispatch

- utility removed from scripts/command-migration-topology.json frontier (topology scope immutable)
- utility removed from PENDING_GROUPS TUI projection
- Live migration gate PASS: frontier [config,core,debug,memory,plugins,project,session,skills] exact source correspondence
- Public dispatch tests: 7-entry portable inventory, pure /jobs+/update, contextual /automation+/task+/mcp+/attach+/network through public seam (Task 6.2)
- FEAT-015 transitional dead-code allowances removed (ContextualCommand legacy/command_handler/is_legacy); PENDING_GROUPS/parts get targeted test/gate allows
- Migration fixture updated to shrunk 8-group frontier; 54/54 fixtures + CI wiring 11/11 green

Generated with Claude Code
…work normalization

- MediaAdapter: video-path attach test (extension-gated, composer reference preserved)
- Network: wildcard/trailing-dot/case host normalization, URL-path rejection, hostless URL rejection, exact conflict removal tests

Generated with Claude Code
…d duration assertion)

run.metrics.duration.as_millis() > 0 flaked under saturated serial test runs:
the offline simulated loop performs real fs/tempdir work that can complete
sub-millisecond, so the millisecond threshold intermittently measured 0ms.
Aligned granularity with the per-tool assertion (as_nanos() > 0), which is
deterministic. Pre-existing on origin/main; repaired under the Boy Scout Rule.

Generated with Claude Code
Require contextual command registrations to declare their exact external capability set, expose only those facets at dispatch, and fail safely when required capabilities are absent. Pure handlers continue to bypass host context construction.\n\nHarden malformed locale placeholder handling through the authoritative English catalog and retain the attachment validation rationale requested during review.
Apply the four semantics-preserving Rust 1.98 Clippy suggestions exposed by the current-main merge. Keep RetryError concrete for its existing inspection API and document the narrowly scoped result_large_err allowance.
@aboimpinto
aboimpinto force-pushed the feat/FEAT-018-adopt-command-shapes-in-tui-utility-group branch from 9531e3e to e4aad4c Compare August 22, 2026 11:46
@aboimpinto

aboimpinto commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

CI follow-up complete. The Rust 1.98 strict-Clippy failure was repaired in e4aad4c06, and the complete matrix is now green on the rebased head: 17 successful checks, 6 expected skips, and no failed, cancelled, or pending checks. The PR reports a clean merge state.

Paulo Aboim Pinto

Lstarsky0 pushed a commit to Lstarsky0/CodeWhale that referenced this pull request Aug 23, 2026
Record the integrated contributions behind PRs Hmbown#5523, Hmbown#5524, and Hmbown#5525, and acknowledge the independently reviewed overlap in Hmbown#5530 without counting it as a duplicate merge.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@Hmbown

Hmbown commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Thank you @aboimpinto — this shipped in v0.9.11 🎉

Your FEAT-018 utility-group command-shapes work (contract-backed dispatch seam, capability facets) is in the released tag v0.9.11 (96d13a0b): the pr-5525 integration merge plus the FEAT-018 acceptance/test commits (e.g. b8c3bbf06, ccdb77909), and you're credited in the CHANGELOG ("Thanks to Paulo Aboim Pinto (@aboimpinto) for PR #5525").

It landed via the v0.9.11 release integration branch rather than this PR's merge button, so GitHub left this one open + conflicting against the released tree. Closing it as shipped — not rejected. Your EPIC-006 command-decomposition work is live and credited; thanks for driving it, and apologies this PR sat open.

@Hmbown Hmbown closed this Aug 23, 2026
@Hmbown Hmbown reopened this Aug 23, 2026
@Hmbown
Hmbown merged commit 473954f into Hmbown:main Aug 23, 2026
24 checks passed
aboimpinto pushed a commit to aboimpinto/CodeWhale that referenced this pull request Aug 25, 2026
The envelope grew to ten facets (session, model, cost, mode_policy,
system_prompt, skills, workspace, presentation, media, memory). The
stale 'seven facets' comments were the exact hygiene item flagged in the
FEAT-018 PR review (Lstarsky0, Hmbown#5525) and recorded as
FEAT-019 D11.
Hmbown pushed a commit that referenced this pull request Aug 25, 2026
The envelope grew to ten facets (session, model, cost, mode_policy,
system_prompt, skills, workspace, presentation, media, memory). The
stale 'seven facets' comments were the exact hygiene item flagged in the
FEAT-018 PR review (Lstarsky0, #5525) and recorded as
FEAT-019 D11.

(cherry picked from commit 93aa513)
timothybrush pushed a commit to timothybrush/DeepSeek-TUI that referenced this pull request Sep 3, 2026
The envelope grew to ten facets (session, model, cost, mode_policy,
system_prompt, skills, workspace, presentation, media, memory). The
stale 'seven facets' comments were the exact hygiene item flagged in the
FEAT-018 PR review (Lstarsky0, Hmbown#5525) and recorded as
FEAT-019 D11.

(cherry picked from commit 93aa513)
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.

3 participants