Private
Public Access
0
0

refactor(consumers): migrate 85 'from src.models import' sites to direct subsystem imports

Per post_module_taxonomy_de_cruft_20260627 Phase 2 (FR7). Each
'from src.models import X' for a moved class is rewritten to
'from src.<destination> import X':

  Ticket, Track, WorkerContext, TrackState, TrackMetadata,
    ThinkingSegment, EMPTY_TRACK_STATE            -> src.mma
  ProjectContext, ProjectMeta, ProjectOutput, ProjectFiles,
    ProjectScreenshots, ProjectDiscussion, EMPTY_PROJECT_CONTEXT -> src.project
  FileItem, Preset, ContextPreset, ContextFileEntry,
    NamedViewPreset                                -> src.project_files
  Tool, ToolPreset                                 -> src.tool_presets
  BiasProfile                                      -> src.tool_bias
  TextEditorConfig, ExternalEditorConfig,
    EMPTY_TEXT_EDITOR_CONFIG                       -> src.external_editor
  Persona                                          -> src.personas
  WorkspaceProfile                                -> src.workspace_manager
  MCPServerConfig, MCPConfiguration, VectorStoreConfig,
    RAGConfig, load_mcp_config                      -> src.mcp_client

NOT touched (kept on src.models; Phase 3 or Phase 4 will move them):
  GenerateRequest, ConfirmRequest, DEFAULT_TOOL_CATEGORIES, Metadata, PROVIDERS

Migration was performed by the one-time script
scripts/tier2/artifacts/post_module_taxonomy_de_cruft_20260627/migrate_imports.py
which uses a class-to-module map and re.sub() to rewrite each
'from src.models import X' line.

Total: 85 import lines rewritten across 71 files.

