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.
- ctrlcode/__init__.py +8 -0
- ctrlcode/agents/__init__.py +29 -0
- ctrlcode/agents/cleanup.py +388 -0
- ctrlcode/agents/communication.py +439 -0
- ctrlcode/agents/observability.py +421 -0
- ctrlcode/agents/react_loop.py +297 -0
- ctrlcode/agents/registry.py +211 -0
- ctrlcode/agents/result_parser.py +242 -0
- ctrlcode/agents/workflow.py +723 -0
- ctrlcode/analysis/__init__.py +28 -0
- ctrlcode/analysis/ast_diff.py +163 -0
- ctrlcode/analysis/bug_detector.py +149 -0
- ctrlcode/analysis/code_graphs.py +329 -0
- ctrlcode/analysis/semantic.py +205 -0
- ctrlcode/analysis/static.py +183 -0
- ctrlcode/analysis/synthesizer.py +281 -0
- ctrlcode/analysis/tests.py +189 -0
- ctrlcode/cleanup/__init__.py +16 -0
- ctrlcode/cleanup/auto_merge.py +350 -0
- ctrlcode/cleanup/doc_gardening.py +388 -0
- ctrlcode/cleanup/pr_automation.py +330 -0
- ctrlcode/cleanup/scheduler.py +356 -0
- ctrlcode/config.py +380 -0
- ctrlcode/embeddings/__init__.py +6 -0
- ctrlcode/embeddings/embedder.py +192 -0
- ctrlcode/embeddings/vector_store.py +213 -0
- ctrlcode/fuzzing/__init__.py +24 -0
- ctrlcode/fuzzing/analyzer.py +280 -0
- ctrlcode/fuzzing/budget.py +112 -0
- ctrlcode/fuzzing/context.py +665 -0
- ctrlcode/fuzzing/context_fuzzer.py +506 -0
- ctrlcode/fuzzing/derived_orchestrator.py +732 -0
- ctrlcode/fuzzing/oracle_adapter.py +135 -0
- ctrlcode/linters/__init__.py +11 -0
- ctrlcode/linters/hand_rolled_utils.py +221 -0
- ctrlcode/linters/yolo_parsing.py +217 -0
- ctrlcode/metrics/__init__.py +6 -0
- ctrlcode/metrics/dashboard.py +283 -0
- ctrlcode/metrics/tech_debt.py +663 -0
- ctrlcode/paths.py +68 -0
- ctrlcode/permissions.py +179 -0
- ctrlcode/providers/__init__.py +15 -0
- ctrlcode/providers/anthropic.py +138 -0
- ctrlcode/providers/base.py +77 -0
- ctrlcode/providers/openai.py +197 -0
- ctrlcode/providers/parallel.py +104 -0
- ctrlcode/server.py +871 -0
- ctrlcode/session/__init__.py +6 -0
- ctrlcode/session/baseline.py +57 -0
- ctrlcode/session/manager.py +967 -0
- ctrlcode/skills/__init__.py +10 -0
- ctrlcode/skills/builtin/commit.toml +29 -0
- ctrlcode/skills/builtin/docs.toml +25 -0
- ctrlcode/skills/builtin/refactor.toml +33 -0
- ctrlcode/skills/builtin/review.toml +28 -0
- ctrlcode/skills/builtin/test.toml +28 -0
- ctrlcode/skills/loader.py +111 -0
- ctrlcode/skills/registry.py +139 -0
- ctrlcode/storage/__init__.py +19 -0
- ctrlcode/storage/history_db.py +708 -0
- ctrlcode/tools/__init__.py +220 -0
- ctrlcode/tools/bash.py +112 -0
- ctrlcode/tools/browser.py +352 -0
- ctrlcode/tools/executor.py +153 -0
- ctrlcode/tools/explore.py +486 -0
- ctrlcode/tools/mcp.py +108 -0
- ctrlcode/tools/observability.py +561 -0
- ctrlcode/tools/registry.py +193 -0
- ctrlcode/tools/todo.py +291 -0
- ctrlcode/tools/update.py +266 -0
- ctrlcode/tools/webfetch.py +147 -0
- ctrlcode-0.1.0.dist-info/METADATA +93 -0
- ctrlcode-0.1.0.dist-info/RECORD +75 -0
- ctrlcode-0.1.0.dist-info/WHEEL +4 -0
- ctrlcode-0.1.0.dist-info/entry_points.txt +3 -0
ctrlcode/tools/update.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Built-in file update tools for modifying existing files."""
|
|
2
|
+
|
|
3
|
+
import fcntl
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
import signal
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UpdateFileTools:
|
|
12
|
+
"""Built-in tools for updating file contents."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, workspace_root: str | Path):
|
|
15
|
+
"""
|
|
16
|
+
Initialize update tools.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
workspace_root: Root directory for file operations
|
|
20
|
+
"""
|
|
21
|
+
self.workspace_root = Path(workspace_root).resolve()
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def _validate_regex_pattern(pattern: str) -> tuple[bool, str]:
|
|
25
|
+
"""
|
|
26
|
+
Validate regex pattern to prevent ReDoS attacks.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
pattern: Regex pattern to validate
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Tuple of (is_valid, error_message)
|
|
33
|
+
"""
|
|
34
|
+
# Length limit to prevent excessive complexity
|
|
35
|
+
if len(pattern) > 500:
|
|
36
|
+
return False, "Pattern too long (max 500 chars)"
|
|
37
|
+
|
|
38
|
+
# Check for dangerous nested quantifiers that cause catastrophic backtracking
|
|
39
|
+
dangerous_patterns = [
|
|
40
|
+
r'\([^)]*\+[^)]*\)\+', # (a+)+ style
|
|
41
|
+
r'\([^)]*\*[^)]*\)\+', # (a*)+ style
|
|
42
|
+
r'\([^)]*\+[^)]*\)\*', # (a+)* style
|
|
43
|
+
r'\([^)]*\*[^)]*\)\*', # (a*)* style
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
for dangerous in dangerous_patterns:
|
|
47
|
+
if re.search(dangerous, pattern):
|
|
48
|
+
return False, "Pattern contains nested quantifiers that may cause ReDoS"
|
|
49
|
+
|
|
50
|
+
# Try to compile pattern to check validity
|
|
51
|
+
try:
|
|
52
|
+
re.compile(pattern)
|
|
53
|
+
except re.error as e:
|
|
54
|
+
return False, f"Invalid regex pattern: {e}"
|
|
55
|
+
|
|
56
|
+
return True, ""
|
|
57
|
+
|
|
58
|
+
@contextmanager
|
|
59
|
+
def _regex_timeout(self, seconds: int = 1):
|
|
60
|
+
"""Context manager to timeout regex operations."""
|
|
61
|
+
def timeout_handler(signum, frame):
|
|
62
|
+
raise TimeoutError("Regex search timed out")
|
|
63
|
+
|
|
64
|
+
# Set alarm (Unix only)
|
|
65
|
+
old_handler = signal.signal(signal.SIGALRM, timeout_handler)
|
|
66
|
+
signal.alarm(seconds)
|
|
67
|
+
try:
|
|
68
|
+
yield
|
|
69
|
+
finally:
|
|
70
|
+
signal.alarm(0)
|
|
71
|
+
signal.signal(signal.SIGALRM, old_handler)
|
|
72
|
+
|
|
73
|
+
def update_file(
|
|
74
|
+
self,
|
|
75
|
+
path: str,
|
|
76
|
+
operation: str,
|
|
77
|
+
start_line: int | None = None,
|
|
78
|
+
end_line: int | None = None,
|
|
79
|
+
content: str | None = None,
|
|
80
|
+
search_pattern: str | None = None,
|
|
81
|
+
) -> dict[str, Any]:
|
|
82
|
+
"""
|
|
83
|
+
Update a file by replacing, inserting, or deleting lines.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
path: File path relative to workspace root
|
|
87
|
+
operation: Operation to perform (replace, insert, delete)
|
|
88
|
+
start_line: Start line number (1-indexed, inclusive)
|
|
89
|
+
end_line: End line number (1-indexed, inclusive)
|
|
90
|
+
content: Content for replace/insert operations
|
|
91
|
+
search_pattern: Regex pattern to find target lines (alternative to line numbers)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Dict with success status and metadata
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
file_path = (self.workspace_root / path).resolve()
|
|
98
|
+
|
|
99
|
+
# Security: ensure path is within workspace
|
|
100
|
+
if not str(file_path).startswith(str(self.workspace_root)):
|
|
101
|
+
return {"error": "Path outside workspace"}
|
|
102
|
+
|
|
103
|
+
if not file_path.exists():
|
|
104
|
+
return {"error": "File not found"}
|
|
105
|
+
|
|
106
|
+
if not file_path.is_file():
|
|
107
|
+
return {"error": "Not a file"}
|
|
108
|
+
|
|
109
|
+
# Validate operation
|
|
110
|
+
if operation not in ["replace", "insert", "delete"]:
|
|
111
|
+
return {"error": f"Invalid operation: {operation}"}
|
|
112
|
+
|
|
113
|
+
# Read file
|
|
114
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
115
|
+
lines = f.readlines()
|
|
116
|
+
|
|
117
|
+
total_lines = len(lines)
|
|
118
|
+
original_line_count = total_lines
|
|
119
|
+
|
|
120
|
+
# Determine target line range
|
|
121
|
+
if search_pattern:
|
|
122
|
+
# Security: validate regex pattern to prevent ReDoS
|
|
123
|
+
is_valid, error_msg = self._validate_regex_pattern(search_pattern)
|
|
124
|
+
if not is_valid:
|
|
125
|
+
return {"error": f"Invalid search pattern: {error_msg}"}
|
|
126
|
+
|
|
127
|
+
# Find lines matching pattern with timeout protection
|
|
128
|
+
matches = []
|
|
129
|
+
try:
|
|
130
|
+
with self._regex_timeout(seconds=2):
|
|
131
|
+
compiled_pattern = re.compile(search_pattern)
|
|
132
|
+
for i, line in enumerate(lines, 1):
|
|
133
|
+
if compiled_pattern.search(line):
|
|
134
|
+
matches.append(i)
|
|
135
|
+
except TimeoutError:
|
|
136
|
+
return {"error": "Regex search timed out - pattern may be too complex"}
|
|
137
|
+
|
|
138
|
+
if not matches:
|
|
139
|
+
return {"error": f"Pattern '{search_pattern}' not found"}
|
|
140
|
+
|
|
141
|
+
# Use first match for single-line operations, or all matches for multi-line
|
|
142
|
+
if operation == "delete" and len(matches) > 1:
|
|
143
|
+
# Delete all matching lines
|
|
144
|
+
start_line = matches[0]
|
|
145
|
+
end_line = matches[-1]
|
|
146
|
+
else:
|
|
147
|
+
start_line = matches[0]
|
|
148
|
+
end_line = matches[0]
|
|
149
|
+
|
|
150
|
+
# Validate line numbers
|
|
151
|
+
if start_line is None:
|
|
152
|
+
return {"error": "start_line or search_pattern required"}
|
|
153
|
+
|
|
154
|
+
if start_line < 1 or start_line > total_lines:
|
|
155
|
+
return {"error": f"start_line {start_line} out of range (1-{total_lines})"}
|
|
156
|
+
|
|
157
|
+
if end_line is None:
|
|
158
|
+
end_line = start_line
|
|
159
|
+
|
|
160
|
+
if end_line < start_line or end_line > total_lines:
|
|
161
|
+
return {"error": f"end_line {end_line} invalid (must be {start_line}-{total_lines})"}
|
|
162
|
+
|
|
163
|
+
# Perform operation
|
|
164
|
+
if operation == "replace":
|
|
165
|
+
if content is None:
|
|
166
|
+
return {"error": "content required for replace operation"}
|
|
167
|
+
|
|
168
|
+
# Ensure content ends with newline
|
|
169
|
+
if not content.endswith("\n"):
|
|
170
|
+
content += "\n"
|
|
171
|
+
|
|
172
|
+
# Replace lines
|
|
173
|
+
new_lines = lines[: start_line - 1] + [content] + lines[end_line:]
|
|
174
|
+
|
|
175
|
+
elif operation == "insert":
|
|
176
|
+
if content is None:
|
|
177
|
+
return {"error": "content required for insert operation"}
|
|
178
|
+
|
|
179
|
+
# Ensure content ends with newline
|
|
180
|
+
if not content.endswith("\n"):
|
|
181
|
+
content += "\n"
|
|
182
|
+
|
|
183
|
+
# Insert at line (before start_line)
|
|
184
|
+
new_lines = lines[: start_line - 1] + [content] + lines[start_line - 1 :]
|
|
185
|
+
|
|
186
|
+
elif operation == "delete":
|
|
187
|
+
# Delete lines
|
|
188
|
+
new_lines = lines[: start_line - 1] + lines[end_line:]
|
|
189
|
+
|
|
190
|
+
else:
|
|
191
|
+
return {"error": f"Unsupported operation: {operation}"}
|
|
192
|
+
|
|
193
|
+
# Write file atomically with locking
|
|
194
|
+
temp_path = file_path.with_suffix(file_path.suffix + ".tmp")
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
# Write to temp file with exclusive lock
|
|
198
|
+
with open(temp_path, "w", encoding="utf-8") as f:
|
|
199
|
+
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
|
|
200
|
+
try:
|
|
201
|
+
f.writelines(new_lines)
|
|
202
|
+
f.flush()
|
|
203
|
+
finally:
|
|
204
|
+
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
|
205
|
+
|
|
206
|
+
# Atomic rename
|
|
207
|
+
temp_path.replace(file_path)
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
if temp_path.exists():
|
|
211
|
+
temp_path.unlink()
|
|
212
|
+
raise e
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
"success": True,
|
|
216
|
+
"path": str(file_path), # Return absolute path so TUI can read it back
|
|
217
|
+
"operation": operation,
|
|
218
|
+
"lines_before": original_line_count,
|
|
219
|
+
"lines_after": len(new_lines),
|
|
220
|
+
"affected_range": f"{start_line}-{end_line}",
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
except UnicodeDecodeError:
|
|
224
|
+
return {"error": "File is binary or uses unsupported encoding"}
|
|
225
|
+
except Exception as e:
|
|
226
|
+
return {"error": str(e)}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# Tool schemas for LLM providers (Anthropic/OpenAI format)
|
|
230
|
+
UPDATE_TOOL_SCHEMAS = [
|
|
231
|
+
{
|
|
232
|
+
"name": "update_file",
|
|
233
|
+
"description": "**USE THIS** to modify existing files. Replaces, inserts, or deletes specific line ranges. WORKFLOW: (1) read_file to see current content, (2) determine which lines to modify, (3) call update_file with line numbers and new content.",
|
|
234
|
+
"input_schema": {
|
|
235
|
+
"type": "object",
|
|
236
|
+
"properties": {
|
|
237
|
+
"path": {
|
|
238
|
+
"type": "string",
|
|
239
|
+
"description": "File path relative to workspace root",
|
|
240
|
+
},
|
|
241
|
+
"operation": {
|
|
242
|
+
"type": "string",
|
|
243
|
+
"description": "Operation to perform",
|
|
244
|
+
"enum": ["replace", "insert", "delete"],
|
|
245
|
+
},
|
|
246
|
+
"start_line": {
|
|
247
|
+
"type": "integer",
|
|
248
|
+
"description": "Start line number (1-indexed, inclusive). Required unless using search_pattern.",
|
|
249
|
+
},
|
|
250
|
+
"end_line": {
|
|
251
|
+
"type": "integer",
|
|
252
|
+
"description": "End line number (1-indexed, inclusive). Defaults to start_line if not specified.",
|
|
253
|
+
},
|
|
254
|
+
"content": {
|
|
255
|
+
"type": "string",
|
|
256
|
+
"description": "Content for replace/insert operations",
|
|
257
|
+
},
|
|
258
|
+
"search_pattern": {
|
|
259
|
+
"type": "string",
|
|
260
|
+
"description": "Regex pattern to find target lines (alternative to line numbers)",
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
"required": ["path", "operation"],
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
]
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Built-in web fetch tools for making HTTP requests."""
|
|
2
|
+
|
|
3
|
+
import urllib.request
|
|
4
|
+
import urllib.error
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WebFetchTools:
|
|
9
|
+
"""Built-in tools for fetching web content."""
|
|
10
|
+
|
|
11
|
+
def fetch(
|
|
12
|
+
self,
|
|
13
|
+
url: str,
|
|
14
|
+
method: str = "GET",
|
|
15
|
+
headers: dict[str, str] | None = None,
|
|
16
|
+
body: str | None = None,
|
|
17
|
+
timeout: int = 30,
|
|
18
|
+
) -> dict[str, Any]:
|
|
19
|
+
"""
|
|
20
|
+
Make an HTTP request.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
url: URL to fetch
|
|
24
|
+
method: HTTP method (GET, POST, PUT, DELETE)
|
|
25
|
+
headers: Optional HTTP headers
|
|
26
|
+
body: Optional request body (for POST/PUT)
|
|
27
|
+
timeout: Timeout in seconds (default 30, max 120)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Dict with response body, status code, and headers
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
# Validate URL protocol
|
|
34
|
+
if not url.startswith(("http://", "https://")):
|
|
35
|
+
return {"error": "Only http and https protocols are supported"}
|
|
36
|
+
|
|
37
|
+
# Basic SSRF protection - block localhost
|
|
38
|
+
if any(
|
|
39
|
+
host in url.lower()
|
|
40
|
+
for host in ["localhost", "127.0.0.1", "0.0.0.0", "[::1]"]
|
|
41
|
+
):
|
|
42
|
+
return {"error": "Cannot fetch from localhost"}
|
|
43
|
+
|
|
44
|
+
# Enforce timeout limits
|
|
45
|
+
if timeout > 120:
|
|
46
|
+
timeout = 120
|
|
47
|
+
if timeout < 1:
|
|
48
|
+
timeout = 1
|
|
49
|
+
|
|
50
|
+
# Validate HTTP method
|
|
51
|
+
method = method.upper()
|
|
52
|
+
if method not in ["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH"]:
|
|
53
|
+
return {"error": f"Unsupported HTTP method: {method}"}
|
|
54
|
+
|
|
55
|
+
# Prepare request
|
|
56
|
+
request_headers = headers or {}
|
|
57
|
+
request_body = body.encode("utf-8") if body else None
|
|
58
|
+
|
|
59
|
+
req = urllib.request.Request(
|
|
60
|
+
url,
|
|
61
|
+
data=request_body,
|
|
62
|
+
headers=request_headers,
|
|
63
|
+
method=method,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Execute request
|
|
67
|
+
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
68
|
+
response_body = response.read().decode("utf-8")
|
|
69
|
+
response_headers = dict(response.headers)
|
|
70
|
+
status_code = response.status
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"body": response_body,
|
|
74
|
+
"status_code": status_code,
|
|
75
|
+
"headers": response_headers,
|
|
76
|
+
"url": url,
|
|
77
|
+
"method": method,
|
|
78
|
+
"success": True,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
except urllib.error.HTTPError as e:
|
|
82
|
+
# HTTP errors (4xx, 5xx)
|
|
83
|
+
error_body = e.read().decode("utf-8") if e.fp else ""
|
|
84
|
+
return {
|
|
85
|
+
"error": f"HTTP {e.code}: {e.reason}",
|
|
86
|
+
"status_code": e.code,
|
|
87
|
+
"body": error_body,
|
|
88
|
+
"url": url,
|
|
89
|
+
"success": False,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
except urllib.error.URLError as e:
|
|
93
|
+
# Network errors (DNS, connection, etc.)
|
|
94
|
+
return {
|
|
95
|
+
"error": f"Request failed: {str(e.reason)}",
|
|
96
|
+
"url": url,
|
|
97
|
+
"success": False,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
except TimeoutError:
|
|
101
|
+
return {
|
|
102
|
+
"error": f"Request timed out after {timeout} seconds",
|
|
103
|
+
"url": url,
|
|
104
|
+
"success": False,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
return {"error": str(e), "url": url, "success": False}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Tool schemas for LLM providers (Anthropic/OpenAI format)
|
|
112
|
+
WEBFETCH_TOOL_SCHEMAS = [
|
|
113
|
+
{
|
|
114
|
+
"name": "fetch",
|
|
115
|
+
"description": "Make HTTP requests to fetch web content or call APIs. Supports GET, POST, PUT, DELETE with custom headers and body.",
|
|
116
|
+
"input_schema": {
|
|
117
|
+
"type": "object",
|
|
118
|
+
"properties": {
|
|
119
|
+
"url": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"description": "URL to fetch (must be http or https)",
|
|
122
|
+
},
|
|
123
|
+
"method": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"description": "HTTP method",
|
|
126
|
+
"enum": ["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH"],
|
|
127
|
+
"default": "GET",
|
|
128
|
+
},
|
|
129
|
+
"headers": {
|
|
130
|
+
"type": "object",
|
|
131
|
+
"description": "Optional HTTP headers as key-value pairs",
|
|
132
|
+
"additionalProperties": {"type": "string"},
|
|
133
|
+
},
|
|
134
|
+
"body": {
|
|
135
|
+
"type": "string",
|
|
136
|
+
"description": "Optional request body (for POST/PUT/PATCH)",
|
|
137
|
+
},
|
|
138
|
+
"timeout": {
|
|
139
|
+
"type": "integer",
|
|
140
|
+
"description": "Timeout in seconds (default 30, max 120)",
|
|
141
|
+
"default": 30,
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
"required": ["url"],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ctrlcode
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Adaptive coding harness with differential fuzzing - transforms AI slop into production-ready code
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: aiohttp>=3.10.0
|
|
7
|
+
Requires-Dist: anthropic>=0.40.0
|
|
8
|
+
Requires-Dist: apscheduler>=3.11.2
|
|
9
|
+
Requires-Dist: ctrlcode-tui
|
|
10
|
+
Requires-Dist: faiss-cpu>=1.13.2
|
|
11
|
+
Requires-Dist: harness-utils>=0.3.1
|
|
12
|
+
Requires-Dist: mcp>=1.0.0
|
|
13
|
+
Requires-Dist: networkx>=3.6.1
|
|
14
|
+
Requires-Dist: openai>=1.54.0
|
|
15
|
+
Requires-Dist: platformdirs>=4.5.1
|
|
16
|
+
Requires-Dist: playwright>=1.58.0
|
|
17
|
+
Requires-Dist: sentence-transformers>=5.2.2
|
|
18
|
+
Requires-Dist: tiktoken>=0.12.0
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# ctrl+code
|
|
22
|
+
|
|
23
|
+
Adaptive coding harness with differential fuzzing - transforms AI slop into production-ready code.
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
ctrl+code follows platform conventions for config and data storage:
|
|
28
|
+
|
|
29
|
+
| Platform | Config | Data | Cache |
|
|
30
|
+
|----------|--------|------|-------|
|
|
31
|
+
| **Linux** | `~/.config/ctrlcode/` | `~/.local/share/ctrlcode/` | `~/.cache/ctrlcode/` |
|
|
32
|
+
| **macOS** | `~/Library/Application Support/ctrlcode/` | `~/Library/Application Support/ctrlcode/` | `~/Library/Caches/ctrlcode/` |
|
|
33
|
+
| **Windows** | `%APPDATA%\ctrlcode\` | `%LOCALAPPDATA%\ctrlcode\` | `%LOCALAPPDATA%\ctrlcode\Cache\` |
|
|
34
|
+
|
|
35
|
+
### Environment Variables
|
|
36
|
+
|
|
37
|
+
Override default directories:
|
|
38
|
+
- `CTRLCODE_CONFIG_DIR`: Config file location
|
|
39
|
+
- `CTRLCODE_DATA_DIR`: Session logs and persistent data
|
|
40
|
+
- `CTRLCODE_CACHE_DIR`: Conversation storage and temp files
|
|
41
|
+
|
|
42
|
+
### Configuration File
|
|
43
|
+
|
|
44
|
+
Copy `config.example.toml` to your config directory as `config.toml` and fill in your API keys.
|
|
45
|
+
|
|
46
|
+
### Agent Instructions (AGENT.md)
|
|
47
|
+
|
|
48
|
+
Customize agent behavior with `AGENT.md` files, loaded hierarchically:
|
|
49
|
+
|
|
50
|
+
1. **Global** (`~/.config/ctrlcode/AGENT.md`) - Your personal defaults across all projects
|
|
51
|
+
2. **Project** (`<workspace>/AGENT.md`) - Project-specific instructions
|
|
52
|
+
|
|
53
|
+
Example global `AGENT.md`:
|
|
54
|
+
```markdown
|
|
55
|
+
# Global Agent Defaults
|
|
56
|
+
|
|
57
|
+
- Always use semantic commit messages
|
|
58
|
+
- Show tool results explicitly
|
|
59
|
+
- Prefer built-in tools over scripts
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Example project `AGENT.md`:
|
|
63
|
+
```markdown
|
|
64
|
+
# MyProject Instructions
|
|
65
|
+
|
|
66
|
+
## Architecture
|
|
67
|
+
- Frontend: React + TypeScript
|
|
68
|
+
- Backend: FastAPI + PostgreSQL
|
|
69
|
+
|
|
70
|
+
## Style
|
|
71
|
+
- Use async/await for all I/O
|
|
72
|
+
- Prefer functional components
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Instructions are injected into the system prompt, giving the agent context about your preferences and project structure.
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uv pip install ctrlcode
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
Start the TUI (auto-launches server):
|
|
86
|
+
```bash
|
|
87
|
+
ctrlcode
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Or start server separately:
|
|
91
|
+
```bash
|
|
92
|
+
ctrlcode-server
|
|
93
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
ctrlcode/__init__.py,sha256=kAT6NmF-Gb1M3VNRna0be9CzF8JHROpvCI9wYFNZbgk,207
|
|
2
|
+
ctrlcode/config.py,sha256=fNcAh9oP39Iv1IMbGa-KymAt_qWSTX_MbQKiAsqOc6o,13332
|
|
3
|
+
ctrlcode/paths.py,sha256=eD-RWOra2aGkhhBYbfqHHh2BaGvljZjIJdeO9N2Cuxg,2046
|
|
4
|
+
ctrlcode/permissions.py,sha256=Y4fqYK9iuGH9bqHbYrEpbpc38_PgkKNkjhM8lYaXmkU,5600
|
|
5
|
+
ctrlcode/server.py,sha256=ArRA_mfHRPqYtws66ag_jV1hgGyfTO3nLaPByjAQ3rg,31722
|
|
6
|
+
ctrlcode/agents/__init__.py,sha256=CS7JCKjxLV9uZOvcfX6yE6pBgf9Y2j2FWPIwgAL6-Z4,747
|
|
7
|
+
ctrlcode/agents/cleanup.py,sha256=tq7PVLX1UIGi1EtvtLk-DzKwg3u8fG4nDGLAaOHyJw8,11548
|
|
8
|
+
ctrlcode/agents/communication.py,sha256=9jD1Zz3fDXL4sK_RjjfXuw1AHW3saWnivZ1aI_6iH84,14196
|
|
9
|
+
ctrlcode/agents/observability.py,sha256=m1iNfmSKy_AuKzU4nAq7CetTLJqME69VUz8y61h7768,11950
|
|
10
|
+
ctrlcode/agents/react_loop.py,sha256=meOb1t7P_a6cACDCWhzEFpZyAmyqO_kSiHAHn1JQYfE,12214
|
|
11
|
+
ctrlcode/agents/registry.py,sha256=ulR0r_ulFrLMmMCORhH0mu9pDWtGUI2jjO1e-sMIi58,6638
|
|
12
|
+
ctrlcode/agents/result_parser.py,sha256=NN9QxL2iaRDN_SAaVLRT65NJhW2MkkzqmAAuQ75SFvw,8615
|
|
13
|
+
ctrlcode/agents/workflow.py,sha256=8rMmndcpjQYxJ9MnDL3ueCswHPHiJqKkAZpNUM9i5D4,22973
|
|
14
|
+
ctrlcode/analysis/__init__.py,sha256=ALlJjhWNH6i7I0V_eOOzxvxgpyapvOXH0dv7Yy6ojVo,798
|
|
15
|
+
ctrlcode/analysis/ast_diff.py,sha256=7VslujMfOjWKLwL-aBJq9bueFXoJWWn4o8QppRETBH0,4977
|
|
16
|
+
ctrlcode/analysis/bug_detector.py,sha256=ZMUf81G1VQl5zWFWRX87oB-LVvAfJ-ibaPphhqcmZxQ,4814
|
|
17
|
+
ctrlcode/analysis/code_graphs.py,sha256=eoDPpnx-daupny37Z_ls8JZHpnKUmy3F8RyayurNlcM,10827
|
|
18
|
+
ctrlcode/analysis/semantic.py,sha256=ebZQOlEepW7kTpAxONXSeuJZqGD0gyVVW0a2CZXUJ8I,5647
|
|
19
|
+
ctrlcode/analysis/static.py,sha256=MwMMbyyff2bcz5lCwmOxgQPrH0JxYzlvN39axRm-2EY,5215
|
|
20
|
+
ctrlcode/analysis/synthesizer.py,sha256=wzgIvc6U4paWvj6zaHsOI5YJYf8T2Lg6orL3n97v8VY,9322
|
|
21
|
+
ctrlcode/analysis/tests.py,sha256=xfFv6fjcPxQTwaTBSkq2c6k_X2y3oXtdVtSGEdECCY0,5077
|
|
22
|
+
ctrlcode/cleanup/__init__.py,sha256=op6vMRW6qfwdrXfxKfa-ryUO9zQA1mZde0ebD_lXrjg,410
|
|
23
|
+
ctrlcode/cleanup/auto_merge.py,sha256=wn3P_n_GzxDIWUVQF7Xdr7Y3MlWiOi8qJCOHUT0DFeU,11563
|
|
24
|
+
ctrlcode/cleanup/doc_gardening.py,sha256=---B8_vHiLEdbR_ixyp0tIXcBLdZMdkMVdKMY1Vsd1E,12948
|
|
25
|
+
ctrlcode/cleanup/pr_automation.py,sha256=jg2-sjU-bGuoFVsS8G2560dvrVERptFMWw0ORpvM5dY,9742
|
|
26
|
+
ctrlcode/cleanup/scheduler.py,sha256=Oz1DPFYK3xfmSmWBBWhWHHWqLpJexnxhPDGq4VXfznw,11953
|
|
27
|
+
ctrlcode/embeddings/__init__.py,sha256=ONEGtZi3GHk_KzUYDPU0GPEDTui2QkzPGLdxkWqS4ZI,238
|
|
28
|
+
ctrlcode/embeddings/embedder.py,sha256=XAFWzniSrjEmnCFENtWIjWgvLCfcxEbi3-essUpKHJ8,6233
|
|
29
|
+
ctrlcode/embeddings/vector_store.py,sha256=EGQSFFsdSilWfhN-KoYP4ow96BlCFjermOWE0Uyezk4,7112
|
|
30
|
+
ctrlcode/fuzzing/__init__.py,sha256=7m7q9GS67Wy30lxePFzlt_j77A3UMExArsrLQuvyKZY,846
|
|
31
|
+
ctrlcode/fuzzing/analyzer.py,sha256=4daiYnRIbLQz_cwGajajBJVQOd225-IoGEjgIajxwrg,9300
|
|
32
|
+
ctrlcode/fuzzing/budget.py,sha256=MN-E9DnUXR2KG68C2IfrfptzhnyeN0NY08RRWrD4feU,3076
|
|
33
|
+
ctrlcode/fuzzing/context.py,sha256=RKHBCFpp39X0FijvX3c2MujCOiXSNRKpGM9Ds_6ZwSY,24181
|
|
34
|
+
ctrlcode/fuzzing/context_fuzzer.py,sha256=m0GH9jRPEQJmceBSOxMc9k26rWk-vYQX33oorVBJ0nw,17436
|
|
35
|
+
ctrlcode/fuzzing/derived_orchestrator.py,sha256=lcwivhUIENxmpv8QWDsD69zZkUXC7rYODmCXjl_Fvag,28302
|
|
36
|
+
ctrlcode/fuzzing/oracle_adapter.py,sha256=_rO_ylebUnjT5nS_2Z0P1KTkhJMh9MLGEeXBa-9Tj2g,4210
|
|
37
|
+
ctrlcode/linters/__init__.py,sha256=L3bleyJIZr2foHrOQBufhErhlUnKV8UiVto4gwyBeg4,304
|
|
38
|
+
ctrlcode/linters/hand_rolled_utils.py,sha256=zCmb5Wd2ixteaSGMU85qrJUeIvY3290aEiTdwEKs2CU,6232
|
|
39
|
+
ctrlcode/linters/yolo_parsing.py,sha256=nn187KRVio9WniJMy0NFqA4TnyGCw8rXgWkEJYCSnCk,6433
|
|
40
|
+
ctrlcode/metrics/__init__.py,sha256=6S4RCHT6leSY0ONzB4-bvufBIkKcvK_PbDV2SANeHU0,241
|
|
41
|
+
ctrlcode/metrics/dashboard.py,sha256=M2kOQKvtV7UQ_Uz2lVPJsWJRMXByAxsiOg00xNWgdrE,9903
|
|
42
|
+
ctrlcode/metrics/tech_debt.py,sha256=gFTIdg2r39U1LuGkElI1J2km8vnQyqziQ6PPtKKqv8A,21299
|
|
43
|
+
ctrlcode/providers/__init__.py,sha256=xJcoWTMOd5EZYNctGDuRy3GRJudW6TwDGG78crEtPII,361
|
|
44
|
+
ctrlcode/providers/anthropic.py,sha256=SNaA2eZ_An9xO9zk9BQtaHxrUPPBngMcA-DP5FZf-6Y,5188
|
|
45
|
+
ctrlcode/providers/base.py,sha256=fubYqGjj5twihic28wg8PIBwFOBIp3fQD9sNK5y53_M,1994
|
|
46
|
+
ctrlcode/providers/openai.py,sha256=H1lwPfnLO7I7h7Hs6zTEAmdsJkTO7Ud48miH23EAv-c,7560
|
|
47
|
+
ctrlcode/providers/parallel.py,sha256=ucYh8byJKM0Rq1YA9eHURh99nxMPp_bDoDZSoavepqE,3041
|
|
48
|
+
ctrlcode/session/__init__.py,sha256=Kv-V1cPLJVCh0Oal9_d4i-JJ6A-8hZUzbKe3TRgG6fY,206
|
|
49
|
+
ctrlcode/session/baseline.py,sha256=3fok8n0SeI3IaqiPfO3t9rl7uA72SmhvgOULHUjHQY4,1700
|
|
50
|
+
ctrlcode/session/manager.py,sha256=MgwzXxqCt8Z6dT62n3sP2Hz1FN-qJjIDKhHVxcQfKJY,43271
|
|
51
|
+
ctrlcode/skills/__init__.py,sha256=p-62spnGJNkZqBsjHKVvvycXiUjEG3s7vgqWY5x_YYo,179
|
|
52
|
+
ctrlcode/skills/loader.py,sha256=kauoPOFuuaCwkIS66zufdADKVWRFgY2NjLmKfcS_b4o,3055
|
|
53
|
+
ctrlcode/skills/registry.py,sha256=2SCo8n1p_Nu1FNuW05WxotKTQCgUuHpVTKPkVvQZR88,3565
|
|
54
|
+
ctrlcode/skills/builtin/commit.toml,sha256=biDWNESOCzlJ8s555_dQtZl6AiYgz3cU2yxeWmPNITI,984
|
|
55
|
+
ctrlcode/skills/builtin/docs.toml,sha256=VdJ5O1iCVZCnC_fh-vOSqvhn3z_oCaPr5W6OtUBcIxw,651
|
|
56
|
+
ctrlcode/skills/builtin/refactor.toml,sha256=aXIZ_0alp54IAjhbKBxbhQzkyixebqtdmYtE5nXMJLs,797
|
|
57
|
+
ctrlcode/skills/builtin/review.toml,sha256=h3Kmo29vmz7lDrkCne8QpFwZMUR-MWyLL4GManisfkI,711
|
|
58
|
+
ctrlcode/skills/builtin/test.toml,sha256=NgYPf9v_pVwGSU-zaANAGRsbsvlLcxdkGjRowP8HFnA,637
|
|
59
|
+
ctrlcode/storage/__init__.py,sha256=t8_whfiyzIpXwDkSqz_woKsIZDLiGCzmwx5ENHleKBI,347
|
|
60
|
+
ctrlcode/storage/history_db.py,sha256=Ih9X1Dsx0AjwwrfgXSfInf7zDn-gwbnCT8p9JkdgD9A,22429
|
|
61
|
+
ctrlcode/tools/__init__.py,sha256=KvK2ED4OiesYQUNsSv0sq3a-0dbM_ZzjI1_QARypoRs,6240
|
|
62
|
+
ctrlcode/tools/bash.py,sha256=8k-qSFLU-r6j8-IgfiCWTHzv0ZQUS4HE2K8n_AEsI-8,3586
|
|
63
|
+
ctrlcode/tools/browser.py,sha256=SPOUX3zfNPBn1UIvX7USt_6rgLoJY2BYMZBKr1_BM0k,12155
|
|
64
|
+
ctrlcode/tools/executor.py,sha256=ybRPyKlW4sA8bLyYwXf7vofX4NF0le1ur--bvvQ9VQw,4749
|
|
65
|
+
ctrlcode/tools/explore.py,sha256=WLChsk4KO_Ulaqb2XZNhxKdZPSoCM6r3KWOVEVDZB_E,16252
|
|
66
|
+
ctrlcode/tools/mcp.py,sha256=ijCMVKmrvRH4tjUHxvBdpq2FAI2VVqw0Z5Ry1WFZtX8,3131
|
|
67
|
+
ctrlcode/tools/observability.py,sha256=ubeKxqD--Vdy8a3I7N0Mofn4_8aS6tQq_pzUTHJTv2g,18333
|
|
68
|
+
ctrlcode/tools/registry.py,sha256=6g08SGzYLHmQwta9Q-89WJh8frC_owwcJIYQX5Eu8Fc,5643
|
|
69
|
+
ctrlcode/tools/todo.py,sha256=LyNmZzgSVaBzL5FO0MEroQ4WDgWnIYri2TymsKjCQuk,10315
|
|
70
|
+
ctrlcode/tools/update.py,sha256=TZsmdugryoWKdcZyKEe2wZbaG5NzLIotdIbj2OGDvBE,9817
|
|
71
|
+
ctrlcode/tools/webfetch.py,sha256=tMOoMA9zr3ErZbKPdsu5XxTvdA_QHZ1kja6J56n-RgE,4834
|
|
72
|
+
ctrlcode-0.1.0.dist-info/METADATA,sha256=cwVabi2IcF61b3OFVbW5PkzTYm6QqVYr-StJUV21Y7k,2564
|
|
73
|
+
ctrlcode-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
74
|
+
ctrlcode-0.1.0.dist-info/entry_points.txt,sha256=Q2-XnFYshpszkPxrw0dFD59SDJRnrNs32QpAQdoR-C4,90
|
|
75
|
+
ctrlcode-0.1.0.dist-info/RECORD,,
|