Skip to content

refactor(tui): adopt command shapes in plugins group (FEAT-020) - #5657

Merged
Hmbown merged 7 commits into
Hmbown:codex/v0912-integration-20260823from
aboimpinto:feat/FEAT-020-adopt-command-shapes-in-tui-plugins-group
Aug 27, 2026
Merged

refactor(tui): adopt command shapes in plugins group (FEAT-020)#5657
Hmbown merged 7 commits into
Hmbown:codex/v0912-integration-20260823from
aboimpinto:feat/FEAT-020-adopt-command-shapes-in-tui-plugins-group

Conversation

@aboimpinto

@aboimpinto aboimpinto commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

FEAT-020 converts the TUI plugins command group (/plugin, alias plugins) to the external command shapes introduced by FEAT-014 and hosted by FEAT-015, following the pattern FEAT-018 (utility) and FEAT-019 (memory) established. The command files remain under codewhale-tui; this PR changes their execution boundary without physically moving them.

Baseline: the implementation is built on the v0.9.12 integration branch (codex/v0912-integration-20260823, 96bc9e79c), where FEAT-019 landed and the plugins group gained the marketplace and Kimi managed-import verbs. Those verbs joined the FEAT-020 scope per the migration rule ("every production command then present in that group joins the FEAT-020 scope").

This PR:

  • adds the contract-owned CommandPluginContext facet with exact-minimum typed delegates: registry reads (summaries, detail, registry_diagnostics, validation_is_clean, len, is_empty, state_path, suggest), registry mutations (reload/rediscover + skill-cache, trust/enable/disable/revoke_trust), async-bridged bundle mutations returning synchronous portable receipts (install, update, uninstall, uninstall_path rollback, export, legacy_scan, Kimi managed_scan/managed_install, marketplace marketplace_state/marketplace_add/marketplace_remove/marketplace_install) and the PLUGIN = 1 << 10 capability bit with one envelope slot;
  • converts the complete /plugin dispatch, render helpers, legacy-tools scan, Kimi managed import, and marketplace operations to portable contract values — 19 structured DTOs, byte-identical output preserved;
  • registers /plugin via ContextualCommand::from_contract with the exact WORKSPACE | PRESENTATION | PLUGIN capability set (no SKILLS: skill-cache refresh and active-skill resets happen host-side in the adapter);
  • maps all plugin catalog messages to stable keys through the presentation facet (key_to_plugin_message_id), with description_key: "cmd_plugin_description" and authoritative English fallback;
  • removes plugins from both migration-frontier representations atomically;
  • preserves names, aliases, usage strings, registry order, messages, diagnostics, review tokens, network approval/denial text, and safety checks byte-for-byte.

Tracking: EPIC-006 / FEAT-020 in umbrella Hmbown/CodeWhale#5316.

Dependency boundary

The intended dependency direction remains acyclic:

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

/plugin handlers no longer name or call concrete App, PluginRegistry, crate::plugins, or Config; the plugin facet delegates to the TUI-owned services through the adapter. The capability-driven dispatcher populates only declared slots.

