diff --git a/AGENTS.md b/AGENTS.md index b4e75fad..399c1a7f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,19 +31,24 @@ relayburn — thin install-wrapper so `npm i -g relayburn` exposes the ### Rust crates (`crates/`) -Cargo workspace mirroring the TS dependency graph. Crate names are prefixed `relayburn-*` because `burn` is taken on crates.io; the binary keeps the `burn` invocation via `[[bin]] name = "burn"` in `relayburn-cli`. +The Rust tree is a **monolith**: only `relayburn-sdk` and `relayburn-cli` are published to crates.io. Crate names are prefixed `relayburn-*` because `burn` is taken on crates.io; the binary keeps the `burn` invocation via `[[bin]] name = "burn"` in `relayburn-cli`. ``` -relayburn-reader — parsers + classifier (port of @relayburn/reader; #242) -relayburn-ledger — JSONL append + content sidecar + lock + sqlite archive (#243) -relayburn-analyze — pricing, cost derivation, hotspots, overhead (#244) -relayburn-ingest — session discovery + parse-and-append + pending stamps + watch loop (#245) relayburn-sdk — PUBLISHED to crates.io; embedding API mirroring @relayburn/sdk (#246) -relayburn-cli — PUBLISHED to crates.io; produces the `burn` binary via [[bin]] rename (#248) -relayburn-sdk-node — napi-rs bindings; built in CI to produce @relayburn/sdk@2.0 .node artifacts (#247) + src/{reader,ledger,analyze,ingest}/ are internal modules + (formerly the four lower crates from #242–#245; absorbed + when keeping them as separate crates would have forced a + public crates.io contract for what is really a single + implementation) +relayburn-cli — PUBLISHED to crates.io; produces the `burn` binary via [[bin]] rename (#248). + Consumes the SDK as an external embedder would. +relayburn-sdk-node — napi-rs bindings; built in CI to produce @relayburn/sdk@2.0 .node artifacts (#247). + Not published to crates.io. ``` -Build order matches TS: `relayburn-reader → -ledger → -analyze → -ingest → -sdk → -cli`, with `relayburn-sdk-node` depending on `relayburn-sdk`. Toolchain pinned in `rust-toolchain.toml` at the repo root. +Three crates total. Build order is `relayburn-sdk → relayburn-cli`, with `relayburn-sdk-node` also depending on `relayburn-sdk`. Toolchain pinned in `rust-toolchain.toml` at the repo root. + +Inside `relayburn-sdk`, the absorbed modules build in dependency order `reader → ledger → analyze → ingest`; each module's source-of-truth comment still points back to the TS sibling under `packages/`. The verb surface lives in `src/{query_verbs,export_verbs,ingest_verb}.rs` at the SDK crate root and pulls from those modules. `@relayburn/sdk` owns the canonical query/compute surface — every new read verb should land there first as a pure function. `@relayburn/mcp` and `@relayburn/cli` are presenters: MCP wraps SDK calls in tool definitions, CLI wraps them in flag parsing + table rendering. Don't duplicate query logic across CLI/MCP — extract it into SDK. @@ -161,8 +166,8 @@ The codex / opencode adapters share the pending-stamp + watch-loop shape; both a ## When in doubt -- **Architecture / API surface:** read `README.md` first, then the package's `src/index.ts` for exports. -- **Activity classifier rules:** the rule tables (`TEST_PATTERNS`, `EDIT_TOOLS`, `TOOL_ALIASES`, etc.) live at `packages/reader/src/classifier.ts`. They're the source of truth for what `burn compare` buckets each turn into. Adding a new harness = adding entries to `TOOL_ALIASES`; adding a new category = updating `ActivityCategory` in `packages/reader/src/types.ts` and adding its rule + a test. +- **Architecture / API surface:** read `README.md` first, then the package's `src/index.ts` for exports (TS) or `crates/relayburn-sdk/src/lib.rs` for the Rust public surface. +- **Activity classifier rules:** the rule tables (`TEST_PATTERNS`, `EDIT_TOOLS`, `TOOL_ALIASES`, etc.) live at `packages/reader/src/classifier.ts` (TS) and `crates/relayburn-sdk/src/reader/classifier.rs` (Rust). They're the source of truth for what `burn compare` buckets each turn into. Adding a new harness = adding entries to `TOOL_ALIASES`; adding a new category = updating `ActivityCategory` in `packages/reader/src/types.ts` (and the Rust mirror at `crates/relayburn-sdk/src/reader/types.rs`) and adding its rule + a test. - **Derived state commands:** status, rebuild targets, and content pruning live under `burn state` in `packages/cli/src/commands/state.ts`. Keep maintenance verbs there rather than adding new top-level CLI dispatch. -- **Ledger schema:** `packages/reader/src/types.ts` (`TurnRecord`, `ContentRecord`) and `packages/ledger/src/schema.ts` (`LedgerLine`, `TurnLine`, `StampLine`). Bump `v` if the on-disk shape changes. +- **Ledger schema:** `packages/reader/src/types.ts` (`TurnRecord`, `ContentRecord`) and `packages/ledger/src/schema.ts` (`LedgerLine`, `TurnLine`, `StampLine`); Rust mirrors at `crates/relayburn-sdk/src/reader/types.rs` and `crates/relayburn-sdk/src/ledger/schema.rs`. Bump `v` if the on-disk shape changes. - **Concurrency:** any read-modify-write on the ledger MUST hold `withLock('ledger', …)` from `@relayburn/ledger`. Append-only writes use the same lock to avoid racing reclassify-style rewrites. diff --git a/Cargo.lock b/Cargo.lock index fd8e0a80..a5b79ccf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -412,78 +412,28 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" -[[package]] -name = "relayburn-analyze" -version = "0.0.0" -dependencies = [ - "indexmap", - "phf", - "regex", - "relayburn-ledger", - "relayburn-reader", - "serde", - "serde_json", - "tempfile", -] - [[package]] name = "relayburn-cli" version = "0.0.0" - -[[package]] -name = "relayburn-ingest" -version = "0.0.0" -dependencies = [ - "anyhow", - "libc", - "relayburn-ledger", - "relayburn-reader", - "serde", - "serde_json", - "tempfile", - "thiserror", - "tokio", -] - -[[package]] -name = "relayburn-ledger" -version = "0.0.0" dependencies = [ - "hex", - "relayburn-reader", - "rusqlite", - "serde", - "serde_json", - "sha2", - "tempfile", - "thiserror", + "relayburn-sdk", ] [[package]] -name = "relayburn-reader" +name = "relayburn-sdk" version = "0.0.0" dependencies = [ + "anyhow", "hex", + "indexmap", + "libc", "phf", "regex", + "rusqlite", "serde", "serde_json", "sha2", "tempfile", -] - -[[package]] -name = "relayburn-sdk" -version = "0.0.0" -dependencies = [ - "anyhow", - "relayburn-analyze", - "relayburn-ingest", - "relayburn-ledger", - "relayburn-reader", - "serde", - "serde_json", - "tempfile", "thiserror", "tokio", ] @@ -491,6 +441,9 @@ dependencies = [ [[package]] name = "relayburn-sdk-node" version = "0.0.0" +dependencies = [ + "relayburn-sdk", +] [[package]] name = "rusqlite" diff --git a/crates/relayburn-analyze/Cargo.toml b/crates/relayburn-analyze/Cargo.toml deleted file mode 100644 index 94fb9a61..00000000 --- a/crates/relayburn-analyze/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "relayburn-analyze" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -description = "Pricing, cost derivation, hotspots, and overhead computation (Rust port)." - -[dependencies] -serde = { workspace = true } -serde_json = { workspace = true } -# `IndexMap` preserves JSON insertion order so duplicate model IDs in -# `models.dev.json` (e.g. `claude-sonnet-4-6` under both `anthropic` and -# `nano-gpt`) resolve deterministically — last entry in file order wins, -# matching the TS `Object.values` / `Object.entries` iteration semantics. -indexmap = { version = "2", features = ["serde"] } -phf = { version = "0.11", features = ["macros"] } -regex = "1" -relayburn-reader = { path = "../relayburn-reader", version = "0.0.0" } -relayburn-ledger = { path = "../relayburn-ledger", version = "0.0.0" } - -[dev-dependencies] -tempfile = "3" diff --git a/crates/relayburn-cli/Cargo.toml b/crates/relayburn-cli/Cargo.toml index 3fc86181..aa00e6c1 100644 --- a/crates/relayburn-cli/Cargo.toml +++ b/crates/relayburn-cli/Cargo.toml @@ -10,3 +10,15 @@ description = "The `burn` CLI — published to crates.io. Crate name is relaybur [[bin]] name = "burn" path = "src/main.rs" + +[dependencies] +# The CLI is the canonical external embedder of the SDK — every read +# verb the binary surfaces will wrap a `relayburn-sdk` call once #248 +# fills in the CLI source (the binary is an `eprintln!` stub today). +# +# Version requirement is `0.0` (= `>=0.0.0, <0.1.0`) so the loose +# 0.0.x prerelease line satisfies both the local workspace path +# (currently 0.0.0) AND the published `relayburn-sdk` on crates.io +# (currently 0.0.1) without forcing a lockstep bump on every release. +# Tighten this once the SDK ships a stable 0.x line. +relayburn-sdk = { path = "../relayburn-sdk", version = "0.0" } diff --git a/crates/relayburn-ingest/Cargo.toml b/crates/relayburn-ingest/Cargo.toml deleted file mode 100644 index e59cab45..00000000 --- a/crates/relayburn-ingest/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "relayburn-ingest" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -description = "Session-store discovery, parse-and-append orchestration, pending stamps, and watch loop (Rust port)." - -[dependencies] -relayburn-reader = { path = "../relayburn-reader", version = "0.0.0" } -relayburn-ledger = { path = "../relayburn-ledger", version = "0.0.0" } -serde = { workspace = true } -serde_json = { workspace = true, features = ["preserve_order"] } -tokio = { workspace = true, features = ["sync"] } -thiserror = { workspace = true } -anyhow = { workspace = true } - -[target.'cfg(unix)'.dependencies] -libc = "0.2" - -[dev-dependencies] -tempfile = "3" -tokio = { workspace = true, features = ["rt-multi-thread", "macros", "time", "sync", "test-util"] } diff --git a/crates/relayburn-ledger/Cargo.toml b/crates/relayburn-ledger/Cargo.toml deleted file mode 100644 index 9f07d36f..00000000 --- a/crates/relayburn-ledger/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "relayburn-ledger" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -description = "SQLite-backed ledger (events + stamps) and FTS5 content store for relayburn (Rust port)." - -[dependencies] -relayburn-reader = { path = "../relayburn-reader", version = "0.0.0" } -serde = { workspace = true } -serde_json = { workspace = true } -rusqlite = { workspace = true } -sha2 = "0.10" -hex = "0.4" -thiserror = { workspace = true } - -[dev-dependencies] -tempfile = "3" diff --git a/crates/relayburn-reader/Cargo.toml b/crates/relayburn-reader/Cargo.toml deleted file mode 100644 index 1194d355..00000000 --- a/crates/relayburn-reader/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "relayburn-reader" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -description = "Session-log parsers and activity classifier for relayburn (Rust port)." - -[dependencies] -serde = { workspace = true } -serde_json = { workspace = true } -sha2 = "0.10" -hex = "0.4" -regex = "1" -phf = { version = "0.11", features = ["macros"] } - -[dev-dependencies] -tempfile = "3" diff --git a/crates/relayburn-sdk-node/Cargo.toml b/crates/relayburn-sdk-node/Cargo.toml index d491a01d..eeead086 100644 --- a/crates/relayburn-sdk-node/Cargo.toml +++ b/crates/relayburn-sdk-node/Cargo.toml @@ -7,3 +7,9 @@ license.workspace = true repository.workspace = true description = "napi-rs bindings over relayburn-sdk — built in CI to produce the @relayburn/sdk npm artifacts. Not published to crates.io." publish = false + +[dependencies] +# napi-rs glue lands in #247; this crate's only direct dep is the SDK. +# Not published to crates.io, so the version pin is loose by design — it +# just keeps `cargo metadata` and the workspace resolver happy. +relayburn-sdk = { path = "../relayburn-sdk", version = "0.0" } diff --git a/crates/relayburn-sdk/Cargo.toml b/crates/relayburn-sdk/Cargo.toml index 27a56773..4e8e305a 100644 --- a/crates/relayburn-sdk/Cargo.toml +++ b/crates/relayburn-sdk/Cargo.toml @@ -8,20 +8,38 @@ repository.workspace = true description = "Embedding API for relayburn — published to crates.io as the supported Rust surface." [dependencies] -# Path + version on every workspace dep so `cargo publish --dry-run` can -# verify the manifest without erroring on missing version requirements. -# The four lower crates publish in lockstep; the workspace version is the -# source of truth (`workspace = true`). -relayburn-reader = { path = "../relayburn-reader", version = "0.0.0" } -relayburn-ledger = { path = "../relayburn-ledger", version = "0.0.0" } -relayburn-analyze = { path = "../relayburn-analyze", version = "0.0.0" } -relayburn-ingest = { path = "../relayburn-ingest", version = "0.0.0" } +# Deps absorbed from the four former lower crates +# (`relayburn-{reader,ledger,analyze,ingest}`) when the monolith +# restructure collapsed them into modules under +# `src/{reader,ledger,analyze,ingest}/`. + +# Common serde = { workspace = true } -serde_json = { workspace = true } +serde_json = { workspace = true, features = ["preserve_order"] } anyhow = { workspace = true } thiserror = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["sync"] } + +# reader: hashing + classifier rule tables +sha2 = "0.10" +hex = "0.4" +regex = "1" +phf = { version = "0.11", features = ["macros"] } + +# ledger: SQLite events + content store +rusqlite = { workspace = true } + +# analyze: order-preserving pricing table. +# `IndexMap` preserves JSON insertion order so duplicate model IDs in +# `models.dev.json` (e.g. `claude-sonnet-4-6` under both `anthropic` and +# `nano-gpt`) resolve deterministically — last entry in file order wins, +# matching the TS `Object.values` / `Object.entries` iteration semantics. +indexmap = { version = "2", features = ["serde"] } + +[target.'cfg(unix)'.dependencies] +# ingest: pid-liveness probe in the pending-stamp resolver +libc = "0.2" [dev-dependencies] tempfile = "3" -tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros", "time", "sync", "test-util"] } diff --git a/crates/relayburn-analyze/data/models.dev.json b/crates/relayburn-sdk/data/models.dev.json similarity index 100% rename from crates/relayburn-analyze/data/models.dev.json rename to crates/relayburn-sdk/data/models.dev.json diff --git a/crates/relayburn-analyze/src/lib.rs b/crates/relayburn-sdk/src/analyze.rs similarity index 95% rename from crates/relayburn-analyze/src/lib.rs rename to crates/relayburn-sdk/src/analyze.rs index 713f3c8a..c9150f78 100644 --- a/crates/relayburn-analyze/src/lib.rs +++ b/crates/relayburn-sdk/src/analyze.rs @@ -15,6 +15,12 @@ //! the 1e-9 USD precision contract that `overhead` and `hotspots` gate //! against. +// The four absorbed module roots carry the lower crates whole, including +// items the SDK does not re-export (dead from the SDK perspective). Silence +// the never-used warnings rather than handpicking re-exports — the next +// agent absorbing more verbs will need them. +#![allow(dead_code, unused_imports)] + pub mod claude_md; pub mod compare; pub mod compare_archive; diff --git a/crates/relayburn-analyze/src/claude_md.rs b/crates/relayburn-sdk/src/analyze/claude_md.rs similarity index 99% rename from crates/relayburn-analyze/src/claude_md.rs rename to crates/relayburn-sdk/src/analyze/claude_md.rs index 68a4c3e3..d4200c5c 100644 --- a/crates/relayburn-analyze/src/claude_md.rs +++ b/crates/relayburn-sdk/src/analyze/claude_md.rs @@ -15,11 +15,11 @@ use std::path::{Path, PathBuf}; use indexmap::IndexMap; use regex::Regex; -use relayburn_reader::TurnRecord; +use crate::reader::TurnRecord; use serde::{Deserialize, Serialize}; -use crate::cost::lookup_model_rate; -use crate::pricing::PricingTable; +use crate::analyze::cost::lookup_model_rate; +use crate::analyze::pricing::PricingTable; const PER_MILLION: f64 = 1_000_000.0; const CHARS_PER_TOKEN: u64 = 4; @@ -511,8 +511,8 @@ fn to_posix_relative(file_path: &str, base_dir: Option<&Path>) -> String { #[cfg(test)] mod tests { use super::*; - use crate::pricing::load_builtin_pricing; - use relayburn_reader::{SourceKind, ToolCall, TurnRecord, Usage}; + use crate::analyze::pricing::load_builtin_pricing; + use crate::reader::{SourceKind, ToolCall, TurnRecord, Usage}; use std::fs; use tempfile::TempDir; diff --git a/crates/relayburn-analyze/src/compare.rs b/crates/relayburn-sdk/src/analyze/compare.rs similarity index 98% rename from crates/relayburn-analyze/src/compare.rs rename to crates/relayburn-sdk/src/analyze/compare.rs index c86b262b..18cd69f9 100644 --- a/crates/relayburn-analyze/src/compare.rs +++ b/crates/relayburn-sdk/src/analyze/compare.rs @@ -11,11 +11,11 @@ use std::collections::BTreeMap; -use relayburn_ledger::EnrichedTurn; -use relayburn_reader::ActivityCategory; +use crate::ledger::EnrichedTurn; +use crate::reader::ActivityCategory; -use crate::cost::cost_for_turn; -use crate::pricing::PricingTable; +use crate::analyze::cost::cost_for_turn; +use crate::analyze::pricing::PricingTable; /// Activity category label, or `"unclassified"` for turns the classifier /// couldn't bucket. Mirrors the TS `ActivityCategory | "unclassified"` @@ -311,9 +311,9 @@ fn activity_label(activity: Option) -> String { #[cfg(test)] mod tests { use super::*; - use crate::pricing::load_builtin_pricing; - use relayburn_ledger::EnrichedTurn; - use relayburn_reader::{ActivityCategory, SourceKind, ToolCall, TurnRecord, Usage}; + use crate::analyze::pricing::load_builtin_pricing; + use crate::ledger::EnrichedTurn; + use crate::reader::{ActivityCategory, SourceKind, ToolCall, TurnRecord, Usage}; use std::collections::BTreeMap; fn turn( diff --git a/crates/relayburn-analyze/src/compare_archive.rs b/crates/relayburn-sdk/src/analyze/compare_archive.rs similarity index 97% rename from crates/relayburn-analyze/src/compare_archive.rs rename to crates/relayburn-sdk/src/analyze/compare_archive.rs index 78b499c5..abb8a89c 100644 --- a/crates/relayburn-analyze/src/compare_archive.rs +++ b/crates/relayburn-sdk/src/analyze/compare_archive.rs @@ -11,9 +11,9 @@ //! semantics that derive it from `queryAll(q).length` rather than from the //! post-filter table. -use relayburn_ledger::{Ledger, Query, Result as LedgerResult}; +use crate::ledger::{Ledger, Query, Result as LedgerResult}; -use crate::compare::{build_compare_table, CompareOptions, CompareTable}; +use crate::analyze::compare::{build_compare_table, CompareOptions, CompareTable}; #[derive(Debug, Clone, PartialEq)] pub struct CompareFromArchiveResult { @@ -44,10 +44,10 @@ pub fn compare_from_archive( #[cfg(test)] mod tests { use super::*; - use crate::compare::CompareOptions; - use crate::pricing::load_builtin_pricing; - use relayburn_ledger::{Ledger, LedgerLayout, Query}; - use relayburn_reader::{ActivityCategory, SourceKind, ToolCall, TurnRecord, Usage}; + use crate::analyze::compare::CompareOptions; + use crate::analyze::pricing::load_builtin_pricing; + use crate::ledger::{Ledger, LedgerLayout, Query}; + use crate::reader::{ActivityCategory, SourceKind, ToolCall, TurnRecord, Usage}; use tempfile::TempDir; fn open_in(tmp: &TempDir) -> Ledger { @@ -274,7 +274,7 @@ mod tests { min_sample: Some(5), }; let in_memory_turns = ledger.query_turns(&Query::default()).unwrap(); - let in_memory = crate::compare::build_compare_table(&in_memory_turns, &opts); + let in_memory = crate::analyze::compare::build_compare_table(&in_memory_turns, &opts); let from_archive = compare_from_archive(&ledger, &Query::default(), &opts).unwrap(); assert_eq!(from_archive.table.models, in_memory.models, "models order"); @@ -732,7 +732,7 @@ mod tests { let opts = CompareOptions::new(&pricing); let in_memory_turns = ledger.query_turns(&Query::default()).unwrap(); - let in_memory = crate::compare::build_compare_table(&in_memory_turns, &opts); + let in_memory = crate::analyze::compare::build_compare_table(&in_memory_turns, &opts); let from_archive = compare_from_archive(&ledger, &Query::default(), &opts).unwrap(); let expected = in_memory diff --git a/crates/relayburn-analyze/src/cost.rs b/crates/relayburn-sdk/src/analyze/cost.rs similarity index 97% rename from crates/relayburn-analyze/src/cost.rs rename to crates/relayburn-sdk/src/analyze/cost.rs index 46786fa8..fe231bc2 100644 --- a/crates/relayburn-analyze/src/cost.rs +++ b/crates/relayburn-sdk/src/analyze/cost.rs @@ -6,10 +6,10 @@ //! drift stays bounded by the documented 1e-9 USD precision contract that the //! later overhead sub-issue depends on. -use relayburn_reader::{SourceKind, TurnRecord, Usage}; +use crate::reader::{SourceKind, TurnRecord, Usage}; -use crate::pricing::{ModelCost, PricingTable, ReasoningMode}; -use crate::provider_reattribution::resolve_provider; +use crate::analyze::pricing::{ModelCost, PricingTable, ReasoningMode}; +use crate::analyze::provider_reattribution::resolve_provider; #[derive(Debug, Clone, PartialEq)] pub struct CostBreakdown { @@ -152,8 +152,8 @@ where #[cfg(test)] mod tests { use super::*; - use crate::pricing::{load_builtin_pricing, ModelCost, ReasoningMode}; - use relayburn_reader::{SourceKind, ToolCall, TurnRecord, Usage}; + use crate::analyze::pricing::{load_builtin_pricing, ModelCost, ReasoningMode}; + use crate::reader::{SourceKind, ToolCall, TurnRecord, Usage}; fn turn(model: &str, usage: Usage, source: SourceKind) -> TurnRecord { TurnRecord { diff --git a/crates/relayburn-analyze/src/fidelity.rs b/crates/relayburn-sdk/src/analyze/fidelity.rs similarity index 99% rename from crates/relayburn-analyze/src/fidelity.rs rename to crates/relayburn-sdk/src/analyze/fidelity.rs index d59bdf45..88f50a6c 100644 --- a/crates/relayburn-analyze/src/fidelity.rs +++ b/crates/relayburn-sdk/src/analyze/fidelity.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; -use relayburn_reader::{ +use crate::reader::{ classify_fidelity, Coverage, Fidelity, FidelityClass, TurnRecord, UsageGranularity, }; @@ -159,7 +159,7 @@ pub fn has_minimum_fidelity(fidelity: Option<&Fidelity>, minimum: FidelityClass) #[cfg(test)] mod tests { use super::*; - use relayburn_reader::{ + use crate::reader::{ parse_opencode_session, Coverage, Fidelity, FidelityClass, ParseOpencodeOptions, UsageGranularity, }; diff --git a/crates/relayburn-analyze/src/findings.rs b/crates/relayburn-sdk/src/analyze/findings.rs similarity index 99% rename from crates/relayburn-analyze/src/findings.rs rename to crates/relayburn-sdk/src/analyze/findings.rs index 83b77cc8..1a489eb3 100644 --- a/crates/relayburn-analyze/src/findings.rs +++ b/crates/relayburn-sdk/src/analyze/findings.rs @@ -14,7 +14,7 @@ //! this module so the future `patterns.rs` port can depend on it without a //! circular dependency. See AgentWorkforce/burn#268. -use relayburn_reader::{SourceKind, ToolResultEventSource}; +use crate::reader::{SourceKind, ToolResultEventSource}; use serde::{Deserialize, Serialize}; // --------------------------------------------------------------------------- diff --git a/crates/relayburn-analyze/src/ghost_surface.rs b/crates/relayburn-sdk/src/analyze/ghost_surface.rs similarity index 99% rename from crates/relayburn-analyze/src/ghost_surface.rs rename to crates/relayburn-sdk/src/analyze/ghost_surface.rs index 6bf206fa..3d58a0d6 100644 --- a/crates/relayburn-analyze/src/ghost_surface.rs +++ b/crates/relayburn-sdk/src/analyze/ghost_surface.rs @@ -38,10 +38,10 @@ use std::fs; use std::path::{Path, PathBuf}; use regex::Regex; -use relayburn_reader::SourceKind; +use crate::reader::SourceKind; use serde::{Deserialize, Serialize}; -use crate::findings::{EstimatedSavings, WasteAction, WasteFinding, WasteSeverity}; +use crate::analyze::findings::{EstimatedSavings, WasteAction, WasteFinding, WasteSeverity}; // --------------------------------------------------------------------------- // Public types diff --git a/crates/relayburn-analyze/src/ghost_surface_inputs.rs b/crates/relayburn-sdk/src/analyze/ghost_surface_inputs.rs similarity index 97% rename from crates/relayburn-analyze/src/ghost_surface_inputs.rs rename to crates/relayburn-sdk/src/analyze/ghost_surface_inputs.rs index 2e33a083..2befb21d 100644 --- a/crates/relayburn-analyze/src/ghost_surface_inputs.rs +++ b/crates/relayburn-sdk/src/analyze/ghost_surface_inputs.rs @@ -15,10 +15,10 @@ use std::collections::{HashMap, HashSet}; -use relayburn_reader::{SourceKind, TurnRecord}; +use crate::reader::{SourceKind, TurnRecord}; -use crate::ghost_surface::GhostSurfaceInputs; -use crate::pricing::PricingTable; +use crate::analyze::ghost_surface::GhostSurfaceInputs; +use crate::analyze::pricing::PricingTable; /// Per-source set of *observed invocation names* in this slice. Each turn /// contributes its tool-call names, any `skillName` set on a tool call, and @@ -118,9 +118,9 @@ pub fn build_ghost_surface_inputs( #[cfg(test)] mod tests { use super::*; - use relayburn_reader::{Subagent, ToolCall, Usage}; + use crate::reader::{Subagent, ToolCall, Usage}; - use crate::pricing::{ModelCost, ReasoningMode}; + use crate::analyze::pricing::{ModelCost, ReasoningMode}; fn make_turn( source: SourceKind, diff --git a/crates/relayburn-analyze/src/hotspots.rs b/crates/relayburn-sdk/src/analyze/hotspots.rs similarity index 99% rename from crates/relayburn-analyze/src/hotspots.rs rename to crates/relayburn-sdk/src/analyze/hotspots.rs index 006c810d..8324c562 100644 --- a/crates/relayburn-analyze/src/hotspots.rs +++ b/crates/relayburn-sdk/src/analyze/hotspots.rs @@ -11,14 +11,14 @@ use std::collections::HashMap; use indexmap::IndexMap; use phf::phf_set; -use relayburn_reader::{ +use crate::reader::{ BashParse, ContentKind, ContentRecord, TurnRecord, UserTurnBlockKind, UserTurnRecord, }; use serde::{Deserialize, Serialize}; use serde_json::Value; -use crate::cost::{cost_for_turn, lookup_model_rate}; -use crate::pricing::PricingTable; +use crate::analyze::cost::{cost_for_turn, lookup_model_rate}; +use crate::analyze::pricing::PricingTable; const PER_MILLION: f64 = 1_000_000.0; const CHARS_PER_TOKEN: u64 = 4; @@ -796,8 +796,8 @@ pub fn aggregate_by_subagent(attributions: &[ToolAttribution]) -> Vec String { #[cfg(test)] mod tests { use super::*; - use crate::claude_md::parse_claude_md; - use crate::pricing::{ModelCost, ReasoningMode}; - use relayburn_reader::Usage; + use crate::analyze::claude_md::parse_claude_md; + use crate::analyze::pricing::{ModelCost, ReasoningMode}; + use crate::reader::Usage; fn pricing_with(model: &str, cache_read: f64) -> PricingTable { let mut p = PricingTable::new(); diff --git a/crates/relayburn-analyze/src/patterns.rs b/crates/relayburn-sdk/src/analyze/patterns.rs similarity index 99% rename from crates/relayburn-analyze/src/patterns.rs rename to crates/relayburn-sdk/src/analyze/patterns.rs index 26f72d45..b5cd2ced 100644 --- a/crates/relayburn-analyze/src/patterns.rs +++ b/crates/relayburn-sdk/src/analyze/patterns.rs @@ -16,21 +16,21 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use relayburn_reader::{ +use crate::reader::{ count_retries, normalize_tool_name, CompactionEvent, ContentKind, ContentRecord, ContentToolResult, ContentToolUse, SourceKind, ToolCall, ToolResultEventRecord, ToolResultEventSource, ToolResultStatus, TurnRecord, UserTurnRecord, }; use serde_json::Value; -use crate::cost::{cost_for_turn, cost_for_usage, CostForUsageOptions}; -use crate::findings::{ +use crate::analyze::cost::{cost_for_turn, cost_for_usage, CostForUsageOptions}; +use crate::analyze::findings::{ CancellationRun, CompactionLoss, CompactionLostWork, EditHeavySession, EditPreview, EditRevertCycle, EditRevertSamplePreview, FailureRun, FailureRunErrorSignature, PatternEventSource, PatternsResult, RetryLoop, SessionPatternSummary, SkillPruningProtection, SkillRecallDup, SystemPromptTax, }; -use crate::pricing::PricingTable; +use crate::analyze::pricing::PricingTable; // --------------------------------------------------------------------------- // Hardcoded thresholds. Each constant cites the TS source line so future @@ -1150,7 +1150,7 @@ fn detect_compaction_losses( if tokens > 0 { if let Some(precid) = e.preceding_message_id.as_deref() { if let Some(preceding) = turn_by_message_id.get(precid) { - let usage = relayburn_reader::Usage { + let usage = crate::reader::Usage { input: 0, output: 0, reasoning: 0, diff --git a/crates/relayburn-analyze/src/patterns_tests.rs b/crates/relayburn-sdk/src/analyze/patterns_tests.rs similarity index 99% rename from crates/relayburn-analyze/src/patterns_tests.rs rename to crates/relayburn-sdk/src/analyze/patterns_tests.rs index 278d702d..44465ada 100644 --- a/crates/relayburn-analyze/src/patterns_tests.rs +++ b/crates/relayburn-sdk/src/analyze/patterns_tests.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use std::path::PathBuf; -use relayburn_reader::{ +use crate::reader::{ parse_claude_session, ClaudeParseOptions, CompactionEvent, ContentKind, ContentRecord, ContentRole, ContentToolResult, ContentToolUse, SourceKind, ToolCall, ToolResultEventRecord, ToolResultEventSource, ToolResultStatus, TurnRecord, Usage, UserTurnBlock, UserTurnBlockKind, @@ -15,8 +15,8 @@ use relayburn_reader::{ }; use serde_json::{json, Value}; -use crate::patterns::{detect_patterns, DetectPatternsOptions}; -use crate::pricing::load_builtin_pricing; +use crate::analyze::patterns::{detect_patterns, DetectPatternsOptions}; +use crate::analyze::pricing::load_builtin_pricing; fn fixture(name: &str) -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) @@ -306,7 +306,7 @@ mod retry_loops { let l = &legacy.retry_loops[0]; assert_eq!( g.event_source, - Some(crate::findings::PatternEventSource::ToolResult) + Some(crate::analyze::findings::PatternEventSource::ToolResult) ); assert_eq!(g.tool, l.tool); assert_eq!(g.attempts, l.attempts); @@ -393,14 +393,14 @@ mod retry_loops { }, ); assert_eq!(result.retry_loops.len(), 2); - let by_session: HashMap<&str, &crate::findings::RetryLoop> = result + let by_session: HashMap<&str, &crate::analyze::findings::RetryLoop> = result .retry_loops .iter() .map(|l| (l.session_id.as_str(), l)) .collect(); assert_eq!( by_session["graph"].event_source, - Some(crate::findings::PatternEventSource::ToolResult) + Some(crate::analyze::findings::PatternEventSource::ToolResult) ); assert_eq!(by_session["fallback"].event_source, None); } @@ -511,7 +511,7 @@ mod consecutive_failure_runs { assert_eq!(result.failure_runs[0].length, 3); assert_eq!( result.failure_runs[0].event_source, - Some(crate::findings::PatternEventSource::SubagentNotification) + Some(crate::analyze::findings::PatternEventSource::SubagentNotification) ); } } @@ -553,7 +553,7 @@ mod cancelled_graph_events { assert_eq!(result.cancelled_runs[0].length, 3); assert_eq!( result.cancelled_runs[0].event_source, - crate::findings::PatternEventSource::ToolResult + crate::analyze::findings::PatternEventSource::ToolResult ); } } diff --git a/crates/relayburn-analyze/src/pricing.rs b/crates/relayburn-sdk/src/analyze/pricing.rs similarity index 98% rename from crates/relayburn-analyze/src/pricing.rs rename to crates/relayburn-sdk/src/analyze/pricing.rs index 4e0ac556..fb3d22b2 100644 --- a/crates/relayburn-analyze/src/pricing.rs +++ b/crates/relayburn-sdk/src/analyze/pricing.rs @@ -83,11 +83,11 @@ struct ModelsDevProvider { type ModelsDevRoot = IndexMap; /// Bundled `models.dev.json` snapshot. Refreshed via `pnpm run pricing:update`, -/// which writes through to both this crate's `data/` copy and the TS tree's +/// which writes through to both the SDK crate's `data/` copy and the TS tree's /// `packages/analyze/pricing/` copy (kept in lockstep). Vendoring inside the /// crate is required so `cargo package` / `cargo publish --dry-run` can verify /// the tarball without reaching back into the TS tree. -const BUILTIN_PRICING_JSON: &str = include_str!("../data/models.dev.json"); +const BUILTIN_PRICING_JSON: &str = include_str!("../../data/models.dev.json"); /// Load the bundled `models.dev` snapshot. No I/O — the JSON is embedded at /// compile time via `include_str!`. diff --git a/crates/relayburn-analyze/src/provider.rs b/crates/relayburn-sdk/src/analyze/provider.rs similarity index 97% rename from crates/relayburn-analyze/src/provider.rs rename to crates/relayburn-sdk/src/analyze/provider.rs index dd99a3ba..7bf38630 100644 --- a/crates/relayburn-analyze/src/provider.rs +++ b/crates/relayburn-sdk/src/analyze/provider.rs @@ -13,11 +13,11 @@ use std::cmp::Ordering; use std::collections::BTreeSet; use indexmap::IndexMap; -use relayburn_reader::{Coverage, SourceKind, TurnRecord, Usage}; +use crate::reader::{Coverage, SourceKind, TurnRecord, Usage}; -use crate::cost::{cost_for_turn, CostBreakdown}; -use crate::pricing::PricingTable; -use crate::provider_reattribution::{default_rules, resolve_provider_with_rules, ProviderRule}; +use crate::analyze::cost::{cost_for_turn, CostBreakdown}; +use crate::analyze::pricing::PricingTable; +use crate::analyze::provider_reattribution::{default_rules, resolve_provider_with_rules, ProviderRule}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct TurnProvider { @@ -354,8 +354,8 @@ fn provider_from_source(source: SourceKind) -> String { #[cfg(test)] mod tests { use super::*; - use crate::pricing::{ModelCost, ReasoningMode}; - use relayburn_reader::{ToolCall, Usage}; + use crate::analyze::pricing::{ModelCost, ReasoningMode}; + use crate::reader::{ToolCall, Usage}; fn pricing_fixture() -> PricingTable { let mut p = PricingTable::new(); @@ -609,9 +609,9 @@ mod cost_lookup_via_reattribution_tests { //! to the provider tests so the conformance gate in #267 is colocated. use super::*; - use crate::cost::cost_for_turn; - use crate::pricing::load_builtin_pricing; - use relayburn_reader::{ToolCall, Usage}; + use crate::analyze::cost::cost_for_turn; + use crate::analyze::pricing::load_builtin_pricing; + use crate::reader::{ToolCall, Usage}; fn turn(model: &str, usage: Usage) -> TurnRecord { TurnRecord { diff --git a/crates/relayburn-analyze/src/provider_reattribution.rs b/crates/relayburn-sdk/src/analyze/provider_reattribution.rs similarity index 100% rename from crates/relayburn-analyze/src/provider_reattribution.rs rename to crates/relayburn-sdk/src/analyze/provider_reattribution.rs diff --git a/crates/relayburn-analyze/src/quality.rs b/crates/relayburn-sdk/src/analyze/quality.rs similarity index 99% rename from crates/relayburn-analyze/src/quality.rs rename to crates/relayburn-sdk/src/analyze/quality.rs index 2ade007e..5131c3c2 100644 --- a/crates/relayburn-analyze/src/quality.rs +++ b/crates/relayburn-sdk/src/analyze/quality.rs @@ -15,7 +15,7 @@ use std::collections::HashMap; -use relayburn_reader::{ContentKind, ContentRecord, ContentRole, TurnRecord}; +use crate::reader::{ContentKind, ContentRecord, ContentRole, TurnRecord}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -475,7 +475,7 @@ fn days_from_civil(y: i32, m: u32, d: u32) -> i64 { #[cfg(test)] mod tests { use super::*; - use relayburn_reader::{ + use crate::reader::{ ContentKind, ContentRecord, ContentRole, SourceKind, Subagent, ToolCall, Usage, }; diff --git a/crates/relayburn-analyze/src/replacement_savings.rs b/crates/relayburn-sdk/src/analyze/replacement_savings.rs similarity index 99% rename from crates/relayburn-analyze/src/replacement_savings.rs rename to crates/relayburn-sdk/src/analyze/replacement_savings.rs index 0a571f15..9bede84c 100644 --- a/crates/relayburn-analyze/src/replacement_savings.rs +++ b/crates/relayburn-sdk/src/analyze/replacement_savings.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use indexmap::IndexMap; use phf::phf_map; -use relayburn_reader::{ToolCall, TurnRecord}; +use crate::reader::{ToolCall, TurnRecord}; /// Average tokens (input + output) one vanilla call of each tool consumes. /// Numbers mirror `DEFAULT_REPLACED_TOOL_TOKEN_COST` in the TS implementation. @@ -170,7 +170,7 @@ pub fn summarize_replacement_savings( #[cfg(test)] mod tests { use super::*; - use relayburn_reader::{SourceKind, Usage}; + use crate::reader::{SourceKind, Usage}; fn empty_usage() -> Usage { Usage { diff --git a/crates/relayburn-analyze/src/subagent_tree.rs b/crates/relayburn-sdk/src/analyze/subagent_tree.rs similarity index 99% rename from crates/relayburn-analyze/src/subagent_tree.rs rename to crates/relayburn-sdk/src/analyze/subagent_tree.rs index 4d1a43db..c7fc296c 100644 --- a/crates/relayburn-analyze/src/subagent_tree.rs +++ b/crates/relayburn-sdk/src/analyze/subagent_tree.rs @@ -8,11 +8,11 @@ //! `TurnRecord.subagent` only. use indexmap::{IndexMap, IndexSet}; -use relayburn_reader::{RelationshipType, SessionRelationshipRecord, TurnRecord}; +use crate::reader::{RelationshipType, SessionRelationshipRecord, TurnRecord}; use serde::{Deserialize, Serialize}; -use crate::cost::cost_for_turn; -use crate::pricing::PricingTable; +use crate::analyze::cost::cost_for_turn; +use crate::analyze::pricing::PricingTable; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -833,8 +833,8 @@ fn percentile(sorted: &[f64], p: f64) -> f64 { #[cfg(test)] mod tests { use super::*; - use crate::pricing::load_builtin_pricing; - use relayburn_reader::{ + use crate::analyze::pricing::load_builtin_pricing; + use crate::reader::{ RelationshipSourceKind, RelationshipType, SourceKind, Subagent, ToolCall, TurnRecord, Usage, }; diff --git a/crates/relayburn-analyze/src/tool_call_patterns.rs b/crates/relayburn-sdk/src/analyze/tool_call_patterns.rs similarity index 99% rename from crates/relayburn-analyze/src/tool_call_patterns.rs rename to crates/relayburn-sdk/src/analyze/tool_call_patterns.rs index c96c596f..8042a635 100644 --- a/crates/relayburn-analyze/src/tool_call_patterns.rs +++ b/crates/relayburn-sdk/src/analyze/tool_call_patterns.rs @@ -10,16 +10,16 @@ use std::collections::{BTreeMap, HashSet}; use phf::phf_set; -use relayburn_reader::{ +use crate::reader::{ normalize_tool_name, parse_bash_command, BashParse, SourceKind, ToolCall, TurnRecord, }; use serde::{Deserialize, Serialize}; -use crate::cost::lookup_model_rate; -use crate::findings::{ +use crate::analyze::cost::lookup_model_rate; +use crate::analyze::findings::{ severity_from_usd_pub as severity_from_usd, EstimatedSavings, WasteAction, WasteFinding, }; -use crate::pricing::PricingTable; +use crate::analyze::pricing::PricingTable; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] @@ -537,9 +537,9 @@ Estimated overhead: {tokens} tokens ({usd} at this session's input rate).{eviden #[cfg(test)] mod tests { use super::*; - use crate::findings::WasteSeverity; - use crate::pricing::load_builtin_pricing; - use relayburn_reader::{SourceKind, Usage}; + use crate::analyze::findings::WasteSeverity; + use crate::analyze::pricing::load_builtin_pricing; + use crate::reader::{SourceKind, Usage}; fn usage() -> Usage { Usage { diff --git a/crates/relayburn-analyze/src/tool_output_bloat.rs b/crates/relayburn-sdk/src/analyze/tool_output_bloat.rs similarity index 99% rename from crates/relayburn-analyze/src/tool_output_bloat.rs rename to crates/relayburn-sdk/src/analyze/tool_output_bloat.rs index ac04fb63..5df4bc52 100644 --- a/crates/relayburn-analyze/src/tool_output_bloat.rs +++ b/crates/relayburn-sdk/src/analyze/tool_output_bloat.rs @@ -22,15 +22,15 @@ use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; -use relayburn_reader::types::UserTurnBlockKind; -use relayburn_reader::{ +use crate::reader::types::UserTurnBlockKind; +use crate::reader::{ normalize_tool_name, SourceKind, ToolResultEventRecord, ToolResultEventSource, TurnRecord, UserTurnRecord, }; -use crate::cost::lookup_model_rate; -use crate::findings::{EstimatedSavings, WasteAction, WasteFinding, WasteSeverity}; -use crate::pricing::PricingTable; +use crate::analyze::cost::lookup_model_rate; +use crate::analyze::findings::{EstimatedSavings, WasteAction, WasteFinding, WasteSeverity}; +use crate::analyze::pricing::PricingTable; /// Env-var key the static-config check fires on. Claude's harness exposes /// this inside `.claude/settings.json` under `env.BASH_MAX_OUTPUT_LENGTH`. @@ -649,8 +649,8 @@ Estimated next-turn carry cost {usd}. {advice}", #[cfg(test)] mod tests { use super::*; - use crate::pricing::load_builtin_pricing; - use relayburn_reader::{ + use crate::analyze::pricing::load_builtin_pricing; + use crate::reader::{ ToolCall, ToolResultEventSource, ToolResultStatus, Usage, UserTurnBlock, }; use serde_json::json; @@ -1503,7 +1503,7 @@ mod tests { #[test] fn fixture_claude_oversized_bash_output_enriched_path() { - use relayburn_reader::{parse_claude_session, ClaudeParseOptions}; + use crate::reader::{parse_claude_session, ClaudeParseOptions}; let pricing = load_builtin_pricing(); let path = workspace_fixture("claude/oversized-bash-output.jsonl"); let parsed = parse_claude_session(&path, &ClaudeParseOptions::default()).expect("parses"); @@ -1527,7 +1527,7 @@ mod tests { #[test] fn fixture_claude_oversized_bash_output_content_length_fallback() { - use relayburn_reader::{parse_claude_session, ClaudeParseOptions}; + use crate::reader::{parse_claude_session, ClaudeParseOptions}; let pricing = load_builtin_pricing(); let path = workspace_fixture("claude/oversized-bash-output.jsonl"); let parsed = parse_claude_session(&path, &ClaudeParseOptions::default()).expect("parses"); @@ -1547,7 +1547,7 @@ mod tests { #[test] fn fixture_codex_oversized_shell_output() { - use relayburn_reader::{parse_codex_session, ParseCodexOptions}; + use crate::reader::{parse_codex_session, ParseCodexOptions}; let pricing = load_builtin_pricing(); let path = workspace_fixture("codex/oversized-shell-output.jsonl"); let parsed = parse_codex_session(&path, &ParseCodexOptions::default()).expect("parses"); diff --git a/crates/relayburn-sdk/src/export_verbs.rs b/crates/relayburn-sdk/src/export_verbs.rs index 2aa61687..a2fdb7b6 100644 --- a/crates/relayburn-sdk/src/export_verbs.rs +++ b/crates/relayburn-sdk/src/export_verbs.rs @@ -1,7 +1,7 @@ //! Export + search verbs — `search`, `export_ledger`, `export_stamps`. //! //! Wraps the FTS5 search and JSONL export APIs on -//! [`relayburn_ledger::Ledger`]. Each verb appears as an +//! [`crate::ledger::Ledger`]. Each verb appears as an //! [`LedgerHandle`] method (sync, returns [`anyhow::Result`]) plus a //! free-function form that opens its own handle from a //! [`LedgerOpenOptions`]. @@ -15,7 +15,7 @@ use crate::{Ledger, LedgerHandle, LedgerOpenOptions, SearchHit, SearchOptions}; // --- search ---------------------------------------------------------------- /// Options for the FTS5 search verb. Equivalent to -/// [`relayburn_ledger::SearchOptions`] but owns its strings so callers can +/// [`crate::ledger::SearchOptions`] but owns its strings so callers can /// build it without juggling lifetimes. #[derive(Debug, Clone)] pub struct SearchQueryOptions { @@ -112,7 +112,7 @@ pub struct ExportLedgerOptions { impl LedgerHandle { /// Stream every event row as a JSONL-shaped [`serde_json::Value`]. /// Each value has the form `{"v":1,"kind":"","record":}`, - /// matching the bytes [`relayburn_ledger::Ledger::export_ledger_jsonl`] + /// matching the bytes [`crate::ledger::Ledger::export_ledger_jsonl`] /// would write. /// /// Buffered into a `Vec` for v1 (relayburn ledgers are small enough @@ -295,7 +295,7 @@ mod tests { #[test] fn export_ledger_returns_one_value_per_record() { - use relayburn_reader::{TurnRecord, Usage}; + use crate::reader::{TurnRecord, Usage}; let tmp = tempfile::TempDir::new().unwrap(); let mut handle = open_handle(&tmp); @@ -355,7 +355,7 @@ mod tests { #[test] fn export_stamps_returns_appended_stamps() { - use relayburn_ledger::{Stamp, StampSelector}; + use crate::ledger::{Stamp, StampSelector}; use std::collections::BTreeMap; let tmp = tempfile::TempDir::new().unwrap(); diff --git a/crates/relayburn-ingest/src/lib.rs b/crates/relayburn-sdk/src/ingest.rs similarity index 52% rename from crates/relayburn-ingest/src/lib.rs rename to crates/relayburn-sdk/src/ingest.rs index 84e4bae9..00264dcb 100644 --- a/crates/relayburn-ingest/src/lib.rs +++ b/crates/relayburn-sdk/src/ingest.rs @@ -10,15 +10,20 @@ //! Example: run a one-shot ingest sweep against the default ledger paths. //! //! ```no_run -//! use relayburn_ingest::{ingest_all, IngestOptions}; -//! use relayburn_ledger::Ledger; +//! use relayburn_sdk::{ingest_all, RawIngestOptions, RawLedger}; //! # async fn run() -> anyhow::Result<()> { -//! let mut ledger = Ledger::open_default()?; -//! let report = ingest_all(&mut ledger, &IngestOptions::default()).await?; +//! let mut ledger = RawLedger::open_default()?; +//! let report = ingest_all(&mut ledger, &RawIngestOptions::default()).await?; //! println!("ingested {} turns", report.appended_turns); //! # Ok(()) } //! ``` +// The four absorbed module roots carry the lower crates whole, including +// items the SDK does not re-export (dead from the SDK perspective). Silence +// the never-used warnings rather than handpicking re-exports — the next +// agent absorbing more verbs will need them. +#![allow(dead_code, unused_imports)] + pub mod cursors; pub mod gap; pub mod ingest; @@ -27,6 +32,34 @@ pub mod reingest; pub mod walk; pub mod watch_loop; +// Tests preserved from the pre-restructure `relayburn-ingest` integration +// `tests/` directory. They were promoted to in-crate tests when the +// monolith collapsed because they exercise crate-private items +// (`parse_pending_stamp`, `set_ingest_gap_writer`, `LedgerLayout`, etc.) +// that the new SDK surface intentionally doesn't re-export. +// +// Lock note: the original tests each owned their own `static ENV_LOCK` +// because they ran as separate integration-test binaries (= separate +// processes), so the locks didn't need to coordinate. Bundled into one +// binary those statics become distinct mutexes in the same address +// space — `$RELAYBURN_HOME` mutations in `orchestration_tests` then +// race gap-state mutations in `gap_warning_tests`. The shared +// `TEST_ENV_LOCK` and `TEST_GAP_LOCK` below fix that — every test that +// touches `$RELAYBURN_HOME` or the process-global gap tracker takes them. +#[cfg(test)] +mod gap_warning_tests; +#[cfg(test)] +mod orchestration_tests; +#[cfg(test)] +mod pending_stamps_compat_tests; +#[cfg(test)] +mod watch_loop_tests; + +#[cfg(test)] +pub(crate) static TEST_ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); +#[cfg(test)] +pub(crate) static TEST_GAP_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + pub use cursors::{ load_cursors, save_cursor_changes, save_cursors, ClaudeCursor, CodexCumulative, CodexCursor, Cursors, FileCursor, OpencodeCursor, OpencodeStreamCursor, @@ -40,7 +73,7 @@ pub use ingest::{ ingest_all, ingest_claude_projects, ingest_claude_session, ingest_codex_sessions, ingest_opencode_sessions, IngestOptions, IngestReport, IngestRoots, }; -pub use relayburn_reader::ContentStoreMode; +pub use crate::reader::ContentStoreMode; pub use reingest::{derive_codex_session_id, reingest_missing_content, ReingestContentReport}; pub use pending_stamps::{ cleanup_stale_pending_stamps, cleanup_stale_pending_stamps_at, pending_stamps_dir, diff --git a/crates/relayburn-ingest/src/cursors.rs b/crates/relayburn-sdk/src/ingest/cursors.rs similarity index 99% rename from crates/relayburn-ingest/src/cursors.rs rename to crates/relayburn-sdk/src/ingest/cursors.rs index 2a551a3b..92a65632 100644 --- a/crates/relayburn-ingest/src/cursors.rs +++ b/crates/relayburn-sdk/src/ingest/cursors.rs @@ -13,7 +13,7 @@ use std::collections::BTreeMap; -use relayburn_ledger::{Ledger, Result as LedgerResult}; +use crate::ledger::{Ledger, Result as LedgerResult}; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; diff --git a/crates/relayburn-ingest/src/gap.rs b/crates/relayburn-sdk/src/ingest/gap.rs similarity index 98% rename from crates/relayburn-ingest/src/gap.rs rename to crates/relayburn-sdk/src/ingest/gap.rs index 87e13023..b3224002 100644 --- a/crates/relayburn-ingest/src/gap.rs +++ b/crates/relayburn-sdk/src/ingest/gap.rs @@ -40,7 +40,7 @@ use std::collections::{HashMap, HashSet}; use std::sync::{Arc, Mutex, MutexGuard, OnceLock}; -use relayburn_reader::{ContentKind, ContentRecord, ContentStoreMode, TurnRecord}; +use crate::reader::{ContentKind, ContentRecord, ContentStoreMode, TurnRecord}; /// Adapter discriminator for the gap tracker. Mirrors the TS /// `'claude' | 'codex' | 'opencode'` union. @@ -359,16 +359,20 @@ mod tests { use super::*; use std::sync::{Mutex, MutexGuard}; - use relayburn_reader::{ + use crate::reader::{ ContentKind, ContentRecord, ContentRole, SourceKind, ToolCall, TurnRecord, Usage, }; // The gap state is process-global, so we serialize the tests that - // mutate it. A panicking test poisons the lock; recover the inner - // value so a peer test still runs. + // mutate it. The shared `TEST_GAP_LOCK` (declared in `crate::ingest`) + // is used here too — without that, this module's tests would race + // gap_warning_tests, which mutate the same global tracker. A + // panicking test poisons the lock; recover the inner value so a + // peer test still runs. fn test_lock() -> MutexGuard<'static, ()> { - static LOCK: Mutex<()> = Mutex::new(()); - LOCK.lock().unwrap_or_else(|e| e.into_inner()) + super::super::TEST_GAP_LOCK + .lock() + .unwrap_or_else(|e| e.into_inner()) } fn make_turn(session: &str, message: &str, tool_call_count: usize) -> TurnRecord { diff --git a/crates/relayburn-ingest/tests/gap_warning_integration.rs b/crates/relayburn-sdk/src/ingest/gap_warning_tests.rs similarity index 98% rename from crates/relayburn-ingest/tests/gap_warning_integration.rs rename to crates/relayburn-sdk/src/ingest/gap_warning_tests.rs index 506eb093..5a9011b6 100644 --- a/crates/relayburn-ingest/tests/gap_warning_integration.rs +++ b/crates/relayburn-sdk/src/ingest/gap_warning_tests.rs @@ -25,18 +25,19 @@ use std::fs; use std::path::Path; use std::sync::{Arc, Mutex}; -use relayburn_ingest::{ +use crate::ingest::{ ingest_all, ingest_claude_projects, ingest_codex_sessions, ingest_opencode_sessions, reset_ingest_gap_warnings, restore_ingest_gap_writer, set_ingest_gap_writer, IngestOptions, IngestRoots, }; -use relayburn_ledger::{Ledger, LedgerLayout}; +use crate::ledger::{Ledger, LedgerLayout}; use tempfile::TempDir; -/// Serialises env mutation so `RELAYBURN_HOME` is stable for each test body. -static ENV_LOCK: Mutex<()> = Mutex::new(()); -/// Serialises the process-global gap state so tests don't bleed into each other. -static GAP_LOCK: Mutex<()> = Mutex::new(()); +// Shared with the other absorbed test modules in the same binary so +// `$RELAYBURN_HOME` mutations and gap-state mutations don't interleave. +// See note in `crate::ingest`. +use super::TEST_ENV_LOCK as ENV_LOCK; +use super::TEST_GAP_LOCK as GAP_LOCK; fn open_ledger_in(tmp: &TempDir) -> Ledger { let layout = LedgerLayout::under(tmp.path().join("ledger")); diff --git a/crates/relayburn-ingest/src/ingest.rs b/crates/relayburn-sdk/src/ingest/ingest.rs similarity index 98% rename from crates/relayburn-ingest/src/ingest.rs rename to crates/relayburn-sdk/src/ingest/ingest.rs index bc4fba89..7b5be500 100644 --- a/crates/relayburn-ingest/src/ingest.rs +++ b/crates/relayburn-sdk/src/ingest/ingest.rs @@ -13,7 +13,7 @@ //! the `ingest_claude_session` fast-path. The gap-warning state machine //! (`gap`) and `reingest_missing_content` (`reingest`) landed in #278 and //! depend on the freshly-ported `Ledger::list_content_session_ids` / -//! `Ledger::list_user_turn_session_ids` plus `relayburn_ledger::load_config` +//! `Ledger::list_user_turn_session_ids` plus `crate::ledger::load_config` //! from #279. use std::collections::HashMap; @@ -24,8 +24,8 @@ use std::time::SystemTime; use serde::{Deserialize, Serialize}; use serde_json::Value; -use relayburn_ledger::{load_config, Ledger}; -use relayburn_reader::{ +use crate::ledger::{load_config, Ledger}; +use crate::reader::{ parse_claude_session, parse_claude_session_incremental, parse_codex_session_incremental, parse_opencode_session_incremental, reconcile_claude_session_relationships, ClaudeParseIncrementalOptions, ClaudeParseOptions, CodexLastCompletedTurn, CodexResumeState, @@ -34,20 +34,20 @@ use relayburn_reader::{ ReconcileClaudeRelationshipsInput, }; -use crate::cursors::{ +use crate::ingest::cursors::{ load_cursors, save_cursor_changes, ClaudeCursor, CodexCumulative, CodexCursor, Cursors, FileCursor, OpencodeCursor, }; -use crate::gap::{ +use crate::ingest::gap::{ count_new_tool_calls, count_new_tool_results, emit_gap_warning, record_session_gap, AdapterName, }; -use crate::pending_stamps::{ +use crate::ingest::pending_stamps::{ cleanup_stale_pending_stamps, resolve_pending_stamps_for_session, PendingStampHarness, PendingStampSessionCandidate, }; -use crate::reingest::derive_codex_session_id; -use crate::walk::{walk_jsonl, walk_opencode_sessions}; +use crate::ingest::reingest::derive_codex_session_id; +use crate::ingest::walk::{walk_jsonl, walk_opencode_sessions}; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -865,7 +865,7 @@ fn user_turn_slot_from_value(v: &Value) -> Option { // JSON object we wrote out via `user_turn_slot_to_value`. let obj = v.as_object()?; let blocks_v = obj.get("blocks")?.clone(); - let blocks: Vec = serde_json::from_value(blocks_v).ok()?; + let blocks: Vec = serde_json::from_value(blocks_v).ok()?; let preceding_message_id = obj .get("precedingMessageId") .and_then(Value::as_str) diff --git a/crates/relayburn-ingest/tests/orchestration.rs b/crates/relayburn-sdk/src/ingest/orchestration_tests.rs similarity index 97% rename from crates/relayburn-ingest/tests/orchestration.rs rename to crates/relayburn-sdk/src/ingest/orchestration_tests.rs index fbd8ad40..770b8da0 100644 --- a/crates/relayburn-ingest/tests/orchestration.rs +++ b/crates/relayburn-sdk/src/ingest/orchestration_tests.rs @@ -29,17 +29,19 @@ use std::collections::HashSet; use std::fs; use std::path::{Path, PathBuf}; -use std::sync::Mutex; -use relayburn_ingest::{ +use crate::ingest::{ ingest_all, ingest_claude_projects, ingest_claude_session, ingest_codex_sessions, ingest_opencode_sessions, IngestOptions, IngestRoots, }; -use relayburn_ingest::{load_cursors, ClaudeCursor, FileCursor}; -use relayburn_ledger::{Ledger, LedgerLayout, Query}; +use crate::ingest::{load_cursors, ClaudeCursor, FileCursor}; +use crate::ledger::{Ledger, LedgerLayout, Query}; use tempfile::TempDir; -static ENV_LOCK: Mutex<()> = Mutex::new(()); +// Shared with gap_warning_tests / watch_loop_tests so that +// `$RELAYBURN_HOME` mutations stay serialised across all test modules +// in the same binary. See note in `crate::ingest`. +use super::TEST_ENV_LOCK as ENV_LOCK; fn shared_fixture_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) diff --git a/crates/relayburn-ingest/src/pending_stamps.rs b/crates/relayburn-sdk/src/ingest/pending_stamps.rs similarity index 99% rename from crates/relayburn-ingest/src/pending_stamps.rs rename to crates/relayburn-sdk/src/ingest/pending_stamps.rs index 87f07aad..8e6c2d7f 100644 --- a/crates/relayburn-ingest/src/pending_stamps.rs +++ b/crates/relayburn-sdk/src/ingest/pending_stamps.rs @@ -28,7 +28,7 @@ use std::io::Write; use std::path::{Path, PathBuf}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use relayburn_ledger::{ledger_home, Enrichment, Ledger, Stamp, StampSelector}; +use crate::ledger::{ledger_home, Enrichment, Ledger, Stamp, StampSelector}; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; diff --git a/crates/relayburn-ingest/tests/pending_stamps_compat.rs b/crates/relayburn-sdk/src/ingest/pending_stamps_compat_tests.rs similarity index 82% rename from crates/relayburn-ingest/tests/pending_stamps_compat.rs rename to crates/relayburn-sdk/src/ingest/pending_stamps_compat_tests.rs index b07cb00c..d68798fd 100644 --- a/crates/relayburn-ingest/tests/pending_stamps_compat.rs +++ b/crates/relayburn-sdk/src/ingest/pending_stamps_compat_tests.rs @@ -23,9 +23,9 @@ fn fixture_path(name: &str) -> PathBuf { fn rust_parses_ts_written_stamp() { let raw = std::fs::read_to_string(fixture_path("ts_codex_stamp.json")).unwrap(); let parsed = - relayburn_ingest::pending_stamps::parse_pending_stamp(&raw).expect("ts fixture parses"); + crate::ingest::pending_stamps::parse_pending_stamp(&raw).expect("ts fixture parses"); assert_eq!(parsed.v, 1); - assert_eq!(parsed.harness, relayburn_ingest::PendingStampHarness::Codex); + assert_eq!(parsed.harness, crate::ingest::PendingStampHarness::Codex); assert_eq!(parsed.spawner_pid, 12345); assert_eq!(parsed.spawn_start_ts, "2025-05-01T12:34:56.789Z"); assert_eq!(parsed.cwd, "/home/user/repo"); @@ -40,8 +40,8 @@ fn rust_parses_ts_written_stamp() { #[test] fn rust_reserialization_is_byte_identical_to_ts_fixture() { let raw = std::fs::read_to_string(fixture_path("ts_codex_stamp.json")).unwrap(); - let parsed = relayburn_ingest::pending_stamps::parse_pending_stamp(&raw).unwrap(); - let reserialized = relayburn_ingest::pending_stamps::serialize_stamp(&parsed); + let parsed = crate::ingest::pending_stamps::parse_pending_stamp(&raw).unwrap(); + let reserialized = crate::ingest::pending_stamps::serialize_stamp(&parsed); assert_eq!( reserialized, raw, "Rust re-serialization diverged from TS-written fixture" @@ -50,8 +50,8 @@ fn rust_reserialization_is_byte_identical_to_ts_fixture() { #[test] fn rust_written_stamp_round_trips_through_parser() { - use relayburn_ingest::pending_stamps::{parse_pending_stamp, serialize_stamp}; - use relayburn_ingest::{PendingStamp, PendingStampHarness}; + use crate::ingest::pending_stamps::{parse_pending_stamp, serialize_stamp}; + use crate::ingest::{PendingStamp, PendingStampHarness}; let mut enrichment = std::collections::BTreeMap::new(); enrichment.insert("role".into(), "ship".into()); @@ -79,7 +79,7 @@ fn rust_written_stamp_round_trips_through_parser() { fn rejects_wrong_version() { let raw = r#"{"v":2,"harness":"codex","spawnerPid":1,"spawnStartTs":"2025-05-01T00:00:00.000Z","cwd":"/x","enrichment":{}}"#; assert!( - relayburn_ingest::pending_stamps::parse_pending_stamp(raw).is_none(), + crate::ingest::pending_stamps::parse_pending_stamp(raw).is_none(), "bumped version must trip the parser so a forward-incompatible writer can't poison the matcher" ); } @@ -87,5 +87,5 @@ fn rejects_wrong_version() { #[test] fn rejects_unknown_harness() { let raw = r#"{"v":1,"harness":"cursor","spawnerPid":1,"spawnStartTs":"2025-05-01T00:00:00.000Z","cwd":"/x","enrichment":{}}"#; - assert!(relayburn_ingest::pending_stamps::parse_pending_stamp(raw).is_none()); + assert!(crate::ingest::pending_stamps::parse_pending_stamp(raw).is_none()); } diff --git a/crates/relayburn-ingest/src/reingest.rs b/crates/relayburn-sdk/src/ingest/reingest.rs similarity index 98% rename from crates/relayburn-ingest/src/reingest.rs rename to crates/relayburn-sdk/src/ingest/reingest.rs index 4740f076..5f0fd014 100644 --- a/crates/relayburn-ingest/src/reingest.rs +++ b/crates/relayburn-sdk/src/ingest/reingest.rs @@ -29,21 +29,21 @@ use std::path::{Path, PathBuf}; -use relayburn_reader::{ +use crate::reader::{ parse_claude_session_incremental, parse_codex_session_incremental, parse_opencode_session_incremental, read_codex_session_id_hint, ClaudeParseIncrementalOptions, ContentRecord, ContentStoreMode, ParseCodexIncrementalOptions, ParseOpencodeIncrementalOptions, UserTurnRecord, }; -use relayburn_ledger::Ledger; +use crate::ledger::Ledger; use serde::{Deserialize, Serialize}; use std::collections::{BTreeSet, HashSet}; -use crate::ingest::{ +use crate::ingest::ingest::{ claude_projects_dir, codex_sessions_dir, opencode_message_root, opencode_session_root, IngestOptions, IngestRoots, }; -use crate::walk::{walk_jsonl, walk_opencode_sessions}; +use crate::ingest::walk::{walk_jsonl, walk_opencode_sessions}; /// Outcome of [`reingest_missing_content`]. Mirrors the TS /// `ReingestContentReport` shape one-to-one. @@ -381,13 +381,13 @@ mod tests { use std::path::PathBuf; use tempfile::TempDir; - use relayburn_ledger::{Ledger, LedgerLayout}; - use relayburn_reader::{ + use crate::ledger::{Ledger, LedgerLayout}; + use crate::reader::{ ContentKind, ContentRecord, ContentRole, SourceKind, UserTurnBlock, UserTurnBlockKind, UserTurnRecord, }; - use crate::ingest::{IngestOptions, IngestRoots}; + use crate::ingest::ingest::{IngestOptions, IngestRoots}; fn open_ledger(tmp: &TempDir) -> Ledger { let layout = LedgerLayout::under(tmp.path()); diff --git a/crates/relayburn-ingest/src/walk.rs b/crates/relayburn-sdk/src/ingest/walk.rs similarity index 100% rename from crates/relayburn-ingest/src/walk.rs rename to crates/relayburn-sdk/src/ingest/walk.rs diff --git a/crates/relayburn-ingest/src/watch_loop.rs b/crates/relayburn-sdk/src/ingest/watch_loop.rs similarity index 99% rename from crates/relayburn-ingest/src/watch_loop.rs rename to crates/relayburn-sdk/src/ingest/watch_loop.rs index 2814c632..d86e4c19 100644 --- a/crates/relayburn-ingest/src/watch_loop.rs +++ b/crates/relayburn-sdk/src/ingest/watch_loop.rs @@ -26,7 +26,7 @@ use std::time::Duration; use tokio::sync::{Mutex, Notify}; use tokio::task::JoinHandle; -use crate::ingest::IngestReport; +use crate::ingest::ingest::IngestReport; pub type IngestFn = Arc< dyn Fn() -> Pin> + Send>> + Send + Sync, diff --git a/crates/relayburn-ingest/tests/watch_loop_integration.rs b/crates/relayburn-sdk/src/ingest/watch_loop_tests.rs similarity index 98% rename from crates/relayburn-ingest/tests/watch_loop_integration.rs rename to crates/relayburn-sdk/src/ingest/watch_loop_tests.rs index bd6e6835..a55c4156 100644 --- a/crates/relayburn-ingest/tests/watch_loop_integration.rs +++ b/crates/relayburn-sdk/src/ingest/watch_loop_tests.rs @@ -10,8 +10,8 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; -use relayburn_ingest::watch_loop::{start_watch_loop, IngestFn, StartWatchLoopOptions}; -use relayburn_ingest::IngestReport; +use crate::ingest::watch_loop::{start_watch_loop, IngestFn, StartWatchLoopOptions}; +use crate::ingest::IngestReport; #[tokio::test(flavor = "current_thread", start_paused = true)] async fn watch_loop_drains_pending_work_within_two_ticks() { diff --git a/crates/relayburn-sdk/src/ingest_verb.rs b/crates/relayburn-sdk/src/ingest_verb.rs index 8102c23c..0ddf1e8e 100644 --- a/crates/relayburn-sdk/src/ingest_verb.rs +++ b/crates/relayburn-sdk/src/ingest_verb.rs @@ -1,4 +1,4 @@ -//! Ingest verb — async wrapper over [`relayburn_ingest::ingest_all`]. +//! Ingest verb — async wrapper over [`crate::ingest::ingest_all`]. //! //! Mirrors the TS `ingest` verb in `packages/sdk/index.js`. The Rust port //! threads the ledger location through [`crate::Ledger::open`] explicitly @@ -7,7 +7,7 @@ use std::path::PathBuf; -use relayburn_ingest::{ingest_all, IngestOptions as RawIngestOptions, IngestReport, IngestRoots}; +use crate::ingest::{ingest_all, IngestOptions as RawIngestOptions, IngestReport, IngestRoots}; use crate::{Ledger, LedgerHandle, LedgerOpenOptions}; diff --git a/crates/relayburn-ledger/src/lib.rs b/crates/relayburn-sdk/src/ledger.rs similarity index 89% rename from crates/relayburn-ledger/src/lib.rs rename to crates/relayburn-sdk/src/ledger.rs index 4b0cb244..16643e26 100644 --- a/crates/relayburn-ledger/src/lib.rs +++ b/crates/relayburn-sdk/src/ledger.rs @@ -6,11 +6,17 @@ //! for the full rationale. //! //! ```no_run -//! use relayburn_ledger::Ledger; -//! let ledger = Ledger::open_default().unwrap(); +//! use relayburn_sdk::RawLedger; +//! let ledger = RawLedger::open_default().unwrap(); //! // append turns / compactions / stamps / content via Ledger methods. //! ``` +// The four absorbed module roots carry the lower crates whole, including +// items the SDK does not re-export (dead from the SDK perspective). Silence +// the never-used warnings rather than handpicking re-exports — the next +// agent absorbing more verbs will need them. +#![allow(dead_code, unused_imports)] + mod config; mod content; mod db; @@ -28,28 +34,28 @@ use std::path::{Path, PathBuf}; use rusqlite::params; -pub use crate::config::{ +pub use crate::ledger::config::{ config_path, load_config, load_config_at, BurnConfig, ContentConfig, Retention, DEFAULT_RETENTION_DAYS, }; -pub use crate::content::{PruneStats, SearchHit, SearchOptions}; -pub use crate::error::{LedgerError, Result}; -pub use crate::fingerprint::{ +pub use crate::ledger::content::{PruneStats, SearchHit, SearchOptions}; +pub use crate::ledger::error::{LedgerError, Result}; +pub use crate::ledger::fingerprint::{ compaction_id_fingerprint, content_blob_fingerprint, relationship_id_fingerprint, tool_result_event_id_fingerprint, turn_content_fingerprint, turn_id_fingerprint, user_turn_id_fingerprint, }; -pub use crate::paths::{ +pub use crate::ledger::paths::{ burn_sqlite_path, content_sqlite_path, is_valid_session_id, ledger_home, }; -pub use crate::query::Query; -pub use crate::reader::EnrichedTurn; -pub use crate::schema::{DERIVABLE_TABLES, FIRST_PARTY_TABLES, SCHEMA_VERSION}; -pub use crate::stamp::{ +pub use crate::ledger::query::Query; +pub use crate::ledger::reader::EnrichedTurn; +pub use crate::ledger::schema::{DERIVABLE_TABLES, FIRST_PARTY_TABLES, SCHEMA_VERSION}; +pub use crate::ledger::stamp::{ stamp_matches, Enrichment, MessageRange, Stamp, StampError, StampSelector, }; -use crate::db::Connections; +use crate::ledger::db::Connections; /// Owning handle on the two SQLite databases. Holds open connections, /// applies DDL on first open, and exposes append / query / search / state @@ -86,34 +92,34 @@ impl Ledger { // --- append paths ------------------------------------------------- - pub fn append_turns(&mut self, turns: &[relayburn_reader::TurnRecord]) -> Result { + pub fn append_turns(&mut self, turns: &[crate::reader::TurnRecord]) -> Result { writer::append_turns(&mut self.conns.burn, turns) } pub fn append_compactions( &mut self, - events: &[relayburn_reader::CompactionEvent], + events: &[crate::reader::CompactionEvent], ) -> Result { writer::append_compactions(&mut self.conns.burn, events) } pub fn append_relationships( &mut self, - records: &[relayburn_reader::SessionRelationshipRecord], + records: &[crate::reader::SessionRelationshipRecord], ) -> Result { writer::append_relationships(&mut self.conns.burn, records) } pub fn append_tool_result_events( &mut self, - records: &[relayburn_reader::ToolResultEventRecord], + records: &[crate::reader::ToolResultEventRecord], ) -> Result { writer::append_tool_result_events(&mut self.conns.burn, records) } pub fn append_user_turns( &mut self, - records: &[relayburn_reader::UserTurnRecord], + records: &[crate::reader::UserTurnRecord], ) -> Result { writer::append_user_turns(&mut self.conns.burn, records) } @@ -124,7 +130,7 @@ impl Ledger { pub fn append_content( &mut self, - records: &[relayburn_reader::ContentRecord], + records: &[crate::reader::ContentRecord], ) -> Result { writer::append_content(&mut self.conns.content, records) } @@ -138,28 +144,28 @@ impl Ledger { pub fn query_compactions( &self, q: &Query, - ) -> Result> { + ) -> Result> { reader::query_compactions(&self.conns.burn, q) } pub fn query_relationships( &self, q: &Query, - ) -> Result> { + ) -> Result> { reader::query_relationships(&self.conns.burn, q) } pub fn query_tool_result_events( &self, q: &Query, - ) -> Result> { + ) -> Result> { reader::query_tool_result_events(&self.conns.burn, q) } pub fn query_user_turns( &self, q: &Query, - ) -> Result> { + ) -> Result> { reader::query_user_turns(&self.conns.burn, q) } diff --git a/crates/relayburn-ledger/src/config.rs b/crates/relayburn-sdk/src/ledger/config.rs similarity index 99% rename from crates/relayburn-ledger/src/config.rs rename to crates/relayburn-sdk/src/ledger/config.rs index c1a358ca..ab5660bf 100644 --- a/crates/relayburn-ledger/src/config.rs +++ b/crates/relayburn-sdk/src/ledger/config.rs @@ -18,10 +18,10 @@ use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; -use relayburn_reader::ContentStoreMode; +use crate::reader::ContentStoreMode; -use crate::error::Result; -use crate::paths::ledger_home; +use crate::ledger::error::Result; +use crate::ledger::paths::ledger_home; /// Default content retention window in days. Matches TS /// `DEFAULT_RETENTION_DAYS`. diff --git a/crates/relayburn-ledger/src/content.rs b/crates/relayburn-sdk/src/ledger/content.rs similarity index 98% rename from crates/relayburn-ledger/src/content.rs rename to crates/relayburn-sdk/src/ledger/content.rs index 1ba68c4c..6a1d97f4 100644 --- a/crates/relayburn-ledger/src/content.rs +++ b/crates/relayburn-sdk/src/ledger/content.rs @@ -17,8 +17,8 @@ use std::collections::HashSet; use rusqlite::{params, Connection}; use serde::{Deserialize, Serialize}; -use crate::error::Result; -use crate::paths::is_valid_session_id; +use crate::ledger::error::Result; +use crate::ledger::paths::is_valid_session_id; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct SearchHit { diff --git a/crates/relayburn-ledger/src/db.rs b/crates/relayburn-sdk/src/ledger/db.rs similarity index 95% rename from crates/relayburn-ledger/src/db.rs rename to crates/relayburn-sdk/src/ledger/db.rs index afdde4bc..400ba43f 100644 --- a/crates/relayburn-ledger/src/db.rs +++ b/crates/relayburn-sdk/src/ledger/db.rs @@ -16,8 +16,8 @@ use std::time::Duration; use rusqlite::Connection; -use crate::error::Result; -use crate::schema::{BURN_DDL, CONTENT_DDL, SCHEMA_VERSION}; +use crate::ledger::error::Result; +use crate::ledger::schema::{BURN_DDL, CONTENT_DDL, SCHEMA_VERSION}; const BUSY_TIMEOUT: Duration = Duration::from_secs(30); @@ -89,7 +89,7 @@ fn verify_schema_version(conn: &Connection) -> Result<()> { .map(|v| v as u32) .unwrap_or(SCHEMA_VERSION); if version > SCHEMA_VERSION { - return Err(crate::error::LedgerError::SchemaTooNew { + return Err(crate::ledger::error::LedgerError::SchemaTooNew { found: version, supported: SCHEMA_VERSION, }); diff --git a/crates/relayburn-ledger/src/error.rs b/crates/relayburn-sdk/src/ledger/error.rs similarity index 100% rename from crates/relayburn-ledger/src/error.rs rename to crates/relayburn-sdk/src/ledger/error.rs diff --git a/crates/relayburn-ledger/src/fingerprint.rs b/crates/relayburn-sdk/src/ledger/fingerprint.rs similarity index 98% rename from crates/relayburn-ledger/src/fingerprint.rs rename to crates/relayburn-sdk/src/ledger/fingerprint.rs index ad63d1d2..79ccece6 100644 --- a/crates/relayburn-ledger/src/fingerprint.rs +++ b/crates/relayburn-sdk/src/ledger/fingerprint.rs @@ -21,7 +21,7 @@ //! easy; the bound is conservative — birthday collisions at 2^32 turns //! are still under one in a billion. -use relayburn_reader::{ +use crate::reader::{ CompactionEvent, SessionRelationshipRecord, ToolResultEventRecord, TurnRecord, UserTurnRecord, }; use sha2::{Digest, Sha256}; @@ -126,7 +126,7 @@ fn serde_plain(value: &T) -> String { #[cfg(test)] mod tests { use super::*; - use relayburn_reader::{SourceKind, ToolCall, Usage}; + use crate::reader::{SourceKind, ToolCall, Usage}; fn turn(message_id: &str, ts: &str) -> TurnRecord { TurnRecord { diff --git a/crates/relayburn-ledger/src/paths.rs b/crates/relayburn-sdk/src/ledger/paths.rs similarity index 100% rename from crates/relayburn-ledger/src/paths.rs rename to crates/relayburn-sdk/src/ledger/paths.rs diff --git a/crates/relayburn-ledger/src/query.rs b/crates/relayburn-sdk/src/ledger/query.rs similarity index 97% rename from crates/relayburn-ledger/src/query.rs rename to crates/relayburn-sdk/src/ledger/query.rs index 83313e5e..ab97f022 100644 --- a/crates/relayburn-ledger/src/query.rs +++ b/crates/relayburn-sdk/src/ledger/query.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; -use relayburn_reader::SourceKind; +use crate::reader::SourceKind; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] diff --git a/crates/relayburn-ledger/src/reader.rs b/crates/relayburn-sdk/src/ledger/reader.rs similarity index 97% rename from crates/relayburn-ledger/src/reader.rs rename to crates/relayburn-sdk/src/ledger/reader.rs index 2e9915f7..75b86720 100644 --- a/crates/relayburn-ledger/src/reader.rs +++ b/crates/relayburn-sdk/src/ledger/reader.rs @@ -11,14 +11,14 @@ use std::collections::{BTreeMap, HashSet}; use rusqlite::{params, Connection}; use serde::{Deserialize, Serialize}; -use relayburn_reader::{ +use crate::reader::{ CompactionEvent, SessionRelationshipRecord, ToolResultEventRecord, TurnRecord, UserTurnRecord, }; -use crate::error::Result; -use crate::paths::is_valid_session_id; -use crate::query::Query; -use crate::stamp::{stamp_matches, Enrichment, Stamp, StampSelector}; +use crate::ledger::error::Result; +use crate::ledger::paths::is_valid_session_id; +use crate::ledger::query::Query; +use crate::ledger::stamp::{stamp_matches, Enrichment, Stamp, StampSelector}; /// A turn with stamp enrichment folded in. Enrichment is a flat /// string→string map; entries from later stamps win. @@ -346,7 +346,7 @@ fn validate_table(table: &str) -> Result<()> { if QUERYABLE_TABLES.contains(&table) { Ok(()) } else { - Err(crate::error::LedgerError::Other(format!( + Err(crate::ledger::error::LedgerError::Other(format!( "unknown ledger table: {table}" ))) } diff --git a/crates/relayburn-ledger/src/schema.rs b/crates/relayburn-sdk/src/ledger/schema.rs similarity index 100% rename from crates/relayburn-ledger/src/schema.rs rename to crates/relayburn-sdk/src/ledger/schema.rs diff --git a/crates/relayburn-ledger/src/stamp.rs b/crates/relayburn-sdk/src/ledger/stamp.rs similarity index 98% rename from crates/relayburn-ledger/src/stamp.rs rename to crates/relayburn-sdk/src/ledger/stamp.rs index d4c473d8..c80c4e41 100644 --- a/crates/relayburn-ledger/src/stamp.rs +++ b/crates/relayburn-sdk/src/ledger/stamp.rs @@ -16,7 +16,7 @@ use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; use thiserror::Error; -use relayburn_reader::TurnRecord; +use crate::reader::TurnRecord; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -132,7 +132,7 @@ pub fn stamp_matches(stamp: &Stamp, turn: &TurnRecord) -> bool { #[cfg(test)] mod tests { use super::*; - use relayburn_reader::{SourceKind, Usage}; + use crate::reader::{SourceKind, Usage}; fn make_turn(session: &str, message: &str, ts: &str) -> TurnRecord { TurnRecord { diff --git a/crates/relayburn-ledger/src/tests.rs b/crates/relayburn-sdk/src/ledger/tests.rs similarity index 99% rename from crates/relayburn-ledger/src/tests.rs rename to crates/relayburn-sdk/src/ledger/tests.rs index 5791de6c..8aad91a0 100644 --- a/crates/relayburn-ledger/src/tests.rs +++ b/crates/relayburn-sdk/src/ledger/tests.rs @@ -7,7 +7,7 @@ use std::collections::BTreeMap; use std::sync::{Arc, Barrier, Mutex}; use std::thread; -use relayburn_reader::{ +use crate::reader::{ ContentKind, ContentRecord, ContentRole, RelationshipSourceKind, RelationshipType, SessionRelationshipRecord, SourceKind, ToolCall, TurnRecord, Usage, UserTurnBlock, UserTurnBlockKind, UserTurnRecord, diff --git a/crates/relayburn-ledger/src/writer.rs b/crates/relayburn-sdk/src/ledger/writer.rs similarity index 97% rename from crates/relayburn-ledger/src/writer.rs rename to crates/relayburn-sdk/src/ledger/writer.rs index 26a84cdc..a0750a68 100644 --- a/crates/relayburn-ledger/src/writer.rs +++ b/crates/relayburn-sdk/src/ledger/writer.rs @@ -10,18 +10,18 @@ use rusqlite::{params, Connection, OptionalExtension}; -use relayburn_reader::{ +use crate::reader::{ CompactionEvent, ContentRecord, SessionRelationshipRecord, ToolResultEventRecord, TurnRecord, UserTurnRecord, }; -use crate::error::{LedgerError, Result}; -use crate::fingerprint::{ +use crate::ledger::error::{LedgerError, Result}; +use crate::ledger::fingerprint::{ compaction_id_fingerprint, content_blob_fingerprint, relationship_id_fingerprint, tool_result_event_id_fingerprint, turn_content_fingerprint, user_turn_id_fingerprint, }; -use crate::paths::is_valid_session_id; -use crate::stamp::Stamp; +use crate::ledger::paths::is_valid_session_id; +use crate::ledger::stamp::Stamp; fn now_iso() -> String { use std::time::{SystemTime, UNIX_EPOCH}; @@ -320,7 +320,7 @@ pub(crate) fn append_content( /// implied subagent relationship row. Returns `None` for stamps that /// don't target a session or don't carry the enrichment key. pub(crate) fn synthesize_relationship(stamp: &Stamp) -> Option { - use relayburn_reader::{RelationshipSourceKind, RelationshipType}; + use crate::reader::{RelationshipSourceKind, RelationshipType}; let session_id = stamp.selector.session_id.clone()?; if session_id.is_empty() { return None; diff --git a/crates/relayburn-sdk/src/lib.rs b/crates/relayburn-sdk/src/lib.rs index 4cf7c272..f6c24f0c 100644 --- a/crates/relayburn-sdk/src/lib.rs +++ b/crates/relayburn-sdk/src/lib.rs @@ -36,6 +36,18 @@ use std::path::PathBuf; +// Absorbed lower-stack modules. Order matches the dependency graph +// (reader → ledger → analyze → ingest); the verb modules below pull from +// them. These were standalone crates (#242–#245) until the monolith +// restructure; collapsing them removed a lockstep-publish requirement +// against crates.io that bought us nothing for ~3s of incremental rebuild +// tax. Each module's source-of-truth comment still points back to the TS +// sibling under `packages/`. +mod analyze; +mod ingest; +mod ledger; +mod reader; + // Verb modules — each is populated by a separate follow-up PR. They share // the `LedgerHandle` and `LedgerOpenOptions` types defined here, plus the // re-exports below. Keeping them in their own files lets the three @@ -60,7 +72,7 @@ pub use query_verbs::*; // destructure a result, without forcing them to add the lower crates to // their own `Cargo.toml`. The grouping mirrors the four wave-1 crates. -pub use relayburn_reader::{ +pub use crate::reader::{ parse_bash_command, resolve_project, ActivityCategory, BashParse, ClassificationInput, ClassificationResult, CompactionEvent, ContentKind, ContentRecord, ContentRole, ContentStoreMode, ContentToolResult, ContentToolUse, Coverage, Fidelity, FidelityClass, @@ -70,14 +82,14 @@ pub use relayburn_reader::{ UsageGranularity, UserTurnBlock, UserTurnBlockKind, UserTurnRecord, }; -pub use relayburn_ledger::{ +pub use crate::ledger::{ burn_sqlite_path, config_path, content_sqlite_path, is_valid_session_id, ledger_home, load_config, BurnConfig, ContentConfig, EnrichedTurn, Enrichment, Ledger as RawLedger, LedgerError, MessageRange, PruneStats, Query, RebuildSummary, Retention, SearchHit, SearchOptions, Stamp, StampError, StampSelector, DEFAULT_RETENTION_DAYS, }; -pub use relayburn_analyze::{ +pub use crate::analyze::{ aggregate_by_bash, aggregate_by_bash_verb, aggregate_by_file, aggregate_by_subagent, attribute_hotspots, attribute_overhead, build_trim_recommendations, cost_for_turn, cost_for_usage, detect_patterns, detect_tool_call_patterns, detect_tool_output_bloat, @@ -91,7 +103,7 @@ pub use relayburn_analyze::{ WasteSeverity, }; -pub use relayburn_ingest::{ +pub use crate::ingest::{ cleanup_stale_pending_stamps, ingest_all, IngestOptions as RawIngestOptions, IngestReport, IngestRoots, }; @@ -145,7 +157,7 @@ impl LedgerOpenOptions { // --- Ledger / LedgerHandle ------------------------------------------------- -/// Handle on an open relayburn ledger. Wraps [`relayburn_ledger::Ledger`] +/// Handle on an open relayburn ledger. Wraps [`crate::ledger::Ledger`] /// (re-exported as [`RawLedger`]) and exposes the SDK verb surface. /// /// Not `Sync`; wrap in a `Mutex` if you need to share it across threads. diff --git a/crates/relayburn-sdk/src/query_verbs.rs b/crates/relayburn-sdk/src/query_verbs.rs index 85ea5c5f..b8a2b850 100644 --- a/crates/relayburn-sdk/src/query_verbs.rs +++ b/crates/relayburn-sdk/src/query_verbs.rs @@ -15,7 +15,7 @@ use std::path::{Path, PathBuf}; use anyhow::Result; use serde::{Deserialize, Serialize}; -use relayburn_analyze::{ +use crate::analyze::{ aggregate_by_bash, aggregate_by_bash_verb, aggregate_by_file, aggregate_by_subagent, attribute_hotspots, attribute_overhead, build_trim_recommendations, cost_for_turn, detect_patterns, detect_tool_call_patterns, detect_tool_output_bloat, find_overhead_files, @@ -29,8 +29,8 @@ use relayburn_analyze::{ OverheadFile, OverheadFileKind, ParsedOverheadFile, PricingTable, SubagentAggregation, WasteFinding, }; -use relayburn_ledger::Query; -use relayburn_reader::{ +use crate::ledger::Query; +use crate::reader::{ parse_bash_command, resolve_project, BashParse, SourceKind, TurnRecord, UserTurnRecord, }; @@ -546,7 +546,7 @@ pub struct OverheadTrimResult { struct GatheredOverhead { project_path: PathBuf, files: Vec, - attribution: Option, + attribution: Option, } fn gather_overhead( @@ -1271,7 +1271,7 @@ fn fidelity_summary_to_value(s: &FidelitySummary) -> serde_json::Value { #[cfg(test)] mod tests { use super::*; - use relayburn_reader::{ToolCall, Usage}; + use crate::reader::{ToolCall, Usage}; use tempfile::TempDir; fn fixture_handle() -> (TempDir, LedgerHandle) { diff --git a/crates/relayburn-reader/src/lib.rs b/crates/relayburn-sdk/src/reader.rs similarity index 90% rename from crates/relayburn-reader/src/lib.rs rename to crates/relayburn-sdk/src/reader.rs index 64355351..efae6f24 100644 --- a/crates/relayburn-reader/src/lib.rs +++ b/crates/relayburn-sdk/src/reader.rs @@ -7,6 +7,12 @@ //! parser (`claude`) covers the synchronous, incremental, and cross-file //! reconciliation surface (#255). +// The four absorbed module roots carry the lower crates whole, including +// items the SDK does not re-export (dead from the SDK perspective). Silence +// the never-used warnings rather than handpicking re-exports — the next +// agent absorbing more verbs will need them. +#![allow(dead_code, unused_imports)] + pub mod classifier; pub mod fidelity; pub mod git; diff --git a/crates/relayburn-reader/src/classifier.rs b/crates/relayburn-sdk/src/reader/classifier.rs similarity index 99% rename from crates/relayburn-reader/src/classifier.rs rename to crates/relayburn-sdk/src/reader/classifier.rs index b0328590..75f0accb 100644 --- a/crates/relayburn-reader/src/classifier.rs +++ b/crates/relayburn-sdk/src/reader/classifier.rs @@ -12,7 +12,7 @@ use std::sync::LazyLock; use phf::{phf_map, phf_set}; use regex::Regex; -use crate::types::{ActivityCategory, ToolCall}; +use crate::reader::types::{ActivityCategory, ToolCall}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct ClassificationInput<'a> { diff --git a/crates/relayburn-reader/src/claude.rs b/crates/relayburn-sdk/src/reader/claude.rs similarity index 99% rename from crates/relayburn-reader/src/claude.rs rename to crates/relayburn-sdk/src/reader/claude.rs index 87b14b91..f2a50184 100644 --- a/crates/relayburn-reader/src/claude.rs +++ b/crates/relayburn-sdk/src/reader/claude.rs @@ -15,17 +15,17 @@ use std::path::Path; use serde_json::Value; -use crate::classifier::{classify_activity, ClassificationInput}; -use crate::git::resolve_project; -use crate::hash::{args_hash, content_hash}; -use crate::types::{ +use crate::reader::classifier::{classify_activity, ClassificationInput}; +use crate::reader::git::resolve_project; +use crate::reader::hash::{args_hash, content_hash}; +use crate::reader::types::{ CompactionEvent, ContentKind, ContentRecord, ContentRole, ContentStoreMode, ContentToolResult, ContentToolUse, Coverage, Fidelity, RelationshipSourceKind, RelationshipType, SessionRelationshipRecord, SourceKind, Subagent, ToolCall, ToolResultEventRecord, ToolResultEventSource, ToolResultStatus, TurnRecord, Usage, UsageGranularity, UserTurnBlock, UserTurnRecord, }; -use crate::user_turn::{HeuristicCounter, TokenCounter, UserTurnTokenizer}; +use crate::reader::user_turn::{HeuristicCounter, TokenCounter, UserTurnTokenizer}; // --------------------------------------------------------------------------- // Public surface. @@ -3039,7 +3039,7 @@ mod tests { // `tests/fixtures/claude/` directory so the TS and Rust suites exercise // the same input bytes. - use crate::types::{ActivityCategory, FidelityClass, UserTurnBlockKind}; + use crate::reader::types::{ActivityCategory, FidelityClass, UserTurnBlockKind}; use std::io::Write as _; fn read_bytes(p: &std::path::Path) -> Vec { diff --git a/crates/relayburn-reader/src/codex.rs b/crates/relayburn-sdk/src/reader/codex.rs similarity index 99% rename from crates/relayburn-reader/src/codex.rs rename to crates/relayburn-sdk/src/reader/codex.rs index 226dcb21..51b2504a 100644 --- a/crates/relayburn-reader/src/codex.rs +++ b/crates/relayburn-sdk/src/reader/codex.rs @@ -17,18 +17,18 @@ use std::path::Path; use serde_json::Value; -use crate::classifier::{classify_activity, ClassificationInput}; -use crate::fidelity::classify_fidelity; -use crate::git::ProjectResolver; -use crate::hash::{args_hash, content_hash}; -use crate::types::{ +use crate::reader::classifier::{classify_activity, ClassificationInput}; +use crate::reader::fidelity::classify_fidelity; +use crate::reader::git::ProjectResolver; +use crate::reader::hash::{args_hash, content_hash}; +use crate::reader::types::{ CompactionEvent, ContentKind, ContentRecord, ContentRole, ContentStoreMode, ContentToolResult, ContentToolUse, Coverage, Fidelity, RelationshipSourceKind, RelationshipType, SessionRelationshipRecord, SourceKind, ToolCall, ToolResultEventRecord, ToolResultEventSource, ToolResultStatus, TurnRecord, Usage, UsageGranularity, UserTurnBlock, UserTurnBlockKind, UserTurnRecord, }; -use crate::user_turn::{HeuristicCounter, UserTurnTokenizer}; +use crate::reader::user_turn::{HeuristicCounter, UserTurnTokenizer}; // --------------------------------------------------------------------------- // Public surface diff --git a/crates/relayburn-reader/src/codex/tests.rs b/crates/relayburn-sdk/src/reader/codex/tests.rs similarity index 99% rename from crates/relayburn-reader/src/codex/tests.rs rename to crates/relayburn-sdk/src/reader/codex/tests.rs index 9f8382da..87a65bf1 100644 --- a/crates/relayburn-reader/src/codex/tests.rs +++ b/crates/relayburn-sdk/src/reader/codex/tests.rs @@ -9,11 +9,11 @@ use serde_json::json; use tempfile::tempdir; use super::*; -use crate::types::{ +use crate::reader::types::{ ContentKind, ContentRole, FidelityClass, RelationshipType, SourceKind, ToolResultEventSource, ToolResultStatus, UsageGranularity, UserTurnBlockKind, }; -use crate::user_turn::UserTurnTokenizer; +use crate::reader::user_turn::UserTurnTokenizer; fn fixture(name: &str) -> PathBuf { let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -216,7 +216,7 @@ fn classifies_activity_and_fills_retries_has_edits() { assert_eq!(t.has_edits, Some(true)); assert!(matches!( t.activity, - Some(crate::types::ActivityCategory::Docs) + Some(crate::reader::types::ActivityCategory::Docs) )); assert!(t.retries.is_some()); } @@ -242,7 +242,7 @@ fn marks_failed_exec_command_as_debugging() { assert_eq!(r.turns.len(), 1); assert!(matches!( r.turns[0].activity, - Some(crate::types::ActivityCategory::Debugging) + Some(crate::reader::types::ActivityCategory::Debugging) )); assert_eq!(r.turns[0].has_edits, Some(false)); } @@ -271,7 +271,7 @@ fn user_prompt_drives_keyword_refinement_skipping_boilerplate() { assert_eq!(r.turns.len(), 1); assert!(matches!( r.turns[0].activity, - Some(crate::types::ActivityCategory::Refactoring) + Some(crate::reader::types::ActivityCategory::Refactoring) )); assert_eq!(r.turns[0].has_edits, Some(true)); } diff --git a/crates/relayburn-reader/src/fidelity.rs b/crates/relayburn-sdk/src/reader/fidelity.rs similarity index 98% rename from crates/relayburn-reader/src/fidelity.rs rename to crates/relayburn-sdk/src/reader/fidelity.rs index b4cf6e01..7370751a 100644 --- a/crates/relayburn-reader/src/fidelity.rs +++ b/crates/relayburn-sdk/src/reader/fidelity.rs @@ -5,7 +5,7 @@ //! exposed as a free function and as [`Fidelity::classify`] — same logic, //! pick the form that reads better at the call site. -use crate::types::{Coverage, Fidelity, FidelityClass, UsageGranularity}; +use crate::reader::types::{Coverage, Fidelity, FidelityClass, UsageGranularity}; impl Fidelity { /// Build a `Fidelity` from a granularity + coverage pair. Equivalent to diff --git a/crates/relayburn-reader/src/git.rs b/crates/relayburn-sdk/src/reader/git.rs similarity index 100% rename from crates/relayburn-reader/src/git.rs rename to crates/relayburn-sdk/src/reader/git.rs diff --git a/crates/relayburn-reader/src/hash.rs b/crates/relayburn-sdk/src/reader/hash.rs similarity index 100% rename from crates/relayburn-reader/src/hash.rs rename to crates/relayburn-sdk/src/reader/hash.rs diff --git a/crates/relayburn-reader/src/opencode.rs b/crates/relayburn-sdk/src/reader/opencode.rs similarity index 99% rename from crates/relayburn-reader/src/opencode.rs rename to crates/relayburn-sdk/src/reader/opencode.rs index 1c41e715..35913a53 100644 --- a/crates/relayburn-reader/src/opencode.rs +++ b/crates/relayburn-sdk/src/reader/opencode.rs @@ -25,18 +25,18 @@ use std::path::{Path, PathBuf}; use serde_json::{Map, Value}; -use crate::classifier::{classify_activity, ClassificationInput}; -use crate::fidelity::classify_fidelity; -use crate::git::ProjectResolver; -use crate::hash::{args_hash, content_hash}; -use crate::types::{ +use crate::reader::classifier::{classify_activity, ClassificationInput}; +use crate::reader::fidelity::classify_fidelity; +use crate::reader::git::ProjectResolver; +use crate::reader::hash::{args_hash, content_hash}; +use crate::reader::types::{ CompactionEvent, ContentKind, ContentRecord, ContentRole, ContentStoreMode, ContentToolResult, ContentToolUse, Coverage, Fidelity, RelationshipSourceKind, RelationshipType, SessionRelationshipRecord, SourceKind, Subagent, ToolCall, ToolResultEventRecord, ToolResultEventSource, ToolResultStatus, TurnRecord, Usage, UsageAttribution, UsageGranularity, UserTurnBlock, UserTurnRecord, }; -use crate::user_turn::{HeuristicCounter, TokenCounter, UserTurnTokenizer}; +use crate::reader::user_turn::{HeuristicCounter, TokenCounter, UserTurnTokenizer}; // --------------------------------------------------------------------------- // Public surface diff --git a/crates/relayburn-reader/src/opencode/tests.rs b/crates/relayburn-sdk/src/reader/opencode/tests.rs similarity index 99% rename from crates/relayburn-reader/src/opencode/tests.rs rename to crates/relayburn-sdk/src/reader/opencode/tests.rs index 0d492340..27d41083 100644 --- a/crates/relayburn-reader/src/opencode/tests.rs +++ b/crates/relayburn-sdk/src/reader/opencode/tests.rs @@ -9,7 +9,7 @@ use std::path::{Path, PathBuf}; use tempfile::tempdir; use super::*; -use crate::types::{ +use crate::reader::types::{ ContentKind, ContentRole, FidelityClass, RelationshipType, SourceKind, ToolResultEventSource, ToolResultStatus, UsageAttribution, UsageGranularity, UserTurnBlockKind, }; @@ -186,7 +186,7 @@ fn classifies_activity_via_aliased_tool_names() { let r = parse("with-tool", "ses_tool"); let t = &r.turns[0]; assert_eq!(t.has_edits, Some(true)); - assert_eq!(t.activity.unwrap(), crate::types::ActivityCategory::Coding); + assert_eq!(t.activity.unwrap(), crate::reader::types::ActivityCategory::Coding); } // --------------------------------------------------------------------------- @@ -284,7 +284,7 @@ fn errored_tool_part_marks_turn_as_debugging() { let t = &r.turns[0]; assert_eq!( t.activity.unwrap(), - crate::types::ActivityCategory::Debugging + crate::reader::types::ActivityCategory::Debugging ); assert_eq!(t.has_edits, Some(false)); } diff --git a/crates/relayburn-reader/src/opencode_stream.rs b/crates/relayburn-sdk/src/reader/opencode_stream.rs similarity index 99% rename from crates/relayburn-reader/src/opencode_stream.rs rename to crates/relayburn-sdk/src/reader/opencode_stream.rs index 837e0636..67bb07b0 100644 --- a/crates/relayburn-reader/src/opencode_stream.rs +++ b/crates/relayburn-sdk/src/reader/opencode_stream.rs @@ -15,18 +15,18 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use serde_json::{Map, Value}; -use crate::classifier::{classify_activity, ClassificationInput}; -use crate::fidelity::classify_fidelity; -use crate::git::ProjectResolver; -use crate::hash::{args_hash, content_hash}; -use crate::types::{ +use crate::reader::classifier::{classify_activity, ClassificationInput}; +use crate::reader::fidelity::classify_fidelity; +use crate::reader::git::ProjectResolver; +use crate::reader::hash::{args_hash, content_hash}; +use crate::reader::types::{ ContentKind, ContentRecord, ContentRole, ContentStoreMode, ContentToolResult, ContentToolUse, Coverage, Fidelity, RelationshipSourceKind, RelationshipType, SessionRelationshipRecord, SourceKind, Subagent, ToolCall, ToolResultEventRecord, ToolResultEventSource, ToolResultStatus, TurnRecord, Usage, UsageAttribution, UsageGranularity, UserTurnBlock, UserTurnRecord, }; -use crate::user_turn::{HeuristicCounter, TokenCounter, UserTurnTokenizer}; +use crate::reader::user_turn::{HeuristicCounter, TokenCounter, UserTurnTokenizer}; // --------------------------------------------------------------------------- // Public API diff --git a/crates/relayburn-reader/src/opencode_stream/tests.rs b/crates/relayburn-sdk/src/reader/opencode_stream/tests.rs similarity index 99% rename from crates/relayburn-reader/src/opencode_stream/tests.rs rename to crates/relayburn-sdk/src/reader/opencode_stream/tests.rs index 592564be..70039194 100644 --- a/crates/relayburn-reader/src/opencode_stream/tests.rs +++ b/crates/relayburn-sdk/src/reader/opencode_stream/tests.rs @@ -5,7 +5,7 @@ //! lifted in here. use super::*; -use crate::types::{ContentKind, ToolResultEventSource, UsageAttribution}; +use crate::reader::types::{ContentKind, ToolResultEventSource, UsageAttribution}; use serde_json::json; fn ingestor() -> OpencodeStreamIngestor { @@ -172,7 +172,7 @@ fn normalizes_a_stream_owned_session_into_burn_records_on_idle() { assert_eq!(result.user_turns.len(), 1); assert_eq!( result.user_turns[0].blocks[0].kind, - crate::types::UserTurnBlockKind::Text + crate::reader::types::UserTurnBlockKind::Text ); assert_eq!(result.cursor.last_event_id.as_deref(), Some("7")); diff --git a/crates/relayburn-reader/src/types.rs b/crates/relayburn-sdk/src/reader/types.rs similarity index 100% rename from crates/relayburn-reader/src/types.rs rename to crates/relayburn-sdk/src/reader/types.rs diff --git a/crates/relayburn-reader/src/user_turn.rs b/crates/relayburn-sdk/src/reader/user_turn.rs similarity index 98% rename from crates/relayburn-reader/src/user_turn.rs rename to crates/relayburn-sdk/src/reader/user_turn.rs index 1e000854..616a3e85 100644 --- a/crates/relayburn-reader/src/user_turn.rs +++ b/crates/relayburn-sdk/src/reader/user_turn.rs @@ -9,7 +9,7 @@ use serde_json::Value; -use crate::types::{UserTurnBlock, UserTurnBlockKind}; +use crate::reader::types::{UserTurnBlock, UserTurnBlockKind}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum UserTurnTokenizer { diff --git a/crates/relayburn-ingest/tests/fixtures/ts_codex_stamp.json b/crates/relayburn-sdk/tests/fixtures/ts_codex_stamp.json similarity index 100% rename from crates/relayburn-ingest/tests/fixtures/ts_codex_stamp.json rename to crates/relayburn-sdk/tests/fixtures/ts_codex_stamp.json diff --git a/scripts/update-pricing.mjs b/scripts/update-pricing.mjs index c813a362..302d191a 100644 --- a/scripts/update-pricing.mjs +++ b/scripts/update-pricing.mjs @@ -5,11 +5,11 @@ import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Two vendored copies are kept in lockstep: the TS analyze package reads from -// the packages/ copy, and the Rust analyze crate `include_str!`s the crates/ +// the packages/ copy, and the Rust SDK crate `include_str!`s the crates/ // copy (required for `cargo package` to bundle it cleanly). const OUTS = [ path.resolve(__dirname, '..', 'packages', 'analyze', 'pricing', 'models.dev.json'), - path.resolve(__dirname, '..', 'crates', 'relayburn-analyze', 'data', 'models.dev.json'), + path.resolve(__dirname, '..', 'crates', 'relayburn-sdk', 'data', 'models.dev.json'), ]; const res = await fetch('https://models.dev/api.json');