1
0

Compare commits

...

1 Commits

Author SHA1 Message Date
Alexander Whitestone
4e4206a91e fix: resolve 23 ruff lint errors
Fixes #1149
2026-03-23 14:51:57 -04:00
22 changed files with 116 additions and 124 deletions

View File

@@ -33,12 +33,12 @@ from dashboard.routes.calm import router as calm_router
from dashboard.routes.chat_api import router as chat_api_router
from dashboard.routes.chat_api_v1 import router as chat_api_v1_router
from dashboard.routes.daily_run import router as daily_run_router
from dashboard.routes.hermes import router as hermes_router
from dashboard.routes.db_explorer import router as db_explorer_router
from dashboard.routes.discord import router as discord_router
from dashboard.routes.experiments import router as experiments_router
from dashboard.routes.grok import router as grok_router
from dashboard.routes.health import router as health_router
from dashboard.routes.hermes import router as hermes_router
from dashboard.routes.loop_qa import router as loop_qa_router
from dashboard.routes.memory import router as memory_router
from dashboard.routes.mobile import router as mobile_router

View File

@@ -41,6 +41,7 @@ def _save_voice_settings(data: dict) -> None:
except Exception as exc:
logger.warning("Failed to save voice settings: %s", exc)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/voice", tags=["voice"])

View File

