ctrlcode 0.1.0__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 (75) hide show
  1. ctrlcode/__init__.py +8 -0
  2. ctrlcode/agents/__init__.py +29 -0
  3. ctrlcode/agents/cleanup.py +388 -0
  4. ctrlcode/agents/communication.py +439 -0
  5. ctrlcode/agents/observability.py +421 -0
  6. ctrlcode/agents/react_loop.py +297 -0
  7. ctrlcode/agents/registry.py +211 -0
  8. ctrlcode/agents/result_parser.py +242 -0
  9. ctrlcode/agents/workflow.py +723 -0
  10. ctrlcode/analysis/__init__.py +28 -0
  11. ctrlcode/analysis/ast_diff.py +163 -0
  12. ctrlcode/analysis/bug_detector.py +149 -0
  13. ctrlcode/analysis/code_graphs.py +329 -0
  14. ctrlcode/analysis/semantic.py +205 -0
  15. ctrlcode/analysis/static.py +183 -0
  16. ctrlcode/analysis/synthesizer.py +281 -0
  17. ctrlcode/analysis/tests.py +189 -0
  18. ctrlcode/cleanup/__init__.py +16 -0
  19. ctrlcode/cleanup/auto_merge.py +350 -0
  20. ctrlcode/cleanup/doc_gardening.py +388 -0
  21. ctrlcode/cleanup/pr_automation.py +330 -0
  22. ctrlcode/cleanup/scheduler.py +356 -0
  23. ctrlcode/config.py +380 -0
  24. ctrlcode/embeddings/__init__.py +6 -0
  25. ctrlcode/embeddings/embedder.py +192 -0
  26. ctrlcode/embeddings/vector_store.py +213 -0
  27. ctrlcode/fuzzing/__init__.py +24 -0
  28. ctrlcode/fuzzing/analyzer.py +280 -0
  29. ctrlcode/fuzzing/budget.py +112 -0
  30. ctrlcode/fuzzing/context.py +665 -0
  31. ctrlcode/fuzzing/context_fuzzer.py +506 -0
  32. ctrlcode/fuzzing/derived_orchestrator.py +732 -0
  33. ctrlcode/fuzzing/oracle_adapter.py +135 -0
  34. ctrlcode/linters/__init__.py +11 -0
  35. ctrlcode/linters/hand_rolled_utils.py +221 -0
  36. ctrlcode/linters/yolo_parsing.py +217 -0
  37. ctrlcode/metrics/__init__.py +6 -0
  38. ctrlcode/metrics/dashboard.py +283 -0
  39. ctrlcode/metrics/tech_debt.py +663 -0
  40. ctrlcode/paths.py +68 -0
  41. ctrlcode/permissions.py +179 -0
  42. ctrlcode/providers/__init__.py +15 -0
  43. ctrlcode/providers/anthropic.py +138 -0
  44. ctrlcode/providers/base.py +77 -0
  45. ctrlcode/providers/openai.py +197 -0
  46. ctrlcode/providers/parallel.py +104 -0
  47. ctrlcode/server.py +871 -0
  48. ctrlcode/session/__init__.py +6 -0
  49. ctrlcode/session/baseline.py +57 -0
  50. ctrlcode/session/manager.py +967 -0
  51. ctrlcode/skills/__init__.py +10 -0
  52. ctrlcode/skills/builtin/commit.toml +29 -0
  53. ctrlcode/skills/builtin/docs.toml +25 -0
  54. ctrlcode/skills/builtin/refactor.toml +33 -0
  55. ctrlcode/skills/builtin/review.toml +28 -0
  56. ctrlcode/skills/builtin/test.toml +28 -0
  57. ctrlcode/skills/loader.py +111 -0
  58. ctrlcode/skills/registry.py +139 -0
  59. ctrlcode/storage/__init__.py +19 -0
  60. ctrlcode/storage/history_db.py +708 -0
  61. ctrlcode/tools/__init__.py +220 -0
  62. ctrlcode/tools/bash.py +112 -0
  63. ctrlcode/tools/browser.py +352 -0
  64. ctrlcode/tools/executor.py +153 -0
  65. ctrlcode/tools/explore.py +486 -0
  66. ctrlcode/tools/mcp.py +108 -0
  67. ctrlcode/tools/observability.py +561 -0
  68. ctrlcode/tools/registry.py +193 -0
  69. ctrlcode/tools/todo.py +291 -0
  70. ctrlcode/tools/update.py +266 -0
  71. ctrlcode/tools/webfetch.py +147 -0
  72. ctrlcode-0.1.0.dist-info/METADATA +93 -0
  73. ctrlcode-0.1.0.dist-info/RECORD +75 -0
  74. ctrlcode-0.1.0.dist-info/WHEEL +4 -0
  75. ctrlcode-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,242 @@
