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
12 changes: 10 additions & 2 deletions hermes_cli/kanban.py
Original file line number Diff line number Diff line change
Expand Up @@ -1399,11 +1399,19 @@ def _cmd_diagnostics(args: argparse.Namespace) -> int:
if dl:
diags_by_task[tid] = dl

# Severity filter.
# Severity filter. argparse limits ``sev`` to one of
# ``SEVERITY_ORDER``; comparing by index implements the
# documented "at or above" semantics (a `--severity warning`
# request must also surface `error` and `critical`).
sev = getattr(args, "severity", None)
if sev:
severity_rank = {s: i for i, s in enumerate(kd.SEVERITY_ORDER)}
threshold = severity_rank[sev]
for tid in list(diags_by_task.keys()):
kept = [d for d in diags_by_task[tid] if d.severity == sev]
kept = [
d for d in diags_by_task[tid]
if severity_rank.get(d.severity, -1) >= threshold
]
Comment on lines +1411 to +1414
if kept:
diags_by_task[tid] = kept
else:
Expand Down
74 changes: 74 additions & 0 deletions tests/hermes_cli/test_kanban_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import argparse
import json
import os
import re
from pathlib import Path

import pytest

from hermes_cli import kanban as kc
from hermes_cli import kanban_db as kb
from hermes_cli import kanban_diagnostics as kd


@pytest.fixture
Expand Down Expand Up @@ -402,3 +404,75 @@ def test_run_slash_board_override_restores_prior_env(kanban_home, monkeypatch):
kc.run_slash("--board alpha list")

assert os.environ.get("HERMES_KANBAN_BOARD") == "beta"


# ---------------------------------------------------------------------------
# diagnostics --severity filter (regression for #26379)
#
# `--severity X` is documented as "at or above X", but the implementation
# used to be an exact-match equality. That silently dropped higher-severity
# diagnostics under `--severity warning`, breaking monitoring scripts that
# treated warning as a catch-all threshold.
# ---------------------------------------------------------------------------

def _seed_three_severity_diagnostics(kanban_home, monkeypatch):
out = kc.run_slash("create 'noisy task' --assignee alice")
tid = re.search(r"(t_[a-f0-9]+)", out).group(1)

diags = [
kd.Diagnostic(kind="x_warn", severity="warning",
title="warn", detail="w"),
kd.Diagnostic(kind="x_err", severity="error",
title="err", detail="e"),
kd.Diagnostic(kind="x_crit", severity="critical",
title="crit", detail="c"),
]

def fake_compute(task, events, runs, *, now=None, config=None):
if task["id"] == tid:
return list(diags)
return []

monkeypatch.setattr(kd, "compute_task_diagnostics", fake_compute)
return tid


@pytest.mark.parametrize(
"flag,expected_severities",
[
("", {"warning", "error", "critical"}),
("--severity warning", {"warning", "error", "critical"}),
("--severity error", {"error", "critical"}),
("--severity critical", {"critical"}),
],
)
def test_diagnostics_severity_filter_is_threshold_not_exact_match(
kanban_home, monkeypatch, flag, expected_severities,
):
tid = _seed_three_severity_diagnostics(kanban_home, monkeypatch)

out = kc.run_slash(f"diagnostics {flag} --json".strip())
payload = json.loads(out)
assert len(payload) == 1
assert payload[0]["task_id"] == tid
got = {d["severity"] for d in payload[0]["diagnostics"]}
assert got == expected_severities


def test_diagnostics_severity_filter_drops_task_when_no_match(
kanban_home, monkeypatch,
):
out = kc.run_slash("create 'warning only' --assignee alice")
tid = re.search(r"(t_[a-f0-9]+)", out).group(1)

def fake_compute(task, events, runs, *, now=None, config=None):
if task["id"] == tid:
return [kd.Diagnostic(kind="x", severity="warning",
title="w", detail="w")]
return []

monkeypatch.setattr(kd, "compute_task_diagnostics", fake_compute)

out = kc.run_slash("diagnostics --severity critical --json")
payload = json.loads(out)
assert payload == []
Loading