tunacode-cli 0.0.24__tar.gz → 0.0.26__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 (101) hide show
  1. {tunacode_cli-0.0.24/src/tunacode_cli.egg-info → tunacode_cli-0.0.26}/PKG-INFO +3 -2
  2. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/README.md +2 -0
  3. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/pyproject.toml +1 -2
  4. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/configuration/defaults.py +1 -0
  5. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/constants.py +1 -1
  6. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/agents/main.py +19 -2
  7. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/agents/orchestrator.py +16 -0
  8. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/prompts/system.md +18 -5
  9. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/types.py +27 -1
  10. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/output.py +13 -6
  11. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26/src/tunacode_cli.egg-info}/PKG-INFO +3 -2
  12. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode_cli.egg-info/SOURCES.txt +1 -1
  13. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode_cli.egg-info/requires.txt +0 -1
  14. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_architect_integration.py +46 -22
  15. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_architect_simple.py +37 -5
  16. tunacode_cli-0.0.26/tests/test_fallback_responses.py +54 -0
  17. tunacode_cli-0.0.26/tests/test_fast_glob_search.py +191 -0
  18. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_orchestrator_planning_visibility.py +15 -23
  19. tunacode_cli-0.0.26/tests/test_react_thoughts.py +149 -0
  20. tunacode_cli-0.0.24/src/tunacode/prompts/system.txt +0 -203
  21. tunacode_cli-0.0.24/tests/test_fast_glob_search.py +0 -111
  22. tunacode_cli-0.0.24/tests/test_react_thoughts.py +0 -92
  23. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/CLAUDE.md +0 -0
  24. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/LICENSE +0 -0
  25. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/MANIFEST.in +0 -0
  26. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/TUNACODE.md +0 -0
  27. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/setup.cfg +0 -0
  28. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/setup.py +0 -0
  29. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/__init__.py +0 -0
  30. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/cli/__init__.py +0 -0
  31. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/cli/commands.py +0 -0
  32. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/cli/main.py +1 -1
  33. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/cli/repl.py +0 -0
  34. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/cli/textual_app.py +0 -0
  35. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/cli/textual_bridge.py +0 -0
  36. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/configuration/__init__.py +0 -0
  37. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/configuration/models.py +0 -0
  38. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/configuration/settings.py +0 -0
  39. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/context.py +0 -0
  40. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/__init__.py +0 -0
  41. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/agents/__init__.py +0 -0
  42. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/agents/planner_schema.py +0 -0
  43. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/agents/readonly.py +0 -0
  44. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/background/__init__.py +0 -0
  45. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/background/manager.py +0 -0
  46. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/llm/__init__.py +0 -0
  47. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/llm/planner.py +0 -0
  48. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/setup/__init__.py +0 -0
  49. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/setup/agent_setup.py +0 -0
  50. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/setup/base.py +0 -0
  51. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/setup/config_setup.py +0 -0
  52. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/setup/coordinator.py +0 -0
  53. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/setup/environment_setup.py +0 -0
  54. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/setup/git_safety_setup.py +0 -0
  55. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/state.py +0 -0
  56. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/tool_handler.py +0 -0
  57. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/exceptions.py +0 -0
  58. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/py.typed +0 -0
  59. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/services/__init__.py +0 -0
  60. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/services/mcp.py +0 -0
  61. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/setup.py +0 -0
  62. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/__init__.py +0 -0
  63. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/base.py +0 -0
  64. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/bash.py +0 -0
  65. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/grep.py +0 -0
  66. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/read_file.py +0 -0
  67. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/run_command.py +0 -0
  68. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/update_file.py +0 -0
  69. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/write_file.py +0 -0
  70. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/__init__.py +0 -0
  71. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/completers.py +0 -0
  72. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/console.py +0 -0
  73. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/constants.py +0 -0
  74. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/decorators.py +0 -0
  75. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/input.py +0 -0
  76. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/keybindings.py +0 -0
  77. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/lexers.py +0 -0
  78. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/panels.py +0 -0
  79. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/prompt_manager.py +0 -0
  80. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/tool_ui.py +0 -0
  81. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/validators.py +0 -0
  82. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/__init__.py +0 -0
  83. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/bm25.py +0 -0
  84. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/diff_utils.py +0 -0
  85. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/file_utils.py +0 -0
  86. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/import_cache.py +0 -0
  87. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/ripgrep.py +0 -0
  88. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/system.py +0 -0
  89. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/text_utils.py +0 -0
  90. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/user_configuration.py +0 -0
  91. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
  92. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
  93. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode_cli.egg-info/top_level.txt +0 -0
  94. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_agent_initialization.py +0 -0
  95. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_background_manager.py +0 -0
  96. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_config_setup_async.py +0 -0
  97. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_file_reference_expansion.py +0 -0
  98. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_json_tool_parsing.py +0 -0
  99. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_orchestrator_file_references.py +0 -0
  100. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_orchestrator_import.py +0 -0
  101. {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/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.24
3
+ Version: 0.0.26
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License-Expression: MIT
@@ -30,7 +30,6 @@ 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"
34
33
  Requires-Dist: pytest-cov; extra == "dev"
35
34
  Requires-Dist: textual-dev; extra == "dev"
36
35
  Dynamic: license-file
@@ -45,6 +44,8 @@ Dynamic: license-file
45
44
 
46
45
  **Your AI-powered CLI coding assistant**
47
46
 
47
+ ![Demo](demo.gif)
48
+
48
49
  [Quick Start](#quick-start) • [Features](#features) • [Configuration](#configuration) • [Documentation](#documentation)
49
50
 
50
51
  </div>
@@ -8,6 +8,8 @@
8
8
 
9
9
  **Your AI-powered CLI coding assistant**
10
10
 
11
+ ![Demo](demo.gif)
12
+
11
13
  [Quick Start](#quick-start) • [Features](#features) • [Configuration](#configuration) • [Documentation](#documentation)
12
14
 
13
15
  </div>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tunacode-cli"
7
- version = "0.0.24"
7
+ version = "0.0.26"
8
8
  description = "Your agentic CLI developer."
9
9
  keywords = ["cli", "agent", "development", "automation"]
10
10
  readme = "README.md"
@@ -42,7 +42,6 @@ dev = [
42
42
  "flake8",
43
43
  "isort",
44
44
  "pytest",
45
- "pytest-asyncio",
46
45
  "pytest-cov",
47
46
  "textual-dev",
48
47
  ]
@@ -21,6 +21,7 @@ DEFAULT_USER_CONFIG: UserConfig = {
21
21
  "max_iterations": 20,
22
22
  "tool_ignore": [TOOL_READ_FILE],
23
23
  "guide_file": GUIDE_FILE_NAME,
24
+ "fallback_response": True,
24
25
  },
25
26
  "mcpServers": {},
26
27
  }
@@ -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.24"
10
+ APP_VERSION = "0.0.26"
11
11
 
12
12
  # File patterns
13
13
  GUIDE_FILE_PATTERN = "{name}.md"
@@ -18,8 +18,8 @@ from tunacode.tools.read_file import read_file
18
18
  from tunacode.tools.run_command import run_command
19
19
  from tunacode.tools.update_file import update_file
20
20
  from tunacode.tools.write_file import write_file
21
- from tunacode.types import (AgentRun, ErrorMessage, ModelName, PydanticAgent, ToolCallback,
22
- ToolCallId, ToolName)
21
+ from tunacode.types import (AgentRun, ErrorMessage, FallbackResponse, ModelName, PydanticAgent,
22
+ ResponseState, SimpleResult, ToolCallback, ToolCallId, ToolName)
23
23
 
24
24
 
25
25
  # Lazy import for Agent and Tool
@@ -343,11 +343,19 @@ async def process_request(
343
343
  mh = state_manager.session.messages.copy()
344
344
  # Get max iterations from config (default: 20)
345
345
  max_iterations = state_manager.session.user_config.get("settings", {}).get("max_iterations", 20)
346
+ fallback_enabled = state_manager.session.user_config.get("settings", {}).get(
347
+ "fallback_response", True
348
+ )
349
+
350
+ response_state = ResponseState()
346
351
 
347
352
  async with agent.iter(message, message_history=mh) as agent_run:
348
353
  i = 0
349
354
  async for node in agent_run:
350
355
  await _process_node(node, tool_callback, state_manager)
356
+ if hasattr(node, "result") and node.result and hasattr(node.result, "output"):
357
+ if node.result.output:
358
+ response_state.has_user_response = True
351
359
  i += 1
352
360
 
353
361
  # Display iteration progress if thoughts are enabled
@@ -362,5 +370,14 @@ async def process_request(
362
370
 
363
371
  await ui.warning(f"⚠️ Reached maximum iterations ({max_iterations})")
364
372
  break
373
+ if not response_state.has_user_response and i >= max_iterations and fallback_enabled:
374
+ patch_tool_messages("Task incomplete", state_manager=state_manager)
375
+ response_state.has_final_synthesis = True
376
+ fallback = FallbackResponse(
377
+ summary="Reached maximum iterations without producing a final response.",
378
+ progress=f"{i}/{max_iterations} iterations completed",
379
+ )
365
380
 
381
+ agent_run.result = SimpleResult(fallback.summary)
382
+ agent_run.response_state = response_state
366
383
  return agent_run
@@ -96,4 +96,20 @@ class OrchestratorAgent:
96
96
 
97
97
  console.print("\n[green]Orchestrator completed all tasks successfully![/green]")
98
98
 
99
+ has_output = any(
100
+ hasattr(r, "result") and r.result and getattr(r.result, "output", None) for r in results
101
+ )
102
+
103
+ if results and not has_output:
104
+ lines = [f"Task {i + 1} completed" for i in range(len(results))]
105
+ summary = "\n".join(lines)
106
+
107
+ class SynthResult:
108
+ def __init__(self, output: str):
109
+ self.output = output
110
+
111
+ synth_run = type("SynthRun", (), {})()
112
+ synth_run.result = SynthResult(summary)
113
+ results.append(synth_run)
114
+
99
115
  return results
@@ -14,17 +14,30 @@ You MUST follow these rules:
14
14
 
15
15
  You HAVE the following tools available. USE THEM IMMEDIATELY and CONSTANTLY:
16
16
 
17
- * `run_command(command: str)` — Execute any shell command
18
- * `read_file(filepath: str)` — Read any file
19
- * `write_file(filepath: str, content: str)` — Create or write any file
20
- * `update_file(filepath: str, target: str, patch: str)` — Update existing files
17
+ * `run_command(command: str)` — Execute any shell command in the current working directory
18
+ * `read_file(filepath: str)` — Read any file using RELATIVE paths from current directory
19
+ * `write_file(filepath: str, content: str)` — Create or write any file using RELATIVE paths
20
+ * `update_file(filepath: str, target: str, patch: str)` — Update existing files using RELATIVE paths
21
+
22
+ **IMPORTANT**: All file operations MUST use relative paths from the user's current working directory. NEVER create files in /tmp or use absolute paths.
23
+
24
+ ---
25
+
26
+ \###Working Directory Rules###
27
+
28
+ **CRITICAL**: You MUST respect the user's current working directory:
29
+ - **ALWAYS** use relative paths (e.g., `src/main.py`, `./config.json`, `../lib/utils.js`)
30
+ - **NEVER** use absolute paths (e.g., `/tmp/file.txt`, `/home/user/file.py`)
31
+ - **NEVER** change directories with `cd` unless explicitly requested by the user
32
+ - **VERIFY** the current directory with `run_command("pwd")` if unsure
33
+ - **CREATE** files in the current directory or its subdirectories ONLY
21
34
 
22
35
  ---
23
36
 
24
37
  \###Mandatory Operating Principles###
25
38
 
26
39
  1. **TOOLS FIRST, ALWAYS**: Start every response with tool usage—**no assumptions**.
27
- 2. **USE REAL PATHS**: Files live in `/cli/`, `/core/`, `/tools/`, `/services/`, `/configuration/`, `/utils/`, or `/ui/`.
40
+ 2. **USE RELATIVE PATHS**: Always work in the current directory. Use relative paths like `src/`, `cli/`, `core/`, `tools/`, etc. NEVER use absolute paths starting with `/`.
28
41
  3. **CHAIN TOOLS**: First explore (`run_command`), then read (`read_file`), then modify (`update_file`, `write_file`).
29
42
  4. **ACT IMMEDIATELY**: Don’t describe what to do—**just do it**.
30
43
  5. **NO GUESSING**: Verify file existence with `run_command("ls path/")` before reading or writing.
@@ -5,7 +5,7 @@ This module contains all type aliases, protocols, and type definitions
5
5
  used throughout the TunaCode codebase.
6
6
  """
7
7
 
8
- from dataclasses import dataclass
8
+ from dataclasses import dataclass, field
9
9
  from pathlib import Path
10
10
  from typing import Any, Awaitable, Callable, Dict, List, Optional, Protocol, Tuple, Union
11
11
 
@@ -134,6 +134,32 @@ AgentRun = Any # pydantic_ai.RunContext or similar
134
134
  AgentConfig = Dict[str, Any]
135
135
  AgentName = str
136
136
 
137
+
138
+ @dataclass
139
+ class ResponseState:
140
+ """Track whether a user visible response was produced."""
141
+
142
+ has_user_response: bool = False
143
+ has_final_synthesis: bool = False
144
+
145
+
146
+ @dataclass
147
+ class FallbackResponse:
148
+ """Structure for synthesized fallback responses."""
149
+
150
+ summary: str
151
+ progress: str = ""
152
+ issues: List[str] = field(default_factory=list)
153
+ next_steps: List[str] = field(default_factory=list)
154
+
155
+
156
+ @dataclass
157
+ class SimpleResult:
158
+ """Simple result container for agent responses."""
159
+
160
+ output: str
161
+
162
+
137
163
  # =============================================================================
138
164
  # Session and State Types
139
165
  # =============================================================================
@@ -17,12 +17,19 @@ console = Console()
17
17
  colors = DotDict(UI_COLORS)
18
18
 
19
19
  BANNER = """[bold cyan]
20
- ████████╗██╗ ██╗███╗ ██╗ █████╗ ██████╗ ██████╗ ██████╗ ███████╗
21
- ╚══██╔══╝██║ ██║████╗ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗██╔════╝
22
- ██║ ██║ ██║██╔██╗ ██║███████║██║ ██║ ██║██║ ██║█████╗
23
- ██║ ██║ ██║██║╚██╗██║██╔══██║██║ ██║ ██║██║ ██║██╔══╝
24
- ██║ ╚██████╔╝██║ ╚████║██║ ██║╚██████╗╚██████╔╝██████╔╝███████╗
25
- ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
20
+ ████████╗██╗ ██╗███╗ ██╗ █████╗
21
+ ╚══██╔══╝██║ ██║████╗ ██║██╔══██╗
22
+ ██║ ██║ ██║██╔██╗ ██║███████║
23
+ ██║ ██║ ██║██║╚██╗██║██╔══██║
24
+ ██║ ╚██████╔╝██║ ╚████║██║ ██║
25
+ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝
26
+
27
+ ██████╗ ██████╗ ██████╗ ███████╗
28
+ ██╔════╝██╔═══██╗██╔══██╗██╔════╝
29
+ ██║ ██║ ██║██║ ██║█████╗
30
+ ██║ ██║ ██║██║ ██║██╔══╝
31
+ ╚██████╗╚██████╔╝██████╔╝███████╗
32
+ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
26
33
  [/bold cyan]
27
34
 
28
35
  ● Caution: This tool can modify your codebase - always use git branches"""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.24
3
+ Version: 0.0.26
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License-Expression: MIT
@@ -30,7 +30,6 @@ 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"
34
33
  Requires-Dist: pytest-cov; extra == "dev"
35
34
  Requires-Dist: textual-dev; extra == "dev"
36
35
  Dynamic: license-file
@@ -45,6 +44,8 @@ Dynamic: license-file
45
44
 
46
45
  **Your AI-powered CLI coding assistant**
47
46
 
47
+ ![Demo](demo.gif)
48
+
48
49
  [Quick Start](#quick-start) • [Features](#features) • [Configuration](#configuration) • [Documentation](#documentation)
49
50
 
50
51
  </div>
@@ -42,7 +42,6 @@ src/tunacode/core/setup/coordinator.py
42
42
  src/tunacode/core/setup/environment_setup.py
43
43
  src/tunacode/core/setup/git_safety_setup.py
44
44
  src/tunacode/prompts/system.md
45
- src/tunacode/prompts/system.txt
46
45
  src/tunacode/services/__init__.py
47
46
  src/tunacode/services/mcp.py
48
47
  src/tunacode/tools/__init__.py
@@ -86,6 +85,7 @@ tests/test_architect_integration.py
86
85
  tests/test_architect_simple.py
87
86
  tests/test_background_manager.py
88
87
  tests/test_config_setup_async.py
88
+ tests/test_fallback_responses.py
89
89
  tests/test_fast_glob_search.py
90
90
  tests/test_file_reference_expansion.py
91
91
  tests/test_json_tool_parsing.py
@@ -10,6 +10,5 @@ black
10
10
  flake8
11
11
  isort
12
12
  pytest
13
- pytest-asyncio
14
13
  pytest-cov
15
14
  textual-dev
@@ -4,21 +4,20 @@
4
4
  import asyncio
5
5
  import sys
6
6
  import os
7
- import pytest
8
7
 
9
8
  # Add src to path
10
9
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../src'))
11
10
 
12
11
 
13
- @pytest.mark.asyncio
14
12
  async def test_orchestrator_planning():
15
13
  """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, AsyncMock
21
- from tunacode.core.agents.planner_schema import Task
20
+ from unittest.mock import patch, MagicMock
22
21
 
23
22
  # Create state manager
24
23
  state = StateManager()
@@ -33,10 +32,10 @@ async def test_orchestrator_planning():
33
32
  # Create orchestrator
34
33
  orchestrator = OrchestratorAgent(state)
35
34
 
36
- # Create proper Task objects
35
+ # Mock the LLM to return a simple plan
37
36
  mock_tasks = [
38
- Task(id=1, description="Read the file", mutate=False),
39
- Task(id=2, description="Update the code", mutate=True)
37
+ MagicMock(id=1, description="Read the file", mutate=False),
38
+ MagicMock(id=2, description="Update the code", mutate=True)
40
39
  ]
41
40
 
42
41
  # Capture console output
@@ -46,33 +45,35 @@ async def test_orchestrator_planning():
46
45
  if args:
47
46
  outputs.append(str(args[0]))
48
47
 
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
-
53
48
  with patch('rich.console.Console.print', side_effect=capture_print):
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)):
49
+ with patch('tunacode.core.llm.planner.make_plan', return_value=mock_tasks):
56
50
  # Mock the agent execution
57
51
  mock_run = MagicMock()
58
52
  mock_run.result = MagicMock(output="Task completed")
59
53
 
60
- with patch.object(orchestrator, '_run_sub_task', new_callable=AsyncMock, return_value=mock_run):
54
+ with patch.object(orchestrator, '_run_sub_task', return_value=mock_run):
61
55
  # Run orchestrator
62
56
  results = await orchestrator.run("Test request")
63
57
 
64
58
  # Check outputs
65
- assert any("Orchestrator Mode" in out for out in outputs), "Missing orchestrator start message"
59
+ assert any("[TARGET] Orchestrator Mode" in out for out in outputs), "Missing orchestrator start message"
66
60
  assert any("Executing plan" in out for out in outputs), "Missing execution message"
67
- assert any("Orchestrator completed" in out for out in outputs), "Missing completion 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!")
68
67
 
69
68
  except ImportError as e:
70
- pytest.skip(f"Skipping integration test due to missing dependencies: {e}")
69
+ print(f"[WARNING] Skipping integration test due to missing dependencies: {e}")
70
+ print("[SUCCESS] Integration test skipped (dependencies not available)")
71
71
 
72
72
 
73
- @pytest.mark.asyncio
74
73
  async def test_architect_mode_check():
75
74
  """Test the actual check in repl.py for architect mode."""
75
+ print("\n[TEST] Testing architect mode check in process_request...")
76
+
76
77
  # Simulate the check from repl.py
77
78
  class MockSession:
78
79
  architect_mode = False
@@ -84,13 +85,36 @@ async def test_architect_mode_check():
84
85
 
85
86
  # Test the actual condition used in repl.py
86
87
  if getattr(state.session, 'architect_mode', False):
87
- assert False, "Should not use orchestrator when architect_mode is 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")
88
92
 
