diff --git a/agentrace/cli.py b/agentrace/cli.py index da59a72..fec377b 100644 --- a/agentrace/cli.py +++ b/agentrace/cli.py @@ -1,9 +1,9 @@ """agentrace CLI. - agentrace list what did my subagents do - agentrace check flag the suspicious results - agentrace show read one run in full - agentrace stats where the time went +agentrace list what did my subagents do +agentrace check flag the suspicious results +agentrace show read one run in full +agentrace stats where the time went """ from __future__ import annotations @@ -12,6 +12,7 @@ import json import sys from pathlib import Path +from statistics import median from rich.console import Console from rich.markup import escape @@ -80,7 +81,9 @@ def cmd_check(args) -> int: continue flagged += 1 total_findings += len(findings) - console.print(f"\n[bold]{r.description or '(no description)'}[/] [dim]{r.tool_use_id[-8:]}[/]") + console.print( + f"\n[bold]{r.description or '(no description)'}[/] [dim]{r.tool_use_id[-8:]}[/]" + ) for f in findings: colour = _SEV_COLOUR.get(f.severity, "white") console.print(f" [{colour}]{f.severity:<6}[/] [bold]{f.check}[/] {f.message}") @@ -136,9 +139,9 @@ def cmd_stats(args) -> int: table.add_row("errored", f"{errors:,}") table.add_row("empty results", f"{empty:,}") if durations: - table.add_row("total agent time", f"{total_s/3600:.1f} h") - table.add_row("median run", f"{sorted(durations)[len(durations)//2]:.0f} s") - table.add_row("slowest run", f"{max(durations)/60:.1f} min") + table.add_row("total agent time", f"{total_s / 3600:.1f} h") + table.add_row("median run", f"{median(durations):.0f} s") + table.add_row("slowest run", f"{max(durations) / 60:.1f} min") table.add_row("prompt chars written", f"{prompt_chars:,}") table.add_row("result chars returned", f"{result_chars:,}") console.print(table) diff --git a/tests/test_cli.py b/tests/test_cli.py index ecaf0fc..1489d5c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,6 +2,13 @@ import subprocess import sys +from argparse import Namespace +from datetime import UTC, datetime, timedelta + +import pytest + +from agentrace import cli +from agentrace.parse import AgentRun def run_cli(*args: str) -> subprocess.CompletedProcess[str]: @@ -13,6 +20,42 @@ def run_cli(*args: str) -> subprocess.CompletedProcess[str]: ) +@pytest.mark.parametrize( + "durations,expected", + [ + ([10, 20], "15 s"), + ([30, 10, 20], "20 s"), + ([10], "10 s"), + ([10, None, 20], "15 s"), + ([None], None), + ], +) +def test_stats_median(durations, expected, monkeypatch, capsys): + start = datetime(2026, 1, 1, tzinfo=UTC) + runs = [ + AgentRun( + tool_use_id=str(i), + description="test", + prompt="prompt", + result="result", + started_at=start, + ended_at=start + timedelta(seconds=duration) if duration is not None else None, + ) + for i, duration in enumerate(durations) + ] + monkeypatch.setattr(cli, "_load", lambda args: runs) + + assert cli.cmd_stats(Namespace(json=False)) == 0 + + rows = capsys.readouterr().out.splitlines() + median_rows = [row for row in rows if "median run" in row] + if expected is None: + assert median_rows == [] + else: + assert len(median_rows) == 1 + assert " ".join(median_rows[0].split()) == f"median run {expected}" + + def test_file_and_dir_are_mutually_exclusive(tmp_path): transcript = tmp_path / "session.jsonl" transcript.write_text("")