Review hardening (PR #5525 lessons + post-completion audit)

  • Missing required facets return Command capability unavailable: <facet> — zero .expect() in production handlers.
  • D1 fix: the Kimi content-hash-mismatch rollback previously called crate::plugins::install::uninstall directly from the portable handler; it now crosses the boundary through the new CommandPluginContext::uninstall_path facet method, and the host adapter owns the crate::plugins call.
  • Facet-count doc comments refreshed to eleven slots; preservation proven by parity tests with exact-string evidence.

Scope and behavior

  • Only groups/plugins is migrated (one production command: /plugin).
  • No command file moves out of codewhale-tui; no shared result/action ownership moves (FEAT-037); CommandResult and AppAction remain bounded temporary data references.
  • No localization system extraction occurs (FEAT-036); the presentation facet maps existing MessageIds.
  • Names, aliases, usage strings, registry order, help/palette discovery, messages, parsing, review-token semantics, marketplace/Kimi behavior, and safety checks remain unchanged.

Testing

  • cargo fmt --all -- --check
  • cargo test -p codewhale-command-contract --lib --locked — 23 passed
  • cargo test -p codewhale-tui --lib --locked plugins-scoped — 205 passed; full TUI lib 11,394 passed / 0 failed at completion
  • python3 scripts/test_check_command_crate_boundaries.py — 8 passed
  • python3 scripts/check-command-migration-manifest.py --baseline-ref origin/codex/v0912-integration-20260823 — PASS (plugins absent from both frontier representations)
  • python3 scripts/test_check_command_migration_manifest.py — 54 passed
  • Strict workspace Clippy with -D warnings — 0 warnings
  • git diff --check

Checklist

  • Only /plugin migrated; zero concrete-App / PluginRegistry / crate::plugins / Config handler dependencies
  • CommandPluginContext is the only new plugin-specific host facet; PLUGIN = 1 << 10
  • /plugin declares exactly WORKSPACE | PRESENTATION | PLUGIN; no other facets
  • Typed signatures and contract-owned result shapes for every operation
  • Existing user-facing output byte-identical (parity tests with exact-string evidence)
  • plugins removed from both migration-frontier representations
  • Rollback routed through the facet (D1); host-helper extraction tracked for the FEAT-040 move
  • Dispatcher exposes only declared facets; missing facets fail safely
  • No manual UI verification required for this structural change

CI status

The base is the codex/v0912-integration-20260823 branch (where FEAT-019 landed and the plugins
group's marketplace/Kimi verbs live); the repo's ci.yml triggers only on master/main-targeted
PRs, so the full Actions matrix does not run here. Lightweight cross-repo checks (DCO, link,
GitGuardian, contribution gate) are green, and the full matrix was verified locally with the exact
configured gates: TUI lib 11,394/0, contract 23/23, boundary 8/8 + live, migration 54/54 + live
scan, strict workspace Clippy -D warnings 0, cargo fmt --check. The matrix will run on this
branch when the v0.9.12 integration branch reaches main.

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

Paulo Aboim Pinto

Paulo Aboim Pinto added 7 commits August 27, 2026 01:34
…es to command contract

- CommandPluginContext: object-safe synchronous facet covering registry reads/mutations, async-bridged install/update/uninstall with sync receipts (D11), export, legacy scan, kimi managed import, and marketplace
- Portable DTOs: PluginSummary/Detail/Diagnostic/McpServerDetail, mutation outcome+receipt, export receipt, legacy tool+scan, managed candidate+scan, marketplace catalog/candidate/add/state, suggestion
- PLUGIN = 1 << 10 capability bit and one plugin envelope slot with with_plugin builder
- Contract tests: object safety, field/variant closure, sync receipt outcomes, exact-hash mismatch, managed/marketplace portability, envelope transport, duplicate-slot rejection, bit stability
- Contract boundary gate green; workspace compiles; fmt clean; 23/23 contract tests pass

Generated with Claude Code
…misleading receipts

Code-review finding: trust/enable/disable/revoke_trust returned a PluginMutationReceipt
with outcome always NoChange, which is semantically wrong (NoChange means 'already up
to date' in the install/update path). The host registry returns Result<(), String>;
the handler renders the action word from its own dispatch arm and re-reads detail
for post-mutation state. Return Result<(), String> — the exact-minimum typed surface.

Generated with Claude Code
…tricted exposure

- PluginAdapter implements CommandPluginContext against App: registry reads (summaries/detail/diagnostics/validation/suggest), registry mutations (trust/enable/disable/revoke with skill-cache + active-skill side effects), async-bridged install/update/uninstall with synchronous receipts (D11), export, legacy scan, kimi managed scan/install, marketplace state/add/remove/install (incl. builtin official catalog)
- CommandContextBundle grows to eleven slots with plugin; contexts() exposes plugin only for PLUGIN capability
- Portable conversion helpers: summary/detail/mcp server/diagnostic/marketplace diagnostic/mutation receipt/export receipt/legacy tool/marketplace candidate/catalog
- kimi_import: scan_managed_plugins_portable wrapper; group modules made pub(crate); plugin_network_policy/run_async exposed
- Adapter tests: host-data projection, registry mutation + suggest behavior, restricted exposure (3 tests)
- Full TUI lib suite 11395/0; boundary gate green; fmt clean

Generated with Claude Code
…acet parity

- mod.rs: portable plugins() dispatch consuming workspace/presentation/plugin facets; legacy shell builds bundle and delegates (Phase 6 replaces with from_contract)
- render.rs: render_bundle_detail/escape helpers consume portable PluginDetail + presentation facet
- legacy.rs: consumes PluginLegacyScan; kimi_import.rs: consumes PluginManagedScan; marketplace.rs: consumes PluginMarketplaceState with localized plan text
- Presentation facet: key_to_plugin_message_id maps all 52 plugin keys; source_path carried for marketplace provenance
- Contract: PluginSuggestion.state_label, PluginDetail.inventory_summary, PluginMarketplaceCatalog.source_path, reload() facet method
- Tests: 18 plugin tests converted to the portable shell path; full parity preserved
- Full TUI lib 11394/0; contract 23/23; boundary gates green

Generated with Claude Code
…nk both frontiers

- PluginsCmd implements contract RegisterCommand<CommandResult> with exact WORKSPACE | PRESENTATION | PLUGIN; PluginsCommands group registers via ContextualCommand::from_contract
- plugins_contextual destructures facets with safe missing-facet errors; transitional App shell now test-only
- Public dispatch tests: exact capability set, undeclared facets absent, public seam dispatch, no-panic matrix (3 tests)
- Remove plugins from PENDING_GROUPS and scripts/command-migration-topology.json frontier (same commit)
- Migration fixture updated for six-group frontier; feat015 legacy-assertion test adds plugin to MIGRATED
- All gates green: contract 23/23, TUI lib 11397/0, migration/boundary/CI fixtures + live gates, fmt, diff hygiene

Generated with Claude Code
- Fix clippy findings in FEAT-020 plugin files: identical if blocks (contract.rs), useless as_ref/map (marketplace.rs), useless format + redundant closure (render.rs), manual unwrap_or_default (mod.rs), collapsible if (contract tests)
- Boy Scout: repair pre-existing lints outside FEAT-020 scope (computer-use linux.rs &PathBuf->&Path, config catalog tests contains()/type_complexity)
- cargo clippy --workspace --all-targets --locked -- -D warnings exits 0 with zero warnings

Generated with Claude Code
rollback_hash_mismatch called crate::plugins::install::uninstall directly
from the portable handler, a TUI-owned executable dependency that violates
the D1 boundary and would break the FEAT-040 physical move. Add
CommandPluginContext::uninstall_path(name, plugins_dir) - a file-level
rollback removal with no registry resolution or skill side effects - and
route the content-hash-mismatch rollback through it. The host adapter owns
the crate::plugins call.

Verified: contract 23/23, plugins group 18/18, plugin-scoped TUI suite
205/0, clippy -D warnings clean.

Hmbown commented Aug 27, 2026

Copy link
Copy Markdown
Owner

Landing into codex/v0912-integration-20260823 (the v0.9.12 stack), not main.

Rebase onto origin/main is not landable: FEAT-020 is stacked on FEAT-019 (CommandMemoryContext, CommandCapabilities::MEMORY = 1<<9, PLUGIN = 1<<10). Current main still has the FEAT-018 contract (no capability bitset, no memory facet), so cherry-picking the seven commits immediately conflicts in crates/command-contract/{facets,handler,tests}.rs and would require rewriting the slot numbering.

Verified on be4626fd891e1de48db16775c81ddc6891fa9cba:

  • cargo fmt --all -- --check
  • codewhale-command-contract --lib 23/23
  • codewhale-tui --lib plugin filter 205/205
  • command-crate-boundary 8/8; migration-manifest 54/54; live scan PASS (plugins removed from both frontiers)

Merging with a merge commit to keep Paulo's authorship.

@Hmbown
Hmbown merged commit ece99d5 into Hmbown:codex/v0912-integration-20260823 Aug 27, 2026
6 checks passed
Hmbown pushed a commit that referenced this pull request Aug 29, 2026
…ntities

Twenty external contributions landed after the v0.9.11 band closed and none
were credited: the newest band in docs/CONTRIBUTORS.md was still v0.9.11 while
main shipped 0.9.12 work. Adds the v0.9.12 band covering all twenty (#5591,
#5599, #5604, #5610, #5613, #5621-#5624, #5629, #5650, #5657, #5669, #5682,
#5683, #5685, #5687, #5688, #5689, #5692) and demotes v0.9.11 so only the
newest band stays expanded.

AUTHOR_MAP gains numeric-noreply entries for musichen, M-Maciej, and serephus
so future harvested credit is graph-mappable, plus a comment recording that
wangfengcsu@qq.com (21 commits on main via #704) has no resolved login yet.

AUTHOR_MAP is a project convention consumed by scripts/check-coauthor-trailers.py
for new commits; GitHub does not read it, so it grants no retroactive
contribution-graph credit for history already on main.

No-Issue: contributor credit hygiene; no issue tracks it
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
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.

2 participants