gptdiff 0.1.15__tar.gz → 0.1.18__tar.gz
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.
- {gptdiff-0.1.15 → gptdiff-0.1.18}/PKG-INFO +34 -3
- {gptdiff-0.1.15 → gptdiff-0.1.18}/README.md +32 -1
- gptdiff-0.1.18/gptdiff/__init__.py +3 -0
- {gptdiff-0.1.15 → gptdiff-0.1.18}/gptdiff/gptdiff.py +43 -15
- {gptdiff-0.1.15 → gptdiff-0.1.18}/gptdiff.egg-info/PKG-INFO +34 -3
- {gptdiff-0.1.15 → gptdiff-0.1.18}/gptdiff.egg-info/SOURCES.txt +1 -0
- {gptdiff-0.1.15 → gptdiff-0.1.18}/gptdiff.egg-info/requires.txt +1 -1
- {gptdiff-0.1.15 → gptdiff-0.1.18}/setup.py +2 -2
- gptdiff-0.1.18/tests/test_failing_case.py +25 -0
- gptdiff-0.1.15/gptdiff/__init__.py +0 -3
- {gptdiff-0.1.15 → gptdiff-0.1.18}/LICENSE.txt +0 -0
- {gptdiff-0.1.15 → gptdiff-0.1.18}/gptdiff/gptpatch.py +0 -0
- {gptdiff-0.1.15 → gptdiff-0.1.18}/gptdiff.egg-info/dependency_links.txt +0 -0
- {gptdiff-0.1.15 → gptdiff-0.1.18}/gptdiff.egg-info/entry_points.txt +0 -0
- {gptdiff-0.1.15 → gptdiff-0.1.18}/gptdiff.egg-info/top_level.txt +0 -0
- {gptdiff-0.1.15 → gptdiff-0.1.18}/setup.cfg +0 -0
- {gptdiff-0.1.15 → gptdiff-0.1.18}/tests/test_applydiff.py +0 -0
- {gptdiff-0.1.15 → gptdiff-0.1.18}/tests/test_applydiff_edgecases.py +0 -0
- {gptdiff-0.1.15 → gptdiff-0.1.18}/tests/test_diff_parse.py +0 -0
- {gptdiff-0.1.15 → gptdiff-0.1.18}/tests/test_parse_diff_per_file.py +0 -0
- {gptdiff-0.1.15 → gptdiff-0.1.18}/tests/test_smartapply.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: gptdiff
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.18
|
4
4
|
Summary: A tool to generate and apply git diffs using LLMs
|
5
5
|
Author: 255labs
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -10,7 +10,7 @@ Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE.txt
|
11
11
|
Requires-Dist: openai>=1.0.0
|
12
12
|
Requires-Dist: tiktoken>=0.5.0
|
13
|
-
Requires-Dist: ai_agent_toolbox>=0.1.
|
13
|
+
Requires-Dist: ai_agent_toolbox>=0.1.13
|
14
14
|
Provides-Extra: test
|
15
15
|
Requires-Dist: pytest; extra == "test"
|
16
16
|
Requires-Dist: pytest-mock; extra == "test"
|
@@ -175,7 +175,38 @@ OpenAI will not be called unless you specify `--call` or `--apply`
|
|
175
175
|
|
176
176
|
Prevent files being appended to the prompt by adding them to `.gitignore` or `.gptignore`
|
177
177
|
|
178
|
-
|
178
|
+
## Command Line Usage
|
179
|
+
|
180
|
+
## gptpatch: Apply Diffs Directly
|
181
|
+
|
182
|
+
`gptpatch` is a companion command-line tool to [GPTDiff](https://github.com/255BITS/gptdiff) that applies unified diffs directly to your project.
|
183
|
+
|
184
|
+
### Usage
|
185
|
+
|
186
|
+
Apply a diff provided directly:
|
187
|
+
|
188
|
+
```bash
|
189
|
+
gptpatch --diff "<diff text>"
|
190
|
+
```
|
191
|
+
|
192
|
+
Or apply a diff from a file:
|
193
|
+
|
194
|
+
```bash
|
195
|
+
gptpatch path/to/diff.patch
|
196
|
+
```
|
197
|
+
|
198
|
+
### Options
|
199
|
+
|
200
|
+
- **--project-dir**: Specify the target project directory (default: current directory)
|
201
|
+
- **--model**: (Optional) Specify the LLM model for advanced conflict resolution
|
202
|
+
- **--max_tokens**: (Optional) Define the maximum token count for LLM responses during patch application
|
203
|
+
- **--nobeep**: Disable the completion beep notification
|
204
|
+
|
205
|
+
### Workflow
|
206
|
+
|
207
|
+
`gptpatch` first attempts to apply the diff using standard patch logic. If that fails, it automatically falls back to a smart apply mechanism that leverages AI-powered conflict resolution.
|
208
|
+
|
209
|
+
For more details, see the [gptpatch documentation](https://gptdiff.255labs.xyz) on our docs site.
|
179
210
|
|
180
211
|
After installing the package, use the `gptdiff` command in your terminal. Change directory into your codebase and run:
|
181
212
|
|
@@ -148,7 +148,38 @@ OpenAI will not be called unless you specify `--call` or `--apply`
|
|
148
148
|
|
149
149
|
Prevent files being appended to the prompt by adding them to `.gitignore` or `.gptignore`
|
150
150
|
|
151
|
-
|
151
|
+
## Command Line Usage
|
152
|
+
|
153
|
+
## gptpatch: Apply Diffs Directly
|
154
|
+
|
155
|
+
`gptpatch` is a companion command-line tool to [GPTDiff](https://github.com/255BITS/gptdiff) that applies unified diffs directly to your project.
|
156
|
+
|
157
|
+
### Usage
|
158
|
+
|
159
|
+
Apply a diff provided directly:
|
160
|
+
|
161
|
+
```bash
|
162
|
+
gptpatch --diff "<diff text>"
|
163
|
+
```
|
164
|
+
|
165
|
+
Or apply a diff from a file:
|
166
|
+
|
167
|
+
```bash
|
168
|
+
gptpatch path/to/diff.patch
|
169
|
+
```
|
170
|
+
|
171
|
+
### Options
|
172
|
+
|
173
|
+
- **--project-dir**: Specify the target project directory (default: current directory)
|
174
|
+
- **--model**: (Optional) Specify the LLM model for advanced conflict resolution
|
175
|
+
- **--max_tokens**: (Optional) Define the maximum token count for LLM responses during patch application
|
176
|
+
- **--nobeep**: Disable the completion beep notification
|
177
|
+
|
178
|
+
### Workflow
|
179
|
+
|
180
|
+
`gptpatch` first attempts to apply the diff using standard patch logic. If that fails, it automatically falls back to a smart apply mechanism that leverages AI-powered conflict resolution.
|
181
|
+
|
182
|
+
For more details, see the [gptpatch documentation](https://gptdiff.255labs.xyz) on our docs site.
|
152
183
|
|
153
184
|
After installing the package, use the `gptdiff` command in your terminal. Change directory into your codebase and run:
|
154
185
|
|
@@ -21,7 +21,7 @@ import argparse
|
|
21
21
|
import pkgutil
|
22
22
|
import re
|
23
23
|
import contextvars
|
24
|
-
from ai_agent_toolbox import
|
24
|
+
from ai_agent_toolbox import MarkdownParser, MarkdownPromptFormatter, Toolbox, FlatXMLParser, FlatXMLPromptFormatter
|
25
25
|
import threading
|
26
26
|
from pkgutil import get_data
|
27
27
|
|
@@ -165,7 +165,7 @@ def load_project_files(project_dir, cwd):
|
|
165
165
|
Prints skipped files to stdout for visibility
|
166
166
|
"""
|
167
167
|
ignore_paths = [Path(cwd) / ".gitignore", Path(cwd) / ".gptignore"]
|
168
|
-
gitignore_patterns = [".gitignore", "diff.patch", "prompt.txt", ".gptignore", "*.pdf", "*.docx", ".git", "*.orig", "*.rej"]
|
168
|
+
gitignore_patterns = [".gitignore", "diff.patch", "prompt.txt", ".gptignore", "*.pdf", "*.docx", ".git", "*.orig", "*.rej", "*.diff"]
|
169
169
|
|
170
170
|
for p in ignore_paths:
|
171
171
|
if p.exists():
|
@@ -196,8 +196,8 @@ def call_llm_for_diff(system_prompt, user_prompt, files_content, model, temperat
|
|
196
196
|
enc = tiktoken.get_encoding("o200k_base")
|
197
197
|
start_time = time.time()
|
198
198
|
|
199
|
-
parser =
|
200
|
-
formatter =
|
199
|
+
parser = MarkdownParser()
|
200
|
+
formatter = MarkdownPromptFormatter()
|
201
201
|
toolbox = create_diff_toolbox()
|
202
202
|
tool_prompt = formatter.usage_prompt(toolbox)
|
203
203
|
system_prompt += "\n"+tool_prompt
|
@@ -273,11 +273,12 @@ prepended to the system prompt.
|
|
273
273
|
with urllib.request.urlopen(prepend) as response:
|
274
274
|
prepend = response.read().decode('utf-8')
|
275
275
|
else:
|
276
|
-
prepend = load_prepend_file(prepend)
|
276
|
+
prepend = load_prepend_file(prepend)+"\n"
|
277
277
|
else:
|
278
278
|
prepend = ""
|
279
279
|
|
280
|
-
|
280
|
+
diff_tag = "```diff"
|
281
|
+
system_prompt = prepend + f"Output a git diff into a \"{diff_tag}\" block."
|
281
282
|
_, diff_text, _, _, _, _ = call_llm_for_diff(
|
282
283
|
system_prompt,
|
283
284
|
goal,
|
@@ -696,14 +697,14 @@ def call_llm_for_apply(file_path, original_content, file_diff, model, api_key=No
|
|
696
697
|
4. You must return the entire file. It overwrites the existing file."""
|
697
698
|
user_prompt = f"""File: {file_path}
|
698
699
|
File contents:
|
699
|
-
|
700
|
+
```
|
700
701
|
{original_content}
|
701
|
-
|
702
|
+
```
|
702
703
|
|
703
704
|
Diff to apply:
|
704
|
-
|
705
|
+
```diff
|
705
706
|
{file_diff}
|
706
|
-
|
707
|
+
```"""
|
707
708
|
if extra_prompt:
|
708
709
|
user_prompt += f"\n\n{extra_prompt}"
|
709
710
|
if model == "gemini-2.0-flash-thinking-exp-01-21":
|
@@ -810,6 +811,32 @@ def smart_apply_patch(project_dir, diff_text, user_prompt, args):
|
|
810
811
|
if args.beep:
|
811
812
|
print("\a")
|
812
813
|
|
814
|
+
def save_files(files_dict, target_directory):
|
815
|
+
"""
|
816
|
+
Save files from a dictionary mapping relative file paths to file contents
|
817
|
+
into the specified target directory.
|
818
|
+
|
819
|
+
Args:
|
820
|
+
files_dict (dict): A dictionary where keys are file paths (relative)
|
821
|
+
and values are the corresponding file contents.
|
822
|
+
target_directory (str or Path): The directory where files will be saved.
|
823
|
+
"""
|
824
|
+
target_directory = Path(target_directory)
|
825
|
+
# Create the target directory if it doesn't exist.
|
826
|
+
target_directory.mkdir(parents=True, exist_ok=True)
|
827
|
+
|
828
|
+
for file_path, content in files_dict.items():
|
829
|
+
# Create the full path for the file.
|
830
|
+
full_path = target_directory / file_path
|
831
|
+
|
832
|
+
# Ensure parent directories exist.
|
833
|
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
834
|
+
|
835
|
+
# Write the file content.
|
836
|
+
with full_path.open('w', encoding='utf-8') as f:
|
837
|
+
f.write(content)
|
838
|
+
print(f"Saved: {full_path}")
|
839
|
+
|
813
840
|
def main():
|
814
841
|
# Adding color support for Windows CMD
|
815
842
|
if os.name == 'nt':
|
@@ -840,7 +867,7 @@ def main():
|
|
840
867
|
project_files.extend(load_project_files(additional_path, project_dir))
|
841
868
|
|
842
869
|
if args.prepend:
|
843
|
-
prepend = args.prepend
|
870
|
+
prepend = args.prepend+"\n"
|
844
871
|
else:
|
845
872
|
prepend = ""
|
846
873
|
|
@@ -858,8 +885,10 @@ def main():
|
|
858
885
|
# If the specified prepend path does not exist, treat the value as literal content.
|
859
886
|
prepend = prepend
|
860
887
|
|
861
|
-
|
862
|
-
|
888
|
+
if prepend != "":
|
889
|
+
prepend += "\n"
|
890
|
+
|
891
|
+
system_prompt = prepend + f"Output a git diff into a ```diff block"
|
863
892
|
|
864
893
|
files_content = ""
|
865
894
|
for file, content in project_files:
|
@@ -874,9 +903,8 @@ def main():
|
|
874
903
|
args.model = os.getenv('GPTDIFF_MODEL', 'deepseek-reasoner')
|
875
904
|
|
876
905
|
if not args.call and not args.apply:
|
877
|
-
append = "\nInstead of using <diff> tags, use ```diff backticks."
|
878
906
|
with open('prompt.txt', 'w') as f:
|
879
|
-
f.write(full_prompt
|
907
|
+
f.write(full_prompt)
|
880
908
|
print(f"Total tokens: {token_count:5d}")
|
881
909
|
print(f"\033[1;32mNot calling GPT-4.\033[0m") # Green color for success message
|
882
910
|
print('Instead, wrote full prompt to prompt.txt. Use `xclip -selection clipboard < prompt.txt` then paste into chatgpt')
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: gptdiff
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.18
|
4
4
|
Summary: A tool to generate and apply git diffs using LLMs
|
5
5
|
Author: 255labs
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -10,7 +10,7 @@ Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE.txt
|
11
11
|
Requires-Dist: openai>=1.0.0
|
12
12
|
Requires-Dist: tiktoken>=0.5.0
|
13
|
-
Requires-Dist: ai_agent_toolbox>=0.1.
|
13
|
+
Requires-Dist: ai_agent_toolbox>=0.1.13
|
14
14
|
Provides-Extra: test
|
15
15
|
Requires-Dist: pytest; extra == "test"
|
16
16
|
Requires-Dist: pytest-mock; extra == "test"
|
@@ -175,7 +175,38 @@ OpenAI will not be called unless you specify `--call` or `--apply`
|
|
175
175
|
|
176
176
|
Prevent files being appended to the prompt by adding them to `.gitignore` or `.gptignore`
|
177
177
|
|
178
|
-
|
178
|
+
## Command Line Usage
|
179
|
+
|
180
|
+
## gptpatch: Apply Diffs Directly
|
181
|
+
|
182
|
+
`gptpatch` is a companion command-line tool to [GPTDiff](https://github.com/255BITS/gptdiff) that applies unified diffs directly to your project.
|
183
|
+
|
184
|
+
### Usage
|
185
|
+
|
186
|
+
Apply a diff provided directly:
|
187
|
+
|
188
|
+
```bash
|
189
|
+
gptpatch --diff "<diff text>"
|
190
|
+
```
|
191
|
+
|
192
|
+
Or apply a diff from a file:
|
193
|
+
|
194
|
+
```bash
|
195
|
+
gptpatch path/to/diff.patch
|
196
|
+
```
|
197
|
+
|
198
|
+
### Options
|
199
|
+
|
200
|
+
- **--project-dir**: Specify the target project directory (default: current directory)
|
201
|
+
- **--model**: (Optional) Specify the LLM model for advanced conflict resolution
|
202
|
+
- **--max_tokens**: (Optional) Define the maximum token count for LLM responses during patch application
|
203
|
+
- **--nobeep**: Disable the completion beep notification
|
204
|
+
|
205
|
+
### Workflow
|
206
|
+
|
207
|
+
`gptpatch` first attempts to apply the diff using standard patch logic. If that fails, it automatically falls back to a smart apply mechanism that leverages AI-powered conflict resolution.
|
208
|
+
|
209
|
+
For more details, see the [gptpatch documentation](https://gptdiff.255labs.xyz) on our docs site.
|
179
210
|
|
180
211
|
After installing the package, use the `gptdiff` command in your terminal. Change directory into your codebase and run:
|
181
212
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
2
2
|
|
3
3
|
setup(
|
4
4
|
name='gptdiff',
|
5
|
-
version='0.1.
|
5
|
+
version='0.1.18',
|
6
6
|
description='A tool to generate and apply git diffs using LLMs',
|
7
7
|
author='255labs',
|
8
8
|
packages=find_packages(), # Use find_packages() to automatically discover packages
|
@@ -12,7 +12,7 @@ setup(
|
|
12
12
|
install_requires=[
|
13
13
|
'openai>=1.0.0',
|
14
14
|
'tiktoken>=0.5.0',
|
15
|
-
'ai_agent_toolbox>=0.1.
|
15
|
+
'ai_agent_toolbox>=0.1.13'
|
16
16
|
],
|
17
17
|
extras_require={
|
18
18
|
'test': ['pytest', 'pytest-mock'],
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import unittest
|
2
|
+
from gptdiff.gptdiff import smartapply
|
3
|
+
|
4
|
+
class TestSmartApplyEdgeCase(unittest.TestCase):
|
5
|
+
def test_smartapply_think_tag_stripping(self):
|
6
|
+
"""
|
7
|
+
This test verifies that if an LLM response includes extraneous wrapping (e.g.
|
8
|
+
), these tags are stripped from the final applied diff.
|
9
|
+
"""
|
10
|
+
diff_text = '''diff --git a/hello.py b/hello.py
|
11
|
+
--- a/hello.py
|
12
|
+
++++ b/hello.py
|
13
|
+
@@ -1,2 +1,5 @@
|
14
|
+
def hello():
|
15
|
+
print('Hello')
|
16
|
+
++
|
17
|
+
++def goodbye():
|
18
|
+
++ print('Goodbye')'''
|
19
|
+
original_files = {"hello.py": "def hello():\n print('Hello')\n"}
|
20
|
+
|
21
|
+
from unittest.mock import patch
|
22
|
+
with patch('gptdiff.gptdiff.call_llm_for_apply', return_value="\ndef goodbye():\n print('Goodbye')"):
|
23
|
+
updated_files = smartapply(diff_text, original_files)
|
24
|
+
|
25
|
+
self.assertIn("def goodbye():", updated_files.get("hello.py", ""))
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|