1
+ """Parse agent execution results into structured outputs."""
2
+
3
+ import json
4
+ import logging
5
+ import re
6
+ from typing import Any
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class AgentResultParser:
12
+ """Parse outputs from different agent types."""
13
+
14
+ @staticmethod
15
+ def parse_planner_result(assistant_text: str, tool_calls: list[dict]) -> dict[str, Any]:
16
+ """
17
+ Extract task graph from planner output.
18
+
19
+ Priority:
20
+ 1. task_write tool call with JSON task graph
21
+ 2. JSON blocks in assistant text
22
+ 3. Fallback: error with raw text
23
+
24
+ Args:
25
+ assistant_text: Full text response from planner
26
+ tool_calls: All tool calls made by planner
27
+
28
+ Returns:
29
+ Task graph dictionary
30
+ """
31
+ # Priority 1: Look for task_write tool call
32
+ for tool_call in tool_calls:
33
+ if tool_call.get("tool") == "task_write":
34
+ tool_input = tool_call.get("input", {})
35
+
36
+ # Check if input already has task graph structure
37
+ if "tasks" in tool_input:
38
+ logger.info("Parsed task graph from task_write tool call (direct)")
39
+ return tool_input
40
+
41
+ # Otherwise try to parse from content field
42
+ content = tool_input.get("content", "")
43
+ if content:
44
+ try:
45
+ task_graph = json.loads(content)
46
+ logger.info("Parsed task graph from task_write tool call")
47
+ return task_graph
48
+ except json.JSONDecodeError as e:
49
+ logger.warning(f"Failed to parse task_write content: {e}")
50
+
51
+ # Priority 2: Extract JSON from text (look for code blocks or raw JSON)
52
+ json_blocks = re.findall(r'```(?:json)?\s*(\{.*?\})\s*```', assistant_text, re.DOTALL)
53
+ for block in json_blocks:
54
+ try:
55
+ task_graph = json.loads(block)
56
+ logger.info("Parsed task graph from JSON code block")
57
+ return task_graph
58
+ except json.JSONDecodeError:
59
+ continue
60
+
61
+ # Try raw JSON in text
62
+ try:
63
+ # Look for first { to last }
64
+ start = assistant_text.find('{')
65
+ end = assistant_text.rfind('}')
66
+ if start != -1 and end != -1:
67
+ potential_json = assistant_text[start:end + 1]
68
+ task_graph = json.loads(potential_json)
69
+ logger.info("Parsed task graph from raw JSON in text")
70
+ return task_graph
71
+ except (json.JSONDecodeError, ValueError):
72
+ pass
73
+
74
+ # Priority 3: Fallback - return error
75
+ logger.error("Failed to parse task graph from planner output")
76
+ return {
77
+ "tasks": [],
78
+ "parallel_groups": [],
79
+ "risks": [],
80
+ "checkpoints": [],
81
+ "error": "Failed to parse task graph",
82
+ "raw_text": assistant_text[:500] # First 500 chars for debugging
83
+ }
84
+
85
+ @staticmethod
86
+ def parse_coder_result(assistant_text: str, tool_calls: list[dict]) -> dict[str, Any]:
87
+ """
88
+ Extract files changed and implementation details from coder output.
89
+
90
+ Args:
91
+ assistant_text: Full text response from coder
92
+ tool_calls: All tool calls made by coder
93
+
94
+ Returns:
95
+ Coder result dictionary
96
+ """
97
+ # Extract file operations from tool calls
98
+ files_written = []
99
+ files_edited = []
100
+ files_read = []
101
+
102
+ for tool_call in tool_calls:
103
+ tool_name = tool_call.get("tool", "")
104
+ tool_input = tool_call.get("input", {})
105
+
106
+ if tool_name == "write_file":
107
+ files_written.append(tool_input.get("path", "unknown"))
108
+ elif tool_name == "edit_file":
109
+ files_edited.append(tool_input.get("path", "unknown"))
110
+ elif tool_name == "read_file":
111
+ files_read.append(tool_input.get("path", "unknown"))
112
+
113
+ return {
114
+ "files_written": files_written,
115
+ "files_edited": files_edited,
116
+ "files_read": files_read,
117
+ "summary": assistant_text,
118
+ "tool_count": len(tool_calls)
119
+ }
120
+
121
+ @staticmethod
122
+ def parse_reviewer_result(assistant_text: str, tool_calls: list[dict]) -> dict[str, Any]:
123
+ """
124
+ Extract approval/feedback from reviewer output.
125
+
126
+ Looks for:
127
+ - task_update with approval status
128
+ - Explicit approval/changes_requested keywords
129
+ - Feedback items
130
+
131
+ Args:
132
+ assistant_text: Full text response from reviewer
133
+ tool_calls: All tool calls made by reviewer
134
+
135
+ Returns:
136
+ Reviewer result dictionary
137
+ """
138
+ # Look for task_update tool call
139
+ for tool_call in tool_calls:
140
+ if tool_call.get("tool") == "task_update":
141
+ tool_input = tool_call.get("input", {})
142
+ status = tool_input.get("status", "")
143
+
144
+ if status == "completed":
145
+ return {
146
+ "status": "approved",
147
+ "feedback": assistant_text,
148
+ "changes_required": []
149
+ }
150
+ elif "changes" in status.lower():
151
+ return {
152
+ "status": "changes_requested",
153
+ "feedback": assistant_text,
154
+ "changes_required": _extract_feedback_items(assistant_text)
155
+ }
156
+
157
+ # Parse from text
158
+ text_lower = assistant_text.lower()
159
+ if any(keyword in text_lower for keyword in ["approved", "looks good", "lgtm"]):
160
+ return {
161
+ "status": "approved",
162
+ "feedback": assistant_text,
163
+ "changes_required": []
164
+ }
165
+ elif any(keyword in text_lower for keyword in ["changes requested", "needs work", "issues found"]):
166
+ return {
167
+ "status": "changes_requested",
168
+ "feedback": assistant_text,
169
+ "changes_required": _extract_feedback_items(assistant_text)
170
+ }
171
+
172
+ # Default: inconclusive
173
+ return {
174
+ "status": "inconclusive",
175
+ "feedback": assistant_text,
176
+ "changes_required": []
177
+ }
178
+
179
+ @staticmethod
180
+ def parse_executor_result(assistant_text: str, tool_calls: list[dict]) -> dict[str, Any]:
181
+ """
182
+ Extract validation status and test results from executor output.
183
+
184
+ Args:
185
+ assistant_text: Full text response from executor
186
+ tool_calls: All tool calls made by executor
187
+
188
+ Returns:
189
+ Executor result dictionary
190
+ """
191
+ # Look for bash/test tool calls
192
+ test_results = []
193
+ commands_run = []
194
+
195
+ for tool_call in tool_calls:
196
+ tool_name = tool_call.get("tool", "")
197
+ tool_input = tool_call.get("input", {})
198
+
199
+ if tool_name == "bash":
200
+ command = tool_input.get("command", "")
201
+ commands_run.append(command)
202
+
203
+ # Detect test commands
204
+ if any(test_cmd in command for test_cmd in ["pytest", "npm test", "go test", "cargo test"]):
205
+ test_results.append({
206
+ "command": command,
207
+ "type": "test"
208
+ })
209
+
210
+ # Parse validation status from text
211
+ text_lower = assistant_text.lower()
212
+ if any(keyword in text_lower for keyword in ["all tests pass", "validation successful", "✓"]):
213
+ validation_status = "passed"
214
+ elif any(keyword in text_lower for keyword in ["tests fail", "validation failed", "✗", "error"]):
215
+ validation_status = "failed"
216
+ else:
217
+ validation_status = "inconclusive"
218
+
219
+ return {
220
+ "validation_status": validation_status,
221
+ "test_results": test_results,
222
+ "commands_run": commands_run,
223
+ "output": assistant_text
224
+ }
225
+
226
+
227
+ def _extract_feedback_items(text: str) -> list[str]:
228
+ """Extract bulleted or numbered feedback items from text."""
229
+ items = []
230
+
231
+ # Look for markdown lists (- or 1.)
232
+ lines = text.split('\n')
233
+ for line in lines:
234
+ stripped = line.strip()
235
+ # Bullet points
236
+ if stripped.startswith('- ') or stripped.startswith('* '):
237
+ items.append(stripped[2:].strip())
238
+ # Numbered lists
239
+ elif re.match(r'^\d+\.\s+', stripped):
240
+ items.append(re.sub(r'^\d+\.\s+', '', stripped))
241
+
242
+ return items