From bb9311704ae9d3208ba50389e302faba3866d4ef Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Mon, 23 Mar 2026 19:33:08 -0400 Subject: [PATCH] fix: resolve pre-existing ruff lint errors blocking git hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cascade.py: add TYPE_CHECKING guard for TaskComplexity to fix F821 - _base.py: add noqa: F401 to re-exported agno tool imports (FileTools, PythonTools, ShellTools, Toolkit) — these are used by other modules - test_three_strike.py: rename unused loop var `i` to `_` (B007) - Run tox -e format to auto-fix I001 import-sort and F401 errors across 23 files (isort ordering and unused pytest import) Fixes #1247 --- src/dashboard/app.py | 4 +- src/dashboard/routes/nexus.py | 8 ++-- src/dashboard/routes/three_strike.py | 4 +- src/infrastructure/router/cascade.py | 5 +- src/infrastructure/router/classifier.py | 17 ++++--- src/timmy/autoresearch.py | 28 +++-------- src/timmy/cli.py | 48 +++++-------------- src/timmy/memory/embeddings.py | 35 +++++++++----- src/timmy/memory_system.py | 8 ++-- src/timmy/sovereignty/three_strike.py | 8 +--- src/timmy/tools/__init__.py | 4 +- src/timmy/tools/_base.py | 10 ++-- src/timmy/tools/_registry.py | 4 +- src/timmy/tools/file_tools.py | 4 +- src/timmy/tools/system_tools.py | 2 +- tests/dashboard/test_nexus.py | 6 ++- .../infrastructure/test_router_classifier.py | 2 - tests/timmy/test_autoresearch.py | 8 +--- tests/timmy/test_semantic_memory.py | 8 +++- tests/timmy/test_three_strike.py | 2 +- tests/timmy/test_three_strike_routes.py | 4 +- tests/unit/test_paperclip.py | 13 +++-- tests/unit/test_vassal_orchestration_loop.py | 14 ++++-- 23 files changed, 116 insertions(+), 130 deletions(-) diff --git a/src/dashboard/app.py b/src/dashboard/app.py index 67050f7e..3ee815f9 100644 --- a/src/dashboard/app.py +++ b/src/dashboard/app.py @@ -42,19 +42,19 @@ 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 -from dashboard.routes.nexus import router as nexus_router from dashboard.routes.models import api_router as models_api_router from dashboard.routes.models import router as models_router +from dashboard.routes.nexus import router as nexus_router from dashboard.routes.quests import router as quests_router from dashboard.routes.scorecards import router as scorecards_router from dashboard.routes.sovereignty_metrics import router as sovereignty_metrics_router from dashboard.routes.sovereignty_ws import router as sovereignty_ws_router -from dashboard.routes.three_strike import router as three_strike_router from dashboard.routes.spark import router as spark_router from dashboard.routes.system import router as system_router from dashboard.routes.tasks import router as tasks_router from dashboard.routes.telegram import router as telegram_router from dashboard.routes.thinking import router as thinking_router +from dashboard.routes.three_strike import router as three_strike_router from dashboard.routes.tools import router as tools_router from dashboard.routes.tower import router as tower_router from dashboard.routes.voice import router as voice_router diff --git a/src/dashboard/routes/nexus.py b/src/dashboard/routes/nexus.py index 061d1485..9a76caa7 100644 --- a/src/dashboard/routes/nexus.py +++ b/src/dashboard/routes/nexus.py @@ -12,7 +12,7 @@ Routes: import asyncio import logging -from datetime import datetime, timezone +from datetime import UTC, datetime from fastapi import APIRouter, Form, Request from fastapi.responses import HTMLResponse @@ -39,7 +39,7 @@ _nexus_log: list[dict] = [] def _ts() -> str: - return datetime.now(timezone.utc).strftime("%H:%M:%S") + return datetime.now(UTC).strftime("%H:%M:%S") def _append_log(role: str, content: str) -> None: @@ -94,9 +94,7 @@ async def nexus_chat(request: Request, message: str = Form(...)): # Fetch semantically relevant memories to surface in the sidebar try: - memory_hits = await asyncio.to_thread( - search_memories, query=message, limit=4 - ) + memory_hits = await asyncio.to_thread(search_memories, query=message, limit=4) except Exception as exc: logger.warning("Nexus memory search failed: %s", exc) memory_hits = [] diff --git a/src/dashboard/routes/three_strike.py b/src/dashboard/routes/three_strike.py index b46f9c7a..e215dcd3 100644 --- a/src/dashboard/routes/three_strike.py +++ b/src/dashboard/routes/three_strike.py @@ -101,9 +101,7 @@ async def record_strike(body: RecordRequest) -> dict[str, Any]: @router.post("/{category}/{key}/automation") -async def register_automation( - category: str, key: str, body: AutomationRequest -) -> dict[str, bool]: +async def register_automation(category: str, key: str, body: AutomationRequest) -> dict[str, bool]: """Register an automation artifact to unblock a (category, key) pair.""" detector = get_detector() detector.register_automation(category, key, body.artifact_path) diff --git a/src/infrastructure/router/cascade.py b/src/infrastructure/router/cascade.py index 7789645b..306b157f 100644 --- a/src/infrastructure/router/cascade.py +++ b/src/infrastructure/router/cascade.py @@ -16,7 +16,10 @@ from dataclasses import dataclass, field from datetime import UTC, datetime from enum import Enum from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from infrastructure.router.classifier import TaskComplexity from config import settings diff --git a/src/infrastructure/router/classifier.py b/src/infrastructure/router/classifier.py index 26e2fdc2..5399f97d 100644 --- a/src/infrastructure/router/classifier.py +++ b/src/infrastructure/router/classifier.py @@ -13,7 +13,7 @@ from enum import Enum class TaskComplexity(Enum): """Task complexity tier for model routing.""" - SIMPLE = "simple" # Qwen3-8B Q6_K: routine, latency-sensitive + SIMPLE = "simple" # Qwen3-8B Q6_K: routine, latency-sensitive COMPLEX = "complex" # Qwen3-14B Q5_K_M: quality-sensitive, multi-step @@ -118,12 +118,15 @@ def classify_task(messages: list[dict]) -> TaskComplexity: return TaskComplexity.SIMPLE # Concatenate all user-turn content for analysis - user_content = " ".join( - msg.get("content", "") - for msg in messages - if msg.get("role") in ("user", "human") - and isinstance(msg.get("content"), str) - ).lower().strip() + user_content = ( + " ".join( + msg.get("content", "") + for msg in messages + if msg.get("role") in ("user", "human") and isinstance(msg.get("content"), str) + ) + .lower() + .strip() + ) if not user_content: return TaskComplexity.SIMPLE diff --git a/src/timmy/autoresearch.py b/src/timmy/autoresearch.py index b7013063..0d79450b 100644 --- a/src/timmy/autoresearch.py +++ b/src/timmy/autoresearch.py @@ -130,9 +130,7 @@ def run_experiment( "log": output[-2000:], # Keep last 2k chars "duration_s": duration, "success": result.returncode == 0, - "error": ( - None if result.returncode == 0 else f"Exit code {result.returncode}" - ), + "error": (None if result.returncode == 0 else f"Exit code {result.returncode}"), } except subprocess.TimeoutExpired: duration = int(time.monotonic() - start) @@ -186,13 +184,9 @@ def evaluate_result( pct = (delta / baseline) * 100 if baseline != 0 else 0.0 if delta < 0: - return ( - f"Improvement: {metric_name} {baseline:.4f} -> {current:.4f} ({pct:+.2f}%)" - ) + return f"Improvement: {metric_name} {baseline:.4f} -> {current:.4f} ({pct:+.2f}%)" elif delta > 0: - return ( - f"Regression: {metric_name} {baseline:.4f} -> {current:.4f} ({pct:+.2f}%)" - ) + return f"Regression: {metric_name} {baseline:.4f} -> {current:.4f} ({pct:+.2f}%)" else: return f"No change: {metric_name} = {current:.4f}" @@ -360,9 +354,7 @@ class SystemExperiment: "log": output[-3000:], "duration_s": duration, "success": result.returncode == 0, - "error": ( - None if result.returncode == 0 else f"Exit code {result.returncode}" - ), + "error": (None if result.returncode == 0 else f"Exit code {result.returncode}"), } except subprocess.TimeoutExpired: duration = int(time.monotonic() - start) @@ -443,9 +435,7 @@ class SystemExperiment: def commit_changes(self, message: str) -> bool: """Stage and commit all changes. Returns True on success.""" try: - subprocess.run( - ["git", "add", "-A"], cwd=str(self.workspace), check=True, timeout=30 - ) + subprocess.run(["git", "add", "-A"], cwd=str(self.workspace), check=True, timeout=30) subprocess.run( ["git", "commit", "-m", message], cwd=str(self.workspace), @@ -500,9 +490,7 @@ class SystemExperiment: ``baseline`` (float|None), ``iterations`` (int), ``results`` (list). """ if create_branch: - branch_name = ( - f"autoresearch/{self.target.replace('/', '-')}-{int(time.time())}" - ) + branch_name = f"autoresearch/{self.target.replace('/', '-')}-{int(time.time())}" self.create_branch(branch_name) baseline: float | None = self.baseline @@ -532,9 +520,7 @@ class SystemExperiment: # Apply edit edit_result = self.apply_edit(hypothesis, model=model) - edit_failed = "not available" in edit_result or edit_result.startswith( - "Aider error" - ) + edit_failed = "not available" in edit_result or edit_result.startswith("Aider error") if edit_failed: logger.warning("Edit phase failed: %s", edit_result) diff --git a/src/timmy/cli.py b/src/timmy/cli.py index 44eb9b79..b05a249a 100644 --- a/src/timmy/cli.py +++ b/src/timmy/cli.py @@ -96,14 +96,10 @@ def _decide_autonomous(req, tool_name: str, tool_args: dict) -> None: logger.info("AUTO-APPROVED (allowlist): %s", tool_name) else: req.reject(note="Auto-rejected: not in allowlist") - logger.info( - "AUTO-REJECTED (not allowlisted): %s %s", tool_name, str(tool_args)[:100] - ) + logger.info("AUTO-REJECTED (not allowlisted): %s %s", tool_name, str(tool_args)[:100]) -def _handle_tool_confirmation( - agent, run_output, session_id: str, *, autonomous: bool = False -): +def _handle_tool_confirmation(agent, run_output, session_id: str, *, autonomous: bool = False): """Prompt user to approve/reject dangerous tool calls. When Agno pauses a run because a tool requires confirmation, this @@ -177,9 +173,7 @@ def think( ): """Ask Timmy to think carefully about a topic.""" timmy = create_timmy(backend=backend, session_id=_CLI_SESSION_ID) - timmy.print_response( - f"Think carefully about: {topic}", stream=True, session_id=_CLI_SESSION_ID - ) + timmy.print_response(f"Think carefully about: {topic}", stream=True, session_id=_CLI_SESSION_ID) def _read_message_input(message: list[str]) -> str: @@ -252,9 +246,7 @@ def chat( timmy = create_timmy(backend=backend, session_id=session_id) run_output = timmy.run(message_str, stream=False, session_id=session_id) - run_output = _handle_tool_confirmation( - timmy, run_output, session_id, autonomous=autonomous - ) + run_output = _handle_tool_confirmation(timmy, run_output, session_id, autonomous=autonomous) content = run_output.content if hasattr(run_output, "content") else str(run_output) if content: @@ -308,9 +300,7 @@ def repl( break try: - response = loop.run_until_complete( - chat(user_input, session_id=session_id) - ) + response = loop.run_until_complete(chat(user_input, session_id=session_id)) if response: typer.echo(response) typer.echo() @@ -373,9 +363,7 @@ def interview( typer.echo("Starting interview...\n") transcript = run_interview( - chat_fn=lambda msg: loop.run_until_complete( - chat(msg, session_id="interview") - ), + chat_fn=lambda msg: loop.run_until_complete(chat(msg, session_id="interview")), on_answer=_on_answer, ) @@ -396,9 +384,7 @@ def interview( @app.command() def up( dev: bool = typer.Option(False, "--dev", help="Enable hot-reload for development"), - build: bool = typer.Option( - True, "--build/--no-build", help="Rebuild images before starting" - ), + build: bool = typer.Option(True, "--build/--no-build", help="Rebuild images before starting"), ): """Start Timmy Time in Docker (dashboard + agents).""" cmd = ["docker", "compose"] @@ -432,18 +418,14 @@ def voice( "-w", help="Whisper model: tiny.en, base.en, small.en, medium.en", ), - use_say: bool = typer.Option( - False, "--say", help="Use macOS `say` instead of Piper TTS" - ), + use_say: bool = typer.Option(False, "--say", help="Use macOS `say` instead of Piper TTS"), threshold: float = typer.Option( 0.015, "--threshold", "-t", help="Mic silence threshold (RMS). Lower = more sensitive.", ), - silence: float = typer.Option( - 1.5, "--silence", help="Seconds of silence to end recording" - ), + silence: float = typer.Option(1.5, "--silence", help="Seconds of silence to end recording"), backend: str | None = _BACKEND_OPTION, model_size: str | None = _MODEL_SIZE_OPTION, ): @@ -487,9 +469,7 @@ def focus( None, help='Topic to focus on (e.g. "three-phase loop"). Omit to show current focus.', ), - clear: bool = typer.Option( - False, "--clear", "-c", help="Clear focus and return to broad mode" - ), + clear: bool = typer.Option(False, "--clear", "-c", help="Clear focus and return to broad mode"), ): """Set deep-focus mode on a single problem. @@ -525,9 +505,7 @@ def healthcheck( verbose: bool = typer.Option( False, "--verbose", "-v", help="Show verbose output including issue details" ), - quiet: bool = typer.Option( - False, "--quiet", "-q", help="Only show status line (no details)" - ), + quiet: bool = typer.Option(False, "--quiet", "-q", help="Only show status line (no details)"), ): """Quick health snapshot before coding. @@ -649,9 +627,7 @@ def learn( typer.echo() typer.echo(typer.style("Autoresearch", bold=True) + f" — {target}") - typer.echo( - f" metric={metric} budget={budget}min max={max_experiments} tox={tox_env}" - ) + typer.echo(f" metric={metric} budget={budget}min max={max_experiments} tox={tox_env}") if dry_run: typer.echo(" (dry-run — no changes will be made)") typer.echo() diff --git a/src/timmy/memory/embeddings.py b/src/timmy/memory/embeddings.py index 52cf01a0..4dd8b711 100644 --- a/src/timmy/memory/embeddings.py +++ b/src/timmy/memory/embeddings.py @@ -7,10 +7,11 @@ Also includes vector similarity utilities (cosine similarity, keyword overlap). """ import hashlib +import json import logging import math -import json -import httpx # Import httpx for Ollama API calls + +import httpx # Import httpx for Ollama API calls from config import settings @@ -20,14 +21,21 @@ logger = logging.getLogger(__name__) EMBEDDING_MODEL = None EMBEDDING_DIM = 384 # MiniLM dimension, will be overridden if Ollama model has different dim + class OllamaEmbedder: """Mimics SentenceTransformer interface for Ollama.""" + def __init__(self, model_name: str, ollama_url: str): self.model_name = model_name self.ollama_url = ollama_url - self.dimension = 0 # Will be updated after first call + self.dimension = 0 # Will be updated after first call - def encode(self, sentences: str | list[str], convert_to_numpy: bool = False, normalize_embeddings: bool = True) -> list[list[float]] | list[float]: + def encode( + self, + sentences: str | list[str], + convert_to_numpy: bool = False, + normalize_embeddings: bool = True, + ) -> list[list[float]] | list[float]: """Generate embeddings using Ollama.""" if isinstance(sentences, str): sentences = [sentences] @@ -43,9 +51,9 @@ class OllamaEmbedder: response.raise_for_status() embedding = response.json()["embedding"] if not self.dimension: - self.dimension = len(embedding) # Set dimension on first successful call + self.dimension = len(embedding) # Set dimension on first successful call global EMBEDDING_DIM - EMBEDDING_DIM = self.dimension # Update global EMBEDDING_DIM + EMBEDDING_DIM = self.dimension # Update global EMBEDDING_DIM all_embeddings.append(embedding) except httpx.RequestError as exc: logger.error("Ollama embeddings request failed: %s", exc) @@ -59,6 +67,7 @@ class OllamaEmbedder: return all_embeddings[0] return all_embeddings + def _get_embedding_model(): """Lazy-load embedding model, preferring Ollama if configured.""" global EMBEDDING_MODEL @@ -69,8 +78,13 @@ def _get_embedding_model(): return EMBEDDING_MODEL if settings.timmy_embedding_backend == "ollama": - logger.info("MemorySystem: Using Ollama for embeddings with model %s", settings.ollama_embedding_model) - EMBEDDING_MODEL = OllamaEmbedder(settings.ollama_embedding_model, settings.normalized_ollama_url) + logger.info( + "MemorySystem: Using Ollama for embeddings with model %s", + settings.ollama_embedding_model, + ) + EMBEDDING_MODEL = OllamaEmbedder( + settings.ollama_embedding_model, settings.normalized_ollama_url + ) # We don't know the dimension until after the first call, so keep it default for now. # It will be updated dynamically in OllamaEmbedder.encode return EMBEDDING_MODEL @@ -79,7 +93,7 @@ def _get_embedding_model(): from sentence_transformers import SentenceTransformer EMBEDDING_MODEL = SentenceTransformer("all-MiniLM-L6-v2") - EMBEDDING_DIM = 384 # Reset to MiniLM dimension + EMBEDDING_DIM = 384 # Reset to MiniLM dimension logger.info("MemorySystem: Loaded local embedding model (all-MiniLM-L6-v2)") except ImportError: logger.warning("MemorySystem: sentence-transformers not installed, using fallback") @@ -107,13 +121,12 @@ def embed_text(text: str) -> list[float]: if model and model is not False: embedding = model.encode(text) # Ensure it's a list of floats, not numpy array - if hasattr(embedding, 'tolist'): + if hasattr(embedding, "tolist"): return embedding.tolist() return embedding return _simple_hash_embedding(text) - def cosine_similarity(a: list[float], b: list[float]) -> float: """Calculate cosine similarity between two vectors.""" dot = sum(x * y for x, y in zip(a, b, strict=False)) diff --git a/src/timmy/memory_system.py b/src/timmy/memory_system.py index 924465e5..3f37a48a 100644 --- a/src/timmy/memory_system.py +++ b/src/timmy/memory_system.py @@ -1318,11 +1318,11 @@ def memory_store(topic: str, report: str, type: str = "research") -> str: try: # Dedup check for facts and research — skip if similar exists if type in ("fact", "research"): - existing = search_memories( - full_content, limit=3, context_type=type, min_relevance=0.75 - ) + existing = search_memories(full_content, limit=3, context_type=type, min_relevance=0.75) if existing: - return f"Similar {type} already stored (id={existing[0].id[:8]}). Skipping duplicate." + return ( + f"Similar {type} already stored (id={existing[0].id[:8]}). Skipping duplicate." + ) entry = store_memory( content=full_content, diff --git a/src/timmy/sovereignty/three_strike.py b/src/timmy/sovereignty/three_strike.py index 52a8bc53..06544893 100644 --- a/src/timmy/sovereignty/three_strike.py +++ b/src/timmy/sovereignty/three_strike.py @@ -222,9 +222,7 @@ class ThreeStrikeStore: ThreeStrikeError: On the third (or later) strike with no automation. """ if category not in CATEGORIES: - raise ValueError( - f"Unknown category '{category}'. Valid: {sorted(CATEGORIES)}" - ) + raise ValueError(f"Unknown category '{category}'. Valid: {sorted(CATEGORIES)}") now = datetime.now(UTC).isoformat() meta_json = json.dumps(metadata or {}) @@ -404,9 +402,7 @@ class ThreeStrikeStore: """Return all strike records ordered by last seen (most recent first).""" try: with closing(self._connect()) as conn: - rows = conn.execute( - "SELECT * FROM strikes ORDER BY last_seen DESC" - ).fetchall() + rows = conn.execute("SELECT * FROM strikes ORDER BY last_seen DESC").fetchall() return [ StrikeRecord( category=r["category"], diff --git a/src/timmy/tools/__init__.py b/src/timmy/tools/__init__.py index dd5d955e..d8efc1db 100644 --- a/src/timmy/tools/__init__.py +++ b/src/timmy/tools/__init__.py @@ -20,12 +20,12 @@ Sub-modules: # ``from timmy.tools import `` continue to work unchanged. from timmy.tools._base import ( + _AGNO_TOOLS_AVAILABLE, + _TOOL_USAGE, AgentTools, PersonaTools, ToolStats, - _AGNO_TOOLS_AVAILABLE, _ImportError, - _TOOL_USAGE, _track_tool_usage, get_tool_stats, ) diff --git a/src/timmy/tools/_base.py b/src/timmy/tools/_base.py index 98efb081..806d44e3 100644 --- a/src/timmy/tools/_base.py +++ b/src/timmy/tools/_base.py @@ -11,10 +11,10 @@ logger = logging.getLogger(__name__) # Lazy imports to handle test mocking _ImportError = None try: - from agno.tools import Toolkit - from agno.tools.file import FileTools - from agno.tools.python import PythonTools - from agno.tools.shell import ShellTools + from agno.tools import Toolkit # noqa: F401 + from agno.tools.file import FileTools # noqa: F401 + from agno.tools.python import PythonTools # noqa: F401 + from agno.tools.shell import ShellTools # noqa: F401 _AGNO_TOOLS_AVAILABLE = True except ImportError as e: @@ -41,7 +41,7 @@ class AgentTools: agent_id: str agent_name: str - toolkit: "Toolkit" + toolkit: Toolkit available_tools: list[str] = field(default_factory=list) diff --git a/src/timmy/tools/_registry.py b/src/timmy/tools/_registry.py index e6bd6bb2..ed5b12a6 100644 --- a/src/timmy/tools/_registry.py +++ b/src/timmy/tools/_registry.py @@ -16,11 +16,11 @@ from pathlib import Path from timmy.tools._base import ( _AGNO_TOOLS_AVAILABLE, - _ImportError, FileTools, PythonTools, ShellTools, Toolkit, + _ImportError, ) from timmy.tools.file_tools import ( _make_smart_read_file, @@ -363,7 +363,7 @@ AGENT_TOOLKITS: dict[str, Callable[[], Toolkit]] = { } -def get_tools_for_agent(agent_id: str, base_dir: str | Path | None = None) -> "Toolkit | None": +def get_tools_for_agent(agent_id: str, base_dir: str | Path | None = None) -> Toolkit | None: """Get the appropriate toolkit for an agent. Args: diff --git a/src/timmy/tools/file_tools.py b/src/timmy/tools/file_tools.py index 30a85f7b..9d292124 100644 --- a/src/timmy/tools/file_tools.py +++ b/src/timmy/tools/file_tools.py @@ -13,16 +13,16 @@ from pathlib import Path from timmy.tools._base import ( _AGNO_TOOLS_AVAILABLE, - _ImportError, FileTools, PythonTools, Toolkit, + _ImportError, ) logger = logging.getLogger(__name__) -def _make_smart_read_file(file_tools: "FileTools") -> Callable: +def _make_smart_read_file(file_tools: FileTools) -> Callable: """Wrap FileTools.read_file so directories auto-list their contents. When the user (or the LLM) passes a directory path to read_file, diff --git a/src/timmy/tools/system_tools.py b/src/timmy/tools/system_tools.py index 2b161b40..710e5f2e 100644 --- a/src/timmy/tools/system_tools.py +++ b/src/timmy/tools/system_tools.py @@ -17,11 +17,11 @@ from pathlib import Path from timmy.tools._base import ( _AGNO_TOOLS_AVAILABLE, - _ImportError, FileTools, PythonTools, ShellTools, Toolkit, + _ImportError, ) from timmy.tools.file_tools import _make_smart_read_file diff --git a/tests/dashboard/test_nexus.py b/tests/dashboard/test_nexus.py index 70faefa1..0e4cc43d 100644 --- a/tests/dashboard/test_nexus.py +++ b/tests/dashboard/test_nexus.py @@ -49,8 +49,10 @@ def test_nexus_chat_posts_message(client): def test_nexus_teach_stores_fact(client): """POST /nexus/teach should persist a fact and return confirmation.""" - with patch("dashboard.routes.nexus.store_personal_fact") as mock_store, \ - patch("dashboard.routes.nexus.recall_personal_facts_with_ids", return_value=[]): + with ( + patch("dashboard.routes.nexus.store_personal_fact") as mock_store, + patch("dashboard.routes.nexus.recall_personal_facts_with_ids", return_value=[]), + ): mock_store.return_value = None response = client.post("/nexus/teach", data={"fact": "Timmy loves Python"}) assert response.status_code == 200 diff --git a/tests/infrastructure/test_router_classifier.py b/tests/infrastructure/test_router_classifier.py index 1e9d4df7..f1442475 100644 --- a/tests/infrastructure/test_router_classifier.py +++ b/tests/infrastructure/test_router_classifier.py @@ -1,7 +1,5 @@ """Tests for Qwen3 dual-model task complexity classifier.""" -import pytest - from infrastructure.router.classifier import TaskComplexity, classify_task diff --git a/tests/timmy/test_autoresearch.py b/tests/timmy/test_autoresearch.py index a4d260fe..2e670810 100644 --- a/tests/timmy/test_autoresearch.py +++ b/tests/timmy/test_autoresearch.py @@ -39,9 +39,7 @@ class TestPrepareExperiment: from timmy.autoresearch import prepare_experiment with patch("timmy.autoresearch.subprocess.run") as mock_run: - mock_run.return_value = MagicMock( - returncode=1, stdout="", stderr="auth failed" - ) + mock_run.return_value = MagicMock(returncode=1, stdout="", stderr="auth failed") result = prepare_experiment(tmp_path) assert "failed" in result.lower() @@ -104,9 +102,7 @@ class TestRunExperiment: (repo_dir / "train.py").write_text("print('done')") with patch("timmy.autoresearch.subprocess.run") as mock_run: - mock_run.return_value = MagicMock( - returncode=0, stdout="no metrics here", stderr="" - ) + mock_run.return_value = MagicMock(returncode=0, stdout="no metrics here", stderr="") result = run_experiment(tmp_path) assert result["success"] is True diff --git a/tests/timmy/test_semantic_memory.py b/tests/timmy/test_semantic_memory.py index 07dcaa59..019d19ad 100644 --- a/tests/timmy/test_semantic_memory.py +++ b/tests/timmy/test_semantic_memory.py @@ -572,7 +572,9 @@ class TestMemoryStore: mock_vector_store["store"].reset_mock() # Test with 'research' - result = memory_store(topic="Similar research", report="Similar research content", type="research") + result = memory_store( + topic="Similar research", report="Similar research content", type="research" + ) assert "similar" in result.lower() or "duplicate" in result.lower() mock_vector_store["store"].assert_not_called() @@ -600,7 +602,9 @@ class TestMemoryStore: valid_types = ["fact", "conversation", "document", "research"] for ctx_type in valid_types: mock_vector_store["store"].reset_mock() - memory_store(topic=f"Topic for {ctx_type}", report=f"Content for {ctx_type}", type=ctx_type) + memory_store( + topic=f"Topic for {ctx_type}", report=f"Content for {ctx_type}", type=ctx_type + ) mock_vector_store["store"].assert_called_once() def test_memory_store_strips_report_and_adds_topic(self, mock_vector_store): diff --git a/tests/timmy/test_three_strike.py b/tests/timmy/test_three_strike.py index c52c51df..165b5903 100644 --- a/tests/timmy/test_three_strike.py +++ b/tests/timmy/test_three_strike.py @@ -190,7 +190,7 @@ class TestThreeStrikeStore: @pytest.mark.unit def test_get_events_respects_limit(self, store): - for i in range(5): + for _ in range(5): try: store.record("vlm_prompt_edit", "el") except ThreeStrikeError: diff --git a/tests/timmy/test_three_strike_routes.py b/tests/timmy/test_three_strike_routes.py index ef50c807..b1118c1a 100644 --- a/tests/timmy/test_three_strike_routes.py +++ b/tests/timmy/test_three_strike_routes.py @@ -72,9 +72,7 @@ class TestThreeStrikeRoutes: "/sovereignty/three-strike/record", json={"category": "vlm_prompt_edit", "key": "events_test_key"}, ) - response = client.get( - "/sovereignty/three-strike/vlm_prompt_edit/events_test_key/events" - ) + response = client.get("/sovereignty/three-strike/vlm_prompt_edit/events_test_key/events") assert response.status_code == 200 data = response.json() assert data["category"] == "vlm_prompt_edit" diff --git a/tests/unit/test_paperclip.py b/tests/unit/test_paperclip.py index 1120cb79..d38859e0 100644 --- a/tests/unit/test_paperclip.py +++ b/tests/unit/test_paperclip.py @@ -310,7 +310,9 @@ class TestResearchOrchestrator: mock_llm_client = MagicMock() mock_llm_client.completion = AsyncMock(return_value=mock_llm_response) - with patch("timmy.paperclip.google_web_search", new=AsyncMock(return_value=mock_search_results)): + with patch( + "timmy.paperclip.google_web_search", new=AsyncMock(return_value=mock_search_results) + ): with patch("timmy.paperclip.get_llm_client", return_value=mock_llm_client): report = await orchestrator.run_research_pipeline("test query") @@ -358,7 +360,10 @@ class TestResearchOrchestrator: orchestrator.run_research_pipeline = AsyncMock(return_value=mock_report) orchestrator.post_gitea_comment = AsyncMock() - with patch("timmy.paperclip.triage_research_report", new=AsyncMock(return_value=mock_triage_results)): + with patch( + "timmy.paperclip.triage_research_report", + new=AsyncMock(return_value=mock_triage_results), + ): result = await orchestrator.run({"issue_number": 42}) assert "Research complete for issue #42" in result @@ -500,7 +505,9 @@ class TestPaperclipPoller: assert poller.client.update_task_status.call_count == 2 poller.client.update_task_status.assert_any_call("task-1", "running") - poller.client.update_task_status.assert_any_call("task-1", "completed", "Research completed successfully") + poller.client.update_task_status.assert_any_call( + "task-1", "completed", "Research completed successfully" + ) poller.orchestrator.run.assert_called_once_with({"issue_number": 42}) @pytest.mark.asyncio diff --git a/tests/unit/test_vassal_orchestration_loop.py b/tests/unit/test_vassal_orchestration_loop.py index 65b5a681..8904d151 100644 --- a/tests/unit/test_vassal_orchestration_loop.py +++ b/tests/unit/test_vassal_orchestration_loop.py @@ -321,7 +321,10 @@ async def test_run_cycle_counts_dispatched_issues(): patch( "timmy.vassal.backlog.fetch_open_issues", new_callable=AsyncMock, - return_value=[{"number": i, "title": f"Issue {i}", "labels": [], "assignees": []} for i in range(1, 4)], + return_value=[ + {"number": i, "title": f"Issue {i}", "labels": [], "assignees": []} + for i in range(1, 4) + ], ), patch( "timmy.vassal.backlog.triage_issues", @@ -357,7 +360,10 @@ async def test_run_cycle_respects_max_dispatch_cap(): patch( "timmy.vassal.backlog.fetch_open_issues", new_callable=AsyncMock, - return_value=[{"number": i, "title": f"Issue {i}", "labels": [], "assignees": []} for i in range(1, 6)], + return_value=[ + {"number": i, "title": f"Issue {i}", "labels": [], "assignees": []} + for i in range(1, 6) + ], ), patch( "timmy.vassal.backlog.triage_issues", @@ -392,6 +398,8 @@ def test_resolve_interval_uses_explicit_value(): def test_resolve_interval_falls_back_to_300(): orch = VassalOrchestrator() - with patch("timmy.vassal.orchestration_loop.VassalOrchestrator._resolve_interval") as mock_resolve: + with patch( + "timmy.vassal.orchestration_loop.VassalOrchestrator._resolve_interval" + ) as mock_resolve: mock_resolve.return_value = 300.0 assert orch._resolve_interval() == 300.0 -- 2.43.0