patch-code 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.
- patch/__init__.py +3 -0
- patch/__main__.py +4 -0
- patch/analytics.py +268 -0
- patch/args.py +949 -0
- patch/args_formatter.py +227 -0
- patch/coders/__init__.py +34 -0
- patch/coders/architect_coder.py +48 -0
- patch/coders/architect_prompts.py +40 -0
- patch/coders/ask_coder.py +9 -0
- patch/coders/ask_prompts.py +41 -0
- patch/coders/base_coder.py +2485 -0
- patch/coders/base_prompts.py +60 -0
- patch/coders/chat_chunks.py +64 -0
- patch/coders/context_coder.py +53 -0
- patch/coders/context_prompts.py +75 -0
- patch/coders/editblock_coder.py +657 -0
- patch/coders/editblock_fenced_coder.py +10 -0
- patch/coders/editblock_fenced_prompts.py +143 -0
- patch/coders/editblock_func_coder.py +141 -0
- patch/coders/editblock_func_prompts.py +27 -0
- patch/coders/editblock_prompts.py +172 -0
- patch/coders/editor_diff_fenced_coder.py +9 -0
- patch/coders/editor_diff_fenced_prompts.py +11 -0
- patch/coders/editor_editblock_coder.py +8 -0
- patch/coders/editor_editblock_prompts.py +18 -0
- patch/coders/editor_whole_coder.py +8 -0
- patch/coders/editor_whole_prompts.py +10 -0
- patch/coders/help_coder.py +16 -0
- patch/coders/help_prompts.py +46 -0
- patch/coders/patch_coder.py +706 -0
- patch/coders/patch_prompts.py +159 -0
- patch/coders/search_replace.py +757 -0
- patch/coders/shell.py +37 -0
- patch/coders/single_wholefile_func_coder.py +102 -0
- patch/coders/single_wholefile_func_prompts.py +27 -0
- patch/coders/udiff_coder.py +429 -0
- patch/coders/udiff_prompts.py +113 -0
- patch/coders/udiff_simple.py +14 -0
- patch/coders/udiff_simple_prompts.py +25 -0
- patch/coders/wholefile_coder.py +144 -0
- patch/coders/wholefile_func_coder.py +134 -0
- patch/coders/wholefile_func_prompts.py +27 -0
- patch/coders/wholefile_prompts.py +64 -0
- patch/commands.py +1712 -0
- patch/copypaste.py +72 -0
- patch/deprecated.py +126 -0
- patch/diffs.py +128 -0
- patch/docs/__init__.py +1 -0
- patch/docs/analytics.md +28 -0
- patch/docs/config.md +43 -0
- patch/docs/git.md +22 -0
- patch/docs/install.md +54 -0
- patch/docs/models.md +59 -0
- patch/docs/troubleshooting.md +23 -0
- patch/docs/usage.md +47 -0
- patch/dump.py +29 -0
- patch/editor.py +147 -0
- patch/exceptions.py +113 -0
- patch/format_settings.py +26 -0
- patch/gui.py +545 -0
- patch/help.py +118 -0
- patch/history.py +143 -0
- patch/io.py +1191 -0
- patch/linter.py +304 -0
- patch/llm.py +47 -0
- patch/main.py +1274 -0
- patch/mdstream.py +243 -0
- patch/models.py +1338 -0
- patch/onboarding.py +428 -0
- patch/openrouter.py +128 -0
- patch/prompts.py +61 -0
- patch/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- patch/queries/tree-sitter-language-pack/bash-tags.scm +8 -0
- patch/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- patch/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- patch/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
- patch/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- patch/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- patch/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- patch/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- patch/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- patch/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- patch/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- patch/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- patch/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- patch/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- patch/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- patch/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- patch/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- patch/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
- patch/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- patch/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- patch/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- patch/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- patch/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- patch/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- patch/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- patch/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- patch/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- patch/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- patch/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- patch/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- patch/queries/tree-sitter-languages/bash-tags.scm +8 -0
- patch/queries/tree-sitter-languages/c-tags.scm +9 -0
- patch/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- patch/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- patch/queries/tree-sitter-languages/dart-tags.scm +91 -0
- patch/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- patch/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- patch/queries/tree-sitter-languages/elm-tags.scm +19 -0
- patch/queries/tree-sitter-languages/fortran-tags.scm +15 -0
- patch/queries/tree-sitter-languages/go-tags.scm +30 -0
- patch/queries/tree-sitter-languages/haskell-tags.scm +3 -0
- patch/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- patch/queries/tree-sitter-languages/java-tags.scm +20 -0
- patch/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- patch/queries/tree-sitter-languages/julia-tags.scm +60 -0
- patch/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- patch/queries/tree-sitter-languages/matlab-tags.scm +10 -0
- patch/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- patch/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- patch/queries/tree-sitter-languages/php-tags.scm +26 -0
- patch/queries/tree-sitter-languages/python-tags.scm +12 -0
- patch/queries/tree-sitter-languages/ql-tags.scm +26 -0
- patch/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- patch/queries/tree-sitter-languages/rust-tags.scm +60 -0
- patch/queries/tree-sitter-languages/scala-tags.scm +65 -0
- patch/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- patch/queries/tree-sitter-languages/zig-tags.scm +3 -0
- patch/reasoning_tags.py +82 -0
- patch/repo.py +622 -0
- patch/repomap.py +867 -0
- patch/report.py +200 -0
- patch/resources/__init__.py +3 -0
- patch/resources/model-metadata.json +715 -0
- patch/resources/model-settings.yml +3128 -0
- patch/run_cmd.py +132 -0
- patch/scrape.py +284 -0
- patch/sendchat.py +61 -0
- patch/special.py +203 -0
- patch/urls.py +17 -0
- patch/utils.py +348 -0
- patch/versioncheck.py +130 -0
- patch/voice.py +187 -0
- patch/waiting.py +221 -0
- patch/watch.py +318 -0
- patch/watch_prompts.py +12 -0
- patch_code-0.1.0.dist-info/METADATA +467 -0
- patch_code-0.1.0.dist-info/RECORD +153 -0
- patch_code-0.1.0.dist-info/WHEEL +5 -0
- patch_code-0.1.0.dist-info/entry_points.txt +2 -0
- patch_code-0.1.0.dist-info/licenses/LICENSE.txt +202 -0
- patch_code-0.1.0.dist-info/top_level.txt +1 -0
patch/__init__.py
ADDED
patch/__main__.py
ADDED
patch/analytics.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import platform
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
import uuid
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from mixpanel import MixpanelException
|
|
9
|
+
from posthog import Posthog
|
|
10
|
+
|
|
11
|
+
from patch import __version__
|
|
12
|
+
from patch.dump import dump # noqa: F401
|
|
13
|
+
from patch.models import model_info_manager
|
|
14
|
+
|
|
15
|
+
PERCENT = 10
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def compute_hex_threshold(percent):
|
|
19
|
+
"""Convert percentage to 6-digit hex threshold.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
percent: Percentage threshold (0-100)
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
str: 6-digit hex threshold
|
|
26
|
+
"""
|
|
27
|
+
return format(int(0xFFFFFF * percent / 100), "06x")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_uuid_in_percentage(uuid_str, percent):
|
|
31
|
+
"""Check if a UUID string falls within the first X percent of the UUID space.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
uuid_str: UUID string to test
|
|
35
|
+
percent: Percentage threshold (0-100)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
bool: True if UUID falls within the first X percent
|
|
39
|
+
"""
|
|
40
|
+
if not (0 <= percent <= 100):
|
|
41
|
+
raise ValueError("Percentage must be between 0 and 100")
|
|
42
|
+
|
|
43
|
+
if not uuid_str:
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
# Convert percentage to hex threshold (1% = "04...", 10% = "1a...", etc)
|
|
47
|
+
# Using first 6 hex digits
|
|
48
|
+
if percent == 0:
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
threshold = compute_hex_threshold(percent)
|
|
52
|
+
return uuid_str[:6] <= threshold
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Patch does not operate an analytics service, and deliberately ships no default
|
|
56
|
+
# destination for events. Collection stays off unless the user points it at a
|
|
57
|
+
# PostHog project they control with --analytics-posthog-project-api-key.
|
|
58
|
+
posthog_host = "https://us.i.posthog.com"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Analytics:
|
|
62
|
+
# providers
|
|
63
|
+
mp = None
|
|
64
|
+
ph = None
|
|
65
|
+
|
|
66
|
+
# saved
|
|
67
|
+
user_id = None
|
|
68
|
+
permanently_disable = None
|
|
69
|
+
asked_opt_in = None
|
|
70
|
+
|
|
71
|
+
# ephemeral
|
|
72
|
+
logfile = None
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
logfile=None,
|
|
77
|
+
permanently_disable=False,
|
|
78
|
+
posthog_host=None,
|
|
79
|
+
posthog_project_api_key=None,
|
|
80
|
+
):
|
|
81
|
+
self.logfile = logfile
|
|
82
|
+
self.get_or_create_uuid()
|
|
83
|
+
self.custom_posthog_host = posthog_host
|
|
84
|
+
self.custom_posthog_project_api_key = posthog_project_api_key
|
|
85
|
+
|
|
86
|
+
if self.permanently_disable or permanently_disable or not self.asked_opt_in:
|
|
87
|
+
self.disable(permanently_disable)
|
|
88
|
+
|
|
89
|
+
def enable(self):
|
|
90
|
+
if not self.user_id:
|
|
91
|
+
self.disable(False)
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
if self.permanently_disable:
|
|
95
|
+
self.disable(True)
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
if not self.asked_opt_in:
|
|
99
|
+
self.disable(False)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
if not self.custom_posthog_project_api_key:
|
|
103
|
+
# No destination is configured, so there is nothing to send events to.
|
|
104
|
+
self.disable(False)
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
self.ph = Posthog(
|
|
108
|
+
project_api_key=self.custom_posthog_project_api_key,
|
|
109
|
+
host=self.custom_posthog_host or posthog_host,
|
|
110
|
+
on_error=self.posthog_error,
|
|
111
|
+
# Autocapture bypasses the redaction applied in event(), so leave it off.
|
|
112
|
+
enable_exception_autocapture=False,
|
|
113
|
+
super_properties=self.get_system_info(), # Add system info to all events
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def disable(self, permanently):
|
|
117
|
+
self.mp = None
|
|
118
|
+
self.ph = None
|
|
119
|
+
|
|
120
|
+
if permanently:
|
|
121
|
+
self.asked_opt_in = True
|
|
122
|
+
self.permanently_disable = True
|
|
123
|
+
self.save_data()
|
|
124
|
+
|
|
125
|
+
def need_to_ask(self, args_analytics):
|
|
126
|
+
if args_analytics is False:
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
if not self.custom_posthog_project_api_key:
|
|
130
|
+
# Nothing would be collected, so never ask the user to opt in.
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
could_ask = not self.asked_opt_in and not self.permanently_disable
|
|
134
|
+
if not could_ask:
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
if args_analytics is True:
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
assert args_analytics is None, args_analytics
|
|
141
|
+
|
|
142
|
+
if not self.user_id:
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
return is_uuid_in_percentage(self.user_id, PERCENT)
|
|
146
|
+
|
|
147
|
+
def get_data_file_path(self):
|
|
148
|
+
try:
|
|
149
|
+
data_file = Path.home() / ".patch" / "analytics.json"
|
|
150
|
+
data_file.parent.mkdir(parents=True, exist_ok=True)
|
|
151
|
+
return data_file
|
|
152
|
+
except OSError:
|
|
153
|
+
# If we can't create/access the directory, just disable analytics
|
|
154
|
+
self.disable(permanently=False)
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
def get_or_create_uuid(self):
|
|
158
|
+
self.load_data()
|
|
159
|
+
if self.user_id:
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
self.user_id = str(uuid.uuid4())
|
|
163
|
+
self.save_data()
|
|
164
|
+
|
|
165
|
+
def load_data(self):
|
|
166
|
+
data_file = self.get_data_file_path()
|
|
167
|
+
if not data_file:
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
if data_file.exists():
|
|
171
|
+
try:
|
|
172
|
+
data = json.loads(data_file.read_text())
|
|
173
|
+
self.permanently_disable = data.get("permanently_disable")
|
|
174
|
+
self.user_id = data.get("uuid")
|
|
175
|
+
self.asked_opt_in = data.get("asked_opt_in", False)
|
|
176
|
+
except (json.decoder.JSONDecodeError, OSError):
|
|
177
|
+
self.disable(permanently=False)
|
|
178
|
+
|
|
179
|
+
def save_data(self):
|
|
180
|
+
data_file = self.get_data_file_path()
|
|
181
|
+
if not data_file:
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
data = dict(
|
|
185
|
+
uuid=self.user_id,
|
|
186
|
+
permanently_disable=self.permanently_disable,
|
|
187
|
+
asked_opt_in=self.asked_opt_in,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
data_file.write_text(json.dumps(data, indent=4))
|
|
192
|
+
except OSError:
|
|
193
|
+
# If we can't write the file, just disable analytics
|
|
194
|
+
self.disable(permanently=False)
|
|
195
|
+
|
|
196
|
+
def get_system_info(self):
|
|
197
|
+
return {
|
|
198
|
+
"python_version": sys.version.split()[0],
|
|
199
|
+
"os_platform": platform.system(),
|
|
200
|
+
"os_release": platform.release(),
|
|
201
|
+
"machine": platform.machine(),
|
|
202
|
+
"patch_version": __version__,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
def _redact_model_name(self, model):
|
|
206
|
+
if not model:
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
info = model_info_manager.get_model_from_cached_json_db(model.name)
|
|
210
|
+
if info:
|
|
211
|
+
return model.name
|
|
212
|
+
elif "/" in model.name:
|
|
213
|
+
return model.name.split("/")[0] + "/REDACTED"
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
def posthog_error(self):
|
|
217
|
+
"""disable posthog if we get an error"""
|
|
218
|
+
print("X" * 100)
|
|
219
|
+
# https://github.com/PostHog/posthog-python/blob/9e1bb8c58afaa229da24c4fb576c08bb88a75752/posthog/consumer.py#L86
|
|
220
|
+
# https://github.com/Aider-AI/aider/issues/2532
|
|
221
|
+
self.ph = None
|
|
222
|
+
|
|
223
|
+
def event(self, event_name, main_model=None, **kwargs):
|
|
224
|
+
if not self.mp and not self.ph and not self.logfile:
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
properties = {}
|
|
228
|
+
|
|
229
|
+
if main_model:
|
|
230
|
+
properties["main_model"] = self._redact_model_name(main_model)
|
|
231
|
+
properties["weak_model"] = self._redact_model_name(main_model.weak_model)
|
|
232
|
+
properties["editor_model"] = self._redact_model_name(main_model.editor_model)
|
|
233
|
+
|
|
234
|
+
properties.update(kwargs)
|
|
235
|
+
|
|
236
|
+
# Handle numeric values
|
|
237
|
+
for key, value in properties.items():
|
|
238
|
+
if isinstance(value, (int, float)):
|
|
239
|
+
properties[key] = value
|
|
240
|
+
else:
|
|
241
|
+
properties[key] = str(value)
|
|
242
|
+
|
|
243
|
+
if self.mp:
|
|
244
|
+
try:
|
|
245
|
+
self.mp.track(self.user_id, event_name, dict(properties))
|
|
246
|
+
except MixpanelException:
|
|
247
|
+
self.mp = None # Disable mixpanel on connection errors
|
|
248
|
+
|
|
249
|
+
if self.ph:
|
|
250
|
+
self.ph.capture(event_name, distinct_id=self.user_id, properties=dict(properties))
|
|
251
|
+
|
|
252
|
+
if self.logfile:
|
|
253
|
+
log_entry = {
|
|
254
|
+
"event": event_name,
|
|
255
|
+
"properties": properties,
|
|
256
|
+
"user_id": self.user_id,
|
|
257
|
+
"time": int(time.time()),
|
|
258
|
+
}
|
|
259
|
+
try:
|
|
260
|
+
with open(self.logfile, "a") as f:
|
|
261
|
+
json.dump(log_entry, f)
|
|
262
|
+
f.write("\n")
|
|
263
|
+
except OSError:
|
|
264
|
+
pass # Ignore OS errors when writing to logfile
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
if __name__ == "__main__":
|
|
268
|
+
dump(compute_hex_threshold(PERCENT))
|