Push A (single-machine): - HLC + event-sourced SQLite (events table is source of truth, projections rebuildable) - Pydantic v2 domain models (Project, Task, Assignment, Session, Group, Update) - rclaude subprocess wrapper (local_sessions via _claude-projects --sessions) - Typer CLI: init, project, task, assign, pull, status, broadcast, serve, sync - FastAPI + Jinja2 + HTMX dashboard - 26 unit tests passing Push B (HTTP API + sync substrate): - /api/v1/* JSON routes (projects, tasks, assignments, sessions, status, broadcast, sync) - CLI refactored as thin httpx client over the API — single business-logic codepath - web/service.py: every business op defined once; HTML routes + API routes both call into it - sync.py: peer-to-peer sync via /api/v1/sync/events with HLC + uuid-based dedup - 32 tests passing including two-Clare convergence test Push C (cross-host deployment): - apricot install via uv (Python 3.12.12) - systemd --user unit for clare-serve on apricot - Cross-host sync demoed plum (10.9.0.3) ↔ apricot (10.9.0.2) over wg - .local → .lan rename for forge URLs Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
67 lines
1.9 KiB
Python
67 lines
1.9 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid as _uuid
|
|
|
|
from clare.domain import Session, Task, TaskStatus
|
|
from clare.scheduler import next_task_for_session, rank_open_tasks, session_attention_score
|
|
|
|
|
|
def _task(prio: int, status: TaskStatus = TaskStatus.TODO, hlc: str = "0") -> Task:
|
|
return Task(
|
|
id=_uuid.uuid4(),
|
|
project_id=_uuid.uuid4(),
|
|
title="t",
|
|
status=status,
|
|
priority=prio,
|
|
created_hlc=hlc,
|
|
updated_hlc=hlc,
|
|
)
|
|
|
|
|
|
def test_rank_open_tasks_drops_done() -> None:
|
|
tasks = [
|
|
_task(0, TaskStatus.DONE),
|
|
_task(2),
|
|
_task(1),
|
|
]
|
|
ranked = rank_open_tasks(tasks)
|
|
assert [t.priority for t in ranked] == [1, 2]
|
|
|
|
|
|
def test_rank_open_tasks_orders_by_priority_then_created() -> None:
|
|
t_a = _task(1, hlc="00000000001")
|
|
t_b = _task(1, hlc="00000000002")
|
|
t_c = _task(0, hlc="00000000003")
|
|
ranked = rank_open_tasks([t_a, t_b, t_c])
|
|
assert ranked == [t_c, t_a, t_b]
|
|
|
|
|
|
def test_next_task_for_session_returns_top_owned() -> None:
|
|
sid = _uuid.uuid4()
|
|
other_sid = _uuid.uuid4()
|
|
t1 = _task(2)
|
|
t2 = _task(0)
|
|
t3 = _task(1)
|
|
active = {t1.id: sid, t2.id: other_sid, t3.id: sid}
|
|
nxt = next_task_for_session([t1, t2, t3], active, sid)
|
|
assert nxt == t3 # priority 1 (owned by sid) beats priority 2
|
|
|
|
|
|
def test_next_task_for_session_none_when_no_assignments() -> None:
|
|
sid = _uuid.uuid4()
|
|
nxt = next_task_for_session([_task(0)], {}, sid)
|
|
assert nxt is None
|
|
|
|
|
|
def test_session_attention_score_inverts_priority() -> None:
|
|
def make_session(prio: int | None) -> Session:
|
|
return Session(
|
|
uuid=_uuid.uuid4(),
|
|
host="local",
|
|
last_triage_priority=prio,
|
|
updated_hlc="0",
|
|
)
|
|
|
|
assert session_attention_score(make_session(0)) == 4
|
|
assert session_attention_score(make_session(4)) == 0
|
|
assert session_attention_score(make_session(None)) == 0
|