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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: gptdiff
3
- Version: 0.1.15
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.11
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
- ### Command Line Usage
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
- ### Command Line Usage
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
 
@@ -0,0 +1,3 @@
1
+ from .gptdiff import generate_diff, smartapply, load_project_files, build_environment, save_files
2
+
3
+ __all__ = ['generate_diff', 'smartapply', 'load_project_files', 'build_environment', 'save_files']
@@ -21,7 +21,7 @@ import argparse
21
21
  import pkgutil
22
22
  import re
23
23
  import contextvars
24
- from ai_agent_toolbox import FlatXMLParser, FlatXMLPromptFormatter, Toolbox
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 = FlatXMLParser("diff")
200
- formatter = FlatXMLPromptFormatter(tag="diff")
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
- system_prompt = prepend+f"Output a git diff into a <diff> block."
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
- <filecontents>
700
+ ```
700
701
  {original_content}
701
- </filecontents>
702
+ ```
702
703
 
703
704
  Diff to apply:
704
- <diff>
705
+ ```diff
705
706
  {file_diff}
706
- </diff>"""
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
- # Prepare system prompt
862
- system_prompt = prepend + f"Output a git diff into a <diff> block."
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+append)
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.15
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.11
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
- ### Command Line Usage
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
 
@@ -13,5 +13,6 @@ gptdiff.egg-info/top_level.txt
13
13
  tests/test_applydiff.py
14
14
  tests/test_applydiff_edgecases.py
15
15
  tests/test_diff_parse.py
16
+ tests/test_failing_case.py
16
17
  tests/test_parse_diff_per_file.py
17
18
  tests/test_smartapply.py
@@ -1,6 +1,6 @@
1
1
  openai>=1.0.0
2
2
  tiktoken>=0.5.0
3
- ai_agent_toolbox>=0.1.11
3
+ ai_agent_toolbox>=0.1.13
4
4
 
5
5
  [docs]
6
6
  mkdocs
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='gptdiff',
5
- version='0.1.15',
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.11'
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", ""))
@@ -1,3 +0,0 @@
1
- from .gptdiff import generate_diff, smartapply
2
-
3
- __all__ = ['generate_diff', 'smartapply']
File without changes
File without changes
File without changes