ata-coder 2.4.2__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.
Files changed (118) hide show
  1. ata_coder/__init__.py +1 -0
  2. ata_coder/agent.py +874 -0
  3. ata_coder/agent_compact.py +190 -0
  4. ata_coder/agent_controller.py +218 -0
  5. ata_coder/agent_extension.py +69 -0
  6. ata_coder/agent_routing.py +105 -0
  7. ata_coder/agent_subsystems.py +72 -0
  8. ata_coder/agent_tools.py +318 -0
  9. ata_coder/agent_undo.py +63 -0
  10. ata_coder/anthropic_client.py +465 -0
  11. ata_coder/change_tracker.py +368 -0
  12. ata_coder/clawd_integration.py +574 -0
  13. ata_coder/commands/__init__.py +128 -0
  14. ata_coder/commands/_core.py +184 -0
  15. ata_coder/commands/_safety.py +95 -0
  16. ata_coder/commands/_settings.py +241 -0
  17. ata_coder/commands/_workflow.py +451 -0
  18. ata_coder/commands.py +974 -0
  19. ata_coder/config.py +257 -0
  20. ata_coder/core/__init__.py +35 -0
  21. ata_coder/core/events.py +73 -0
  22. ata_coder/core/queue.py +85 -0
  23. ata_coder/core/state.py +17 -0
  24. ata_coder/event_queue.py +5 -0
  25. ata_coder/extension.py +654 -0
  26. ata_coder/extensions/__init__.py +1 -0
  27. ata_coder/extensions/hello_skill.py +47 -0
  28. ata_coder/fool_proof.py +295 -0
  29. ata_coder/git_workflow.py +371 -0
  30. ata_coder/gui.py +511 -0
  31. ata_coder/llm_client.py +543 -0
  32. ata_coder/main.py +814 -0
  33. ata_coder/mcp_client.py +1095 -0
  34. ata_coder/memory.py +539 -0
  35. ata_coder/model_registry.py +134 -0
  36. ata_coder/model_router.py +105 -0
  37. ata_coder/permissions.py +274 -0
  38. ata_coder/privilege.py +464 -0
  39. ata_coder/project.py +273 -0
  40. ata_coder/prompt_template.py +423 -0
  41. ata_coder/prompts/auto-mode.md +7 -0
  42. ata_coder/prompts/coding-rules.md +40 -0
  43. ata_coder/prompts/execution-guardrails.md +14 -0
  44. ata_coder/prompts/memory-system.md +24 -0
  45. ata_coder/prompts/output-style.md +23 -0
  46. ata_coder/prompts/safety.md +17 -0
  47. ata_coder/prompts/slash-commands.md +24 -0
  48. ata_coder/prompts/sub-agents.md +38 -0
  49. ata_coder/prompts/system-reminders.md +17 -0
  50. ata_coder/prompts/system.md +105 -0
  51. ata_coder/prompts/tool-policy.md +46 -0
  52. ata_coder/repl_theme.py +99 -0
  53. ata_coder/repl_tracker.py +89 -0
  54. ata_coder/repl_ui.py +1214 -0
  55. ata_coder/safety_guard.py +434 -0
  56. ata_coder/self_correct.py +346 -0
  57. ata_coder/server.py +882 -0
  58. ata_coder/server_session.py +159 -0
  59. ata_coder/server_shell.py +129 -0
  60. ata_coder/session.py +431 -0
  61. ata_coder/settings.py +439 -0
  62. ata_coder/setup_wizard.py +136 -0
  63. ata_coder/skill_extension.py +92 -0
  64. ata_coder/skills/architect/SKILL.md +42 -0
  65. ata_coder/skills/code-reviewer/SKILL.md +37 -0
  66. ata_coder/skills/codecraft/SKILL.md +452 -0
  67. ata_coder/skills/debugger/SKILL.md +45 -0
  68. ata_coder/skills/doc-writer/SKILL.md +36 -0
  69. ata_coder/skills/general-coder/SKILL.md +76 -0
  70. ata_coder/skills/math-calculator/README.md +40 -0
  71. ata_coder/skills/math-calculator/SKILL.md +59 -0
  72. ata_coder/skills/math-calculator/handler.py +103 -0
  73. ata_coder/skills/math-calculator/prompts/system.md +8 -0
  74. ata_coder/skills/math-calculator/requirements.txt +2 -0
  75. ata_coder/skills/math-calculator/resources/constants.json +8 -0
  76. ata_coder/skills/math-calculator/tests/test_handler.py +53 -0
  77. ata_coder/skills/security-auditor/SKILL.md +40 -0
  78. ata_coder/skills/test-writer/SKILL.md +36 -0
  79. ata_coder/skills/weather-skill/README.md +45 -0
  80. ata_coder/skills/weather-skill/handler.py +76 -0
  81. ata_coder/skills/weather-skill/manifest.json +48 -0
  82. ata_coder/skills/weather-skill/prompts/system_prompt.txt +9 -0
  83. ata_coder/skills/weather-skill/prompts/user_prompt_template.txt +3 -0
  84. ata_coder/skills/weather-skill/requirements.txt +1 -0
  85. ata_coder/skills/weather-skill/resources/city_list.json +17 -0
  86. ata_coder/skills/weather-skill/resources/error_messages.json +7 -0
  87. ata_coder/skills/weather-skill/tests/test_handler.py +28 -0
  88. ata_coder/skills/weather-skill/weather_utils.py +50 -0
  89. ata_coder/skills.py +1014 -0
  90. ata_coder/sub_agent.py +273 -0
  91. ata_coder/sub_agent_manager.py +203 -0
  92. ata_coder/system_prompt_builder.py +146 -0
  93. ata_coder/task_planner.py +391 -0
  94. ata_coder/terminal.py +318 -0
  95. ata_coder/test_runner.py +219 -0
  96. ata_coder/thread_supervisor.py +195 -0
  97. ata_coder/tool_defs.py +335 -0
  98. ata_coder/tools/__init__.py +11 -0
  99. ata_coder/tools/definitions.py +335 -0
  100. ata_coder/tools/executor.py +1036 -0
  101. ata_coder/tools/result.py +26 -0
  102. ata_coder/tools/subagent.py +332 -0
  103. ata_coder/tools/web.py +361 -0
  104. ata_coder/tools.py +1576 -0
  105. ata_coder/types.py +92 -0
  106. ata_coder/utils.py +113 -0
  107. ata_coder/web/css/style.css +180 -0
  108. ata_coder/web/index.html +84 -0
  109. ata_coder/web/js/app.js +489 -0
  110. ata_coder/web/package-lock.json +25 -0
  111. ata_coder/web/package.json +10 -0
  112. ata_coder/web/tsconfig.json +13 -0
  113. ata_coder-2.4.2.dist-info/METADATA +799 -0
  114. ata_coder-2.4.2.dist-info/RECORD +118 -0
  115. ata_coder-2.4.2.dist-info/WHEEL +5 -0
  116. ata_coder-2.4.2.dist-info/entry_points.txt +2 -0
  117. ata_coder-2.4.2.dist-info/licenses/LICENSE +21 -0
  118. ata_coder-2.4.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,346 @@
