From bbdd279b174c8dedf55e6b365e7285f5a33fd7e3 Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Wed, 6 May 2026 21:37:14 -0400 Subject: [PATCH] relayburn-sdk: default ledger home moves to ~/.agentworkforce/burn The Rust 2.0 port now defaults to `~/.agentworkforce/burn` instead of `~/.relayburn` so it can coexist on disk with the TS 1.x package during the #249 cutover. `RELAYBURN_HOME` (and the per-DB path overrides) continue to override the location, so existing test infrastructure and embedders are unaffected. - `ledger_home()` in `crates/relayburn-sdk/src/ledger/paths.rs` now pushes `.agentworkforce/burn` (two levels) under `HOME`. The two-level path is handled by the existing `create_dir_all` in `Connections::open`, so no additional plumbing is needed. - `analyze::ghost_surface::default_archive_dir` now reuses `crate::ledger::ledger_home()` instead of duplicating the home resolution; `ghost-archive/` follows the configured home (and any env override) for free. - Doc-only updates to `relayburn-cli` (CLI help text, harness comments) and `relayburn-sdk-node` swap `~/.relayburn` for the new default. - New unit tests in `paths.rs` lock the default and the env-var override; cli golden snapshots are unaffected. --- CHANGELOG.md | 1 + crates/relayburn-cli/src/cli.rs | 6 +-- crates/relayburn-cli/src/harnesses/codex.rs | 2 +- .../relayburn-cli/src/harnesses/opencode.rs | 2 +- crates/relayburn-sdk-node/src/lib.rs | 2 +- .../src/analyze/ghost_surface.rs | 2 +- .../src/ingest/orchestration_tests.rs | 4 +- crates/relayburn-sdk/src/ingest_verb.rs | 2 +- crates/relayburn-sdk/src/ledger/config.rs | 2 +- crates/relayburn-sdk/src/ledger/paths.rs | 54 +++++++++++++++++-- crates/relayburn-sdk/src/lib.rs | 2 +- crates/relayburn-sdk/tests/integration.rs | 2 +- 12 files changed, 64 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 739460d3..3f9cdd4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Cross-package release notes for relayburn. Package changelogs contain package-le ## [Unreleased] +- `relayburn-sdk` (Rust): default ledger home moves from `~/.relayburn` to `~/.agentworkforce/burn` so the Rust 2.0 port and the TS 1.x package can coexist on disk during the #249 cutover. `RELAYBURN_HOME` (and the per-DB path overrides) continue to override the path; TS 1.x users on `~/.relayburn` are unaffected. Rust-port testers with data under the old path can `mv ~/.relayburn ~/.agentworkforce/burn` to carry it over (formats are not compatible — Rust treats any non-2.0 layout as empty and requires a `burn ingest` re-population). - `relayburn-cli` (Rust): wire opencode `HarnessAdapter` via `pending_stamp::adapter_static` factory; registered in `RUNTIME_ADAPTERS`. (#248 D7) - `relayburn-cli` (Rust): wire `burn run ` driver + claude adapter (eager unit-struct in `EAGER_ADAPTERS`); `afterExit` ingest folds into `[burn] claude ingest: ...` summary line. (#248 D5) - `relayburn-cli` (Rust): wire `burn ingest` (no-flag scan, `--watch` poll loop, `--hook claude --quiet`) and `burn mcp-server` stdio subcommand exposing `burn__sessionCost`; closes #210. (#248 D8) diff --git a/crates/relayburn-cli/src/cli.rs b/crates/relayburn-cli/src/cli.rs index 522d74fc..7686b365 100644 --- a/crates/relayburn-cli/src/cli.rs +++ b/crates/relayburn-cli/src/cli.rs @@ -35,7 +35,7 @@ pub struct GlobalArgs { pub json: bool, /// Optional override for the relayburn home directory (the dir /// containing `burn.sqlite` + `content.sqlite`). When `None`, - /// commands fall through to the SDK's env-var / `~/.relayburn` + /// commands fall through to the SDK's env-var / `~/.agentworkforce/burn` /// resolution. pub ledger_path: Option, /// Suppress ANSI color output. Honored by the table renderer and @@ -64,7 +64,7 @@ pub struct Args { /// Override the relayburn home directory (the dir containing /// `burn.sqlite` + `content.sqlite`). Defaults to `$RELAYBURN_HOME` - /// or `~/.relayburn`. + /// or `~/.agentworkforce/burn`. #[arg(long, global = true, value_name = "PATH")] pub ledger_path: Option, @@ -111,7 +111,7 @@ pub enum Command { /// session log on exit. Run(RunArgs), - /// Inspect or rebuild derived state under `~/.relayburn`. + /// Inspect or rebuild derived state under `~/.agentworkforce/burn`. State(StateArgs), /// Scan harness session stores and append new turns to the ledger. diff --git a/crates/relayburn-cli/src/harnesses/codex.rs b/crates/relayburn-cli/src/harnesses/codex.rs index e64cbfb7..5644868e 100644 --- a/crates/relayburn-cli/src/harnesses/codex.rs +++ b/crates/relayburn-cli/src/harnesses/codex.rs @@ -51,7 +51,7 @@ pub fn config() -> PendingStampAdapter { // Open a fresh ledger handle per tick. The TS sibling's // `ingestCodexSessions` does the same via `withLock('ledger', …)`; // SQLite WAL keeps the per-call open cheap (no DDL after first - // open). Defaults pull `$RELAYBURN_HOME` (or `~/.relayburn`) + // open). Defaults pull `$RELAYBURN_HOME` (or `~/.agentworkforce/burn`) // and the same per-harness session-store root the factory's // `session_root` closure resolves above. let mut handle = Ledger::open(LedgerOpenOptions::default())?; diff --git a/crates/relayburn-cli/src/harnesses/opencode.rs b/crates/relayburn-cli/src/harnesses/opencode.rs index 6c6efc79..aea20131 100644 --- a/crates/relayburn-cli/src/harnesses/opencode.rs +++ b/crates/relayburn-cli/src/harnesses/opencode.rs @@ -59,7 +59,7 @@ pub fn config() -> PendingStampAdapter { // Open a fresh ledger handle per tick. The TS sibling's // `ingestOpencodeSessions` does the same via `withLock('ledger', …)`; // SQLite WAL keeps the per-call open cheap (no DDL after first - // open). Defaults pull `$RELAYBURN_HOME` (or `~/.relayburn`) + // open). Defaults pull `$RELAYBURN_HOME` (or `~/.agentworkforce/burn`) // and the same per-harness session-store root the factory's // `session_root` closure resolves above. let mut handle = Ledger::open(LedgerOpenOptions::default())?; diff --git a/crates/relayburn-sdk-node/src/lib.rs b/crates/relayburn-sdk-node/src/lib.rs index d1c0b2f8..1ed37de6 100644 --- a/crates/relayburn-sdk-node/src/lib.rs +++ b/crates/relayburn-sdk-node/src/lib.rs @@ -340,7 +340,7 @@ unsafe fn promote_value( /// Where on disk a ledger should land. Mirrors /// `relayburn_sdk::LedgerOpenOptions`. `home` defaults to `RELAYBURN_HOME` -/// (or `~/.relayburn`); `contentHome` overrides only the `content.sqlite` +/// (or `~/.agentworkforce/burn`); `contentHome` overrides only the `content.sqlite` /// path when it makes sense to park content on different storage. #[napi(object)] pub struct LedgerOpenOptions { diff --git a/crates/relayburn-sdk/src/analyze/ghost_surface.rs b/crates/relayburn-sdk/src/analyze/ghost_surface.rs index 3d58a0d6..3fb83f79 100644 --- a/crates/relayburn-sdk/src/analyze/ghost_surface.rs +++ b/crates/relayburn-sdk/src/analyze/ghost_surface.rs @@ -757,7 +757,7 @@ fn severity_from_usd(usd: f64) -> WasteSeverity { } fn default_archive_dir() -> PathBuf { - home_dir().join(".relayburn").join("ghost-archive") + crate::ledger::ledger_home().join("ghost-archive") } fn home_dir() -> PathBuf { diff --git a/crates/relayburn-sdk/src/ingest/orchestration_tests.rs b/crates/relayburn-sdk/src/ingest/orchestration_tests.rs index 770b8da0..e41133f5 100644 --- a/crates/relayburn-sdk/src/ingest/orchestration_tests.rs +++ b/crates/relayburn-sdk/src/ingest/orchestration_tests.rs @@ -58,8 +58,8 @@ fn open_ledger_in(tmp: &TempDir) -> Ledger { } /// Pin RELAYBURN_HOME under `tmp` so the pending-stamp + config layers -/// can't scribble on the developer's `~/.relayburn`. Caller holds the -/// returned mutex guard for the whole test body. +/// can't scribble on the developer's `~/.agentworkforce/burn`. Caller holds +/// the returned mutex guard for the whole test body. fn isolated_relayburn_home<'a>(tmp: &TempDir) -> std::sync::MutexGuard<'a, ()> { let guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); let home = tmp.path().join("relayburn"); diff --git a/crates/relayburn-sdk/src/ingest_verb.rs b/crates/relayburn-sdk/src/ingest_verb.rs index 0ddf1e8e..d98daee6 100644 --- a/crates/relayburn-sdk/src/ingest_verb.rs +++ b/crates/relayburn-sdk/src/ingest_verb.rs @@ -84,7 +84,7 @@ mod tests { // Point `RELAYBURN_HOME` at the temp dir so `cleanup_stale_pending_stamps` // and `load_config` (called inside ingest_all) don't touch the real - // `~/.relayburn`. Set before any ledger-open call. + // `~/.agentworkforce/burn`. Set before any ledger-open call. std::env::set_var("RELAYBURN_HOME", home.path()); let opts = IngestOptions { diff --git a/crates/relayburn-sdk/src/ledger/config.rs b/crates/relayburn-sdk/src/ledger/config.rs index 014fd28d..924caa14 100644 --- a/crates/relayburn-sdk/src/ledger/config.rs +++ b/crates/relayburn-sdk/src/ledger/config.rs @@ -121,7 +121,7 @@ pub fn load_config_with_home(home: Option<&Path>) -> Result { } /// Load with an explicit config path. Tests use this to avoid touching -/// `$HOME/.relayburn/config.json`. +/// `$HOME/.agentworkforce/burn/config.json`. pub fn load_config_at(path: &Path) -> Result { let from_file = read_config_file(path); let store = pick_store( diff --git a/crates/relayburn-sdk/src/ledger/paths.rs b/crates/relayburn-sdk/src/ledger/paths.rs index 99ddb08a..3b2b56e2 100644 --- a/crates/relayburn-sdk/src/ledger/paths.rs +++ b/crates/relayburn-sdk/src/ledger/paths.rs @@ -8,6 +8,10 @@ //! content.sqlite # content blobs + FTS5 index //! ``` //! +//! `$RELAYBURN_HOME` defaults to `~/.agentworkforce/burn` so the Rust 2.0 +//! port and the TS 1.x package (still at `~/.relayburn`) can coexist on +//! disk during the #249 cutover. +//! //! Both paths are overridable via env vars so they can live on different //! mounts (e.g. cheaper/bigger storage for `content.sqlite`). See the //! redesign issue for the rationale behind splitting them. @@ -15,9 +19,9 @@ use std::env; use std::path::PathBuf; -/// `$RELAYBURN_HOME`, defaulting to `~/.relayburn`. Reads the env var on -/// every call so test harnesses can flip it between cases without process -/// restart. +/// `$RELAYBURN_HOME`, defaulting to `~/.agentworkforce/burn`. Reads the +/// env var on every call so test harnesses can flip it between cases +/// without process restart. pub fn ledger_home() -> PathBuf { if let Ok(env) = env::var("RELAYBURN_HOME") { if !env.is_empty() { @@ -26,7 +30,8 @@ pub fn ledger_home() -> PathBuf { } let home = env::var("HOME").unwrap_or_else(|_| ".".to_string()); let mut p = PathBuf::from(home); - p.push(".relayburn"); + p.push(".agentworkforce"); + p.push("burn"); p } @@ -90,6 +95,47 @@ fn is_id_char(b: u8) -> bool { #[cfg(test)] mod tests { use super::*; + use std::sync::Mutex; + + /// Serialises tests that mutate `RELAYBURN_HOME` / `HOME` so they + /// don't trample one another (cargo runs tests in parallel by + /// default; env vars are process-global). + static ENV_LOCK: Mutex<()> = Mutex::new(()); + + #[test] + fn ledger_home_defaults_to_agentworkforce_burn_under_home() { + let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + let prev_home = env::var("HOME").ok(); + let prev_relayburn = env::var("RELAYBURN_HOME").ok(); + env::remove_var("RELAYBURN_HOME"); + env::set_var("HOME", "/tmp/burn-paths-test-home"); + + let p = ledger_home(); + assert_eq!(p, PathBuf::from("/tmp/burn-paths-test-home/.agentworkforce/burn")); + + match prev_home { + Some(v) => env::set_var("HOME", v), + None => env::remove_var("HOME"), + } + if let Some(v) = prev_relayburn { + env::set_var("RELAYBURN_HOME", v); + } + } + + #[test] + fn ledger_home_env_var_override_takes_precedence() { + let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + let prev_relayburn = env::var("RELAYBURN_HOME").ok(); + env::set_var("RELAYBURN_HOME", "/tmp/explicit-burn-home"); + + let p = ledger_home(); + assert_eq!(p, PathBuf::from("/tmp/explicit-burn-home")); + + match prev_relayburn { + Some(v) => env::set_var("RELAYBURN_HOME", v), + None => env::remove_var("RELAYBURN_HOME"), + } + } #[test] fn rejects_traversal_and_empty() { diff --git a/crates/relayburn-sdk/src/lib.rs b/crates/relayburn-sdk/src/lib.rs index e93506f3..23fbf850 100644 --- a/crates/relayburn-sdk/src/lib.rs +++ b/crates/relayburn-sdk/src/lib.rs @@ -126,7 +126,7 @@ pub use crate::ingest::{ pub struct LedgerOpenOptions { /// Override for `$RELAYBURN_HOME` (the directory containing /// `burn.sqlite`). When `None`, the env var is consulted, then - /// `~/.relayburn`. + /// `~/.agentworkforce/burn`. pub home: Option, /// Override for the `content.sqlite` location specifically. When /// `None`, follows `home` (or its env-var fallback). Provided as a diff --git a/crates/relayburn-sdk/tests/integration.rs b/crates/relayburn-sdk/tests/integration.rs index 96d7a2a4..f1d73a03 100644 --- a/crates/relayburn-sdk/tests/integration.rs +++ b/crates/relayburn-sdk/tests/integration.rs @@ -255,7 +255,7 @@ async fn ingest_with_empty_roots_returns_zero_report_via_handle_and_free_fn() { // `cleanup_stale_pending_stamps` and `load_config` inside ingest_all // honor RELAYBURN_HOME; pin it to the temp dir so the test never - // touches `~/.relayburn`. + // touches `~/.agentworkforce/burn`. std::env::set_var("RELAYBURN_HOME", home.path()); let mut handle = Ledger::open(LedgerOpenOptions::with_home(home.path())).expect("open");