tunacode-cli 0.0.23__tar.gz → 0.0.24__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tunacode-cli might be problematic. Click here for more details.

Files changed (100) hide show
  1. {tunacode_cli-0.0.23/src/tunacode_cli.egg-info → tunacode_cli-0.0.24}/PKG-INFO +20 -1
  2. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/README.md +18 -0
  3. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/pyproject.toml +2 -1
  4. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/cli/main.py +4 -0
  5. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/cli/repl.py +9 -2
  6. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/constants.py +1 -1
  7. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24/src/tunacode_cli.egg-info}/PKG-INFO +20 -1
  8. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode_cli.egg-info/requires.txt +1 -0
  9. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/tests/test_architect_integration.py +22 -46
  10. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/tests/test_architect_simple.py +5 -37
  11. tunacode_cli-0.0.24/tests/test_fast_glob_search.py +111 -0
  12. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/tests/test_orchestrator_planning_visibility.py +23 -15
  13. tunacode_cli-0.0.24/tests/test_react_thoughts.py +92 -0
  14. tunacode_cli-0.0.23/tests/test_fast_glob_search.py +0 -191
  15. tunacode_cli-0.0.23/tests/test_react_thoughts.py +0 -149
  16. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/CLAUDE.md +0 -0
  17. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/LICENSE +0 -0
  18. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/MANIFEST.in +0 -0
  19. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/TUNACODE.md +0 -0
  20. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/setup.cfg +0 -0
  21. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/setup.py +0 -0
  22. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/__init__.py +0 -0
  23. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/cli/__init__.py +0 -0
  24. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/cli/commands.py +0 -0
  25. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/cli/textual_app.py +0 -0
  26. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/cli/textual_bridge.py +0 -0
  27. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/configuration/__init__.py +0 -0
  28. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/configuration/defaults.py +0 -0
  29. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/configuration/models.py +0 -0
  30. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/configuration/settings.py +0 -0
  31. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/context.py +0 -0
  32. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/__init__.py +0 -0
  33. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/agents/__init__.py +0 -0
  34. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/agents/main.py +0 -0
  35. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/agents/orchestrator.py +0 -0
  36. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/agents/planner_schema.py +0 -0
  37. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/agents/readonly.py +0 -0
  38. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/background/__init__.py +0 -0
  39. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/background/manager.py +0 -0
  40. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/llm/__init__.py +0 -0
  41. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/llm/planner.py +0 -0
  42. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/setup/__init__.py +0 -0
  43. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/setup/agent_setup.py +0 -0
  44. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/setup/base.py +0 -0
  45. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/setup/config_setup.py +0 -0
  46. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/setup/coordinator.py +0 -0
  47. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/setup/environment_setup.py +0 -0
  48. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/setup/git_safety_setup.py +0 -0
  49. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/state.py +0 -0
  50. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/core/tool_handler.py +0 -0
  51. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/exceptions.py +0 -0
  52. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/prompts/system.md +0 -0
  53. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/prompts/system.txt +0 -0
  54. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/py.typed +0 -0
  55. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/services/__init__.py +0 -0
  56. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/services/mcp.py +0 -0
  57. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/setup.py +0 -0
  58. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/tools/__init__.py +0 -0
  59. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/tools/base.py +0 -0
  60. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/tools/bash.py +0 -0
  61. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/tools/grep.py +0 -0
  62. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/tools/read_file.py +0 -0
  63. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/tools/run_command.py +0 -0
  64. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/tools/update_file.py +0 -0
  65. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/tools/write_file.py +0 -0
  66. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/types.py +0 -0
  67. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/ui/__init__.py +0 -0
  68. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/ui/completers.py +0 -0
  69. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/ui/console.py +0 -0
  70. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/ui/constants.py +0 -0
  71. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/ui/decorators.py +0 -0
  72. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/ui/input.py +0 -0
  73. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/ui/keybindings.py +0 -0
  74. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/ui/lexers.py +0 -0
  75. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/ui/output.py +0 -0
  76. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/ui/panels.py +0 -0
  77. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/ui/prompt_manager.py +0 -0
  78. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/ui/tool_ui.py +0 -0
  79. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/ui/validators.py +0 -0
  80. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/utils/__init__.py +0 -0
  81. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/utils/bm25.py +0 -0
  82. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/utils/diff_utils.py +0 -0
  83. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/utils/file_utils.py +0 -0
  84. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/utils/import_cache.py +0 -0
  85. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/utils/ripgrep.py +0 -0
  86. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/utils/system.py +0 -0
  87. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/utils/text_utils.py +0 -0
  88. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode/utils/user_configuration.py +0 -0
  89. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode_cli.egg-info/SOURCES.txt +0 -0
  90. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
  91. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
  92. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/src/tunacode_cli.egg-info/top_level.txt +0 -0
  93. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/tests/test_agent_initialization.py +0 -0
  94. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/tests/test_background_manager.py +0 -0
  95. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/tests/test_config_setup_async.py +0 -0
  96. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/tests/test_file_reference_expansion.py +0 -0
  97. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/tests/test_json_tool_parsing.py +0 -0
  98. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/tests/test_orchestrator_file_references.py +0 -0
  99. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/tests/test_orchestrator_import.py +0 -0
  100. {tunacode_cli-0.0.23 → tunacode_cli-0.0.24}/tests/test_update_command.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.23
