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,47 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Example extension — demonstrates the Extension API.
4
+
5
+ This is a minimal example showing how to create a custom extension
6
+ that contributes a system prompt and a tool.
7
+
8
+ Usage:
9
+ # Auto-discovered from extensions/ directory
10
+ # Or register manually:
11
+ from ata_coder.extension import get_extension_manager
12
+ from extensions.hello_skill import HelloSkill
13
+ get_extension_manager().register(HelloSkill())
14
+ get_extension_manager().activate("hello-skill")
15
+ """
16
+
17
+ from ata_coder.extension import Extension, extension
18
+
19
+
20
+ @extension(
21
+ name="hello-skill",
22
+ version="1.0.0",
23
+ description="A friendly companion skill that greets you",
24
+ tags=["skill", "example"],
25
+ priority=90,
26
+ )
27
+ class HelloSkill(Extension):
28
+ """Example skill extension that adds a friendly tone to responses."""
29
+
30
+ def get_prompt(self) -> str:
31
+ return (
32
+ "You are a friendly and encouraging coding assistant. "
33
+ "Always start your responses with a warm greeting and "
34
+ "end with an encouraging note."
35
+ )
36
+
37
+ def on_activate(self) -> None:
38
+ """Called when this extension is activated."""
39
+ import logging
40
+ logger = logging.getLogger(__name__)
41
+ logger.info("HelloSkill activated — be friendly!")
42
+
43
+ def on_deactivate(self) -> None:
44
+ """Called when this extension is deactivated."""
45
+ import logging
46
+ logger = logging.getLogger(__name__)
47
+ logger.info("HelloSkill deactivated — back to normal.")
@@ -0,0 +1,295 @@
1
+ """
2
+ Fool-proof integration layer — ties together safety guards, change tracking,
3
+ dry-run preview, and interactive confirmation.
4
+
5
+ Provides a single unified interface for "check before execute":
6
+
7
+ check = guard.evaluate(tool_name, arguments)
8
+ if check.needs_confirmation:
9
+ ui.show_confirmation(check) # interactive prompt
10
+ if check.allowed:
11
+ result = execute(...)
12
+ tracker.capture(result)
13
+ """
14
+
15
+ import logging
16
+ from dataclasses import dataclass, field
17
+ from enum import Enum
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+ from .safety_guard import SafetyGuard, SafetyCheck, RiskLevel
22
+ from .change_tracker import ChangeTracker, FileChange
23
+ from .permissions import PermissionStore, PermissionMode, tool_category
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ # ═══════════════════════════════════════════════════════════════════════════════
29
+ # Unified check result
30
+ # ═══════════════════════════════════════════════════════════════════════════════
31
+
32
+ class ActionRequired(Enum):
33
+ PROCEED = "proceed" # No confirmation needed
34
+ CONFIRM = "confirm" # Show confirmation prompt (y/n/a/d)
35
+ WARN_CONFIRM = "warn_confirm" # Show warning + confirmation
36
+ TYPE_CONFIRM = "type_confirm" # User must type a phrase to confirm
37
+ BLOCKED = "blocked" # Hard blocked, cannot proceed
38
+
39
+
40
+ @dataclass
41
+ class OperationCheck:
42
+ """Complete pre-execution check result."""
43
+ tool_name: str
44
+ arguments: dict[str, Any]
45
+ category: str = ""
46
+ allowed: bool = True
47
+ action: ActionRequired = ActionRequired.PROCEED
48
+ risk: RiskLevel = RiskLevel.SAFE
49
+ safety: SafetyCheck | None = None
50
+ warnings: list[str] = field(default_factory=list)
51
+ confirm_message: str = "" # What to show the user
52
+ confirm_phrase: str = "" # Phrase user must type (for TYPE_CONFIRM)
53
+ dry_run_preview: str = "" # What would happen in dry-run
54
+
55
+
56
+ # ═══════════════════════════════════════════════════════════════════════════════
57
+ # Fool-proof engine
58
+ # ═══════════════════════════════════════════════════════════════════════════════
59
+
60
+ class FoolProofEngine:
61
+ """
62
+ Unified "fool-proof" engine that combines:
63
+ - Safety guards (pattern-based blocking)
64
+ - Permission system (user-configurable allow/deny)
65
+ - Change tracking (backup + undo)
66
+ - Dry-run preview
67
+ - Interactive confirmation
68
+
69
+ Usage:
70
+ engine = FoolProofEngine(workspace, permission_store, change_tracker)
71
+
72
+ # Before executing a tool:
73
+ check = engine.evaluate("run_shell", {"command": "rm file.txt"})
74
+ if check.action == ActionRequired.BLOCKED:
75
+ print(f"Blocked: {check.warnings}")
76
+ elif check.action in (ActionRequired.CONFIRM, ActionRequired.WARN_CONFIRM):
77
+ if ui.confirm(check):
78
+ execute_tool()
79
+ else:
80
+ print("Cancelled.")
81
+
82
+ # After executing:
83
+ engine.capture("write_file", {"file_path": "x.py"}, result)
84
+ """
85
+
86
+ def __init__(self,
87
+ workspace: str | Path,
88
+ permission_store: PermissionStore | None = None,
89
+ change_tracker: ChangeTracker | None = None,
90
+ safety_guard: SafetyGuard | None = None):
91
+ self.workspace = Path(workspace).resolve()
92
+ self.permissions = permission_store
93
+ self.tracker = change_tracker
94
+ self.guard = safety_guard or SafetyGuard(workspace)
95
+
96
+ # Stats
97
+ self._blocks = 0
98
+ self._confirmations = 0
99
+ self._dry_runs = 0
100
+
101
+ # ── Evaluate before execution ────────────────────────────────────────
102
+
103
+ def evaluate(self, tool_name: str, arguments: dict[str, Any]) -> OperationCheck:
104
+ """
105
+ Evaluate a tool call BEFORE execution.
106
+ Returns an OperationCheck indicating whether and how to proceed.
107
+ """
108
+ category = tool_category(tool_name)
109
+
110
+ check = OperationCheck(
111
+ tool_name=tool_name,
112
+ arguments=arguments,
113
+ category=category,
114
+ )
115
+
116
+ # 1. Safety guard check
117
+ safety = self._run_safety_check(tool_name, arguments)
118
+ check.safety = safety
119
+ check.risk = safety.risk
120
+ check.warnings = safety.warnings
121
+
122
+ if not safety.allowed:
123
+ check.allowed = False
124
+ check.action = ActionRequired.BLOCKED
125
+ check.confirm_message = safety.reason
126
+ self._blocks += 1
127
+ return check
128
+
129
+ # 2. Read tools — always safe
130
+ if category == "read":
131
+ check.allowed = True
132
+ check.action = ActionRequired.PROCEED
133
+ return check
134
+
135
+ # 3. For write/shell/mcp — check permissions + risk
136
+ # NOTE: safety.risk == CRITICAL is already caught by safety.allowed above;
137
+ # no need for a redundant check here.
138
+ if safety.risk == RiskLevel.DANGER:
139
+ # Check if user already allowed this category
140
+ if self.permissions:
141
+ cat_mode = self.permissions.get_category_mode(category)
142
+ if cat_mode == PermissionMode.ALLOW:
143
+ check.action = ActionRequired.PROCEED
144
+ elif cat_mode == PermissionMode.DENY:
145
+ check.action = ActionRequired.BLOCKED
146
+ check.allowed = False
147
+ else:
148
+ check.action = ActionRequired.WARN_CONFIRM
149
+ else:
150
+ check.action = ActionRequired.WARN_CONFIRM
151
+
152
+ check.confirm_message = self._format_danger_message(tool_name, arguments, safety)
153
+
154
+ elif safety.risk == RiskLevel.CAUTION:
155
+ if self.permissions:
156
+ cat_mode = self.permissions.get_category_mode(category)
157
+ if cat_mode == PermissionMode.ALLOW:
158
+ check.action = ActionRequired.PROCEED
159
+ elif cat_mode == PermissionMode.DENY:
160
+ check.action = ActionRequired.BLOCKED
161
+ check.allowed = False
162
+ else:
163
+ check.action = ActionRequired.CONFIRM
164
+ else:
165
+ check.action = ActionRequired.CONFIRM
166
+
167
+ check.confirm_message = self._format_caution_message(tool_name, arguments, safety)
168
+
169
+ else:
170
+ check.action = ActionRequired.PROCEED
171
+
172
+ # 4. Generate dry-run preview
173
+ if self.tracker and self.tracker.dry_run:
174
+ check.dry_run_preview = self._preview_dry_run(tool_name, arguments)
175
+
176
+ # Track statistics
177
+ if check.action in (ActionRequired.CONFIRM, ActionRequired.WARN_CONFIRM,
178
+ ActionRequired.TYPE_CONFIRM):
179
+ self._confirmations += 1
180
+
181
+ return check
182
+
183
+ # ── Capture after execution ──────────────────────────────────────────
184
+
185
+ def capture(self, tool_name: str, arguments: dict[str, Any],
186
+ result: Any, old_content: str = "") -> FileChange | None:
187
+ """Record a completed operation in the change tracker."""
188
+ if not self.tracker:
189
+ return None
190
+
191
+ if tool_name == "write_file":
192
+ file_path = arguments.get("file_path", "")
193
+ content = arguments.get("content", "")
194
+ if file_path:
195
+ return self.tracker.capture_write(file_path, content)
196
+
197
+ elif tool_name == "edit_file":
198
+ file_path = arguments.get("file_path", "")
199
+ if file_path and old_content:
200
+ old_str = arguments.get("old_string", "")
201
+ new_str = arguments.get("new_string", "")
202
+ if old_str:
203
+ # Reconstruct new content from edit args (avoids re-reading from disk)
204
+ new_content = old_content.replace(old_str, new_str, 1)
205
+ else:
206
+ # Fallback: read from disk (old_string not in args — test/legacy path)
207
+ try:
208
+ with open(file_path, "r", encoding="utf-8") as f:
209
+ new_content = f.read()
210
+ except Exception:
211
+ new_content = old_content # keep old content if read fails
212
+ return self.tracker.capture_edit(file_path, old_content, new_content)
213
+
214
+ return None
215
+
216
+ # ── Safety check dispatch ────────────────────────────────────────────
217
+
218
+ def _run_safety_check(self, tool_name: str, arguments: dict) -> SafetyCheck:
219
+ if tool_name == "read_file":
220
+ return self.guard.check_read_file(arguments.get("file_path", ""))
221
+ elif tool_name == "write_file":
222
+ return self.guard.check_write_file(
223
+ arguments.get("file_path", ""),
224
+ arguments.get("content", ""),
225
+ )
226
+ elif tool_name == "edit_file":
227
+ return self.guard.check_edit_file(
228
+ arguments.get("file_path", ""),
229
+ arguments.get("old_string", ""),
230
+ arguments.get("new_string", ""),
231
+ )
232
+ elif tool_name == "run_shell":
233
+ return self.guard.check_shell(arguments.get("command", ""))
234
+ elif tool_name.startswith("mcp__"):
235
+ return SafetyCheck(allowed=True, risk=RiskLevel.CAUTION,
236
+ warnings=["MCP tool — verify on server side"])
237
+ else:
238
+ # Unknown tool — be conservative, not permissive.
239
+ # Defaulting to SAFE would silently skip safety checks for any
240
+ # newly-added tool that wasn't wired into this dispatch.
241
+ return SafetyCheck(allowed=True, risk=RiskLevel.CAUTION,
242
+ warnings=[f"Unknown tool type: {tool_name} — no safety rules defined"])
243
+
244
+ # ── Message formatting ───────────────────────────────────────────────
245
+
246
+ def _format_danger_message(self, tool_name: str, arguments: dict,
247
+ safety: SafetyCheck) -> str:
248
+ lines = [f"[DANGER] {tool_name}"]
249
+ if tool_name == "run_shell" and "command" in arguments:
250
+ lines.append(f" Command: {arguments['command'][:200]}")
251
+ elif "file_path" in arguments:
252
+ lines.append(f" File: {arguments['file_path']}")
253
+ for w in safety.warnings:
254
+ lines.append(f" ! {w}")
255
+ return "\n".join(lines)
256
+
257
+ def _format_caution_message(self, tool_name: str, arguments: dict,
258
+ safety: SafetyCheck) -> str:
259
+ lines = [f"{tool_name}"]
260
+ if "file_path" in arguments:
261
+ lines.append(f" File: {arguments['file_path']}")
262
+ elif "command" in arguments:
263
+ lines.append(f" Cmd: {arguments['command'][:120]}")
264
+ for w in safety.warnings:
265
+ lines.append(f" ! {w}")
266
+ return "\n".join(lines)
267
+
268
+ def _preview_dry_run(self, tool_name: str, arguments: dict) -> str:
269
+ """Generate a dry-run preview string."""
270
+ if tool_name == "write_file":
271
+ fp = arguments.get("file_path", "")
272
+ content = arguments.get("content", "")
273
+ lines = content.count("\n") + 1
274
+ return f"Would WRITE {fp} ({lines} lines, {len(content)} bytes)"
275
+ elif tool_name == "edit_file":
276
+ fp = arguments.get("file_path", "")
277
+ old = arguments.get("old_string", "")
278
+ new = arguments.get("new_string", "")
279
+ return f"Would EDIT {fp}:\n - {old[:80]}\n + {new[:80]}"
280
+ elif tool_name == "run_shell":
281
+ return f"Would RUN: {arguments.get('command', '')[:200]}"
282
+ return ""
283
+
284
+ # ── Stats ────────────────────────────────────────────────────────────
285
+
286
+ @property
287
+ def stats(self) -> dict:
288
+ return {
289
+ "blocks": self._blocks,
290
+ "confirmations": self._confirmations,
291
+ "dry_runs": self._dry_runs,
292
+ "safety_guard": self.guard.stats,
293
+ "tracker_changes": self.tracker.count_active() if self.tracker else 0,
294
+ "tracker_total": self.tracker.count_all() if self.tracker else 0,
295
+ }