auto-code-fixer 0.3.6__py3-none-any.whl → 0.4.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.
@@ -0,0 +1,61 @@
1
+ import dataclasses
2
+ import datetime as _dt
3
+ import difflib
4
+ import json
5
+ import os
6
+ from typing import Any
7
+
8
+
9
+ def utc_now_iso() -> str:
10
+ return _dt.datetime.now(tz=_dt.timezone.utc).isoformat()
11
+
12
+
13
+ def unified_diff(*, old: str, new: str, fromfile: str, tofile: str) -> str:
14
+ return "".join(
15
+ difflib.unified_diff(
16
+ (old or "").splitlines(keepends=True),
17
+ (new or "").splitlines(keepends=True),
18
+ fromfile=fromfile,
19
+ tofile=tofile,
20
+ )
21
+ )
22
+
23
+
24
+ @dataclasses.dataclass
25
+ class ReportAttempt:
26
+ index: int
27
+ run_cmd: str
28
+ return_code: int = 0
29
+ stdout: str | None = None
30
+ stderr: str | None = None
31
+ selected_file: str | None = None
32
+ ruff_first_applied: bool = False
33
+ changed_files: list[str] = dataclasses.field(default_factory=list)
34
+
35
+
36
+ @dataclasses.dataclass
37
+ class FixReport:
38
+ tool: str = "auto-code-fixer"
39
+ version: str | None = None
40
+ mode: str = "file" # file|fixpack
41
+ project_root: str | None = None
42
+ sandbox_root: str | None = None
43
+ target: str | None = None
44
+ run_cmd: str | None = None
45
+ started_at: str = dataclasses.field(default_factory=utc_now_iso)
46
+ finished_at: str | None = None
47
+ ok: bool | None = None
48
+ attempts: list[ReportAttempt] = dataclasses.field(default_factory=list)
49
+ files_touched: list[str] = dataclasses.field(default_factory=list)
50
+ diffs: dict[str, str] = dataclasses.field(default_factory=dict) # relpath -> unified diff
51
+
52
+ def to_dict(self) -> dict[str, Any]:
53
+ d = dataclasses.asdict(self)
54
+ # dataclasses.asdict converts nested dataclasses, OK
55
+ return d
56
+
57
+ def write_json(self, path: str) -> None:
58
+ os.makedirs(os.path.dirname(os.path.abspath(path)) or ".", exist_ok=True)
59
+ with open(path, "w", encoding="utf-8") as f:
60
+ json.dump(self.to_dict(), f, indent=2, sort_keys=False)
61
+ f.write("\n")
@@ -35,6 +35,41 @@ def make_sandbox(*, entry_file: str, project_root: str) -> tuple[str, str]:
35
35
  return sandbox_root, sandbox_entry
36
36
 
37
37
 
38
+ def make_sandbox_project(*, project_root: str) -> str:
39
+ """Create a sandbox directory with a full copy of project_root.
40
+
41
+ This is used for "Fixpack" runs where we execute a command (e.g., pytest) across
42
+ a package/folder and iteratively fix failures.
43
+ """
44
+
45
+ project_root = os.path.abspath(project_root)
46
+ sandbox_root = tempfile.mkdtemp(prefix="codefix_sandbox_")
47
+
48
+ def _ignore(_dir: str, names: list[str]):
49
+ skip = {".git",
50
+ ".venv",
51
+ "__pycache__",
52
+ "build",
53
+ "dist",
54
+ ".mypy_cache",
55
+ ".ruff_cache",
56
+ ".pytest_cache",
57
+ }
58
+ return {n for n in names if n in skip}
59
+
60
+ # Copy everything into sandbox_root.
61
+ # Note: copytree requires dest not to exist; copy contents manually.
62
+ for item in os.listdir(project_root):
63
+ src = os.path.join(project_root, item)
64
+ dst = os.path.join(sandbox_root, item)
65
+ if os.path.isdir(src):
66
+ shutil.copytree(src, dst, ignore=_ignore)
67
+ else:
68
+ shutil.copy2(src, dst)
69
+
70
+ return sandbox_root
71
+
72
+
38
73
  def apply_sandbox_back(*, sandbox_root: str, project_root: str, changed_paths: list[str]) -> None:
39
74
  """Copy changed sandbox files back into project_root."""
40
75
 
auto_code_fixer/utils.py CHANGED
@@ -104,3 +104,45 @@ def discover_all_files(entry_file: str) -> list[str]:
104
104
 
105
105
  def is_in_project(file_path: str, project_root: str) -> bool:
106
106
  return os.path.abspath(file_path).startswith(os.path.abspath(project_root))
