tunacode-cli 0.0.76.6__py3-none-any.whl → 0.0.77.1__py3-none-any.whl
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/commands/implementations/development.py +4 -4
- tunacode/configuration/key_descriptions.py +2 -9
- tunacode/constants.py +2 -2
- tunacode/context.py +5 -5
- tunacode/core/agents/agent_components/__init__.py +2 -0
- tunacode/core/agents/agent_components/agent_config.py +41 -38
- tunacode/core/agents/agent_components/agent_helpers.py +27 -0
- tunacode/core/agents/main.py +6 -65
- tunacode/core/setup/__init__.py +0 -2
- tunacode/prompts/{system.md → system.xml} +10 -7
- tunacode/setup.py +0 -2
- tunacode/tools/grep.py +3 -16
- tunacode/ui/output.py +4 -15
- tunacode/utils/models_registry.py +3 -3
- {tunacode_cli-0.0.76.6.dist-info → tunacode_cli-0.0.77.1.dist-info}/METADATA +1 -1
- {tunacode_cli-0.0.76.6.dist-info → tunacode_cli-0.0.77.1.dist-info}/RECORD +19 -20
- tunacode/core/setup/git_safety_setup.py +0 -186
- {tunacode_cli-0.0.76.6.dist-info → tunacode_cli-0.0.77.1.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.76.6.dist-info → tunacode_cli-0.0.77.1.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.76.6.dist-info → tunacode_cli-0.0.77.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -49,25 +49,25 @@ class BranchCommand(SimpleCommand):
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
class InitCommand(SimpleCommand):
|
|
52
|
-
"""Creates or updates
|
|
52
|
+
"""Creates or updates AGENTS.md with project-specific context."""
|
|
53
53
|
|
|
54
54
|
spec = CommandSpec(
|
|
55
55
|
name="/init",
|
|
56
56
|
aliases=[],
|
|
57
|
-
description="Analyze codebase and create/update
|
|
57
|
+
description="Analyze codebase and create/update AGENTS.md file",
|
|
58
58
|
category=CommandCategory.DEVELOPMENT,
|
|
59
59
|
)
|
|
60
60
|
|
|
61
61
|
async def execute(self, args, context: CommandContext) -> CommandResult:
|
|
62
62
|
"""Execute the init command."""
|
|
63
63
|
# Minimal implementation to make test pass
|
|
64
|
-
prompt = """Please analyze this codebase and create a
|
|
64
|
+
prompt = """Please analyze this codebase and create a AGENTS.md file containing:
|
|
65
65
|
1. Build/lint/test commands - especially for running a single test
|
|
66
66
|
2. Code style guidelines including imports, formatting, types, naming conventions, error handling, etc.
|
|
67
67
|
|
|
68
68
|
The file you create will be given to agentic coding agents (such as yourself) that operate in this repository.
|
|
69
69
|
Make it about 20 lines long.
|
|
70
|
-
If there's already a
|
|
70
|
+
If there's already a AGENTS.md, improve it.
|
|
71
71
|
If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md),
|
|
72
72
|
make sure to include them."""
|
|
73
73
|
|
|
@@ -32,13 +32,6 @@ CONFIG_KEY_DESCRIPTIONS: Dict[str, KeyDescription] = {
|
|
|
32
32
|
help_text="Format: provider:model-name. Examples: openai:gpt-4, anthropic:claude-3-sonnet, google:gemini-pro",
|
|
33
33
|
category="AI Models",
|
|
34
34
|
),
|
|
35
|
-
"skip_git_safety": KeyDescription(
|
|
36
|
-
name="skip_git_safety",
|
|
37
|
-
description="Skip Git safety checks when making changes",
|
|
38
|
-
example=True,
|
|
39
|
-
help_text="When true, TunaCode won't create safety branches before making changes. Use with caution!",
|
|
40
|
-
category="Safety Settings",
|
|
41
|
-
),
|
|
42
35
|
# Environment variables (API Keys)
|
|
43
36
|
"env.OPENAI_API_KEY": KeyDescription(
|
|
44
37
|
name="OPENAI_API_KEY",
|
|
@@ -108,8 +101,8 @@ CONFIG_KEY_DESCRIPTIONS: Dict[str, KeyDescription] = {
|
|
|
108
101
|
"settings.guide_file": KeyDescription(
|
|
109
102
|
name="guide_file",
|
|
110
103
|
description="Name of your project guide file",
|
|
111
|
-
example="
|
|
112
|
-
help_text="TunaCode looks for this file to understand your project. Usually
|
|
104
|
+
example="AGENTS.md",
|
|
105
|
+
help_text="TunaCode looks for this file to understand your project. Usually AGENTS.md or README.md.",
|
|
113
106
|
category="Project Settings",
|
|
114
107
|
),
|
|
115
108
|
"settings.fallback_response": KeyDescription(
|
tunacode/constants.py
CHANGED
|
@@ -9,12 +9,12 @@ from enum import Enum
|
|
|
9
9
|
|
|
10
10
|
# Application info
|
|
11
11
|
APP_NAME = "TunaCode"
|
|
12
|
-
APP_VERSION = "0.0.
|
|
12
|
+
APP_VERSION = "0.0.77.1"
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
# File patterns
|
|
16
16
|
GUIDE_FILE_PATTERN = "{name}.md"
|
|
17
|
-
GUIDE_FILE_NAME = "
|
|
17
|
+
GUIDE_FILE_NAME = "AGENTS.md"
|
|
18
18
|
ENV_FILE = ".env"
|
|
19
19
|
CONFIG_FILE_NAME = "tunacode.json"
|
|
20
20
|
|
tunacode/context.py
CHANGED
|
@@ -50,16 +50,16 @@ async def get_directory_structure(max_depth: int = 3) -> str:
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
async def get_code_style() -> str:
|
|
53
|
-
"""Concatenate contents of all
|
|
53
|
+
"""Concatenate contents of all AGENTS.md files up the directory tree."""
|
|
54
54
|
parts: List[str] = []
|
|
55
55
|
current = Path.cwd()
|
|
56
56
|
while True:
|
|
57
|
-
file = current / "
|
|
57
|
+
file = current / "AGENTS.md"
|
|
58
58
|
if file.exists():
|
|
59
59
|
try:
|
|
60
60
|
parts.append(file.read_text(encoding="utf-8"))
|
|
61
61
|
except Exception as e:
|
|
62
|
-
logger.debug(f"Failed to read
|
|
62
|
+
logger.debug(f"Failed to read AGENTS.md at {file}: {e}")
|
|
63
63
|
if current == current.parent:
|
|
64
64
|
break
|
|
65
65
|
current = current.parent
|
|
@@ -67,5 +67,5 @@ async def get_code_style() -> str:
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
async def get_claude_files() -> List[str]:
|
|
70
|
-
"""Return a list of additional
|
|
71
|
-
return ripgrep("
|
|
70
|
+
"""Return a list of additional AGENTS.md files in the repo."""
|
|
71
|
+
return ripgrep("AGENTS.md", ".")
|
|
@@ -13,6 +13,7 @@ from .agent_helpers import (
|
|
|
13
13
|
get_tool_description,
|
|
14
14
|
get_tool_summary,
|
|
15
15
|
get_user_prompt_part_class,
|
|
16
|
+
handle_empty_response,
|
|
16
17
|
)
|
|
17
18
|
from .json_tool_parser import extract_and_execute_tool_calls, parse_json_tool_calls
|
|
18
19
|
from .message_handler import get_model_messages, patch_tool_messages
|
|
@@ -47,6 +48,7 @@ __all__ = [
|
|
|
47
48
|
"get_tool_description",
|
|
48
49
|
"get_tool_summary",
|
|
49
50
|
"get_user_prompt_part_class",
|
|
51
|
+
"handle_empty_response",
|
|
50
52
|
"stream_model_request_node",
|
|
51
53
|
"get_batch_description",
|
|
52
54
|
]
|
|
@@ -30,6 +30,9 @@ _TUNACODE_CACHE: Dict[str, Tuple[str, float]] = {}
|
|
|
30
30
|
_AGENT_CACHE: Dict[ModelName, PydanticAgent] = {}
|
|
31
31
|
_AGENT_CACHE_VERSION: Dict[ModelName, int] = {}
|
|
32
32
|
|
|
33
|
+
_PROMPT_FILENAMES: Tuple[str, ...] = ("system.xml", "system.md", "system.txt")
|
|
34
|
+
_DEFAULT_SYSTEM_PROMPT = "You are a helpful AI assistant."
|
|
35
|
+
|
|
33
36
|
|
|
34
37
|
def clear_all_caches():
|
|
35
38
|
"""Clear all module-level caches. Useful for testing."""
|
|
@@ -46,55 +49,55 @@ def get_agent_tool():
|
|
|
46
49
|
return Agent, Tool
|
|
47
50
|
|
|
48
51
|
|
|
49
|
-
def
|
|
50
|
-
"""
|
|
51
|
-
prompt_path = base_path / "prompts" / "system.md"
|
|
52
|
+
def _read_prompt_from_path(prompt_path: Path) -> str:
|
|
53
|
+
"""Return prompt content from disk, leveraging the cache when possible."""
|
|
52
54
|
cache_key = str(prompt_path)
|
|
53
55
|
|
|
54
|
-
# Check cache with file modification time
|
|
55
56
|
try:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if current_mtime == cached_mtime:
|
|
60
|
-
return cached_content
|
|
57
|
+
current_mtime = prompt_path.stat().st_mtime
|
|
58
|
+
except FileNotFoundError as error:
|
|
59
|
+
raise FileNotFoundError from error
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
if cache_key in _PROMPT_CACHE:
|
|
62
|
+
cached_content, cached_mtime = _PROMPT_CACHE[cache_key]
|
|
63
|
+
if current_mtime == cached_mtime:
|
|
64
|
+
return cached_content
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
content = prompt_path.read_text(encoding="utf-8").strip()
|
|
68
|
+
except FileNotFoundError as error:
|
|
69
|
+
raise FileNotFoundError from error
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
prompt_path = base_path / "prompts" / "system.txt"
|
|
71
|
-
cache_key = str(prompt_path)
|
|
71
|
+
_PROMPT_CACHE[cache_key] = (content, current_mtime)
|
|
72
|
+
return content
|
|
72
73
|
|
|
73
|
-
try:
|
|
74
|
-
if cache_key in _PROMPT_CACHE:
|
|
75
|
-
cached_content, cached_mtime = _PROMPT_CACHE[cache_key]
|
|
76
|
-
current_mtime = prompt_path.stat().st_mtime
|
|
77
|
-
if current_mtime == cached_mtime:
|
|
78
|
-
return cached_content
|
|
79
74
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return content
|
|
75
|
+
def load_system_prompt(base_path: Path) -> str:
|
|
76
|
+
"""Load the system prompt from file with caching."""
|
|
77
|
+
prompts_dir = base_path / "prompts"
|
|
84
78
|
|
|
79
|
+
for prompt_name in _PROMPT_FILENAMES:
|
|
80
|
+
prompt_path = prompts_dir / prompt_name
|
|
81
|
+
if not prompt_path.exists():
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
return _read_prompt_from_path(prompt_path)
|
|
85
86
|
except FileNotFoundError:
|
|
86
|
-
#
|
|
87
|
-
|
|
87
|
+
# File disappeared between exists() check and read. Try next candidate.
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
return _DEFAULT_SYSTEM_PROMPT
|
|
88
91
|
|
|
89
92
|
|
|
90
93
|
def load_tunacode_context() -> str:
|
|
91
|
-
"""Load
|
|
94
|
+
"""Load AGENTS.md context if it exists with caching."""
|
|
92
95
|
try:
|
|
93
|
-
tunacode_path = Path.cwd() / "
|
|
96
|
+
tunacode_path = Path.cwd() / "AGENTS.md"
|
|
94
97
|
cache_key = str(tunacode_path)
|
|
95
98
|
|
|
96
99
|
if not tunacode_path.exists():
|
|
97
|
-
logger.info("📄
|
|
100
|
+
logger.info("📄 AGENTS.md not found: Using default context")
|
|
98
101
|
return ""
|
|
99
102
|
|
|
100
103
|
# Check cache with file modification time
|
|
@@ -107,17 +110,17 @@ def load_tunacode_context() -> str:
|
|
|
107
110
|
# Load from file and cache
|
|
108
111
|
tunacode_content = tunacode_path.read_text(encoding="utf-8")
|
|
109
112
|
if tunacode_content.strip():
|
|
110
|
-
logger.info("📄
|
|
111
|
-
result = "\n\n# Project Context from
|
|
113
|
+
logger.info("📄 AGENTS.md located: Loading context...")
|
|
114
|
+
result = "\n\n# Project Context from AGENTS.md\n" + tunacode_content
|
|
112
115
|
_TUNACODE_CACHE[cache_key] = (result, tunacode_path.stat().st_mtime)
|
|
113
116
|
return result
|
|
114
117
|
else:
|
|
115
|
-
logger.info("📄
|
|
118
|
+
logger.info("📄 AGENTS.md not found: Using default context")
|
|
116
119
|
_TUNACODE_CACHE[cache_key] = ("", tunacode_path.stat().st_mtime)
|
|
117
120
|
return ""
|
|
118
121
|
|
|
119
122
|
except Exception as e:
|
|
120
|
-
logger.debug(f"Error loading
|
|
123
|
+
logger.debug(f"Error loading AGENTS.md: {e}")
|
|
121
124
|
return ""
|
|
122
125
|
|
|
123
126
|
|
|
@@ -164,7 +167,7 @@ def get_or_create_agent(model: ModelName, state_manager: StateManager) -> Pydant
|
|
|
164
167
|
base_path = Path(__file__).parent.parent.parent.parent
|
|
165
168
|
system_prompt = load_system_prompt(base_path)
|
|
166
169
|
|
|
167
|
-
# Load
|
|
170
|
+
# Load AGENTS.md context
|
|
168
171
|
system_prompt += load_tunacode_context()
|
|
169
172
|
|
|
170
173
|
# Add plan mode context if in plan mode
|
|
@@ -201,6 +201,33 @@ def create_fallback_response(
|
|
|
201
201
|
return fallback
|
|
202
202
|
|
|
203
203
|
|
|
204
|
+
async def handle_empty_response(
|
|
205
|
+
message: str,
|
|
206
|
+
reason: str,
|
|
207
|
+
iter_index: int,
|
|
208
|
+
state: Any,
|
|
209
|
+
) -> None:
|
|
210
|
+
"""Handle empty responses by creating a synthetic user message with retry guidance."""
|
|
211
|
+
from tunacode.ui import console as ui
|
|
212
|
+
|
|
213
|
+
force_action_content = create_empty_response_message(
|
|
214
|
+
message,
|
|
215
|
+
reason,
|
|
216
|
+
getattr(state.sm.session, "tool_calls", []),
|
|
217
|
+
iter_index,
|
|
218
|
+
state.sm,
|
|
219
|
+
)
|
|
220
|
+
create_user_message(force_action_content, state.sm)
|
|
221
|
+
|
|
222
|
+
if state.show_thoughts:
|
|
223
|
+
await ui.warning("\nEMPTY RESPONSE FAILURE - AGGRESSIVE RETRY TRIGGERED")
|
|
224
|
+
await ui.muted(f" Reason: {reason}")
|
|
225
|
+
await ui.muted(
|
|
226
|
+
f" Recent tools: {get_recent_tools_context(getattr(state.sm.session, 'tool_calls', []))}"
|
|
227
|
+
)
|
|
228
|
+
await ui.muted(" Injecting retry guidance prompt")
|
|
229
|
+
|
|
230
|
+
|
|
204
231
|
def format_fallback_output(fallback: FallbackResponse) -> str:
|
|
205
232
|
"""Format a fallback response into a comprehensive output string."""
|
|
206
233
|
output_parts = [fallback.summary, ""]
|
tunacode/core/agents/main.py
CHANGED
|
@@ -31,20 +31,10 @@ from tunacode.types import (
|
|
|
31
31
|
ToolCallback,
|
|
32
32
|
UsageTrackerProtocol,
|
|
33
33
|
)
|
|
34
|
-
from tunacode.ui.tool_descriptions import get_batch_description
|
|
35
|
-
|
|
36
|
-
# Optional UI console (avoid nested imports in hot paths)
|
|
37
|
-
try:
|
|
38
|
-
from tunacode.ui import console as ui # rich-style helpers with async methods
|
|
39
|
-
except Exception: # pragma: no cover - UI is optional
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
async def success(self, *_: Any, **__: Any) -> None: ...
|
|
45
|
-
async def update_spinner_message(self, *_: Any, **__: Any) -> None: ...
|
|
46
|
-
|
|
47
|
-
ui = _NoopUI() # type: ignore
|
|
35
|
+
# CLAUDE_ANCHOR[key=d595ceb5] Direct UI console import aligns with removal of defensive shim
|
|
36
|
+
from tunacode.ui import console as ui
|
|
37
|
+
from tunacode.ui.tool_descriptions import get_batch_description
|
|
48
38
|
|
|
49
39
|
# Streaming parts (keep guarded import but avoid per-iteration imports)
|
|
50
40
|
try:
|
|
@@ -56,16 +46,10 @@ except Exception: # pragma: no cover
|
|
|
56
46
|
TextPartDelta = None # type: ignore
|
|
57
47
|
STREAMING_AVAILABLE = False
|
|
58
48
|
|
|
59
|
-
# Agent components (flattned to a single module import to reduce coupling)
|
|
60
49
|
from . import agent_components as ac
|
|
61
50
|
|
|
62
|
-
# Configure logging
|
|
63
51
|
logger = get_logger(__name__)
|
|
64
52
|
|
|
65
|
-
|
|
66
|
-
# -----------------------
|
|
67
|
-
# Module exports
|
|
68
|
-
# -----------------------
|
|
69
53
|
__all__ = [
|
|
70
54
|
"process_request",
|
|
71
55
|
"get_mcp_servers",
|
|
@@ -73,9 +57,6 @@ __all__ = [
|
|
|
73
57
|
"check_query_satisfaction",
|
|
74
58
|
]
|
|
75
59
|
|
|
76
|
-
# -----------------------
|
|
77
|
-
# Constants & Defaults
|
|
78
|
-
# -----------------------
|
|
79
60
|
DEFAULT_MAX_ITERATIONS = 15
|
|
80
61
|
UNPRODUCTIVE_LIMIT = 3 # iterations without tool use before forcing action
|
|
81
62
|
FALLBACK_VERBOSITY_DEFAULT = "normal"
|
|
@@ -84,9 +65,6 @@ FORCED_REACT_INTERVAL = 2
|
|
|
84
65
|
FORCED_REACT_LIMIT = 5
|
|
85
66
|
|
|
86
67
|
|
|
87
|
-
# -----------------------
|
|
88
|
-
# Data structures
|
|
89
|
-
# -----------------------
|
|
90
68
|
@dataclass(slots=True)
|
|
91
69
|
class RequestContext:
|
|
92
70
|
request_id: str
|
|
@@ -96,12 +74,11 @@ class RequestContext:
|
|
|
96
74
|
|
|
97
75
|
|
|
98
76
|
class StateFacade:
|
|
99
|
-
"""
|
|
77
|
+
"""wrapper to centralize session mutations and reads."""
|
|
100
78
|
|
|
101
79
|
def __init__(self, state_manager: StateManager) -> None:
|
|
102
80
|
self.sm = state_manager
|
|
103
81
|
|
|
104
|
-
# ---- safe getters ----
|
|
105
82
|
def get_setting(self, dotted: str, default: Any) -> Any:
|
|
106
83
|
cfg: Dict[str, Any] = getattr(self.sm.session, "user_config", {}) or {}
|
|
107
84
|
node = cfg
|
|
@@ -119,7 +96,6 @@ class StateFacade:
|
|
|
119
96
|
def messages(self) -> list:
|
|
120
97
|
return list(getattr(self.sm.session, "messages", []))
|
|
121
98
|
|
|
122
|
-
# ---- safe setters ----
|
|
123
99
|
def set_request_id(self, req_id: str) -> None:
|
|
124
100
|
try:
|
|
125
101
|
self.sm.session.request_id = req_id
|
|
@@ -160,9 +136,6 @@ class StateFacade:
|
|
|
160
136
|
setattr(self.sm.session, "consecutive_empty_responses", 0)
|
|
161
137
|
|
|
162
138
|
|
|
163
|
-
# -----------------------
|
|
164
|
-
# Helper functions
|
|
165
|
-
# -----------------------
|
|
166
139
|
def _init_context(state: StateFacade, fallback_enabled: bool) -> RequestContext:
|
|
167
140
|
req_id = str(uuid.uuid4())[:8]
|
|
168
141
|
state.set_request_id(req_id)
|
|
@@ -292,30 +265,6 @@ async def _maybe_force_react_snapshot(
|
|
|
292
265
|
logger.debug("Forced react snapshot failed", exc_info=True)
|
|
293
266
|
|
|
294
267
|
|
|
295
|
-
async def _handle_empty_response(
|
|
296
|
-
message: str,
|
|
297
|
-
reason: str,
|
|
298
|
-
iter_index: int,
|
|
299
|
-
state: StateFacade,
|
|
300
|
-
) -> None:
|
|
301
|
-
force_action_content = ac.create_empty_response_message(
|
|
302
|
-
message,
|
|
303
|
-
reason,
|
|
304
|
-
getattr(state.sm.session, "tool_calls", []),
|
|
305
|
-
iter_index,
|
|
306
|
-
state.sm,
|
|
307
|
-
)
|
|
308
|
-
ac.create_user_message(force_action_content, state.sm)
|
|
309
|
-
|
|
310
|
-
if state.show_thoughts:
|
|
311
|
-
await ui.warning("\nEMPTY RESPONSE FAILURE - AGGRESSIVE RETRY TRIGGERED")
|
|
312
|
-
await ui.muted(f" Reason: {reason}")
|
|
313
|
-
await ui.muted(
|
|
314
|
-
f" Recent tools: {ac.get_recent_tools_context(getattr(state.sm.session, 'tool_calls', []))}"
|
|
315
|
-
)
|
|
316
|
-
await ui.muted(" Injecting retry guidance prompt")
|
|
317
|
-
|
|
318
|
-
|
|
319
268
|
async def _force_action_if_unproductive(
|
|
320
269
|
message: str,
|
|
321
270
|
unproductive_count: int,
|
|
@@ -445,9 +394,6 @@ def _build_fallback_output(
|
|
|
445
394
|
return ac.format_fallback_output(fallback)
|
|
446
395
|
|
|
447
396
|
|
|
448
|
-
# -----------------------
|
|
449
|
-
# Public API
|
|
450
|
-
# -----------------------
|
|
451
397
|
def get_agent_tool() -> tuple[type[Agent], type["Tool"]]:
|
|
452
398
|
"""Return Agent and Tool classes without importing at module load time."""
|
|
453
399
|
from pydantic_ai import Agent as AgentCls
|
|
@@ -526,7 +472,7 @@ async def process_request(
|
|
|
526
472
|
# Handle empty response (aggressive retry prompt)
|
|
527
473
|
if empty_response:
|
|
528
474
|
if state.increment_empty_response() >= 1:
|
|
529
|
-
await
|
|
475
|
+
await ac.handle_empty_response(message, empty_reason, i, state)
|
|
530
476
|
state.clear_empty_response()
|
|
531
477
|
else:
|
|
532
478
|
state.clear_empty_response()
|
|
@@ -601,11 +547,7 @@ async def process_request(
|
|
|
601
547
|
"Progress summary:\n"
|
|
602
548
|
f"- Tools used: {tools_str}\n"
|
|
603
549
|
f"- Iterations completed: {i}\n\n"
|
|
604
|
-
"
|
|
605
|
-
"1. Continue working (extend limit)\n"
|
|
606
|
-
"2. Summarize what I've done and stop\n"
|
|
607
|
-
"3. Try a different approach\n\n"
|
|
608
|
-
"Please let me know how to proceed."
|
|
550
|
+
"Plese add more context to the task."
|
|
609
551
|
)
|
|
610
552
|
ac.create_user_message(extend_content, state.sm)
|
|
611
553
|
if state.show_thoughts:
|
|
@@ -617,7 +559,6 @@ async def process_request(
|
|
|
617
559
|
|
|
618
560
|
i += 1
|
|
619
561
|
|
|
620
|
-
# Final buffered read-only tasks (batch)
|
|
621
562
|
await _finalize_buffered_tasks(tool_buffer, tool_callback, state)
|
|
622
563
|
|
|
623
564
|
# Build fallback synthesis if needed
|
tunacode/core/setup/__init__.py
CHANGED
|
@@ -3,7 +3,6 @@ from .base import BaseSetup
|
|
|
3
3
|
from .config_setup import ConfigSetup
|
|
4
4
|
from .coordinator import SetupCoordinator
|
|
5
5
|
from .environment_setup import EnvironmentSetup
|
|
6
|
-
from .git_safety_setup import GitSafetySetup
|
|
7
6
|
from .template_setup import TemplateSetup
|
|
8
7
|
|
|
9
8
|
__all__ = [
|
|
@@ -11,7 +10,6 @@ __all__ = [
|
|
|
11
10
|
"SetupCoordinator",
|
|
12
11
|
"ConfigSetup",
|
|
13
12
|
"EnvironmentSetup",
|
|
14
|
-
"GitSafetySetup",
|
|
15
13
|
"AgentSetup",
|
|
16
14
|
"TemplateSetup",
|
|
17
15
|
]
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
<instructions>
|
|
3
2
|
You are "TunaCode", a senior software developer AI assistant operating inside the user's terminal.
|
|
4
3
|
|
|
5
4
|
YOU ARE NOT A CHATBOT. YOU ARE AN OPERATIONAL EXPERIENCED DEVELOPER WITH AGENT WITH TOOLS.
|
|
@@ -18,9 +17,10 @@ CRITICAL BEHAVIOR RULES:
|
|
|
18
17
|
9. Prefer sequential simplicity: break complex tasks into clear, interactive steps and confirm assumptions.
|
|
19
18
|
10. Use affirmative directives and directive phrasing in your own planning: "Your task is...", "You MUST..." when restating goals.
|
|
20
19
|
11. you MUST follow best practises, you will be punished for cheap bandaid fixes. ALWAYS aim to fix issues properly.
|
|
20
|
+
</instructions>
|
|
21
21
|
|
|
22
22
|
### Completion Signaling
|
|
23
|
-
|
|
23
|
+
<completion>
|
|
24
24
|
When you have fully completed the user’s task:
|
|
25
25
|
|
|
26
26
|
- Start your response with a single line: `TUNACODE DONE:` followed by a brief outcome summary.
|
|
@@ -28,9 +28,10 @@ When you have fully completed the user’s task:
|
|
|
28
28
|
- Do NOT mark DONE if you have queued tools in the same response — execute tools first, then mark DONE.
|
|
29
29
|
- Example:
|
|
30
30
|
- `TUNACODE DONE: Implemented enum state machine and updated completion logic`
|
|
31
|
+
</completion>
|
|
31
32
|
|
|
32
33
|
###Tool Access Rules###
|
|
33
|
-
|
|
34
|
+
<tools>
|
|
34
35
|
You have 9 powerful tools at your disposal. Understanding their categories is CRITICAL for performance:
|
|
35
36
|
|
|
36
37
|
READONLY TOOLS (Safe, ParallelExecutable)
|
|
@@ -72,11 +73,11 @@ These tools modify state and MUST run one at a time with user confirmation:
|
|
|
72
73
|
9. `bash(command: str)` — Advanced shell with environment control
|
|
73
74
|
Safety: Enhanced security, output limits (5KB)
|
|
74
75
|
Use for: Complex scripts, interactive commands
|
|
76
|
+
</tools>
|
|
75
77
|
|
|
76
78
|
|
|
77
79
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
<examples>
|
|
80
81
|
CRITICAL: These examples show EXACTLY how to use each tool. Study them carefully.
|
|
81
82
|
|
|
82
83
|
1. read_file Reading File Contents
|
|
@@ -284,6 +285,7 @@ bash("echo $PATH && which python && python --version")
|
|
|
284
285
|
bash("python -m venv venv && source venv/bin/activate && pip list")
|
|
285
286
|
→ Returns: Installed packages in new venv
|
|
286
287
|
```
|
|
288
|
+
</examples>
|
|
287
289
|
|
|
288
290
|
REMEMBER:
|
|
289
291
|
Always use these exact patterns
|
|
@@ -337,7 +339,7 @@ Tool Selection Quick Guide:
|
|
|
337
339
|
Need to run commands? → `run_command` (simple) or `bash` (complex)
|
|
338
340
|
|
|
339
341
|
### CRITICAL JSON FORMATTING RULES ###
|
|
340
|
-
|
|
342
|
+
<formatting>
|
|
341
343
|
**TOOL ARGUMENT JSON RULES - MUST FOLLOW EXACTLY:**
|
|
342
344
|
|
|
343
345
|
1. **ALWAYS emit exactly ONE JSON object per tool call**
|
|
@@ -363,6 +365,7 @@ read_file({"filepath": "main.py"}{"filepath": "config.py"})
|
|
|
363
365
|
```
|
|
364
366
|
|
|
365
367
|
**VALIDATION:** Every tool argument must parse as a single, valid JSON object. Concatenated objects will cause tool execution failures.
|
|
368
|
+
</formatting>
|
|
366
369
|
|
|
367
370
|
OUTPUT AND STYLE RULES:
|
|
368
371
|
1. Directness: Keep responses short and to the point. Avoid polite filler.
|
tunacode/setup.py
CHANGED
|
@@ -11,7 +11,6 @@ from tunacode.core.setup import (
|
|
|
11
11
|
AgentSetup,
|
|
12
12
|
ConfigSetup,
|
|
13
13
|
EnvironmentSetup,
|
|
14
|
-
GitSafetySetup,
|
|
15
14
|
SetupCoordinator,
|
|
16
15
|
TemplateSetup,
|
|
17
16
|
)
|
|
@@ -39,7 +38,6 @@ async def setup(
|
|
|
39
38
|
coordinator.register_step(config_setup)
|
|
40
39
|
coordinator.register_step(EnvironmentSetup(state_manager))
|
|
41
40
|
coordinator.register_step(TemplateSetup(state_manager))
|
|
42
|
-
coordinator.register_step(GitSafetySetup(state_manager))
|
|
43
41
|
|
|
44
42
|
# Run all setup steps
|
|
45
43
|
await coordinator.run_setup(force_setup=run_setup, wizard_mode=wizard_mode)
|
tunacode/tools/grep.py
CHANGED
|
@@ -281,7 +281,6 @@ Usage:
|
|
|
281
281
|
def run_enhanced_ripgrep():
|
|
282
282
|
"""Execute ripgrep search using the new executor."""
|
|
283
283
|
start_time = time.time()
|
|
284
|
-
first_match_time = None
|
|
285
284
|
results = []
|
|
286
285
|
|
|
287
286
|
# Configure timeout from settings
|
|
@@ -306,17 +305,8 @@ Usage:
|
|
|
306
305
|
context_after=config.context_lines,
|
|
307
306
|
)
|
|
308
307
|
|
|
309
|
-
#
|
|
310
|
-
|
|
311
|
-
first_match_time = time.time() - start_time
|
|
312
|
-
|
|
313
|
-
# Check if we exceeded the first match deadline
|
|
314
|
-
if first_match_time > config.first_match_deadline:
|
|
315
|
-
if self._config.get("debug", False):
|
|
316
|
-
logger.debug(
|
|
317
|
-
f"Search exceeded first match deadline: {first_match_time:.2f}s"
|
|
318
|
-
)
|
|
319
|
-
raise TooBroadPatternError(pattern, config.first_match_deadline)
|
|
308
|
+
# Ripgrep doesn't provide timing info for first match, so we rely on
|
|
309
|
+
# the overall timeout mechanism instead of first_match_deadline
|
|
320
310
|
|
|
321
311
|
# Parse results
|
|
322
312
|
for result_line in search_results:
|
|
@@ -363,10 +353,7 @@ Usage:
|
|
|
363
353
|
)
|
|
364
354
|
|
|
365
355
|
if self._config.get("debug", False):
|
|
366
|
-
logger.debug(
|
|
367
|
-
f"Ripgrep search completed in {total_time:.2f}s "
|
|
368
|
-
f"(first match: {first_match_time:.2f}s if found)"
|
|
369
|
-
)
|
|
356
|
+
logger.debug(f"Ripgrep search completed in {total_time:.2f}s")
|
|
370
357
|
|
|
371
358
|
return results
|
|
372
359
|
|
tunacode/ui/output.py
CHANGED
|
@@ -22,27 +22,16 @@ from .constants import SPINNER_TYPE
|
|
|
22
22
|
from .decorators import create_sync_wrapper
|
|
23
23
|
from .logging_compat import ui_logger
|
|
24
24
|
|
|
25
|
-
# Create console with explicit settings to ensure ANSI codes work properly
|
|
26
25
|
console = Console()
|
|
27
26
|
colors = DotDict(UI_COLORS)
|
|
28
27
|
|
|
29
28
|
BANNER = """[bold cyan]
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
██║ ██║ ██║██║╚██╗██║██╔══██║
|
|
34
|
-
██║ ╚██████╔╝██║ ╚████║██║ ██║
|
|
35
|
-
╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝
|
|
36
|
-
|
|
37
|
-
██████╗ ██████╗ ██████╗ ███████╗ dev
|
|
38
|
-
██╔════╝██╔═══██╗██╔══██╗██╔════╝
|
|
39
|
-
██║ ██║ ██║██║ ██║█████╗
|
|
40
|
-
██║ ██║ ██║██║ ██║██╔══╝
|
|
41
|
-
╚██████╗╚██████╔╝██████╔╝███████╗
|
|
42
|
-
╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
|
|
29
|
+
><>
|
|
30
|
+
><(((°>
|
|
31
|
+
><> TunaCode <>
|
|
43
32
|
[/bold cyan]
|
|
44
33
|
|
|
45
|
-
|
|
34
|
+
"""
|
|
46
35
|
|
|
47
36
|
|
|
48
37
|
@create_sync_wrapper
|
|
@@ -62,9 +62,9 @@ class ModelLimits(BaseModel):
|
|
|
62
62
|
if v is None:
|
|
63
63
|
return v
|
|
64
64
|
iv = int(v)
|
|
65
|
-
if iv
|
|
66
|
-
raise ValueError("limits must be
|
|
67
|
-
return iv
|
|
65
|
+
if iv < 0:
|
|
66
|
+
raise ValueError("limits must be non-negative integers")
|
|
67
|
+
return iv if iv > 0 else None
|
|
68
68
|
|
|
69
69
|
def format_limits(self) -> str:
|
|
70
70
|
"""Format limits as a readable string."""
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
tunacode/__init__.py,sha256=yUul8igNYMfUrHnYfioIGAqvrH8b5BKiO_pt1wVnmd0,119
|
|
2
|
-
tunacode/constants.py,sha256=
|
|
3
|
-
tunacode/context.py,sha256=
|
|
2
|
+
tunacode/constants.py,sha256=my0w0CGjHBr32c9QDlNDMpTqiBJExfTg9qNi61w82M0,6168
|
|
3
|
+
tunacode/context.py,sha256=4xMzqhSBqtTLuXnPBkJzn0SA61G5kAQytFvVY8TDnYY,2395
|
|
4
4
|
tunacode/exceptions.py,sha256=m80njR-LqBXhFAEOPqCE7N2QPU4Fkjlf_f6CWKO0_Is,8479
|
|
5
5
|
tunacode/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
tunacode/setup.py,sha256=
|
|
6
|
+
tunacode/setup.py,sha256=1GXwD1cNDkGcmylZbytdlGnnHzpsGIwnGpnN0OFp1F0,2022
|
|
7
7
|
tunacode/types.py,sha256=xNpDRjIRYg4qGNbl3EG8B13CWAWBoob9ekVm8_6dvnc,10496
|
|
8
8
|
tunacode/cli/__init__.py,sha256=zgs0UbAck8hfvhYsWhWOfBe5oK09ug2De1r4RuQZREA,55
|
|
9
9
|
tunacode/cli/main.py,sha256=MAKPFA4kGSFciqMdxnyp2r9XzVp8TfxvK6ztt7dvjwM,3445
|
|
@@ -16,7 +16,7 @@ tunacode/cli/commands/implementations/__init__.py,sha256=dFczjIqCJTPrsSycD6PZYnp
|
|
|
16
16
|
tunacode/cli/commands/implementations/command_reload.py,sha256=GyjeKvJbgE4VYkaasGajspdk9wffumZMNLzfCUeNazM,1555
|
|
17
17
|
tunacode/cli/commands/implementations/conversation.py,sha256=ZijCNaRi1p5v1Q-IaVHtU2_BripSW3JCVKTtqFkOUjg,4676
|
|
18
18
|
tunacode/cli/commands/implementations/debug.py,sha256=w2fUgqFB4ipBCmNotbvaOOVW4OiCwJM6MXNWlyKyoqs,6754
|
|
19
|
-
tunacode/cli/commands/implementations/development.py,sha256=
|
|
19
|
+
tunacode/cli/commands/implementations/development.py,sha256=Imm8LiDtE69CtLwdtIdOzhCE4FAFEBZJqQimnNtlPY0,2836
|
|
20
20
|
tunacode/cli/commands/implementations/model.py,sha256=dFRmMlcN78TdGMFX-B2OPyoWqOVQL72XC8ayPyUQmpA,16166
|
|
21
21
|
tunacode/cli/commands/implementations/plan.py,sha256=iZtvdGPqvGqMr8_lYil8_8NOL1iyc54Bxtb0gb9VOnw,1825
|
|
22
22
|
tunacode/cli/commands/implementations/quickstart.py,sha256=53H7ubYMGMgmCeYCs6o_F91Q4pd3Ky008lCU4GPuRP8,1363
|
|
@@ -36,7 +36,7 @@ tunacode/cli/repl_components/output_display.py,sha256=uzse2bhxSyCWnJD0Ni5lwnp0Bm
|
|
|
36
36
|
tunacode/cli/repl_components/tool_executor.py,sha256=IBzlyyJrVJwlmmIetBRli9aIPIJqB4xKfAtGZlvdOgY,3762
|
|
37
37
|
tunacode/configuration/__init__.py,sha256=MbVXy8bGu0yKehzgdgZ_mfWlYGvIdb1dY2Ly75nfuPE,17
|
|
38
38
|
tunacode/configuration/defaults.py,sha256=eFUDD73tTWa3HM320BEn0VWM-XuDKW7d6m32qTK2eRI,1313
|
|
39
|
-
tunacode/configuration/key_descriptions.py,sha256=
|
|
39
|
+
tunacode/configuration/key_descriptions.py,sha256=4sD2Up5URtq7ZIcCnt-tZmXrfinMET77MTKEAiEC7x4,11095
|
|
40
40
|
tunacode/configuration/models.py,sha256=buH8ZquvcYI3OQBDIZeJ08cu00rSCeNABtUwl3VQS0E,4103
|
|
41
41
|
tunacode/configuration/settings.py,sha256=9wtIWBlLhW_ZBlLx-GA4XDfVZyGj2Gs6Zk49vk-nHq0,1047
|
|
42
42
|
tunacode/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -44,11 +44,11 @@ tunacode/core/code_index.py,sha256=2qxEn2eTIegV4F_gLeZO5lAOv8mkf4Y_t21whZ9F2Fk,1
|
|
|
44
44
|
tunacode/core/state.py,sha256=4SzI_OPiL2VIDywtUavtE6jZnIJS7K2IVgj8AYcawW0,8706
|
|
45
45
|
tunacode/core/tool_handler.py,sha256=42yUfnq5jgk-0LK93JoJgtsXfVDTf-7hNXyKEfH2FM0,3626
|
|
46
46
|
tunacode/core/agents/__init__.py,sha256=ZOSvWhqBWX3ADzTUZ7ILY92xZ7VMXanjPQ_Sf37WYmU,1005
|
|
47
|
-
tunacode/core/agents/main.py,sha256=
|
|
47
|
+
tunacode/core/agents/main.py,sha256=IFKuGy-3t9XJDAs4rO0oTPZ1xGkZAXxoK1efdgIN15Q,23251
|
|
48
48
|
tunacode/core/agents/utils.py,sha256=cQJbpXzMcixJ1TKdLsEcpJHMMMXiH6_qcT3wbnSQ9gc,9411
|
|
49
|
-
tunacode/core/agents/agent_components/__init__.py,sha256=
|
|
50
|
-
tunacode/core/agents/agent_components/agent_config.py,sha256=
|
|
51
|
-
tunacode/core/agents/agent_components/agent_helpers.py,sha256=
|
|
49
|
+
tunacode/core/agents/agent_components/__init__.py,sha256=65V5ijSKen0F0zLvUO3AkZJmCrocSW3lEbqNPrHqxoc,1706
|
|
50
|
+
tunacode/core/agents/agent_components/agent_config.py,sha256=X4S5PCsndoHBd7EHHuNlwrXRN7Q2WuWtmEoC8w-2LKc,12921
|
|
51
|
+
tunacode/core/agents/agent_components/agent_helpers.py,sha256=m0sB0_ztHhRJYFnG2tmQyNmyZk_WnZExBE5jKIHN5lA,9121
|
|
52
52
|
tunacode/core/agents/agent_components/json_tool_parser.py,sha256=HuyNT0rs-ppx_gLAI2e0XMVGbR_F0WXZfP3sx38VoMg,3447
|
|
53
53
|
tunacode/core/agents/agent_components/message_handler.py,sha256=KJGOtb9VhumgZpxxwO45HrKLhU9_MwuoWRsSQwJviNU,3704
|
|
54
54
|
tunacode/core/agents/agent_components/node_processor.py,sha256=oOi3Yuccpdmnlza6vZRr_wB2_OczV0OLcqvs0a3BJcA,24734
|
|
@@ -68,20 +68,19 @@ tunacode/core/logging/config.py,sha256=bhJ6KYrEliKC5BehXKXZHHPBJUBX0g5O3uxbr8qUK
|
|
|
68
68
|
tunacode/core/logging/formatters.py,sha256=uWx-M0jSvsAVo5JVdCK1VIVawXNONjJ2CvMwPuuUTg8,1236
|
|
69
69
|
tunacode/core/logging/handlers.py,sha256=lkLczpcI6kSammSdjrCccosGMrRdcAA_3UmuTOiPnxg,3788
|
|
70
70
|
tunacode/core/logging/logger.py,sha256=9RjRuX0GoUojRJ8WnJGQPFdXiluiJMCoFmvc8xEioB8,142
|
|
71
|
-
tunacode/core/setup/__init__.py,sha256=
|
|
71
|
+
tunacode/core/setup/__init__.py,sha256=edzZ5tdWPdokPaOuFgYGEUGY_Fcn6bcWSiDOhGGZTBc,372
|
|
72
72
|
tunacode/core/setup/agent_setup.py,sha256=tpOIW85C6o1m8pwAZQBIMKxKIyBUOpHHn4JJmDBFH3Q,1403
|
|
73
73
|
tunacode/core/setup/base.py,sha256=FMjBQQS_q3KOxHqfg7NJGmKq-1nxC40htiPZprzTu7I,970
|
|
74
74
|
tunacode/core/setup/config_setup.py,sha256=j04mf4DAi_WLJid3h-ylomqQIybWbCMoPd_70JOEfEs,19158
|
|
75
75
|
tunacode/core/setup/config_wizard.py,sha256=nYlgq1Q645h8hsrD9VdhG933lPdxbDUNREAWYfozfaA,9361
|
|
76
76
|
tunacode/core/setup/coordinator.py,sha256=5ZhD4rHUrW0RIdGnjmoK4wCvqlNGcXal4Qwev4s039U,2393
|
|
77
77
|
tunacode/core/setup/environment_setup.py,sha256=n3IrObKEynHZSwtUJ1FddMg2C4sHz7ca42awemImV8s,2225
|
|
78
|
-
tunacode/core/setup/git_safety_setup.py,sha256=Htt8A4BAn7F4DbjhNu_SO01zjwaRQ3wMv-vZujE1-JA,7328
|
|
79
78
|
tunacode/core/setup/template_setup.py,sha256=0lDGhNVCvGN7ykqHnl3pj4CONH3I2PvMzkmIZibfSoc,2640
|
|
80
79
|
tunacode/core/token_usage/api_response_parser.py,sha256=plLltHg4zGVzxjv3MFj45bbd-NOJeT_v3P0Ki4zlvn4,1831
|
|
81
80
|
tunacode/core/token_usage/cost_calculator.py,sha256=RjO-O0JENBuGOrWP7QgBZlZxeXC-PAIr8tj_9p_BxOU,2058
|
|
82
81
|
tunacode/core/token_usage/usage_tracker.py,sha256=YUCnF-712nLrbtEvFrsC-VZuYjKUCz3hf-_do6GKSDA,6016
|
|
83
|
-
tunacode/prompts/system.md,sha256=Q9QhUzeHISdxHJNkufo5QNJH5P0b-b8qwhgrMuTc3Zk,13345
|
|
84
82
|
tunacode/prompts/system.md.bak,sha256=q0gbk_-pvQlNtZBonRo4gNILkKStqNxgDN0ZEwzC3E4,17541
|
|
83
|
+
tunacode/prompts/system.xml,sha256=IKGelP8ukqP3x-bNPCH_MJ7VcdMjrQjo_Mik89ZBKYM,13405
|
|
85
84
|
tunacode/services/__init__.py,sha256=w_E8QK6RnvKSvU866eDe8BCRV26rAm4d3R-Yg06OWCU,19
|
|
86
85
|
tunacode/services/mcp.py,sha256=quO13skECUGt-4QE2NkWk6_8qhmZ5qjgibvw8tUOt-4,3761
|
|
87
86
|
tunacode/templates/__init__.py,sha256=ssEOPrPjyCywtKI-QFcoqcWhMjlfI5TbI8Ip0_wyqGM,241
|
|
@@ -91,7 +90,7 @@ tunacode/tools/base.py,sha256=jQz_rz2rNZrKo2vZtyArwiHCMdAaqRYJGYtSZ27nxcU,10711
|
|
|
91
90
|
tunacode/tools/bash.py,sha256=fEjI5Vm7yqQiOzc83kFzu1n4zAPiWLQNHZYY-ORNV4Q,12437
|
|
92
91
|
tunacode/tools/exit_plan_mode.py,sha256=DOl_8CsY7h9N-SuCg2YgMjp8eEMuO5I8Tv8XjoJcTJ0,10597
|
|
93
92
|
tunacode/tools/glob.py,sha256=_uAMV5cloRP0AQMbm7h_bKeqfhe7KFoBx9gfYls5ZzE,22956
|
|
94
|
-
tunacode/tools/grep.py,sha256=
|
|
93
|
+
tunacode/tools/grep.py,sha256=5539a4VHNUhfyKBd51mRYIckOMpL1N82otHoTwu9w0Y,21545
|
|
95
94
|
tunacode/tools/list_dir.py,sha256=aJ2FdAUU-HxOmAwBk188KYIYB94thESIrSBflzoUlYs,12402
|
|
96
95
|
tunacode/tools/present_plan.py,sha256=PjpZ7Ll9T6Ij-oBNPK9iysvGJZpvKr1-lqBpURNXiLM,10856
|
|
97
96
|
tunacode/tools/react.py,sha256=qEXhtxFM3skoz__L9R0Rabt1bmKdNkRyFMyAgNB_TFo,5602
|
|
@@ -134,7 +133,7 @@ tunacode/ui/keybindings.py,sha256=8j58NN432XyawffssFNe86leXaPur12qBX3O7hOOGsc,23
|
|
|
134
133
|
tunacode/ui/lexers.py,sha256=tmg4ic1enyTRLzanN5QPP7D_0n12YjX_8ZhsffzhXA4,1340
|
|
135
134
|
tunacode/ui/logging_compat.py,sha256=5v6lcjVaG1CxdY1Zm9FAGr9H7Sy-tP6ihGfhP-5YvAY,1406
|
|
136
135
|
tunacode/ui/model_selector.py,sha256=07kV9v0VWFgDapiXTz1_BjzHF1AliRq24I9lDq-hmHc,13426
|
|
137
|
-
tunacode/ui/output.py,sha256=
|
|
136
|
+
tunacode/ui/output.py,sha256=YW0ABzZQbIE_jkDdrWVIeinTPuQUTnl-W638RUAqeoA,5569
|
|
138
137
|
tunacode/ui/panels.py,sha256=6XGOeax4m-yQvwP1iML67GK10AKlDLD46dB522gcNPU,17236
|
|
139
138
|
tunacode/ui/path_heuristics.py,sha256=SkhGaM8WCRuK86vLwypbfhtI81PrXtOsWoz-P0CTsmQ,2221
|
|
140
139
|
tunacode/ui/prompt_manager.py,sha256=HUL6443pFPb41uDAnAKD-sZsrWd_VhWYRGwvrFH_9SI,5618
|
|
@@ -151,7 +150,7 @@ tunacode/utils/file_utils.py,sha256=84g-MQRzmBI2aG_CuXsDl2OhvvWoSL7YdL5Kz_UKSwk,
|
|
|
151
150
|
tunacode/utils/import_cache.py,sha256=q_xjJbtju05YbFopLDSkIo1hOtCx3DOTl3GQE5FFDgs,295
|
|
152
151
|
tunacode/utils/json_utils.py,sha256=cMVctSwwV9Z1c-rZdj6UuOlZwsUPSTF5oUruP6uPix0,6470
|
|
153
152
|
tunacode/utils/message_utils.py,sha256=V4MrZZPmwO22_MVGupMqtE5ltQEBwaSIqGD5LEb_bLw,1050
|
|
154
|
-
tunacode/utils/models_registry.py,sha256=
|
|
153
|
+
tunacode/utils/models_registry.py,sha256=Tn2ByGFV1yJsWumFYy6JuT0eVpuPeZ1Zxj6JYsRRy1g,21277
|
|
155
154
|
tunacode/utils/retry.py,sha256=AHdUzY6m-mwlT4OPXdtWWMAafL_NeS7JAMORGyM8c5k,4931
|
|
156
155
|
tunacode/utils/ripgrep.py,sha256=VdGWYPQ1zCwUidw2QicuVmG5OiAgqI93jAsjS3y3ksE,11001
|
|
157
156
|
tunacode/utils/security.py,sha256=i3eGKg4o-qY2S_ObTlEaHO93q14iBfiPXR5O7srHn58,6579
|
|
@@ -159,8 +158,8 @@ tunacode/utils/system.py,sha256=J8KqJ4ZqQrNSnM5rrJxPeMk9z2xQQp6dWtI1SKBY1-0,1112
|
|
|
159
158
|
tunacode/utils/text_utils.py,sha256=HAwlT4QMy41hr53cDbbNeNo05MI461TpI9b_xdIv8EY,7288
|
|
160
159
|
tunacode/utils/token_counter.py,sha256=dmFuqVz4ywGFdLfAi5Mg9bAGf8v87Ek-mHU-R3fsYjI,2711
|
|
161
160
|
tunacode/utils/user_configuration.py,sha256=OA-L0BgWNbf9sWpc8lyivgLscwJdpdI8TAYbe0wRs1s,4836
|
|
162
|
-
tunacode_cli-0.0.
|
|
163
|
-
tunacode_cli-0.0.
|
|
164
|
-
tunacode_cli-0.0.
|
|
165
|
-
tunacode_cli-0.0.
|
|
166
|
-
tunacode_cli-0.0.
|
|
161
|
+
tunacode_cli-0.0.77.1.dist-info/METADATA,sha256=64_1eQhSU7--GGUc5RVSArPEfWhD0sJKAYmwuDF7dQs,8913
|
|
162
|
+
tunacode_cli-0.0.77.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
163
|
+
tunacode_cli-0.0.77.1.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
|
|
164
|
+
tunacode_cli-0.0.77.1.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
|
|
165
|
+
tunacode_cli-0.0.77.1.dist-info/RECORD,,
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
"""Git safety setup to create a working branch for TunaCode."""
|
|
2
|
-
|
|
3
|
-
import subprocess
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
from tunacode.core.setup.base import BaseSetup
|
|
7
|
-
from tunacode.core.state import StateManager
|
|
8
|
-
from tunacode.ui import console as ui
|
|
9
|
-
from tunacode.ui.input import input as prompt_input
|
|
10
|
-
from tunacode.ui.panels import panel
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
async def yes_no_prompt(question: str, default: bool = True) -> bool:
|
|
14
|
-
"""Simple yes/no prompt."""
|
|
15
|
-
default_text = "[Y/n]" if default else "[y/N]"
|
|
16
|
-
response = await prompt_input(session_key="yes_no", pretext=f"{question} {default_text}: ")
|
|
17
|
-
|
|
18
|
-
if not response.strip():
|
|
19
|
-
return default
|
|
20
|
-
|
|
21
|
-
return response.lower().strip() in ["y", "yes"]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class GitSafetySetup(BaseSetup):
|
|
25
|
-
"""Setup step to create a safe working branch for TunaCode."""
|
|
26
|
-
|
|
27
|
-
def __init__(self, state_manager: StateManager):
|
|
28
|
-
super().__init__(state_manager)
|
|
29
|
-
|
|
30
|
-
@property
|
|
31
|
-
def name(self) -> str:
|
|
32
|
-
"""Return the name of this setup step."""
|
|
33
|
-
return "Git Safety"
|
|
34
|
-
|
|
35
|
-
async def should_run(self, _force: bool = False) -> bool:
|
|
36
|
-
"""Check if we should run git safety setup."""
|
|
37
|
-
# Always run unless user has explicitly disabled it
|
|
38
|
-
return not self.state_manager.session.user_config.get("skip_git_safety", False)
|
|
39
|
-
|
|
40
|
-
async def execute(self, _force: bool = False, wizard_mode: bool = False) -> None:
|
|
41
|
-
"""Create a safety branch for TunaCode operations."""
|
|
42
|
-
# Skip git safety during wizard mode to avoid UI interference
|
|
43
|
-
if wizard_mode:
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
try:
|
|
47
|
-
# Check if git is installed
|
|
48
|
-
result = subprocess.run(
|
|
49
|
-
["git", "--version"], capture_output=True, text=True, check=False
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
if result.returncode != 0:
|
|
53
|
-
await panel(
|
|
54
|
-
" Git Not Found",
|
|
55
|
-
"Git is not installed or not in PATH. TunaCode will modify files directly.\n"
|
|
56
|
-
"It's strongly recommended to install Git for safety.",
|
|
57
|
-
border_style="yellow",
|
|
58
|
-
)
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
# Check if we're in a git repository
|
|
62
|
-
result = subprocess.run(
|
|
63
|
-
["git", "rev-parse", "--git-dir"],
|
|
64
|
-
capture_output=True,
|
|
65
|
-
text=True,
|
|
66
|
-
check=False,
|
|
67
|
-
cwd=Path.cwd(),
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
if result.returncode != 0:
|
|
71
|
-
await panel(
|
|
72
|
-
" Not a Git Repository",
|
|
73
|
-
"This directory is not a Git repository. TunaCode will modify files directly.\n"
|
|
74
|
-
"Consider initializing a Git repository for safety: git init",
|
|
75
|
-
border_style="yellow",
|
|
76
|
-
)
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
# Get current branch name
|
|
80
|
-
result = subprocess.run(
|
|
81
|
-
["git", "branch", "--show-current"], capture_output=True, text=True, check=True
|
|
82
|
-
)
|
|
83
|
-
current_branch = result.stdout.strip()
|
|
84
|
-
|
|
85
|
-
if not current_branch:
|
|
86
|
-
# Detached HEAD state
|
|
87
|
-
await panel(
|
|
88
|
-
" Detached HEAD State",
|
|
89
|
-
"You're in a detached HEAD state. TunaCode will continue without creating a branch.",
|
|
90
|
-
border_style="yellow",
|
|
91
|
-
)
|
|
92
|
-
return
|
|
93
|
-
|
|
94
|
-
# Check if we're already on a -tunacode branch
|
|
95
|
-
if current_branch.endswith("-tunacode"):
|
|
96
|
-
await ui.info(f"Already on a TunaCode branch: {current_branch}")
|
|
97
|
-
return
|
|
98
|
-
|
|
99
|
-
# Propose new branch name
|
|
100
|
-
new_branch = f"{current_branch}-tunacode"
|
|
101
|
-
|
|
102
|
-
# Check if there are uncommitted changes
|
|
103
|
-
result = subprocess.run(
|
|
104
|
-
["git", "status", "--porcelain"], capture_output=True, text=True, check=True
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
has_changes = bool(result.stdout.strip())
|
|
108
|
-
|
|
109
|
-
# Ask user if they want to create a safety branch
|
|
110
|
-
message = (
|
|
111
|
-
f"For safety, TunaCode can create a new branch '{new_branch}' based on '{current_branch}'.\n"
|
|
112
|
-
f"This helps protect your work from unintended changes.\n"
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
if has_changes:
|
|
116
|
-
message += "\n You have uncommitted changes that will be brought to the new branch."
|
|
117
|
-
|
|
118
|
-
create_branch = await yes_no_prompt(f"{message}\n\nCreate safety branch?", default=True)
|
|
119
|
-
|
|
120
|
-
if not create_branch:
|
|
121
|
-
# User declined - show warning
|
|
122
|
-
await panel(
|
|
123
|
-
" Working Without Safety Branch",
|
|
124
|
-
"You've chosen to work directly on your current branch.\n"
|
|
125
|
-
"TunaCode will modify files in place. Make sure you have backups!",
|
|
126
|
-
border_style="red",
|
|
127
|
-
)
|
|
128
|
-
# Save preference
|
|
129
|
-
self.state_manager.session.user_config["skip_git_safety"] = True
|
|
130
|
-
# Save the updated configuration to disk
|
|
131
|
-
try:
|
|
132
|
-
from tunacode.utils.user_configuration import save_config
|
|
133
|
-
|
|
134
|
-
save_config(self.state_manager)
|
|
135
|
-
except Exception as e:
|
|
136
|
-
# Log the error but don't fail the setup process
|
|
137
|
-
import logging
|
|
138
|
-
|
|
139
|
-
logging.warning(f"Failed to save skip_git_safety preference: {e}")
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
# Create and checkout the new branch
|
|
143
|
-
try:
|
|
144
|
-
# Check if branch already exists
|
|
145
|
-
result = subprocess.run(
|
|
146
|
-
["git", "show-ref", "--verify", f"refs/heads/{new_branch}"],
|
|
147
|
-
capture_output=True,
|
|
148
|
-
check=False,
|
|
149
|
-
text=True,
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
if result.returncode == 0:
|
|
153
|
-
# Branch exists, ask to use it
|
|
154
|
-
use_existing = await yes_no_prompt(
|
|
155
|
-
f"Branch '{new_branch}' already exists. Switch to it?", default=True
|
|
156
|
-
)
|
|
157
|
-
if use_existing:
|
|
158
|
-
subprocess.run(["git", "checkout", new_branch], check=True)
|
|
159
|
-
await ui.success(f"Switched to existing branch: {new_branch}")
|
|
160
|
-
else:
|
|
161
|
-
await ui.warning("Continuing on current branch")
|
|
162
|
-
else:
|
|
163
|
-
# Create new branch
|
|
164
|
-
subprocess.run(["git", "checkout", "-b", new_branch], check=True)
|
|
165
|
-
await ui.success(f"Created and switched to new branch: {new_branch}")
|
|
166
|
-
|
|
167
|
-
except subprocess.CalledProcessError as e:
|
|
168
|
-
await panel(
|
|
169
|
-
" Failed to Create Branch",
|
|
170
|
-
f"Could not create branch '{new_branch}': {str(e)}\n"
|
|
171
|
-
"Continuing on current branch.",
|
|
172
|
-
border_style="red",
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
except Exception as e:
|
|
176
|
-
# Non-fatal error - just warn the user
|
|
177
|
-
await panel(
|
|
178
|
-
" Git Safety Setup Failed",
|
|
179
|
-
f"Could not set up Git safety: {str(e)}\n"
|
|
180
|
-
"TunaCode will continue without branch protection.",
|
|
181
|
-
border_style="yellow",
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
async def validate(self) -> bool:
|
|
185
|
-
"""Validate git safety setup - always returns True as this is optional."""
|
|
186
|
-
return True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|