diff --git a/src/app_controller.py b/src/app_controller.py index 46d6e293..a0c05b6c 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -27,6 +27,7 @@ from src.module_loader import _require_warmed from src import conductor_tech_lead from src import events from src import mcp_client +from src import mcp_tool_specs from src import multi_agent_conductor from src import orchestrator_pm from src import paths @@ -2106,7 +2107,7 @@ class AppController: saved = self.config.get("gui", {}).get("show_windows", {}) self.show_windows = {k: saved.get(k, v) for k, v in _default_windows.items()} agent_tools_cfg = self.project.get("agent", {}).get("tools", {}) - self.ui_agent_tools = {t: agent_tools_cfg.get(t, True) for t in models.AGENT_TOOL_NAMES} + self.ui_agent_tools = {t: agent_tools_cfg.get(t, True) for t in mcp_tool_specs.tool_names()} label = self.project.get("project", {}).get("name", "") session_logger.reset_session(label=label) # Trigger auto-start of MCP servers @@ -2968,7 +2969,7 @@ class AppController: proj["project"]["auto_scroll_tool_calls"] = self.ui_auto_scroll_tool_calls proj.setdefault("gemini_cli", {})["binary_path"] = self.ui_gemini_cli_path proj.setdefault("agent", {}).setdefault("tools", {}) - for t_name in models.AGENT_TOOL_NAMES: + for t_name in mcp_tool_specs.tool_names(): proj["agent"]["tools"][t_name] = self.ui_agent_tools.get(t_name, True) self._flush_disc_entries_to_project() disc_sec = proj.setdefault("discussion", {}) @@ -3268,7 +3269,7 @@ class AppController: self.ui_auto_scroll_tool_calls = proj.get("project", {}).get("auto_scroll_tool_calls", True) self.ui_word_wrap = proj.get("project", {}).get("word_wrap", True) agent_tools_cfg = proj.get("agent", {}).get("tools", {}) - self.ui_agent_tools = {t: agent_tools_cfg.get(t, True) for t in models.AGENT_TOOL_NAMES} + self.ui_agent_tools = {t: agent_tools_cfg.get(t, True) for t in mcp_tool_specs.tool_names()} # MMA Tracks self.tracks = project_manager.get_all_tracks(self.active_project_root) # Restore MMA state diff --git a/src/models.py b/src/models.py index 4a097324..683dae34 100644 --- a/src/models.py +++ b/src/models.py @@ -28,7 +28,7 @@ Thread Safety: Configuration Integration: - load_config() / save_config() read/write the global config.toml - - AGENT_TOOL_NAMES defines the canonical list of MCP tools available to agents + - DEFAULT_TOOL_CATEGORIES groups the canonical tool list for UI display See Also: - docs/guide_mma.md for MMA orchestration documentation - src/dag_engine.py for TrackDAG and ExecutionEngine @@ -117,67 +117,15 @@ from src.project_files import ( # src.models; if we eagerly imported Tool from src.tool_presets, the # cycle would deadlock on initial load). Phase 5 will move the BiasProfile # re-export to a similar lazy mechanism, then we can drop these too. - - #region: Constants -# PROVIDERS is the source of truth in src/ai_client.py (per the -# follow-up track's Naming Convention HARD RULE). Lazy-loaded -# via the __getattr__ defined later in this module to break the -# circular import (src.ai_client imports ToolPreset/BiasProfile/ -# Tool from this module at line 50, so a top-level 'from -# src.ai_client import PROVIDERS' here would deadlock). The -# audit script scripts/audit_providers_source_of_truth.py -# verifies PROVIDERS is declared in src/ai_client.py and not here. - -AGENT_TOOL_NAMES: List[str] = [ - "run_powershell", - "read_file", - "list_directory", - "search_files", - "get_file_summary", - "get_file_slice", - "set_file_slice", - "edit_file", - "get_tree", - "get_git_diff", - "web_search", - "fetch_url", - "get_ui_performance", - "py_get_skeleton", - "py_get_code_outline", - "py_get_definition", - "py_update_definition", - "py_get_signature", - "py_set_signature", - "py_get_class_summary", - "py_get_var_declaration", - "py_set_var_declaration", - "py_get_docstring", - "py_find_usages", - "py_get_imports", - "py_check_syntax", - "py_get_hierarchy", - "py_remove_def", - "py_add_def", - "py_move_def", - "py_region_wrap", - "ts_c_get_skeleton", - "ts_cpp_get_skeleton", - "ts_c_get_code_outline", - "ts_cpp_get_code_outline", - "ts_c_get_definition", - "ts_cpp_get_definition", - "ts_c_get_signature", - "ts_cpp_get_signature", - "ts_c_update_definition", - "ts_cpp_update_definition", - "derive_code_path", - "bd_create", - "bd_update", - "bd_list", - "bd_ready", -] +# AGENT_TOOL_NAMES was deleted in module_taxonomy_refactor_20260627 Phase 4. +# It was a hardcoded snapshot of mcp_tool_specs.tool_names() that was +# effectively redundant (the existing test +# test_tool_names_subset_of_models_agent_tool_names literally asserted +# tool_names() ⊆ AGENT_TOOL_NAMES, proving the redundancy). Consumers +# must now use mcp_tool_specs.tool_names() directly. See the v2 spec +# for the migration rationale. DEFAULT_TOOL_CATEGORIES: Dict[str, List[str]] = { "General": ["read_file", "list_directory", "search_files", "get_tree", "get_file_summary"], diff --git a/tests/test_arch_boundary_phase2.py b/tests/test_arch_boundary_phase2.py index 235e9cdf..e04e4c2f 100644 --- a/tests/test_arch_boundary_phase2.py +++ b/tests/test_arch_boundary_phase2.py @@ -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().""" diff --git a/tests/test_mcp_tool_specs.py b/tests/test_mcp_tool_specs.py index 2212d5f5..79ccf5e2 100644 --- a/tests/test_mcp_tool_specs.py +++ b/tests/test_mcp_tool_specs.py @@ -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