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.
- {tunacode_cli-0.0.24/src/tunacode_cli.egg-info → tunacode_cli-0.0.26}/PKG-INFO +3 -2
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/README.md +2 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/pyproject.toml +1 -2
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/configuration/defaults.py +1 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/constants.py +1 -1
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/agents/main.py +19 -2
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/agents/orchestrator.py +16 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/prompts/system.md +18 -5
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/types.py +27 -1
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/output.py +13 -6
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26/src/tunacode_cli.egg-info}/PKG-INFO +3 -2
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode_cli.egg-info/SOURCES.txt +1 -1
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode_cli.egg-info/requires.txt +0 -1
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_architect_integration.py +46 -22
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_architect_simple.py +37 -5
- tunacode_cli-0.0.26/tests/test_fallback_responses.py +54 -0
- tunacode_cli-0.0.26/tests/test_fast_glob_search.py +191 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_orchestrator_planning_visibility.py +15 -23
- tunacode_cli-0.0.26/tests/test_react_thoughts.py +149 -0
- tunacode_cli-0.0.24/src/tunacode/prompts/system.txt +0 -203
- tunacode_cli-0.0.24/tests/test_fast_glob_search.py +0 -111
- tunacode_cli-0.0.24/tests/test_react_thoughts.py +0 -92
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/CLAUDE.md +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/LICENSE +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/MANIFEST.in +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/TUNACODE.md +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/setup.cfg +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/setup.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/__init__.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/cli/__init__.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/cli/commands.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/cli/main.py +1 -1
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/cli/repl.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/cli/textual_app.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/cli/textual_bridge.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/configuration/__init__.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/configuration/models.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/configuration/settings.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/context.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/__init__.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/agents/__init__.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/agents/planner_schema.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/agents/readonly.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/background/__init__.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/background/manager.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/llm/__init__.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/llm/planner.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/setup/__init__.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/setup/agent_setup.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/setup/base.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/setup/config_setup.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/setup/coordinator.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/setup/environment_setup.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/setup/git_safety_setup.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/state.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/core/tool_handler.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/exceptions.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/py.typed +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/services/__init__.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/services/mcp.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/setup.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/__init__.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/base.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/bash.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/grep.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/read_file.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/run_command.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/update_file.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/tools/write_file.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/__init__.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/completers.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/console.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/constants.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/decorators.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/input.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/keybindings.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/lexers.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/panels.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/prompt_manager.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/tool_ui.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/ui/validators.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/__init__.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/bm25.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/diff_utils.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/file_utils.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/import_cache.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/ripgrep.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/system.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/text_utils.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode/utils/user_configuration.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/src/tunacode_cli.egg-info/top_level.txt +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_agent_initialization.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_background_manager.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_config_setup_async.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_file_reference_expansion.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_json_tool_parsing.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_orchestrator_file_references.py +0 -0
- {tunacode_cli-0.0.24 → tunacode_cli-0.0.26}/tests/test_orchestrator_import.py +0 -0
- {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.
|
|
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
|
+

|
|
48
|
+
|
|
48
49
|
[Quick Start](#quick-start) • [Features](#features) • [Configuration](#configuration) • [Documentation](#documentation)
|
|
49
50
|
|
|
50
51
|
</div>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tunacode-cli"
|
|
7
|
-
version = "0.0.
|
|
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
|
]
|
|
@@ -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,
|
|
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
|
|
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.
|
|
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
|
+

|
|
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
|
|
@@ -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
|
|
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
|
-
#
|
|
35
|
+
# Mock the LLM to return a simple plan
|
|
37
36
|
mock_tasks = [
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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()
|