Note: this commit depends on the v2 SHIPPED work
(origin/tier2/module_taxonomy_refactor_20260627) being merged into
this branch NEXT. On master (without the v2 SHIPPED commits), the
destination modules do not exist and these imports would fail.
This commit is contained in:
2026-06-26 13:34:03 -04:00
parent e14cfb13da
commit 8f11340b38
72 changed files with 266 additions and 85 deletions
@@ -0,0 +1,167 @@
"""One-time migration script: src.models import -> direct subsystem imports.
Per post_module_taxonomy_de_cruft_20260627 Phase 2. Updates 95 consumer
sites that use 'from src.models import X' to use the direct subsystem
import path. Each 'from src.models import X' is rewritten based on the
class mapping:
Ticket, Track, WorkerContext, TrackState, TrackMetadata,
ThinkingSegment, EMPTY_TRACK_STATE -> src.mma
ProjectContext, ProjectMeta, ProjectOutput, ProjectFiles,
ProjectScreenshots, ProjectDiscussion, EMPTY_PROJECT_CONTEXT -> src.project
FileItem, Preset, ContextPreset, ContextFileEntry, NamedViewPreset -> src.project_files
Tool, ToolPreset -> src.tool_presets
BiasProfile -> src.tool_bias
TextEditorConfig, ExternalEditorConfig,
EMPTY_TEXT_EDITOR_CONFIG -> src.external_editor
Persona -> src.personas
WorkspaceProfile -> src.workspace_manager
MCPServerConfig, MCPConfiguration, VectorStoreConfig,
RAGConfig, load_mcp_config -> src.mcp_client
NOT touched (kept on src.models):
GenerateRequest, ConfirmRequest -> Phase 4 (api_hooks.py)
DEFAULT_TOOL_CATEGORIES -> Phase 3 (ai_client.py)
Metadata (the legacy alias) -> kept (re-exported at module level)
PROVIDERS -> kept (lazy __getattr__)
Usage:
uv run python scripts/tier2/artifacts/post_module_taxonomy_de_cruft_20260627/migrate_imports.py
This is a one-time script; it does not run as part of the test suite.
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
CLASS_TO_MODULE: dict[str, str] = {
"Ticket": "mma",
"Track": "mma",
"WorkerContext": "mma",
"TrackState": "mma",
"TrackMetadata": "mma",
"ThinkingSegment": "mma",
"EMPTY_TRACK_STATE": "mma",
"ProjectContext": "project",
"ProjectMeta": "project",
"ProjectOutput": "project",
"ProjectFiles": "project",
"ProjectScreenshots": "project",
"ProjectDiscussion": "project",
"EMPTY_PROJECT_CONTEXT": "project",
"FileItem": "project_files",
"Preset": "project_files",
"ContextPreset": "project_files",
"ContextFileEntry": "project_files",
"NamedViewPreset": "project_files",
"Tool": "tool_presets",
"ToolPreset": "tool_presets",
"BiasProfile": "tool_bias",
"TextEditorConfig": "external_editor",
"ExternalEditorConfig": "external_editor",
"EMPTY_TEXT_EDITOR_CONFIG": "external_editor",
"Persona": "personas",
"WorkspaceProfile": "workspace_manager",
"MCPServerConfig": "mcp_client",
"MCPConfiguration": "mcp_client",
"VectorStoreConfig": "mcp_client",
"RAGConfig": "mcp_client",
"load_mcp_config": "mcp_client",
}
KEEP_ON_MODELS: set[str] = {
"GenerateRequest",
"ConfirmRequest",
"DEFAULT_TOOL_CATEGORIES",
"Metadata",
"PROVIDERS",
}
def migrate_file(path: Path) -> tuple[int, list[str]]:
"""Rewrite 'from src.models import X' lines in path. Returns (count, errors)."""
try:
content = path.read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError) as e:
return 0, [f" {path}: cannot read: {e}"]
original = content
errors: list[str] = []
pattern = re.compile(r"^(\s*)from\s+src\.models\s+import\s+(.+?)$", re.MULTILINE)
def replace(m: re.Match[str]) -> str:
indent = m.group(1)
names_str = m.group(2)
names = [n.strip() for n in names_str.split(",")]
kept: list[str] = []
moved: dict[str, list[str]] = {}
for name in names:
if not name:
continue
if name in KEEP_ON_MODELS:
kept.append(name)
continue
if " as " in name:
orig, alias = [s.strip() for s in name.split(" as ", 1)]
if orig in KEEP_ON_MODELS:
kept.append(name)
continue
if orig in CLASS_TO_MODULE:
target_mod = CLASS_TO_MODULE[orig]
moved.setdefault(target_mod, []).append(name)
else:
errors.append(f" {path}: unknown alias '{name}' (orig={orig})")
kept.append(name)
continue
if name in CLASS_TO_MODULE:
target_mod = CLASS_TO_MODULE[name]
moved.setdefault(target_mod, []).append(name)
else:
errors.append(f" {path}: unknown class '{name}'")
kept.append(name)
if not moved and kept == names:
return m.group(0)
lines: list[str] = []
for mod, names_in_mod in sorted(moved.items()):
lines.append(f"{indent}from src.{mod} import {', '.join(names_in_mod)}")
if kept:
lines.append(f"{indent}from src.models import {', '.join(kept)}")
return "\n".join(lines)
new_content = pattern.sub(replace, content)
if new_content != original:
try:
path.write_text(new_content, encoding="utf-8", newline="")
except OSError as e:
return 0, [f" {path}: cannot write: {e}"]
return len(pattern.findall(original)), []
return 0, []
def main() -> int:
root = Path(".")
src_files = sorted(root.glob("src/*.py")) + sorted(root.glob("tests/*.py"))
total_changed = 0
files_changed = 0
all_errors: list[str] = []
for path in src_files:
count, errors = migrate_file(path)
all_errors.extend(errors)
if count > 0:
files_changed += 1
total_changed += count
print(f" {path}: {count} import line(s) rewritten")
print(f"\nTotal: {total_changed} import line(s) rewritten in {files_changed} file(s)")
if all_errors:
print("\nWarnings:")
for err in all_errors:
print(err)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
+6 -4
View File
@@ -49,7 +49,9 @@ from src.vendor_capabilities import VendorCapabilities, get_capabilities
# TODO(Ed): Eliminate these? # TODO(Ed): Eliminate these?
from src.events import EventEmitter from src.events import EventEmitter
from src.gemini_cli_adapter import GeminiCliAdapter from src.gemini_cli_adapter import GeminiCliAdapter
from src.models import FileItem, ToolPreset, BiasProfile, Tool from src.project_files import FileItem
from src.tool_bias import BiasProfile
from src.tool_presets import ToolPreset, Tool
from src.paths import get_credentials_path from src.paths import get_credentials_path
from src.tool_bias import ToolBiasEngine from src.tool_bias import ToolBiasEngine
from src.tool_presets import ToolPresetManager from src.tool_presets import ToolPresetManager
@@ -2561,7 +2563,7 @@ def _send_grok(md_content: str, user_message: str, base_dir: str,
if file_items: if file_items:
for fi in file_items: for fi in file_items:
if fi.get("is_image") and fi.get("base64_data"): if fi.get("is_image") and fi.get("base64_data"):
from src.models import FileItem as _FIC from src.project_files import FileItem as _FIC
fi_item = fi if isinstance(fi, _FIC) else _FIC.from_dict(fi) fi_item = fi if isinstance(fi, _FIC) else _FIC.from_dict(fi)
user_content = f"[IMAGE: {fi_item.path or 'attachment'}]\n{user_content}" user_content = f"[IMAGE: {fi_item.path or 'attachment'}]\n{user_content}"
if discussion_history and not history: if discussion_history and not history:
@@ -2805,7 +2807,7 @@ def _send_qwen(md_content: str, user_message: str, base_dir: str,
if file_items: if file_items:
for fi in file_items: for fi in file_items:
if fi.get("is_image") and fi.get("base64_data"): if fi.get("is_image") and fi.get("base64_data"):
from src.models import FileItem as _FIC from src.project_files import FileItem as _FIC
fi_item = fi if isinstance(fi, _FIC) else _FIC.from_dict(fi) fi_item = fi if isinstance(fi, _FIC) else _FIC.from_dict(fi)
user_content = f"[IMAGE: {fi_item.path or 'attachment'}]\n{user_content}" user_content = f"[IMAGE: {fi_item.path or 'attachment'}]\n{user_content}"
if discussion_history and not history: if discussion_history and not history:
@@ -2898,7 +2900,7 @@ def _send_llama(md_content: str, user_message: str, base_dir: str,
if file_items: if file_items:
for fi in file_items: for fi in file_items:
if fi.get("is_image") and fi.get("base64_data"): if fi.get("is_image") and fi.get("base64_data"):
from src.models import FileItem as _FIC from src.project_files import FileItem as _FIC
fi_item = fi if isinstance(fi, _FIC) else _FIC.from_dict(fi) fi_item = fi if isinstance(fi, _FIC) else _FIC.from_dict(fi)
user_content = f"[IMAGE: {fi_item.path or 'attachment'}]\n{user_content}" user_content = f"[IMAGE: {fi_item.path or 'attachment'}]\n{user_content}"
if discussion_history and not history: if discussion_history and not history:
+1 -1
View File
@@ -101,7 +101,7 @@ def generate_tickets(track_brief: str, module_skeletons: str) -> list[dict[str,
ai_client.set_current_tier(None) ai_client.set_current_tier(None)
from src.dag_engine import TrackDAG from src.dag_engine import TrackDAG
from src.models import Ticket from src.mma import Ticket
from src.result_types import ErrorInfo, ErrorKind, Result from src.result_types import ErrorInfo, ErrorKind, Result
def topological_sort(tickets: list[Ticket]) -> list[Ticket]: def topological_sort(tickets: list[Ticket]) -> list[Ticket]:
+1 -1
View File
@@ -1,6 +1,6 @@
from typing import Dict, Any from typing import Dict, Any
from src.models import ContextPreset from src.project_files import ContextPreset
from src.result_types import Result, ErrorInfo, ErrorKind from src.result_types import Result, ErrorInfo, ErrorKind
+1 -1
View File
@@ -28,7 +28,7 @@ See Also:
""" """
from typing import List from typing import List
from src.models import Ticket from src.mma import Ticket
from src.performance_monitor import get_monitor from src.performance_monitor import get_monitor
+3 -3
View File
@@ -9,7 +9,7 @@ import tempfile
from pathlib import Path from pathlib import Path
from typing import Optional, List, Dict, Any from typing import Optional, List, Dict, Any
from src.models import ExternalEditorConfig, TextEditorConfig from src.external_editor import ExternalEditorConfig, TextEditorConfig
from src.result_types import ErrorInfo, ErrorKind, Result from src.result_types import ErrorInfo, ErrorKind, Result
@@ -24,7 +24,7 @@ class ExternalEditorLauncher:
""" """
[C: tests/test_external_editor.py:TestExternalEditorLauncher.test_get_editor_by_name, tests/test_external_editor.py:TestExternalEditorLauncher.test_get_editor_returns_default, tests/test_external_editor.py:TestExternalEditorLauncher.test_get_editor_unknown_name] [C: tests/test_external_editor.py:TestExternalEditorLauncher.test_get_editor_by_name, tests/test_external_editor.py:TestExternalEditorLauncher.test_get_editor_returns_default, tests/test_external_editor.py:TestExternalEditorLauncher.test_get_editor_unknown_name]
""" """
from src.models import EMPTY_TEXT_EDITOR_CONFIG from src.external_editor import EMPTY_TEXT_EDITOR_CONFIG
if editor_name: if editor_name:
return self.config.editors.get(editor_name) or EMPTY_TEXT_EDITOR_CONFIG return self.config.editors.get(editor_name) or EMPTY_TEXT_EDITOR_CONFIG
return self.config.get_default() return self.config.get_default()
@@ -96,7 +96,7 @@ def _find_vscode_common_paths() -> str:
def auto_detect_vscode() -> TextEditorConfig: def auto_detect_vscode() -> TextEditorConfig:
from src.models import EMPTY_TEXT_EDITOR_CONFIG from src.external_editor import EMPTY_TEXT_EDITOR_CONFIG
global _cached_vscode_config global _cached_vscode_config
if _cached_vscode_config is not None: if _cached_vscode_config is not None:
return _cached_vscode_config return _cached_vscode_config
+1 -1
View File
@@ -44,7 +44,7 @@ from src import paths
from src import summarize from src import summarize
from src.dag_engine import TrackDAG, ExecutionEngine from src.dag_engine import TrackDAG, ExecutionEngine
from src.models import Ticket, Track, WorkerContext from src.mma import Ticket, Track, WorkerContext
from src.personas import PersonaManager from src.personas import PersonaManager
from src.result_types import ErrorInfo, ErrorKind, Result from src.result_types import ErrorInfo, ErrorKind, Result
+1 -1
View File
@@ -8,7 +8,7 @@ from src import ai_client
from src import mma_prompts from src import mma_prompts
from src import paths from src import paths
from src import summarize from src import summarize
from src.models import FileItem from src.project_files import FileItem
from src.result_types import Result, ErrorInfo, ErrorKind from src.result_types import Result, ErrorInfo, ErrorKind
from src.type_aliases import Metadata from src.type_aliases import Metadata
+1 -1
View File
@@ -4,7 +4,7 @@ import tomli_w
from pathlib import Path from pathlib import Path
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from src.models import Persona from src.personas import Persona
from src import paths from src import paths
class PersonaManager: class PersonaManager:
+1 -1
View File
@@ -5,7 +5,7 @@ import tomli_w
from pathlib import Path from pathlib import Path
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from src.models import Preset from src.project_files import Preset
from src.paths import get_global_presets_path, get_project_presets_path from src.paths import get_global_presets_path, get_project_presets_path
from src.result_types import ErrorInfo, ErrorKind, Result from src.result_types import ErrorInfo, ErrorKind, Result
+3 -3
View File
@@ -32,7 +32,7 @@ from src.type_aliases import (
) )
if TYPE_CHECKING: if TYPE_CHECKING:
from src.models import TrackState from src.mma import TrackState
TS_FMT: str = "%Y-%m-%dT%H:%M:%S" TS_FMT: str = "%Y-%m-%dT%H:%M:%S"
@@ -270,7 +270,7 @@ def flat_config(proj: Metadata, disc_name: Optional[str] = None, track_id: Optio
The returned dataclass supports dict-compat (__getitem__ / get) so The returned dataclass supports dict-compat (__getitem__ / get) so
existing consumers using .get() and [] continue to work unchanged existing consumers using .get() and [] continue to work unchanged
(Phase 2 Option A per SPEC_CORRECTION_phase_2.md).""" (Phase 2 Option A per SPEC_CORRECTION_phase_2.md)."""
from src.models import ProjectContext, ProjectMeta, ProjectOutput, ProjectFiles, ProjectScreenshots, ProjectDiscussion from src.project import ProjectContext, ProjectMeta, ProjectOutput, ProjectFiles, ProjectScreenshots, ProjectDiscussion
disc_sec = proj.get("discussion", {}) disc_sec = proj.get("discussion", {})
if track_id: if track_id:
history = load_track_history(track_id, proj.get("files", {}).get("base_dir", ".")) history = load_track_history(track_id, proj.get("files", {}).get("base_dir", "."))
@@ -321,7 +321,7 @@ def load_track_state(track_id: str, base_dir: Union[str, Path] = ".") -> "TrackS
Returns empty TrackState (zero-init) if not found. Returns empty TrackState (zero-init) if not found.
[C: tests/test_track_state_persistence.py:test_track_state_persistence] [C: tests/test_track_state_persistence.py:test_track_state_persistence]
""" """
from src.models import TrackState, EMPTY_TRACK_STATE from src.mma import TrackState, EMPTY_TRACK_STATE
state_file = paths.get_track_state_dir(track_id, project_path=str(base_dir)) / 'state.toml' state_file = paths.get_track_state_dir(track_id, project_path=str(base_dir)) / 'state.toml'
if not state_file.exists(): return EMPTY_TRACK_STATE if not state_file.exists(): return EMPTY_TRACK_STATE
try: try:
+1 -1
View File
@@ -2,7 +2,7 @@ import re
from typing import List, Tuple from typing import List, Tuple
from src.models import ThinkingSegment from src.mma import ThinkingSegment
def parse_thinking_trace(text: str) -> Tuple[List[ThinkingSegment], str]: def parse_thinking_trace(text: str) -> Tuple[List[ThinkingSegment], str]:
+3 -1
View File
@@ -1,6 +1,8 @@
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
from src.models import Tool, ToolPreset, BiasProfile from src.tool_bias import BiasProfile
from src.tool_presets import Tool, ToolPreset
class ToolBiasEngine: class ToolBiasEngine:
+2 -1
View File
@@ -5,7 +5,8 @@ from pathlib import Path
from typing import Dict, List, Optional, Union, Any from typing import Dict, List, Optional, Union, Any
from src import paths from src import paths
from src.models import ToolPreset, BiasProfile from src.tool_bias import BiasProfile
from src.tool_presets import ToolPreset
class ToolPresetManager: class ToolPresetManager:
+1 -1
View File
@@ -4,7 +4,7 @@ import tomli_w
from pathlib import Path from pathlib import Path
from typing import Dict, Any, Optional, Union from typing import Dict, Any, Optional, Union
from src.models import WorkspaceProfile from src.workspace_manager import WorkspaceProfile
from src import paths from src import paths
+5 -5
View File
@@ -7,7 +7,7 @@ class TestArchBoundaryPhase3(unittest.TestCase):
pass pass
def test_cascade_blocks_simple(self) -> None: def test_cascade_blocks_simple(self) -> None:
"""Test that a blocked dependency blocks its immediate dependent.""" """Test that a blocked dependency blocks its immediate dependent."""
from src.models import Ticket, Track from src.mma import Ticket, Track
t1 = Ticket(id="T1", description="d1", status="blocked", assigned_to="worker1") t1 = Ticket(id="T1", description="d1", status="blocked", assigned_to="worker1")
t2 = Ticket(id="T2", description="d2", status="todo", assigned_to="worker1", depends_on=["T1"]) t2 = Ticket(id="T2", description="d2", status="todo", assigned_to="worker1", depends_on=["T1"])
track = Track(id="TR1", description="track", tickets=[t1, t2]) track = Track(id="TR1", description="track", tickets=[t1, t2])
@@ -20,7 +20,7 @@ class TestArchBoundaryPhase3(unittest.TestCase):
self.assertIn("T1", t2.blocked_reason) self.assertIn("T1", t2.blocked_reason)
def test_cascade_blocks_multi_hop(self) -> None: def test_cascade_blocks_multi_hop(self) -> None:
"""Test that blocking cascades through multiple dependencies.""" """Test that blocking cascades through multiple dependencies."""
from src.models import Ticket from src.mma import Ticket
from src.dag_engine import TrackDAG, ExecutionEngine from src.dag_engine import TrackDAG, ExecutionEngine
t1 = Ticket(id="T1", description="d1", status="blocked", assigned_to="worker1") t1 = Ticket(id="T1", description="d1", status="blocked", assigned_to="worker1")
t2 = Ticket(id="T2", description="d2", status="todo", assigned_to="worker1", depends_on=["T1"]) t2 = Ticket(id="T2", description="d2", status="todo", assigned_to="worker1", depends_on=["T1"])
@@ -32,7 +32,7 @@ class TestArchBoundaryPhase3(unittest.TestCase):
self.assertEqual(t3.status, "blocked") self.assertEqual(t3.status, "blocked")
def test_manual_unblock_restores_todo(self) -> None: def test_manual_unblock_restores_todo(self) -> None:
"""Test that unblocking a task manually works if dependencies are met.""" """Test that unblocking a task manually works if dependencies are met."""
from src.models import Ticket from src.mma import Ticket
from src.dag_engine import TrackDAG, ExecutionEngine from src.dag_engine import TrackDAG, ExecutionEngine
t1 = Ticket(id="T1", description="d1", status="completed", assigned_to="worker1") t1 = Ticket(id="T1", description="d1", status="completed", assigned_to="worker1")
t2 = Ticket(id="T2", description="d2", status="blocked", assigned_to="worker1", blocked_reason="manual") t2 = Ticket(id="T2", description="d2", status="blocked", assigned_to="worker1", blocked_reason="manual")
@@ -44,7 +44,7 @@ class TestArchBoundaryPhase3(unittest.TestCase):
self.assertIn(t2, ready) self.assertIn(t2, ready)
def test_in_progress_not_blocked(self) -> None: def test_in_progress_not_blocked(self) -> None:
"""Test that in_progress tasks are not blocked automatically (only todo).""" """Test that in_progress tasks are not blocked automatically (only todo)."""
from src.models import Ticket from src.mma import Ticket
from src.dag_engine import TrackDAG, ExecutionEngine from src.dag_engine import TrackDAG, ExecutionEngine
t1 = Ticket(id="T1", description="d1", status="blocked", assigned_to="worker1") t1 = Ticket(id="T1", description="d1", status="blocked", assigned_to="worker1")
t2 = Ticket(id="T2", description="d2", status="in_progress", assigned_to="worker1", depends_on=["T1"]) t2 = Ticket(id="T2", description="d2", status="in_progress", assigned_to="worker1", depends_on=["T1"])
@@ -54,7 +54,7 @@ class TestArchBoundaryPhase3(unittest.TestCase):
self.assertEqual(t2.status, "in_progress") self.assertEqual(t2.status, "in_progress")
def test_execution_engine_tick_cascades_blocks(self) -> None: def test_execution_engine_tick_cascades_blocks(self) -> None:
"""Test that ExecutionEngine.tick() triggers the cascading blocks.""" """Test that ExecutionEngine.tick() triggers the cascading blocks."""
from src.models import Ticket from src.mma import Ticket
from src.dag_engine import TrackDAG, ExecutionEngine from src.dag_engine import TrackDAG, ExecutionEngine
t1 = Ticket(id="T1", description="d1", status="blocked", assigned_to="worker1") t1 = Ticket(id="T1", description="d1", status="blocked", assigned_to="worker1")
t2 = Ticket(id="T2", description="d2", status="todo", assigned_to="worker1", depends_on=["T1"]) t2 = Ticket(id="T2", description="d2", status="todo", assigned_to="worker1", depends_on=["T1"])
+2 -1
View File
@@ -1,6 +1,7 @@
import pytest import pytest
from src import ai_client from src import ai_client
from src.models import ToolPreset, Tool, BiasProfile from src.tool_bias import BiasProfile
from src.tool_presets import ToolPreset, Tool
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
def test_bias_efficacy_prompt_generation(): def test_bias_efficacy_prompt_generation():
+2 -1
View File
@@ -1,6 +1,7 @@
import pytest import pytest
from src import ai_client from src import ai_client
from src.models import ToolPreset, Tool, BiasProfile from src.tool_bias import BiasProfile
from src.tool_presets import ToolPreset, Tool
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
def test_system_prompt_biasing(): def test_system_prompt_biasing():
+2 -1
View File
@@ -1,5 +1,6 @@
import pytest import pytest
from src.models import Tool, ToolPreset, BiasProfile from src.tool_bias import BiasProfile
from src.tool_presets import Tool, ToolPreset
def test_tool_model(): def test_tool_model():
tool = Tool(name="read_file", weight=5, parameter_bias={"path": "preferred"}) tool = Tool(name="read_file", weight=5, parameter_bias={"path": "preferred"})
+1 -1
View File
@@ -1,7 +1,7 @@
import pytest import pytest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from src.multi_agent_conductor import ConductorEngine from src.multi_agent_conductor import ConductorEngine
from src.models import Ticket, Track from src.mma import Ticket, Track
import threading import threading
def test_conductor_abort_event_populated(): def test_conductor_abort_event_populated():
+1 -1
View File
@@ -3,7 +3,7 @@ from unittest.mock import MagicMock
import threading import threading
import time import time
from src.multi_agent_conductor import ConductorEngine from src.multi_agent_conductor import ConductorEngine
from src.models import Track from src.mma import Track
def test_conductor_engine_initializes_empty_worker_and_abort_dicts() -> None: def test_conductor_engine_initializes_empty_worker_and_abort_dicts() -> None:
""" """
+1 -1
View File
@@ -4,7 +4,7 @@ They MUST NOT be simplified, and their assertions on exact call counts and depen
""" """
import pytest import pytest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from src.models import Ticket, Track, WorkerContext from src.mma import Ticket, Track, WorkerContext
from src import ai_client from src import ai_client
from src.result_types import Result from src.result_types import Result
+1 -1
View File
@@ -1,7 +1,7 @@
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
from src import conductor_tech_lead from src import conductor_tech_lead
from src.models import Ticket from src.mma import Ticket
from src.result_types import Result from src.result_types import Result
import pytest import pytest
+1 -1
View File
@@ -1,7 +1,7 @@
import pytest import pytest
from pathlib import Path from pathlib import Path
from src.app_controller import AppController from src.app_controller import AppController
from src.models import FileItem from src.project_files import FileItem
def test_context_files_is_decoupled(): def test_context_files_is_decoupled():
controller = AppController() controller = AppController()
+1 -1
View File
@@ -1,6 +1,6 @@
import pytest import pytest
from src.aggregate import group_files_by_dir, compute_file_stats from src.aggregate import group_files_by_dir, compute_file_stats
from src.models import FileItem from src.project_files import FileItem
def test_group_files_by_dir(): def test_group_files_by_dir():
files = [ files = [
+1 -1
View File
@@ -1,6 +1,6 @@
import pytest import pytest
from src.gui_2 import App from src.gui_2 import App
from src.models import FileItem from src.project_files import FileItem
def test_view_mode_initialization(): def test_view_mode_initialization():
app = App() app = App()
+1 -1
View File
@@ -1,6 +1,6 @@
import pytest import pytest
from src.context_presets import ContextPresetManager from src.context_presets import ContextPresetManager
from src.models import ContextPreset, ContextFileEntry from src.project_files import ContextPreset, ContextFileEntry
def test_save_context_preset(): def test_save_context_preset():
manager = ContextPresetManager() manager = ContextPresetManager()
+1 -1
View File
@@ -1,6 +1,6 @@
import pytest import pytest
from src.context_presets import ContextPresetManager from src.context_presets import ContextPresetManager
from src.models import ContextPreset, ContextFileEntry from src.project_files import ContextPreset, ContextFileEntry
from src.app_controller import AppController from src.app_controller import AppController
from pathlib import Path from pathlib import Path
import tomli_w import tomli_w
+1 -1
View File
@@ -1,5 +1,5 @@
import pytest import pytest
from src.models import ContextPreset, ContextFileEntry from src.project_files import ContextPreset, ContextFileEntry
def test_context_file_entry_serialization(): def test_context_file_entry_serialization():
p = ContextFileEntry(path="test.py", view_mode="skeleton") p = ContextFileEntry(path="test.py", view_mode="skeleton")
+2 -2
View File
@@ -2,7 +2,7 @@ import pytest
from unittest.mock import Mock from unittest.mock import Mock
from pathlib import Path from pathlib import Path
from src.gui_2 import App from src.gui_2 import App
from src.models import FileItem from src.project_files import FileItem
def test_preview_button_syncs_context_files_to_controller(): def test_preview_button_syncs_context_files_to_controller():
app = Mock(spec=App) app = Mock(spec=App)
@@ -51,7 +51,7 @@ def test_preview_generates_nonempty_for_real_files(monkeypatch):
"""Integration test: Preview button should generate content when context_files has real FileItems.""" """Integration test: Preview button should generate content when context_files has real FileItems."""
import src.project_manager as pm import src.project_manager as pm
import src.aggregate as agg import src.aggregate as agg
from src.models import FileItem from src.project_files import FileItem
app = Mock(spec=App) app = Mock(spec=App)
test_file = FileItem(path='tests/test_context_composition_decoupled.py', view_mode='summary') test_file = FileItem(path='tests/test_context_composition_decoupled.py', view_mode='summary')
+1 -1
View File
@@ -2,7 +2,7 @@ import pytest
import time import time
from pathlib import Path from pathlib import Path
from src.file_cache import ASTParser from src.file_cache import ASTParser
from src.models import Ticket, Track, WorkerContext from src.mma import Ticket, Track, WorkerContext
from src.multi_agent_conductor import run_worker_lifecycle from src.multi_agent_conductor import run_worker_lifecycle
from src.result_types import Result from src.result_types import Result
+1 -1
View File
@@ -1,5 +1,5 @@
import pytest import pytest
from src.models import FileItem from src.project_files import FileItem
def test_file_item_custom_slices_serialization_with_annotations(): def test_file_item_custom_slices_serialization_with_annotations():
# Test that FileItem correctly serializes custom_slices with tag and comment. # Test that FileItem correctly serializes custom_slices with tag and comment.
+1 -1
View File
@@ -4,7 +4,7 @@ They MUST NOT be simplified. They ensure that dependency resolution, cycle detec
and topological sorting work perfectly to prevent catastrophic orchestrator deadlocks. and topological sorting work perfectly to prevent catastrophic orchestrator deadlocks.
""" """
import pytest import pytest
from src.models import Ticket from src.mma import Ticket
from src.dag_engine import TrackDAG from src.dag_engine import TrackDAG
def test_get_ready_tasks_linear(): def test_get_ready_tasks_linear():
+1 -1
View File
@@ -1,4 +1,4 @@
from src.models import Ticket from src.mma import Ticket
from src.dag_engine import TrackDAG, ExecutionEngine from src.dag_engine import TrackDAG, ExecutionEngine
def test_execution_engine_basic_flow(): def test_execution_engine_basic_flow():
+1 -1
View File
@@ -1,7 +1,7 @@
"""Tests for external editor integration.""" """Tests for external editor integration."""
import pytest import pytest
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
from src.models import TextEditorConfig, ExternalEditorConfig from src.external_editor import TextEditorConfig, ExternalEditorConfig
from src.external_editor import ( from src.external_editor import (
ExternalEditorLauncher, ExternalEditorLauncher,
get_default_launcher, get_default_launcher,
+1 -1
View File
@@ -1,5 +1,5 @@
import pytest import pytest
from src.models import FileItem from src.project_files import FileItem
def test_file_item_fields(): def test_file_item_fields():
"""Test that FileItem exists and has correct default values.""" """Test that FileItem exists and has correct default values."""
+2 -2
View File
@@ -2315,7 +2315,7 @@ def test_phase_10_l7271_dag_cycle_check_result_no_cycle():
opening the "Cycle Detected!" popup. opening the "Cycle Detected!" popup.
""" """
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from src.models import Ticket from src.mma import Ticket
import src.gui_2 as gui2_mod import src.gui_2 as gui2_mod
app = MagicMock() app = MagicMock()
app.active_tickets = [Ticket(id="T-001", description="T-001", depends_on=[])] app.active_tickets = [Ticket(id="T-001", description="T-001", depends_on=[])]
@@ -2335,7 +2335,7 @@ def test_phase_10_l7271_dag_cycle_check_result_cycle_detected():
returns Result(data=True). The caller opens the "Cycle Detected!" popup. returns Result(data=True). The caller opens the "Cycle Detected!" popup.
""" """
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from src.models import Ticket from src.mma import Ticket
import src.gui_2 as gui2_mod import src.gui_2 as gui2_mod
app = MagicMock() app = MagicMock()
app.active_tickets = [ app.active_tickets = [
+1 -1
View File
@@ -2,7 +2,7 @@ import pytest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from src import gui_2 from src import gui_2
from src.gui_2 import App from src.gui_2 import App
from src.models import Track from src.mma import Track
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def setup_mock_app(mock_app: App): def setup_mock_app(mock_app: App):
+1 -1
View File
@@ -2,7 +2,7 @@ import pytest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from src import gui_2 from src import gui_2
from src.gui_2 import App, C_LBL, C_VAL from src.gui_2 import App, C_LBL, C_VAL
from src.models import Ticket from src.mma import Ticket
def test_render_mma_dashboard_progress(): def test_render_mma_dashboard_progress():
# Create a mock for the imgui module used in gui_2 # Create a mock for the imgui module used in gui_2
+1 -1
View File
@@ -1,7 +1,7 @@
from typing import Any from typing import Any
import pytest import pytest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from src.models import Ticket, Track from src.mma import Ticket, Track
from src import multi_agent_conductor from src import multi_agent_conductor
from src.multi_agent_conductor import ConductorEngine from src.multi_agent_conductor import ConductorEngine
from src import ai_client from src import ai_client
+1 -1
View File
@@ -1,5 +1,5 @@
import pytest import pytest
from src.models import Ticket from src.mma import Ticket
def test_ticket_has_manual_block_field(): def test_ticket_has_manual_block_field():
t = Ticket(id="T-001", description="Test") t = Ticket(id="T-001", description="Test")
+2 -2
View File
@@ -10,7 +10,7 @@ Verifies:
import inspect import inspect
from unittest.mock import patch from unittest.mock import patch
from src.models import Ticket from src.mma import Ticket
class TestActiveTicketsType: class TestActiveTicketsType:
@@ -58,7 +58,7 @@ class TestActiveTicketsLoadBoundaries:
def test_load_active_tickets_beads_branch_converts_dicts_to_tickets(self) -> None: def test_load_active_tickets_beads_branch_converts_dicts_to_tickets(self) -> None:
"""_load_active_tickets (beads branch) must wrap bead dicts as models.Ticket.""" """_load_active_tickets (beads branch) must wrap bead dicts as models.Ticket."""
from src.app_controller import AppController from src.app_controller import AppController
from src.models import Ticket from src.mma import Ticket
ctrl = AppController.__new__(AppController) ctrl = AppController.__new__(AppController)
ctrl._last_request_errors = [] ctrl._last_request_errors = []
ctrl.ui_project_execution_mode = "beads" ctrl.ui_project_execution_mode = "beads"
+1 -1
View File
@@ -1,4 +1,4 @@
from src.models import Ticket, Track, WorkerContext from src.mma import Ticket, Track, WorkerContext
from src.dag_engine import get_executable_tickets from src.dag_engine import get_executable_tickets
def test_ticket_instantiation() -> None: def test_ticket_instantiation() -> None:
+1 -1
View File
@@ -1,5 +1,5 @@
from src.gui_2 import App from src.gui_2 import App
from src.models import Ticket from src.mma import Ticket
def test_cb_ticket_retry(app_instance: App) -> None: def test_cb_ticket_retry(app_instance: App) -> None:
ticket_id = "test_ticket_1" ticket_id = "test_ticket_1"
+1 -1
View File
@@ -3,7 +3,7 @@ from unittest.mock import patch, MagicMock
from src import orchestrator_pm from src import orchestrator_pm
from src import multi_agent_conductor from src import multi_agent_conductor
from src import conductor_tech_lead from src import conductor_tech_lead
from src.models import Ticket, Track, WorkerContext from src.mma import Ticket, Track, WorkerContext
from src.result_types import Result from src.result_types import Result
def test_generate_tracks() -> None: def test_generate_tracks() -> None:
+1 -1
View File
@@ -67,7 +67,7 @@ def test_worker_pool_completion_cleanup():
assert "t1" not in pool._active assert "t1" not in pool._active
from unittest.mock import patch from unittest.mock import patch
from src.models import Track, Ticket from src.mma import Track, Ticket
from src.multi_agent_conductor import ConductorEngine from src.multi_agent_conductor import ConductorEngine
@patch('src.multi_agent_conductor.run_worker_lifecycle') @patch('src.multi_agent_conductor.run_worker_lifecycle')
+1 -1
View File
@@ -1,5 +1,5 @@
import pytest import pytest
from src.models import Ticket from src.mma import Ticket
def test_ticket_has_model_override_field(): def test_ticket_has_model_override_field():
t = Ticket(id="T-001", description="Test") t = Ticket(id="T-001", description="Test")
+1 -1
View File
@@ -1,5 +1,5 @@
import pytest import pytest
from src.models import Ticket from src.mma import Ticket
from src.dag_engine import TrackDAG from src.dag_engine import TrackDAG
from src.performance_monitor import get_monitor from src.performance_monitor import get_monitor
+1 -1
View File
@@ -1,5 +1,5 @@
import pytest import pytest
from src.models import Ticket, WorkerContext from src.mma import Ticket, WorkerContext
def test_ticket_persona_id_serialization(): def test_ticket_persona_id_serialization():
+1 -1
View File
@@ -1,7 +1,7 @@
import pytest import pytest
import tomli_w import tomli_w
from pathlib import Path from pathlib import Path
from src.models import Persona from src.personas import Persona
from src.personas import PersonaManager from src.personas import PersonaManager
from src import paths from src import paths
+1 -1
View File
@@ -1,5 +1,5 @@
import pytest import pytest
from src.models import Persona from src.personas import Persona
def test_persona_serialization(): def test_persona_serialization():
persona = Persona( persona = Persona(
+1 -1
View File
@@ -1,6 +1,6 @@
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from src.multi_agent_conductor import ConductorEngine, run_worker_lifecycle from src.multi_agent_conductor import ConductorEngine, run_worker_lifecycle
from src.models import Ticket, Track, WorkerContext from src.mma import Ticket, Track, WorkerContext
from src import ai_client from src import ai_client
from src.result_types import Result from src.result_types import Result
+1 -1
View File
@@ -1,6 +1,6 @@
import pytest import pytest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from src.models import Ticket, Track from src.mma import Ticket, Track
from src.multi_agent_conductor import ConductorEngine from src.multi_agent_conductor import ConductorEngine
def test_conductor_engine_has_pause_event(): def test_conductor_engine_has_pause_event():
+1 -1
View File
@@ -1,7 +1,7 @@
import pytest import pytest
from pathlib import Path from pathlib import Path
from src.presets import PresetManager from src.presets import PresetManager
from src.models import Preset from src.project_files import Preset
def test_load_all_merged(tmp_path, monkeypatch): def test_load_all_merged(tmp_path, monkeypatch):
"""Tests that load_all correctly merges global and project presets.""" """Tests that load_all correctly merges global and project presets."""
+1 -1
View File
@@ -4,7 +4,7 @@ from pathlib import Path
import tempfile import tempfile
import shutil import shutil
from src.presets import PresetManager from src.presets import PresetManager
from src.models import Preset from src.project_files import Preset
class TestPresetManager(unittest.TestCase): class TestPresetManager(unittest.TestCase):
def setUp(self): def setUp(self):
+1 -1
View File
@@ -1,6 +1,6 @@
import pytest import pytest
from src.project_manager import calculate_track_progress from src.project_manager import calculate_track_progress
from src.models import Ticket from src.mma import Ticket
def test_calculate_track_progress_empty(): def test_calculate_track_progress_empty():
results = calculate_track_progress([]) results = calculate_track_progress([])
+2 -1
View File
@@ -2,7 +2,8 @@ import pytest
from typing import Any from typing import Any
import json import json
from src.project_manager import get_all_tracks, save_track_state from src.project_manager import get_all_tracks, save_track_state
from src.models import TrackState, Metadata, Ticket from src.mma import TrackState, Ticket
from src.models import Metadata
from datetime import datetime from datetime import datetime
def test_get_all_tracks_empty(tmp_path: Any) -> None: def test_get_all_tracks_empty(tmp_path: Any) -> None:
+1 -1
View File
@@ -5,7 +5,7 @@ import tempfile
import pytest import pytest
from src.rag_engine import RAGEngine from src.rag_engine import RAGEngine
from src.models import RAGConfig, VectorStoreConfig from src.mcp_client import RAGConfig, VectorStoreConfig
LOCAL_EMBED_DIM = 384 LOCAL_EMBED_DIM = 384
+1 -1
View File
@@ -4,7 +4,7 @@ import threading
import json import json
import time import time
from src.multi_agent_conductor import run_worker_lifecycle from src.multi_agent_conductor import run_worker_lifecycle
from src.models import Ticket, WorkerContext from src.mma import Ticket, WorkerContext
class TestRunWorkerLifecycleAbort(unittest.TestCase): class TestRunWorkerLifecycleAbort(unittest.TestCase):
def test_run_worker_lifecycle_returns_early_on_abort(self): def test_run_worker_lifecycle_returns_early_on_abort(self):
+1 -1
View File
@@ -1,5 +1,5 @@
import pytest import pytest
from src.models import FileItem from src.project_files import FileItem
from src.fuzzy_anchor import FuzzyAnchor from src.fuzzy_anchor import FuzzyAnchor
def test_add_slice_with_annotations(): def test_add_slice_with_annotations():
+1 -1
View File
@@ -1,7 +1,7 @@
import pytest import pytest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from src import multi_agent_conductor from src import multi_agent_conductor
from src.models import Ticket, WorkerContext from src.mma import Ticket, WorkerContext
from src import events from src import events
from src.result_types import Result from src.result_types import Result
import threading import threading
+1 -1
View File
@@ -32,7 +32,7 @@ def test_discussion_entry_without_thinking():
def test_thinking_segment_model_compatibility(): def test_thinking_segment_model_compatibility():
from src.models import ThinkingSegment from src.mma import ThinkingSegment
segment = ThinkingSegment(content="test", marker="thinking") segment = ThinkingSegment(content="test", marker="thinking")
assert segment.content == "test" assert segment.content == "test"
+1 -1
View File
@@ -3,7 +3,7 @@ import tempfile
import os import os
from pathlib import Path from pathlib import Path
from src import project_manager from src import project_manager
from src.models import ThinkingSegment from src.mma import ThinkingSegment
def test_save_and_load_history_with_thinking_segments(): def test_save_and_load_history_with_thinking_segments():
+1 -1
View File
@@ -1,6 +1,6 @@
import pytest import pytest
from unittest.mock import patch from unittest.mock import patch
from src.models import Ticket from src.mma import Ticket
def test_ticket_priority_default(): def test_ticket_priority_default():
ticket = Ticket(id="T1", description="Test ticket") ticket = Ticket(id="T1", description="Test ticket")
+2 -1
View File
@@ -1,5 +1,6 @@
import pytest import pytest
from src.models import Persona, Ticket, WorkerContext from src.mma import Ticket, WorkerContext
from src.personas import Persona
from src import multi_agent_conductor from src import multi_agent_conductor
from src import app_controller from src import app_controller
from src import aggregate from src import aggregate
+2 -1
View File
@@ -1,6 +1,7 @@
import pytest import pytest
from src.tool_bias import ToolBiasEngine from src.tool_bias import ToolBiasEngine
from src.models import ToolPreset, Tool, BiasProfile from src.tool_bias import BiasProfile
from src.tool_presets import ToolPreset, Tool
def test_apply_semantic_nudges(): def test_apply_semantic_nudges():
engine = ToolBiasEngine() engine = ToolBiasEngine()
+2 -1
View File
@@ -2,7 +2,8 @@ import pytest
import tomli_w import tomli_w
from pathlib import Path from pathlib import Path
from src.tool_presets import ToolPresetManager from src.tool_presets import ToolPresetManager
from src.models import ToolPreset, BiasProfile, Tool from src.tool_bias import BiasProfile
from src.tool_presets import ToolPreset, Tool
from src import paths from src import paths
@pytest.fixture @pytest.fixture
+1 -1
View File
@@ -3,7 +3,7 @@ import asyncio
from src import ai_client from src import ai_client
from src import mcp_client from src import mcp_client
from src import models from src import models
from src.models import ToolPreset, Tool from src.tool_presets import ToolPreset, Tool
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
@pytest.mark.asyncio @pytest.mark.asyncio
+2 -1
View File
@@ -1,7 +1,8 @@
from datetime import datetime from datetime import datetime
# Import the real models # Import the real models
from src.models import TrackState, Metadata, Ticket from src.mma import TrackState, Ticket
from src.models import Metadata
# Import the persistence functions from project_manager # Import the persistence functions from project_manager
from src.project_manager import save_track_state, load_track_state from src.project_manager import save_track_state, load_track_state
+2 -1
View File
@@ -1,7 +1,8 @@
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone, timedelta
# Import necessary classes from models.py # Import necessary classes from models.py
from src.models import Metadata, TrackState, Ticket from src.mma import TrackState, Ticket
from src.models import Metadata
# --- Pytest Tests --- # --- Pytest Tests ---
+1 -1
View File
@@ -1,7 +1,7 @@
import pytest import pytest
from pathlib import Path from pathlib import Path
from src.workspace_manager import WorkspaceManager from src.workspace_manager import WorkspaceManager
from src.models import WorkspaceProfile from src.workspace_manager import WorkspaceProfile
from src import paths from src import paths
def test_load_all_profiles_merged(tmp_path, monkeypatch): def test_load_all_profiles_merged(tmp_path, monkeypatch):
@@ -2,7 +2,7 @@ import io
import tomllib import tomllib
import pytest import pytest
import tomli_w import tomli_w
from src.models import WorkspaceProfile from src.workspace_manager import WorkspaceProfile
def test_workspace_profile_empty_ini_content_roundtrips(): def test_workspace_profile_empty_ini_content_roundtrips():