Skip to content

[workspace-split] P2: extract cc-bootstrap, cc-auth, cc-skills leaf crates - #81

Merged
Crsei merged 1 commit into
Crsei:rust-litefrom
yaohaowei0914:claude/fervent-benz-0c2ffa
Apr 21, 2026
Merged

[workspace-split] P2: extract cc-bootstrap, cc-auth, cc-skills leaf crates#81
Crsei merged 1 commit into
Crsei:rust-litefrom
yaohaowei0914:claude/fervent-benz-0c2ffa

Conversation

@yaohaowei0914

Copy link
Copy Markdown
Contributor

Closes #71. Follow-up to #80 (P1.3). Part of #68.

Summary

Three Phase-2 extractions in one commit — each builds + tests green at
the boundary, and the root crate keeps every crate::{auth, bootstrap, skills}::… path working via use cc_X as X; aliases (the pattern #79
established for cc-keybindings and cc-observability).

cc-bootstrap — 7 files, 922 LOC (clean leaf)

src/bootstrap/{diagnostics,ids,mod,model,signal,state,timing}.rs
crates/cc-bootstrap/src/…. Pure git mv; no dependency injection
needed — the directory was already a true leaf per its own module doc.

Direct deps: serde, parking_lot, tokio, uuid (+ serde_json dev).

cc-auth — 8 files, ~1.5k LOC (one tie broken)

src/auth/{api_key,codex_cli,mod,token}.rs and
src/auth/oauth/{mod,client,config,pkce}.rscrates/cc-auth/src/….

The one reverse-dep was token.rs:9 calling
crate::config::paths::credentials_path(). Broken via a global init
registered from the host at process startup (mirrors the
set_event_sender pattern that mcp/plugins/lsp already use):

// In cc-auth/src/lib.rs
pub fn set_credentials_path(path: PathBuf);

// In main.rs — first thing after load_env_files(), before any fast path
cc_auth::set_credentials_path(crate::config::paths::credentials_path());

credentials_path() falls back to
{CC_RUST_HOME | ~/.cc-rust | \$TMP/cc-rust}/credentials.json when the host
hasn't registered one, so the 10 unit tests that exercise resolve_auth
directly (doctor, logout, voice_cmd) keep passing without a custom
setup. That duplicates ~10 LOC from config::paths::data_root — a small
price for a zero-cycle extraction.

Keychain service name stays \"cc-rust\" (the explicit acceptance
criterion on #71). OAuth scopes, PKCE, token-refresh flow are all
byte-identical — only storage-path resolution changed.

Direct deps: anyhow, serde, serde_json, chrono, tokio,
tracing, reqwest, keyring, base64, rand, sha2, dirs,
parking_lot, urlencoding (+ tempfile dev).

cc-skills — 3 files, ~1k LOC (two ties broken)

src/skills/{bundled,loader,mod}.rscrates/cc-skills/src/….

Tie #1 — event emission. emit_event held a
broadcast::Sender<crate::ipc::subsystem_events::SubsystemEvent>, which
is a cycle the moment skills leaves the root crate. Replaced with a
minimal cc-skills-owned enum + callback:

// cc-skills
pub enum SkillSubsystemEvent { SkillsLoaded { count: usize } }
pub fn set_event_callback<F: Fn(SkillSubsystemEvent) + Send + Sync + 'static>(cb: F);

// Root crate — ipc/runtime.rs adapts it back into its own SubsystemEvent
let skills_tx = event_bus.sender();
crate::skills::set_event_callback(move |e| {
    let adapted = match e {
        SkillSubsystemEvent::SkillsLoaded { count } =>
            SubsystemEvent::Skill(SkillEvent::SkillsLoaded { count }),
    };
    let _ = skills_tx.send(adapted);
});

Tie #2 — user-skills directory. init_skills used to resolve
crate::config::paths::skills_dir_global() internally. Now takes it as
the first parameter; both call sites (main.rs:258 and
ipc::subsystem_handlers.rs:163) pass
&crate::config::paths::skills_dir_global() in.

