[claude] Fix 27 ruff lint errors blocking all pushes (#1149) (#1153)

Co-authored-by: Claude (Opus 4.6) <claude@hermes.local>
Co-committed-by: Claude (Opus 4.6) <claude@hermes.local>
This commit is contained in:
2026-03-23 19:06:11 +00:00
committed by rockachopa
parent da29631c43
commit 495c1ac2bd
23 changed files with 124 additions and 131 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}