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,371 @@
1
+ """
2
+ Git Workflow — integrated version control for AI-assisted coding.
3
+
4
+ Features:
5
+ - Auto-create feature branches for tasks
6
+ - Auto-commit after successful changes with meaningful messages
7
+ - Pre-commit safety checks (no secrets, no giant files)
8
+ - Git status in UI
9
+ - PR/merge request creation hints
10
+ - Stash management for interrupted work
11
+
12
+ Commands (via CLI):
13
+ /git status → Show working tree status
14
+ /git diff → Show unstaged changes
15
+ /git commit → Auto-commit with generated message
16
+ /git branch <name> → Create and switch to feature branch
17
+ /git undo-commit → Undo last commit (soft reset)
18
+ /git log [n] → Show recent commits
19
+
20
+ Safety:
21
+ - Never force-push
22
+ - Never amend pushed commits
23
+ - Auto-stash before dangerous operations
24
+ - Warn on large diffs (>500 lines)
25
+ """
26
+
27
+ import logging
28
+ import os
29
+ import re
30
+ import subprocess
31
+ import time
32
+ from dataclasses import dataclass
33
+ from pathlib import Path
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+
38
+ # ═══════════════════════════════════════════════════════════════════════════════
39
+ # Helpers
40
+ # ═══════════════════════════════════════════════════════════════════════════════
41
+
42
+ def _run_git(args: list[str], cwd: str | Path, timeout: int = 30) -> tuple[int, str, str]:
43
+ """Run a git command. Returns (returncode, stdout, stderr)."""
44
+ try:
45
+ result = subprocess.run(
46
+ ["git"] + args,
47
+ capture_output=True, text=True,
48
+ encoding="utf-8", errors="replace",
49
+ cwd=str(cwd), timeout=timeout,
50
+ )
51
+ return result.returncode, result.stdout.strip(), result.stderr.strip()
52
+ except FileNotFoundError:
53
+ return -1, "", "git not found"
54
+ except subprocess.TimeoutExpired:
55
+ return -1, "", "timeout"
56
+ except Exception as e:
57
+ return -1, "", str(e)
58
+
59
+
60
+ def is_git_repo(cwd: str | Path) -> bool:
61
+ code, _, _ = _run_git(["rev-parse", "--git-dir"], cwd)
62
+ return code == 0
63
+
64
+
65
+ # ═══════════════════════════════════════════════════════════════════════════════
66
+ # Git Workflow Manager
67
+ # ═══════════════════════════════════════════════════════════════════════════════
68
+
69
+ @dataclass
70
+ class GitStatus:
71
+ branch: str = ""
72
+ clean: bool = True
73
+ staged: int = 0
74
+ modified: int = 0
75
+ untracked: int = 0
76
+ ahead: int = 0
77
+ behind: int = 0
78
+ last_commit: str = ""
79
+ last_commit_msg: str = ""
80
+
81
+ def summary(self) -> str:
82
+ if self.clean:
83
+ return f"[{self.branch}] clean"
84
+ parts = []
85
+ if self.modified:
86
+ parts.append(f"M:{self.modified}")
87
+ if self.staged:
88
+ parts.append(f"S:{self.staged}")
89
+ if self.untracked:
90
+ parts.append(f"?:{self.untracked}")
91
+ return f"[{self.branch}] " + " ".join(parts)
92
+
93
+ def is_dirty(self) -> bool:
94
+ return not self.clean
95
+
96
+
97
+ class GitWorkflow:
98
+ """
99
+ Git workflow manager for AI-assisted development.
100
+
101
+ Provides safe, automated git operations with guard rails.
102
+ """
103
+
104
+ def __init__(self, cwd: str | Path | None = None):
105
+ self.cwd = Path(cwd) if cwd else Path.cwd()
106
+ self._commits_made: list[str] = [] # track commits made in this session
107
+ self._branches_created: list[str] = []
108
+
109
+ # ── Status ──────────────────────────────────────────────────────────
110
+
111
+ def get_status(self) -> GitStatus:
112
+ """Get the current git working tree status."""
113
+ if not is_git_repo(self.cwd):
114
+ return GitStatus(branch="(not a git repo)")
115
+
116
+ status = GitStatus()
117
+
118
+ # Branch
119
+ _, branch, _ = _run_git(["branch", "--show-current"], self.cwd)
120
+ status.branch = branch or "(detached)"
121
+
122
+ # Short status
123
+ code, output, _ = _run_git(["status", "--short"], self.cwd)
124
+ if code == 0 and output:
125
+ for line in output.split("\n"):
126
+ line = line.strip()
127
+ if not line:
128
+ continue
129
+ if line.startswith("?"):
130
+ status.untracked += 1
131
+ elif line[0] in "MADRC":
132
+ status.staged += 1
133
+ elif len(line) > 1 and line[1] in "MD":
134
+ status.modified += 1
135
+ status.clean = (status.staged == 0 and status.modified == 0 and status.untracked == 0)
136
+
137
+ # Ahead/behind
138
+ _, ahead_str, _ = _run_git(["rev-list", "--count", "@{u}..HEAD"], self.cwd, timeout=10)
139
+ if ahead_str and ahead_str.isdigit():
140
+ status.ahead = int(ahead_str)
141
+ _, behind_str, _ = _run_git(["rev-list", "--count", "HEAD..@{u}"], self.cwd, timeout=10)
142
+ if behind_str and behind_str.isdigit():
143
+ status.behind = int(behind_str)
144
+
145
+ # Last commit
146
+ _, last, _ = _run_git(["log", "-1", "--format=%h %s"], self.cwd)
147
+ if last:
148
+ parts = last.split(" ", 1)
149
+ status.last_commit = parts[0]
150
+ status.last_commit_msg = parts[1] if len(parts) > 1 else ""
151
+
152
+ return status
153
+
154
+ def get_diff(self, staged: bool = False) -> str:
155
+ """Get the current diff."""
156
+ args = ["diff"]
157
+ if staged:
158
+ args.append("--staged")
159
+ _, output, _ = _run_git(args, self.cwd)
160
+ return output or "(no changes)"
161
+
162
+ def get_log(self, count: int = 10) -> str:
163
+ """Get recent commit log."""
164
+ _, output, _ = _run_git(
165
+ ["log", f"-{count}", "--oneline", "--decorate"],
166
+ self.cwd,
167
+ )
168
+ return output or "(no commits)"
169
+
170
+ # ── Branch ──────────────────────────────────────────────────────────
171
+
172
+ def create_branch(self, name: str, switch: bool = True) -> tuple[bool, str]:
173
+ """
174
+ Create a new feature branch from current HEAD.
175
+ Sanitizes the branch name.
176
+ """
177
+ # Sanitize branch name
178
+ safe_name = re.sub(r"[^a-zA-Z0-9._/-]", "-", name.lower())
179
+ safe_name = re.sub(r"-+", "-", safe_name).strip("-")
180
+ if not safe_name:
181
+ safe_name = f"feature-{int(time.time())}"
182
+
183
+ code, out, err = _run_git(["checkout", "-b", safe_name], self.cwd)
184
+ if code == 0:
185
+ self._branches_created.append(safe_name)
186
+ return True, f"Branch created: {safe_name}"
187
+ return False, err or "Failed to create branch"
188
+
189
+ def switch_branch(self, name: str) -> tuple[bool, str]:
190
+ """Switch to an existing branch."""
191
+ code, out, err = _run_git(["checkout", name], self.cwd)
192
+ return code == 0, out or err
193
+
194
+ def list_branches(self) -> str:
195
+ """List local branches."""
196
+ _, out, _ = _run_git(["branch"], self.cwd)
197
+ return out or ""
198
+
199
+ # ── Commit ──────────────────────────────────────────────────────────
200
+
201
+ def commit(self, message: str = "", files: list[str] | None = None,
202
+ all_changes: bool = True) -> tuple[bool, str]:
203
+ """
204
+ Stage and commit changes with a meaningful message.
205
+ Auto-generates message if none provided.
206
+ """
207
+ status = self.get_status()
208
+ if status.clean:
209
+ return False, "Nothing to commit (working tree clean)"
210
+
211
+ # Stage files
212
+ if all_changes:
213
+ code, _, err = _run_git(["add", "-A"], self.cwd)
214
+ elif files:
215
+ code, _, err = _run_git(["add"] + files, self.cwd)
216
+ else:
217
+ return False, "No files specified"
218
+
219
+ if code != 0:
220
+ return False, err
221
+
222
+ # Check for secrets in staged changes
223
+ secret_check = self._check_secrets()
224
+ if secret_check:
225
+ logger.warning("Potential secrets in commit: %s", secret_check)
226
+
227
+ # Generate commit message if not provided
228
+ if not message:
229
+ message = self._generate_commit_message()
230
+
231
+ # Check diff size
232
+ _, diff, _ = _run_git(["diff", "--staged", "--stat"], self.cwd)
233
+ line_count = diff.count("\n") if diff else 0
234
+ if line_count > 500:
235
+ logger.warning("Large commit: %d files changed", line_count)
236
+
237
+ code, _, err = _run_git(["commit", "-m", message], self.cwd)
238
+ if code == 0:
239
+ self._commits_made.append(message)
240
+ return True, f"Committed: {message}"
241
+ return False, err or "Commit failed"
242
+
243
+ def _generate_commit_message(self) -> str:
244
+ """Generate a descriptive commit message from the diff."""
245
+ _, diff, _ = _run_git(["diff", "--staged", "--stat"], self.cwd)
246
+
247
+ if not diff:
248
+ return "Update files"
249
+
250
+ # Extract file patterns
251
+ files = []
252
+ for line in diff.split("\n"):
253
+ if "|" in line:
254
+ fname = line.split("|")[0].strip()
255
+ files.append(fname)
256
+
257
+ if not files:
258
+ return "Update files"
259
+
260
+ # All files in a diff are changes (new, modified, or deleted)
261
+ py_files = [f for f in files if f.endswith(".py")]
262
+ js_files = [f for f in files if f.endswith((".js", ".ts", ".jsx", ".tsx"))]
263
+ test_files = [f for f in files if "test" in f.lower()]
264
+
265
+ if test_files:
266
+ return f"test: add/update tests ({len(files)} files)"
267
+
268
+ if len(files) == 1:
269
+ return f"Update {os.path.basename(files[0])}"
270
+
271
+ # Group by directory
272
+ dirs = {}
273
+ for f in files:
274
+ d = os.path.dirname(f) or "root"
275
+ dirs.setdefault(d, []).append(f)
276
+
277
+ if len(dirs) == 1:
278
+ d = list(dirs.keys())[0]
279
+ return f"Update {d}/ ({len(files)} files)"
280
+
281
+ # Generic but descriptive
282
+ if py_files:
283
+ return f"refactor: update Python code ({len(files)} files)"
284
+ if js_files:
285
+ return f"feat: update frontend code ({len(files)} files)"
286
+
287
+ return f"chore: update {len(files)} files"
288
+
289
+ def _check_secrets(self) -> str | None:
290
+ """Check staged changes for potential secrets."""
291
+ _, diff, _ = _run_git(["diff", "--staged"], self.cwd)
292
+ if not diff:
293
+ return None
294
+
295
+ # Patterns that look like secrets
296
+ secret_patterns = [
297
+ (r"(?:api_key|apikey|secret|password|token)\s*[:=]\s*[\"'`][^\s\"'`]{20,}[\"'`]", "API key / secret"),
298
+ (r"-----BEGIN (?:RSA |EC )?PRIVATE KEY-----", "Private key"),
299
+ (r"(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}", "GitHub token"),
300
+ (r"AKIA[0-9A-Z]{16}", "AWS access key"),
301
+ (r"sk-[A-Za-z0-9]{32,}", "OpenAI API key"),
302
+ ]
303
+
304
+ for pattern, name in secret_patterns:
305
+ if re.search(pattern, diff, re.IGNORECASE):
306
+ return name
307
+ return None
308
+
309
+ # ── Undo ────────────────────────────────────────────────────────────
310
+
311
+ def undo_commit(self) -> tuple[bool, str]:
312
+ """Undo the last commit (soft reset — keeps changes staged)."""
313
+ code, _, err = _run_git(["reset", "--soft", "HEAD~1"], self.cwd)
314
+ if code == 0:
315
+ if self._commits_made:
316
+ undone = self._commits_made.pop()
317
+ return True, f"Undid: {undone}"
318
+ return True, "Undid last commit (soft reset)"
319
+ return False, err or "Cannot undo — no previous commit?"
320
+
321
+ # ── Stash ────────────────────────────────────────────────────────────
322
+
323
+ def stash(self, message: str = "") -> tuple[bool, str]:
324
+ """Stash current changes."""
325
+ args = ["stash", "push"]
326
+ if message:
327
+ args.extend(["-m", message])
328
+ code, out, err = _run_git(args, self.cwd)
329
+ return code == 0, out or err or "Stashed"
330
+
331
+ def stash_pop(self) -> tuple[bool, str]:
332
+ """Pop the most recent stash."""
333
+ code, out, err = _run_git(["stash", "pop"], self.cwd)
334
+ return code == 0, out or err or "Popped stash"
335
+
336
+ # ── Safety checks ───────────────────────────────────────────────────
337
+
338
+ def pre_operation_check(self) -> tuple[bool, str]:
339
+ """
340
+ Check if it's safe to perform git operations.
341
+ Returns (safe, reason).
342
+ """
343
+ # Check for detached HEAD
344
+ _, branch, _ = _run_git(["branch", "--show-current"], self.cwd)
345
+ if not branch:
346
+ return False, "Detached HEAD — create or switch to a branch first."
347
+
348
+ # Check for rebase in progress
349
+ if (self.cwd / ".git" / "rebase-merge").exists():
350
+ return False, "Rebase in progress. Complete or abort it first."
351
+ if (self.cwd / ".git" / "rebase-apply").exists():
352
+ return False, "Rebase in progress. Complete or abort it first."
353
+
354
+ # Check for merge in progress
355
+ if (self.cwd / ".git" / "MERGE_HEAD").exists():
356
+ return False, "Merge in progress. Complete or abort it first."
357
+
358
+ return True, ""
359
+
360
+ # ── Session summary ─────────────────────────────────────────────────
361
+
362
+ def session_summary(self) -> str:
363
+ """Summarize all git activity in this session."""
364
+ lines = []
365
+ if self._branches_created:
366
+ lines.append(f"Branches created: {', '.join(self._branches_created)}")
367
+ if self._commits_made:
368
+ lines.append(f"Commits: {len(self._commits_made)}")
369
+ for c in self._commits_made[-5:]:
370
+ lines.append(f" - {c[:80]}")
371
+ return "\n".join(lines) if lines else "(no git activity in this session)"