From c6f9dc886fbc3c5cc5ee9a13b6570c2602120a83 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 7 Mar 2026 15:03:03 -0500 Subject: [PATCH] feat(controller): Integrate py_get_definition for on-demand lookup --- .../on_demand_def_lookup_20260306/plan.md | 2 +- src/app_controller.py | 7 ++ src/mcp_client.py | 2 +- tests/test_symbol_lookup.py | 75 +++++++++++++------ 4 files changed, 62 insertions(+), 24 deletions(-) diff --git a/conductor/tracks/on_demand_def_lookup_20260306/plan.md b/conductor/tracks/on_demand_def_lookup_20260306/plan.md index c4a1a87..a8cd937 100644 --- a/conductor/tracks/on_demand_def_lookup_20260306/plan.md +++ b/conductor/tracks/on_demand_def_lookup_20260306/plan.md @@ -19,7 +19,7 @@ Focus: Parse @symbol syntax from user input ## Phase 2: Definition Retrieval Focus: Use existing MCP tool to get definitions -- [ ] Task 2.1: Integrate py_get_definition +- [~] Task 2.1: Integrate py_get_definition - WHERE: `src/gui_2.py` - WHAT: Call MCP tool for each symbol - HOW: diff --git a/src/app_controller.py b/src/app_controller.py index e666fbb..9d0fe71 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -46,6 +46,13 @@ def parse_symbols(text: str) -> list[str]: """ return re.findall(r"@([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)", text) +def get_symbol_definition(symbol: str, files: list[str]) -> tuple[str, str] | None: + for file_path in files: + result = mcp_client.py_get_definition(file_path, symbol) + if 'not found' not in result.lower(): + return (file_path, result) + return None + class GenerateRequest(BaseModel): prompt: str auto_add_history: bool = True diff --git a/src/mcp_client.py b/src/mcp_client.py index 7123dc9..5c5e839 100644 --- a/src/mcp_client.py +++ b/src/mcp_client.py @@ -409,7 +409,7 @@ def py_get_definition(path: str, name: str) -> str: start = cast(int, getattr(node, "lineno")) - 1 end = cast(int, getattr(node, "end_lineno")) return "".join(lines[start:end]) - return f"ERROR: could not find definition '{name}' in {path}" + return f"ERROR: definition '{name}' not found in {path}" except Exception as e: return f"ERROR retrieving definition '{name}' from '{path}': {e}" diff --git a/tests/test_symbol_lookup.py b/tests/test_symbol_lookup.py index 94d17f8..ceade6d 100644 --- a/tests/test_symbol_lookup.py +++ b/tests/test_symbol_lookup.py @@ -1,27 +1,58 @@ -import pytest -from src.app_controller import parse_symbols +import unittest +from unittest.mock import patch, MagicMock +from src.app_controller import parse_symbols, get_symbol_definition -def test_parse_symbols_basic(): - text = "Check @MyClass and @my_func." - symbols = parse_symbols(text) - assert symbols == ["MyClass", "my_func"] +class TestSymbolLookup(unittest.TestCase): + def test_parse_symbols_basic(self): + text = "Check @MyClass and @my_func." + symbols = parse_symbols(text) + self.assertEqual(symbols, ["MyClass", "my_func"]) -def test_parse_symbols_methods(): - text = "Calling @MyClass.my_method and @AnotherClass.method_name." - symbols = parse_symbols(text) - assert symbols == ["MyClass.my_method", "AnotherClass.method_name"] + def test_parse_symbols_methods(self): + text = "Calling @MyClass.my_method and @AnotherClass.method_name." + symbols = parse_symbols(text) + self.assertEqual(symbols, ["MyClass.my_method", "AnotherClass.method_name"]) -def test_parse_symbols_no_symbols(): - text = "This string has no symbols." - symbols = parse_symbols(text) - assert symbols == [] + def test_parse_symbols_no_symbols(self): + text = "This string has no symbols." + symbols = parse_symbols(text) + self.assertEqual(symbols, []) -def test_parse_symbols_mixed(): - text = "Mixed text: @Class1, @func_2, and some text @MyClass.method." - symbols = parse_symbols(text) - assert symbols == ["Class1", "func_2", "MyClass.method"] + def test_parse_symbols_mixed(self): + text = "Mixed text: @Class1, @func_2, and some text @MyClass.method." + symbols = parse_symbols(text) + self.assertEqual(symbols, ["Class1", "func_2", "MyClass.method"]) -def test_parse_symbols_edge_cases(): - text = "@LeadingSymbol and @SymbolAtEnd" - symbols = parse_symbols(text) - assert symbols == ["LeadingSymbol", "SymbolAtEnd"] + def test_parse_symbols_edge_cases(self): + text = "@LeadingSymbol and @SymbolAtEnd" + symbols = parse_symbols(text) + self.assertEqual(symbols, ["LeadingSymbol", "SymbolAtEnd"]) + + def test_get_symbol_definition_found(self): + files = ["file1.py", "file2.py"] + symbol = "my_func" + def_content = "def my_func():\n pass" + + with patch("src.mcp_client.py_get_definition") as mock_get_def: + # First file not found, second file found + mock_get_def.side_effect = [ + "ERROR: definition 'my_func' not found in file1.py", + def_content + ] + + result = get_symbol_definition(symbol, files) + self.assertEqual(result, ("file2.py", def_content)) + self.assertEqual(mock_get_def.call_count, 2) + + def test_get_symbol_definition_not_found(self): + files = ["file1.py"] + symbol = "my_func" + + with patch("src.mcp_client.py_get_definition") as mock_get_def: + mock_get_def.return_value = "ERROR: definition 'my_func' not found in file1.py" + + result = get_symbol_definition(symbol, files) + self.assertIsNone(result) + +if __name__ == "__main__": + unittest.main()