@@ -4,6 +4,6 @@ Monitors the local machine (Hermes/M3 Max) for memory pressure, disk usage,
Ollama model health, zombie processes, and network connectivity.
"""
from infrastructure.hermes.monitor import HermesMonitor, HealthLevel, HealthReport, hermes_monitor
from infrastructure.hermes.monitor import HealthLevel, HealthReport, HermesMonitor, hermes_monitor
__all__ = ["HermesMonitor", "HealthLevel", "HealthReport", "hermes_monitor"]

View File

@@ -19,11 +19,12 @@ import json
import logging
import shutil
import subprocess
import tempfile
import time
import urllib.request
from dataclasses import dataclass, field
from datetime import UTC, datetime
from enum import Enum
from enum import StrEnum
from typing import Any
from config import settings
@@ -31,7 +32,7 @@ from config import settings
logger = logging.getLogger(__name__)
class HealthLevel(str, Enum):
class HealthLevel(StrEnum):
"""Severity level for a health check result."""
OK = "ok"
@@ -194,8 +195,7 @@ class HermesMonitor:
name="memory",
level=HealthLevel.CRITICAL,
message=(
f"Critical: only {free_gb:.1f}GB free "
f"(threshold: {memory_free_min_gb}GB)"
f"Critical: only {free_gb:.1f}GB free (threshold: {memory_free_min_gb}GB)"
),
details=details,
needs_human=True,
@@ -302,8 +302,7 @@ class HermesMonitor:
name="disk",
level=HealthLevel.CRITICAL,
message=(
f"Critical: only {free_gb:.1f}GB free "
f"(threshold: {disk_free_min_gb}GB)"
f"Critical: only {free_gb:.1f}GB free (threshold: {disk_free_min_gb}GB)"
),
details=details,
needs_human=True,
@@ -335,7 +334,7 @@ class HermesMonitor:
cutoff = time.time() - 86400 # 24 hours ago
try:
tmp = Path("/tmp")
tmp = Path(tempfile.gettempdir())
for item in tmp.iterdir():
try:
stat = item.stat()
@@ -345,11 +344,7 @@ class HermesMonitor:
freed_bytes += stat.st_size
item.unlink(missing_ok=True)
elif item.is_dir():
dir_size = sum(
f.stat().st_size
for f in item.rglob("*")
if f.is_file()
)
dir_size = sum(f.stat().st_size for f in item.rglob("*") if f.is_file())
freed_bytes += dir_size
shutil.rmtree(str(item), ignore_errors=True)
except (PermissionError, OSError):
@@ -392,10 +387,7 @@ class HermesMonitor:
return CheckResult(
name="ollama",
level=HealthLevel.OK,
message=(
f"Ollama OK — {len(models)} model(s) available, "
f"{len(loaded)} loaded"
),
message=(f"Ollama OK — {len(models)} model(s) available, {len(loaded)} loaded"),
details={
"reachable": True,
"model_count": len(models),

View File

@@ -135,7 +135,9 @@ class BannerlordObserver:
self._host = host or settings.gabs_host
self._port = port or settings.gabs_port
self._timeout = timeout if timeout is not None else settings.gabs_timeout
self._poll_interval = poll_interval if poll_interval is not None else settings.gabs_poll_interval
self._poll_interval = (
poll_interval if poll_interval is not None else settings.gabs_poll_interval
)
self._journal_path = Path(journal_path) if journal_path else _get_journal_path()
self._entry_count = 0
self._days_observed: set[str] = set()

View File

@@ -196,9 +196,7 @@ class EmotionalStateTracker:
"intensity_label": _intensity_label(self.state.intensity),
"previous_emotion": self.state.previous_emotion,
"trigger_event": self.state.trigger_event,
"prompt_modifier": EMOTION_PROMPT_MODIFIERS.get(
self.state.current_emotion, ""
),
"prompt_modifier": EMOTION_PROMPT_MODIFIERS.get(self.state.current_emotion, ""),
}
def get_prompt_modifier(self) -> str:

View File

@@ -36,7 +36,7 @@ import asyncio
import logging
import re
from dataclasses import dataclass, field
from datetime import UTC, datetime, timedelta
from datetime import UTC, datetime
from typing import Any
import httpx
@@ -70,7 +70,9 @@ _LOOP_TAG = "loop-generated"
# Regex patterns for scoring
_TAG_RE = re.compile(r"\[([^\]]+)\]")
_FILE_RE = re.compile(r"(?:src/|tests/|scripts/|\.py|\.html|\.js|\.yaml|\.toml|\.sh)", re.IGNORECASE)
_FILE_RE = re.compile(
r"(?:src/|tests/|scripts/|\.py|\.html|\.js|\.yaml|\.toml|\.sh)", re.IGNORECASE
)
_FUNC_RE = re.compile(r"(?:def |class |function |method |`\w+\(\)`)", re.IGNORECASE)
_ACCEPT_RE = re.compile(
r"(?:should|must|expect|verify|assert|test.?case|acceptance|criteria"
@@ -451,9 +453,7 @@ async def add_label(
# Apply to the issue
apply_url = _repo_url(f"issues/{issue_number}/labels")
apply_resp = await client.post(
apply_url, headers=headers, json={"labels": [label_id]}
)
apply_resp = await client.post(apply_url, headers=headers, json={"labels": [label_id]})
return apply_resp.status_code in (200, 201)
except (httpx.ConnectError, httpx.ReadError, httpx.TimeoutException) as exc:
@@ -692,7 +692,9 @@ class BacklogTriageLoop:
# 1. Fetch
raw_issues = await fetch_open_issues(client)
result.total_open = len(raw_issues)
logger.info("Triage cycle #%d: fetched %d open issues", self._cycle_count, len(raw_issues))
logger.info(
"Triage cycle #%d: fetched %d open issues", self._cycle_count, len(raw_issues)
)
# 2. Score
scored = [score_issue(i) for i in raw_issues]

View File

@@ -37,7 +37,7 @@ from __future__ import annotations
import asyncio
import logging
from dataclasses import dataclass, field
from enum import Enum
from enum import StrEnum
from typing import Any
from config import settings
@@ -48,7 +48,8 @@ logger = logging.getLogger(__name__)
# Enumerations
# ---------------------------------------------------------------------------
class AgentType(str, Enum):
class AgentType(StrEnum):
"""Known agents in the swarm."""
CLAUDE_CODE = "claude_code"
@@ -57,7 +58,7 @@ class AgentType(str, Enum):
TIMMY = "timmy"
class TaskType(str, Enum):
class TaskType(StrEnum):
"""Categories of engineering work."""
# Claude Code strengths
@@ -83,7 +84,7 @@ class TaskType(str, Enum):
ORCHESTRATION = "orchestration"
class DispatchStatus(str, Enum):
class DispatchStatus(StrEnum):
"""Lifecycle state of a dispatched task."""
PENDING = "pending"
@@ -99,6 +100,7 @@ class DispatchStatus(str, Enum):
# Agent registry
# ---------------------------------------------------------------------------
@dataclass
class AgentSpec:
"""Capabilities and limits for a single agent."""
@@ -106,9 +108,9 @@ class AgentSpec:
name: AgentType
display_name: str
strengths: frozenset[TaskType]
gitea_label: str | None # label to apply when dispatching
gitea_label: str | None # label to apply when dispatching
max_concurrent: int = 1
interface: str = "gitea" # "gitea" | "api" | "local"
interface: str = "gitea" # "gitea" | "api" | "local"
api_endpoint: str | None = None # for interface="api"
@@ -197,6 +199,7 @@ _TASK_ROUTING: dict[TaskType, AgentType] = {
# Dispatch result
# ---------------------------------------------------------------------------
@dataclass
class DispatchResult:
"""Outcome of a dispatch call."""
@@ -220,6 +223,7 @@ class DispatchResult:
# Routing logic
# ---------------------------------------------------------------------------
def select_agent(task_type: TaskType) -> AgentType:
"""Return the best agent for *task_type* based on the routing table.
@@ -248,11 +252,23 @@ def infer_task_type(title: str, description: str = "") -> TaskType:
text = (title + " " + description).lower()
_SIGNALS: list[tuple[TaskType, frozenset[str]]] = [
(TaskType.ARCHITECTURE, frozenset({"architect", "design", "adr", "system design", "schema"})),
(TaskType.REFACTORING, frozenset({"refactor", "clean up", "cleanup", "reorganise", "reorganize"})),
(
TaskType.ARCHITECTURE,
frozenset({"architect", "design", "adr", "system design", "schema"}),
),
(
TaskType.REFACTORING,
frozenset({"refactor", "clean up", "cleanup", "reorganise", "reorganize"}),
),
(TaskType.CODE_REVIEW, frozenset({"review", "pr review", "pull request review", "audit"})),
(TaskType.COMPLEX_REASONING, frozenset({"complex", "hard problem", "debug", "investigate", "diagnose"})),
(TaskType.RESEARCH, frozenset({"research", "survey", "literature", "benchmark", "analyse", "analyze"})),
(
TaskType.COMPLEX_REASONING,
frozenset({"complex", "hard problem", "debug", "investigate", "diagnose"}),
),
(
TaskType.RESEARCH,
frozenset({"research", "survey", "literature", "benchmark", "analyse", "analyze"}),
),
(TaskType.ANALYSIS, frozenset({"analysis", "profil", "trace", "metric", "performance"})),
(TaskType.TRIAGE, frozenset({"triage", "classify", "prioritise", "prioritize"})),
(TaskType.PLANNING, frozenset({"plan", "roadmap", "milestone", "epic", "spike"})),
@@ -273,6 +289,7 @@ def infer_task_type(title: str, description: str = "") -> TaskType:
# Gitea helpers
# ---------------------------------------------------------------------------
async def _post_gitea_comment(
client: Any,
base_url: str,
@@ -405,6 +422,7 @@ async def _poll_issue_completion(
# Core dispatch functions
# ---------------------------------------------------------------------------
async def _dispatch_via_gitea(
agent: AgentType,
issue_number: int,
@@ -479,7 +497,11 @@ async def _dispatch_via_gitea(
)
# 2. Post assignment comment
criteria_md = "\n".join(f"- {c}" for c in acceptance_criteria) if acceptance_criteria else "_None specified_"
criteria_md = (
"\n".join(f"- {c}" for c in acceptance_criteria)
if acceptance_criteria
else "_None specified_"
)
comment_body = (
f"## Assigned to {spec.display_name}\n\n"
f"**Task type:** `{task_type.value}`\n\n"
@@ -616,9 +638,7 @@ async def _dispatch_local(
assumed to succeed at dispatch time).
"""
task_type = infer_task_type(title, description)
logger.info(
"Timmy handling task locally: %r (issue #%s)", title[:60], issue_number
)
logger.info("Timmy handling task locally: %r (issue #%s)", title[:60], issue_number)
return DispatchResult(
task_type=task_type,
agent=AgentType.TIMMY,
@@ -632,6 +652,7 @@ async def _dispatch_local(
# Public entry point
# ---------------------------------------------------------------------------
async def dispatch_task(
title: str,
description: str = "",
@@ -769,9 +790,7 @@ async def _log_escalation(
f"---\n*Timmy agent dispatcher.*"
)
async with httpx.AsyncClient(timeout=10) as client:
await _post_gitea_comment(
client, base_url, repo, headers, issue_number, body
)
await _post_gitea_comment(client, base_url, repo, headers, issue_number, body)
except Exception as exc:
logger.warning("Failed to post escalation comment: %s", exc)
@@ -780,6 +799,7 @@ async def _log_escalation(
# Monitoring helper
# ---------------------------------------------------------------------------
async def wait_for_completion(
issue_number: int,
poll_interval: int = 60,

View File

@@ -418,9 +418,7 @@ class MCPBridge:
return f"Error executing {name}: {exc}"
@staticmethod
def _build_initial_messages(
prompt: str, system_prompt: str | None
) -> list[dict]:
def _build_initial_messages(prompt: str, system_prompt: str | None) -> list[dict]:
"""Build the initial message list for a run."""
messages: list[dict] = []
if system_prompt:
@@ -512,9 +510,7 @@ class MCPBridge:
error_msg = ""
try:
content, tool_calls_made, rounds, error_msg = await self._run_tool_loop(
messages, tools
)
content, tool_calls_made, rounds, error_msg = await self._run_tool_loop(messages, tools)
except httpx.ConnectError as exc:
logger.warning("Ollama connection failed: %s", exc)
error_msg = f"Ollama connection failed: {exc}"

View File

@@ -47,13 +47,11 @@ _DEFAULT_IDLE_THRESHOLD = 30
class AgentStatus:
"""Health snapshot for one agent at a point in time."""
agent: str # "claude" | "kimi" | "timmy"
agent: str # "claude" | "kimi" | "timmy"
is_idle: bool = True
active_issue_numbers: list[int] = field(default_factory=list)
stuck_issue_numbers: list[int] = field(default_factory=list)
checked_at: str = field(
default_factory=lambda: datetime.now(UTC).isoformat()
)
checked_at: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
@property
def is_stuck(self) -> bool:
@@ -69,9 +67,7 @@ class AgentHealthReport:
"""Combined health report for all monitored agents."""
agents: list[AgentStatus] = field(default_factory=list)
generated_at: str = field(
default_factory=lambda: datetime.now(UTC).isoformat()
)
generated_at: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
@property
def any_stuck(self) -> bool:
@@ -193,18 +189,14 @@ async def check_agent_health(
try:
async with httpx.AsyncClient(timeout=15) as client:
issues = await _fetch_labeled_issues(
client, base_url, headers, repo, label
)
issues = await _fetch_labeled_issues(client, base_url, headers, repo, label)
for issue in issues:
num = issue.get("number", 0)
status.active_issue_numbers.append(num)
# Check last activity
last_activity = await _last_comment_time(
client, base_url, headers, repo, num
)
last_activity = await _last_comment_time(client, base_url, headers, repo, num)
if last_activity is None:
last_activity = await _issue_created_time(issue)

View File

@@ -91,9 +91,9 @@ _PRIORITY_LABEL_SCORES: dict[str, int] = {
class AgentTarget(StrEnum):
"""Which agent should handle this issue."""
TIMMY = "timmy" # Timmy handles locally (self)
TIMMY = "timmy" # Timmy handles locally (self)
CLAUDE = "claude" # Dispatch to Claude Code
KIMI = "kimi" # Dispatch to Kimi Code
KIMI = "kimi" # Dispatch to Kimi Code
@dataclass
@@ -172,9 +172,7 @@ def triage_issues(raw_issues: list[dict[str, Any]]) -> list[TriagedIssue]:
title = issue.get("title", "")
body = issue.get("body") or ""
labels = _extract_labels(issue)
assignees = [
a.get("login", "") for a in issue.get("assignees") or []
]
assignees = [a.get("login", "") for a in issue.get("assignees") or []]
url = issue.get("html_url", "")
priority = _score_priority(labels, assignees)
@@ -252,9 +250,7 @@ async def fetch_open_issues(
params=params,
)
if resp.status_code != 200:
logger.warning(
"fetch_open_issues: Gitea returned %s", resp.status_code
)
logger.warning("fetch_open_issues: Gitea returned %s", resp.status_code)
return []
issues = resp.json()

View File

@@ -34,7 +34,7 @@ _LABEL_MAP: dict[AgentTarget, str] = {
_LABEL_COLORS: dict[str, str] = {
"claude-ready": "#8b6f47", # warm brown
"kimi-ready": "#006b75", # dark teal
"kimi-ready": "#006b75", # dark teal
"timmy-ready": "#0075ca", # blue
}
@@ -52,9 +52,7 @@ class DispatchRecord:
issue_title: str
agent: AgentTarget
rationale: str
dispatched_at: str = field(
default_factory=lambda: datetime.now(UTC).isoformat()
)
dispatched_at: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
label_applied: bool = False
comment_posted: bool = False
@@ -170,9 +168,7 @@ async def dispatch_issue(issue: TriagedIssue) -> DispatchRecord:
try:
async with httpx.AsyncClient(timeout=15) as client:
label_id = await _get_or_create_label(
client, base_url, headers, repo, label_name
)
label_id = await _get_or_create_label(client, base_url, headers, repo, label_name)
# Apply label
if label_id is not None:

View File

@@ -22,9 +22,9 @@ logger = logging.getLogger(__name__)
# Thresholds
# ---------------------------------------------------------------------------
_WARN_DISK_PCT = 85.0 # warn when disk is more than 85% full
_WARN_MEM_PCT = 90.0 # warn when memory is more than 90% used
_WARN_CPU_PCT = 95.0 # warn when CPU is above 95% sustained
_WARN_DISK_PCT = 85.0 # warn when disk is more than 85% full
_WARN_MEM_PCT = 90.0 # warn when memory is more than 90% used
_WARN_CPU_PCT = 95.0 # warn when CPU is above 95% sustained
# ---------------------------------------------------------------------------
@@ -63,9 +63,7 @@ class SystemSnapshot:
memory: MemoryUsage = field(default_factory=MemoryUsage)
ollama: OllamaHealth = field(default_factory=OllamaHealth)
warnings: list[str] = field(default_factory=list)
taken_at: str = field(
default_factory=lambda: datetime.now(UTC).isoformat()
)
taken_at: str = field(default_factory=lambda: datetime.now(UTC).isoformat())
@property
def healthy(self) -> bool:
@@ -117,8 +115,8 @@ def _probe_memory() -> MemoryUsage:
def _probe_ollama_sync(ollama_url: str) -> OllamaHealth:
"""Synchronous Ollama health probe — run in a thread."""
try:
import urllib.request
import json
import urllib.request
url = ollama_url.rstrip("/") + "/api/tags"
with urllib.request.urlopen(url, timeout=5) as resp: # noqa: S310
@@ -154,14 +152,12 @@ async def get_system_snapshot() -> SystemSnapshot:
if disk.percent_used >= _WARN_DISK_PCT:
warnings.append(
f"Disk {disk.path}: {disk.percent_used:.0f}% used "
f"({disk.free_gb:.1f} GB free)"
f"Disk {disk.path}: {disk.percent_used:.0f}% used ({disk.free_gb:.1f} GB free)"
)
if memory.percent_used >= _WARN_MEM_PCT:
warnings.append(
f"Memory: {memory.percent_used:.0f}% used "
f"({memory.available_gb:.1f} GB available)"
f"Memory: {memory.percent_used:.0f}% used ({memory.available_gb:.1f} GB available)"
)
if not ollama.reachable:
@@ -216,7 +212,5 @@ async def cleanup_stale_files(
errors.append(str(exc))
await asyncio.to_thread(_cleanup)
logger.info(
"cleanup_stale_files: deleted %d files, %d errors", deleted, len(errors)
)
logger.info("cleanup_stale_files: deleted %d files, %d errors", deleted, len(errors))
return {"deleted_count": deleted, "errors": errors}

View File

@@ -10,14 +10,12 @@ from __future__ import annotations
import json
import socket
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from integrations.bannerlord.gabs_client import GabsClient, GabsError
# ── GabsClient unit tests ─────────────────────────────────────────────────────
@@ -236,7 +234,13 @@ class TestBannerlordObserver:
snapshot = {
"game_state": {"day": 7, "season": "winter", "campaign_phase": "early"},
"player": {"name": "Timmy", "clan": "Thalheimer", "renown": 42, "level": 3, "gold": 1000},
"player": {
"name": "Timmy",
"clan": "Thalheimer",
"renown": 42,
"level": 3,
"gold": 1000,
},
"player_party": {"size": 25, "morale": 80, "food_days_left": 5},
"kingdoms": [{"name": "Vlandia", "ruler": "Derthert", "military_strength": 5000}],
}

View File

@@ -1,7 +1,6 @@
"""Tests for agent emotional state simulation (src/timmy/agents/emotional_state.py)."""
import time
from unittest.mock import patch
from timmy.agents.emotional_state import (
EMOTION_PROMPT_MODIFIERS,

View File

@@ -4,8 +4,6 @@ from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from timmy.dispatcher import (
AGENT_REGISTRY,
AgentType,
@@ -21,11 +19,11 @@ from timmy.dispatcher import (
wait_for_completion,
)
# ---------------------------------------------------------------------------
# Agent registry
# ---------------------------------------------------------------------------
class TestAgentRegistry:
def test_all_agents_present(self):
for member in AgentType:
@@ -41,7 +39,7 @@ class TestAgentRegistry:
assert spec.gitea_label, f"{agent} is gitea interface but has no label"
def test_non_gitea_agents_have_no_labels(self):
for agent, spec in AGENT_REGISTRY.items():
for _agent, spec in AGENT_REGISTRY.items():
if spec.interface not in ("gitea",):
# api and local agents may have no label
assert spec.gitea_label is None or spec.interface == "gitea"
@@ -55,6 +53,7 @@ class TestAgentRegistry:
# select_agent
# ---------------------------------------------------------------------------
class TestSelectAgent:
def test_architecture_routes_to_claude(self):
assert select_agent(TaskType.ARCHITECTURE) == AgentType.CLAUDE_CODE
@@ -85,6 +84,7 @@ class TestSelectAgent:
# infer_task_type
# ---------------------------------------------------------------------------
class TestInferTaskType:
def test_architecture_keyword(self):
assert infer_task_type("Design the LLM router architecture") == TaskType.ARCHITECTURE
@@ -119,6 +119,7 @@ class TestInferTaskType:
# DispatchResult
# ---------------------------------------------------------------------------
class TestDispatchResult:
def test_success_when_assigned(self):
r = DispatchResult(
@@ -161,6 +162,7 @@ class TestDispatchResult:
# _dispatch_local
# ---------------------------------------------------------------------------
class TestDispatchLocal:
async def test_returns_assigned(self):
result = await _dispatch_local(
@@ -190,6 +192,7 @@ class TestDispatchLocal:
# _dispatch_via_api
# ---------------------------------------------------------------------------
class TestDispatchViaApi:
async def test_no_endpoint_returns_failed(self):
result = await _dispatch_via_api(
@@ -304,7 +307,9 @@ class TestDispatchViaGitea:
assert result.status == DispatchStatus.ASSIGNED
async def test_no_gitea_token_returns_failed(self):
bad_settings = MagicMock(gitea_enabled=True, gitea_token="", gitea_url="http://x", gitea_repo="a/b")
bad_settings = MagicMock(
gitea_enabled=True, gitea_token="", gitea_url="http://x", gitea_repo="a/b"
)
with patch("timmy.dispatcher.settings", bad_settings):
result = await _dispatch_via_gitea(
agent=AgentType.CLAUDE_CODE,
@@ -317,7 +322,9 @@ class TestDispatchViaGitea:
assert "not configured" in (result.error or "").lower()
async def test_gitea_disabled_returns_failed(self):
bad_settings = MagicMock(gitea_enabled=False, gitea_token="tok", gitea_url="http://x", gitea_repo="a/b")
bad_settings = MagicMock(
gitea_enabled=False, gitea_token="tok", gitea_url="http://x", gitea_repo="a/b"
)
with patch("timmy.dispatcher.settings", bad_settings):
result = await _dispatch_via_gitea(
agent=AgentType.CLAUDE_CODE,
@@ -368,6 +375,7 @@ class TestDispatchViaGitea:
# dispatch_task (integration-style)
# ---------------------------------------------------------------------------
class TestDispatchTask:
async def test_empty_title_returns_failed(self):
result = await dispatch_task(title=" ")
@@ -396,7 +404,9 @@ class TestDispatchTask:
client_mock = AsyncMock()
client_mock.__aenter__ = AsyncMock(return_value=client_mock)
client_mock.__aexit__ = AsyncMock(return_value=False)
client_mock.get = AsyncMock(return_value=MagicMock(status_code=200, json=MagicMock(return_value=[])))
client_mock.get = AsyncMock(
return_value=MagicMock(status_code=200, json=MagicMock(return_value=[]))
)
create_resp = MagicMock(status_code=201, json=MagicMock(return_value={"id": 1}))
apply_resp = MagicMock(status_code=201)
comment_resp = MagicMock(status_code=201, json=MagicMock(return_value={"id": 5}))
@@ -464,6 +474,7 @@ class TestDispatchTask:
# wait_for_completion
# ---------------------------------------------------------------------------
class TestWaitForCompletion:
async def test_returns_completed_when_issue_closed(self):
closed_resp = MagicMock(

View File

@@ -25,7 +25,6 @@ from timmy.backlog_triage import (
score_issue,
)
# ── Fixtures ─────────────────────────────────────────────────────────────────

View File

@@ -7,7 +7,6 @@ Refs: #1073
"""
import json
from io import BytesIO
from unittest.mock import MagicMock, patch
import pytest
@@ -79,7 +78,9 @@ def test_get_memory_info_handles_subprocess_failure(monitor):
@pytest.mark.asyncio
async def test_check_memory_ok(monitor):
with patch.object(monitor, "_get_memory_info", return_value={"free_gb": 20.0, "total_gb": 64.0}):
with patch.object(
monitor, "_get_memory_info", return_value={"free_gb": 20.0, "total_gb": 64.0}
):
result = await monitor._check_memory()
assert result.name == "memory"
@@ -126,7 +127,7 @@ async def test_check_memory_exception_returns_unknown(monitor):
@pytest.mark.asyncio
async def test_check_disk_ok(monitor):
usage = MagicMock()
usage.free = 100 * (1024**3) # 100 GB
usage.free = 100 * (1024**3) # 100 GB
usage.total = 500 * (1024**3) # 500 GB
usage.used = 400 * (1024**3)
@@ -140,7 +141,7 @@ async def test_check_disk_ok(monitor):
@pytest.mark.asyncio
async def test_check_disk_low_triggers_cleanup(monitor):
usage = MagicMock()
usage.free = 5 * (1024**3) # 5 GB — below threshold
usage.free = 5 * (1024**3) # 5 GB — below threshold
usage.total = 500 * (1024**3)
usage.used = 495 * (1024**3)
@@ -176,12 +177,8 @@ async def test_check_disk_critical_when_cleanup_fails(monitor):
def test_get_ollama_status_reachable(monitor):
tags_body = json.dumps({
"models": [{"name": "qwen3:30b"}, {"name": "llama3.1:8b"}]
}).encode()
ps_body = json.dumps({
"models": [{"name": "qwen3:30b", "size": 1000}]
}).encode()
tags_body = json.dumps({"models": [{"name": "qwen3:30b"}, {"name": "llama3.1:8b"}]}).encode()
ps_body = json.dumps({"models": [{"name": "qwen3:30b", "size": 1000}]}).encode()
responses = [
_FakeHTTPResponse(tags_body),

View File

@@ -6,7 +6,6 @@ import pytest
from timmy.vassal.agent_health import AgentHealthReport, AgentStatus
# ---------------------------------------------------------------------------
# AgentStatus
# ---------------------------------------------------------------------------
@@ -49,9 +48,7 @@ def test_report_any_stuck():
def test_report_all_idle():
report = AgentHealthReport(
agents=[AgentStatus(agent="claude"), AgentStatus(agent="kimi")]
)
report = AgentHealthReport(agents=[AgentStatus(agent="claude"), AgentStatus(agent="kimi")])
assert report.all_idle is True

View File

@@ -6,14 +6,12 @@ import pytest
from timmy.vassal.backlog import (
AgentTarget,
TriagedIssue,
_choose_agent,
_extract_labels,
_score_priority,
triage_issues,
)
# ---------------------------------------------------------------------------
# _extract_labels
# ---------------------------------------------------------------------------

View File

@@ -12,7 +12,6 @@ from timmy.vassal.house_health import (
_probe_disk,
)
# ---------------------------------------------------------------------------
# Data model tests
# ---------------------------------------------------------------------------

View File

@@ -6,7 +6,6 @@ import pytest
from timmy.vassal.orchestration_loop import VassalCycleRecord, VassalOrchestrator
# ---------------------------------------------------------------------------
# VassalCycleRecord
# ---------------------------------------------------------------------------
@@ -134,6 +133,6 @@ def test_orchestrator_stop_when_not_running():
def test_module_singleton_exists():
from timmy.vassal import vassal_orchestrator, VassalOrchestrator
from timmy.vassal import VassalOrchestrator, vassal_orchestrator
assert isinstance(vassal_orchestrator, VassalOrchestrator)