Skip to content

refactor(workspace): Phase 6/7 scaffold + AppState/Tool trait extraction (#75 #76) - #86

Merged
Crsei merged 6 commits into
Crsei:rust-litefrom
yaohaowei0914:claude/modest-morse-592f2d
Apr 21, 2026
Merged

refactor(workspace): Phase 6/7 scaffold + AppState/Tool trait extraction (#75 #76)#86
Crsei merged 6 commits into
Crsei:rust-litefrom
yaohaowei0914:claude/modest-morse-592f2d

Conversation

@yaohaowei0914

Copy link
Copy Markdown
Contributor

Summary

Lands Phase 6/7 of the workspace split (issues #75, #76) in five incremental commits:

  1. Scaffold + cycle-breaking (6cb15c0) — publishes nine new workspace crates (cc-engine, cc-query, cc-tools, cc-lsp-service, cc-plugins, cc-teams, cc-daemon, cc-commands, cc-ipc) as stubs wired into the workspace; eliminates five remaining architectural cycles by moving the hook trait plumbing, background-agents types, agent IPC types, LSP shared types, and ToolUseContext::bg_agent_tx wiring to cc-types.
  2. TeamContext to cc-types (dfff0df) — pure-data TeamContext / TeammateInfo land in cc-types so AppState stops reaching into crate::teams::types::*.
  3. status_line to cc-engine (f3225dc) — ~1k LOC of src/ui/status_line/ moves to cc-engine/src/status_line/. Two root-crate back-edges (engine::output_style::resolve, tools::worktree::get_current_worktree_session) inverted via pre-resolved fields on StatusLineSnapshot.
  4. AppState + Tool trait + types/config to cc-engine (8ebfd4c) — the core Phase 6 goal: Tool trait and ToolUseContext finally leave the root bin crate. Every downstream Phase 6/7 crate can now import them via cc_engine::types::tool.
  5. Benchmark cleanup (db60cdd) — removes a stray empty file created during incremental-build benchmarking.

Why

Without moving AppState and the Tool trait out of the root bin crate, Phase 6/7 physical source moves are blocked — cc-tools, cc-plugins, cc-teams, cc-daemon, cc-commands, cc-ipc, cc-lsp-service all need Tool and ToolUseContext from somewhere other than the root bin crate they'd be pulled out of.

The chosen solution puts AppState + Tool trait in cc-engine (rather than abstracting them behind trait objects in cc-types) because both transitively hold runtime handles (StatusLineRunner, KeybindingRegistry) that don't belong in the leaf-types crate. cc-engine is their semantic home — it already owns QueryEngine and the new status_line submodule.

Key architectural notes

  • cc-types dep graph unchanged — cc-types stays a pure-ish leaf. The new teams, agent_channel, agent_events, agent_types, background_agents modules only add plain-data structs/enums plus a couple tokio-backed mpsc channel types.
  • cc-engine dep graph — depends on cc-types, cc-config, cc-bootstrap, cc-compact, cc-keybindings. Zero cycles; all deps are already-extracted sibling crates.
  • Back-compat shim — root src/types/mod.rs and src/ui/mod.rs re-export the moved modules so the ~200+ existing crate::types::{app_state, tool, config}::* and crate::ui::status_line::* call sites compile unchanged.

Incremental build speed (measured)

Against the design-doc baseline of 7.5s after a main.rs touch:

Touch point Before (doc baseline) After
main.rs 7.5s 5.96s
Root tool file (tools/fs/file_read.rs) ~7.5s 7.21s
cc-engine/src/status_line/runner.rs n/a 7.01s
cc-engine/src/types/tool.rs n/a 6.76s
cc-types leaf file n/a 8.38s
No-op 0.5–1.5s

~20% win on main.rs touch already. The full <3s target arrives when src/tools/ (~16.8k LOC) physically moves into cc-tools — documented in the follow-up section of the design doc.

Phase 6 remaining

Documented in docs/superpowers/specs/2026-04-20-workspace-split-design.md:

  • Move src/engine/ lifecycle code into cc-engine (the bulk; ~5k LOC)
  • Move src/query/ into cc-engine (or independent cc-query depending on cc-engine; 2.3k LOC)
  • Move src/tools/ minus the cycle-causing files into cc-tools (16.8k LOC — biggest single-step win)
  • Move src/lsp_service/ plus tools/lsp.rs into cc-lsp-service
  • Phase 7 crates (commands, teams, daemon, plugins, ipc)

Test plan

  • cargo check --all-targets clean (only the 2 pre-existing web/handlers.rs dead-field warnings)
  • cargo test -p cc-types: 4/4 passing
  • cargo test -p cc-engine: 12/12 passing (status_line payload + runner)
  • cargo test --bin claude-code-rs -- types: 18/18 passing
  • cargo test --bin claude-code-rs -- engine query: 181/181 passing
  • cargo test --bin claude-code-rs -- statusline: 7/7 passing
  • cargo test --bin claude-code-rs -- ipc::sdk_mapper: 5/5 passing
  • cargo test --bin claude-code-rs teams -- --test-threads=1: 58/58 passing
  • Full --test-threads=1 run: only pre-existing cc-observability::sink::off_mode_skips_redaction flaky failure (fails on HEAD too; unrelated)

🤖 Generated with Claude Code

Crsei and others added 5 commits April 21, 2026 08:27
…rsei#76)

Lands the Phase 6 / Phase 7 crate scaffold and the last cycle-breaking
work needed before the physical source move.

## Scaffold (9 new workspace members)

Phase 6 (issue Crsei#75):
- cc-engine      target for src/engine/
- cc-query       target for src/query/
- cc-tools       target for src/tools/ (minus lsp, send_message, team_spawn, system_status)
- cc-lsp-service target for src/lsp_service/ + tools/lsp.rs

Phase 7 (issue Crsei#76):
- cc-plugins     target for src/plugins/
- cc-teams       target for src/teams/ (+ tools/send_message.rs, tools/team_spawn.rs)
- cc-daemon      target for src/daemon/
- cc-commands    target for src/commands/
- cc-ipc         target for src/ipc/ (+ tools/system_status.rs)

Each crate publishes a stub lib.rs documenting its target scope and the
remaining blockers. Description comments note which cycle-causing tool
files travel with which crate when the source move lands.

## Cycle-breaking (done in this PR)

1. query -> tools: eliminated. The query loop now drives hooks via the
   cc_types::hooks::HookRunner trait through a new QueryDeps::hook_runner()
   accessor. HookRunner gained a run_stop_hooks method, implemented by
   NoopHookRunner (cc-types) and ShellHookRunner (root crate's tools::hooks).

2. query -> tools (background_agents): eliminated. CompletedBackgroundAgent
   and PendingBackgroundResults moved from tools/background_agents.rs to
   cc-types::background_agents. Root tools/background_agents.rs is now a
   thin re-export so existing call sites compile unchanged.

3. engine -> ipc (agent events): eliminated. The agent IPC type trio
   moved from src/ipc/ to cc-types:
     agent_types   -> cc-types::agent_types   (AgentNode, AgentInfo, TeamMemberInfo)
     agent_events  -> cc-types::agent_events  (AgentEvent, TeamEvent, commands)
     agent_channel -> cc-types::agent_channel (AgentSender, AgentIpcEvent)
   Root src/ipc/agent_*.rs are now thin re-exports. The engine
   sdk_to_agent_event helper now imports only from cc-types.

4. types -> ipc: eliminated from the ToolUseContext surface.
   ToolUseContext::bg_agent_tx is now
     Option<cc_types::agent_channel::AgentSender>
   instead of
     Option<crate::ipc::agent_channel::AgentSender>.

5. tools::lsp <-> lsp_service: eliminated. The three LSP shared types
   (HoverInfo, SourceLocation, SymbolInfo) moved from tools/lsp.rs to a
   new lsp_service/types.rs. tools/lsp.rs re-exports them. Next pass
   moves tools/lsp.rs itself into cc-lsp-service, making the crate fully
   self-contained.

## Remaining before the source move (documented in design doc)

- Hoist types/tool.rs (Tool trait, ToolUseContext) to cc-types. Blocker:
  ToolUseContext references AppState, which reaches into teams/ui.
  Either (a) abstract AppState behind a trait, or (b) move TeamContext
  and StatusLineRunner to cc-types first.
- Physically move src/engine/, src/query/, src/tools/, src/lsp_service/
  into their respective crates and rewire imports (follow-up PR).
- Move cycle-causing tool files to their natural homes:
    tools/lsp.rs           -> cc-lsp-service
    tools/send_message.rs  -> cc-teams
    tools/team_spawn.rs    -> cc-teams
    tools/system_status.rs -> cc-ipc

See docs/superpowers/specs/2026-04-20-workspace-split-design.md for the
full plan and remaining work breakdown.

## Verification

- cargo check --workspace: clean (2 pre-existing warnings, unrelated).
- cargo test --lib -p cc-types: 2/2 passing.
- cargo test --lib --bin claude-code-rs ipc::agent: 8/8 passing.
- cargo test --lib --bin claude-code-rs query: 35/35 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…i#75)

Removes the `types/app_state.rs -> teams::types::*` edge — the last
run-time-free leaf dependency blocking `AppState` from moving into cc-types
(which in turn blocks Phase 6 `types/tool.rs` extraction).

## What moves

Only the two pure data types `AppState` actually consumes:

- `TeamContext` (team_name, paths, self-identity, teammates map)
- `TeammateInfo` (per-teammate runtime info)

Both are plain field bags with no internal deps beyond `String` /
`Option` / `HashMap`. They now live in `cc-types/src/teams.rs`.

## What stays in root

Everything else in `src/teams/types.rs` — `TeamFile`, `TeamMember`,
`BackendType`, `TeammateMessage`, `TeammateSpawnConfig`, `TeammateSpawnResult`,
`TaskStatus`, `TeammateIdentity`, `InProcessTeammateTaskState`, `IdleReason`,
`SystemPromptMode` — is used only by the team runtime modules (backend,
mailbox, runner, protocol, in_process). Moving them here would pull
`tokio::task::AbortHandle` and team-specific impls into cc-types, which
violates the leaf-crate invariant. They'll travel with the full
`src/teams/` tree into cc-teams in Phase 7.

## Backward compatibility

`src/teams/types.rs` keeps `pub use cc_types::teams::{TeamContext,
TeammateInfo};` re-exports, so the ~7 call sites (`commands/agents_cmd.rs`,
`commands/team_cmd.rs`, `teams/identity.rs`, `tools/send_message.rs`,
`tools/team_spawn.rs`) compile unchanged.

`types/app_state.rs` switches from `crate::teams::types::TeamContext` to
`cc_types::teams::TeamContext` directly, breaking the paper dependency.

## Verification

- `cargo check --workspace`: clean (2 pre-existing warnings unrelated).
- `cargo test -p cc-types`: 4/4 passing (added 2 new teams tests).
- `cargo test --bin claude-code-rs teams -- --test-threads=1`: 58/58
  passing.
- Parallel test-runner interference on 5 mailbox/helpers tests is
  pre-existing (shared `~/.cc-rust/teams/` paths); not caused by this
  change.

Next step: abstract `StatusLineRunner` behind a `StatusLineHandle` trait
in cc-types so `AppState` itself can move.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First real source move of Phase 6: the scriptable status-line payload
and runner (~1k LOC across 3 files) leave the root crate and land in
`cc-engine`. This is the lightest-weight step toward moving `AppState`
out of root, since `AppState::status_line_runner: StatusLineRunner`
will now resolve to `cc_engine::status_line::StatusLineRunner`.

## What moves

- `src/ui/status_line/{mod.rs, payload.rs, runner.rs}` -> `crates/cc-engine/src/status_line/`

## Decoupling the two root-crate back-edges

The payload used to call into the root crate for two things:

- `crate::engine::output_style::resolve(style, cwd)` -> canonical style name
- `crate::tools::worktree::get_current_worktree_session()` -> active worktree

Both live deep in the root crate and can't follow status_line yet. Rather
than introduce callback trait objects, the contract now inverts: callers
pre-resolve the two values and pass them into `StatusLineSnapshot`:

- removed fields: `output_style: Option<&'a str>`
- added fields:   `resolved_output_style_name: Option<String>`,
                  `worktree: Option<WorktreeStatus>`

A tiny helper module `src/ui/status_line_resolver.rs` in the root crate
does the resolution once per call site, so each of the three call sites
(commands/statusline_cmd, ipc/sdk_mapper, ui/app) just plugs it in.

## Call-site compatibility

`src/ui/mod.rs` re-exports the module (`pub use cc_engine::status_line;`),
so existing `crate::ui::status_line::*` import paths keep compiling
without touching the ~5 consumer files.

## Dependency envelope (cc-engine Cargo.toml)

tokio, futures, async-trait, serde, serde_json, anyhow, tracing, chrono,
uuid, parking_lot, git2, cc-types, cc-config, cc-bootstrap, cc-compact,
cc-keybindings. All already-extracted workspace siblings — no cycle.

## Verification

- `cargo check --all-targets`: clean (2 pre-existing warnings).
- `cargo test -p cc-engine`: 12/12 passing (status_line payload + runner).
- `cargo test --bin claude-code-rs statusline`: 7/7 passing.
- `cargo test --bin claude-code-rs ipc::sdk_mapper`: 5/5 passing.

Next step: move `types/{tool, app_state, config}.rs` into cc-engine so
the Tool trait leaves the root crate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rsei#75)

The last runtime-adjacent type files finally leave the root crate:

- `src/types/app_state.rs` -> `crates/cc-engine/src/types/app_state.rs`
- `src/types/tool.rs`      -> `crates/cc-engine/src/types/tool.rs`
- `src/types/config.rs`    -> `crates/cc-engine/src/types/config.rs`

With the `Tool` trait and `ToolUseContext` now in cc-engine, Phase 6's
`cc-tools` can finally import them from a workspace crate rather than
from the root bin crate — the core architectural goal of the phase.

## Why cc-engine, not cc-types

`AppState` transitively holds runtime handles (`StatusLineRunner` in
cc-engine, `KeybindingRegistry` in cc-keybindings) and `ToolUseContext`
exposes `get_app_state: Arc<dyn Fn() -> AppState>` — both pull in things
that cc-types (a pure leaf) deliberately refuses to carry. cc-engine is
the natural home: it's where `QueryEngine` lives and it already owns the
status-line runner after the previous commit.

## Import rewiring inside the moved files

- `use super::message::{...}`                -> `use cc_types::message::{...}`
- `crate::keybindings::KeybindingRegistry`   -> `cc_keybindings::KeybindingRegistry`
- `crate::ui::status_line::StatusLineRunner` -> `crate::status_line::StatusLineRunner`
  (since the files now live in cc-engine, `crate` means cc-engine)

`super::tool::*` and `super::app_state::*` work unchanged — the modules
are siblings in `cc-engine/src/types/`.

## Back-compat shim

Root `src/types/mod.rs` becomes a re-export:

    pub use cc_engine::types::{app_state, config, tool};
    pub use cc_types::{message, state, transitions};

So every `crate::types::{app_state, tool, config}::*` path across the
root crate still resolves. No call sites changed.

## Verification

- `cargo check --all-targets`: clean (2 pre-existing warnings).
- `cargo test -p cc-types`: 4/4 passing.
- `cargo test -p cc-engine`: 12/12 passing.
- `cargo test --bin claude-code-rs -- types`: 18/18 passing.
- `cargo test --bin claude-code-rs -- engine query`: 181/181 passing.
- Full `--test-threads=1` run: only the pre-existing
  `sink::tests::off_mode_skips_redaction` in cc-observability fails
  (flaky on HEAD too, unrelated).

## What this unblocks

- `cc-tools`, `cc-plugins`, `cc-teams`, `cc-daemon`, `cc-commands`,
  `cc-ipc`, `cc-lsp-service` can all now import the `Tool` trait and
  `ToolUseContext` via `cc_engine::types::tool`, paving the way for the
  full Phase 6/7 source moves.
- Next: move `src/engine/` and `src/query/` into cc-engine (the bulk of
  Phase 6) — these are lifecycle code, not just types.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Accidentally created during incremental-build benchmarking in
F:/AIclassmanager/cc/rust/.claude/worktrees/modest-morse-592f2d
when I mistook the file's canonical path (real file lives at
tools/fs/file_read.rs). The stray file had no module declaration
referencing it so it was dead weight; deleting it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Integrates the fork-agent infrastructure and ancillary command/IPC
updates that landed on rust-lite after this branch opened:

- PR Crsei#85: fork-agent infra + /btw /simplify /advisor (Crsei#33 Crsei#37 Crsei#62)
- PR Crsei#87: MCP/plugin/reload-plugins/ide/reload-plugins commands (Crsei#41 Crsei#44 Crsei#47 Crsei#49)
- PR Crsei#88: /loop /schedule /team-onboarding (Crsei#43 Crsei#58 Crsei#60 Crsei#63)

## Conflict resolution

Git's recursive merge handled every hunk on its own — each of the five
files flagged as overlapping had non-overlapping line ranges between
my workspace-split edits and rust-lite's feature edits:

- `crates/cc-engine/src/types/app_state.rs` — my move to cc-engine vs.
  advisor/fork-related AppState fields. Clean auto-merge.
- `crates/claude-code-rs/src/engine/agent/mod.rs` — my `cc_types::agent_*`
  import rewrites vs. rust-lite's `pub mod fork;` addition. Clean.
- `crates/claude-code-rs/src/engine/lifecycle/deps.rs` — my
  `cc_types::background_agents` retyping and `hook_runner()` impl vs.
  rust-lite's `advisor_model` stripping logic. Clean.
- `crates/claude-code-rs/src/query/deps.rs` — my `hook_runner()` /
  `drain_background_results` signature changes vs. rust-lite's new
  `ModelCallParams::advisor_model` field. Clean.
- `crates/claude-code-rs/src/query/loop_impl.rs` — my hook-runner-trait
  rewiring vs. rust-lite's advisor model plumbing. Clean.

All 40+ other touched files (api/*, commands/*, ide/*, ipc/*,
services/*, etc.) merged without conflict — they're in regions my
branch didn't touch.

## Verification

- `cargo check --workspace`: clean (2 pre-existing warnings).
- `cargo test -p cc-types`: 4/4 passing (teams + background_agents).
- `cargo test -p cc-engine`: status_line + types suite unchanged.
- `cargo test --bin claude-code-rs engine::agent query::loop_impl`:
  69/69 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Crsei
Crsei merged commit 0d3b75c into Crsei:rust-lite Apr 21, 2026
0 of 3 checks passed
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