Replace local-only _claude-projects --sessions path with 'rclaude list sessions --tsv' (full uuids, separate cwd/mtime columns) and add 'rclaude triage --tsv' ingestion emitting TriageRecorded events. Diff against the sessions projection to skip no-op events for unchanged (host, cwd, mtime) and (priority, status, summary) tuples.
105 lines
4.1 KiB
Python
105 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
from clare.rclaude import Rclaude, RclaudeError
|
|
|
|
|
|
def _fake_runner(stdout: str = "", stderr: str = "", returncode: int = 0):
|
|
def runner(*args, **kwargs):
|
|
return subprocess.CompletedProcess(
|
|
args=args[0] if args else [], returncode=returncode, stdout=stdout, stderr=stderr
|
|
)
|
|
|
|
return runner
|
|
|
|
|
|
def test_rclaude_raises_when_binary_missing(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr("shutil.which", lambda _: None)
|
|
rcl = Rclaude(binary="rclaude")
|
|
with pytest.raises(RclaudeError, match="not found on PATH"):
|
|
rcl.version()
|
|
|
|
|
|
def test_rclaude_raises_on_nonzero_exit(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr("shutil.which", lambda _: "/usr/local/bin/rclaude")
|
|
rcl = Rclaude(
|
|
binary="rclaude",
|
|
runner=_fake_runner(stderr="boom", returncode=1),
|
|
)
|
|
with pytest.raises(RclaudeError, match="exited 1"):
|
|
rcl.version()
|
|
|
|
|
|
def test_rclaude_list_tmux_parses_tsv(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr("shutil.which", lambda _: "/usr/local/bin/rclaude")
|
|
output = (
|
|
"local\ttmux\tclaude-natalie-foo-1715964800\t1 windows (created ...)\n"
|
|
"apricot\ttmux\tclaude-natalie-bar-1715964900\t2 windows (created ...)\n"
|
|
)
|
|
rcl = Rclaude(runner=_fake_runner(stdout=output))
|
|
rows = rcl.list_tmux()
|
|
assert len(rows) == 2
|
|
assert rows[0].host == "local"
|
|
assert rows[0].session_name == "claude-natalie-foo-1715964800"
|
|
assert rows[0].detail.startswith("1 windows")
|
|
assert rows[1].host == "apricot"
|
|
|
|
|
|
def test_rclaude_list_sessions_parses_tsv(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr("shutil.which", lambda _: "/usr/local/bin/rclaude")
|
|
# `rclaude list sessions --tsv` interleaves tmux rows with session rows;
|
|
# the wrapper should skip the tmux ones.
|
|
output = (
|
|
"local\ttmux\tclaude-foo\t1 windows\n"
|
|
"local\tsession\t94fc4f45-0160-4fb8-9c23-475a0c63c983\thello world\t/Users/x/Code\t1779098754\n"
|
|
"apricot\tsession\tbd306333-eb2d-4b65-8a50-2179b2697756\tworking on rclaude\t/home/u/proj\t1779098693\n"
|
|
)
|
|
rcl = Rclaude(runner=_fake_runner(stdout=output))
|
|
rows = rcl.list_sessions()
|
|
assert len(rows) == 2
|
|
assert rows[0].host == "local"
|
|
assert str(rows[0].uuid) == "94fc4f45-0160-4fb8-9c23-475a0c63c983"
|
|
assert rows[0].cwd == "/Users/x/Code"
|
|
assert rows[0].mtime_epoch == 1779098754
|
|
assert rows[0].snippet == "hello world"
|
|
assert rows[1].host == "apricot"
|
|
assert rows[1].cwd == "/home/u/proj"
|
|
|
|
|
|
def test_rclaude_triage_parses_tsv(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr("shutil.which", lambda _: "/usr/local/bin/rclaude")
|
|
output = (
|
|
"local\ttriage\t94fc4f45-0160-4fb8-9c23-475a0c63c983\t1\tin_progress"
|
|
"\tMulti-agent setup\tResume #31\t/Users/x/Code\t1779098754\n"
|
|
"apricot\ttriage\t41daf63a-245b-4194-b9ee-b704f833400f\t2\tblocked"
|
|
"\tWrong branch\tUpdate config\t/home/u/proj\t1779098796\n"
|
|
)
|
|
rcl = Rclaude(runner=_fake_runner(stdout=output))
|
|
rows = rcl.triage()
|
|
assert len(rows) == 2
|
|
assert rows[0].host == "local"
|
|
assert str(rows[0].uuid) == "94fc4f45-0160-4fb8-9c23-475a0c63c983"
|
|
assert rows[0].priority == 1
|
|
assert rows[0].status == "in_progress"
|
|
assert rows[0].summary == "Multi-agent setup"
|
|
assert rows[0].next_action == "Resume #31"
|
|
assert rows[0].cwd == "/Users/x/Code"
|
|
assert rows[0].mtime_epoch == 1779098754
|
|
assert rows[1].host == "apricot"
|
|
assert rows[1].priority == 2
|
|
|
|
|
|
def test_rclaude_triage_passes_flags(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr("shutil.which", lambda _: "/usr/local/bin/rclaude")
|
|
captured_args: list[list[str]] = []
|
|
|
|
def runner(args, **kwargs):
|
|
captured_args.append(list(args))
|
|
return subprocess.CompletedProcess(args=args, returncode=0, stdout="", stderr="")
|
|
|
|
rcl = Rclaude(runner=runner)
|
|
rcl.triage(refresh=True, limit=5)
|
|
assert captured_args == [["rclaude", "triage", "--tsv", "--refresh", "--limit", "5"]]
|