Restructure relayburn crates into a single SDK monolith#305
Restructure relayburn crates into a single SDK monolith#305willwashburn merged 1 commit intomainfrom
Conversation
Absorbs `relayburn-{reader,ledger,analyze,ingest}` into `relayburn-sdk`
as `src/{reader,ledger,analyze,ingest}/` modules. The Rust workspace is
now three crates (`relayburn-sdk`, `relayburn-cli`, `relayburn-sdk-node`)
instead of seven. Only the SDK and CLI are published to crates.io; the
four absorbed modules become internal implementation details rather than
a public crates.io contract.
Why: keeping the lower crates separate forced a lockstep-publish
arrangement (the prep for which landed in #304) for what is really a
single implementation. The CLI's `relayburn-sdk` dep now exercises the
SDK's public surface as an external embedder would, which is the
property we actually wanted from the split.
Mechanics:
- `git mv` each lower crate's `src/` into the corresponding SDK
submodule, with each crate's old `lib.rs` becoming the module root.
- Cross-crate imports rewritten in two passes: first scope-rename
intra-module sibling refs (`crate::config::` → `crate::ledger::config::`),
then `relayburn_X::` → `crate::X::`.
- `models.dev.json` moves to `crates/relayburn-sdk/data/`; the
`include_str!` path and `scripts/update-pricing.mjs` follow.
- Ingest's four `tests/*.rs` files become in-crate `#[cfg(test)] mod`s
(they exercise crate-private items that the SDK doesn't re-export).
Their per-binary `static ENV_LOCK` / `GAP_LOCK` mutexes are
consolidated into shared `TEST_ENV_LOCK` / `TEST_GAP_LOCK` in
`crate::ingest`; without that, bundling them into one binary races
`$RELAYBURN_HOME` and gap-state mutations across modules.
- `relayburn-cli` and `relayburn-sdk-node` `Cargo.toml`s now depend
only on `relayburn-sdk`. The CLI's version requirement is `0.0`
(= `>=0.0.0, <0.1.0`) so it satisfies both the local 0.0.0 path dep
and the published `relayburn-sdk` 0.0.1 on crates.io.
- `AGENTS.md` (`CLAUDE.md`) Layout / When-in-doubt sections updated to
describe the three-crate shape and module-path pointers.
Verification: `cargo build --workspace` and `cargo test --workspace`
green (605 unit + 2 integration + 3 doctests, including all four
absorbed ingest test files); `cargo doc --no-deps -p relayburn-sdk`,
`cargo publish --dry-run -p relayburn-sdk --allow-dirty`, and
`cargo publish --dry-run -p relayburn-cli --allow-dirty` all succeed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR consolidates a multi-crate Rust workspace (relayburn-analyze, relayburn-ingest, relayburn-ledger, relayburn-reader) into a single monolithic relayburn-sdk crate. All inter-crate imports are replaced with internal crate::* module paths, old Cargo.toml files are deleted, and the SDK's public surface is reorganized to re-export from internal modules instead of external crates. ChangesWorkspace Consolidation
Estimated code review effort🎯 5 (Critical) | ⏱️ ~110 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/relayburn-sdk/src/lib.rs (1)
6-10: 💤 Low valueConsider updating the module doc to reflect absorbed modules.
The doc still describes the crate as "a thin re-export over
relayburn-reader,relayburn-ledger,relayburn-analyze, andrelayburn-ingest" as if they're external crates. Since these are now internal modules, a minor wording update (e.g., "internal modules") would improve clarity for future readers.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/relayburn-sdk/src/lib.rs` around lines 6 - 10, Update the crate-level documentation in lib.rs to replace wording that implies external crates (e.g., "a thin re-export over `relayburn-reader`, `relayburn-ledger`, `relayburn-analyze`, and `relayburn-ingest`") with language that reflects these are now internal modules (for example "re-exports internal modules" or "a thin re-export of internal modules"). Edit the module doc comment at the top of lib.rs so the public API description and the mention of those module names correctly indicate they are internal modules rather than separate external crates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@crates/relayburn-sdk/src/lib.rs`:
- Around line 6-10: Update the crate-level documentation in lib.rs to replace
wording that implies external crates (e.g., "a thin re-export over
`relayburn-reader`, `relayburn-ledger`, `relayburn-analyze`, and
`relayburn-ingest`") with language that reflects these are now internal modules
(for example "re-exports internal modules" or "a thin re-export of internal
modules"). Edit the module doc comment at the top of lib.rs so the public API
description and the mention of those module names correctly indicate they are
internal modules rather than separate external crates.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ef1555fe-7b16-42fc-bd81-e7886dc1c923
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (75)
AGENTS.mdcrates/relayburn-analyze/Cargo.tomlcrates/relayburn-cli/Cargo.tomlcrates/relayburn-ingest/Cargo.tomlcrates/relayburn-ledger/Cargo.tomlcrates/relayburn-reader/Cargo.tomlcrates/relayburn-sdk-node/Cargo.tomlcrates/relayburn-sdk/Cargo.tomlcrates/relayburn-sdk/data/models.dev.jsoncrates/relayburn-sdk/src/analyze.rscrates/relayburn-sdk/src/analyze/claude_md.rscrates/relayburn-sdk/src/analyze/compare.rscrates/relayburn-sdk/src/analyze/compare_archive.rscrates/relayburn-sdk/src/analyze/cost.rscrates/relayburn-sdk/src/analyze/fidelity.rscrates/relayburn-sdk/src/analyze/findings.rscrates/relayburn-sdk/src/analyze/ghost_surface.rscrates/relayburn-sdk/src/analyze/ghost_surface_inputs.rscrates/relayburn-sdk/src/analyze/hotspots.rscrates/relayburn-sdk/src/analyze/overhead.rscrates/relayburn-sdk/src/analyze/patterns.rscrates/relayburn-sdk/src/analyze/patterns_tests.rscrates/relayburn-sdk/src/analyze/pricing.rscrates/relayburn-sdk/src/analyze/provider.rscrates/relayburn-sdk/src/analyze/provider_reattribution.rscrates/relayburn-sdk/src/analyze/quality.rscrates/relayburn-sdk/src/analyze/replacement_savings.rscrates/relayburn-sdk/src/analyze/subagent_tree.rscrates/relayburn-sdk/src/analyze/tool_call_patterns.rscrates/relayburn-sdk/src/analyze/tool_output_bloat.rscrates/relayburn-sdk/src/export_verbs.rscrates/relayburn-sdk/src/ingest.rscrates/relayburn-sdk/src/ingest/cursors.rscrates/relayburn-sdk/src/ingest/gap.rscrates/relayburn-sdk/src/ingest/gap_warning_tests.rscrates/relayburn-sdk/src/ingest/ingest.rscrates/relayburn-sdk/src/ingest/orchestration_tests.rscrates/relayburn-sdk/src/ingest/pending_stamps.rscrates/relayburn-sdk/src/ingest/pending_stamps_compat_tests.rscrates/relayburn-sdk/src/ingest/reingest.rscrates/relayburn-sdk/src/ingest/walk.rscrates/relayburn-sdk/src/ingest/watch_loop.rscrates/relayburn-sdk/src/ingest/watch_loop_tests.rscrates/relayburn-sdk/src/ingest_verb.rscrates/relayburn-sdk/src/ledger.rscrates/relayburn-sdk/src/ledger/config.rscrates/relayburn-sdk/src/ledger/content.rscrates/relayburn-sdk/src/ledger/db.rscrates/relayburn-sdk/src/ledger/error.rscrates/relayburn-sdk/src/ledger/fingerprint.rscrates/relayburn-sdk/src/ledger/paths.rscrates/relayburn-sdk/src/ledger/query.rscrates/relayburn-sdk/src/ledger/reader.rscrates/relayburn-sdk/src/ledger/schema.rscrates/relayburn-sdk/src/ledger/stamp.rscrates/relayburn-sdk/src/ledger/tests.rscrates/relayburn-sdk/src/ledger/writer.rscrates/relayburn-sdk/src/lib.rscrates/relayburn-sdk/src/query_verbs.rscrates/relayburn-sdk/src/reader.rscrates/relayburn-sdk/src/reader/classifier.rscrates/relayburn-sdk/src/reader/claude.rscrates/relayburn-sdk/src/reader/codex.rscrates/relayburn-sdk/src/reader/codex/tests.rscrates/relayburn-sdk/src/reader/fidelity.rscrates/relayburn-sdk/src/reader/git.rscrates/relayburn-sdk/src/reader/hash.rscrates/relayburn-sdk/src/reader/opencode.rscrates/relayburn-sdk/src/reader/opencode/tests.rscrates/relayburn-sdk/src/reader/opencode_stream.rscrates/relayburn-sdk/src/reader/opencode_stream/tests.rscrates/relayburn-sdk/src/reader/types.rscrates/relayburn-sdk/src/reader/user_turn.rscrates/relayburn-sdk/tests/fixtures/ts_codex_stamp.jsonscripts/update-pricing.mjs
💤 Files with no reviewable changes (4)
- crates/relayburn-reader/Cargo.toml
- crates/relayburn-analyze/Cargo.toml
- crates/relayburn-ingest/Cargo.toml
- crates/relayburn-ledger/Cargo.toml
Summary
relayburn-{reader,ledger,analyze,ingest}) intorelayburn-sdkassrc/{reader,ledger,analyze,ingest}/modules. The Rust workspace shrinks from seven crates to three:relayburn-sdk,relayburn-cli,relayburn-sdk-node.relayburn-sdkexercises the SDK's public surface as an external embedder would, which is the property we wanted from the original split.Why
The four-lower-crates layout (set up in #242–#245 and made publish-ready in #304) forced a lockstep crates.io publish dance for what is really a single implementation. Continuing the conversation in #246's PR thread, we measured the rebuild tax: worst-case incremental goes from ~1.4s (workspace) → ~3–6s (monolith). At this codebase size, that's not meaningful, while exposing four internal crates as a public crates.io contract has real costs (semver pressure, naming, version coordination).
This PR reverts the lockstep-publish prep from #304 (the version-pinned path deps on the four absorbed crates) and replaces it with a single SDK crate that re-exports the same surface from internal modules.
Mechanics
git mveach lower crate'ssrc/into the corresponding SDK submodule, with each crate's oldlib.rsbecoming the module root (reader.rs,ledger.rs,analyze.rs,ingest.rs); blame history is preserved.crate::config::→crate::ledger::config::), thenrelayburn_X::→crate::X::everywhere.models.dev.jsonmoves tocrates/relayburn-sdk/data/; theinclude_str!path andscripts/update-pricing.mjsfollow. Nothing underpackages/is touched (project rule).tests/*.rs) become in-crate#[cfg(test)] mods — they exercise crate-private items (parse_pending_stamp,set_ingest_gap_writer,LedgerLayout) that the SDK doesn't re-export. Their per-binarystatic ENV_LOCK/GAP_LOCKmutexes are consolidated into sharedTEST_ENV_LOCK/TEST_GAP_LOCKincrate::ingest; without that, bundling them into one binary races$RELAYBURN_HOMEand gap-state mutations across modules.relayburn-cliandrelayburn-sdk-nodeCargo.tomls now depend only onrelayburn-sdk. The CLI's version requirement is0.0(=>=0.0.0, <0.1.0) so it satisfies both the local 0.0.0 path dep and the publishedrelayburn-sdk0.0.1 on crates.io — no lockstep-bump tax on every release.AGENTS.md(CLAUDE.md) Layout / When-in-doubt sections updated to describe the three-crate shape and module-path pointers (e.g.crates/relayburn-sdk/src/reader/classifier.rsrather thancrates/relayburn-reader/src/classifier.rs).Test plan
cargo build --workspacecleancargo test --workspacegreen — 605 unit + 2 integration + 3 doctests, including all four absorbed ingest test filescargo doc --no-deps -p relayburn-sdkclean (3 pre-existing intra-doc-link warnings —classify_fidelityetc. now point to private items since the lower crates aren't a public surface)cargo publish --dry-run -p relayburn-sdk --allow-dirtysucceeds (no transitive crates.io needed anymore — the whole point)cargo publish --dry-run -p relayburn-cli --allow-dirtysucceeds (resolvesrelayburn-sdk0.0.1 from crates.io)crates/relayburn-sdk/tests/integration.rs) still passes — exercises all 9 verbs against a fixture ledgerTEST_ENV_LOCK/TEST_GAP_LOCKconsolidation was needed to avoid intermittent failures from the absorbed test files racing on global state)🤖 Generated with Claude Code