3
+ Version: 0.0.24
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License-Expression: MIT
@@ -30,6 +30,7 @@ Requires-Dist: black; extra == "dev"
30
30
  Requires-Dist: flake8; extra == "dev"
31
31
  Requires-Dist: isort; extra == "dev"
32
32
  Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: pytest-asyncio; extra == "dev"
33
34
  Requires-Dist: pytest-cov; extra == "dev"
34
35
  Requires-Dist: textual-dev; extra == "dev"
35
36
  Dynamic: license-file
@@ -131,6 +132,24 @@ Dynamic: license-file
131
132
  pip install tunacode-cli
132
133
  ```
133
134
 
135
+ #### Development Installation
136
+
137
+ ```bash
138
+ # Clone the repository
139
+ git clone https://github.com/larock22/tunacode.git
140
+ cd tunacode
141
+
142
+ # Run the setup script
143
+ ./scripts/setup_dev_env.sh
144
+
145
+ # Or manually:
146
+ python3 -m venv venv
147
+ source venv/bin/activate
148
+ pip install -e ".[dev]"
149
+ ```
150
+
151
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed development setup.
152
+
134
153
  #### One-line Install (Linux/macOS)
135
154
 
136
155
  ```bash
@@ -95,6 +95,24 @@
95
95
  pip install tunacode-cli
96
96
  ```
97
97
 
98
+ #### Development Installation
99
+
100
+ ```bash
101
+ # Clone the repository
102
+ git clone https://github.com/larock22/tunacode.git
103
+ cd tunacode
104
+
105
+ # Run the setup script
106
+ ./scripts/setup_dev_env.sh
107
+
108
+ # Or manually:
109
+ python3 -m venv venv
110
+ source venv/bin/activate
111
+ pip install -e ".[dev]"
112
+ ```
113
+
114
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed development setup.
115
+
98
116
  #### One-line Install (Linux/macOS)
99
117
 
100
118
  ```bash
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tunacode-cli"
7
- version = "0.0.23"
7
+ version = "0.0.24"
8
8
  description = "Your agentic CLI developer."
9
9
  keywords = ["cli", "agent", "development", "automation"]
10
10
  readme = "README.md"
@@ -42,6 +42,7 @@ dev = [
42
42
  "flake8",
43
43
  "isort",
44
44
  "pytest",
45
+ "pytest-asyncio",
45
46
  "pytest-cov",
46
47
  "textual-dev",
47
48
  ]
@@ -14,6 +14,7 @@ from tunacode.core.state import StateManager
14
14
  from tunacode.setup import setup
15
15
  from tunacode.ui import console as ui
16
16
  from tunacode.utils.system import check_for_updates
