relayburn-cli: burn summary + hotspots presenters (#248 D1)#315
Conversation
Wires burn summary and burn hotspots as thin presenters over relayburn-sdk, matching the TS CLI's flag set and stdout output byte-for-byte for the default + --json flows that drive the golden snapshots. - summary: aggregates by model (default) or provider (--by-provider), emitting the canonical TS shape (`ingest`, `turns`, `totalCost`, `byModel`/`byProvider`, `fidelity` with ordered byClass/byGranularity/ missingCoverage keys, optional `replacementSavings`). Subagent / relationship / quality / by-tool flag wiring is in place but surfaces a clear "not yet implemented" stderr line + exit 2 (those modes land in follow-ups). Reuses the SDK's aggregate_by_provider / cost_for_turn / sum_costs / summarize_fidelity / summarize_replacement_savings. - hotspots: opens the ledger, drives a synchronous ingest sweep, then calls relayburn_sdk::hotspots and unwraps the Attribution variant for rendering. JSON output drops the `kind` discriminator to match the TS surface. Per-session / patterns / findings / group-by surfaces print "not yet implemented" until the follow-up PRs. Other changes: - crates/relayburn-cli/src/render/format.rs: new module with TS-CLI- equivalent format_usd / format_int / render_table helpers and a coerce_whole_f64_to_int Value walker that matches JS JSON.stringify's int-vs-float behavior (the SDK uses f64 for cost / token-share fields). - relayburn-sdk/src/lib.rs: re-exports the analyze types the CLI needs to construct option structs and walk results (UsageCostAggregateRow, ProviderAggregateRow, RowCoverage, FieldCoverage, CoverageField, AggregateByProviderOptions, ReplacementSavingsSummary, plus aggregate_by_provider + summarize_replacement_savings free fns). - crates/relayburn-cli/tests/golden.rs: bootstraps the SQLite fixture from the committed ledger.jsonl on first run, normalizing the legacy eventSource: "transcript" tool-result-event flag into the current schema's "tool_result" so the strict serde derive accepts the synthetic fixture. The Rust SDK is sqlite-only; the in-tree fixture is JSONL because the sqlite blobs are gitignored. - tests/fixtures/cli-golden/invocations.json: enable summary, summary-json, hotspots, hotspots-json. - tests/fixtures/cli-golden/ledger/.gitignore: also ignore content.sqlite (the bootstrap rematerializes it alongside burn.sqlite). - crates/relayburn-cli/tests/smoke.rs: drop summary + hotspots from the not-yet-implemented stub assertion (now real presenters). Cargo: - relayburn-cli adds tokio rt + indexmap; the rt feature lets the presenter drive ingest_all (async) from the otherwise-sync command body via a current-thread runtime.
|
Caution Review failedFailed to post review comments 📝 WalkthroughWalkthroughThe PR implements two CLI commands (summary and hotspots) as thin presenters over relayburn-sdk. It introduces payload-bearing Command enum variants with per-command argument structs, adds full implementations with JSON and human-readable output formatting, includes formatting helpers aligned with the TypeScript CLI, updates test infrastructure to bootstrap SQLite from JSONL ledger events, and enables corresponding golden test invocations. ChangesSummary and Hotspots CLI Commands
Sequence DiagramsequenceDiagram
actor User
participant CLI as CLI Dispatcher
participant Presenter as Summary/Hotspots Presenter
participant SDK as relayburn-sdk
participant Ledger as SQLite Ledger
participant Render as Formatter/Renderer
User->>CLI: run burn summary/hotspots [flags]
CLI->>Presenter: dispatch with parsed Args
Presenter->>Ledger: open ledger (burn.sqlite)
Presenter->>SDK: ingest sessions via RawLedger
Presenter->>SDK: build query (session/project/since filter)
Presenter->>Ledger: fetch turns matching query
Presenter->>SDK: aggregate by model/provider OR compute hotspots
SDK-->>Presenter: aggregated rows / hotspots result
alt JSON output mode
Presenter->>Render: serialize to JSON (cost, fidelity, attribution)
else Human output mode
Presenter->>Render: format tables with coverage cells
Presenter->>Render: format USD costs with locale separators
Render-->>Presenter: formatted strings
end
Presenter-->>Render: rendered output
Render-->>CLI: emit to stdout
CLI-->>User: display summary/hotspots
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
…nline Trivial cleanup: `run_ingest` was carrying a `ledger_home` argument it never consulted (the open happens in `run_inner`), and the `ledger_home = globals.ledger_path.clone()` local was a one-shot used only to construct `LedgerOpenOptions`. Inline both.
cargo fmt --check trips on a few cosmetic items in the files this PR
adds — long match arms, attribute formatting, etc. Apply rustfmt only to
the files D1 actually owns (commands/{summary,hotspots}.rs,
render/format.rs, tests/golden.rs, tests/smoke.rs); leave the
harnesses/ + render/table.rs files for sibling PRs.
💡 Codex Reviewburn/crates/relayburn-cli/src/commands/hotspots.rs Lines 752 to 756 in ec40249 The coverage notice hard-codes a single source label ( ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
… notice
Codex review flagged that the previous `coverage_notice` synthesized
the source label from aggregate `missingCoverage` flags
("any tool-result-events missing → codex; otherwise claude-code"),
which silently misattributes excluded turns when a slice spans more
than one source kind (e.g. excluded codex + opencode turns both
reported as just "codex").
Re-walk the matched-window turns ourselves to compute a proper
per-`SourceKind` breakdown — same shape as the TS CLI's
`describeExcluded` — and render one inline clause per source joined
with " and ". The SDK's `HotspotsResult` aggregate `fidelity.summary`
block is unchanged; the breakdown is computed locally so the JSON
output stays byte-identical (the TS `--json` shape doesn't carry
per-source breakdowns either).
|
Addressed in e50f490 — the hotspots presenter now re-walks the matched-window turns itself to build a per- |
Folds D1+D2+D3 (PRs #315/#312/#314) into the D4 state branch. Resolutions: - CHANGELOG.md: union all four bullets, alphabetized. - crates/relayburn-cli/src/cli.rs: keep all five typed Command variants (Summary/Hotspots/Overhead/Compare/State); merge ValueEnum import; keep D4's state-related types (StateArgs et al.) plus D2/D3's OverheadArgs / CompareArgs / OverheadKind. - crates/relayburn-cli/src/main.rs: auto-merged — five typed dispatch arms. - crates/relayburn-cli/tests/smoke.rs: drop REAL_COMMANDS allow-list and early-continue; remove "state" from UNIMPLEMENTED_SUBCOMMANDS, leaving ["run", "ingest", "mcp-server"]. - tests/fixtures/cli-golden/ledger/.gitignore: superset of patterns. - Cargo.lock: regenerated from origin/main. - tests/fixtures/cli-golden/snapshots/state-status*.stdout.txt: keep D4's 2.0 SQLite layout (deliberate divergence) but refresh row counts to reflect the bootstrapped fixture (golden.rs on main now replays ledger.jsonl into burn.sqlite before running, so the fixture has 18 rows instead of 0).
Summary
burn summaryandburn hotspotsas thin presenters overrelayburn_sdk::summary/::hotspots, matching the TS CLI flag set + output byte-for-byte.summary/summary-json/hotspots/hotspots-jsongolden invocations.Test plan
🤖 Generated with Claude Code