auto-code-fixer 0.3.6__tar.gz → 0.4.0__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.
Files changed (35) hide show
  1. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/PKG-INFO +58 -6
  2. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/README.md +57 -5
  3. auto_code_fixer-0.4.0/auto_code_fixer/__init__.py +1 -0
  4. auto_code_fixer-0.4.0/auto_code_fixer/approval.py +114 -0
  5. auto_code_fixer-0.4.0/auto_code_fixer/cli.py +1254 -0
  6. auto_code_fixer-0.4.0/auto_code_fixer/reporting.py +61 -0
  7. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/sandbox.py +35 -0
  8. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/utils.py +42 -0
  9. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer.egg-info/PKG-INFO +58 -6
  10. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer.egg-info/SOURCES.txt +5 -1
  11. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/pyproject.toml +1 -1
  12. auto_code_fixer-0.4.0/tests/test_approval_and_guards.py +55 -0
  13. auto_code_fixer-0.4.0/tests/test_reporting.py +23 -0
  14. auto_code_fixer-0.3.6/auto_code_fixer/__init__.py +0 -1
  15. auto_code_fixer-0.3.6/auto_code_fixer/cli.py +0 -554
  16. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/LICENSE +0 -0
  17. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/command_runner.py +0 -0
  18. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/fixer.py +0 -0
  19. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/installer.py +0 -0
  20. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/models.py +0 -0
  21. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/patch_protocol.py +0 -0
  22. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/patcher.py +0 -0
  23. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/plan.py +0 -0
  24. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/runner.py +0 -0
  25. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/traceback_utils.py +0 -0
  26. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/venv_manager.py +0 -0
  27. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer.egg-info/dependency_links.txt +0 -0
  28. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer.egg-info/entry_points.txt +0 -0
  29. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer.egg-info/requires.txt +0 -0
  30. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer.egg-info/top_level.txt +0 -0
  31. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/setup.cfg +0 -0
  32. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/tests/test_atomic_write.py +0 -0
  33. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/tests/test_fix_imported_file.py +0 -0
  34. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/tests/test_internal_imports.py +0 -0
  35. {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/tests/test_patch_protocol.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: auto-code-fixer
3
- Version: 0.3.6
3
+ Version: 0.4.0
4
4
  Summary: Automatically fix Python code using ChatGPT
5
5
  Author-email: Arif Shah <ashah7775@gmail.com>
6
6
  License: MIT
@@ -87,6 +87,18 @@ pip install requests
87
87
  Before overwriting any file, it creates a backup:
88
88
  - `file.py.bak` (or `.bak1`, `.bak2`, ...)
89
89
 
90
+ ### Approval mode (diff review)
91
+ ```bash
92
+ auto-code-fixer path/to/main.py --project-root . --approve
93
+ ```
94
+ In patch-protocol mode, approvals are **file-by-file** (apply/skip).
95
+
96
+ ### Diff / size guards
97
+ To prevent huge edits from being applied accidentally:
98
+ - `--max-diff-lines` limits unified-diff size per file
99
+ - `--max-file-bytes` limits the proposed new content size per file
100
+ - `--max-total-bytes` limits total proposed new content across all files
101
+
90
102
  ### Dry run
91
103
  ```bash
92
104
  auto-code-fixer path/to/main.py --project-root . --dry-run
@@ -96,6 +108,15 @@ auto-code-fixer path/to/main.py --project-root . --dry-run
96
108
 
97
109
  ## Advanced options
98
110
 
111
+ ### Fixpack mode (folder/package runner)
112
+ Fixpack runs a command (default: `pytest -q`) against a **full-project sandbox**, then iteratively fixes the failing file(s) until green or you hit `--max-retries`.
113
+
114
+ ```bash
115
+ auto-code-fixer path/to/package --project-root . --fixpack --no-ask
116
+ # equivalent explicit command:
117
+ auto-code-fixer path/to/package --project-root . --fixpack --run "pytest -q" --no-ask
118
+ ```
119
+
99
120
  ### Run a custom command (pytest, etc.)
100
121
  Instead of `python main.py`, run tests:
101
122
 
@@ -103,6 +124,16 @@ Instead of `python main.py`, run tests:
103
124
  auto-code-fixer . --project-root . --run "pytest -q" --no-ask
104
125
  ```
105
126
 
127
+ When you use `--run`, the tool (by default) also performs a **post-apply check**:
128
+ after copying fixes back to your project, it re-runs the same command against the real project files
129
+ (using the sandbox venv for dependencies).
130
+
131
+ You can disable that extra check with:
132
+
133
+ ```bash
134
+ auto-code-fixer . --project-root . --run "pytest -q" --no-post-apply-check
135
+ ```
136
+
106
137
  ### Model selection
107
138
  ```bash
108
139
  export AUTO_CODE_FIXER_MODEL=gpt-4.1-mini
@@ -121,13 +152,25 @@ auto-code-fixer main.py --ai-plan
121
152
  ```
122
153
  This enables a helper that can suggest which local file to edit. It is best-effort.
123
154
 
124
- ### Optional structured patch protocol (JSON + sha256)
155
+ ### Structured patch protocol (JSON + sha256) (default)
156
+ By default, Auto Code Fixer uses a structured **patch protocol** where the model returns strict JSON:
157
+
158
+ `{ "files": [ {"path": "...", "new_content": "...", "sha256": "..."}, ... ] }`
159
+
160
+ The tool verifies the SHA-256 hash of `new_content` before applying edits.
161
+
162
+ To disable this and use legacy full-text mode only:
163
+
125
164
  ```bash
126
- auto-code-fixer main.py --patch-protocol
165
+ auto-code-fixer main.py --legacy-mode
166
+ ```
167
+
168
+ ### Ruff-first mode (best-effort)
169
+ Before calling the LLM, you can try Ruff auto-fixes (including import sorting) and Ruff formatting:
170
+
171
+ ```bash
172
+ auto-code-fixer path/to/package --project-root . --fixpack --ruff-first
127
173
  ```
128
- When enabled, the model is asked to return strict JSON with `{files:[{path,new_content,sha256}]}`.
129
- The tool verifies the SHA-256 hash of `new_content` before applying edits, and falls back to the
130
- legacy full-text mode if parsing/validation fails.
131
174
 
132
175
  ### Optional formatting / linting (best-effort)
133
176
  ```bash
@@ -136,6 +179,15 @@ auto-code-fixer main.py --lint ruff --fix
136
179
  ```
137
180
  These run inside the sandbox venv and are skipped if the tools are not installed.
138
181
 
182
+ ### JSON report (explain)
183
+ Write a machine-readable report with attempts, files touched, and unified diffs:
184
+
185
+ ```bash
186
+ auto-code-fixer main.py --explain
187
+ # or
188
+ auto-code-fixer main.py --report /tmp/report.json
189
+ ```
190
+
139
191
  ---
140
192
 
141
193
  ## Environment variables
@@ -66,6 +66,18 @@ pip install requests
66
66
  Before overwriting any file, it creates a backup:
67
67
  - `file.py.bak` (or `.bak1`, `.bak2`, ...)
68
68
 
69
+ ### Approval mode (diff review)
70
+ ```bash
71
+ auto-code-fixer path/to/main.py --project-root . --approve
72
+ ```
73
+ In patch-protocol mode, approvals are **file-by-file** (apply/skip).
74
+
75
+ ### Diff / size guards
76
+ To prevent huge edits from being applied accidentally:
77
+ - `--max-diff-lines` limits unified-diff size per file
78
+ - `--max-file-bytes` limits the proposed new content size per file
79
+ - `--max-total-bytes` limits total proposed new content across all files
80
+
69
81
  ### Dry run
70
82
  ```bash
71
83
  auto-code-fixer path/to/main.py --project-root . --dry-run
@@ -75,6 +87,15 @@ auto-code-fixer path/to/main.py --project-root . --dry-run
75
87
 
76
88
  ## Advanced options
77
89
 
90
+ ### Fixpack mode (folder/package runner)
91
+ Fixpack runs a command (default: `pytest -q`) against a **full-project sandbox**, then iteratively fixes the failing file(s) until green or you hit `--max-retries`.
92
+
93
+ ```bash
94
+ auto-code-fixer path/to/package --project-root . --fixpack --no-ask
95
+ # equivalent explicit command:
96
+ auto-code-fixer path/to/package --project-root . --fixpack --run "pytest -q" --no-ask
97
+ ```
98
+
78
99
  ### Run a custom command (pytest, etc.)
79
100
  Instead of `python main.py`, run tests:
80
101
 
@@ -82,6 +103,16 @@ Instead of `python main.py`, run tests:
82
103
  auto-code-fixer . --project-root . --run "pytest -q" --no-ask
83
104
  ```
84
105
 
106
+ When you use `--run`, the tool (by default) also performs a **post-apply check**:
107
+ after copying fixes back to your project, it re-runs the same command against the real project files
108
+ (using the sandbox venv for dependencies).
109
+
110
+ You can disable that extra check with:
111
+
112
+ ```bash
113
+ auto-code-fixer . --project-root . --run "pytest -q" --no-post-apply-check
114
+ ```
115
+
85
116
  ### Model selection
86
117
  ```bash
87
118
  export AUTO_CODE_FIXER_MODEL=gpt-4.1-mini
@@ -100,13 +131,25 @@ auto-code-fixer main.py --ai-plan
100
131
  ```
101
132
  This enables a helper that can suggest which local file to edit. It is best-effort.
102
133
 
103
- ### Optional structured patch protocol (JSON + sha256)
134
+ ### Structured patch protocol (JSON + sha256) (default)
135
+ By default, Auto Code Fixer uses a structured **patch protocol** where the model returns strict JSON:
136
+
137
+ `{ "files": [ {"path": "...", "new_content": "...", "sha256": "..."}, ... ] }`
138
+
139
+ The tool verifies the SHA-256 hash of `new_content` before applying edits.
140
+
141
+ To disable this and use legacy full-text mode only:
142
+
104
143
  ```bash
105
- auto-code-fixer main.py --patch-protocol
144
+ auto-code-fixer main.py --legacy-mode
145
+ ```
146
+
147
+ ### Ruff-first mode (best-effort)
148
+ Before calling the LLM, you can try Ruff auto-fixes (including import sorting) and Ruff formatting:
149
+
150
+ ```bash
151
+ auto-code-fixer path/to/package --project-root . --fixpack --ruff-first
106
152
  ```
107
- When enabled, the model is asked to return strict JSON with `{files:[{path,new_content,sha256}]}`.
108
- The tool verifies the SHA-256 hash of `new_content` before applying edits, and falls back to the
109
- legacy full-text mode if parsing/validation fails.
110
153
 
111
154
  ### Optional formatting / linting (best-effort)
112
155
  ```bash
@@ -115,6 +158,15 @@ auto-code-fixer main.py --lint ruff --fix
115
158
  ```
116
159
  These run inside the sandbox venv and are skipped if the tools are not installed.
117
160
 
161
+ ### JSON report (explain)
162
+ Write a machine-readable report with attempts, files touched, and unified diffs:
163
+
164
+ ```bash
165
+ auto-code-fixer main.py --explain
166
+ # or
167
+ auto-code-fixer main.py --report /tmp/report.json
168
+ ```
169
+
118
170
  ---
119
171
 
120
172
  ## Environment variables
@@ -0,0 +1 @@
1
+ __version__ = "0.4.0"
@@ -0,0 +1,114 @@
1
+ import difflib
2
+ from dataclasses import dataclass
3
+
4
+
5
+ @dataclass(frozen=True)
6
+ class PlannedChange:
7
+ abs_path: str
8
+ rel_path: str
9
+ old_content: str
10
+ new_content: str
11
+
12
+
13
+ class UserAbort(Exception):
14
+ pass
15
+
16
+
17
+ def unified_diff_text(rel_path: str, old: str, new: str) -> str:
18
+ return "".join(
19
+ difflib.unified_diff(
20
+ (old or "").splitlines(keepends=True),
21
+ (new or "").splitlines(keepends=True),
22
+ fromfile=rel_path + " (before)",
23
+ tofile=rel_path + " (after)",
24
+ )
25
+ )
26
+
27
+
28
+ def guard_planned_changes(
29
+ planned: list[PlannedChange],
30
+ *,
31
+ max_file_bytes: int,
32
+ max_total_bytes: int,
33
+ max_diff_lines: int,
34
+ ) -> None:
35
+ """Safety guards against huge edits.
36
+
37
+ Raises ValueError when limits are exceeded.
38
+ """
39
+
40
+ if max_file_bytes <= 0 or max_total_bytes <= 0 or max_diff_lines <= 0:
41
+ raise ValueError("Guard limits must be positive")
42
+
43
+ total = 0
44
+
45
+ for ch in planned:
46
+ b = len((ch.new_content or "").encode("utf-8"))
47
+ total += b
48
+ if b > max_file_bytes:
49
+ raise ValueError(
50
+ f"Proposed content for {ch.rel_path} is {b} bytes, exceeding --max-file-bytes={max_file_bytes}"
51
+ )
52
+
53
+ diff = unified_diff_text(ch.rel_path, ch.old_content, ch.new_content)
54
+ diff_lines = len(diff.splitlines())
55
+ if diff_lines > max_diff_lines:
56
+ raise ValueError(
57
+ f"Diff for {ch.rel_path} is {diff_lines} lines, exceeding --max-diff-lines={max_diff_lines}"
58
+ )
59
+
60
+ if total > max_total_bytes:
61
+ raise ValueError(
62
+ f"Total proposed content is {total} bytes, exceeding --max-total-bytes={max_total_bytes}"
63
+ )
64
+
65
+
66
+ def prompt_approve_file_by_file(
67
+ planned: list[PlannedChange],
68
+ *,
69
+ input_fn=input,
70
+ print_fn=print,
71
+ ) -> list[PlannedChange]:
72
+ """Interactive approval per file.
73
+
74
+ Commands:
75
+ y = apply this change
76
+ n = skip this change
77
+ a = apply this and all remaining
78
+ q = abort
79
+ """
80
+
81
+ if not planned:
82
+ return []
83
+
84
+ out: list[PlannedChange] = []
85
+ apply_all = False
86
+
87
+ for ch in planned:
88
+ diff = unified_diff_text(ch.rel_path, ch.old_content, ch.new_content)
89
+ print_fn("\nPROPOSED CHANGE:")
90
+ print_fn(diff)
91
+
92
+ if apply_all:
93
+ out.append(ch)
94
+ continue
95
+
96
+ ans = (
97
+ input_fn(f"Apply change for {ch.rel_path}? (y/n/a/q): ")
98
+ .strip()
99
+ .lower()
100
+ )
101
+ if ans == "y":
102
+ out.append(ch)
103
+ elif ans == "n":
104
+ continue
105
+ elif ans == "a":
106
+ out.append(ch)
107
+ apply_all = True
108
+ elif ans == "q":
109
+ raise UserAbort("User aborted approvals")
110
+ else:
111
+ # default: be safe
112
+ continue
113
+
114
+ return out