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.
Files changed (153) hide show
  1. patch/__init__.py +3 -0
  2. patch/__main__.py +4 -0
  3. patch/analytics.py +268 -0
  4. patch/args.py +949 -0
  5. patch/args_formatter.py +227 -0
  6. patch/coders/__init__.py +34 -0
  7. patch/coders/architect_coder.py +48 -0
  8. patch/coders/architect_prompts.py +40 -0
  9. patch/coders/ask_coder.py +9 -0
  10. patch/coders/ask_prompts.py +41 -0
  11. patch/coders/base_coder.py +2485 -0
  12. patch/coders/base_prompts.py +60 -0
  13. patch/coders/chat_chunks.py +64 -0
  14. patch/coders/context_coder.py +53 -0
  15. patch/coders/context_prompts.py +75 -0
  16. patch/coders/editblock_coder.py +657 -0
  17. patch/coders/editblock_fenced_coder.py +10 -0
  18. patch/coders/editblock_fenced_prompts.py +143 -0
  19. patch/coders/editblock_func_coder.py +141 -0
  20. patch/coders/editblock_func_prompts.py +27 -0
  21. patch/coders/editblock_prompts.py +172 -0
  22. patch/coders/editor_diff_fenced_coder.py +9 -0
  23. patch/coders/editor_diff_fenced_prompts.py +11 -0
  24. patch/coders/editor_editblock_coder.py +8 -0
  25. patch/coders/editor_editblock_prompts.py +18 -0
  26. patch/coders/editor_whole_coder.py +8 -0
  27. patch/coders/editor_whole_prompts.py +10 -0
  28. patch/coders/help_coder.py +16 -0
  29. patch/coders/help_prompts.py +46 -0
  30. patch/coders/patch_coder.py +706 -0
  31. patch/coders/patch_prompts.py +159 -0
  32. patch/coders/search_replace.py +757 -0
  33. patch/coders/shell.py +37 -0
  34. patch/coders/single_wholefile_func_coder.py +102 -0
  35. patch/coders/single_wholefile_func_prompts.py +27 -0
  36. patch/coders/udiff_coder.py +429 -0
  37. patch/coders/udiff_prompts.py +113 -0
  38. patch/coders/udiff_simple.py +14 -0
  39. patch/coders/udiff_simple_prompts.py +25 -0
  40. patch/coders/wholefile_coder.py +144 -0
  41. patch/coders/wholefile_func_coder.py +134 -0
  42. patch/coders/wholefile_func_prompts.py +27 -0
  43. patch/coders/wholefile_prompts.py +64 -0
  44. patch/commands.py +1712 -0
  45. patch/copypaste.py +72 -0
  46. patch/deprecated.py +126 -0
  47. patch/diffs.py +128 -0
  48. patch/docs/__init__.py +1 -0
  49. patch/docs/analytics.md +28 -0
  50. patch/docs/config.md +43 -0
  51. patch/docs/git.md +22 -0
  52. patch/docs/install.md +54 -0
  53. patch/docs/models.md +59 -0
  54. patch/docs/troubleshooting.md +23 -0
  55. patch/docs/usage.md +47 -0
  56. patch/dump.py +29 -0
  57. patch/editor.py +147 -0
  58. patch/exceptions.py +113 -0
  59. patch/format_settings.py +26 -0
  60. patch/gui.py +545 -0
  61. patch/help.py +118 -0
  62. patch/history.py +143 -0
  63. patch/io.py +1191 -0
  64. patch/linter.py +304 -0
  65. patch/llm.py +47 -0
  66. patch/main.py +1274 -0
  67. patch/mdstream.py +243 -0
  68. patch/models.py +1338 -0
  69. patch/onboarding.py +428 -0
  70. patch/openrouter.py +128 -0
  71. patch/prompts.py +61 -0
  72. patch/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  73. patch/queries/tree-sitter-language-pack/bash-tags.scm +8 -0
  74. patch/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  75. patch/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  76. patch/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
  77. patch/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  78. patch/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  79. patch/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  80. patch/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  81. patch/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  82. patch/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  83. patch/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  84. patch/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  85. patch/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  86. patch/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  87. patch/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  88. patch/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  89. patch/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  90. patch/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
  91. patch/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  92. patch/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  93. patch/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  94. patch/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  95. patch/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  96. patch/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  97. patch/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  98. patch/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  99. patch/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  100. patch/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  101. patch/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  102. patch/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  103. patch/queries/tree-sitter-languages/bash-tags.scm +8 -0
  104. patch/queries/tree-sitter-languages/c-tags.scm +9 -0
  105. patch/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  106. patch/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  107. patch/queries/tree-sitter-languages/dart-tags.scm +91 -0
  108. patch/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  109. patch/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  110. patch/queries/tree-sitter-languages/elm-tags.scm +19 -0
  111. patch/queries/tree-sitter-languages/fortran-tags.scm +15 -0
  112. patch/queries/tree-sitter-languages/go-tags.scm +30 -0
  113. patch/queries/tree-sitter-languages/haskell-tags.scm +3 -0
  114. patch/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  115. patch/queries/tree-sitter-languages/java-tags.scm +20 -0
  116. patch/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  117. patch/queries/tree-sitter-languages/julia-tags.scm +60 -0
  118. patch/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  119. patch/queries/tree-sitter-languages/matlab-tags.scm +10 -0
  120. patch/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  121. patch/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  122. patch/queries/tree-sitter-languages/php-tags.scm +26 -0
  123. patch/queries/tree-sitter-languages/python-tags.scm +12 -0
  124. patch/queries/tree-sitter-languages/ql-tags.scm +26 -0
  125. patch/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  126. patch/queries/tree-sitter-languages/rust-tags.scm +60 -0
  127. patch/queries/tree-sitter-languages/scala-tags.scm +65 -0
  128. patch/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  129. patch/queries/tree-sitter-languages/zig-tags.scm +3 -0
  130. patch/reasoning_tags.py +82 -0
  131. patch/repo.py +622 -0
  132. patch/repomap.py +867 -0
  133. patch/report.py +200 -0
  134. patch/resources/__init__.py +3 -0
  135. patch/resources/model-metadata.json +715 -0
  136. patch/resources/model-settings.yml +3128 -0
  137. patch/run_cmd.py +132 -0
  138. patch/scrape.py +284 -0
  139. patch/sendchat.py +61 -0
  140. patch/special.py +203 -0
  141. patch/urls.py +17 -0
  142. patch/utils.py +348 -0
  143. patch/versioncheck.py +130 -0
  144. patch/voice.py +187 -0
  145. patch/waiting.py +221 -0
  146. patch/watch.py +318 -0
  147. patch/watch_prompts.py +12 -0
  148. patch_code-0.1.0.dist-info/METADATA +467 -0
  149. patch_code-0.1.0.dist-info/RECORD +153 -0
  150. patch_code-0.1.0.dist-info/WHEEL +5 -0
  151. patch_code-0.1.0.dist-info/entry_points.txt +2 -0
  152. patch_code-0.1.0.dist-info/licenses/LICENSE.txt +202 -0
  153. patch_code-0.1.0.dist-info/top_level.txt +1 -0
patch/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ __version__ = "0.1.0"
2
+
3
+ __all__ = ["__version__"]
patch/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .main import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
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))