personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix all flake8 warnings: unused imports, f-strings, undefined names, dead code

- Fix F821 bug: agents_dir → muse_dir in test_app_agents.py fixture
- Remove 8 unnecessary f-string prefixes (F541) across observe/, think/, scripts/
- Remove ~30 unused imports (F401) from production and test code
- Remove unused variable assignments (F841) in tests
- Rename ambiguous variable l → line in test_describe_config.py (E741)
- Remove redundant datetime re-import in calendar routes (F402)
- Suppress E402 (deferred imports) in .flake8 — all are intentional
- Remove dead [tool.flake8] section from pyproject.toml (flake8 reads .flake8)
- Add config location comments in Makefile and pyproject.toml

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+47 -70
+1 -1
.flake8
··· 1 1 [flake8] 2 - extend-ignore = W503,W504,E501,E203 2 + extend-ignore = W503,W504,E501,E203,E402 3 3 exclude = .venv,scratch,logs,build,dist,*.egg-info,.mypy_cache,.pytest_cache,__pycache__
+1
Makefile
··· 111 111 $(TEST_ENV) $(PYTEST) tests/ -v --cov=. && $(TEST_ENV) $(PYTEST) apps/ -v --cov=. --cov-append 112 112 113 113 # Auto-format code, then report any remaining issues 114 + # Linting config: .flake8 | Formatting config: pyproject.toml [tool.black] / [tool.isort] 114 115 format: .installed 115 116 @echo "Formatting code with black and isort..." 116 117 @$(BLACK) .
-4
apps/calendar/routes.py
··· 3 3 4 4 from __future__ import annotations 5 5 6 - import json 7 6 import os 8 7 import re 9 8 from datetime import datetime ··· 11 10 12 11 from flask import Blueprint, jsonify, redirect, render_template, url_for 13 12 14 - from convey import state 15 13 from convey.utils import DATE_RE, format_date 16 14 from observe.utils import VIDEO_EXTENSIONS 17 15 from think.utils import day_path ··· 223 221 continue 224 222 225 223 # Format timestamp as human-readable time 226 - from datetime import datetime 227 - 228 224 try: 229 225 time_obj = datetime.strptime(timestamp[:6], "%H%M%S") 230 226 human_time = time_obj.strftime("%I:%M:%S %p").lstrip("0")
+1 -1
apps/dev/maint/000_example.py
··· 20 20 21 21 def main(): 22 22 parser = argparse.ArgumentParser(description=__doc__.split("\n")[0]) 23 - args = setup_cli(parser) 23 + setup_cli(parser) 24 24 25 25 logger.info("Example maintenance task starting") 26 26 print("This is an example maintenance task.")
+1 -1
apps/remote/routes.py
··· 24 24 from flask import Blueprint, jsonify, request 25 25 from werkzeug.utils import secure_filename 26 26 27 - from apps.utils import get_app_storage_path, log_app_action 27 + from apps.utils import log_app_action 28 28 from convey import emit 29 29 from observe.utils import ( 30 30 MAX_SEGMENT_ATTEMPTS,
+1 -2
apps/remote/tests/test_client.py
··· 5 5 6 6 from __future__ import annotations 7 7 8 - from pathlib import Path 9 8 from unittest.mock import MagicMock, patch 10 9 11 10 import pytest ··· 67 66 68 67 def test_upload_segment_retry_on_failure(mock_session, tmp_path): 69 68 """Test that upload retries on failure.""" 70 - from observe.sync import RETRY_BACKOFF, RemoteClient 69 + from observe.sync import RemoteClient 71 70 72 71 # Create test file 73 72 file1 = tmp_path / "audio.flac"
+9 -3
apps/settings/routes.py
··· 207 207 - config: Current transcribe config from journal 208 208 """ 209 209 try: 210 - from observe.transcribe import BACKEND_METADATA, get_backend_list 210 + from observe.transcribe import get_backend_list 211 211 212 212 config = get_journal_config() 213 213 transcribe_config = config.get("transcribe", {}) ··· 837 837 _build_generator_info(key, meta) 838 838 for key, meta in sorted( 839 839 get_muse_configs( 840 - has_tools=False, has_output=True, schedule="segment", include_disabled=True 840 + has_tools=False, 841 + has_output=True, 842 + schedule="segment", 843 + include_disabled=True, 841 844 ).items() 842 845 ) 843 846 ] ··· 845 848 _build_generator_info(key, meta) 846 849 for key, meta in sorted( 847 850 get_muse_configs( 848 - has_tools=False, has_output=True, schedule="daily", include_disabled=True 851 + has_tools=False, 852 + has_output=True, 853 + schedule="daily", 854 + include_disabled=True, 849 855 ).items() 850 856 ) 851 857 ]
-2
apps/todos/tests/test_logging.py
··· 7 7 from datetime import datetime 8 8 from pathlib import Path 9 9 10 - import pytest 11 - 12 10 from apps.todos.tools import todo_add, todo_cancel, todo_done 13 11 14 12
+1 -1
convey/config.py
··· 9 9 from pathlib import Path 10 10 from typing import Any 11 11 12 - from flask import Blueprint, jsonify, request 12 + from flask import Blueprint, request 13 13 14 14 from . import state 15 15 from .utils import error_response, load_json, save_json, success_response
+3 -4
observe/plaud.py
··· 22 22 import re 23 23 import sys 24 24 import tempfile 25 - from datetime import datetime 26 25 from typing import Any, Dict, List, Optional 27 26 28 27 import requests ··· 264 263 # Get temp URL 265 264 temp_url = get_temp_url(session, token, file_id) 266 265 if not temp_url: 267 - print(f" ✗ Failed to get download URL", file=sys.stderr) 266 + print(" ✗ Failed to get download URL", file=sys.stderr) 268 267 failed += 1 269 268 continue 270 269 271 270 # Download file 272 271 if download_to_file(session, temp_url, local_path): 273 272 downloaded += 1 274 - print(f" ✓ Downloaded") 273 + print(" ✓ Downloaded") 275 274 else: 276 275 failed += 1 277 - print(f" ✗ Download failed", file=sys.stderr) 276 + print(" ✗ Download failed", file=sys.stderr) 278 277 279 278 print(f"\n{'SUMMARY':-^70}") 280 279 print(f"✓ Downloaded: {downloaded}")
+1 -1
observe/sync.py
··· 85 85 if response.status_code == 200: 86 86 return (True, f"Connected to {host} (key: {key_prefix})") 87 87 elif response.status_code == 401: 88 - return (False, f"Invalid key (401) - check remote URL") 88 + return (False, "Invalid key (401) - check remote URL") 89 89 elif response.status_code == 403: 90 90 try: 91 91 error = response.json().get("error", "forbidden")
+1 -2
observe/transfer.py
··· 18 18 import logging 19 19 import os 20 20 import platform 21 - import subprocess 22 21 import tarfile 23 22 import time 24 23 from pathlib import Path ··· 427 426 print("Nothing to import - all segments already synced") 428 427 elif result["status"] == "dry_run": 429 428 v = result["validation"] 430 - print(f"Dry run validation:") 429 + print("Dry run validation:") 431 430 print(f" Would skip: {len(v['skip'])} segments") 432 431 print(f" Would import: {len(v['import_as'])} segments") 433 432 if v["deconflicted"]:
+1 -3
pyproject.toml
··· 130 130 profile = "black" 131 131 extend_skip = ["journal", ".venv", "scratch", "logs", "build", "dist"] 132 132 133 - [tool.flake8] 134 - # No line length restrictions 135 - extend-ignore = ["W503", "W504"] 133 + # NOTE: flake8 config lives in .flake8 (flake8 does not read pyproject.toml) 136 134 137 135 [tool.mypy] 138 136 python_version = "3.12"
+2 -2
scripts/migrate_insights_to_agents.py
··· 102 102 return False 103 103 104 104 if dry_run: 105 - print(f" [DRY-RUN] Would update config: 'insights' -> 'agents'") 105 + print(" [DRY-RUN] Would update config: 'insights' -> 'agents'") 106 106 else: 107 107 config["agents"] = config.pop("insights") 108 108 config_file.write_text(json.dumps(config, indent=2) + "\n") 109 - print(f" Updated config: 'insights' -> 'agents'") 109 + print(" Updated config: 'insights' -> 'agents'") 110 110 111 111 return True 112 112
-1
tests/conftest.py
··· 2 2 # Copyright (c) 2026 sol pbc 3 3 4 4 import importlib 5 - import os 6 5 import sys 7 6 import time 8 7 import types
+1 -4
tests/integration/test_app_tool_discovery.py
··· 5 5 6 6 import os 7 7 import sys 8 - from pathlib import Path 9 8 10 9 import pytest 11 10 ··· 117 116 _discover_app_tools() 118 117 119 118 # Should log debug message about missing directory 120 - debug_logs = [ 121 - record.message for record in caplog.records if record.levelname == "DEBUG" 122 - ] 119 + # debug_logs available in caplog.records if needed 123 120 # This will use the real apps dir, so no debug message expected 124 121 # The test mainly verifies no crash occurs 125 122
-1
tests/integration/test_batch.py
··· 3 3 4 4 """Integration tests for Batch with real LLM APIs.""" 5 5 6 - import asyncio 7 6 import os 8 7 import tempfile 9 8 import time
-1
tests/integration/test_callosum.py
··· 9 9 import os 10 10 import threading 11 11 import time 12 - from pathlib import Path 13 12 14 13 import pytest 15 14
+1 -2
tests/integration/test_cortex.py
··· 7 7 import os 8 8 import threading 9 9 import time 10 - from pathlib import Path 11 10 12 11 import pytest 13 12 ··· 202 201 time.sleep(0.1) 203 202 204 203 # Make a request 205 - agent_id = cortex_request( 204 + cortex_request( 206 205 prompt="Test error handling", 207 206 name="nonexistent_agent", # This may cause issues 208 207 provider="openai",
+1 -1
tests/integration/test_google_provider.py
··· 178 178 events = [json.loads(line) for line in stdout_lines if line] 179 179 180 180 # Check for thinking events (may be present with thinking models) 181 - thinking_events = [e for e in events if e.get("event") == "thinking"] 181 + # thinking_events may be present with thinking models (not asserted) 182 182 # With thinking models, we might get thinking events 183 183 184 184 # Verify the answer is correct
+1 -3
tests/integration/test_mcp_server.py
··· 3 3 4 4 """Integration tests for MCP server with full protocol testing.""" 5 5 6 - import asyncio 7 6 import json 8 7 import os 9 - from pathlib import Path 10 8 11 9 import pytest 12 10 from mcp import ClientSession, StdioServerParameters ··· 177 175 assert "1:" in result_data["markdown"], "Expected numbered output" 178 176 179 177 # Test 3: List resources (may be empty if using templates) 180 - resources_result = await session.list_resources() 178 + await session.list_resources() 181 179 # Resources might be empty - the server uses resource templates 182 180 # which are dynamically resolved. This is expected behavior. 183 181
+3 -5
tests/test_app_agents.py
··· 5 5 6 6 import json 7 7 import os 8 - from pathlib import Path 9 8 10 9 import pytest 11 10 ··· 43 42 "priority": 42, 44 43 } 45 44 json_str = json.dumps(metadata, indent=2) 46 - (agents_dir / "myhelper.md").write_text( 45 + (muse_dir / "myhelper.md").write_text( 47 46 f"{{\n{json_str[1:-1]}\n}}\n\nYou are a test helper agent.\n\n## Purpose\nHelp with testing." 48 47 ) 49 48 50 49 # Create another agent without metadata (defaults only) 51 - (agents_dir / "simple.md").write_text("A simple test agent with no metadata.") 50 + (muse_dir / "simple.md").write_text("A simple test agent with no metadata.") 52 51 53 52 # Monkeypatch the parent directory so apps discovery finds our temp apps 54 - original_file = Path(__file__).parent.parent / "think" / "utils.py" 55 53 monkeypatch.setattr( 56 54 "think.utils.Path.__file__", 57 55 str(tmp_path / "think" / "utils.py"), ··· 63 61 yield { 64 62 "tmp_path": tmp_path, 65 63 "app_dir": app_dir, 66 - "agents_dir": agents_dir, 64 + "muse_dir": muse_dir, 67 65 } 68 66 69 67
-3
tests/test_convey_utils.py
··· 1 1 # SPDX-License-Identifier: AGPL-3.0-only 2 2 # Copyright (c) 2026 sol pbc 3 3 4 - import json 5 - import os 6 - 7 4 from convey.utils import format_date, format_date_short, time_since 8 5 from think.utils import day_path 9 6
+3 -2
tests/test_cortex.py
··· 4 4 """Tests for the file-based Cortex agent manager.""" 5 5 6 6 import json 7 - import sys 8 7 from pathlib import Path 9 8 from unittest.mock import MagicMock, patch 10 9 ··· 142 141 @patch("think.cortex.subprocess.Popen") 143 142 @patch("think.cortex.threading.Thread") 144 143 @patch("think.cortex.threading.Timer") 145 - def test_spawn_generator_via_agent(mock_timer, mock_thread, mock_popen, cortex_service, mock_journal): 144 + def test_spawn_generator_via_agent( 145 + mock_timer, mock_thread, mock_popen, cortex_service, mock_journal 146 + ): 146 147 """Test spawning a generator subprocess via _spawn_agent.""" 147 148 mock_process = MagicMock() 148 149 mock_process.pid = 54321
+2 -2
tests/test_describe_config.py
··· 71 71 72 72 # Extract category lines from prompt 73 73 lines = prompt.split("\n") 74 - category_lines = [l for l in lines if l.startswith("- ") and ":" in l] 74 + category_lines = [line for line in lines if line.startswith("- ") and ":" in line] 75 75 76 76 # Extract category names 77 - categories = [l.split(":")[0].replace("- ", "") for l in category_lines] 77 + categories = [line.split(":")[0].replace("- ", "") for line in category_lines] 78 78 79 79 # Should be sorted 80 80 assert categories == sorted(categories)
-1
tests/test_entities.py
··· 31 31 rename_entity_memory, 32 32 resolve_entity, 33 33 save_entities, 34 - save_journal_entity, 35 34 save_observations, 36 35 touch_entities_from_activity, 37 36 touch_entity,
-1
tests/test_formatters.py
··· 3 3 4 4 """Tests for the formatters framework.""" 5 5 6 - import json 7 6 import os 8 7 import tempfile 9 8 from pathlib import Path
+10 -5
tests/test_generators.py
··· 3 3 4 4 import importlib 5 5 import os 6 - import tempfile 7 - from pathlib import Path 8 6 9 7 10 8 def test_get_muse_configs_generators(): ··· 69 67 assert meta.get("schedule") == "daily", f"{key} should have schedule=daily" 70 68 71 69 # Get segment generators 72 - segment = utils.get_muse_configs(has_tools=False, has_output=True, schedule="segment") 70 + segment = utils.get_muse_configs( 71 + has_tools=False, has_output=True, schedule="segment" 72 + ) 73 73 assert len(segment) > 0 74 74 for key, meta in segment.items(): 75 75 assert meta.get("schedule") == "segment", f"{key} should have schedule=segment" ··· 80 80 ), "daily and segment should not overlap" 81 81 82 82 # Unknown schedule returns empty dict 83 - assert utils.get_muse_configs(has_tools=False, has_output=True, schedule="hourly") == {} 83 + assert ( 84 + utils.get_muse_configs(has_tools=False, has_output=True, schedule="hourly") 85 + == {} 86 + ) 84 87 assert utils.get_muse_configs(has_tools=False, has_output=True, schedule="") == {} 85 88 86 89 ··· 89 92 utils = importlib.import_module("think.utils") 90 93 91 94 # Get generators without disabled (default) 92 - without_disabled = utils.get_muse_configs(has_tools=False, has_output=True, schedule="daily") 95 + without_disabled = utils.get_muse_configs( 96 + has_tools=False, has_output=True, schedule="daily" 97 + ) 93 98 94 99 # Get generators with disabled included 95 100 with_disabled = utils.get_muse_configs(
-1
tests/test_screen_formatter.py
··· 9 9 CATEGORIES, 10 10 _load_category_formatter, 11 11 format_screen, 12 - format_screen_text, 13 12 ) 14 13 15 14
-3
tests/test_sense.py
··· 3 3 4 4 """Tests for observe.sense module.""" 5 5 6 - import subprocess 7 6 import tempfile 8 7 import threading 9 8 import time 10 9 from pathlib import Path 11 10 from unittest.mock import MagicMock, patch 12 - 13 - import pytest 14 11 15 12 from observe.sense import FileSensor, HandlerProcess, HandlerQueue, QueuedItem 16 13 from think.runner import DailyLogWriter as ProcessLogWriter
-1
tests/test_supervisor.py
··· 4 4 import importlib 5 5 import io 6 6 import logging 7 - import os 8 7 import subprocess 9 8 import time 10 9
+1 -1
tests/test_transfer.py
··· 319 319 think.utils._journal_path_cache = None 320 320 321 321 # Mock subprocess to avoid running real indexer 322 - with patch("subprocess.run") as mock_run: 322 + with patch("subprocess.run"): 323 323 result = import_archive(archive_path) 324 324 325 325 assert result["status"] == "imported"
-2
tests/test_vad.py
··· 11 11 from observe.utils import SAMPLE_RATE 12 12 from observe.vad import ( 13 13 GAP_BUFFER, 14 - MIN_GAP_TO_REDUCE, 15 - MIN_NONSPEECH_SEGMENT, 16 14 AudioReduction, 17 15 SpeechSegment, 18 16 VadResult,
-1
think/events.py
··· 10 10 11 11 import json 12 12 import logging 13 - import os 14 13 import re 15 14 from datetime import datetime 16 15 from pathlib import Path
-1
think/formatters.py
··· 37 37 import fnmatch 38 38 import json 39 39 import os 40 - import re 41 40 import sys 42 41 from importlib import import_module 43 42 from pathlib import Path
+1 -1
think/resources/transcripts.py
··· 32 32 return TextResource( 33 33 uri=f"journal://transcripts/{mode}/{day}/{time}/{length}", 34 34 name=f"Transcripts Error ({mode}): {day} {time} ({length}min)", 35 - description=f"Error: Requested length exceeds maximum", 35 + description="Error: Requested length exceeds maximum", 36 36 mime_type="text/markdown", 37 37 text=error_content, 38 38 )