diff --git a/AGENTS.md b/AGENTS.md index 6b0a704d..5888ea4f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,8 +66,8 @@ make docker-agent # add a worker |---------|-----------| | New route | `src/dashboard/routes/.py` + register in `app.py` | | New template | `src/dashboard/templates/.html` extends `base.html` | -| New subsystem | `src//` with `__init__.py` | -| New test | `tests/test_.py` | +| New subsystem | Add to existing `src//` — see module map in CLAUDE.md | +| New test | `tests//test_.py` (mirror source structure) | | Secrets | Via `config.settings` + startup warning if default | | DB files | Project root or `data/` — never in `src/` | diff --git a/CLAUDE.md b/CLAUDE.md index a8ccf62a..d994e8c9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,8 +20,8 @@ url = settings.ollama_url # never use os.environ.get() directly in app code ```python from dashboard.store import message_log -from notifications.push import notifier -from ws_manager.handler import ws_manager +from infrastructure.notifications.push import notifier +from infrastructure.ws_manager.handler import ws_manager from swarm.coordinator import coordinator ``` @@ -90,5 +90,26 @@ make test-cov # With coverage (term-missing + XML) |---------|--------|---------| | `timmy` | `src/timmy/cli.py` | Chat, think, status | | `timmy-serve` | `src/timmy_serve/cli.py` | L402-gated API server (port 8402) | -| `self-tdd` | `src/self_tdd/watchdog.py` | Continuous test watchdog | -| `self-modify` | `src/self_modify/cli.py` | Self-modification CLI | +| `self-tdd` | `src/self_coding/self_tdd/watchdog.py` | Continuous test watchdog | +| `self-modify` | `src/self_coding/self_modify/cli.py` | Self-modification CLI | + +--- + +## Module Map (14 packages) + +| Package | Purpose | +|---------|---------| +| `timmy/` | Core agent, personas, agent interface, semantic memory | +| `dashboard/` | FastAPI web UI, routes, templates | +| `swarm/` | Multi-agent coordinator, task queue, work orders | +| `self_coding/` | Self-modification, test watchdog, upgrade queue | +| `creative/` | Media generation, MCP tools | +| `infrastructure/` | WebSocket, notifications, events, LLM router | +| `integrations/` | Discord, Telegram, Siri Shortcuts, voice NLU | +| `lightning/` | L402 payment gating (security-sensitive) | +| `mcp/` | MCP tool registry and discovery | +| `spark/` | Event capture and advisory engine | +| `hands/` | 6 autonomous Hand agents | +| `scripture/` | Biblical text integration | +| `timmy_serve/` | L402-gated API server | +| `config.py` | Pydantic settings (foundation for all modules) | diff --git a/REFACTORING_PLAN.md b/REFACTORING_PLAN.md index a7f9f965..3fc5914a 100644 --- a/REFACTORING_PLAN.md +++ b/REFACTORING_PLAN.md @@ -155,69 +155,26 @@ session-scoped context. Either gitignore it or move to `docs/handoff/`. **Goal:** Reduce 28 modules to ~12 by merging small, related modules into coherent packages. This directly reduces cognitive load and token consumption. -### 2.1 Proposed module structure +### 2.1 Module structure (implemented) ``` -src/ - config.py # (keep as-is) +src/ # 14 packages (was 28) + config.py # Pydantic settings (foundation) - timmy/ # Core agent — MERGE IN agents/, agent_core/, memory/ - agent.py # Main Timmy agent - backends.py # Ollama/AirLLM backends - cli.py # CLI entry point - orchestrator.py # ← from agents/timmy.py - personas/ # ← from agents/ (seer, helm, quill, echo, forge) - agent_core/ # ← from src/agent_core/ (becomes subpackage) - memory/ # ← from src/memory/ (becomes subpackage) - prompts.py - ... + timmy/ # Core agent + agents/ + agent_core/ + memory/ + dashboard/ # FastAPI web UI (22 route files) + swarm/ # Coordinator + task_queue/ + work_orders/ + self_coding/ # Git safety + self_modify/ + self_tdd/ + upgrades/ + creative/ # Media generation + tools/ + infrastructure/ # ws_manager/ + notifications/ + events/ + router/ + integrations/ # chat_bridge/ + telegram_bot/ + shortcuts/ + voice/ - dashboard/ # Web UI — CONSOLIDATE routes - app.py - store.py - routes/ # See §2.2 for route consolidation - templates/ - - swarm/ # Multi-agent system — MERGE IN task_queue/, work_orders/ - coordinator.py - tasks.py # ← existing + task_queue/ models - work_orders/ # ← from src/work_orders/ (becomes subpackage) - ... - - integrations/ # NEW — MERGE chat_bridge/, telegram_bot/, shortcuts/ - chat_bridge/ # Discord, unified chat - telegram.py # ← from telegram_bot/ - shortcuts.py # ← from shortcuts/ - voice/ # ← from src/voice/ - - lightning/ # (keep as-is — standalone, security-sensitive) - - self_coding/ # MERGE IN self_modify/, self_tdd/, upgrades/ - codebase_indexer.py - git_safety.py - modification_journal.py - self_modify/ # ← from src/self_modify/ (becomes subpackage) - watchdog.py # ← from src/self_tdd/ - upgrades/ # ← from src/upgrades/ - - mcp/ # (keep as-is — used across multiple modules) - - spark/ # (keep as-is) - - creative/ # MERGE IN tools/ - director.py - assembler.py - tools/ # ← from src/tools/ (becomes subpackage) - - hands/ # (keep as-is) - - scripture/ # (keep as-is — domain-specific) - - infrastructure/ # NEW — MERGE ws_manager/, notifications/, events/, router/ - ws_manager.py # ← from ws_manager/handler.py (157 lines) - notifications.py # ← from notifications/push.py (153 lines) - events.py # ← from events/ (354 lines) - router/ # ← from src/router/ (cascade LLM router) + lightning/ # L402 payment gating (standalone, security-sensitive) + mcp/ # MCP tool registry and discovery + spark/ # Event capture and advisory + hands/ # 6 autonomous Hand agents + scripture/ # Biblical text integration + timmy_serve/ # L402-gated API server ``` ### 2.2 Dashboard route consolidation @@ -476,18 +433,17 @@ patterns (card layouts, form groups, table rows). ## Success Metrics -After refactoring: -- Root `.md` files: 10 → 3 -- Root markdown size: 87KB → ~20KB -- `src/` modules: 28 → ~12-15 -- Dashboard route files: 27 → ~12-15 -- Test files: organized in subdirectories matching source -- Empty skeleton test files: 61 → 0 (either implemented or deleted) -- Real test functions: 471 → 500+ (fill gaps in coverage) -- `pytest -m unit` runs in <10 seconds -- Wheel build includes all modules that are actually imported -- AI assistant context consumption drops ~40% -- Conftest autouse fixtures scoped to relevant test directories +| Metric | Original | Target | Current | +|--------|----------|--------|---------| +| Root `.md` files | 10 | 3 | 5 | +| Root markdown size | 87KB | ~20KB | ~28KB | +| `src/` modules | 28 | ~12-15 | **14** | +| Dashboard routes | 27 | ~12-15 | 22 | +| Test organization | flat | mirrored | **mirrored** | +| Tests passing | 471 | 500+ | **1462** | +| Wheel modules | 17/28 | all | **all** | +| Module-level docs | 0 | all key modules | **6** | +| AI context reduction | — | ~40% | **~50%** (fewer modules to scan) | --- @@ -505,15 +461,21 @@ After refactoring: - [x] **Phase 2a: Route consolidation** — 27 → 22 route files (merged voice, swarm internal/ws, self-modify; deleted mobile_test) +- [x] **Phase 2b: Full module consolidation** — 28 → 14 modules. All merges + completed in a single pass with automated import rewriting (66 source files + + 13 test files updated). Modules consolidated: + - `work_orders/` + `task_queue/` → `swarm/` + - `self_modify/` + `self_tdd/` + `upgrades/` → `self_coding/` + - `tools/` → `creative/tools/` + - `chat_bridge/` + `telegram_bot/` + `shortcuts/` + `voice/` → `integrations/` (new) + - `ws_manager/` + `notifications/` + `events/` + `router/` → `infrastructure/` (new) + - `agents/` + `agent_core/` + `memory/` → `timmy/` + - pyproject.toml entry points and wheel includes updated + - Module-level CLAUDE.md files added (Phase 6.2) + - Zero test regressions: 1462 tests passing +- [x] **Phase 6.2: Module-level CLAUDE.md** — added to swarm/, self_coding/, + infrastructure/, integrations/, creative/, lightning/ + ### Remaining -- [ ] **Phase 2b: Full module consolidation** (28 → ~12 modules) — requires - updating hundreds of import statements. Should be done incrementally across - focused PRs, one module merge at a time. Candidates by import footprint: - - `work_orders/` → `swarm/work_orders/` (1 importer) - - `upgrades/` → `self_coding/upgrades/` (1 importer) - - `shortcuts/` → `integrations/shortcuts/` (1 importer) - - `events/` → `swarm/events/` (4 importers) - - `task_queue/` → `swarm/task_queue/` (3 importers) - - Larger merges: agents/ + agent_core/ + memory/ → timmy/ (many importers) - [ ] **Phase 5: Package extraction** — only if team grows or dep profiles diverge diff --git a/pyproject.toml b/pyproject.toml index f8810629..3e36c019 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,41 +75,26 @@ creative = [ [project.scripts] timmy = "timmy.cli:main" timmy-serve = "timmy_serve.cli:main" -self-tdd = "self_tdd.watchdog:main" -self-modify = "self_modify.cli:main" +self-tdd = "self_coding.self_tdd.watchdog:main" +self-modify = "self_coding.self_modify.cli:main" [tool.hatch.build.targets.wheel] sources = {"src" = ""} include = [ "src/config.py", - "src/agent_core", - "src/agents", - "src/chat_bridge", "src/creative", "src/dashboard", - "src/events", "src/hands", + "src/infrastructure", + "src/integrations", "src/lightning", "src/mcp", - "src/memory", - "src/notifications", - "src/router", "src/scripture", "src/self_coding", - "src/self_modify", - "src/self_tdd", - "src/shortcuts", "src/spark", "src/swarm", - "src/task_queue", - "src/telegram_bot", "src/timmy", "src/timmy_serve", - "src/tools", - "src/upgrades", - "src/voice", - "src/work_orders", - "src/ws_manager", ] [tool.pytest.ini_options] diff --git a/src/agents/__init__.py b/src/agents/__init__.py deleted file mode 100644 index 03a76c4b..00000000 --- a/src/agents/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Agents package — Timmy and sub-agents. -""" - -from agents.timmy import TimmyOrchestrator, create_timmy_swarm -from agents.base import BaseAgent -from agents.seer import SeerAgent -from agents.forge import ForgeAgent -from agents.quill import QuillAgent -from agents.echo import EchoAgent -from agents.helm import HelmAgent - -__all__ = [ - "BaseAgent", - "TimmyOrchestrator", - "create_timmy_swarm", - "SeerAgent", - "ForgeAgent", - "QuillAgent", - "EchoAgent", - "HelmAgent", -] diff --git a/src/chat_bridge/__init__.py b/src/chat_bridge/__init__.py deleted file mode 100644 index 7aa82bd6..00000000 --- a/src/chat_bridge/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Chat Bridge — vendor-agnostic chat platform abstraction. - -Provides a clean interface for integrating any chat platform -(Discord, Telegram, Slack, etc.) with Timmy's agent core. - -Usage: - from chat_bridge.base import ChatPlatform - from chat_bridge.registry import platform_registry - from chat_bridge.vendors.discord import DiscordVendor -""" diff --git a/src/creative/CLAUDE.md b/src/creative/CLAUDE.md new file mode 100644 index 00000000..cd25f394 --- /dev/null +++ b/src/creative/CLAUDE.md @@ -0,0 +1,18 @@ +# creative/ — Module Guide + +GPU-accelerated media generation. Heavy dependencies (PyTorch, diffusers). + +## Structure +- `director.py` — Orchestrates multi-step creative pipelines +- `assembler.py` — Video assembly and stitching +- `tools/` — MCP-compliant tool implementations + - `image_tools.py` — FLUX.2 image generation + - `music_tools.py` — ACE-Step music generation + - `video_tools.py` — Wan 2.1 video generation + - `git_tools.py`, `file_ops.py`, `code_exec.py` — Utility tools + - `self_edit.py` — Self-modification MCP tool (protected file) + +## Testing +```bash +pytest tests/creative/ -q +``` diff --git a/src/creative/director.py b/src/creative/director.py index 968fff9b..914297b7 100644 --- a/src/creative/director.py +++ b/src/creative/director.py @@ -132,7 +132,7 @@ def run_storyboard(project_id: str) -> dict: project.status = "storyboard" - from tools.image_tools import generate_storyboard + from creative.tools.image_tools import generate_storyboard scene_descriptions = [s["description"] for s in project.scenes] result = generate_storyboard(scene_descriptions) @@ -159,7 +159,7 @@ def run_music( project.status = "music" - from tools.music_tools import generate_song + from creative.tools.music_tools import generate_song # Default duration: ~15s per scene, minimum 60s target_duration = duration or max(60, len(project.scenes) * 15) @@ -192,7 +192,7 @@ def run_video_generation(project_id: str) -> dict: project.status = "video" - from tools.video_tools import generate_video_clip, image_to_video + from creative.tools.video_tools import generate_video_clip, image_to_video clips = [] for i, scene in enumerate(project.scenes): diff --git a/src/tools/__init__.py b/src/creative/tools/__init__.py similarity index 100% rename from src/tools/__init__.py rename to src/creative/tools/__init__.py diff --git a/src/tools/code_exec.py b/src/creative/tools/code_exec.py similarity index 100% rename from src/tools/code_exec.py rename to src/creative/tools/code_exec.py diff --git a/src/tools/file_ops.py b/src/creative/tools/file_ops.py similarity index 100% rename from src/tools/file_ops.py rename to src/creative/tools/file_ops.py diff --git a/src/tools/git_tools.py b/src/creative/tools/git_tools.py similarity index 100% rename from src/tools/git_tools.py rename to src/creative/tools/git_tools.py diff --git a/src/tools/image_tools.py b/src/creative/tools/image_tools.py similarity index 100% rename from src/tools/image_tools.py rename to src/creative/tools/image_tools.py diff --git a/src/tools/memory_tool.py b/src/creative/tools/memory_tool.py similarity index 100% rename from src/tools/memory_tool.py rename to src/creative/tools/memory_tool.py diff --git a/src/tools/music_tools.py b/src/creative/tools/music_tools.py similarity index 100% rename from src/tools/music_tools.py rename to src/creative/tools/music_tools.py diff --git a/src/tools/self_edit.py b/src/creative/tools/self_edit.py similarity index 99% rename from src/tools/self_edit.py rename to src/creative/tools/self_edit.py index a28f314a..3553b92c 100644 --- a/src/tools/self_edit.py +++ b/src/creative/tools/self_edit.py @@ -13,7 +13,7 @@ This is the core self-modification orchestrator that: 10. Generates reflections Usage: - from tools.self_edit import self_edit_tool + from creative.tools.self_edit import self_edit_tool from mcp.registry import tool_registry # Register with MCP @@ -818,7 +818,7 @@ def register_self_edit_tool(registry: Any, llm_adapter: Optional[object] = None) category="self_coding", requires_confirmation=True, # Safety: require user approval tags=["self-modification", "code-generation"], - source_module="tools.self_edit", + source_module="creative.tools.self_edit", ) logger.info("Self-edit tool registered with MCP") diff --git a/src/tools/video_tools.py b/src/creative/tools/video_tools.py similarity index 100% rename from src/tools/video_tools.py rename to src/creative/tools/video_tools.py diff --git a/src/tools/web_search.py b/src/creative/tools/web_search.py similarity index 100% rename from src/tools/web_search.py rename to src/creative/tools/web_search.py diff --git a/src/dashboard/app.py b/src/dashboard/app.py index 3ea65570..17058f9f 100644 --- a/src/dashboard/app.py +++ b/src/dashboard/app.py @@ -34,7 +34,7 @@ from dashboard.routes.scripture import router as scripture_router from dashboard.routes.self_coding import router as self_coding_router from dashboard.routes.self_coding import self_modify_router from dashboard.routes.hands import router as hands_router -from router.api import router as cascade_router +from infrastructure.router.api import router as cascade_router logging.basicConfig( level=logging.INFO, @@ -57,7 +57,7 @@ async def _briefing_scheduler() -> None: exists (< 30 min old). """ from timmy.briefing import engine as briefing_engine - from notifications.push import notify_briefing_ready + from infrastructure.notifications.push import notify_briefing_ready await asyncio.sleep(2) # Let server finish starting before first run @@ -135,9 +135,9 @@ async def lifespan(app: FastAPI): logger.info("Spark Intelligence active — event capture enabled") # Auto-start chat integrations (skip silently if unconfigured) - from telegram_bot.bot import telegram_bot - from chat_bridge.vendors.discord import discord_bot - from chat_bridge.registry import platform_registry + from integrations.telegram_bot.bot import telegram_bot + from integrations.chat_bridge.vendors.discord import discord_bot + from integrations.chat_bridge.registry import platform_registry platform_registry.register(discord_bot) if settings.telegram_token: @@ -208,5 +208,5 @@ async def index(request: Request): @app.get("/shortcuts/setup") async def shortcuts_setup(): """Siri Shortcuts setup guide.""" - from shortcuts.siri import get_setup_guide + from integrations.shortcuts.siri import get_setup_guide return get_setup_guide() diff --git a/src/dashboard/routes/agents.py b/src/dashboard/routes/agents.py index c25028e6..e8526752 100644 --- a/src/dashboard/routes/agents.py +++ b/src/dashboard/routes/agents.py @@ -125,7 +125,7 @@ def _extract_task_from_message(message: str) -> dict | None: def _build_queue_context() -> str: """Build a concise task queue summary for context injection.""" try: - from task_queue.models import get_counts_by_status, list_tasks, TaskStatus + from swarm.task_queue.models import get_counts_by_status, list_tasks, TaskStatus counts = get_counts_by_status() pending = counts.get("pending_approval", 0) running = counts.get("running", 0) @@ -215,7 +215,7 @@ async def chat_timmy(request: Request, message: str = Form(...)): task_info = _extract_task_from_message(message) if task_info: try: - from task_queue.models import create_task + from swarm.task_queue.models import create_task task = create_task( title=task_info["title"], description=task_info["description"], diff --git a/src/dashboard/routes/creative.py b/src/dashboard/routes/creative.py index f24b5ff1..c18b4453 100644 --- a/src/dashboard/routes/creative.py +++ b/src/dashboard/routes/creative.py @@ -68,7 +68,7 @@ async def creative_projects_api(): async def creative_genres_api(): """Return supported music genres.""" try: - from tools.music_tools import GENRES + from creative.tools.music_tools import GENRES return {"genres": GENRES} except ImportError: return {"genres": []} @@ -78,7 +78,7 @@ async def creative_genres_api(): async def creative_video_styles_api(): """Return supported video styles and resolutions.""" try: - from tools.video_tools import VIDEO_STYLES, RESOLUTION_PRESETS + from creative.tools.video_tools import VIDEO_STYLES, RESOLUTION_PRESETS return { "styles": VIDEO_STYLES, "resolutions": list(RESOLUTION_PRESETS.keys()), diff --git a/src/dashboard/routes/discord.py b/src/dashboard/routes/discord.py index 28629a5d..c8e54a08 100644 --- a/src/dashboard/routes/discord.py +++ b/src/dashboard/routes/discord.py @@ -25,7 +25,7 @@ async def setup_discord(payload: TokenPayload): Send POST with JSON body: {"token": ""} Get the token from https://discord.com/developers/applications """ - from chat_bridge.vendors.discord import discord_bot + from integrations.chat_bridge.vendors.discord import discord_bot token = payload.token.strip() if not token: @@ -51,7 +51,7 @@ async def setup_discord(payload: TokenPayload): @router.get("/status") async def discord_status(): """Return current Discord bot status.""" - from chat_bridge.vendors.discord import discord_bot + from integrations.chat_bridge.vendors.discord import discord_bot return discord_bot.status().to_dict() @@ -70,8 +70,8 @@ async def join_from_image( The bot validates the invite and returns the OAuth2 URL for the server admin to authorize the bot. """ - from chat_bridge.invite_parser import invite_parser - from chat_bridge.vendors.discord import discord_bot + from integrations.chat_bridge.invite_parser import invite_parser + from integrations.chat_bridge.vendors.discord import discord_bot invite_info = None @@ -129,7 +129,7 @@ async def join_from_image( @router.get("/oauth-url") async def discord_oauth_url(): """Get the bot's OAuth2 authorization URL for adding to servers.""" - from chat_bridge.vendors.discord import discord_bot + from integrations.chat_bridge.vendors.discord import discord_bot url = discord_bot.get_oauth2_url() if url: diff --git a/src/dashboard/routes/memory.py b/src/dashboard/routes/memory.py index 678720a1..067b970b 100644 --- a/src/dashboard/routes/memory.py +++ b/src/dashboard/routes/memory.py @@ -7,7 +7,7 @@ from fastapi import APIRouter, Form, HTTPException, Request from fastapi.responses import HTMLResponse, JSONResponse from fastapi.templating import Jinja2Templates -from memory.vector_store import ( +from timmy.memory.vector_store import ( store_memory, search_memories, get_memory_stats, diff --git a/src/dashboard/routes/self_coding.py b/src/dashboard/routes/self_coding.py index 53f9b8af..57d7d405 100644 --- a/src/dashboard/routes/self_coding.py +++ b/src/dashboard/routes/self_coding.py @@ -209,7 +209,7 @@ async def api_execute(request: ExecuteRequest): This is the API endpoint for manual task execution. In production, this should require authentication and confirmation. """ - from tools.self_edit import SelfEditTool + from creative.tools.self_edit import SelfEditTool tool = SelfEditTool() result = await tool.execute(request.task_description) @@ -332,7 +332,7 @@ async def execute_task( ): """HTMX endpoint to execute a task.""" from dashboard.app import templates - from tools.self_edit import SelfEditTool + from creative.tools.self_edit import SelfEditTool tool = SelfEditTool() result = await tool.execute(task_description) @@ -388,7 +388,7 @@ async def run_self_modify( if not settings.self_modify_enabled: raise HTTPException(403, "Self-modification is disabled") - from self_modify.loop import SelfModifyLoop, ModifyRequest + from self_coding.self_modify.loop import SelfModifyLoop, ModifyRequest files = [f.strip() for f in target_files.split(",") if f.strip()] request = ModifyRequest( diff --git a/src/dashboard/routes/swarm.py b/src/dashboard/routes/swarm.py index f9aec8ab..cfb90970 100644 --- a/src/dashboard/routes/swarm.py +++ b/src/dashboard/routes/swarm.py @@ -20,7 +20,7 @@ from swarm import learner as swarm_learner from swarm import registry from swarm.coordinator import coordinator from swarm.tasks import TaskStatus, update_task -from ws_manager.handler import ws_manager +from infrastructure.ws_manager.handler import ws_manager logger = logging.getLogger(__name__) diff --git a/src/dashboard/routes/tasks.py b/src/dashboard/routes/tasks.py index 11048c14..0c2e892d 100644 --- a/src/dashboard/routes/tasks.py +++ b/src/dashboard/routes/tasks.py @@ -24,7 +24,7 @@ from fastapi import APIRouter, Form, HTTPException, Request from fastapi.responses import HTMLResponse, JSONResponse from fastapi.templating import Jinja2Templates -from task_queue.models import ( +from swarm.task_queue.models import ( QueueTask, TaskPriority, TaskStatus, @@ -49,7 +49,7 @@ def _broadcast_task_event(event_type: str, task: QueueTask): """Best-effort broadcast a task event to connected WebSocket clients.""" try: import asyncio - from ws_manager.handler import ws_manager + from infrastructure.ws_manager.handler import ws_manager payload = { "type": "task_event", @@ -461,7 +461,7 @@ def _task_to_dict(task: QueueTask) -> dict: def _notify_task_created(task: QueueTask): try: - from notifications.push import notifier + from infrastructure.notifications.push import notifier notifier.notify( title="New Task", message=f"{task.created_by} created: {task.title}", diff --git a/src/dashboard/routes/telegram.py b/src/dashboard/routes/telegram.py index 00b93ca4..303d5cf0 100644 --- a/src/dashboard/routes/telegram.py +++ b/src/dashboard/routes/telegram.py @@ -17,7 +17,7 @@ async def setup_telegram(payload: TokenPayload): Send a POST with JSON body: {"token": ""} Get the token from @BotFather on Telegram. """ - from telegram_bot.bot import telegram_bot + from integrations.telegram_bot.bot import telegram_bot token = payload.token.strip() if not token: @@ -43,7 +43,7 @@ async def setup_telegram(payload: TokenPayload): @router.get("/status") async def telegram_status(): """Return the current state of the Telegram bot.""" - from telegram_bot.bot import telegram_bot + from integrations.telegram_bot.bot import telegram_bot return { "running": telegram_bot.is_running, diff --git a/src/dashboard/routes/upgrades.py b/src/dashboard/routes/upgrades.py index e4bc88b2..4065c17e 100644 --- a/src/dashboard/routes/upgrades.py +++ b/src/dashboard/routes/upgrades.py @@ -6,8 +6,8 @@ from fastapi import APIRouter, Form, HTTPException, Request from fastapi.responses import HTMLResponse, JSONResponse from fastapi.templating import Jinja2Templates -from upgrades.models import list_upgrades, get_upgrade, UpgradeStatus, get_pending_count -from upgrades.queue import UpgradeQueue +from self_coding.upgrades.models import list_upgrades, get_upgrade, UpgradeStatus, get_pending_count +from self_coding.upgrades.queue import UpgradeQueue router = APIRouter(prefix="/self-modify", tags=["upgrades"]) templates = Jinja2Templates(directory=str(Path(__file__).parent.parent / "templates")) diff --git a/src/dashboard/routes/voice.py b/src/dashboard/routes/voice.py index a5db56d1..d7ee407b 100644 --- a/src/dashboard/routes/voice.py +++ b/src/dashboard/routes/voice.py @@ -8,7 +8,7 @@ import logging from fastapi import APIRouter, Form -from voice.nlu import detect_intent, extract_command +from integrations.voice.nlu import detect_intent, extract_command from timmy.agent import create_timmy logger = logging.getLogger(__name__) @@ -104,7 +104,7 @@ async def process_voice_input( ) else: import asyncio - from self_modify.loop import SelfModifyLoop, ModifyRequest + from self_coding.self_modify.loop import SelfModifyLoop, ModifyRequest target_files = [] if "target_file" in intent.entities: diff --git a/src/dashboard/routes/work_orders.py b/src/dashboard/routes/work_orders.py index 80b5a6b9..860cab1e 100644 --- a/src/dashboard/routes/work_orders.py +++ b/src/dashboard/routes/work_orders.py @@ -8,7 +8,7 @@ from fastapi import APIRouter, Form, HTTPException, Request from fastapi.responses import HTMLResponse, JSONResponse from fastapi.templating import Jinja2Templates -from work_orders.models import ( +from swarm.work_orders.models import ( WorkOrder, WorkOrderCategory, WorkOrderPriority, @@ -20,7 +20,7 @@ from work_orders.models import ( list_work_orders, update_work_order_status, ) -from work_orders.risk import compute_risk_score, should_auto_execute +from swarm.work_orders.risk import compute_risk_score, should_auto_execute logger = logging.getLogger(__name__) @@ -68,7 +68,7 @@ async def submit_work_order( # Notify try: - from notifications.push import notifier + from infrastructure.notifications.push import notifier notifier.notify( title="New Work Order", message=f"{wo.submitter} submitted: {wo.title}", @@ -116,7 +116,7 @@ async def submit_work_order_json(request: Request): ) try: - from notifications.push import notifier + from infrastructure.notifications.push import notifier notifier.notify( title="New Work Order", message=f"{wo.submitter} submitted: {wo.title}", @@ -315,7 +315,7 @@ async def execute_order(wo_id: str): update_work_order_status(wo_id, WorkOrderStatus.IN_PROGRESS) try: - from work_orders.executor import work_order_executor + from swarm.work_orders.executor import work_order_executor success, result = work_order_executor.execute(wo) if success: update_work_order_status(wo_id, WorkOrderStatus.COMPLETED, result=result) diff --git a/src/data/self_modify_reports/20260226_215611_add_docstring.md b/src/data/self_modify_reports/20260226_215611_add_docstring.md new file mode 100644 index 00000000..804143d0 --- /dev/null +++ b/src/data/self_modify_reports/20260226_215611_add_docstring.md @@ -0,0 +1,19 @@ +# Self-Modify Report: 20260226_215611 + +**Instruction:** Add docstring +**Target files:** src/foo.py +**Dry run:** True +**Backend:** ollama +**Branch:** N/A +**Result:** SUCCESS +**Error:** none +**Commit:** none +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- dry_run + +### LLM Response +``` +llm raw +``` diff --git a/src/data/self_modify_reports/20260226_215611_break_it.md b/src/data/self_modify_reports/20260226_215611_break_it.md new file mode 100644 index 00000000..1c2b9363 --- /dev/null +++ b/src/data/self_modify_reports/20260226_215611_break_it.md @@ -0,0 +1,31 @@ +# Self-Modify Report: 20260226_215611 + +**Instruction:** Break it +**Target files:** src/foo.py +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** FAILED +**Error:** Tests failed after 1 attempt(s). +**Commit:** none +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- complete + +### LLM Response +``` +llm raw +``` + +### Edits Written +#### src/foo.py +```python +x = 1 + +``` + +### Test Result: FAILED +``` +1 failed +``` diff --git a/src/data/self_modify_reports/20260226_215611_do_something_vague.md b/src/data/self_modify_reports/20260226_215611_do_something_vague.md new file mode 100644 index 00000000..5dd424df --- /dev/null +++ b/src/data/self_modify_reports/20260226_215611_do_something_vague.md @@ -0,0 +1,12 @@ +# Self-Modify Report: 20260226_215611 + +**Instruction:** do something vague +**Target files:** (auto-detected) +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** FAILED +**Error:** No target files identified. Specify target_files or use more specific language. +**Commit:** none +**Attempts:** 0 +**Autonomous cycles:** 0 diff --git a/src/data/self_modify_reports/20260226_215611_fix_foo.md b/src/data/self_modify_reports/20260226_215611_fix_foo.md new file mode 100644 index 00000000..a4da53d7 --- /dev/null +++ b/src/data/self_modify_reports/20260226_215611_fix_foo.md @@ -0,0 +1,31 @@ +# Self-Modify Report: 20260226_215611 + +**Instruction:** Fix foo +**Target files:** src/foo.py +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** FAILED +**Error:** Tests failed after 1 attempt(s). +**Commit:** none +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- complete + +### LLM Response +``` +llm raw +``` + +### Edits Written +#### src/foo.py +```python +x = 2 + +``` + +### Test Result: FAILED +``` +FAILED +``` diff --git a/src/data/self_modify_reports/20260226_215611_fix_foo_important_correction_from_previ.md b/src/data/self_modify_reports/20260226_215611_fix_foo_important_correction_from_previ.md new file mode 100644 index 00000000..bfa02c28 --- /dev/null +++ b/src/data/self_modify_reports/20260226_215611_fix_foo_important_correction_from_previ.md @@ -0,0 +1,34 @@ +# Self-Modify Report: 20260226_215611 + +**Instruction:** Fix foo + +IMPORTANT CORRECTION from previous failure: +Fix: do X instead of Y +**Target files:** src/foo.py +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** SUCCESS +**Error:** none +**Commit:** abc123 +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- complete + +### LLM Response +``` +llm raw +``` + +### Edits Written +#### src/foo.py +```python +x = 2 + +``` + +### Test Result: PASSED +``` +PASSED +``` diff --git a/src/data/self_modify_reports/20260226_220014_add_docstring.md b/src/data/self_modify_reports/20260226_220014_add_docstring.md new file mode 100644 index 00000000..c6f9b1fd --- /dev/null +++ b/src/data/self_modify_reports/20260226_220014_add_docstring.md @@ -0,0 +1,19 @@ +# Self-Modify Report: 20260226_220014 + +**Instruction:** Add docstring +**Target files:** src/foo.py +**Dry run:** True +**Backend:** ollama +**Branch:** N/A +**Result:** SUCCESS +**Error:** none +**Commit:** none +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- dry_run + +### LLM Response +``` +llm raw +``` diff --git a/src/data/self_modify_reports/20260226_220014_break_it.md b/src/data/self_modify_reports/20260226_220014_break_it.md new file mode 100644 index 00000000..1c01b312 --- /dev/null +++ b/src/data/self_modify_reports/20260226_220014_break_it.md @@ -0,0 +1,31 @@ +# Self-Modify Report: 20260226_220014 + +**Instruction:** Break it +**Target files:** src/foo.py +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** FAILED +**Error:** Tests failed after 1 attempt(s). +**Commit:** none +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- complete + +### LLM Response +``` +llm raw +``` + +### Edits Written +#### src/foo.py +```python +x = 1 + +``` + +### Test Result: FAILED +``` +1 failed +``` diff --git a/src/data/self_modify_reports/20260226_220014_do_something_vague.md b/src/data/self_modify_reports/20260226_220014_do_something_vague.md new file mode 100644 index 00000000..55c6a35f --- /dev/null +++ b/src/data/self_modify_reports/20260226_220014_do_something_vague.md @@ -0,0 +1,12 @@ +# Self-Modify Report: 20260226_220014 + +**Instruction:** do something vague +**Target files:** (auto-detected) +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** FAILED +**Error:** No target files identified. Specify target_files or use more specific language. +**Commit:** none +**Attempts:** 0 +**Autonomous cycles:** 0 diff --git a/src/data/self_modify_reports/20260226_220014_fix_foo.md b/src/data/self_modify_reports/20260226_220014_fix_foo.md new file mode 100644 index 00000000..f427b616 --- /dev/null +++ b/src/data/self_modify_reports/20260226_220014_fix_foo.md @@ -0,0 +1,48 @@ +# Self-Modify Report: 20260226_220014 + +**Instruction:** Fix foo +**Target files:** src/foo.py +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** SUCCESS +**Error:** none +**Commit:** abc123 +**Attempts:** 2 +**Autonomous cycles:** 0 + +## Attempt 1 -- syntax_validation + +**Error:** src/foo.py: line 1: '(' was never closed + +### LLM Response +``` +bad llm +``` + +### Edits Written +#### src/foo.py +```python +def foo( + +``` + +## Attempt 2 -- complete + +### LLM Response +``` +good llm +``` + +### Edits Written +#### src/foo.py +```python +def foo(): + pass + +``` + +### Test Result: PASSED +``` +passed +``` diff --git a/src/data/self_modify_reports/20260226_220015_fix_foo.md b/src/data/self_modify_reports/20260226_220015_fix_foo.md new file mode 100644 index 00000000..e7e727d6 --- /dev/null +++ b/src/data/self_modify_reports/20260226_220015_fix_foo.md @@ -0,0 +1,31 @@ +# Self-Modify Report: 20260226_220015 + +**Instruction:** Fix foo +**Target files:** src/foo.py +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** FAILED +**Error:** Tests failed after 1 attempt(s). +**Commit:** none +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- complete + +### LLM Response +``` +llm raw +``` + +### Edits Written +#### src/foo.py +```python +x = 2 + +``` + +### Test Result: FAILED +``` +FAILED +``` diff --git a/src/data/self_modify_reports/20260226_220015_fix_foo_important_correction_from_previ.md b/src/data/self_modify_reports/20260226_220015_fix_foo_important_correction_from_previ.md new file mode 100644 index 00000000..8634111e --- /dev/null +++ b/src/data/self_modify_reports/20260226_220015_fix_foo_important_correction_from_previ.md @@ -0,0 +1,34 @@ +# Self-Modify Report: 20260226_220015 + +**Instruction:** Fix foo + +IMPORTANT CORRECTION from previous failure: +Fix: do X instead of Y +**Target files:** src/foo.py +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** SUCCESS +**Error:** none +**Commit:** abc123 +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- complete + +### LLM Response +``` +llm raw +``` + +### Edits Written +#### src/foo.py +```python +x = 2 + +``` + +### Test Result: PASSED +``` +PASSED +``` diff --git a/src/data/self_modify_reports/20260226_220410_add_docstring.md b/src/data/self_modify_reports/20260226_220410_add_docstring.md new file mode 100644 index 00000000..c0b11892 --- /dev/null +++ b/src/data/self_modify_reports/20260226_220410_add_docstring.md @@ -0,0 +1,19 @@ +# Self-Modify Report: 20260226_220410 + +**Instruction:** Add docstring +**Target files:** src/foo.py +**Dry run:** True +**Backend:** ollama +**Branch:** N/A +**Result:** SUCCESS +**Error:** none +**Commit:** none +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- dry_run + +### LLM Response +``` +llm raw +``` diff --git a/src/data/self_modify_reports/20260226_220410_break_it.md b/src/data/self_modify_reports/20260226_220410_break_it.md new file mode 100644 index 00000000..5a4059ef --- /dev/null +++ b/src/data/self_modify_reports/20260226_220410_break_it.md @@ -0,0 +1,31 @@ +# Self-Modify Report: 20260226_220410 + +**Instruction:** Break it +**Target files:** src/foo.py +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** FAILED +**Error:** Tests failed after 1 attempt(s). +**Commit:** none +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- complete + +### LLM Response +``` +llm raw +``` + +### Edits Written +#### src/foo.py +```python +x = 1 + +``` + +### Test Result: FAILED +``` +1 failed +``` diff --git a/src/data/self_modify_reports/20260226_220410_do_something_vague.md b/src/data/self_modify_reports/20260226_220410_do_something_vague.md new file mode 100644 index 00000000..b7ea87cf --- /dev/null +++ b/src/data/self_modify_reports/20260226_220410_do_something_vague.md @@ -0,0 +1,12 @@ +# Self-Modify Report: 20260226_220410 + +**Instruction:** do something vague +**Target files:** (auto-detected) +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** FAILED +**Error:** No target files identified. Specify target_files or use more specific language. +**Commit:** none +**Attempts:** 0 +**Autonomous cycles:** 0 diff --git a/src/data/self_modify_reports/20260226_220410_fix_foo.md b/src/data/self_modify_reports/20260226_220410_fix_foo.md new file mode 100644 index 00000000..29f47668 --- /dev/null +++ b/src/data/self_modify_reports/20260226_220410_fix_foo.md @@ -0,0 +1,31 @@ +# Self-Modify Report: 20260226_220410 + +**Instruction:** Fix foo +**Target files:** src/foo.py +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** FAILED +**Error:** Tests failed after 1 attempt(s). +**Commit:** none +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- complete + +### LLM Response +``` +llm raw +``` + +### Edits Written +#### src/foo.py +```python +x = 2 + +``` + +### Test Result: FAILED +``` +FAILED +``` diff --git a/src/data/self_modify_reports/20260226_220410_fix_foo_important_correction_from_previ.md b/src/data/self_modify_reports/20260226_220410_fix_foo_important_correction_from_previ.md new file mode 100644 index 00000000..14006047 --- /dev/null +++ b/src/data/self_modify_reports/20260226_220410_fix_foo_important_correction_from_previ.md @@ -0,0 +1,34 @@ +# Self-Modify Report: 20260226_220410 + +**Instruction:** Fix foo + +IMPORTANT CORRECTION from previous failure: +Fix: do X instead of Y +**Target files:** src/foo.py +**Dry run:** False +**Backend:** ollama +**Branch:** N/A +**Result:** SUCCESS +**Error:** none +**Commit:** abc123 +**Attempts:** 1 +**Autonomous cycles:** 0 + +## Attempt 1 -- complete + +### LLM Response +``` +llm raw +``` + +### Edits Written +#### src/foo.py +```python +x = 2 + +``` + +### Test Result: PASSED +``` +PASSED +``` diff --git a/src/infrastructure/CLAUDE.md b/src/infrastructure/CLAUDE.md new file mode 100644 index 00000000..d07c5a56 --- /dev/null +++ b/src/infrastructure/CLAUDE.md @@ -0,0 +1,22 @@ +# infrastructure/ — Module Guide + +Cross-cutting services used by many modules. + +## Structure +- `ws_manager/` — WebSocket connection manager (singleton: `ws_manager`) +- `notifications/` — Push notification store (singleton: `notifier`) +- `events/` — Domain event bus and broadcaster +- `router/` — Cascade LLM router with circuit-breaker failover + +## Key singletons +```python +from infrastructure.ws_manager.handler import ws_manager +from infrastructure.notifications.push import notifier +from infrastructure.events.bus import event_bus +from infrastructure.router import get_router +``` + +## Testing +```bash +pytest tests/infrastructure/ tests/integrations/test_websocket*.py tests/integrations/test_notifications.py -q +``` diff --git a/src/infrastructure/__init__.py b/src/infrastructure/__init__.py new file mode 100644 index 00000000..3e7026e9 --- /dev/null +++ b/src/infrastructure/__init__.py @@ -0,0 +1 @@ +"""Infrastructure — Cross-cutting services (WebSocket, notifications, events, router).""" diff --git a/src/events/__init__.py b/src/infrastructure/events/__init__.py similarity index 100% rename from src/events/__init__.py rename to src/infrastructure/events/__init__.py diff --git a/src/events/broadcaster.py b/src/infrastructure/events/broadcaster.py similarity index 97% rename from src/events/broadcaster.py rename to src/infrastructure/events/broadcaster.py index d03f79c3..c7ba26ad 100644 --- a/src/events/broadcaster.py +++ b/src/infrastructure/events/broadcaster.py @@ -18,7 +18,7 @@ class EventBroadcaster: """Broadcasts events to WebSocket clients. Usage: - from events.broadcaster import event_broadcaster + from infrastructure.events.broadcaster import event_broadcaster event_broadcaster.broadcast(event) """ @@ -29,7 +29,7 @@ class EventBroadcaster: """Lazy import to avoid circular deps.""" if self._ws_manager is None: try: - from ws_manager.handler import ws_manager + from infrastructure.ws_manager.handler import ws_manager self._ws_manager = ws_manager except Exception as exc: logger.debug("WebSocket manager not available: %s", exc) diff --git a/src/events/bus.py b/src/infrastructure/events/bus.py similarity index 100% rename from src/events/bus.py rename to src/infrastructure/events/bus.py diff --git a/src/notifications/__init__.py b/src/infrastructure/notifications/__init__.py similarity index 100% rename from src/notifications/__init__.py rename to src/infrastructure/notifications/__init__.py diff --git a/src/notifications/push.py b/src/infrastructure/notifications/push.py similarity index 100% rename from src/notifications/push.py rename to src/infrastructure/notifications/push.py diff --git a/src/router/__init__.py b/src/infrastructure/router/__init__.py similarity index 100% rename from src/router/__init__.py rename to src/infrastructure/router/__init__.py diff --git a/src/router/api.py b/src/infrastructure/router/api.py similarity index 100% rename from src/router/api.py rename to src/infrastructure/router/api.py diff --git a/src/router/cascade.py b/src/infrastructure/router/cascade.py similarity index 100% rename from src/router/cascade.py rename to src/infrastructure/router/cascade.py diff --git a/src/ws_manager/__init__.py b/src/infrastructure/ws_manager/__init__.py similarity index 100% rename from src/ws_manager/__init__.py rename to src/infrastructure/ws_manager/__init__.py diff --git a/src/ws_manager/handler.py b/src/infrastructure/ws_manager/handler.py similarity index 100% rename from src/ws_manager/handler.py rename to src/infrastructure/ws_manager/handler.py diff --git a/src/integrations/CLAUDE.md b/src/integrations/CLAUDE.md new file mode 100644 index 00000000..258ac18f --- /dev/null +++ b/src/integrations/CLAUDE.md @@ -0,0 +1,14 @@ +# integrations/ — Module Guide + +External platform bridges. All are optional dependencies. + +## Structure +- `chat_bridge/` — Vendor-agnostic chat platform abstraction (Discord impl) +- `telegram_bot/` — Telegram bot bridge +- `shortcuts/` — iOS Siri Shortcuts API metadata +- `voice/` — Local NLU intent detection (regex-based, no cloud) + +## Testing +```bash +pytest tests/integrations/ -q +``` diff --git a/src/integrations/__init__.py b/src/integrations/__init__.py new file mode 100644 index 00000000..8eecf883 --- /dev/null +++ b/src/integrations/__init__.py @@ -0,0 +1 @@ +"""Integrations — External platform bridges (Discord, Telegram, Siri, Voice).""" diff --git a/src/integrations/chat_bridge/__init__.py b/src/integrations/chat_bridge/__init__.py new file mode 100644 index 00000000..7ecf0a7f --- /dev/null +++ b/src/integrations/chat_bridge/__init__.py @@ -0,0 +1,10 @@ +"""Chat Bridge — vendor-agnostic chat platform abstraction. + +Provides a clean interface for integrating any chat platform +(Discord, Telegram, Slack, etc.) with Timmy's agent core. + +Usage: + from integrations.chat_bridge.base import ChatPlatform + from integrations.chat_bridge.registry import platform_registry + from integrations.chat_bridge.vendors.discord import DiscordVendor +""" diff --git a/src/chat_bridge/base.py b/src/integrations/chat_bridge/base.py similarity index 100% rename from src/chat_bridge/base.py rename to src/integrations/chat_bridge/base.py diff --git a/src/chat_bridge/invite_parser.py b/src/integrations/chat_bridge/invite_parser.py similarity index 97% rename from src/chat_bridge/invite_parser.py rename to src/integrations/chat_bridge/invite_parser.py index 2c48770f..df64c440 100644 --- a/src/chat_bridge/invite_parser.py +++ b/src/integrations/chat_bridge/invite_parser.py @@ -11,7 +11,7 @@ Supports Discord invite patterns: - discordapp.com/invite/ Usage: - from chat_bridge.invite_parser import invite_parser + from integrations.chat_bridge.invite_parser import invite_parser # From image bytes (screenshot or QR photo) result = await invite_parser.parse_image(image_bytes) @@ -25,7 +25,7 @@ import logging import re from typing import Optional -from chat_bridge.base import InviteInfo +from integrations.chat_bridge.base import InviteInfo logger = logging.getLogger(__name__) diff --git a/src/chat_bridge/registry.py b/src/integrations/chat_bridge/registry.py similarity index 94% rename from src/chat_bridge/registry.py rename to src/integrations/chat_bridge/registry.py index 16271c47..52b9c7ed 100644 --- a/src/chat_bridge/registry.py +++ b/src/integrations/chat_bridge/registry.py @@ -5,7 +5,7 @@ all chat platform integrations. Dashboard routes and the agent core interact with platforms through this registry. Usage: - from chat_bridge.registry import platform_registry + from integrations.chat_bridge.registry import platform_registry platform_registry.register(discord_vendor) discord = platform_registry.get("discord") @@ -15,7 +15,7 @@ Usage: import logging from typing import Optional -from chat_bridge.base import ChatPlatform, PlatformStatus +from integrations.chat_bridge.base import ChatPlatform, PlatformStatus logger = logging.getLogger(__name__) diff --git a/src/chat_bridge/vendors/__init__.py b/src/integrations/chat_bridge/vendors/__init__.py similarity index 100% rename from src/chat_bridge/vendors/__init__.py rename to src/integrations/chat_bridge/vendors/__init__.py diff --git a/src/chat_bridge/vendors/discord.py b/src/integrations/chat_bridge/vendors/discord.py similarity index 99% rename from src/chat_bridge/vendors/discord.py rename to src/integrations/chat_bridge/vendors/discord.py index 06108843..747f1e3e 100644 --- a/src/chat_bridge/vendors/discord.py +++ b/src/integrations/chat_bridge/vendors/discord.py @@ -19,7 +19,7 @@ import logging from pathlib import Path from typing import Optional -from chat_bridge.base import ( +from integrations.chat_bridge.base import ( ChatMessage, ChatPlatform, ChatThread, diff --git a/src/shortcuts/__init__.py b/src/integrations/shortcuts/__init__.py similarity index 100% rename from src/shortcuts/__init__.py rename to src/integrations/shortcuts/__init__.py diff --git a/src/shortcuts/siri.py b/src/integrations/shortcuts/siri.py similarity index 100% rename from src/shortcuts/siri.py rename to src/integrations/shortcuts/siri.py diff --git a/src/telegram_bot/__init__.py b/src/integrations/telegram_bot/__init__.py similarity index 100% rename from src/telegram_bot/__init__.py rename to src/integrations/telegram_bot/__init__.py diff --git a/src/telegram_bot/bot.py b/src/integrations/telegram_bot/bot.py similarity index 100% rename from src/telegram_bot/bot.py rename to src/integrations/telegram_bot/bot.py diff --git a/src/voice/__init__.py b/src/integrations/voice/__init__.py similarity index 100% rename from src/voice/__init__.py rename to src/integrations/voice/__init__.py diff --git a/src/voice/nlu.py b/src/integrations/voice/nlu.py similarity index 100% rename from src/voice/nlu.py rename to src/integrations/voice/nlu.py diff --git a/src/lightning/CLAUDE.md b/src/lightning/CLAUDE.md new file mode 100644 index 00000000..b2bba170 --- /dev/null +++ b/src/lightning/CLAUDE.md @@ -0,0 +1,9 @@ +# lightning/ — Module Guide + +**Security-sensitive.** Bitcoin Lightning payment gating (L402). +Never hard-code secrets. Use `from config import settings` for all credentials. + +## Testing +```bash +pytest tests/lightning/ -q +``` diff --git a/src/mcp/discovery.py b/src/mcp/discovery.py index a6ec0241..e23e0d93 100644 --- a/src/mcp/discovery.py +++ b/src/mcp/discovery.py @@ -72,10 +72,10 @@ class ToolDiscovery: discovery = ToolDiscovery() # Discover from a module - tools = discovery.discover_module("tools.git") - + tools = discovery.discover_module("creative.tools.git") + # Auto-register with registry - discovery.auto_register("tools") + discovery.auto_register("creative.tools") # Discover from all installed packages tools = discovery.discover_all_packages() @@ -89,7 +89,7 @@ class ToolDiscovery: """Discover all MCP tools in a module. Args: - module_name: Dotted path to module (e.g., "tools.git") + module_name: Dotted path to module (e.g., "creative.tools.git") Returns: List of discovered tools diff --git a/src/self_coding/CLAUDE.md b/src/self_coding/CLAUDE.md new file mode 100644 index 00000000..795892a3 --- /dev/null +++ b/src/self_coding/CLAUDE.md @@ -0,0 +1,23 @@ +# self_coding/ — Module Guide + +Self-modification infrastructure with safety constraints. + +## Structure +- `git_safety.py` — Atomic git operations with rollback +- `codebase_indexer.py` — Live mental model of the codebase +- `modification_journal.py` — Persistent log of modification attempts +- `reflection.py` — Generate lessons learned +- `self_modify/` — Runtime self-modification loop (LLM-driven) +- `self_tdd/` — Continuous test watchdog +- `upgrades/` — Self-upgrade approval queue + +## Entry points +```toml +self-tdd = "self_coding.self_tdd.watchdog:main" +self-modify = "self_coding.self_modify.cli:main" +``` + +## Testing +```bash +pytest tests/self_coding/ -q +``` diff --git a/src/self_modify/__init__.py b/src/self_coding/self_modify/__init__.py similarity index 100% rename from src/self_modify/__init__.py rename to src/self_coding/self_modify/__init__.py diff --git a/src/self_modify/cli.py b/src/self_coding/self_modify/cli.py similarity index 98% rename from src/self_modify/cli.py rename to src/self_coding/self_modify/cli.py index 9a74fb6f..e0f6fe62 100644 --- a/src/self_modify/cli.py +++ b/src/self_coding/self_modify/cli.py @@ -45,7 +45,7 @@ def run( if not branch: os.environ["SELF_MODIFY_SKIP_BRANCH"] = "1" - from self_modify.loop import SelfModifyLoop, ModifyRequest + from self_coding.self_modify.loop import SelfModifyLoop, ModifyRequest target_files = list(file) if file else [] effective_backend = backend or os.environ.get("SELF_MODIFY_BACKEND", "auto") diff --git a/src/self_modify/loop.py b/src/self_coding/self_modify/loop.py similarity index 99% rename from src/self_modify/loop.py rename to src/self_coding/self_modify/loop.py index 633c905a..afb2dbf6 100644 --- a/src/self_modify/loop.py +++ b/src/self_coding/self_modify/loop.py @@ -480,7 +480,7 @@ Keep your response under 500 words. Focus on actionable fix instructions.""" def _create_branch(self) -> str: """Create and switch to a working branch.""" - from tools.git_tools import git_branch + from creative.tools.git_tools import git_branch branch_name = f"timmy/self-modify-{int(time.time())}" git_branch(self._repo_path, create=branch_name, switch=branch_name) @@ -489,7 +489,7 @@ Keep your response under 500 words. Focus on actionable fix instructions.""" def _git_commit(self, message: str, files: list[str]) -> Optional[str]: """Stage files and commit.""" - from tools.git_tools import git_add, git_commit + from creative.tools.git_tools import git_add, git_commit try: git_add(self._repo_path, paths=files) diff --git a/src/self_tdd/__init__.py b/src/self_coding/self_tdd/__init__.py similarity index 100% rename from src/self_tdd/__init__.py rename to src/self_coding/self_tdd/__init__.py diff --git a/src/self_tdd/watchdog.py b/src/self_coding/self_tdd/watchdog.py similarity index 100% rename from src/self_tdd/watchdog.py rename to src/self_coding/self_tdd/watchdog.py diff --git a/src/upgrades/__init__.py b/src/self_coding/upgrades/__init__.py similarity index 100% rename from src/upgrades/__init__.py rename to src/self_coding/upgrades/__init__.py diff --git a/src/upgrades/models.py b/src/self_coding/upgrades/models.py similarity index 100% rename from src/upgrades/models.py rename to src/self_coding/upgrades/models.py diff --git a/src/upgrades/queue.py b/src/self_coding/upgrades/queue.py similarity index 99% rename from src/upgrades/queue.py rename to src/self_coding/upgrades/queue.py index 8b80ef68..b02a12c3 100644 --- a/src/upgrades/queue.py +++ b/src/self_coding/upgrades/queue.py @@ -5,7 +5,7 @@ import subprocess from pathlib import Path from typing import Optional -from upgrades.models import ( +from self_coding.upgrades.models import ( Upgrade, UpgradeStatus, create_upgrade, diff --git a/src/swarm/CLAUDE.md b/src/swarm/CLAUDE.md new file mode 100644 index 00000000..fd21c5d3 --- /dev/null +++ b/src/swarm/CLAUDE.md @@ -0,0 +1,21 @@ +# swarm/ — Module Guide + +Security-sensitive module. Changes to `coordinator.py` require review. + +## Structure +- `coordinator.py` — Auction-based task assignment (singleton: `coordinator`) +- `tasks.py`, `bidder.py`, `comms.py` — Core swarm primitives +- `work_orders/` — External work order submission and execution +- `task_queue/` — Human-in-the-loop approval queue +- `event_log.py` — Structured event logging +- `personas.py`, `persona_node.py` — Agent persona management + +## Key singletons +```python +from swarm.coordinator import coordinator +``` + +## Testing +```bash +pytest tests/swarm/ -q +``` diff --git a/src/swarm/coordinator.py b/src/swarm/coordinator.py index 04b0c0cb..19bd3d2f 100644 --- a/src/swarm/coordinator.py +++ b/src/swarm/coordinator.py @@ -422,7 +422,7 @@ class SwarmCoordinator: async def _broadcast_agent_joined(self, agent_id: str, name: str) -> None: """Broadcast agent joined event via WebSocket.""" try: - from ws_manager.handler import ws_manager + from infrastructure.ws_manager.handler import ws_manager await ws_manager.broadcast_agent_joined(agent_id, name) except Exception as exc: logger.debug("WebSocket broadcast failed (agent_joined): %s", exc) @@ -430,7 +430,7 @@ class SwarmCoordinator: async def _broadcast_bid(self, task_id: str, agent_id: str, bid_sats: int) -> None: """Broadcast bid submitted event via WebSocket.""" try: - from ws_manager.handler import ws_manager + from infrastructure.ws_manager.handler import ws_manager await ws_manager.broadcast_bid_submitted(task_id, agent_id, bid_sats) except Exception as exc: logger.debug("WebSocket broadcast failed (bid): %s", exc) @@ -438,7 +438,7 @@ class SwarmCoordinator: async def _broadcast_task_posted(self, task_id: str, description: str) -> None: """Broadcast task posted event via WebSocket.""" try: - from ws_manager.handler import ws_manager + from infrastructure.ws_manager.handler import ws_manager await ws_manager.broadcast_task_posted(task_id, description) except Exception as exc: logger.debug("WebSocket broadcast failed (task_posted): %s", exc) @@ -446,7 +446,7 @@ class SwarmCoordinator: async def _broadcast_task_assigned(self, task_id: str, agent_id: str) -> None: """Broadcast task assigned event via WebSocket.""" try: - from ws_manager.handler import ws_manager + from infrastructure.ws_manager.handler import ws_manager await ws_manager.broadcast_task_assigned(task_id, agent_id) except Exception as exc: logger.debug("WebSocket broadcast failed (task_assigned): %s", exc) @@ -456,7 +456,7 @@ class SwarmCoordinator: ) -> None: """Broadcast task completed event via WebSocket.""" try: - from ws_manager.handler import ws_manager + from infrastructure.ws_manager.handler import ws_manager await ws_manager.broadcast_task_completed(task_id, agent_id, result) except Exception as exc: logger.debug("WebSocket broadcast failed (task_completed): %s", exc) diff --git a/src/swarm/event_log.py b/src/swarm/event_log.py index bdac7ca8..7b6ec0a4 100644 --- a/src/swarm/event_log.py +++ b/src/swarm/event_log.py @@ -143,7 +143,7 @@ def log_event( # Broadcast to WebSocket clients for real-time activity feed try: - from events.broadcaster import event_broadcaster + from infrastructure.events.broadcaster import event_broadcaster event_broadcaster.broadcast_sync(entry) except Exception: # Don't fail if broadcaster unavailable diff --git a/src/task_queue/__init__.py b/src/swarm/task_queue/__init__.py similarity index 100% rename from src/task_queue/__init__.py rename to src/swarm/task_queue/__init__.py diff --git a/src/task_queue/models.py b/src/swarm/task_queue/models.py similarity index 100% rename from src/task_queue/models.py rename to src/swarm/task_queue/models.py diff --git a/src/swarm/tool_executor.py b/src/swarm/tool_executor.py index 37fc64c0..c0423a69 100644 --- a/src/swarm/tool_executor.py +++ b/src/swarm/tool_executor.py @@ -302,7 +302,7 @@ class DirectToolExecutor(ToolExecutor): if not cfg.self_modify_enabled: return self.execute_task(task_description) - from self_modify.loop import SelfModifyLoop, ModifyRequest + from self_coding.self_modify.loop import SelfModifyLoop, ModifyRequest loop = SelfModifyLoop() result = loop.run(ModifyRequest(instruction=task_description)) diff --git a/src/work_orders/__init__.py b/src/swarm/work_orders/__init__.py similarity index 100% rename from src/work_orders/__init__.py rename to src/swarm/work_orders/__init__.py diff --git a/src/work_orders/executor.py b/src/swarm/work_orders/executor.py similarity index 96% rename from src/work_orders/executor.py rename to src/swarm/work_orders/executor.py index 8c8fd0b3..784c563a 100644 --- a/src/work_orders/executor.py +++ b/src/swarm/work_orders/executor.py @@ -2,7 +2,7 @@ import logging -from work_orders.models import WorkOrder, WorkOrderCategory +from swarm.work_orders.models import WorkOrder, WorkOrderCategory logger = logging.getLogger(__name__) diff --git a/src/work_orders/models.py b/src/swarm/work_orders/models.py similarity index 100% rename from src/work_orders/models.py rename to src/swarm/work_orders/models.py diff --git a/src/work_orders/risk.py b/src/swarm/work_orders/risk.py similarity index 95% rename from src/work_orders/risk.py rename to src/swarm/work_orders/risk.py index 7a93996c..7b8be3be 100644 --- a/src/work_orders/risk.py +++ b/src/swarm/work_orders/risk.py @@ -1,6 +1,6 @@ """Risk scoring and auto-execution threshold logic for work orders.""" -from work_orders.models import WorkOrder, WorkOrderCategory, WorkOrderPriority +from swarm.work_orders.models import WorkOrder, WorkOrderCategory, WorkOrderPriority PRIORITY_WEIGHTS = { diff --git a/src/agent_core/__init__.py b/src/timmy/agent_core/__init__.py similarity index 100% rename from src/agent_core/__init__.py rename to src/timmy/agent_core/__init__.py diff --git a/src/agent_core/interface.py b/src/timmy/agent_core/interface.py similarity index 100% rename from src/agent_core/interface.py rename to src/timmy/agent_core/interface.py diff --git a/src/agent_core/ollama_adapter.py b/src/timmy/agent_core/ollama_adapter.py similarity index 98% rename from src/agent_core/ollama_adapter.py rename to src/timmy/agent_core/ollama_adapter.py index e27a109b..7b024c2b 100644 --- a/src/agent_core/ollama_adapter.py +++ b/src/timmy/agent_core/ollama_adapter.py @@ -5,8 +5,8 @@ to the substrate-agnostic TimAgent interface. It's the bridge between the old codebase and the new embodiment-ready architecture. Usage: - from agent_core import AgentIdentity, Perception - from agent_core.ollama_adapter import OllamaAgent + from timmy.agent_core import AgentIdentity, Perception + from timmy.agent_core.ollama_adapter import OllamaAgent identity = AgentIdentity.generate("Timmy") agent = OllamaAgent(identity) @@ -19,7 +19,7 @@ Usage: from typing import Any, Optional -from agent_core.interface import ( +from timmy.agent_core.interface import ( AgentCapability, AgentIdentity, Perception, diff --git a/src/timmy/agents/__init__.py b/src/timmy/agents/__init__.py new file mode 100644 index 00000000..f097b30e --- /dev/null +++ b/src/timmy/agents/__init__.py @@ -0,0 +1,21 @@ +"""Agents package — Timmy and sub-agents. +""" + +from timmy.agents.timmy import TimmyOrchestrator, create_timmy_swarm +from timmy.agents.base import BaseAgent +from timmy.agents.seer import SeerAgent +from timmy.agents.forge import ForgeAgent +from timmy.agents.quill import QuillAgent +from timmy.agents.echo import EchoAgent +from timmy.agents.helm import HelmAgent + +__all__ = [ + "BaseAgent", + "TimmyOrchestrator", + "create_timmy_swarm", + "SeerAgent", + "ForgeAgent", + "QuillAgent", + "EchoAgent", + "HelmAgent", +] diff --git a/src/agents/base.py b/src/timmy/agents/base.py similarity index 98% rename from src/agents/base.py rename to src/timmy/agents/base.py index 7469868c..7e70239c 100644 --- a/src/agents/base.py +++ b/src/timmy/agents/base.py @@ -15,7 +15,7 @@ from agno.agent import Agent from agno.models.ollama import Ollama from config import settings -from events.bus import EventBus, Event +from infrastructure.events.bus import EventBus, Event from mcp.registry import tool_registry logger = logging.getLogger(__name__) diff --git a/src/agents/echo.py b/src/timmy/agents/echo.py similarity index 98% rename from src/agents/echo.py rename to src/timmy/agents/echo.py index 7bb8a702..cc82a3b9 100644 --- a/src/agents/echo.py +++ b/src/timmy/agents/echo.py @@ -9,7 +9,7 @@ Capabilities: from typing import Any -from agents.base import BaseAgent +from timmy.agents.base import BaseAgent ECHO_SYSTEM_PROMPT = """You are Echo, a memory and context management specialist. diff --git a/src/agents/forge.py b/src/timmy/agents/forge.py similarity index 98% rename from src/agents/forge.py rename to src/timmy/agents/forge.py index fbe44b2b..14ea9e7e 100644 --- a/src/agents/forge.py +++ b/src/timmy/agents/forge.py @@ -9,7 +9,7 @@ Capabilities: from typing import Any -from agents.base import BaseAgent +from timmy.agents.base import BaseAgent FORGE_SYSTEM_PROMPT = """You are Forge, a code generation and tool building specialist. diff --git a/src/agents/helm.py b/src/timmy/agents/helm.py similarity index 98% rename from src/agents/helm.py rename to src/timmy/agents/helm.py index 7d5c9f37..b8c383e4 100644 --- a/src/agents/helm.py +++ b/src/timmy/agents/helm.py @@ -9,7 +9,7 @@ Capabilities: from typing import Any -from agents.base import BaseAgent +from timmy.agents.base import BaseAgent HELM_SYSTEM_PROMPT = """You are Helm, a routing and orchestration specialist. diff --git a/src/agents/quill.py b/src/timmy/agents/quill.py similarity index 98% rename from src/agents/quill.py rename to src/timmy/agents/quill.py index 199d36e9..1ad65af0 100644 --- a/src/agents/quill.py +++ b/src/timmy/agents/quill.py @@ -9,7 +9,7 @@ Capabilities: from typing import Any -from agents.base import BaseAgent +from timmy.agents.base import BaseAgent QUILL_SYSTEM_PROMPT = """You are Quill, a writing and content generation specialist. diff --git a/src/agents/seer.py b/src/timmy/agents/seer.py similarity index 97% rename from src/agents/seer.py rename to src/timmy/agents/seer.py index 3e3e58f3..2e9f43e7 100644 --- a/src/agents/seer.py +++ b/src/timmy/agents/seer.py @@ -9,8 +9,8 @@ Capabilities: from typing import Any -from agents.base import BaseAgent -from events.bus import Event +from timmy.agents.base import BaseAgent +from infrastructure.events.bus import Event SEER_SYSTEM_PROMPT = """You are Seer, a research and information gathering specialist. diff --git a/src/agents/timmy.py b/src/timmy/agents/timmy.py similarity index 94% rename from src/agents/timmy.py rename to src/timmy/agents/timmy.py index acb314c9..95bc517f 100644 --- a/src/agents/timmy.py +++ b/src/timmy/agents/timmy.py @@ -10,9 +10,9 @@ from typing import Any, Optional from agno.agent import Agent from agno.models.ollama import Ollama -from agents.base import BaseAgent +from timmy.agents.base import BaseAgent from config import settings -from events.bus import EventBus, event_bus +from infrastructure.events.bus import EventBus, event_bus from mcp.registry import tool_registry logger = logging.getLogger(__name__) @@ -165,11 +165,11 @@ class TimmyOrchestrator(BaseAgent): # Factory function for creating fully configured Timmy def create_timmy_swarm() -> TimmyOrchestrator: """Create Timmy orchestrator with all sub-agents registered.""" - from agents.seer import SeerAgent - from agents.forge import ForgeAgent - from agents.quill import QuillAgent - from agents.echo import EchoAgent - from agents.helm import HelmAgent + from timmy.agents.seer import SeerAgent + from timmy.agents.forge import ForgeAgent + from timmy.agents.quill import QuillAgent + from timmy.agents.echo import EchoAgent + from timmy.agents.helm import HelmAgent # Create orchestrator timmy = TimmyOrchestrator() diff --git a/src/timmy/briefing.py b/src/timmy/briefing.py index 9b3503b4..57266217 100644 --- a/src/timmy/briefing.py +++ b/src/timmy/briefing.py @@ -169,7 +169,7 @@ def _gather_swarm_summary(since: datetime) -> str: def _gather_task_queue_summary() -> str: """Pull task queue stats for the briefing. Graceful if unavailable.""" try: - from task_queue.models import get_task_summary_for_briefing + from swarm.task_queue.models import get_task_summary_for_briefing stats = get_task_summary_for_briefing() parts = [] if stats["pending_approval"]: diff --git a/src/timmy/cascade_adapter.py b/src/timmy/cascade_adapter.py index 59984648..111afd77 100644 --- a/src/timmy/cascade_adapter.py +++ b/src/timmy/cascade_adapter.py @@ -10,7 +10,7 @@ import logging from dataclasses import dataclass from typing import Optional -from router.cascade import CascadeRouter +from infrastructure.router.cascade import CascadeRouter from timmy.prompts import TIMMY_SYSTEM_PROMPT logger = logging.getLogger(__name__) diff --git a/src/memory/__init__.py b/src/timmy/memory/__init__.py similarity index 100% rename from src/memory/__init__.py rename to src/timmy/memory/__init__.py diff --git a/src/memory/vector_store.py b/src/timmy/memory/vector_store.py similarity index 100% rename from src/memory/vector_store.py rename to src/timmy/memory/vector_store.py diff --git a/src/timmy/tools.py b/src/timmy/tools.py index d680d1af..cfde2e01 100644 --- a/src/timmy/tools.py +++ b/src/timmy/tools.py @@ -411,7 +411,7 @@ def get_all_available_tools() -> dict[str, dict]: # ── Git tools ───────────────────────────────────────────────────────────── try: - from tools.git_tools import GIT_TOOL_CATALOG + from creative.tools.git_tools import GIT_TOOL_CATALOG for tool_id, info in GIT_TOOL_CATALOG.items(): catalog[tool_id] = { "name": info["name"], @@ -423,7 +423,7 @@ def get_all_available_tools() -> dict[str, dict]: # ── Image tools (Pixel) ─────────────────────────────────────────────────── try: - from tools.image_tools import IMAGE_TOOL_CATALOG + from creative.tools.image_tools import IMAGE_TOOL_CATALOG for tool_id, info in IMAGE_TOOL_CATALOG.items(): catalog[tool_id] = { "name": info["name"], @@ -435,7 +435,7 @@ def get_all_available_tools() -> dict[str, dict]: # ── Music tools (Lyra) ──────────────────────────────────────────────────── try: - from tools.music_tools import MUSIC_TOOL_CATALOG + from creative.tools.music_tools import MUSIC_TOOL_CATALOG for tool_id, info in MUSIC_TOOL_CATALOG.items(): catalog[tool_id] = { "name": info["name"], @@ -447,7 +447,7 @@ def get_all_available_tools() -> dict[str, dict]: # ── Video tools (Reel) ──────────────────────────────────────────────────── try: - from tools.video_tools import VIDEO_TOOL_CATALOG + from creative.tools.video_tools import VIDEO_TOOL_CATALOG for tool_id, info in VIDEO_TOOL_CATALOG.items(): catalog[tool_id] = { "name": info["name"], diff --git a/tests/creative/test_creative_director.py b/tests/creative/test_creative_director.py index 4de12317..1166330d 100644 --- a/tests/creative/test_creative_director.py +++ b/tests/creative/test_creative_director.py @@ -113,7 +113,7 @@ class TestRunStoryboard: {"path": "/fake/3.png", "scene_index": 2, "prompt": "sunset"}, ], } - with patch("tools.image_tools.generate_storyboard", return_value=mock_result): + with patch("creative.tools.image_tools.generate_storyboard", return_value=mock_result): with patch("creative.director._save_project"): result = run_storyboard(sample_project) assert result["success"] @@ -130,7 +130,7 @@ class TestRunMusic: "success": True, "path": "/fake/song.wav", "genre": "pop", "duration": 60, } - with patch("tools.music_tools.generate_song", return_value=mock_result): + with patch("creative.tools.music_tools.generate_song", return_value=mock_result): with patch("creative.director._save_project"): result = run_music(sample_project, genre="pop") assert result["success"] @@ -147,8 +147,8 @@ class TestRunVideoGeneration: "success": True, "path": "/fake/clip.mp4", "duration": 5, } - with patch("tools.video_tools.generate_video_clip", return_value=mock_clip): - with patch("tools.video_tools.image_to_video", return_value=mock_clip): + with patch("creative.tools.video_tools.generate_video_clip", return_value=mock_clip): + with patch("creative.tools.video_tools.image_to_video", return_value=mock_clip): with patch("creative.director._save_project"): result = run_video_generation(sample_project) assert result["success"] diff --git a/tests/creative/test_image_tools.py b/tests/creative/test_image_tools.py index 2cc05d0d..25ebd1e2 100644 --- a/tests/creative/test_image_tools.py +++ b/tests/creative/test_image_tools.py @@ -8,7 +8,7 @@ import pytest from unittest.mock import patch, MagicMock from pathlib import Path -from tools.image_tools import ( +from creative.tools.image_tools import ( IMAGE_TOOL_CATALOG, generate_image, generate_storyboard, @@ -47,8 +47,8 @@ class TestSaveMetadata: class TestGenerateImageInterface: def test_raises_without_creative_deps(self): """generate_image raises ImportError when diffusers not available.""" - with patch("tools.image_tools._pipeline", None): - with patch("tools.image_tools._get_pipeline", side_effect=ImportError("no diffusers")): + with patch("creative.tools.image_tools._pipeline", None): + with patch("creative.tools.image_tools._get_pipeline", side_effect=ImportError("no diffusers")): with pytest.raises(ImportError): generate_image("a cat") @@ -67,8 +67,8 @@ class TestGenerateImageInterface: mock_torch.Generator.return_value = MagicMock() with patch.dict(sys.modules, {"torch": mock_torch}): - with patch("tools.image_tools._get_pipeline", return_value=mock_pipe): - with patch("tools.image_tools._output_dir", return_value=tmp_path): + with patch("creative.tools.image_tools._get_pipeline", return_value=mock_pipe): + with patch("creative.tools.image_tools._output_dir", return_value=tmp_path): result = generate_image("a cat", width=512, height=512, steps=1) assert result["success"] @@ -90,7 +90,7 @@ class TestGenerateStoryboardInterface: "id": str(call_count), "prompt": prompt, } - with patch("tools.image_tools.generate_image", side_effect=mock_gen_image): + with patch("creative.tools.image_tools.generate_image", side_effect=mock_gen_image): result = generate_storyboard( ["sunrise", "mountain peak", "sunset"], steps=1, @@ -112,7 +112,7 @@ class TestImageVariationsInterface: "seed": kwargs.get("seed"), } - with patch("tools.image_tools.generate_image", side_effect=mock_gen_image): + with patch("creative.tools.image_tools.generate_image", side_effect=mock_gen_image): result = image_variations("a dog", count=3, steps=1) assert result["success"] diff --git a/tests/creative/test_music_tools.py b/tests/creative/test_music_tools.py index cf258e4b..a45d498d 100644 --- a/tests/creative/test_music_tools.py +++ b/tests/creative/test_music_tools.py @@ -7,7 +7,7 @@ metadata tests run in CI. import pytest from unittest.mock import patch, MagicMock -from tools.music_tools import ( +from creative.tools.music_tools import ( MUSIC_TOOL_CATALOG, GENRES, list_genres, @@ -50,8 +50,8 @@ class TestGenres: class TestGenerateSongInterface: def test_raises_without_ace_step(self): - with patch("tools.music_tools._model", None): - with patch("tools.music_tools._get_model", side_effect=ImportError("no ace-step")): + with patch("creative.tools.music_tools._model", None): + with patch("creative.tools.music_tools._get_model", side_effect=ImportError("no ace-step")): with pytest.raises(ImportError): generate_song("la la la") @@ -63,9 +63,9 @@ class TestGenerateSongInterface: mock_model = MagicMock() mock_model.generate.return_value = mock_audio - with patch("tools.music_tools._get_model", return_value=mock_model): - with patch("tools.music_tools._output_dir", return_value=MagicMock()): - with patch("tools.music_tools._save_metadata"): + with patch("creative.tools.music_tools._get_model", return_value=mock_model): + with patch("creative.tools.music_tools._output_dir", return_value=MagicMock()): + with patch("creative.tools.music_tools._save_metadata"): # Should clamp 5 to 30 generate_song("lyrics", duration=5) call_kwargs = mock_model.generate.call_args[1] @@ -78,8 +78,8 @@ class TestGenerateSongInterface: mock_model = MagicMock() mock_model.generate.return_value = mock_audio - with patch("tools.music_tools._get_model", return_value=mock_model): - with patch("tools.music_tools._output_dir", return_value=tmp_path): + with patch("creative.tools.music_tools._get_model", return_value=mock_model): + with patch("creative.tools.music_tools._output_dir", return_value=tmp_path): result = generate_song( "hello world", genre="rock", duration=60, title="Test Song" ) @@ -98,8 +98,8 @@ class TestGenerateInstrumentalInterface: mock_model = MagicMock() mock_model.generate.return_value = mock_audio - with patch("tools.music_tools._get_model", return_value=mock_model): - with patch("tools.music_tools._output_dir", return_value=tmp_path): + with patch("creative.tools.music_tools._get_model", return_value=mock_model): + with patch("creative.tools.music_tools._output_dir", return_value=tmp_path): result = generate_instrumental("epic orchestral", genre="cinematic") assert result["success"] @@ -115,8 +115,8 @@ class TestGenerateVocalsInterface: mock_model = MagicMock() mock_model.generate.return_value = mock_audio - with patch("tools.music_tools._get_model", return_value=mock_model): - with patch("tools.music_tools._output_dir", return_value=tmp_path): + with patch("creative.tools.music_tools._get_model", return_value=mock_model): + with patch("creative.tools.music_tools._output_dir", return_value=tmp_path): result = generate_vocals("do re mi", style="jazz") assert result["success"] diff --git a/tests/creative/test_music_video_integration.py b/tests/creative/test_music_video_integration.py index 2294a227..4d170392 100644 --- a/tests/creative/test_music_video_integration.py +++ b/tests/creative/test_music_video_integration.py @@ -306,13 +306,13 @@ class TestCreativeDirectorPipeline: assembly_dir.mkdir() with ( - patch("tools.image_tools.generate_storyboard", + patch("creative.tools.image_tools.generate_storyboard", side_effect=self._make_storyboard_stub(frames_dir)), - patch("tools.music_tools.generate_song", + patch("creative.tools.music_tools.generate_song", side_effect=self._make_song_stub(audio_dir)), - patch("tools.video_tools.image_to_video", + patch("creative.tools.video_tools.image_to_video", side_effect=self._make_video_stub(clips_dir)), - patch("tools.video_tools.generate_video_clip", + patch("creative.tools.video_tools.generate_video_clip", side_effect=self._make_video_stub(clips_dir)), patch("creative.director._project_dir", return_value=tmp_path / "project"), @@ -375,7 +375,7 @@ class TestCreativeDirectorPipeline: # 2. Storyboard with ( - patch("tools.image_tools.generate_storyboard", + patch("creative.tools.image_tools.generate_storyboard", side_effect=self._make_storyboard_stub(frames_dir)), patch("creative.director._save_project"), ): @@ -385,7 +385,7 @@ class TestCreativeDirectorPipeline: # 3. Music with ( - patch("tools.music_tools.generate_song", + patch("creative.tools.music_tools.generate_song", side_effect=self._make_song_stub(audio_dir)), patch("creative.director._save_project"), ): @@ -400,7 +400,7 @@ class TestCreativeDirectorPipeline: # 4. Video generation (uses storyboard frames → image_to_video) with ( - patch("tools.video_tools.image_to_video", + patch("creative.tools.video_tools.image_to_video", side_effect=self._make_video_stub(clips_dir)), patch("creative.director._save_project"), ): diff --git a/tests/creative/test_video_tools.py b/tests/creative/test_video_tools.py index e3b282af..2836cad5 100644 --- a/tests/creative/test_video_tools.py +++ b/tests/creative/test_video_tools.py @@ -7,7 +7,7 @@ resolution preset tests run in CI. import pytest from unittest.mock import patch, MagicMock -from tools.video_tools import ( +from creative.tools.video_tools import ( VIDEO_TOOL_CATALOG, RESOLUTION_PRESETS, VIDEO_STYLES, @@ -55,8 +55,8 @@ class TestListVideoStyles: class TestGenerateVideoClipInterface: def test_raises_without_creative_deps(self): - with patch("tools.video_tools._t2v_pipeline", None): - with patch("tools.video_tools._get_t2v_pipeline", side_effect=ImportError("no diffusers")): + with patch("creative.tools.video_tools._t2v_pipeline", None): + with patch("creative.tools.video_tools._get_t2v_pipeline", side_effect=ImportError("no diffusers")): with pytest.raises(ImportError): generate_video_clip("a sunset") @@ -77,17 +77,17 @@ class TestGenerateVideoClipInterface: out_dir.__truediv__ = MagicMock(return_value=MagicMock(__str__=lambda s: "/fake/clip.mp4")) with patch.dict(sys.modules, {"torch": mock_torch}): - with patch("tools.video_tools._get_t2v_pipeline", return_value=mock_pipe): - with patch("tools.video_tools._export_frames_to_mp4"): - with patch("tools.video_tools._output_dir", return_value=out_dir): - with patch("tools.video_tools._save_metadata"): + with patch("creative.tools.video_tools._get_t2v_pipeline", return_value=mock_pipe): + with patch("creative.tools.video_tools._export_frames_to_mp4"): + with patch("creative.tools.video_tools._output_dir", return_value=out_dir): + with patch("creative.tools.video_tools._save_metadata"): result = generate_video_clip("test", duration=50) assert result["duration"] == 10 # clamped class TestImageToVideoInterface: def test_raises_without_creative_deps(self): - with patch("tools.video_tools._t2v_pipeline", None): - with patch("tools.video_tools._get_t2v_pipeline", side_effect=ImportError("no diffusers")): + with patch("creative.tools.video_tools._t2v_pipeline", None): + with patch("creative.tools.video_tools._get_t2v_pipeline", side_effect=ImportError("no diffusers")): with pytest.raises(ImportError): image_to_video("/fake/image.png", "animate") diff --git a/tests/dashboard/test_briefing.py b/tests/dashboard/test_briefing.py index c239bf5e..db1de248 100644 --- a/tests/dashboard/test_briefing.py +++ b/tests/dashboard/test_briefing.py @@ -235,11 +235,11 @@ def test_call_agent_falls_back_on_exception(engine): @pytest.mark.asyncio async def test_notify_briefing_ready_skips_when_no_approvals(caplog): """notify_briefing_ready should NOT fire native notification with 0 approvals.""" - from notifications.push import notify_briefing_ready + from infrastructure.notifications.push import notify_briefing_ready b = _make_briefing() # approval_items=[] - with patch("notifications.push.notifier") as mock_notifier: + with patch("infrastructure.notifications.push.notifier") as mock_notifier: await notify_briefing_ready(b) mock_notifier.notify.assert_not_called() @@ -247,7 +247,7 @@ async def test_notify_briefing_ready_skips_when_no_approvals(caplog): @pytest.mark.asyncio async def test_notify_briefing_ready_fires_when_approvals_exist(): """notify_briefing_ready should fire when there are pending approval items.""" - from notifications.push import notify_briefing_ready + from infrastructure.notifications.push import notify_briefing_ready from timmy.briefing import ApprovalItem b = _make_briefing() @@ -263,7 +263,7 @@ async def test_notify_briefing_ready_fires_when_approvals_exist(): ), ] - with patch("notifications.push.notifier") as mock_notifier: + with patch("infrastructure.notifications.push.notifier") as mock_notifier: await notify_briefing_ready(b) mock_notifier.notify.assert_called_once() call_kwargs = mock_notifier.notify.call_args diff --git a/tests/dashboard/test_integration_full.py b/tests/dashboard/test_integration_full.py index 0a3b134b..4c0a2409 100644 --- a/tests/dashboard/test_integration_full.py +++ b/tests/dashboard/test_integration_full.py @@ -107,7 +107,7 @@ class TestEventBusIntegration: @pytest.mark.asyncio async def test_event_bus_publish_subscribe(self): """Test event bus publish and subscribe works.""" - from events.bus import EventBus, Event + from infrastructure.events.bus import EventBus, Event bus = EventBus() events_received = [] @@ -135,7 +135,7 @@ class TestAgentSystemIntegration: def test_base_agent_imports(self): """Test that base agent can be imported.""" - from agents.base import BaseAgent + from timmy.agents.base import BaseAgent assert BaseAgent is not None diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index 02c226bc..cfe5cefc 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -112,7 +112,7 @@ def serve_runner(): def self_tdd_runner(): """Typer CLI runner for self-tdd CLI tests.""" from typer.testing import CliRunner - from self_tdd.cli import app + from self_coding.self_tdd.cli import app yield CliRunner(), app @@ -142,9 +142,9 @@ def serve_client(): @pytest.fixture def tdd_runner(): """Alias for self_tdd_runner fixture.""" - pytest.importorskip("self_tdd.cli", reason="self_tdd CLI not available") + pytest.importorskip("self_coding.self_tdd.cli", reason="self_tdd CLI not available") from typer.testing import CliRunner - from self_tdd.cli import app + from self_coding.self_tdd.cli import app yield CliRunner(), app diff --git a/tests/infrastructure/test_functional_router.py b/tests/infrastructure/test_functional_router.py index 2e0ad27c..4b0199e0 100644 --- a/tests/infrastructure/test_functional_router.py +++ b/tests/infrastructure/test_functional_router.py @@ -10,7 +10,7 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest -from router.cascade import CascadeRouter, Provider, ProviderStatus, CircuitState +from infrastructure.router.cascade import CascadeRouter, Provider, ProviderStatus, CircuitState class TestCascadeRouterFunctional: diff --git a/tests/infrastructure/test_router_api.py b/tests/infrastructure/test_router_api.py index 1ac5945a..d9c90831 100644 --- a/tests/infrastructure/test_router_api.py +++ b/tests/infrastructure/test_router_api.py @@ -5,8 +5,8 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest from fastapi.testclient import TestClient -from router.cascade import CircuitState, Provider, ProviderStatus -from router.api import router, get_cascade_router +from infrastructure.router.cascade import CircuitState, Provider, ProviderStatus +from infrastructure.router.api import router, get_cascade_router def make_mock_router(): diff --git a/tests/infrastructure/test_router_cascade.py b/tests/infrastructure/test_router_cascade.py index a1a6a2f1..479045c9 100644 --- a/tests/infrastructure/test_router_cascade.py +++ b/tests/infrastructure/test_router_cascade.py @@ -8,7 +8,7 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest import yaml -from router.cascade import ( +from infrastructure.router.cascade import ( CascadeRouter, CircuitState, Provider, @@ -451,7 +451,7 @@ class TestProviderAvailabilityCheck: ) # When requests is None, assume available - import router.cascade as cascade_module + import infrastructure.router.cascade as cascade_module old_requests = cascade_module.requests cascade_module.requests = None try: diff --git a/tests/integrations/test_chat_bridge.py b/tests/integrations/test_chat_bridge.py index 25645faf..ef9a8d47 100644 --- a/tests/integrations/test_chat_bridge.py +++ b/tests/integrations/test_chat_bridge.py @@ -3,7 +3,7 @@ import pytest from unittest.mock import AsyncMock, MagicMock, patch -from chat_bridge.base import ( +from integrations.chat_bridge.base import ( ChatMessage, ChatPlatform, ChatThread, @@ -11,7 +11,7 @@ from chat_bridge.base import ( PlatformState, PlatformStatus, ) -from chat_bridge.registry import PlatformRegistry +from integrations.chat_bridge.registry import PlatformRegistry # ── Base dataclass tests ─────────────────────────────────────────────────────── @@ -206,7 +206,7 @@ class TestPlatformRegistry: class TestInviteParser: def test_parse_text_discord_gg(self): - from chat_bridge.invite_parser import invite_parser + from integrations.chat_bridge.invite_parser import invite_parser result = invite_parser.parse_text("Join us at https://discord.gg/abc123!") assert result is not None @@ -215,7 +215,7 @@ class TestInviteParser: assert result.source == "text" def test_parse_text_discord_com_invite(self): - from chat_bridge.invite_parser import invite_parser + from integrations.chat_bridge.invite_parser import invite_parser result = invite_parser.parse_text( "Link: https://discord.com/invite/myServer2024" @@ -224,7 +224,7 @@ class TestInviteParser: assert result.code == "myServer2024" def test_parse_text_discordapp(self): - from chat_bridge.invite_parser import invite_parser + from integrations.chat_bridge.invite_parser import invite_parser result = invite_parser.parse_text( "https://discordapp.com/invite/test-code" @@ -233,13 +233,13 @@ class TestInviteParser: assert result.code == "test-code" def test_parse_text_no_invite(self): - from chat_bridge.invite_parser import invite_parser + from integrations.chat_bridge.invite_parser import invite_parser result = invite_parser.parse_text("Hello world, no links here") assert result is None def test_parse_text_bare_discord_gg(self): - from chat_bridge.invite_parser import invite_parser + from integrations.chat_bridge.invite_parser import invite_parser result = invite_parser.parse_text("discord.gg/xyz789") assert result is not None @@ -248,7 +248,7 @@ class TestInviteParser: @pytest.mark.asyncio async def test_parse_image_no_deps(self): """parse_image returns None when pyzbar/Pillow are not installed.""" - from chat_bridge.invite_parser import InviteParser + from integrations.chat_bridge.invite_parser import InviteParser parser = InviteParser() # With mocked pyzbar, this should gracefully return None @@ -258,7 +258,7 @@ class TestInviteParser: class TestExtractDiscordCode: def test_various_formats(self): - from chat_bridge.invite_parser import _extract_discord_code + from integrations.chat_bridge.invite_parser import _extract_discord_code assert _extract_discord_code("discord.gg/abc") == "abc" assert _extract_discord_code("https://discord.gg/test") == "test" diff --git a/tests/integrations/test_discord_vendor.py b/tests/integrations/test_discord_vendor.py index f06528e7..b40f0412 100644 --- a/tests/integrations/test_discord_vendor.py +++ b/tests/integrations/test_discord_vendor.py @@ -5,7 +5,7 @@ import pytest from pathlib import Path from unittest.mock import AsyncMock, MagicMock, patch -from chat_bridge.base import PlatformState +from integrations.chat_bridge.base import PlatformState # ── DiscordVendor unit tests ────────────────────────────────────────────────── @@ -13,19 +13,19 @@ from chat_bridge.base import PlatformState class TestDiscordVendor: def test_name(self): - from chat_bridge.vendors.discord import DiscordVendor + from integrations.chat_bridge.vendors.discord import DiscordVendor vendor = DiscordVendor() assert vendor.name == "discord" def test_initial_state(self): - from chat_bridge.vendors.discord import DiscordVendor + from integrations.chat_bridge.vendors.discord import DiscordVendor vendor = DiscordVendor() assert vendor.state == PlatformState.DISCONNECTED def test_status_disconnected(self): - from chat_bridge.vendors.discord import DiscordVendor + from integrations.chat_bridge.vendors.discord import DiscordVendor vendor = DiscordVendor() status = vendor.status() @@ -35,8 +35,8 @@ class TestDiscordVendor: assert status.guild_count == 0 def test_save_and_load_token(self, tmp_path, monkeypatch): - from chat_bridge.vendors import discord as discord_mod - from chat_bridge.vendors.discord import DiscordVendor + from integrations.chat_bridge.vendors import discord as discord_mod + from integrations.chat_bridge.vendors.discord import DiscordVendor state_file = tmp_path / "discord_state.json" monkeypatch.setattr(discord_mod, "_STATE_FILE", state_file) @@ -52,8 +52,8 @@ class TestDiscordVendor: assert loaded == "test-token-abc" def test_load_token_missing_file(self, tmp_path, monkeypatch): - from chat_bridge.vendors import discord as discord_mod - from chat_bridge.vendors.discord import DiscordVendor + from integrations.chat_bridge.vendors import discord as discord_mod + from integrations.chat_bridge.vendors.discord import DiscordVendor state_file = tmp_path / "nonexistent.json" monkeypatch.setattr(discord_mod, "_STATE_FILE", state_file) @@ -66,7 +66,7 @@ class TestDiscordVendor: @pytest.mark.asyncio async def test_start_no_token(self): - from chat_bridge.vendors.discord import DiscordVendor + from integrations.chat_bridge.vendors.discord import DiscordVendor vendor = DiscordVendor() result = await vendor.start(token=None) @@ -74,7 +74,7 @@ class TestDiscordVendor: @pytest.mark.asyncio async def test_start_import_error(self): - from chat_bridge.vendors.discord import DiscordVendor + from integrations.chat_bridge.vendors.discord import DiscordVendor vendor = DiscordVendor() # Simulate discord.py not installed by making import fail @@ -84,7 +84,7 @@ class TestDiscordVendor: @pytest.mark.asyncio async def test_stop_when_disconnected(self): - from chat_bridge.vendors.discord import DiscordVendor + from integrations.chat_bridge.vendors.discord import DiscordVendor vendor = DiscordVendor() # Should not raise @@ -92,13 +92,13 @@ class TestDiscordVendor: assert vendor.state == PlatformState.DISCONNECTED def test_get_oauth2_url_no_client(self): - from chat_bridge.vendors.discord import DiscordVendor + from integrations.chat_bridge.vendors.discord import DiscordVendor vendor = DiscordVendor() assert vendor.get_oauth2_url() is None def test_get_oauth2_url_with_client(self): - from chat_bridge.vendors.discord import DiscordVendor + from integrations.chat_bridge.vendors.discord import DiscordVendor vendor = DiscordVendor() mock_client = MagicMock() @@ -110,7 +110,7 @@ class TestDiscordVendor: @pytest.mark.asyncio async def test_send_message_not_connected(self): - from chat_bridge.vendors.discord import DiscordVendor + from integrations.chat_bridge.vendors.discord import DiscordVendor vendor = DiscordVendor() result = await vendor.send_message("123", "hello") @@ -118,7 +118,7 @@ class TestDiscordVendor: @pytest.mark.asyncio async def test_create_thread_not_connected(self): - from chat_bridge.vendors.discord import DiscordVendor + from integrations.chat_bridge.vendors.discord import DiscordVendor vendor = DiscordVendor() result = await vendor.create_thread("123", "Test Thread") @@ -126,7 +126,7 @@ class TestDiscordVendor: @pytest.mark.asyncio async def test_join_from_invite_not_connected(self): - from chat_bridge.vendors.discord import DiscordVendor + from integrations.chat_bridge.vendors.discord import DiscordVendor vendor = DiscordVendor() result = await vendor.join_from_invite("abc123") @@ -135,13 +135,13 @@ class TestDiscordVendor: class TestChunkMessage: def test_short_message(self): - from chat_bridge.vendors.discord import _chunk_message + from integrations.chat_bridge.vendors.discord import _chunk_message chunks = _chunk_message("Hello!", 2000) assert chunks == ["Hello!"] def test_long_message(self): - from chat_bridge.vendors.discord import _chunk_message + from integrations.chat_bridge.vendors.discord import _chunk_message text = "a" * 5000 chunks = _chunk_message(text, 2000) @@ -150,7 +150,7 @@ class TestChunkMessage: assert "".join(chunks) == text def test_split_at_newline(self): - from chat_bridge.vendors.discord import _chunk_message + from integrations.chat_bridge.vendors.discord import _chunk_message text = "Line1\n" + "x" * 1990 + "\nLine3" chunks = _chunk_message(text, 2000) @@ -179,7 +179,7 @@ class TestDiscordRoutes: def test_setup_with_token(self, client): """Setup with a token — bot won't actually connect but route works.""" with patch( - "chat_bridge.vendors.discord.DiscordVendor.start", + "integrations.chat_bridge.vendors.discord.DiscordVendor.start", new_callable=AsyncMock, return_value=False, ): @@ -200,7 +200,7 @@ class TestDiscordRoutes: def test_join_with_text_invite(self, client): with patch( - "chat_bridge.vendors.discord.DiscordVendor.join_from_invite", + "integrations.chat_bridge.vendors.discord.DiscordVendor.join_from_invite", new_callable=AsyncMock, return_value=True, ): @@ -215,7 +215,7 @@ class TestDiscordRoutes: assert data["invite"]["source"] == "text" def test_oauth_url_not_connected(self, client): - from chat_bridge.vendors.discord import discord_bot + from integrations.chat_bridge.vendors.discord import discord_bot # Reset singleton so it has no client discord_bot._client = None diff --git a/tests/integrations/test_notifications.py b/tests/integrations/test_notifications.py index 6ce386df..5ffdb403 100644 --- a/tests/integrations/test_notifications.py +++ b/tests/integrations/test_notifications.py @@ -1,6 +1,6 @@ """Tests for notifications/push.py — push notification system.""" -from notifications.push import PushNotifier +from infrastructure.notifications.push import PushNotifier def test_notify_creates_notification(): diff --git a/tests/integrations/test_shortcuts.py b/tests/integrations/test_shortcuts.py index 7613435b..bdcb3954 100644 --- a/tests/integrations/test_shortcuts.py +++ b/tests/integrations/test_shortcuts.py @@ -1,6 +1,6 @@ """Tests for shortcuts/siri.py — Siri Shortcuts integration.""" -from shortcuts.siri import get_setup_guide, SHORTCUT_ACTIONS +from integrations.shortcuts.siri import get_setup_guide, SHORTCUT_ACTIONS def test_setup_guide_has_title(): diff --git a/tests/integrations/test_telegram_bot.py b/tests/integrations/test_telegram_bot.py index 8c9f491e..06d50303 100644 --- a/tests/integrations/test_telegram_bot.py +++ b/tests/integrations/test_telegram_bot.py @@ -14,9 +14,9 @@ class TestTelegramBotTokenHelpers: def test_save_and_load_token(self, tmp_path, monkeypatch): """save_token persists to disk; load_token reads it back.""" state_file = tmp_path / "telegram_state.json" - monkeypatch.setattr("telegram_bot.bot._STATE_FILE", state_file) + monkeypatch.setattr("integrations.telegram_bot.bot._STATE_FILE", state_file) - from telegram_bot.bot import TelegramBot + from integrations.telegram_bot.bot import TelegramBot bot = TelegramBot() bot.save_token("test-token-123") @@ -30,28 +30,28 @@ class TestTelegramBotTokenHelpers: def test_load_token_missing_file(self, tmp_path, monkeypatch): """load_token returns None when no state file and no env var.""" state_file = tmp_path / "missing_telegram_state.json" - monkeypatch.setattr("telegram_bot.bot._STATE_FILE", state_file) + monkeypatch.setattr("integrations.telegram_bot.bot._STATE_FILE", state_file) # Ensure settings.telegram_token is empty mock_settings = MagicMock() mock_settings.telegram_token = "" - with patch("telegram_bot.bot._load_token_from_file", return_value=None): + with patch("integrations.telegram_bot.bot._load_token_from_file", return_value=None): with patch("config.settings", mock_settings): - from telegram_bot.bot import TelegramBot + from integrations.telegram_bot.bot import TelegramBot bot = TelegramBot() result = bot.load_token() assert result is None def test_token_set_property(self): """token_set reflects whether a token has been applied.""" - from telegram_bot.bot import TelegramBot + from integrations.telegram_bot.bot import TelegramBot bot = TelegramBot() assert not bot.token_set bot._token = "tok" assert bot.token_set def test_is_running_property(self): - from telegram_bot.bot import TelegramBot + from integrations.telegram_bot.bot import TelegramBot bot = TelegramBot() assert not bot.is_running bot._running = True @@ -63,9 +63,9 @@ class TestTelegramBotLifecycle: async def test_start_no_token_returns_false(self, tmp_path, monkeypatch): """start() returns False and stays idle when no token is available.""" state_file = tmp_path / "telegram_state.json" - monkeypatch.setattr("telegram_bot.bot._STATE_FILE", state_file) + monkeypatch.setattr("integrations.telegram_bot.bot._STATE_FILE", state_file) - from telegram_bot.bot import TelegramBot + from integrations.telegram_bot.bot import TelegramBot bot = TelegramBot() with patch.object(bot, "load_token", return_value=None): result = await bot.start() @@ -74,7 +74,7 @@ class TestTelegramBotLifecycle: @pytest.mark.asyncio async def test_start_already_running_returns_true(self): - from telegram_bot.bot import TelegramBot + from integrations.telegram_bot.bot import TelegramBot bot = TelegramBot() bot._running = True result = await bot.start(token="any") @@ -83,7 +83,7 @@ class TestTelegramBotLifecycle: @pytest.mark.asyncio async def test_start_import_error_returns_false(self): """start() returns False gracefully when python-telegram-bot absent.""" - from telegram_bot.bot import TelegramBot + from integrations.telegram_bot.bot import TelegramBot bot = TelegramBot() with patch.object(bot, "load_token", return_value="tok"), \ @@ -94,7 +94,7 @@ class TestTelegramBotLifecycle: @pytest.mark.asyncio async def test_stop_when_not_running_is_noop(self): - from telegram_bot.bot import TelegramBot + from integrations.telegram_bot.bot import TelegramBot bot = TelegramBot() # Should not raise await bot.stop() @@ -102,7 +102,7 @@ class TestTelegramBotLifecycle: @pytest.mark.asyncio async def test_stop_calls_shutdown(self): """stop() invokes the Application shutdown sequence.""" - from telegram_bot.bot import TelegramBot + from integrations.telegram_bot.bot import TelegramBot bot = TelegramBot() bot._running = True @@ -125,7 +125,7 @@ class TestTelegramBotLifecycle: class TestTelegramRoutes: def test_status_not_running(self, client): """GET /telegram/status returns running=False when bot is idle.""" - from telegram_bot.bot import telegram_bot + from integrations.telegram_bot.bot import telegram_bot telegram_bot._running = False telegram_bot._token = None @@ -137,7 +137,7 @@ class TestTelegramRoutes: def test_status_running(self, client): """GET /telegram/status returns running=True when bot is active.""" - from telegram_bot.bot import telegram_bot + from integrations.telegram_bot.bot import telegram_bot telegram_bot._running = True telegram_bot._token = "tok" @@ -161,7 +161,7 @@ class TestTelegramRoutes: def test_setup_success(self, client): """POST /telegram/setup with valid token starts bot and returns ok.""" - from telegram_bot.bot import telegram_bot + from integrations.telegram_bot.bot import telegram_bot telegram_bot._running = False with patch.object(telegram_bot, "save_token") as mock_save, \ @@ -175,7 +175,7 @@ class TestTelegramRoutes: def test_setup_failure(self, client): """POST /telegram/setup returns error dict when bot fails to start.""" - from telegram_bot.bot import telegram_bot + from integrations.telegram_bot.bot import telegram_bot telegram_bot._running = False with patch.object(telegram_bot, "save_token"), \ @@ -189,7 +189,7 @@ class TestTelegramRoutes: def test_setup_stops_running_bot_first(self, client): """POST /telegram/setup stops any running bot before starting new one.""" - from telegram_bot.bot import telegram_bot + from integrations.telegram_bot.bot import telegram_bot telegram_bot._running = True with patch.object(telegram_bot, "save_token"), \ @@ -207,5 +207,5 @@ class TestTelegramRoutes: def test_module_singleton_exists(): """telegram_bot module exposes a singleton TelegramBot instance.""" - from telegram_bot.bot import telegram_bot, TelegramBot + from integrations.telegram_bot.bot import telegram_bot, TelegramBot assert isinstance(telegram_bot, TelegramBot) diff --git a/tests/integrations/test_voice_nlu.py b/tests/integrations/test_voice_nlu.py index f8f3e963..69770bfc 100644 --- a/tests/integrations/test_voice_nlu.py +++ b/tests/integrations/test_voice_nlu.py @@ -1,6 +1,6 @@ """Tests for voice/nlu.py — intent detection and command extraction.""" -from voice.nlu import detect_intent, extract_command +from integrations.voice.nlu import detect_intent, extract_command # ── Intent detection ───────────────────────────────────────────────────────── diff --git a/tests/integrations/test_websocket.py b/tests/integrations/test_websocket.py index 2d941c8c..477e45f6 100644 --- a/tests/integrations/test_websocket.py +++ b/tests/integrations/test_websocket.py @@ -4,7 +4,7 @@ import json import pytest -from ws_manager.handler import WebSocketManager, WSEvent +from infrastructure.ws_manager.handler import WebSocketManager, WSEvent def test_ws_event_to_json(): diff --git a/tests/integrations/test_websocket_extended.py b/tests/integrations/test_websocket_extended.py index 88bd792b..26448b10 100644 --- a/tests/integrations/test_websocket_extended.py +++ b/tests/integrations/test_websocket_extended.py @@ -6,7 +6,7 @@ from unittest.mock import AsyncMock, MagicMock import pytest -from ws_manager.handler import WebSocketManager, WSEvent +from infrastructure.ws_manager.handler import WebSocketManager, WSEvent class TestWSEventSerialization: diff --git a/tests/self_coding/test_git_tools.py b/tests/self_coding/test_git_tools.py index e7c64e5f..cbb28e7c 100644 --- a/tests/self_coding/test_git_tools.py +++ b/tests/self_coding/test_git_tools.py @@ -7,7 +7,7 @@ working tree. import pytest from pathlib import Path -from tools.git_tools import ( +from creative.tools.git_tools import ( git_init, git_status, git_add, diff --git a/tests/self_coding/test_scary_paths.py b/tests/self_coding/test_scary_paths.py index cb40de57..de0fc0cf 100644 --- a/tests/self_coding/test_scary_paths.py +++ b/tests/self_coding/test_scary_paths.py @@ -274,7 +274,7 @@ class TestWebSocketResilience: def test_websocket_manager_handles_no_connections(self): """WebSocket manager handles zero connected clients.""" - from ws_manager.handler import ws_manager + from infrastructure.ws_manager.handler import ws_manager # Should not crash when broadcasting with no connections try: @@ -297,7 +297,7 @@ class TestVoiceNLUEdgeCases: def test_nlu_empty_string(self): """Empty string doesn't crash NLU.""" - from voice.nlu import detect_intent + from integrations.voice.nlu import detect_intent result = detect_intent("") assert result is not None @@ -306,14 +306,14 @@ class TestVoiceNLUEdgeCases: def test_nlu_all_punctuation(self): """String of only punctuation is handled.""" - from voice.nlu import detect_intent + from integrations.voice.nlu import detect_intent result = detect_intent("...!!!???") assert result is not None def test_nlu_very_long_input(self): """10k character input doesn't crash or hang.""" - from voice.nlu import detect_intent + from integrations.voice.nlu import detect_intent long_input = "word " * 2000 # ~10k chars @@ -327,7 +327,7 @@ class TestVoiceNLUEdgeCases: def test_nlu_non_english_text(self): """Non-English Unicode text is handled.""" - from voice.nlu import detect_intent + from integrations.voice.nlu import detect_intent # Test various Unicode scripts test_inputs = [ @@ -343,7 +343,7 @@ class TestVoiceNLUEdgeCases: def test_nlu_special_characters(self): """Special characters don't break parsing.""" - from voice.nlu import detect_intent + from integrations.voice.nlu import detect_intent special_inputs = [ "", diff --git a/tests/self_coding/test_self_edit_tool.py b/tests/self_coding/test_self_edit_tool.py index 2ce2d7a9..fdc2fd1e 100644 --- a/tests/self_coding/test_self_edit_tool.py +++ b/tests/self_coding/test_self_edit_tool.py @@ -11,7 +11,7 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest -from tools.self_edit import ( +from creative.tools.self_edit import ( MAX_FILES_PER_COMMIT, MAX_RETRIES, PROTECTED_FILES, @@ -81,7 +81,7 @@ def test_hello(): @pytest.fixture(autouse=True) def mock_settings(): """Mock settings to enable self-modification.""" - with patch('tools.self_edit.settings') as mock_settings: + with patch('creative.tools.self_edit.settings') as mock_settings: mock_settings.self_modify_enabled = True yield mock_settings @@ -343,7 +343,7 @@ class TestSelfEditGlobalTool: async def test_self_edit_tool_singleton(self, temp_repo): """Should use singleton pattern.""" - from tools import self_edit as self_edit_module + from creative.tools import self_edit as self_edit_module # Reset singleton self_edit_module._self_edit_tool = None diff --git a/tests/self_coding/test_self_modify.py b/tests/self_coding/test_self_modify.py index 177941e3..1a4cd9b5 100644 --- a/tests/self_coding/test_self_modify.py +++ b/tests/self_coding/test_self_modify.py @@ -8,7 +8,7 @@ from pathlib import Path import pytest -from self_modify.loop import SelfModifyLoop, ModifyRequest, ModifyResult +from self_coding.self_modify.loop import SelfModifyLoop, ModifyRequest, ModifyResult # ── Dataclass tests ─────────────────────────────────────────────────────────── @@ -75,7 +75,7 @@ class TestSelfModifyLoop: assert loop._autonomous is True assert loop._max_autonomous_cycles == 5 - @patch("self_modify.loop.settings") + @patch("self_coding.self_modify.loop.settings") def test_run_disabled(self, mock_settings): mock_settings.self_modify_enabled = False loop = SelfModifyLoop() @@ -83,8 +83,8 @@ class TestSelfModifyLoop: assert not result.success assert "disabled" in result.error.lower() - @patch("self_modify.loop.os.environ", {"SELF_MODIFY_SKIP_BRANCH": "1"}) - @patch("self_modify.loop.settings") + @patch("self_coding.self_modify.loop.os.environ", {"SELF_MODIFY_SKIP_BRANCH": "1"}) + @patch("self_coding.self_modify.loop.settings") def test_run_no_target_files(self, mock_settings): mock_settings.self_modify_enabled = True mock_settings.self_modify_max_retries = 0 @@ -96,8 +96,8 @@ class TestSelfModifyLoop: assert not result.success assert "no target files" in result.error.lower() - @patch("self_modify.loop.os.environ", {"SELF_MODIFY_SKIP_BRANCH": "1"}) - @patch("self_modify.loop.settings") + @patch("self_coding.self_modify.loop.os.environ", {"SELF_MODIFY_SKIP_BRANCH": "1"}) + @patch("self_coding.self_modify.loop.settings") def test_run_success_path(self, mock_settings): mock_settings.self_modify_enabled = True mock_settings.self_modify_max_retries = 2 @@ -125,8 +125,8 @@ class TestSelfModifyLoop: loop._run_tests.assert_called_once() loop._git_commit.assert_called_once() - @patch("self_modify.loop.os.environ", {"SELF_MODIFY_SKIP_BRANCH": "1"}) - @patch("self_modify.loop.settings") + @patch("self_coding.self_modify.loop.os.environ", {"SELF_MODIFY_SKIP_BRANCH": "1"}) + @patch("self_coding.self_modify.loop.settings") def test_run_test_failure_reverts(self, mock_settings): mock_settings.self_modify_enabled = True mock_settings.self_modify_max_retries = 0 @@ -151,8 +151,8 @@ class TestSelfModifyLoop: assert not result.test_passed loop._revert_files.assert_called() - @patch("self_modify.loop.os.environ", {"SELF_MODIFY_SKIP_BRANCH": "1"}) - @patch("self_modify.loop.settings") + @patch("self_coding.self_modify.loop.os.environ", {"SELF_MODIFY_SKIP_BRANCH": "1"}) + @patch("self_coding.self_modify.loop.settings") def test_dry_run(self, mock_settings): mock_settings.self_modify_enabled = True mock_settings.self_modify_max_retries = 2 @@ -207,8 +207,8 @@ class TestSyntaxValidation: errors = loop._validate_syntax({"README.md": "this is not python {{{}"}) assert errors == {} - @patch("self_modify.loop.os.environ", {"SELF_MODIFY_SKIP_BRANCH": "1"}) - @patch("self_modify.loop.settings") + @patch("self_coding.self_modify.loop.os.environ", {"SELF_MODIFY_SKIP_BRANCH": "1"}) + @patch("self_coding.self_modify.loop.settings") def test_syntax_error_skips_write(self, mock_settings): """When LLM produces invalid syntax, we skip writing and retry.""" mock_settings.self_modify_enabled = True @@ -264,8 +264,8 @@ class TestBackendResolution: class TestAutonomousLoop: - @patch("self_modify.loop.os.environ", {"SELF_MODIFY_SKIP_BRANCH": "1"}) - @patch("self_modify.loop.settings") + @patch("self_coding.self_modify.loop.os.environ", {"SELF_MODIFY_SKIP_BRANCH": "1"}) + @patch("self_coding.self_modify.loop.settings") def test_autonomous_retries_after_failure(self, mock_settings): mock_settings.self_modify_enabled = True mock_settings.self_modify_max_retries = 0 @@ -371,43 +371,43 @@ class TestFileInference: class TestCodeIntent: def test_detects_modify_code(self): - from voice.nlu import detect_intent + from integrations.voice.nlu import detect_intent intent = detect_intent("modify the code in config.py") assert intent.name == "code" def test_detects_self_modify(self): - from voice.nlu import detect_intent + from integrations.voice.nlu import detect_intent intent = detect_intent("self-modify to add a new endpoint") assert intent.name == "code" def test_detects_edit_source(self): - from voice.nlu import detect_intent + from integrations.voice.nlu import detect_intent intent = detect_intent("edit the source to fix the bug") assert intent.name == "code" def test_detects_update_your_code(self): - from voice.nlu import detect_intent + from integrations.voice.nlu import detect_intent intent = detect_intent("update your code to handle errors") assert intent.name == "code" def test_detects_fix_function(self): - from voice.nlu import detect_intent + from integrations.voice.nlu import detect_intent intent = detect_intent("fix the function that calculates totals") assert intent.name == "code" def test_does_not_match_general_chat(self): - from voice.nlu import detect_intent + from integrations.voice.nlu import detect_intent intent = detect_intent("tell me about the weather today") assert intent.name == "chat" def test_extracts_target_file_entity(self): - from voice.nlu import detect_intent + from integrations.voice.nlu import detect_intent intent = detect_intent("modify file src/config.py to add debug flag") assert intent.entities.get("target_file") == "src/config.py" diff --git a/tests/self_coding/test_watchdog.py b/tests/self_coding/test_watchdog.py index 934209c4..0e43a2c7 100644 --- a/tests/self_coding/test_watchdog.py +++ b/tests/self_coding/test_watchdog.py @@ -1,6 +1,6 @@ from unittest.mock import MagicMock, patch -from self_tdd.watchdog import _run_tests +from self_coding.self_tdd.watchdog import _run_tests def _mock_result(returncode: int, stdout: str = "", stderr: str = "") -> MagicMock: @@ -12,26 +12,26 @@ def _mock_result(returncode: int, stdout: str = "", stderr: str = "") -> MagicMo def test_run_tests_returns_true_when_suite_passes(): - with patch("self_tdd.watchdog.subprocess.run", return_value=_mock_result(0, "5 passed")): + with patch("self_coding.self_tdd.watchdog.subprocess.run", return_value=_mock_result(0, "5 passed")): passed, _ = _run_tests() assert passed is True def test_run_tests_returns_false_when_suite_fails(): - with patch("self_tdd.watchdog.subprocess.run", return_value=_mock_result(1, "1 failed")): + with patch("self_coding.self_tdd.watchdog.subprocess.run", return_value=_mock_result(1, "1 failed")): passed, _ = _run_tests() assert passed is False def test_run_tests_output_includes_stdout(): - with patch("self_tdd.watchdog.subprocess.run", return_value=_mock_result(0, stdout="5 passed")): + with patch("self_coding.self_tdd.watchdog.subprocess.run", return_value=_mock_result(0, stdout="5 passed")): _, output = _run_tests() assert "5 passed" in output def test_run_tests_output_combines_stdout_and_stderr(): with patch( - "self_tdd.watchdog.subprocess.run", + "self_coding.self_tdd.watchdog.subprocess.run", return_value=_mock_result(1, stdout="FAILED test_foo", stderr="ImportError: no module named bar"), ): _, output = _run_tests() @@ -40,7 +40,7 @@ def test_run_tests_output_combines_stdout_and_stderr(): def test_run_tests_invokes_pytest_with_correct_flags(): - with patch("self_tdd.watchdog.subprocess.run", return_value=_mock_result(0)) as mock_run: + with patch("self_coding.self_tdd.watchdog.subprocess.run", return_value=_mock_result(0)) as mock_run: _run_tests() cmd = mock_run.call_args[0][0] assert "pytest" in cmd @@ -49,6 +49,6 @@ def test_run_tests_invokes_pytest_with_correct_flags(): def test_run_tests_uses_60s_timeout(): - with patch("self_tdd.watchdog.subprocess.run", return_value=_mock_result(0)) as mock_run: + with patch("self_coding.self_tdd.watchdog.subprocess.run", return_value=_mock_result(0)) as mock_run: _run_tests() assert mock_run.call_args.kwargs["timeout"] == 60 diff --git a/tests/self_coding/test_watchdog_functional.py b/tests/self_coding/test_watchdog_functional.py index a5153501..4193fcef 100644 --- a/tests/self_coding/test_watchdog_functional.py +++ b/tests/self_coding/test_watchdog_functional.py @@ -7,11 +7,11 @@ from unittest.mock import patch, MagicMock, call import pytest -from self_tdd.watchdog import _run_tests, watch +from self_coding.self_tdd.watchdog import _run_tests, watch class TestRunTests: - @patch("self_tdd.watchdog.subprocess.run") + @patch("self_coding.self_tdd.watchdog.subprocess.run") def test_run_tests_passing(self, mock_run): mock_run.return_value = MagicMock( returncode=0, @@ -22,7 +22,7 @@ class TestRunTests: assert passed is True assert "5 passed" in output - @patch("self_tdd.watchdog.subprocess.run") + @patch("self_coding.self_tdd.watchdog.subprocess.run") def test_run_tests_failing(self, mock_run): mock_run.return_value = MagicMock( returncode=1, @@ -34,7 +34,7 @@ class TestRunTests: assert "2 failed" in output assert "ERRORS" in output - @patch("self_tdd.watchdog.subprocess.run") + @patch("self_coding.self_tdd.watchdog.subprocess.run") def test_run_tests_command_format(self, mock_run): mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") _run_tests() @@ -48,9 +48,9 @@ class TestRunTests: class TestWatch: - @patch("self_tdd.watchdog.time.sleep") - @patch("self_tdd.watchdog._run_tests") - @patch("self_tdd.watchdog.typer") + @patch("self_coding.self_tdd.watchdog.time.sleep") + @patch("self_coding.self_tdd.watchdog._run_tests") + @patch("self_coding.self_tdd.watchdog.typer") def test_watch_first_pass(self, mock_typer, mock_tests, mock_sleep): """First iteration: None→passing → should print green message.""" call_count = 0 @@ -67,9 +67,9 @@ class TestWatch: # Should have printed green "All tests passing" message mock_typer.secho.assert_called() - @patch("self_tdd.watchdog.time.sleep") - @patch("self_tdd.watchdog._run_tests") - @patch("self_tdd.watchdog.typer") + @patch("self_coding.self_tdd.watchdog.time.sleep") + @patch("self_coding.self_tdd.watchdog._run_tests") + @patch("self_coding.self_tdd.watchdog.typer") def test_watch_regression(self, mock_typer, mock_tests, mock_sleep): """Regression: passing→failing → should print red message + output.""" results = [(True, "ok"), (False, "FAILED: test_foo"), KeyboardInterrupt] @@ -91,9 +91,9 @@ class TestWatch: secho_calls = [str(c) for c in mock_typer.secho.call_args_list] assert any("Regression" in c for c in secho_calls) or any("RED" in c for c in secho_calls) - @patch("self_tdd.watchdog.time.sleep") - @patch("self_tdd.watchdog._run_tests") - @patch("self_tdd.watchdog.typer") + @patch("self_coding.self_tdd.watchdog.time.sleep") + @patch("self_coding.self_tdd.watchdog._run_tests") + @patch("self_coding.self_tdd.watchdog.typer") def test_watch_keyboard_interrupt(self, mock_typer, mock_tests, mock_sleep): mock_tests.side_effect = KeyboardInterrupt watch(interval=60) diff --git a/tests/swarm/test_task_queue.py b/tests/swarm/test_task_queue.py index 2ed6eae4..a35b298a 100644 --- a/tests/swarm/test_task_queue.py +++ b/tests/swarm/test_task_queue.py @@ -16,7 +16,7 @@ os.environ["TIMMY_TEST_MODE"] = "1" def test_create_task(): - from task_queue.models import create_task, TaskStatus, TaskPriority + from swarm.task_queue.models import create_task, TaskStatus, TaskPriority task = create_task( title="Test task", @@ -34,7 +34,7 @@ def test_create_task(): def test_get_task(): - from task_queue.models import create_task, get_task + from swarm.task_queue.models import create_task, get_task task = create_task(title="Get me", created_by="test") retrieved = get_task(task.id) @@ -43,13 +43,13 @@ def test_get_task(): def test_get_task_not_found(): - from task_queue.models import get_task + from swarm.task_queue.models import get_task assert get_task("nonexistent-id") is None def test_list_tasks(): - from task_queue.models import create_task, list_tasks, TaskStatus + from swarm.task_queue.models import create_task, list_tasks, TaskStatus create_task(title="List test 1", created_by="test") create_task(title="List test 2", created_by="test") @@ -58,7 +58,7 @@ def test_list_tasks(): def test_list_tasks_with_status_filter(): - from task_queue.models import ( + from swarm.task_queue.models import ( create_task, list_tasks, update_task_status, TaskStatus, ) @@ -69,7 +69,7 @@ def test_list_tasks_with_status_filter(): def test_update_task_status(): - from task_queue.models import ( + from swarm.task_queue.models import ( create_task, update_task_status, TaskStatus, ) @@ -79,7 +79,7 @@ def test_update_task_status(): def test_update_task_running_sets_started_at(): - from task_queue.models import ( + from swarm.task_queue.models import ( create_task, update_task_status, TaskStatus, ) @@ -89,7 +89,7 @@ def test_update_task_running_sets_started_at(): def test_update_task_completed_sets_completed_at(): - from task_queue.models import ( + from swarm.task_queue.models import ( create_task, update_task_status, TaskStatus, ) @@ -100,7 +100,7 @@ def test_update_task_completed_sets_completed_at(): def test_update_task_fields(): - from task_queue.models import create_task, update_task + from swarm.task_queue.models import create_task, update_task task = create_task(title="Modify test", created_by="test") updated = update_task(task.id, title="Modified title", priority="high") @@ -109,7 +109,7 @@ def test_update_task_fields(): def test_get_counts_by_status(): - from task_queue.models import create_task, get_counts_by_status + from swarm.task_queue.models import create_task, get_counts_by_status create_task(title="Count test", created_by="test") counts = get_counts_by_status() @@ -117,7 +117,7 @@ def test_get_counts_by_status(): def test_get_pending_count(): - from task_queue.models import create_task, get_pending_count + from swarm.task_queue.models import create_task, get_pending_count create_task(title="Pending count test", created_by="test") count = get_pending_count() @@ -125,7 +125,7 @@ def test_get_pending_count(): def test_update_task_steps(): - from task_queue.models import create_task, update_task_steps, get_task + from swarm.task_queue.models import create_task, update_task_steps, get_task task = create_task(title="Steps test", created_by="test") steps = [ @@ -140,14 +140,14 @@ def test_update_task_steps(): def test_auto_approve_not_triggered_by_default(): - from task_queue.models import create_task, TaskStatus + from swarm.task_queue.models import create_task, TaskStatus task = create_task(title="No auto", created_by="user", auto_approve=False) assert task.status == TaskStatus.PENDING_APPROVAL def test_get_task_summary_for_briefing(): - from task_queue.models import create_task, get_task_summary_for_briefing + from swarm.task_queue.models import create_task, get_task_summary_for_briefing create_task(title="Briefing test", created_by="test") summary = get_task_summary_for_briefing() @@ -272,7 +272,7 @@ def test_cancel_task_htmx(client): def test_retry_failed_task(client): - from task_queue.models import create_task, update_task_status, TaskStatus + from swarm.task_queue.models import create_task, update_task_status, TaskStatus task = create_task(title="To retry", created_by="test") update_task_status(task.id, TaskStatus.FAILED, result="Something broke") @@ -533,7 +533,7 @@ class TestBuildQueueContext: def test_returns_string_with_counts(self): from dashboard.routes.agents import _build_queue_context - from task_queue.models import create_task + from swarm.task_queue.models import create_task create_task(title="Context test task", created_by="test") ctx = _build_queue_context() assert "[System: Task queue" in ctx @@ -541,7 +541,7 @@ class TestBuildQueueContext: def test_returns_empty_on_error(self): from dashboard.routes.agents import _build_queue_context - with patch("task_queue.models.get_counts_by_status", side_effect=Exception("DB error")): + with patch("swarm.task_queue.models.get_counts_by_status", side_effect=Exception("DB error")): ctx = _build_queue_context() assert isinstance(ctx, str) assert ctx == "" @@ -552,7 +552,7 @@ class TestBuildQueueContext: def test_briefing_task_queue_summary(): """Briefing engine should include task queue data.""" - from task_queue.models import create_task + from swarm.task_queue.models import create_task from timmy.briefing import _gather_task_queue_summary create_task(title="Briefing integration test", created_by="test") diff --git a/tests/swarm/test_work_orders.py b/tests/swarm/test_work_orders.py index 1a86552a..50fe81bc 100644 --- a/tests/swarm/test_work_orders.py +++ b/tests/swarm/test_work_orders.py @@ -1,6 +1,6 @@ """Tests for the work order system.""" -from work_orders.models import ( +from swarm.work_orders.models import ( WorkOrder, WorkOrderCategory, WorkOrderPriority, @@ -12,7 +12,7 @@ from work_orders.models import ( list_work_orders, update_work_order_status, ) -from work_orders.risk import compute_risk_score, should_auto_execute +from swarm.work_orders.risk import compute_risk_score, should_auto_execute # ── Model CRUD tests ────────────────────────────────────────────────────────── diff --git a/tests/timmy/test_agent_core.py b/tests/timmy/test_agent_core.py index e85c2634..097c1564 100644 --- a/tests/timmy/test_agent_core.py +++ b/tests/timmy/test_agent_core.py @@ -10,7 +10,7 @@ from unittest.mock import MagicMock, patch import pytest -from agent_core.interface import ( +from timmy.agent_core.interface import ( ActionType, AgentCapability, AgentEffect, @@ -303,14 +303,14 @@ class TestOllamaAgent: @pytest.fixture def agent(self): - with patch("agent_core.ollama_adapter.create_timmy") as mock_ct: + with patch("timmy.agent_core.ollama_adapter.create_timmy") as mock_ct: mock_timmy = MagicMock() mock_run = MagicMock() mock_run.content = "Mocked LLM response" mock_timmy.run.return_value = mock_run mock_ct.return_value = mock_timmy - from agent_core.ollama_adapter import OllamaAgent + from timmy.agent_core.ollama_adapter import OllamaAgent identity = AgentIdentity.generate("TestTimmy") return OllamaAgent(identity, effect_log="/tmp/test_effects") @@ -433,10 +433,10 @@ class TestOllamaAgent: assert log[2]["type"] == "act" def test_no_effect_log_when_disabled(self): - with patch("agent_core.ollama_adapter.create_timmy") as mock_ct: + with patch("timmy.agent_core.ollama_adapter.create_timmy") as mock_ct: mock_timmy = MagicMock() mock_ct.return_value = mock_timmy - from agent_core.ollama_adapter import OllamaAgent + from timmy.agent_core.ollama_adapter import OllamaAgent identity = AgentIdentity.generate("NoLog") agent = OllamaAgent(identity) # no effect_log assert agent.get_effect_log() is None diff --git a/tests/timmy/test_vector_store.py b/tests/timmy/test_vector_store.py index 9b4b6f6e..f9113e64 100644 --- a/tests/timmy/test_vector_store.py +++ b/tests/timmy/test_vector_store.py @@ -1,7 +1,7 @@ """Tests for vector store (semantic memory) system.""" import pytest -from memory.vector_store import ( +from timmy.memory.vector_store import ( store_memory, search_memories, get_memory_context,