Skip to content

relayburn-cli: burn summary + hotspots presenters (#248 D1) - #315

Merged
willwashburn merged 4 commits into
mainfrom
rust-port/cli-summary-hotspots
May 6, 2026
Merged

relayburn-cli: burn summary + hotspots presenters (#248 D1)#315
willwashburn merged 4 commits into
mainfrom
rust-port/cli-summary-hotspots

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Summary

  • Implement burn summary and burn hotspots as thin presenters over relayburn_sdk::summary / ::hotspots, matching the TS CLI flag set + output byte-for-byte.
  • Un-ignore the summary / summary-json / hotspots / hotspots-json golden invocations.

Test plan

  • cargo test --workspace
  • BURN_GOLDEN=1 cargo test --test golden -p relayburn-cli (4 entries pass)
  • burn summary --help / burn hotspots --help match TS flag set

🤖 Generated with Claude Code

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.
@coderabbitai

coderabbitaiBot commented May 6, 2026

Copy link
Copy Markdown

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Walkthrough

The 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.

Changes

Summary and Hotspots CLI Commands

Layer / File(s)Summary
CLI Argument Shape
crates/relayburn-cli/src/cli.rs, crates/relayburn-cli/src/commands/summary.rs, crates/relayburn-cli/src/commands/hotspots.rs
Summary and Hotspots enum variants converted from unit-like to payload-bearing, carrying SummaryArgs and HotspotsArgs structs respectively. Each Args struct declares per-command flags (since, project, session, provider, by-tool, by-subagent-type, etc.).
Core Command Logic
crates/relayburn-cli/src/commands/summary.rs
Implements full summary workflow: opens ledger, ingests sessions, builds queries, aggregates by model or provider, computes cost/fidelity/replacement-savings via SDK, and routes to JSON or human output. Validates flag mutual exclusivity and deprecated features.
Core Command Logic
crates/relayburn-cli/src/commands/hotspots.rs
Implements full hotspots workflow: opens ledger, ingests data, computes per-source coverage breakdown, fetches hotspots via SDK, and renders output in JSON or human-readable format with per-file, per-bash, per-subagent detail.
Output Formatting Infrastructure
crates/relayburn-cli/src/render/format.rs, crates/relayburn-cli/src/render/mod.rs
Adds formatting helpers (format_usd, format_int, format_uint, render_table, coerce_whole_f64_to_int) matching TS CLI style; modularized into new format.rs and exported via mod.rs.
JSON & Human Rendering
crates/relayburn-cli/src/commands/summary.rs, crates/relayburn-cli/src/commands/hotspots.rs
Both commands include separate emit_json and emit_human entrypoints with JSON serialization helpers (cost_breakdown_to_json, fidelity_summary_to_json, replacement_savings_to_json, etc.) and rich human-readable table rendering with coverage markers, notices, and per-row metrics.
Dispatch & Integration
crates/relayburn-cli/src/main.rs, crates/relayburn-sdk/src/lib.rs, crates/relayburn-cli/Cargo.toml
Main.rs dispatch logic destructures payload from Command variants and passes to updated run() signatures. SDK exports new AggregateByProviderOptions type. Cargo.toml adds indexmap dependency with serde feature.
Test Bootstrap & Enablement
crates/relayburn-cli/tests/golden.rs, crates/relayburn-cli/tests/smoke.rs, tests/fixtures/cli-golden/invocations.json, tests/fixtures/cli-golden/ledger/.gitignore
Adds bootstrap_sqlite_from_jsonl() to replay ledger.jsonl into SQLite; normalizes tool_result events and constructs Stamps for SDK ingestion. Smoke tests updated to track UNIMPLEMENTED_SUBCOMMANDS separately. Four golden invocations (summary, summary-json, hotspots, hotspots-json) moved from disabled to enabled. .gitignore expanded to cover content.sqlite and archive.sqlite variants.
Documentation
CHANGELOG.md
Unreleased entry documents relayburn-cli wiring to relayburn-sdk with thin presenter pattern, parity with TS CLI flags and stdout behavior, and golden test enablement.

Sequence Diagram

sequenceDiagram
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • AgentWorkforce/burn#248: Implements the Rust CLI porting work (clap-based Command enum variants, payload-bearing Summary/Hotspots args, run() signatures, presenters, tests, and wiring).

Possibly related PRs

  • AgentWorkforce/burn#291: The hotspots CLI command implementation directly consumes the hotspots aggregation surface (attributions/aggregations) exported by this PR.
  • AgentWorkforce/burn#310: Main PR extends the cli-golden test harness and fixtures by implementing Summary/Hotspots CLI commands, enabling golden invocations, and adding ledger bootstrap logic.
  • AgentWorkforce/burn#301: The CLI Summary and Hotspots commands directly use the SDK-level summary and hotspots query verbs implemented in this PR.

Poem

🐰 A hop and a squeal—the presenters arise!
From thin wires to tables, with cost-column eyes,
JSON flows softly, humanity shines,
Bootstrap the ledger in SQLite's lines!
Four golden tests now skip no more—
Relayburn-cli stats adorn the store!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and concisely summarizes the main change: implementing burn summary and hotspots as presenters in the relayburn-cli.
Description check✅ PassedThe description is directly related to the changeset, detailing the implementation of burn summary and hotspots presenters, flag matching, and golden test enablement with test plan confirmation.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rust-port/cli-summary-hotspots

Comment @coderabbitai help to get the list of available commands and usage tips.

…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.
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

let source_label = if missing_fields.contains(&"tool-result events"){
"codex"
}else{
"claude-code"
};

P2 Badge Preserve source attribution in hotspots coverage notice

The coverage notice hard-codes a single source label (codex vs claude-code) from aggregate missing-field stats, so any slice with excluded turns from multiple source kinds will produce a misleading explanation (for example, excluded Claude and OpenCode turns can still be reported as only codex). This affects user diagnosis of why attribution was excluded because the message no longer matches the actual source mix that hotspots processed.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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).
@willwashburn

Copy link
Copy Markdown
MemberAuthor

Addressed in e50f490 — the hotspots presenter now re-walks the matched-window turns itself to build a per-SourceKinddescribeExcluded-style breakdown, then renders one inline clause per source joined with " and " (same shape as the TS CLI). This drops the brittle "any tool-result-events missing → codex; otherwise claude-code" heuristic the previous version used. JSON output is unchanged (the TS --json shape doesn't carry the per-source breakdown).

@willwashburn
willwashburn merged commit 72f2927 into mainMay 6, 2026
8 checks passed
@willwashburn
willwashburn deleted the rust-port/cli-summary-hotspots branch May 6, 2026 18:26
willwashburn added a commit that referenced this pull request May 6, 2026
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).
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@willwashburn