1
+ """
2
+ Self-Correction Loop — automatic error recovery.
3
+
4
+ When a tool returns an error, the agent:
5
+ 1. Reads the error message
6
+ 2. Diagnoses the root cause (common patterns)
7
+ 3. Suggests a fix
8
+ 4. Retries with corrected arguments (max 3 attempts)
9
+ 5. Learns from failures within the session
10
+
11
+ Common error patterns detected:
12
+ - File not found → suggest reading directory first
13
+ - old_string not found → suggest reading file again
14
+ - Command not found → suggest alternative or install
15
+ - Permission denied → suggest elevated privileges
16
+ - Syntax error → suggest reading linter output
17
+ - Import error → suggest installing missing package
18
+ - Network error → suggest retry or check connectivity
19
+ """
20
+
21
+ import logging
22
+ import re
23
+ import time
24
+ from dataclasses import dataclass
25
+ from typing import Any, Callable
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ # ═══════════════════════════════════════════════════════════════════════════════
31
+ # Error pattern → diagnosis + fix suggestion
32
+ # ═══════════════════════════════════════════════════════════════════════════════
33
+
34
+ @dataclass
35
+ class ErrorDiagnosis:
36
+ pattern: str
37
+ diagnosis: str
38
+ fix_suggestion: str
39
+ retry_strategy: str # "auto_fix" | "read_first" | "ask_user" | "skip"
40
+
41
+
42
+ # Common error patterns with auto-fix strategies.
43
+ # NOTE: "old_string not found" and "SyntaxError" removed — AST-based
44
+ # editing (libcst) eliminates these failures deterministically.
45
+ ERROR_PATTERNS: list[ErrorDiagnosis] = [
46
+ ErrorDiagnosis(
47
+ pattern=r"File not found",
48
+ diagnosis="The specified file does not exist at the given path.",
49
+ fix_suggestion="Check the file path. List the directory to find the correct path, or create the file first.",
50
+ retry_strategy="read_first",
51
+ ),
52
+ ErrorDiagnosis(
53
+ pattern=r"Permission denied|EACCES|Access is denied",
54
+ diagnosis="The current user does not have permission to access this file or run this command.",
55
+ fix_suggestion="Check file permissions. Consider using elevated privileges or choosing a different path.",
56
+ retry_strategy="ask_user",
57
+ ),
58
+ ErrorDiagnosis(
59
+ pattern=r"command not found|not recognized|No such file or directory",
60
+ diagnosis="The shell command is not available on this system.",
61
+ fix_suggestion="Check the command name. Consider installing the required tool or using an alternative.",
62
+ retry_strategy="ask_user",
63
+ ),
64
+ ErrorDiagnosis(
65
+ pattern=r"ModuleNotFoundError|ImportError|No module named",
66
+ diagnosis="A required Python module is not installed.",
67
+ fix_suggestion="Install the missing module with pip, or check the import path.",
68
+ retry_strategy="auto_fix",
69
+ ),
70
+ ErrorDiagnosis(
71
+ pattern=r"TypeError|TypeError:|wrong type|expected .* but got",
72
+ diagnosis="A function received an argument of the wrong type.",
73
+ fix_suggestion="Check the types of arguments being passed. Add type conversions if needed.",
74
+ retry_strategy="auto_fix",
75
+ ),
76
+ ErrorDiagnosis(
77
+ pattern=r"timeout|timed out|Timed out",
78
+ diagnosis="The operation took too long and timed out.",
79
+ fix_suggestion="Increase the timeout, break the task into smaller pieces, or check network connectivity.",
80
+ retry_strategy="auto_fix",
81
+ ),
82
+ ErrorDiagnosis(
83
+ pattern=r"rate limit|too many requests|429",
84
+ diagnosis="API rate limit has been exceeded.",
85
+ fix_suggestion="Wait and retry with exponential backoff. The client already handles this automatically.",
86
+ retry_strategy="auto_fix",
87
+ ),
88
+ ErrorDiagnosis(
89
+ pattern=r"connection refused|ConnectionError|NetworkError|could not connect",
90
+ diagnosis="Cannot connect to the remote service.",
91
+ fix_suggestion="Check that the service is running. Verify the URL and port. Check network/firewall.",
92
+ retry_strategy="ask_user",
93
+ ),
94
+ ]
95
+
96
+
97
+ # ═══════════════════════════════════════════════════════════════════════════════
98
+ # Retry tracker
99
+ # ═══════════════════════════════════════════════════════════════════════════════
100
+
101
+ @dataclass
102
+ class RetryAttempt:
103
+ attempt: int
104
+ tool_name: str
105
+ original_args: dict
106
+ error_message: str
107
+ diagnosis: ErrorDiagnosis | None
108
+ fixed_args: dict | None
109
+ success: bool = False
110
+ timestamp: float = 0.0
111
+
112
+
113
+ # ═══════════════════════════════════════════════════════════════════════════════
114
+ # Self-Correction Engine
115
+ # ═══════════════════════════════════════════════════════════════════════════════
116
+
117
+ class SelfCorrectionEngine:
118
+ """
119
+ Automatically diagnoses and recovers from tool execution errors.
120
+
121
+ Usage:
122
+ engine = SelfCorrectionEngine(max_retries=1) # AST editing makes retries rarely needed
123
+
124
+ result = execute_tool(name, args)
125
+ if not result.success:
126
+ diagnosis = engine.diagnose(result.error)
127
+ if diagnosis.retry_strategy == "auto_fix":
128
+ fixed_args = engine.suggest_fix(name, args, diagnosis)
129
+ result = execute_tool(name, fixed_args) # retry
130
+ """
131
+
132
+ def __init__(self, max_retries: int = 3):
133
+ self.max_retries = max_retries
134
+ self._attempts: list[RetryAttempt] = []
135
+ self._learned_patterns: dict[str, str] = {} # session learning
136
+
137
+ # ── Diagnosis ────────────────────────────────────────────────────────
138
+
139
+ def diagnose(self, error_message: str, tool_name: str = "",
140
+ arguments: dict | None = None) -> ErrorDiagnosis | None:
141
+ """
142
+ Analyze an error message and return a diagnosis with fix strategy.
143
+ """
144
+ # Check known patterns
145
+ for pattern in ERROR_PATTERNS:
146
+ if re.search(pattern.pattern, error_message, re.IGNORECASE):
147
+ logger.info("Diagnosed: %s → %s", pattern.pattern[:40], pattern.retry_strategy)
148
+ return pattern
149
+
150
+ # Check learned patterns from this session
151
+ for learned_pattern, fix in self._learned_patterns.items():
152
+ if re.search(learned_pattern, error_message, re.IGNORECASE):
153
+ return ErrorDiagnosis(
154
+ pattern=learned_pattern,
155
+ diagnosis="Learned from previous failure in this session",
156
+ fix_suggestion=fix,
157
+ retry_strategy="auto_fix",
158
+ )
159
+
160
+ # Unknown error — generic diagnosis
161
+ return ErrorDiagnosis(
162
+ pattern="unknown",
163
+ diagnosis=f"Unexpected error during {tool_name}",
164
+ fix_suggestion="Read the error carefully. Check the file, path, and arguments. Try a different approach.",
165
+ retry_strategy="ask_user",
166
+ )
167
+
168
+ # ── Fix suggestion ──────────────────────────────────────────────────
169
+
170
+ def suggest_fix(self, tool_name: str, arguments: dict,
171
+ diagnosis: ErrorDiagnosis,
172
+ error_message: str = "") -> dict | None:
173
+ """
174
+ Suggest corrected arguments based on the diagnosis.
175
+ *error_message* is the original error text (used to extract module
176
+ names, paths, etc.). Without it the fixer has nothing to work with.
177
+ Returns modified arguments dict, or None if no auto-fix is possible.
178
+ """
179
+ if diagnosis.retry_strategy == "ask_user":
180
+ return None
181
+
182
+ fixed = dict(arguments)
183
+
184
+ if tool_name == "edit_file":
185
+ if "old_string not found" in diagnosis.pattern:
186
+ # Can't auto-fix without re-reading the file
187
+ return None
188
+
189
+ elif tool_name == "read_file":
190
+ if "File not found" in diagnosis.pattern:
191
+ # Can't auto-fix — the file doesn't exist.
192
+ # Always return None to avoid a retry loop with the same args.
193
+ return None
194
+
195
+ elif tool_name == "run_shell":
196
+ cmd = arguments.get("command", "")
197
+ if "ModuleNotFoundError" in diagnosis.pattern or "No module named" in diagnosis.pattern:
198
+ # Extract module name from the actual error message.
199
+ # Match hyphenated names like 'python-dateutil' too.
200
+ match = re.search(r"No module named '([\w\-\.]+)'", error_message)
201
+ if not match:
202
+ match = re.search(r"No module named '([\w\-\.]+)'", cmd)
203
+ if match:
204
+ module = match.group(1)
205
+ fixed["command"] = f"pip install {module} && {cmd}"
206
+ return fixed
207
+
208
+ if "command not found" in diagnosis.pattern:
209
+ first_word = cmd.strip().split()[0] if cmd.strip() else ""
210
+ # Only suggest python -m for well-known tools that have Python entry points
211
+ _python_runnable = {"pip", "pytest", "mypy", "ruff", "black", "isort",
212
+ "uvicorn", "gunicorn", "jupyter", "coverage"}
213
+ if first_word in _python_runnable:
214
+ fixed["command"] = f"python -m {cmd}"
215
+ return fixed
216
+ # For other commands (git, npm, etc.), can't auto-fix
217
+ return None
218
+
219
+ if "timeout" in diagnosis.pattern:
220
+ fixed["timeout"] = arguments.get("timeout", 120) * 2
221
+ return fixed
222
+
223
+ return fixed # return original args for retry
224
+
225
+ # ── Retry management ────────────────────────────────────────────────
226
+
227
+ def should_retry(self, tool_name: str, arguments: dict) -> bool:
228
+ """Check if we should retry this tool call."""
229
+ count = sum(
230
+ 1 for a in self._attempts
231
+ if a.tool_name == tool_name
232
+ and a.original_args == arguments
233
+ )
234
+ return count < self.max_retries
235
+
236
+ def record_attempt(self, tool_name: str, original_args: dict,
237
+ error: str, diagnosis: ErrorDiagnosis | None,
238
+ fixed_args: dict | None, success: bool = False):
239
+ """Record a retry attempt for tracking."""
240
+ attempt = RetryAttempt(
241
+ attempt=len(self._attempts) + 1,
242
+ tool_name=tool_name,
243
+ original_args=original_args,
244
+ error_message=error,
245
+ diagnosis=diagnosis,
246
+ fixed_args=fixed_args,
247
+ success=success,
248
+ timestamp=time.time(),
249
+ )
250
+ self._attempts.append(attempt)
251
+
252
+ def learn_from_success(self, error_pattern: str, fix_description: str):
253
+ """Learn a fix pattern from a successful correction."""
254
+ self._learned_patterns[error_pattern] = fix_description
255
+ logger.info("Learned fix: %s", fix_description[:80])
256
+
257
+ # ── Auto-correction loop ─────────────────────────────────────────────
258
+
259
+ def auto_correct(
260
+ self,
261
+ tool_name: str,
262
+ arguments: dict,
263
+ error_message: str,
264
+ execute_fn: Callable[[str, dict], Any],
265
+ ) -> tuple[Any, int]:
266
+ """
267
+ Run the full auto-correction loop.
268
+
269
+ Args:
270
+ tool_name: The tool that failed
271
+ arguments: Original arguments
272
+ error_message: The error from the failed execution
273
+ execute_fn: Function to execute the tool (name, args) → result
274
+
275
+ Returns:
276
+ (result, retry_count): The final result and number of retries used
277
+ """
278
+ retries = 0
279
+
280
+ while retries < self.max_retries:
281
+ # Diagnose
282
+ diagnosis = self.diagnose(error_message, tool_name, arguments)
283
+ if not diagnosis:
284
+ break
285
+
286
+ # Check if auto-fixable
287
+ if diagnosis.retry_strategy == "ask_user":
288
+ logger.info("Cannot auto-fix: %s", diagnosis.diagnosis)
289
+ break
290
+
291
+ if diagnosis.retry_strategy == "read_first":
292
+ logger.info("Need to read context first: %s", diagnosis.diagnosis)
293
+ break
294
+
295
+ # Try auto-fix
296
+ fixed_args = self.suggest_fix(tool_name, arguments, diagnosis,
297
+ error_message=error_message)
298
+ retries += 1
299
+
300
+ if fixed_args is None:
301
+ break
302
+
303
+ logger.info(
304
+ "Auto-correct retry %d/%d: %s",
305
+ retries, self.max_retries, diagnosis.fix_suggestion[:80],
306
+ )
307
+
308
+ # Execute with fixed args
309
+ try:
310
+ result = execute_fn(tool_name, fixed_args)
311
+ except Exception as e:
312
+ result = type('Result', (), {'success': False, 'error': str(e)})()
313
+
314
+ self.record_attempt(
315
+ tool_name, arguments, error_message,
316
+ diagnosis, fixed_args, getattr(result, 'success', False),
317
+ )
318
+
319
+ if getattr(result, 'success', False):
320
+ # Learn from this success
321
+ pattern = re.escape(error_message[:80])
322
+ self.learn_from_success(pattern, diagnosis.fix_suggestion)
323
+ return result, retries
324
+
325
+ # Update error for next iteration
326
+ error_message = getattr(result, 'error', str(result))
327
+ arguments = fixed_args
328
+
329
+ return None, retries
330
+
331
+ # ── Statistics ──────────────────────────────────────────────────────
332
+
333
+ @property
334
+ def stats(self) -> dict:
335
+ total = len(self._attempts)
336
+ successful = sum(1 for a in self._attempts if a.success)
337
+ return {
338
+ "total_retries": total,
339
+ "successful_retries": successful,
340
+ "learned_patterns": len(self._learned_patterns),
341
+ "auto_fix_rate": f"{successful/max(1,total)*100:.0f}%",
342
+ }
343
+
344
+ def reset(self):
345
+ self._attempts.clear()
346
+ self._learned_patterns.clear()