107
+
108
+
109
+ def sha256_text(text: str) -> str:
110
+ import hashlib
111
+
112
+ return hashlib.sha256((text or "").encode("utf-8")).hexdigest()
113
+
114
+
115
+ def snapshot_py_hashes(root: str) -> dict[str, str]:
116
+ """Return mapping of absolute .py path -> sha256 of contents."""
117
+
118
+ import hashlib
119
+
120
+ out: dict[str, str] = {}
121
+ for dirpath, dirnames, filenames in os.walk(root):
122
+ # Keep the walk light.
123
+ dirnames[:] = [
124
+ d
125
+ for d in dirnames
126
+ if d not in {".git", ".venv", "__pycache__", "build", "dist", ".mypy_cache", ".ruff_cache"}
127
+ ]
128
+ for fn in filenames:
129
+ if not fn.endswith(".py"):
130
+ continue
131
+ ap = os.path.join(dirpath, fn)
132
+ try:
133
+ data = Path(ap).read_bytes()
134
+ except Exception:
135
+ continue
136
+ out[os.path.abspath(ap)] = hashlib.sha256(data).hexdigest()
137
+ return out
138
+
139
+
140
+ def changed_py_files(before: dict[str, str], after: dict[str, str]) -> set[str]:
141
+ """Return absolute paths for .py files whose hash changed or were added/removed."""
142
+
143
+ changed: set[str] = set()
144
+ all_paths = set(before) | set(after)
145
+ for p in all_paths:
146
+ if before.get(p) != after.get(p):
147
+ changed.add(os.path.abspath(p))
148
+ return changed
@@ -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
@@ -1,5 +1,6 @@
1
- auto_code_fixer/__init__.py,sha256=W_9dCm49nLvZulVAvvsafxLJjVBSKDBHz9K7szFZllo,22
2
- auto_code_fixer/cli.py,sha256=lFIGgrMQaqAoNtGRlkKXE1Lzk4l74XwsYjdxnrlF6lI,20304
1
+ auto_code_fixer/__init__.py,sha256=42STGor_9nKYXumfeV5tiyD_M8VdcddX7CEexmibPBk,22
2
+ auto_code_fixer/approval.py,sha256=NPSLu54maAK2RAJF_t4fq7Bs_M8K026DhDwqBFJtiwQ,2832
3
+ auto_code_fixer/cli.py,sha256=Dn9A2geL58lvVWLt6K0whZfZVcBN3zpWEM5OS38Q4Ik,47713
3
4
  auto_code_fixer/command_runner.py,sha256=6P8hGRavN5C39x-e03p02Vc805NnZH9U7e48ngb5jJI,1104
4
5
  auto_code_fixer/fixer.py,sha256=zcgw56pRTuOLvna09lTXatD0VWwjjzBVk0OyEKfgxDM,4691
5
6
  auto_code_fixer/installer.py,sha256=LC0jasSsPI7eHMeDxa622OoMCR1951HAXUZWp-kcmVY,1522
@@ -7,14 +8,15 @@ auto_code_fixer/models.py,sha256=JLBJutOoiOjjlT_RMPUPhWlmm1yc_nGcQqv5tY72Al0,317
7
8
  auto_code_fixer/patch_protocol.py,sha256=8l1E9o-3jkO4VAI7Ulrf-1MbAshNzjQXtUkmH-0hYio,3216
8
9
  auto_code_fixer/patcher.py,sha256=BcQTnjWazdpuEXyR2AlumFBzIk_yIrO3fGTaIqpHuiU,1811
9
10
  auto_code_fixer/plan.py,sha256=jrZdG-f1RDxVB0tBLlTwKbCSEiOYI_RMetdzfBcyE4s,1762
11
+ auto_code_fixer/reporting.py,sha256=bulUsP0DIfLBmtVjZ1YdTV7RAk8Aiy81ASavcr4iano,1887
10
12
  auto_code_fixer/runner.py,sha256=BvQm3CrwkQEDOw0tpiamSTcdu3OjbOgA801xW2zWdP8,970
11
- auto_code_fixer/sandbox.py,sha256=FWQcCxNDI4i7ckTKHuARSSIHCopBRqG16MVtx9s75R8,1628
13
+ auto_code_fixer/sandbox.py,sha256=s36i7mzZFjuAWRUEumxtYfVwasFa8TehobzH-rE_kKA,2721
12
14
  auto_code_fixer/traceback_utils.py,sha256=sbSuLO-2UBk5QPJZYJunTK9WGOpEY8mxR6WRKbtCIoM,935
13
- auto_code_fixer/utils.py,sha256=YXCv3PcDo5NBM1odksBTWkHTEELRtEXfPDIORA5iYaM,3090
15
+ auto_code_fixer/utils.py,sha256=JLagGVnUj67zZdpkKSHhVcqwob30f62SyS7IbH20sHs,4369
14
16
  auto_code_fixer/venv_manager.py,sha256=2ww8reYgLbLohh-moAD5YKM09qv_mC5yYzJRwm3XiXc,1202
15
- auto_code_fixer-0.3.6.dist-info/licenses/LICENSE,sha256=hgchJNa26tjXuLztwSUDbYQxNLnAPnLk6kDXNIkC8xc,1066
16
- auto_code_fixer-0.3.6.dist-info/METADATA,sha256=R6geFjh2cpLvAzuR1KQRcAJyryQgClYYB1wWmD1o2Qk,3870
17
- auto_code_fixer-0.3.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
18
- auto_code_fixer-0.3.6.dist-info/entry_points.txt,sha256=a-j2rkfwkrhXZ5Qbz_6_gwk6Bj7nijYR1DALjWp5Myk,61
19
- auto_code_fixer-0.3.6.dist-info/top_level.txt,sha256=qUk1qznb6Qxqmxy2A3z_5dpOZlmNKHwUiLuJwH-CrAk,16
20
- auto_code_fixer-0.3.6.dist-info/RECORD,,
17
+ auto_code_fixer-0.4.0.dist-info/licenses/LICENSE,sha256=hgchJNa26tjXuLztwSUDbYQxNLnAPnLk6kDXNIkC8xc,1066
18
+ auto_code_fixer-0.4.0.dist-info/METADATA,sha256=qyjUMCYrF1_wRp_4LQSa6t1btgwTNslKTl9rBNhSdZo,5570
19
+ auto_code_fixer-0.4.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
20
+ auto_code_fixer-0.4.0.dist-info/entry_points.txt,sha256=a-j2rkfwkrhXZ5Qbz_6_gwk6Bj7nijYR1DALjWp5Myk,61
21
+ auto_code_fixer-0.4.0.dist-info/top_level.txt,sha256=qUk1qznb6Qxqmxy2A3z_5dpOZlmNKHwUiLuJwH-CrAk,16
22
+ auto_code_fixer-0.4.0.dist-info/RECORD,,