17
+ from tunacode.exceptions import UserAbortError
17
18
 
18
19
  app_settings = ApplicationSettings()
19
20
  app = typer.Typer(help="🐟 TunaCode - Your AI-powered development assistant")
@@ -49,6 +50,9 @@ def main(
49
50
  try:
50
51
  await setup(run_setup, state_manager, cli_config)
51
52
  await repl(state_manager)
53
+ except (KeyboardInterrupt, UserAbortError):
54
+ update_task.cancel()
55
+ return
52
56
  except Exception as e:
53
57
  from tunacode.exceptions import ConfigurationError
54
58
 
@@ -282,6 +282,7 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
282
282
 
283
283
  async def repl(state_manager: StateManager):
284
284
  action = None
285
+ ctrl_c_pressed = False
285
286
 
286
287
  # Professional startup information
287
288
  await ui.muted(f"• Model: {state_manager.session.current_model}")
@@ -294,12 +295,18 @@ async def repl(state_manager: StateManager):
294
295
  while True:
295
296
  try:
296
297
  line = await ui.multiline_input(state_manager, _command_registry)
297
- except (EOFError, KeyboardInterrupt):
298
- break
298
+ except UserAbortError:
299
+ if ctrl_c_pressed:
300
+ break
301
+ ctrl_c_pressed = True
302
+ await ui.warning("Hit Ctrl+C again to exit")
303
+ continue
299
304
 
300
305
  if not line:
301
306
  continue
302
307
 
308
+ ctrl_c_pressed = False
309
+
303
310
  if line.lower() in ["exit", "quit"]:
304
311
  break
305
312
 
@@ -7,7 +7,7 @@ Centralizes all magic strings, UI text, error messages, and application constant
7
7
 
8
8
  # Application info
9
9
  APP_NAME = "TunaCode"
10
- APP_VERSION = "0.0.23"
10
+ APP_VERSION = "0.0.24"
11
11
 
12
12
  # File patterns
13
13
  GUIDE_FILE_PATTERN = "{name}.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.23
3
+ Version: 0.0.24
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License-Expression: MIT
@@ -30,6 +30,7 @@ Requires-Dist: black; extra == "dev"
30
30
  Requires-Dist: flake8; extra == "dev"
31
31
  Requires-Dist: isort; extra == "dev"
32
32
  Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: pytest-asyncio; extra == "dev"
33
34
  Requires-Dist: pytest-cov; extra == "dev"
34
35
  Requires-Dist: textual-dev; extra == "dev"
35
36
  Dynamic: license-file
@@ -131,6 +132,24 @@ Dynamic: license-file
131
132
  pip install tunacode-cli
132
133
  ```
133
134
 
135
+ #### Development Installation
136
+
137
+ ```bash
138
+ # Clone the repository
139
+ git clone https://github.com/larock22/tunacode.git
140
+ cd tunacode
141
+
142
+ # Run the setup script
143
+ ./scripts/setup_dev_env.sh
144
+
145
+ # Or manually:
146
+ python3 -m venv venv
147
+ source venv/bin/activate
148
+ pip install -e ".[dev]"
149
+ ```
150
+
151
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed development setup.
152
+
134
153
  #### One-line Install (Linux/macOS)
135
154
 
136
155
  ```bash
@@ -10,5 +10,6 @@ black
10
10
  flake8
11
11
  isort
12
12
  pytest
13
+ pytest-asyncio
13
14
  pytest-cov
14
15
  textual-dev
@@ -4,20 +4,21 @@
4
4
  import asyncio
5
5
  import sys
6
6
  import os
7
+ import pytest
7
8
 
8
9
  # Add src to path
9
10
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../src'))
10
11
 
11
12
 
13
+ @pytest.mark.asyncio
12
14
  async def test_orchestrator_planning():
13
15
  """Test that orchestrator actually creates and shows a plan."""
14
- print("\n[TEST] Testing orchestrator planning output...")
15
-
16
16
  try:
17
17
  from tunacode.core.agents.orchestrator import OrchestratorAgent
18
18
  from tunacode.core.state import StateManager
19
19
  from tunacode.types import ModelName
20
- from unittest.mock import patch, MagicMock
20
+ from unittest.mock import patch, MagicMock, AsyncMock
21
+ from tunacode.core.agents.planner_schema import Task
21
22
 
22
23
  # Create state manager
23
24
  state = StateManager()
@@ -32,10 +33,10 @@ async def test_orchestrator_planning():
32
33
  # Create orchestrator
33
34
  orchestrator = OrchestratorAgent(state)
34
35
 
35
- # Mock the LLM to return a simple plan
36
+ # Create proper Task objects
36
37
  mock_tasks = [
37
- MagicMock(id=1, description="Read the file", mutate=False),
38
- MagicMock(id=2, description="Update the code", mutate=True)
38
+ Task(id=1, description="Read the file", mutate=False),
39
+ Task(id=2, description="Update the code", mutate=True)
39
40
  ]
40
41
 
41
42
  # Capture console output
@@ -45,35 +46,33 @@ async def test_orchestrator_planning():
45
46
  if args:
46
47
  outputs.append(str(args[0]))
47
48
 
49
+ # Mock the Agent class to avoid API key requirement
50
+ mock_agent_instance = MagicMock()
51
+ mock_agent_instance.run = AsyncMock(return_value=MagicMock(data=mock_tasks))
52
+
48
53
  with patch('rich.console.Console.print', side_effect=capture_print):
49
- with patch('tunacode.core.llm.planner.make_plan', return_value=mock_tasks):
54
+ # Mock get_agent_tool to return our mock Agent class
55
+ with patch('tunacode.core.agents.main.get_agent_tool', return_value=(MagicMock(return_value=mock_agent_instance), None)):
50
56
  # Mock the agent execution
51
57
  mock_run = MagicMock()
52
58
  mock_run.result = MagicMock(output="Task completed")
53
59
 
54
- with patch.object(orchestrator, '_run_sub_task', return_value=mock_run):
60
+ with patch.object(orchestrator, '_run_sub_task', new_callable=AsyncMock, return_value=mock_run):
55
61
  # Run orchestrator
56
62
  results = await orchestrator.run("Test request")
57
63
 
58
64
  # Check outputs
59
- assert any("[TARGET] Orchestrator Mode" in out for out in outputs), "Missing orchestrator start message"
65
+ assert any("Orchestrator Mode" in out for out in outputs), "Missing orchestrator start message"
60
66
  assert any("Executing plan" in out for out in outputs), "Missing execution message"
61
- assert any("[SUCCESS] Orchestrator completed" in out for out in outputs), "Missing completion message"
62
-
63
- print("[PASS] Orchestrator displayed planning messages")
64
- print("[PASS] Orchestrator completed successfully")
65
-
66
- print("[SUCCESS] Orchestrator planning test PASSED!")
67
+ assert any("Orchestrator completed" in out for out in outputs), "Missing completion message"
67
68
 
68
69
  except ImportError as e:
69
- print(f"[WARNING] Skipping integration test due to missing dependencies: {e}")
70
- print("[SUCCESS] Integration test skipped (dependencies not available)")
70
+ pytest.skip(f"Skipping integration test due to missing dependencies: {e}")
71
71
 
72
72
 
73
+ @pytest.mark.asyncio
73
74
  async def test_architect_mode_check():
74
75
  """Test the actual check in repl.py for architect mode."""
75
- print("\n[TEST] Testing architect mode check in process_request...")
76
-
77
76
  # Simulate the check from repl.py
78
77
  class MockSession:
79
78
  architect_mode = False
@@ -85,36 +84,13 @@ async def test_architect_mode_check():
85
84
 
86
85
  # Test the actual condition used in repl.py
87
86
  if getattr(state.session, 'architect_mode', False):
88
- print("[FAIL] Should not use orchestrator when architect_mode is False")
89
- assert False
90
- else:
91
- print("[PASS] Correctly skipping orchestrator when architect_mode is False")
87
+ assert False, "Should not use orchestrator when architect_mode is False"
92
88
 
93
89
  # Enable architect mode
94
90
  state.session.architect_mode = True
95
91
 
96
- if getattr(state.session, 'architect_mode', False):
97
- print("[PASS] Correctly using orchestrator when architect_mode is True")
98
- else:
99
- print("[FAIL] Should use orchestrator when architect_mode is True")
100
- assert False
101
-
102
- print("[SUCCESS] Architect mode check test PASSED!")
103
-
104
-
105
- async def main():
106
- """Run integration tests."""
107
- print("=" * 60)
108
- print("ARCHITECT MODE INTEGRATION TESTS")
109
- print("=" * 60)
110
-
111
- await test_architect_mode_check()
112
- await test_orchestrator_planning()
113
-
114
- print("\n" + "=" * 60)
115
- print("[SUCCESS] ALL INTEGRATION TESTS COMPLETED!")
116
- print("=" * 60)
92
+ if not getattr(state.session, 'architect_mode', False):
93
+ assert False, "Should use orchestrator when architect_mode is True"
117
94
 
118
95
 
119
- if __name__ == "__main__":
120
- asyncio.run(main())
96
+ # Remove the main() function and if __name__ block since pytest will handle test discovery and execution
@@ -4,15 +4,15 @@
4
4
  import asyncio
5
5
  import sys
6
6
  import os
7
+ import pytest
7
8
 
8
9
  # Add src to path
9
10
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../src'))
10
11
 
11
12
 
13
+ @pytest.mark.asyncio
12
14
  async def test_architect_toggle():
13
15
  """Test that architect mode can be toggled."""
14
- print("\n[TEST] Testing architect mode toggle...")
15
-
16
16
  # Create a minimal state object
17
17
  class MockSession:
18
18
  def __init__(self):
@@ -26,25 +26,19 @@ async def test_architect_toggle():
26
26
 
27
27
  # Test initial state
28
28
  assert not hasattr(state.session, 'architect_mode') or state.session.architect_mode is False
29
- print("[PASS] Initial state: architect_mode is OFF")
30
29
 
31
30
  # Toggle ON
32
31
  state.session.architect_mode = not getattr(state.session, 'architect_mode', False)
33
32
  assert state.session.architect_mode is True
34
- print("[PASS] After toggle: architect_mode is ON")
35
33
 
36
34
  # Toggle OFF
37
35
  state.session.architect_mode = not state.session.architect_mode
38
36
  assert state.session.architect_mode is False
39
- print("[PASS] After second toggle: architect_mode is OFF")
40
-
41
- print("[SUCCESS] Architect toggle test PASSED!")
42
37
 
43
38
 
39
+ @pytest.mark.asyncio
44
40
  async def test_orchestrator_routing():
45
41
  """Test that process_request routes to orchestrator when architect_mode is ON."""
46
- print("\n[TEST] Testing orchestrator routing...")
47
-
48
42
  # Test the routing logic without actual imports
49
43
  class MockSession:
50
44
  def __init__(self):
@@ -59,28 +53,21 @@ async def test_orchestrator_routing():
59
53
 
60
54
  # Test routing decision
61
55
  if getattr(state.session, 'architect_mode', False):
62
- print("[PASS] Architect mode ON - Would use orchestrator")
63
56
  assert state.session.architect_mode is True
64
57
  else:
65
- print("[FAIL] Architect mode OFF - Would use normal agent")
66
58
  assert False, "Should have used orchestrator"
67
59
 
68
60
  # Test with architect_mode OFF
69
61
  state.session.architect_mode = False
70
62
  if getattr(state.session, 'architect_mode', False):
71
- print("[FAIL] Architect mode ON - Would use orchestrator")
72
63
  assert False, "Should have used normal agent"
73
64
  else:
74
- print("[PASS] Architect mode OFF - Would use normal agent")
75
65
  assert state.session.architect_mode is False
76
-
77
- print("[SUCCESS] Orchestrator routing test PASSED!")
78
66
 
79
67
 
68
+ @pytest.mark.asyncio
80
69
  async def test_command_parsing():
81
70
  """Test architect command argument parsing."""
82
- print("\n[TEST] Testing architect command parsing...")
83
-
84
71
  # Test various command inputs
85
72
  test_cases = [
86
73
  (["on"], True, "Explicit ON"),
@@ -101,25 +88,6 @@ async def test_command_parsing():
101
88
  result = None
102
89
 
103
90
  assert result == expected, f"Failed for {desc}"
104
- print(f"[PASS] {desc}: correctly parsed as {expected}")
105
-
106
- print("[SUCCESS] Command parsing test PASSED!")
107
-
108
-
109
- async def main():
110
- """Run all tests."""
111
- print("=" * 60)
112
- print("ARCHITECT MODE TESTS")
113
- print("=" * 60)
114
-
115
- await test_architect_toggle()
116
- await test_orchestrator_routing()
117
- await test_command_parsing()
118
-
119
- print("\n" + "=" * 60)
120
- print("[SUCCESS] ALL TESTS PASSED!")
121
- print("=" * 60)
122
91
 
123
92
 
124
- if __name__ == "__main__":
125
- asyncio.run(main())
93
+ # Remove the main() function and if __name__ block since pytest will handle test discovery and execution
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Simple test for fast-glob prefilter search functionality
4
+ """
5
+ import asyncio
6
+ import sys
7
+ import os
8
+ import tempfile
9
+ from pathlib import Path
10
+ import pytest
11
+
12
+ # Add src to path so we can import tunacode modules
13
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
14
+
15
+ def test_fast_glob_import():
16
+ """Test that fast_glob function can be imported"""
17
+ from tunacode.tools.grep import fast_glob
18
+ assert fast_glob is not None
19
+
20
+ def test_fast_glob_basic_functionality():
21
+ """Test basic fast_glob functionality with real files"""
22
+ from tunacode.tools.grep import fast_glob
23
+
24
+ # Use current directory which has Python files
25
+ root = Path(".")
26
+
27
+ # Test finding Python files
28
+ python_files = fast_glob(root, "*.py")
29
+
30
+ assert len(python_files) > 0, "Should find at least some Python files"
31
+ assert all(str(f).endswith('.py') for f in python_files), "All results should be .py files"
32
+
33
+ # Test specific pattern
34
+ test_files = fast_glob(root, "test_*.py")
35
+ assert len(test_files) >= 2, "Should find our test files" # At least test_react_thoughts.py and this file
36
+
37
+ def test_fast_glob_multiple_extensions():
38
+ """Test fast_glob with multiple extensions pattern"""
39
+ from tunacode.tools.grep import fast_glob
40
+
41
+ root = Path(".")
42
+
43
+ # Test multiple extensions pattern
44
+ code_files = fast_glob(root, "*.{py,md}")
45
+
46
+ assert len(code_files) > 0, "Should find Python and Markdown files"
47
+
48
+ py_files = [f for f in code_files if str(f).endswith('.py')]
49
+ md_files = [f for f in code_files if str(f).endswith('.md')]
50
+
51
+ assert len(py_files) > 0, "Should find some Python files"
52
+ assert len(md_files) > 0, "Should find some Markdown files"
53
+
54
+ def test_parallel_grep_import():
55
+ """Test that ParallelGrep class can be imported"""
56
+ from tunacode.tools.grep import ParallelGrep, grep
57
+ assert ParallelGrep is not None
58
+ assert grep is not None
59
+
60
+ @pytest.mark.asyncio
61
+ async def test_grep_search_integration():
62
+ """Test that grep function works with fast-glob prefilter"""
63
+ from tunacode.tools.grep import grep
64
+
65
+ # Test searching for a pattern we know exists
66
+ result = await grep("import", ".", include_files="*.py", max_results=5)
67
+
68
+ assert isinstance(result, str), "grep should return a string"
69
+ assert "Found" in result or "No matches" in result, "Result should indicate search status"
70
+
71
+ # If we found matches, check they contain our search pattern
72
+ if "Found" in result:
73
+ assert "Strategy:" in result, "Result should show which strategy was used"
74
+ assert "Candidates:" in result, "Result should show candidate count"
75
+
76
+ @pytest.mark.asyncio
77
+ async def test_smart_strategy_selection():
78
+ """Test that smart strategy selection works based on candidate count"""
79
+ from tunacode.tools.grep import ParallelGrep
80
+
81
+ # Create grep tool instance
82
+ grep_tool = ParallelGrep()
83
+
84
+ # Test with very specific pattern (should find few files)
85
+ result_few = await grep_tool._execute(
86
+ "test_", ".", include_files="test_*.py", search_type="smart", max_results=10
87
+ )
88
+
89
+ # Should use python strategy for small sets
90
+ assert "Strategy: python" in result_few, "Should use python strategy for small candidate sets"
91
+
92
+ # Test with broader pattern (more files)
93
+ result_many = await grep_tool._execute(
94
+ "import", ".", include_files="*.py", search_type="smart", max_results=10
95
+ )
96
+
97
+ # Should show some strategy was selected
98
+ assert "Strategy:" in result_many, "Should show strategy selection"
99
+
100
+ def test_bounded_results():
101
+ """Test that results are properly bounded by MAX_GLOB"""
102
+ from tunacode.tools.grep import fast_glob, MAX_GLOB
103
+
104
+ root = Path(".")
105
+
106
+ # Test that we don't exceed MAX_GLOB even with broad pattern
107
+ all_files = fast_glob(root, "*")
108
+
109
+ assert len(all_files) <= MAX_GLOB, f"Results should be bounded by MAX_GLOB ({MAX_GLOB})"
110
+
111
+ # Remove the main() function and if __name__ block since pytest will handle test discovery and execution
@@ -4,14 +4,18 @@
4
4
  import asyncio
5
5
  import sys
6
6
  import os
7
+ import pytest
7
8
 
8
9
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../src'))
9
10
 
11
+ @pytest.mark.asyncio
10
12
  async def test_planning_visibility():
11
13
  """Test that planning steps are visible in orchestrator output."""
12
14
  from tunacode.core.agents.orchestrator import OrchestratorAgent
13
15
  from tunacode.core.state import StateManager
14
16
  from tunacode.types import ModelName
17
+ from unittest.mock import patch, MagicMock, AsyncMock
18
+ from tunacode.core.agents.planner_schema import Task
15
19
 
16
20
  # Create state manager
17
21
  state = StateManager()
@@ -37,20 +41,24 @@ async def test_planning_visibility():
37
41
  Then create a new file called feature.py with a simple function.
38
42
  """
39
43
 
40
- print("=== Testing Orchestrator Planning Visibility ===\n")
41
- print(f"Request: {test_request.strip()}\n")
44
+ # Create proper Task objects
45
+ mock_tasks = [
46
+ Task(id=1, description="Read README.md", mutate=False),
47
+ Task(id=2, description="Create feature.py", mutate=True)
48
+ ]
42
49
 
43
- try:
44
- # Run the orchestrator
45
- results = await orchestrator.run(test_request)
46
-
47
- print(f"\n=== Orchestrator completed with {len(results)} results ===")
50
+ # Mock the Agent class to avoid API key requirement
51
+ mock_agent_instance = MagicMock()
52
+ mock_agent_instance.run = AsyncMock(return_value=MagicMock(data=mock_tasks))
53
+
54
+ # Mock get_agent_tool to return our mock Agent class
55
+ with patch('tunacode.core.agents.main.get_agent_tool', return_value=(MagicMock(return_value=mock_agent_instance), None)):
56
+ # Mock the agent execution
57
+ mock_run = MagicMock()
58
+ mock_run.result = MagicMock(output="Task completed")
48
59
 
49
- except Exception as e:
50
- print(f"\nError during orchestration: {e}")
51
- import traceback
52
- traceback.print_exc()
53
-
54
-
55
- if __name__ == "__main__":
56
- asyncio.run(test_planning_visibility())
60
+ with patch.object(orchestrator, '_run_sub_task', new_callable=AsyncMock, return_value=mock_run):
61
+ # Run the orchestrator
62
+ results = await orchestrator.run(test_request)
63
+
64
+ assert len(results) == len(mock_tasks), f"Expected {len(mock_tasks)} results, got {len(results)}"
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Simple test for ReAct thoughts functionality
4
+ """
5
+ import asyncio
6
+ import sys
7
+ import os
8
+ import pytest
9
+
10
+ # Add src to path so we can import tunacode modules
11
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
12
+
13
+ def test_thoughts_command_import():
14
+ """Test that ThoughtsCommand can be imported without errors"""
15
+ from tunacode.cli.commands import ThoughtsCommand
16
+ assert ThoughtsCommand is not None
17
+
18
+ def test_session_state_thoughts():
19
+ """Test that SessionState has show_thoughts attribute"""
20
+ from tunacode.core.state import SessionState
21
+
22
+ # Create a session state
23
+ session = SessionState()
24
+
25
+ # Check default value
26
+ assert hasattr(session, 'show_thoughts'), "SessionState missing show_thoughts attribute"
27
+ assert session.show_thoughts == False, "Default show_thoughts should be False"
28
+
29
+ # Test setting the value
30
+ session.show_thoughts = True
31
+ assert session.show_thoughts == True, "show_thoughts should be settable to True"
32
+
33
+ session.show_thoughts = False
34
+ assert session.show_thoughts == False, "show_thoughts should be settable to False"
35
+
36
+ @pytest.mark.asyncio
37
+ async def test_agent_thought_processing():
38
+ """Test that agent can process thought messages"""
39
+ from tunacode.core.agents.main import _process_node
40
+ from tunacode.core.state import StateManager
41
+
42
+ # Create a mock state manager
43
+ state_manager = StateManager()
44
+
45
+ # Create a mock node with thought
46
+ class MockNode:
47
+ def __init__(self, thought_text):
48
+ self.thought = thought_text
49
+
50
+ # Test processing a node with a thought
51
+ node_with_thought = MockNode("This is a test thought")
52
+
53
+ # This should not raise an error
54
+ await _process_node(node_with_thought, None, state_manager)
55
+
56
+ # Check that thought was added to messages
57
+ messages = state_manager.session.messages
58
+ thought_messages = [msg for msg in messages if isinstance(msg, dict) and "thought" in msg]
59
+
60
+ assert len(thought_messages) > 0, "Thought message should be added to session messages"
61
+ assert thought_messages[0]["thought"] == "This is a test thought", "Thought content should match"
62
+
63
+ @pytest.mark.asyncio
64
+ async def test_thoughts_command_functionality():
65
+ """Test ThoughtsCommand execute method"""
66
+ from tunacode.cli.commands import ThoughtsCommand, CommandContext
67
+ from tunacode.core.state import StateManager
68
+
69
+ # Create command and context
70
+ command = ThoughtsCommand()
71
+ state_manager = StateManager()
72
+ context = CommandContext(state_manager=state_manager)
73
+
74
+ # Test initial state
75
+ assert state_manager.session.show_thoughts == False, "Initial state should be False"
76
+
77
+ # Test toggle with no args (should toggle)
78
+ await command.execute([], context)
79
+ assert state_manager.session.show_thoughts == True, "Should toggle to True"
80
+
81
+ await command.execute([], context)
82
+ assert state_manager.session.show_thoughts == False, "Should toggle back to False"
83
+
84
+ # Test explicit on
85
+ await command.execute(["on"], context)
86
+ assert state_manager.session.show_thoughts == True, "Should set to True with 'on'"
87
+
88
+ # Test explicit off
89
+ await command.execute(["off"], context)
90
+ assert state_manager.session.show_thoughts == False, "Should set to False with 'off'"
91
+
92
+ # Remove the main() function and if __name__ block since pytest will handle test discovery and execution