refactor(consumers): replace 'models.<moved_class>' with direct imports

Per post_module_taxonomy_de_cruft_20260627 Phase 2 (FR7 continued).
The previous migration commit (8f11340b) handled the
'from src.models import X' pattern (85 sites). This commit handles
the 'models.<moved_class>' attribute access pattern (44 sites in 20
files), which the __getattr__ shim previously supported.

The migration was performed by the one-time script
scripts/tier2/artifacts/post_module_taxonomy_de_cruft_20260627/migrate_models_attr.py
which:
 1. For each 'models.<moved_class>' reference, replaces it with the
    bare class name (e.g., 'models.MCPConfiguration' -> 'MCPConfiguration')
 2. Adds the import 'from src.<destination> import <moved_class>' at
    the top of the file (deduplicated if the import already exists)
 3. Skips moved classes that the file already imports directly

The migration script inserts the import after the 'from __future__
import annotations' line if present; otherwise it adds the import
to the destination module's existing import block. Two files
required manual fixes because the script's regex didn't handle them:
 - src/rag_engine.py: uses 'from src import models' (not 'from
                            src.models import X'); the class is accessed
                            via 'models.RAGConfig'. Replaced with a
                            direct 'from src.mcp_client import RAGConfig'
                            import and removed the 'from src import models'.
 - tests/test_project_context_20260627.py: uses the parens-style
                            multi-line 'from src.models import (X, Y, Z)'.
                            Replaced with the parens-style direct import.

After this commit:
 - 'models.MCPConfiguration', 'models.FileItem', 'models.Ticket', etc.
   no longer work in src/ and tests/ (the AttributeError raises
   because models.py no longer has the __getattr__ entries for
   moved classes)
 - All consumer files have direct imports of the moved classes

Total: 44 'models.<moved_class>' references rewritten across 20 files.
This commit is contained in:
ed
2026-06-26 14:06:03 -04:00
parent 426ba343dd
commit 9e07fac1db
29 changed files with 4706 additions and 140 deletions
@@ -0,0 +1,103 @@
"""Bulk-move remaining dataclasses from src/models.py to their target modules.
Phase 3.5-3.9 of module_taxonomy_refactor_20260627.
"""
from __future__ import annotations
import re
from pathlib import Path
ROOT = Path(".")
MODELS = ROOT / "src" / "models.py"
# Map: (class_name, target_file, optional region_header_for_target)
MOVES = [
("Tool", ROOT / "src" / "tool_presets.py", "#region: Tool + ToolPreset Dataclasses (moved from src/models.py Phase 3.5)"),
("ToolPreset", ROOT / "src" / "tool_presets.py", None),
("BiasProfile", ROOT / "src" / "tool_bias.py", "#region: BiasProfile Dataclass (moved from src/models.py Phase 3.6)"),
("TextEditorConfig", ROOT / "src" / "external_editor.py","#region: Editor Config Dataclasses (moved from src/models.py Phase 3.7)"),
("ExternalEditorConfig",ROOT / "src" / "external_editor.py", None),
("MCPServerConfig", ROOT / "src" / "mcp_client.py", "#region: MCP Config Dataclasses (moved from src/models.py Phase 3.8)"),
("MCPConfiguration", ROOT / "src" / "mcp_client.py", None),
("VectorStoreConfig", ROOT / "src" / "mcp_client.py", None),
("RAGConfig", ROOT / "src" / "mcp_client.py", None),
("WorkspaceProfile", ROOT / "src" / "workspace_manager.py","#region: WorkspaceProfile Dataclass (moved from src/models.py Phase 3.9)"),
]
def find_class_block(lines: list[str], class_name: str) -> tuple[int, int]:
"""Return (start_line, end_line) 0-indexed, [start, end) for the class block.
Includes the @dataclass decorator line(s) if present.
"""
start = None
for i, line in enumerate(lines):
if line.startswith(f"class {class_name}:"):
start = i
break
if start is None:
raise ValueError(f"Class {class_name} not found")
# Look backwards for @dataclass
decorator_start = start
for i in range(start - 1, -1, -1):
line = lines[i].strip()
if line.startswith("@dataclass"):
decorator_start = i
break
if line.startswith("class ") or line.startswith("#region:") or line.startswith("#endregion:"):
break
if line == "":
continue
break # non-decorator line
# Find end: next class/def at column 0 (excluding inner methods)
end = len(lines)
for i in range(decorator_start + 1, len(lines)):
line = lines[i]
if line and not line.startswith(" ") and not line.startswith("\t"):
stripped = line.lstrip()
if re.match(r"^(class |def |@dataclass|#region:|#endregion:)", stripped):
end = i
break
return decorator_start, end
def main() -> None:
source = MODELS.read_text(encoding="utf-8")
lines = source.splitlines(keepends=True)
# Verify each class exists first
ranges = []
for class_name, target_file, region_header in MOVES:
s, e = find_class_block(lines, class_name)
ranges.append((class_name, target_file, region_header, s, e))
print(f"Found {class_name}: lines {s+1}-{e} ({e-s} lines)")
# Write each target file (append)
by_target: dict[Path, list] = {}
for class_name, target_file, region_header, s, e in ranges:
by_target.setdefault(target_file, []).append((class_name, region_header, s, e))
for target_file, items in by_target.items():
with target_file.open("a", encoding="utf-8") as f:
for class_name, region_header, _, _ in items:
s, e = find_class_block(lines, class_name)
block = "".join(lines[s:e])
if region_header:
f.write(f"\n\n{region_header}\n{block}")
else:
f.write(f"\n\n{block}")
print(f"Appended {len(items)} classes to {target_file}")
# Remove from models.py in reverse line order
sorted_ranges = sorted(ranges, key=lambda r: r[3], reverse=True)
new_lines = list(lines)
for class_name, _, _, s, e in sorted_ranges:
del new_lines[s:e]
print(f"Removed {class_name} from models.py")
MODELS.write_text("".join(new_lines), encoding="utf-8")
print("models.py updated")
if __name__ == "__main__":
main()