89
93
  # Enable architect mode
90
94
  state.session.architect_mode = True
91
95
 
92
- if not getattr(state.session, 'architect_mode', False):
93
- assert False, "Should use orchestrator when architect_mode is True"
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)
94
117
 
95
118
 
96
- # Remove the main() function and if __name__ block since pytest will handle test discovery and execution
119
+ if __name__ == "__main__":
120
+ asyncio.run(main())
@@ -4,15 +4,15 @@
4
4
  import asyncio
5
5
  import sys
6
6
  import os
7
- import pytest
8
7
 
9
8
  # Add src to path
10
9
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../src'))
11
10
 
12
11
 
13
- @pytest.mark.asyncio
14
12
  async def test_architect_toggle():
15
13
  """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,19 +26,25 @@ 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")
29
30
 
30
31
  # Toggle ON
31
32
  state.session.architect_mode = not getattr(state.session, 'architect_mode', False)
32
33
  assert state.session.architect_mode is True
34
+ print("[PASS] After toggle: architect_mode is ON")
33
35
 
34
36
  # Toggle OFF
35
37
  state.session.architect_mode = not state.session.architect_mode
36
38
  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!")
37
42
 
38
43
 
39
- @pytest.mark.asyncio
40
44
  async def test_orchestrator_routing():
41
45
  """Test that process_request routes to orchestrator when architect_mode is ON."""
46
+ print("\n[TEST] Testing orchestrator routing...")
47
+
42
48
  # Test the routing logic without actual imports
43
49
  class MockSession:
44
50
  def __init__(self):
@@ -53,21 +59,28 @@ async def test_orchestrator_routing():
53
59
 
54
60
  # Test routing decision
55
61
  if getattr(state.session, 'architect_mode', False):
62
+ print("[PASS] Architect mode ON - Would use orchestrator")
56
63
  assert state.session.architect_mode is True
57
64
  else:
65
+ print("[FAIL] Architect mode OFF - Would use normal agent")
58
66
  assert False, "Should have used orchestrator"
59
67
 
60
68
  # Test with architect_mode OFF
61
69
  state.session.architect_mode = False
62
70
  if getattr(state.session, 'architect_mode', False):
71
+ print("[FAIL] Architect mode ON - Would use orchestrator")
63
72
  assert False, "Should have used normal agent"
64
73
  else:
74
+ print("[PASS] Architect mode OFF - Would use normal agent")
65
75
  assert state.session.architect_mode is False
76
+
77
+ print("[SUCCESS] Orchestrator routing test PASSED!")
66
78
 
67
79
 
68
- @pytest.mark.asyncio
69
80
  async def test_command_parsing():
70
81
  """Test architect command argument parsing."""
82
+ print("\n[TEST] Testing architect command parsing...")
83
+
71
84
  # Test various command inputs
72
85
  test_cases = [
73
86
  (["on"], True, "Explicit ON"),
@@ -88,6 +101,25 @@ async def test_command_parsing():
88
101
  result = None
89
102
 
90
103
  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)
91
122
 
92
123
 
93
- # Remove the main() function and if __name__ block since pytest will handle test discovery and execution
124
+ if __name__ == "__main__":
125
+ asyncio.run(main())
@@ -0,0 +1,54 @@
1
+ import pytest
2
+ from unittest.mock import patch, MagicMock
3
+
4
+ from tunacode.core.state import StateManager
5
+ from tunacode.core.agents import main as agent_main
6
+ from tunacode.core.agents.orchestrator import OrchestratorAgent
7
+
8
+ class DummyNode:
9
+ pass
10
+
11
+ class FakeAgentRun:
12
+ def __init__(self, nodes):
13
+ self._nodes = nodes
14
+ self.result = None
15
+ def __aiter__(self):
16
+ async def gen():
17
+ for n in self._nodes:
18
+ yield n
19
+ return gen()
20
+ async def __aenter__(self):
21
+ return self
22
+ async def __aexit__(self, exc_type, exc, tb):
23
+ pass
24
+
25
+ class FakeAgent:
26
+ def __init__(self, nodes):
27
+ self._nodes = nodes
28
+ def iter(self, message, message_history=None):
29
+ return FakeAgentRun(self._nodes)
30
+
31
+ @pytest.mark.asyncio
32
+ async def test_process_request_generates_fallback():
33
+ state = StateManager()
34
+ state.session.user_config = {
35
+ "settings": {"max_iterations": 3, "fallback_response": True}
36
+ }
37
+ nodes = [DummyNode() for _ in range(5)]
38
+ with patch("tunacode.core.agents.main.get_or_create_agent", return_value=FakeAgent(nodes)):
39
+ res = await agent_main.process_request("model", "test", state)
40
+ assert hasattr(res, "result")
41
+ assert "maximum iterations" in res.result.output.lower()
42
+
43
+ @pytest.mark.asyncio
44
+ async def test_orchestrator_synthesizes_summary():
45
+ state = StateManager()
46
+ orch = OrchestratorAgent(state)
47
+ tasks = [MagicMock(id=1, description="task", mutate=True)]
48
+ fake_run = MagicMock()
49
+ fake_run.result = None
50
+ with patch.object(orch, "plan", return_value=tasks):
51
+ with patch.object(orch, "_run_sub_task", return_value=fake_run):
52
+ results = await orch.run("req")
53
+ assert len(results) == 2
54
+ assert "task 1" in results[-1].result.output.lower()