Merge origin/tier2/module_taxonomy_refactor_20260627: bring in v2 SHIPPED work

Per post_module_taxonomy_de_cruft_20260627 Phase 0 prerequisite.
Master is at 6344b49f (pre-merge of v2 SHIPPED). This merge brings in
the 18 v2 SHIPPED commits that define the destination modules
(src.mma, src/project.py, src/project_files.py, src.tool_presets,
src.tool_bias, src.external_editor, src.personas,
src.workspace_manager, src.mcp_client) needed by the Phase 2
consumer migration in commit 8f11340b.

Conflicts resolved (all were import-block re-orderings between my
migration's update and v2 SHIPPED's update of the same files):
 - src/external_editor.py: took v2 SHIPPED version (class definitions
                                    + the no-alias import pattern)
 - src/personas.py: took v2 SHIPPED version
 - src/tool_bias.py: took v2 SHIPPED version
 - src/tool_presets.py: took v2 SHIPPED version
 - src/workspace_manager.py: took v2 SHIPPED version
 - src/ai_client.py: took v2 SHIPPED version (removes the 'as _FIC'
                              alias; uses 'from src.project_files import
                              FileItem' directly per the v2 SHIPPED style)
 - conductor/tracks/module_taxonomy_refactor_20260627/spec.md: took
                              HEAD version (my Phase 1 VC2 + VC10
                              corrections; the v2 SHIPPED version was
                              the pre-correction spec)
This commit is contained in:
ed
2026-06-26 13:51:05 -04:00
65 changed files with 4959 additions and 2463 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ from src.result_types import Result
from src.openai_compatible import NormalizedResponse, OpenAICompatibleRequest
from src.openai_schemas import UsageStats
from src.ai_client import run_with_tool_loop
from src.vendor_capabilities import VendorCapabilities
from src.ai_client import VendorCapabilities
@pytest.fixture
def caps() -> VendorCapabilities:
+1 -1
View File
@@ -11,7 +11,7 @@ from src.openai_compatible import NormalizedResponse, OpenAICompatibleRequest
from src.openai_schemas import UsageStats
from src.ai_client import run_with_tool_loop
from src.result_types import Result
from src.vendor_capabilities import VendorCapabilities
from src.ai_client import VendorCapabilities
def _make_normalized_response(text: str = "ok", tool_calls: list[dict[str, Any]] | None = None) -> NormalizedResponse:
return NormalizedResponse(
+1 -1
View File
@@ -9,7 +9,7 @@ from unittest.mock import MagicMock, patch
from src.openai_compatible import NormalizedResponse
from src.openai_schemas import UsageStats
from src.ai_client import run_with_tool_loop
from src.vendor_capabilities import VendorCapabilities
from src.ai_client import VendorCapabilities
def _make_normalized_response(text: str = "ok", tool_calls: list[dict[str, Any]] | None = None) -> NormalizedResponse:
return NormalizedResponse(
+1 -1
View File
@@ -214,7 +214,7 @@ def test_fr3_minimax_thinking_in_returned_text() -> None:
from src import openai_compatible as oc
from src import provider_state
from src.provider_state import ProviderHistory
from src.vendor_capabilities import register, VendorCapabilities
from src.ai_client import register, VendorCapabilities
register(VendorCapabilities(vendor="minimax", model="MiniMax-M2.7", reasoning=True))
ai_client._model = "MiniMax-M2.7"
+8 -7
View File
@@ -13,24 +13,25 @@ class TestArchBoundaryPhase2(unittest.TestCase):
def test_toml_exposes_all_dispatch_tools(self) -> None:
"""manual_slop.toml [agent.tools] must list every tool in mcp_client.dispatch()."""
from src import models
from src import mcp_tool_specs
# We check the tool names in the source of mcp_client.dispatch
import inspect
import src.mcp_client as mcp
source = inspect.getsource(mcp.dispatch)
# This is a bit dynamic, but we can check if it covers our core tool names
for tool in models.AGENT_TOOL_NAMES:
for tool in mcp_tool_specs.tool_names():
if tool not in ("set_file_slice", "py_update_definition", "py_set_signature", "py_set_var_declaration"):
# Non-mutating tools should definitely be handled
pass
def test_toml_mutating_tools_disabled_by_default(self) -> None:
"""Verify that the core set of read-only tools is present."""
from src.models import AGENT_TOOL_NAMES
from src import mcp_tool_specs
tool_names = mcp_tool_specs.tool_names()
# Our architecture now uses a fixed set of high-signal tools
self.assertIn("read_file", AGENT_TOOL_NAMES)
self.assertIn("list_directory", AGENT_TOOL_NAMES)
self.assertIn("py_get_skeleton", AGENT_TOOL_NAMES)
self.assertIn("read_file", tool_names)
self.assertIn("list_directory", tool_names)
self.assertIn("py_get_skeleton", tool_names)
def test_mcp_client_dispatch_completeness(self) -> None:
"""Verify that all tools in tool_schemas are handled by dispatch()."""
+131
View File
@@ -0,0 +1,131 @@
"""Tests for scripts/audit_imports.py (post-2026-06-27).
Verifies:
1. The script flags `from X import Y as _Y` _PREFIX aliasing.
2. The script flags `from X import Y` inside a function body (local import).
3. The script ALLOWS local imports inside `try/except ImportError:` (optional deps).
4. The script respects --strict exit code.
5. The script detects repeated .from_dict() calls in the same expression (info only).
"""
from __future__ import annotations
import subprocess
import sys
import tempfile
import textwrap
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
SCRIPT = REPO_ROOT / "scripts" / "audit_imports.py"
def _run_audit(*args: str) -> subprocess.CompletedProcess:
return subprocess.run(
[sys.executable, str(SCRIPT), *args],
capture_output=True,
text=True,
cwd=REPO_ROOT,
timeout=60,
)
@pytest.fixture
def fixture_file(tmp_path: Path):
"""Create a temporary .py file with known import patterns for testing."""
def _make(content: str, name: str = "test_mod.py") -> Path:
f = tmp_path / name
f.write_text(textwrap.dedent(content), encoding="utf-8")
return f
return _make
def test_script_exists():
assert SCRIPT.is_file(), f"audit_imports.py missing at {SCRIPT}"
def test_no_local_imports_in_clean_file(fixture_file):
f = fixture_file(
"""
from pathlib import Path
from typing import Optional
def clean():
return 42
"""
)
result = _run_audit("--root", str(f.parent), "--strict")
assert result.returncode == 0, (
f"clean file should pass --strict; got {result.returncode}\nstdout: {result.stdout}"
)
def test_local_import_flagged_as_strict(fixture_file):
f = fixture_file(
"""
def bad():
from pathlib import Path
return Path
"""
)
result = _run_audit("--root", str(f.parent), "--strict")
assert result.returncode == 1, "local import should be strict violation"
assert "LOCAL_IMPORT" in result.stdout
assert "bad()" in result.stdout
def test_prefix_aliasing_flagged_as_strict(fixture_file):
f = fixture_file(
"""
from pathlib import Path as _P
def use():
return _P
"""
)
result = _run_audit("--root", str(f.parent), "--strict")
assert result.returncode == 1, "_PREFIX alias should be strict violation"
assert "PREFIX_ALIAS" in result.stdout
assert "_P" in result.stdout
def test_optional_import_in_try_except_allowed(fixture_file):
"""A `from X import Y` inside `try/except ImportError:` is ALLOWED per §17.9a
(canonical "optional dependency" pattern)."""
f = fixture_file(
"""
def lazy_load():
try:
from optional_dep import thing
except ImportError:
thing = None
return thing
"""
)
result = _run_audit("--root", str(f.parent), "--strict")
assert result.returncode == 0, (
f"optional-import try/except should pass --strict; got {result.returncode}\nstdout: {result.stdout}\nstderr: {result.stderr}"
)
def test_repeated_from_dict_is_info_only(fixture_file):
"""Repeated .from_dict() in the same expression is reported but NOT strict (§17.9c)."""
f = fixture_file(
"""
from src.type_aliases import Foo
def use(d):
return Foo.from_dict(d).x + Foo.from_dict(d).y
"""
)
result = _run_audit("--root", str(f.parent))
# Info finding, not strict
assert "REPEATED_FROM_DICT" in result.stdout
result_strict = _run_audit("--root", str(f.parent), "--strict")
# REPEATED_FROM_DICT should NOT cause --strict to exit 1
assert result_strict.returncode == 0, "REPEATED_FROM_DICT is info-only; should not fail --strict"
def test_script_runs_on_real_src():
"""Smoke test: the script runs on the actual src/ tree without erroring."""
result = _run_audit("--root", "src")
# Return code is 1 if strict violations exist; we only check it RAN
assert "STRICT" in result.stdout or "INFO" in result.stdout
+1 -1
View File
@@ -1,4 +1,4 @@
from src.command_palette import Command, ScoredCommand, fuzzy_match
from src.commands import Command, ScoredCommand, fuzzy_match
def _cmd(id: str, title: str) -> Command:
@@ -1,26 +1,22 @@
"""Tests that src/commands.py has NO top-level src.command_palette import.
"""Tests for the post-Phase 1.3 architecture.
Per spec.md:2.2 Layer 1, the main thread's import chain must not include
heavy feature-gated modules. src.command_palette (~244ms) is warmed on
AppController's _io_pool and accessed via _require_warmed at use sites.
Per module_taxonomy_refactor_20260627 Phase 1.3, src/command_palette.py was
deleted and its content split by responsibility:
- Command / ScoredCommand / CommandRegistry / fuzzy_match (data/ops) -> src/commands.py
- render_palette_modal (view) -> src/gui_2.py
src/commands.py is a particularly tricky case: it has 32 `@registry.register`
decorators on its command functions. The naive "drop the top-level import"
approach would break the decorators (they need a registry at module load time).
src/commands.py is a thin data module and can be imported eagerly (no
require_warmed lazy load is needed; the original lazy-load pattern in
startup_speedup_20260606 was specifically to defer the heavy src/command_palette
which pulled in imgui at module load).
Solution: a lazy registry proxy. The @registry.register decorator becomes a
no-op that queues the function; the real CommandRegistry is created on first
attribute access to the proxy (e.g. registry.all, registry.get). The 32
decorated functions get registered at first use, which is the user's first
Ctrl+Shift+P press (or any other access to the palette).
These tests run in a fresh subprocess to ensure no warmup state leaks
from the test runner. We assert:
- `src.command_palette` is NOT in `sys.modules` after `import src.commands`
- The lazy registry proxy works: `from src.commands import registry` succeeds
- Accessing `registry.all()` triggers the real CommandRegistry and
returns all 32 registered commands
- The static audit script reports NO new violation from src/commands.py
These tests run in fresh subprocesses to ensure no warmup state leaks from
the test runner. We assert:
- src/commands imports cleanly and exposes Command + CommandRegistry
- src/gui_2 exposes render_palette_modal
- src/commands does NOT import gui_2 at module level (avoids circular)
- The static audit detects no top-level command_palette import (since the
module no longer exists)
"""
import subprocess
@@ -42,77 +38,63 @@ def _run_in_subprocess(snippet: str) -> subprocess.CompletedProcess:
)
def test_commands_does_not_import_command_palette_at_module_level() -> None:
def test_commands_exposes_command_and_registry() -> None:
res = _run_in_subprocess("""
import sys
import src.commands
print('src.command_palette' in sys.modules)
""")
assert res.returncode == 0, f"stderr: {res.stderr}"
assert res.stdout.strip() == "False", f"src.commands triggered src.command_palette import: {res.stdout}"
def test_commands_lazy_registry_proxies_to_real_registry() -> None:
"""Accessing registry.all() should trigger real init and return registered commands."""
res = _run_in_subprocess("""
from src.commands import registry
# Access .all() triggers real CommandRegistry creation
all_cmds = registry.all()
print(len(list(all_cmds)))
# After access, src.command_palette SHOULD be in sys.modules
import sys
print('src.command_palette' in sys.modules)
from src.commands import Command, CommandRegistry, fuzzy_match, ScoredCommand
r = CommandRegistry()
def my_cmd(app): pass
r.register(my_cmd)
print(len(r.all()))
print(r.all()[0].id)
""")
assert res.returncode == 0, f"stderr: {res.stderr}"
lines = res.stdout.strip().splitlines()
# Should have at least 32 commands registered (matches the 32 @registry.register)
assert int(lines[0]) >= 32, f"Expected >=32 commands, got {lines[0]}"
assert lines[1] == "True", f"src.command_palette should be loaded after registry access, got {lines[1]}"
assert lines[0] == "1"
assert lines[1] == "my_cmd"
def test_commands_register_decorator_is_lazy() -> None:
"""The @registry.register decorator should NOT trigger command_palette import at module load."""
def test_gui_2_exposes_render_palette_modal() -> None:
res = _run_in_subprocess("""
# Fresh subprocess, just import commands
import sys
import src.commands
# Verify decorator ran but did not trigger command_palette
# (the lazy proxy just queues; real init is deferred)
print('src.command_palette' in sys.modules)
# Verify the function references still exist
from src.commands import toggle_command_palette
print(callable(toggle_command_palette))
from src.gui_2 import render_palette_modal
print(callable(render_palette_modal))
""")
assert res.returncode == 0, f"stderr: {res.stderr}"
lines = res.stdout.strip().splitlines()
assert lines[0] == "False", f"Decorator should not trigger command_palette, got {lines[0]}"
assert lines[1] == "True", f"toggle_command_palette should be a callable, got {lines[1]}"
assert res.stdout.strip() == "True"
def test_audit_main_thread_imports_sees_no_new_violation_from_commands() -> None:
"""Run the static audit and check that src/commands.py contributes no
new command_palette violations.
def test_commands_does_not_import_gui_2_at_module_level() -> None:
"""src/commands is imported by src/commands registration sites and by
gui_2.render_palette_modal. To avoid a circular import, commands.py must
NOT import gui_2 at the top of the file. (TYPE_CHECKING imports are
allowed because they don't execute at runtime.)
"""
res = _run_in_subprocess("""
import ast
from pathlib import Path
root = Path('.').resolve()
commands_path = root / 'src' / 'commands.py'
tree = ast.parse(commands_path.read_text(encoding='utf-8'))
heavy = ['src.command_palette', 'command_palette']
commands_path = Path('src') / 'commands.py'
source = commands_path.read_text(encoding='utf-8')
tree = ast.parse(source)
for node in tree.body:
if isinstance(node, ast.Import):
for alias in node.names:
for h in heavy:
if alias.name == h or alias.name.startswith(h + '.'):
print('VIOLATION:', alias.name)
elif isinstance(node, ast.ImportFrom):
if node.module:
for h in heavy:
if node.module == h or node.module.startswith(h + '.'):
print('VIOLATION:', node.module)
if isinstance(node, (ast.Import, ast.ImportFrom)):
mod = getattr(node, 'module', None) or (node.names[0].name if node.names else '')
if mod and ('gui_2' in mod or mod.endswith('gui_2')):
print('VIOLATION:', mod)
print('OK')
""")
assert res.returncode == 0, f"stderr: {res.stderr}"
assert "OK" in res.stdout
assert "VIOLATION" not in res.stdout
assert "OK" in res.stdout
def test_command_palette_module_no_longer_exists() -> None:
"""src/command_palette.py was deleted in Phase 1.3; this is a regression guard."""
res = _run_in_subprocess("""
import importlib
try:
importlib.import_module('src.command_palette')
print('EXISTS')
except ModuleNotFoundError:
print('NOT_FOUND')
""")
assert res.returncode == 0, f"stderr: {res.stderr}"
assert res.stdout.strip() == "NOT_FOUND"
+2 -2
View File
@@ -2,8 +2,8 @@ import pytest
import tempfile
import os
from pathlib import Path
from src.diff_viewer import (
parse_diff, DiffFile, DiffHunk, parse_hunk_header,
from src.gui_2 import (
parse_diff, DiffFile, DiffHunk, parse_hunk_header,
get_line_color, apply_patch_to_file
)
+1 -1
View File
@@ -23,7 +23,7 @@ def test_send_grok_uses_xai_endpoint(monkeypatch: pytest.MonkeyPatch) -> None:
assert mock_client.chat.completions.create.called
def test_grok_2_vision_supports_image() -> None:
from src.vendor_capabilities import get_capabilities
from src.ai_client import get_capabilities
caps = get_capabilities("grok", "grok-2-vision")
assert caps.vision is True
+1 -1
View File
@@ -62,7 +62,7 @@ def test_llama_model_discovery_unions_ollama_and_openrouter() -> None:
assert "llama-3.3-70b-specdec" in models
def test_llama_3_2_vision_vision_capability() -> None:
from src.vendor_capabilities import get_capabilities
from src.ai_client import get_capabilities
caps = get_capabilities("llama", "llama-3.2-11b-vision-preview")
assert caps.vision is True
-10
View File
@@ -7,7 +7,6 @@ Phase 1 of any_type_componentization_20260621. Verifies:
- get_tool_schemas() returns the expected list
- ToolParameter / ToolSpec dataclasses have correct frozen=True semantics
- to_dict() round-trip preserves the legacy dict shape
- Cross-module invariant: tool_names() == models.AGENT_TOOL_NAMES subset
CONVENTION: 1-space indentation. NO COMMENTS.
"""
@@ -15,7 +14,6 @@ from __future__ import annotations
import pytest
from src import mcp_tool_specs
from src import models
EXPECTED_TOOLS: set[str] = {
@@ -107,14 +105,6 @@ def test_tool_parameter_to_dict_includes_enum() -> None:
assert 'before' in d['enum']
def test_tool_names_subset_of_models_agent_tool_names() -> None:
"""Cross-module invariant: every MCP tool is also an agent tool."""
native_names = mcp_tool_specs.tool_names()
agent_names = set(models.AGENT_TOOL_NAMES)
missing_in_agent = native_names - agent_names
assert not missing_in_agent, f"Native tools not in AGENT_TOOL_NAMES: {missing_in_agent}"
def test_register_idempotent_replaces_existing() -> None:
"""register() should overwrite (idempotent for hot-reload scenarios)."""
from src.mcp_tool_specs import ToolSpec, ToolParameter, register
+2 -2
View File
@@ -42,7 +42,7 @@ def test_minimax_reasoning_extractor_used_when_caps_reasoning_true() -> None:
def _fake_send(client, request, *, capabilities):
captured_kwargs.append({"model": request.model})
return MagicMock(text="ok", tool_calls=[], usage=UsageStats(input_tokens=0, output_tokens=0, cache_read_tokens=0, cache_creation_tokens=0), raw_response=None)
from src.vendor_capabilities import register, VendorCapabilities
from src.ai_client import register, VendorCapabilities
register(VendorCapabilities(vendor='minimax', model='MiniMax-M2.5', reasoning=True))
with patch.object(oc, "send_openai_compatible", side_effect=_fake_send), \
patch("src.ai_client._ensure_minimax_client", return_value=MagicMock()), \
@@ -54,7 +54,7 @@ def test_minimax_reasoning_extractor_omitted_when_caps_reasoning_false() -> None
"""caps.reasoning=False (M2/M2.1) should NOT pass the reasoning_extractor (avoid useless getattr)."""
from src import openai_compatible as oc
from src.openai_schemas import UsageStats
from src.vendor_capabilities import register, VendorCapabilities
from src.ai_client import register, VendorCapabilities
register(VendorCapabilities(vendor='minimax', model='MiniMax-M2', reasoning=False))
captured_kwargs: list[dict] = []
def _fake_send(client, request, *, capabilities):
+2 -2
View File
@@ -46,7 +46,7 @@ def test_models_can_still_call_save_config_after_lazy_load() -> None:
"theme": {"palette": "solarized_dark", "font_size": 16.0},
}
try:
src.models._save_config_to_disk(config)
src.models.save_config_to_disk(config)
except Exception as e:
pytest.fail(f"save_config raised after lazy tomli_w: {e}")
finally:
@@ -63,7 +63,7 @@ def test_save_config_uses_tomli_w_on_demand() -> None:
assert "tomli_w" not in sys.modules
# Call save_config - this should trigger the import
try:
src.models._save_config_to_disk({"test_key": "test_value"})
src.models.save_config_to_disk({"test_key": "test_value"})
except Exception:
# We don't care if the save itself fails; we just want to verify
# the import happened.
+1 -1
View File
@@ -6,7 +6,7 @@ from src.openai_compatible import (
send_openai_compatible,
)
from src.openai_schemas import ChatMessage
from src.vendor_capabilities import VendorCapabilities, register
from src.ai_client import VendorCapabilities, register
@pytest.fixture
def caps() -> VendorCapabilities:
+6 -6
View File
@@ -1,13 +1,13 @@
import pytest
from src.vendor_capabilities import VendorCapabilities, get_capabilities, register
from src.ai_client import VendorCapabilities, get_capabilities, register
@pytest.fixture(autouse=True)
def _clean_registry():
import src.vendor_capabilities
snapshot = src.vendor_capabilities._REGISTRY.copy()
import src.ai_client as _ai
snapshot = _ai._VENDOR_REGISTRY.copy()
yield
src.vendor_capabilities._REGISTRY.clear()
src.vendor_capabilities._REGISTRY.update(snapshot)
_ai._VENDOR_REGISTRY.clear()
_ai._VENDOR_REGISTRY.update(snapshot)
def test_registry_lookup_known_model():
caps = VendorCapabilities(
@@ -217,6 +217,6 @@ def test_v2_capability_badge_helper_skips_disabled_fields() -> None:
a live context, but we can verify the helper is a no-op on
the no-cap case.)"""
from src.gui_2 import _render_v2_capability_badges
from src.vendor_capabilities import VendorCapabilities
from src.ai_client import VendorCapabilities
empty_caps = VendorCapabilities(vendor='test', model='empty')
_render_v2_capability_badges(empty_caps)
+2 -1
View File
@@ -1,4 +1,5 @@
from src.vendor_state import get_vendor_state, VendorMetric
from src.gui_2 import _get_vendor_state_metrics as get_vendor_state
from src.ai_client import VendorMetric
class _StubTT:
def __init__(self, used=0, limit=0, cache_hits=0, cache_misses=0):