Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/doberman/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from doberman.demo import format_outcome_line, format_summary_table, run_demo
from doberman.discovery.mcp_scan import MCP_CONFIG_FILES, scan_mcp_configs
from doberman.discovery.scan import enumerate_capabilities, rate_capabilities, render_risk_map
from doberman.models import ActionType
from doberman.policy.checklist import recommend_policy
from doberman.policy.drift import (
_verify_possession_factor,
Expand Down Expand Up @@ -84,6 +85,8 @@ def _ensure_encode_safe_stdio() -> None:

_ensure_encode_safe_stdio()

_ACTION_WIDTH = max(len(action.value) for action in ActionType)

app = typer.Typer(
help="Doberman - adaptive authorization layer for coding agents.",
no_args_is_help=True,
Expand Down Expand Up @@ -1325,7 +1328,7 @@ def log(
reasons = ", ".join(json.loads(row["reason_codes_json"] or "[]")) or "-"
auth = f"; auth={row['auth_result']}" if row["auth_result"] else ""
typer.echo(
f"{row['ts']} {verdict_label_str(row['final_verdict'])} {row['action_type']:<13} "
f"{row['ts']} {verdict_label_str(row['final_verdict'])} {row['action_type']:<{_ACTION_WIDTH}} "
f"{target} [{reasons}]{auth}"
)

Expand Down
29 changes: 29 additions & 0 deletions tests/unit/test_cli_log_jsonl.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,32 @@ async def _empty(*_a, **_k):
result = runner.invoke(app, ["log", "--path", str(tmp_path)])
assert result.exit_code == 0
assert "(no decisions recorded yet)" in result.stdout


def test_log_human_columns_stay_aligned_for_long_action_types(tmp_path):
"""Long action names must not shift the target and reason columns (#428)."""
import doberman.cli.main as main_mod

rows = [
{**_ROWS[0], "action_type": "git_op", "target_path_class": None},
{
**_ROWS[0],
"id": 3,
"action_id": "act-3",
"action_type": "network_request",
"target_path_class": None,
},
]

async def _rows(*_a, **_k):
return list(rows)

with patch.object(main_mod, "read_decisions", _rows):
result = runner.invoke(app, ["log", "--path", str(tmp_path)])

assert result.exit_code == 0
lines = [line for line in result.stdout.splitlines() if line.startswith("2026-")]
assert len(lines) == 2
# The target is always the "-" placeholder, followed by two spaces and reasons.
target_offsets = {line.index(" - ") for line in lines}
assert len(target_offsets) == 1
Loading