Direct deps: serde, parking_lot only — the tokio::sync::broadcast
dep fell out when the event API switched to a plain callback.

Verification

  • cargo build (dev + release): ok, 2 warnings (pre-existing
    web::handlers::session_id dead_code, identical on rust-lite@ab8a2fc).

  • cargo test --bin claude-code-rs --offline -- --test-threads=1
    : 1713 / 1713 pass. Baseline from [workspace-split] P1.3: extract cc-types leaf crate (partial) #80 was 1794 — the 81-test drop
    is the tests that moved out with their code (28 cc-bootstrap +
    32 cc-auth + 21 cc-skills = 81, matches exactly).

  • cargo test --workspace --lib --offline --no-fail-fast -- --test-threads=1:

    Crate Result
    cc-auth 32 / 34 pass (2 ignored, 0 failed)
    cc-bootstrap 28 / 28 pass
    cc-keybindings 51 / 51 pass (unchanged from P1)
    cc-observability 10 / 11 pass (pre-existing off_mode_skips_redaction flake, [workspace-split] Phase 0 + 1 (partial): Workspace skeleton, leaf extractions, CI adaptations #79)
    cc-skills 21 / 21 pass
    cc-types 0 tests
  • Smoke: claude-code-rs --version prints claude-code-rs 0.1.0.

Phase 2 checklist (#71)

  • crates/cc-bootstrap/ created
  • crates/cc-auth/ created
  • crates/cc-skills/ created
  • src/{bootstrap,auth,skills}/ removed from root crate
  • Keychain service name unchanged (\"cc-rust\") — explicit [workspace-split] Phase 2: Extract leaf crates (auth, bootstrap, skills) #71
    acceptance criterion, no regression.
  • All tests pass (1713 bin + 142 extracted-crate; one pre-existing
    cc-observability flake unchanged).
  • Manual OAuth login E2E (/login 2 + /login-code) — not
    reproducible in CI, flagged for maintainer verification. The only
    auth-flow change is storage-path resolution, which is exercised
    by the save/load round-trip test in cc-auth::token::tests.

Follow-ups (out of scope)

Test plan

  • cargo build (dev + release)
  • cargo test --bin claude-code-rs --offline -- --test-threads=1
  • cargo test --workspace --lib
  • claude-code-rs --version smoke test
  • CI across Linux / Windows (left to the workflow)
  • Maintainer: manual /login 2 / /login-code round-trip on a
    clean profile.

🤖 Generated with Claude Code

… crates

Three Phase-2 extractions in one commit; each builds + tests green at
the boundary, and the root crate keeps every `crate::{auth,bootstrap,
skills}::…` path working via `use cc_X as X;` aliases at the crate root
(same pattern Crsei#79 used for `cc-keybindings` and `cc-observability`).

cc-bootstrap — 7 files, 922 LOC (clean leaf)
-------------------------------------------

`src/bootstrap/{diagnostics,ids,mod,model,signal,state,timing}.rs` →
`crates/cc-bootstrap/src/…` with a pure `git mv`. No dependency-injection
needed: `bootstrap/` was already a true import-DAG leaf per its own
module doc. Direct deps: `serde`, `parking_lot`, `tokio`, `uuid` (+
`serde_json` for the round-trip test in `ids.rs`).

cc-auth — 8 files, ~1,500 LOC (one tie broken)
---------------------------------------------

`src/auth/{api_key,codex_cli,mod,token}.rs` and
`src/auth/oauth/{mod,client,config,pkce}.rs` → `crates/cc-auth/src/…`.

The one reverse-dep was `token.rs:9` calling
`crate::config::paths::credentials_path()`. Broken by a global init
registered from the host at process startup (mirrors the `set_event_sender`
pattern already used by mcp/plugins/lsp/skills):

- `cc_auth::set_credentials_path(PathBuf)` — host calls this at the top
  of `fn main()` with `crate::config::paths::credentials_path()`.
- `token::token_file_path()` reads it back via a private
  `crate::credentials_path()` helper.
- Falls back to `{CC_RUST_HOME | ~/.cc-rust | $TMP/cc-rust}/credentials.json`
  when the host hasn't registered one, so unit tests that exercise
  `resolve_auth()` directly (doctor, logout, voice_cmd — 10 tests) keep
  passing. The fallback duplicates ~10 LOC from `config::paths::data_root`
  — a small price for full decoupling.

Keychain service name stays `"cc-rust"` (the original acceptance
criterion on Crsei#71). OAuth scopes, PKCE, and token-refresh flow are
untouched — only the storage path resolution changed.

Direct deps: `anyhow`, `serde`, `serde_json`, `chrono`, `tokio`,
`tracing`, `reqwest`, `keyring`, `base64`, `rand`, `sha2`, `dirs`,
`parking_lot`, `urlencoding`.

cc-skills — 3 files, ~1,000 LOC (two ties broken)
------------------------------------------------

`src/skills/{bundled,loader,mod}.rs` → `crates/cc-skills/src/…`.

Two reverse-deps broken:

1. **Event emission** — `emit_event` held a
   `broadcast::Sender<crate::ipc::subsystem_events::SubsystemEvent>`,
   which is a cycle the moment skills leaves the root crate.
   Replaced with a minimal cc-skills-owned enum and a callback:

   ```rust
   pub enum SkillSubsystemEvent { SkillsLoaded { count: usize } }
   pub fn set_event_callback<F: Fn(SkillSubsystemEvent) + Send + Sync + 'static>(cb: F);
   ```

   The host adapts it into `SubsystemEvent::Skill(SkillEvent::SkillsLoaded { .. })`
   in `ipc/runtime.rs` (replaces the old `set_event_sender(bus.sender())`
   line).

2. **User-skills directory** — `init_skills` used to resolve
   `crate::config::paths::skills_dir_global()` internally. It now takes
   the directory as its first parameter; `main.rs` and `ipc::subsystem_handlers`
   pass `&crate::config::paths::skills_dir_global()` at the call site.

Direct deps: `serde`, `parking_lot` only.

Verification
------------

- `cargo build`                  : ok, 2 warnings (pre-existing
  `web::handlers::session_id` dead_code, identical on `rust-lite@ab8a2fc`).
- `cargo build --release`        : ok, same 2 warnings.
- `cargo test --workspace --lib --offline --no-fail-fast -- --test-threads=1`:
  - `cc-auth`          — 32 passed, 0 failed, 2 ignored
  - `cc-bootstrap`     — 28 passed, 0 failed
  - `cc-keybindings`   — 51 passed, 0 failed (unchanged from P1)
  - `cc-observability` — 10 passed, 1 pre-existing flake
    (`off_mode_skips_redaction`, tracked in Crsei#79)
  - `cc-skills`        — 21 passed, 0 failed
  - `cc-types`         — 0 tests
- `cargo test --bin claude-code-rs --offline -- --test-threads=1`
  : **1713 / 1713 pass**. Baseline from PR Crsei#80 was 1794 — the 81-test
  drop is the tests that moved out with their code (cc-bootstrap 28 +
  cc-auth 32 + cc-skills 21 = 81, matches exactly).
- Smoke: `claude-code-rs --version` prints `claude-code-rs 0.1.0`.

Phase 2 checklist (Crsei#71)
-----------------------

- [x] `crates/cc-bootstrap/` created
- [x] `crates/cc-auth/` created
- [x] `crates/cc-skills/` created
- [x] `src/{bootstrap,auth,skills}/` removed from root crate
- [x] Keychain service name unchanged (`"cc-rust"`)
- [x] All tests pass (1713 bin + 142 extracted, one pre-existing
  cc-observability flake unchanged)
- [ ] Manual OAuth login E2E (`/login 2` + `/login-code`) — not
  reproducible in CI, flagged for maintainer verification.

Refs Crsei#71. Part of Crsei#68.
@Crsei
Crsei merged commit dc6c69e 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.

[workspace-split] Phase 2: Extract leaf crates (auth, bootstrap, skills)

2 participants