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.
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/PKG-INFO +58 -6
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/README.md +57 -5
- auto_code_fixer-0.4.0/auto_code_fixer/__init__.py +1 -0
- auto_code_fixer-0.4.0/auto_code_fixer/approval.py +114 -0
- auto_code_fixer-0.4.0/auto_code_fixer/cli.py +1254 -0
- auto_code_fixer-0.4.0/auto_code_fixer/reporting.py +61 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/sandbox.py +35 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/utils.py +42 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer.egg-info/PKG-INFO +58 -6
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer.egg-info/SOURCES.txt +5 -1
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/pyproject.toml +1 -1
- auto_code_fixer-0.4.0/tests/test_approval_and_guards.py +55 -0
- auto_code_fixer-0.4.0/tests/test_reporting.py +23 -0
- auto_code_fixer-0.3.6/auto_code_fixer/__init__.py +0 -1
- auto_code_fixer-0.3.6/auto_code_fixer/cli.py +0 -554
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/LICENSE +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/command_runner.py +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/fixer.py +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/installer.py +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/models.py +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/patch_protocol.py +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/patcher.py +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/plan.py +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/runner.py +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/traceback_utils.py +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer/venv_manager.py +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer.egg-info/dependency_links.txt +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer.egg-info/entry_points.txt +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer.egg-info/requires.txt +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/auto_code_fixer.egg-info/top_level.txt +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/setup.cfg +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/tests/test_atomic_write.py +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/tests/test_fix_imported_file.py +0 -0
- {auto_code_fixer-0.3.6 → auto_code_fixer-0.4.0}/tests/test_internal_imports.py +0 -0
- {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
|
+
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
|
-
###
|
|
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 --
|
|
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
|
-
###
|
|
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 --
|
|
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
|