pyfixer-ai 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.
- pyfixer_ai-0.4.0/LICENSE +21 -0
- pyfixer_ai-0.4.0/PKG-INFO +420 -0
- pyfixer_ai-0.4.0/README.md +387 -0
- pyfixer_ai-0.4.0/pyfixer/__init__.py +4 -0
- pyfixer_ai-0.4.0/pyfixer/__main__.py +4 -0
- pyfixer_ai-0.4.0/pyfixer/_pair_probe.py +114 -0
- pyfixer_ai-0.4.0/pyfixer/ast_detector.py +1278 -0
- pyfixer_ai-0.4.0/pyfixer/ast_fixer.py +2446 -0
- pyfixer_ai-0.4.0/pyfixer/ast_safety.py +316 -0
- pyfixer_ai-0.4.0/pyfixer/ast_style_fixes.py +837 -0
- pyfixer_ai-0.4.0/pyfixer/byok.py +565 -0
- pyfixer_ai-0.4.0/pyfixer/canon.py +645 -0
- pyfixer_ai-0.4.0/pyfixer/cli.py +2060 -0
- pyfixer_ai-0.4.0/pyfixer/config.py +216 -0
- pyfixer_ai-0.4.0/pyfixer/contracts.py +656 -0
- pyfixer_ai-0.4.0/pyfixer/deterministic.py +237 -0
- pyfixer_ai-0.4.0/pyfixer/embedder.py +79 -0
- pyfixer_ai-0.4.0/pyfixer/feedback_loop.py +221 -0
- pyfixer_ai-0.4.0/pyfixer/fewshot.py +166 -0
- pyfixer_ai-0.4.0/pyfixer/fixer.py +4334 -0
- pyfixer_ai-0.4.0/pyfixer/guardrails.py +138 -0
- pyfixer_ai-0.4.0/pyfixer/kbest.py +241 -0
- pyfixer_ai-0.4.0/pyfixer/knowledge_base.py +1249 -0
- pyfixer_ai-0.4.0/pyfixer/learning.py +495 -0
- pyfixer_ai-0.4.0/pyfixer/llm_targeted.py +540 -0
- pyfixer_ai-0.4.0/pyfixer/logic_fixes.py +977 -0
- pyfixer_ai-0.4.0/pyfixer/main.py +866 -0
- pyfixer_ai-0.4.0/pyfixer/oracle_validate.py +174 -0
- pyfixer_ai-0.4.0/pyfixer/orchestrator.py +240 -0
- pyfixer_ai-0.4.0/pyfixer/postprocess.py +4298 -0
- pyfixer_ai-0.4.0/pyfixer/primitives.py +252 -0
- pyfixer_ai-0.4.0/pyfixer/py.typed +0 -0
- pyfixer_ai-0.4.0/pyfixer/rag_retriever.py +1761 -0
- pyfixer_ai-0.4.0/pyfixer/regex_patterns.py +3066 -0
- pyfixer_ai-0.4.0/pyfixer/report.py +163 -0
- pyfixer_ai-0.4.0/pyfixer/router.py +143 -0
- pyfixer_ai-0.4.0/pyfixer/rule_registry.py +231 -0
- pyfixer_ai-0.4.0/pyfixer/safety.py +474 -0
- pyfixer_ai-0.4.0/pyfixer/sandbox.py +102 -0
- pyfixer_ai-0.4.0/pyfixer/sandboxexec.py +210 -0
- pyfixer_ai-0.4.0/pyfixer/scanner.py +1303 -0
- pyfixer_ai-0.4.0/pyfixer/store.py +502 -0
- pyfixer_ai-0.4.0/pyfixer/style_fixes.py +389 -0
- pyfixer_ai-0.4.0/pyfixer/tests_gen.py +268 -0
- pyfixer_ai-0.4.0/pyfixer/trace.py +39 -0
- pyfixer_ai-0.4.0/pyfixer/type_doc_generator.py +611 -0
- pyfixer_ai-0.4.0/pyfixer/validators.py +316 -0
- pyfixer_ai-0.4.0/pyfixer/verify.py +842 -0
- pyfixer_ai-0.4.0/pyfixer/workers.py +420 -0
- pyfixer_ai-0.4.0/pyfixer_ai.egg-info/PKG-INFO +420 -0
- pyfixer_ai-0.4.0/pyfixer_ai.egg-info/SOURCES.txt +121 -0
- pyfixer_ai-0.4.0/pyfixer_ai.egg-info/dependency_links.txt +1 -0
- pyfixer_ai-0.4.0/pyfixer_ai.egg-info/entry_points.txt +2 -0
- pyfixer_ai-0.4.0/pyfixer_ai.egg-info/requires.txt +12 -0
- pyfixer_ai-0.4.0/pyfixer_ai.egg-info/top_level.txt +1 -0
- pyfixer_ai-0.4.0/pyproject.toml +121 -0
- pyfixer_ai-0.4.0/setup.cfg +4 -0
- pyfixer_ai-0.4.0/tests/test_api.py +148 -0
- pyfixer_ai-0.4.0/tests/test_api_extended.py +415 -0
- pyfixer_ai-0.4.0/tests/test_ast_safety.py +74 -0
- pyfixer_ai-0.4.0/tests/test_ast_style_imports.py +281 -0
- pyfixer_ai-0.4.0/tests/test_byok.py +28 -0
- pyfixer_ai-0.4.0/tests/test_byok_extended.py +63 -0
- pyfixer_ai-0.4.0/tests/test_candidate_preserves_defs.py +55 -0
- pyfixer_ai-0.4.0/tests/test_candidate_preserves_tests.py +119 -0
- pyfixer_ai-0.4.0/tests/test_candidate_terminates.py +46 -0
- pyfixer_ai-0.4.0/tests/test_canon.py +108 -0
- pyfixer_ai-0.4.0/tests/test_cli.py +584 -0
- pyfixer_ai-0.4.0/tests/test_common_logic_imports.py +281 -0
- pyfixer_ai-0.4.0/tests/test_config.py +59 -0
- pyfixer_ai-0.4.0/tests/test_contract_gate.py +368 -0
- pyfixer_ai-0.4.0/tests/test_corpus_regression.py +401 -0
- pyfixer_ai-0.4.0/tests/test_deg_join_runtime_gate.py +116 -0
- pyfixer_ai-0.4.0/tests/test_detector_refinements.py +420 -0
- pyfixer_ai-0.4.0/tests/test_determinism_gates.py +79 -0
- pyfixer_ai-0.4.0/tests/test_deterministic_unified.py +202 -0
- pyfixer_ai-0.4.0/tests/test_differential_rules.py +458 -0
- pyfixer_ai-0.4.0/tests/test_docs_cli.py +55 -0
- pyfixer_ai-0.4.0/tests/test_ensure_typing_imports.py +46 -0
- pyfixer_ai-0.4.0/tests/test_fix_flow.py +576 -0
- pyfixer_ai-0.4.0/tests/test_folder_verify_flow.py +183 -0
- pyfixer_ai-0.4.0/tests/test_ghaction_fix.py +43 -0
- pyfixer_ai-0.4.0/tests/test_guardrails.py +87 -0
- pyfixer_ai-0.4.0/tests/test_idempotency.py +291 -0
- pyfixer_ai-0.4.0/tests/test_improvements.py +749 -0
- pyfixer_ai-0.4.0/tests/test_join_runtime_lane_gate.py +97 -0
- pyfixer_ai-0.4.0/tests/test_kbest.py +135 -0
- pyfixer_ai-0.4.0/tests/test_key_pool.py +77 -0
- pyfixer_ai-0.4.0/tests/test_learning.py +136 -0
- pyfixer_ai-0.4.0/tests/test_llm_targeted.py +388 -0
- pyfixer_ai-0.4.0/tests/test_merge_back_incompatible.py +207 -0
- pyfixer_ai-0.4.0/tests/test_merge_pipeline.py +425 -0
- pyfixer_ai-0.4.0/tests/test_mock_llm_contract.py +872 -0
- pyfixer_ai-0.4.0/tests/test_oracle_mutation_test.py +238 -0
- pyfixer_ai-0.4.0/tests/test_oracle_reflexive.py +377 -0
- pyfixer_ai-0.4.0/tests/test_proto_detector.py +72 -0
- pyfixer_ai-0.4.0/tests/test_quality_fixes.py +585 -0
- pyfixer_ai-0.4.0/tests/test_robustness.py +297 -0
- pyfixer_ai-0.4.0/tests/test_routing_fixes.py +168 -0
- pyfixer_ai-0.4.0/tests/test_rule_registry.py +108 -0
- pyfixer_ai-0.4.0/tests/test_runtime_confirm_lane.py +31 -0
- pyfixer_ai-0.4.0/tests/test_safe_scope.py +129 -0
- pyfixer_ai-0.4.0/tests/test_safety.py +693 -0
- pyfixer_ai-0.4.0/tests/test_sandbox.py +83 -0
- pyfixer_ai-0.4.0/tests/test_sandbox_extended.py +93 -0
- pyfixer_ai-0.4.0/tests/test_sandboxexec.py +89 -0
- pyfixer_ai-0.4.0/tests/test_scanner.py +406 -0
- pyfixer_ai-0.4.0/tests/test_scanner_recall.py +36 -0
- pyfixer_ai-0.4.0/tests/test_self_learning_integration.py +66 -0
- pyfixer_ai-0.4.0/tests/test_semantic_rules.py +169 -0
- pyfixer_ai-0.4.0/tests/test_single_file_lane_gate.py +86 -0
- pyfixer_ai-0.4.0/tests/test_store.py +98 -0
- pyfixer_ai-0.4.0/tests/test_store_extended.py +136 -0
- pyfixer_ai-0.4.0/tests/test_structure_gate.py +190 -0
- pyfixer_ai-0.4.0/tests/test_suppression_honesty.py +77 -0
- pyfixer_ai-0.4.0/tests/test_trace.py +53 -0
- pyfixer_ai-0.4.0/tests/test_type_doc_generator.py +268 -0
- pyfixer_ai-0.4.0/tests/test_validators.py +73 -0
- pyfixer_ai-0.4.0/tests/test_verify.py +219 -0
- pyfixer_ai-0.4.0/tests/test_verify_folder_batch.py +86 -0
- pyfixer_ai-0.4.0/tests/test_verify_gate_logic.py +140 -0
- pyfixer_ai-0.4.0/tests/test_verify_inconclusive.py +74 -0
- pyfixer_ai-0.4.0/tests/test_workers.py +158 -0
pyfixer_ai-0.4.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PyFixer contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyfixer-ai
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: The auto-fix button for Python: upload a broken file, get a fixed file.
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/hix-pro/pyfixer
|
|
7
|
+
Project-URL: Repository, https://github.com/hix-pro/pyfixer
|
|
8
|
+
Keywords: lint,autofix,refactor,code-quality,security
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: fastapi>=0.110
|
|
22
|
+
Requires-Dist: uvicorn>=0.27
|
|
23
|
+
Requires-Dist: python-multipart>=0.0.9
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Requires-Dist: ruff>=0.5
|
|
26
|
+
Requires-Dist: bandit>=1.7
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
31
|
+
Requires-Dist: hypothesis>=6.100; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# PyFixer
|
|
35
|
+
|
|
36
|
+
**The auto-fix button for Python.**
|
|
37
|
+
|
|
38
|
+
Upload a broken file. Get a fixed file. That's it.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
Upload → Scan → Fix → Verify → Contracts → Done
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## What It Does
|
|
47
|
+
|
|
48
|
+
PyFixer finds and fixes Python problems automatically.
|
|
49
|
+
|
|
50
|
+
| Problem | Fix |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Security holes | Replaces eval, exec, hardcoded passwords, weak crypto |
|
|
53
|
+
| Unused imports | Removes them |
|
|
54
|
+
| Wrong types | Adds type annotations |
|
|
55
|
+
| Bad style | Formats code, fixes line length |
|
|
56
|
+
| Missing docs | Adds docstrings |
|
|
57
|
+
| Old Python | Modernizes to current syntax |
|
|
58
|
+
| Bare excepts | Changes to specific exceptions |
|
|
59
|
+
| Print statements | Converts to logging |
|
|
60
|
+
| Mutable defaults | Fixes `def foo(x=[])` |
|
|
61
|
+
| Builtin shadowing | Fixes `list = x` |
|
|
62
|
+
| Type comparison | Fixes `type(x)==type(y)` |
|
|
63
|
+
| Performance | Fixes O(n²) loops, string concat |
|
|
64
|
+
| Weak random | Replaces `random` with `secrets` |
|
|
65
|
+
| Weak hash | Replaces MD5 with SHA256 |
|
|
66
|
+
| Logic bugs | Behavioral tests catch wrong operators, crash-on-empty, stubs |
|
|
67
|
+
|
|
68
|
+
**17+ types of issues. One click to fix.**
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## How It Works (v0.3)
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
1. Upload .py file
|
|
76
|
+
↓
|
|
77
|
+
2. Scan with ruff + bandit + mypy + custom/semantic checks
|
|
78
|
+
↓
|
|
79
|
+
3. Generate a behavioral test for EVERY function (coverage guarantee)
|
|
80
|
+
↓
|
|
81
|
+
4. AI proposes a fix (13 specialist workers)
|
|
82
|
+
- clean functions are never sent to the LLM
|
|
83
|
+
↓
|
|
84
|
+
5. Post-processing layer applies guaranteed fixes
|
|
85
|
+
- hardcoded secrets are scrubbed from the output
|
|
86
|
+
↓
|
|
87
|
+
6. CONTRACT GATE: probe original vs fixed behavior
|
|
88
|
+
- untouched functions must return identical results
|
|
89
|
+
- perf/logging-only fixes must be value-identical
|
|
90
|
+
- violations -> corrective retry -> verbatim splice
|
|
91
|
+
↓
|
|
92
|
+
7. Verify: syntax check + ruff + mypy + findings count
|
|
93
|
+
↓
|
|
94
|
+
8. You approve or reject
|
|
95
|
+
↓
|
|
96
|
+
9. Done. File is fixed.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Features
|
|
102
|
+
|
|
103
|
+
### 13 Specialist Workers
|
|
104
|
+
|
|
105
|
+
Each worker is an expert in one thing:
|
|
106
|
+
|
|
107
|
+
| Worker | What It Fixes |
|
|
108
|
+
|---|---|
|
|
109
|
+
| Syntax Surgeon | Syntax errors, indentation |
|
|
110
|
+
| Security Guard | SQL injection, hardcoded passwords, weak crypto |
|
|
111
|
+
| Type Tamer | Missing type annotations, wrong types |
|
|
112
|
+
| Style Formatter | Line length, imports, formatting |
|
|
113
|
+
| Code Cleaner | Dead code, unused variables |
|
|
114
|
+
| Docstring Writer | Missing docstrings, D101-D107 |
|
|
115
|
+
| Annotation Expert | Missing annotations, ANN001-ANN204 |
|
|
116
|
+
| Modernizer | Old Python syntax, PTH/SIM/RET |
|
|
117
|
+
| Performance Optimizer | O(n²) loops, string concatenation |
|
|
118
|
+
| Logger | print() → logging conversion |
|
|
119
|
+
| Error Handler | Bare excepts, broad exceptions |
|
|
120
|
+
| Generalist | Complex issues, multiple codes |
|
|
121
|
+
| MyPy Fixer | Type ignore comments, mypy-specific |
|
|
122
|
+
|
|
123
|
+
### BYOK (Bring Your Own Key)
|
|
124
|
+
|
|
125
|
+
You bring your own API key. We never store it.
|
|
126
|
+
|
|
127
|
+
| Provider | Key prefix | Model |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| Google Gemini | `AQ.` | gemini-3.7-flash |
|
|
130
|
+
| DeepSeek | `sk-` (probed) | deepseek-chat |
|
|
131
|
+
| OpenRouter | `sk-or-` | deepseek/deepseek-v4-flash |
|
|
132
|
+
| OpenAI | `sk-` | gpt-4o-mini |
|
|
133
|
+
|
|
134
|
+
#### Server-side key pool (optional)
|
|
135
|
+
|
|
136
|
+
Tired of pasting a key into the UI? Configure keys on the server and every
|
|
137
|
+
client can fix without one:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Option A: environment variable (comma or newline separated)
|
|
141
|
+
export PYFIXER_KEYS="AQ.Ab8RN6...,AQ.Ab8RN7..."
|
|
142
|
+
|
|
143
|
+
# Option B: key file, one key per line
|
|
144
|
+
$EDITOR data/gemini.keys # gitignored — never commit this file
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Keys rotate automatically (N keys = N × 15 requests/min). The pool is only
|
|
148
|
+
used when a request carries no `X-Api-Key` header / body key; client keys
|
|
149
|
+
always take priority and are never stored, logged, or written to disk.
|
|
150
|
+
`GET /api/server-keys` reports `{ "available": true }` without exposing them.
|
|
151
|
+
|
|
152
|
+
### LLM control
|
|
153
|
+
|
|
154
|
+
Every model call goes through a task profile (temperature / token budget /
|
|
155
|
+
JSON-mode) so behaviour is tunable in one place — `PROFILES` in
|
|
156
|
+
`pyfixer/byok.py`:
|
|
157
|
+
|
|
158
|
+
| Profile | Used for | Temperature | Max tokens |
|
|
159
|
+
|---|---|---|---|
|
|
160
|
+
| `fix` | code fixes | 0.0 | 4096 |
|
|
161
|
+
| `fix_escalate` | contract-violation retries | 0.0 | 4096 |
|
|
162
|
+
| `testgen` | behavioral test generation | 0.0 | 3072 |
|
|
163
|
+
| `classify` | finding classification | 0.0 | 512 + JSON mode |
|
|
164
|
+
| `probe` | cheap probes | 0.0 | 256 |
|
|
165
|
+
|
|
166
|
+
Model resolution order: per-call override → `PYFIXER_MODEL` env var →
|
|
167
|
+
provider default. The classifier requests provider-side JSON mode (Gemini
|
|
168
|
+
`response_mime_type`, OpenAI-compatible `response_format`).
|
|
169
|
+
|
|
170
|
+
### Verify & Rollback
|
|
171
|
+
|
|
172
|
+
- **Verify**: Proves the fix works (syntax + ruff + mypy + findings)
|
|
173
|
+
- **Rollback**: Undo if you don't like it
|
|
174
|
+
|
|
175
|
+
### Pattern Learning
|
|
176
|
+
|
|
177
|
+
PyFixer remembers what works. The more you use it, the better it gets.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Quickstart
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Clone
|
|
185
|
+
git clone <your-repo>
|
|
186
|
+
cd pyfixer
|
|
187
|
+
|
|
188
|
+
# Setup
|
|
189
|
+
python3 -m venv .venv
|
|
190
|
+
.venv/bin/pip install -r requirements.txt
|
|
191
|
+
|
|
192
|
+
# Run
|
|
193
|
+
.venv/bin/uvicorn pyfixer.main:app --host 127.0.0.1 --port 8501
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Open http://127.0.0.1:8501
|
|
197
|
+
|
|
198
|
+
> Note: `python-multipart` is required (pulled in by `requirements.txt`) so the
|
|
199
|
+
> upload endpoints that use `Form(...)` work. `ruff` and `bandit` must be
|
|
200
|
+
> installed and on `PATH` (the scanner shells out to `python -m ruff` /
|
|
201
|
+
> `python -m bandit`). `mypy` is optional — only used when you enable the mypy
|
|
202
|
+
> scanner.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## CLI
|
|
207
|
+
|
|
208
|
+
Fix files straight from the terminal — same pipeline, same gates, no server:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# module form (works from a checkout)
|
|
212
|
+
python -m pyfixer fix app.py
|
|
213
|
+
|
|
214
|
+
# after `pip install -e .` you also get the console script
|
|
215
|
+
pyfixer scan app.py # findings only, no AI call (exit 1 if any)
|
|
216
|
+
pyfixer fix app.py # writes app.fixed.py next to it
|
|
217
|
+
pyfixer fix app.py --apply # overwrite in place
|
|
218
|
+
pyfixer fix app.py --diff # print the patch, write nothing
|
|
219
|
+
pyfixer fix src/ # every .py under a folder (summary at end)
|
|
220
|
+
pyfixer fix app.py --format json # machine-readable output
|
|
221
|
+
pyfixer fix app.py --worker security # force a specialist
|
|
222
|
+
pyfixer fix app.py --llm # + LLM fixes (needs API key)
|
|
223
|
+
pyfixer fix app.py --llm --fast # fewer model calls, same gates
|
|
224
|
+
pyfixer fix app.py --aggressive # + mechanical ruff upgrades (no key)
|
|
225
|
+
pyfixer fix app.py --check # preview only, write nothing
|
|
226
|
+
pyfixer workers # list all specialists
|
|
227
|
+
|
|
228
|
+
# key: --key beats $PYFIXER_KEYS beats data/gemini.keys (rotating pool)
|
|
229
|
+
|
|
230
|
+
# LLM-gated flags: --comprehensive and --reference work ONLY with --llm;
|
|
231
|
+
# --verify needs --llm (pytest gate) or --llm-targeted (mypy gate).
|
|
232
|
+
# Without them the CLI exits 2 instead of silently ignoring the flag.
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Deterministic Fixes (no API key needed)
|
|
236
|
+
|
|
237
|
+
Deterministic mode (the default — no API key needed) applies 50+ pattern-based fixes instantly:
|
|
238
|
+
|
|
239
|
+
| Code | Fix |
|
|
240
|
+
|---|---|
|
|
241
|
+
| C408 | `dict(name="test")` → `{'name': 'test'}` |
|
|
242
|
+
| C416 | `[x for x in y]` → `list(y)` |
|
|
243
|
+
| E711 | `x == None` → `x is None` |
|
|
244
|
+
| E712 | `x == True` → `x` |
|
|
245
|
+
| E722 | `except:` → `except Exception:` |
|
|
246
|
+
| F401 | Remove unused imports |
|
|
247
|
+
| F841 | Remove unused variables |
|
|
248
|
+
| LOGIC005/009 | Fix inverted comparison signs |
|
|
249
|
+
| LOGIC010 | Fix insertion sort off-by-one |
|
|
250
|
+
| LOGIC014 | `def f(x=[])` → `def f(x=None)` |
|
|
251
|
+
| LOGIC015 | `type(x) == int` → `isinstance(x, int)` |
|
|
252
|
+
| LOGIC016 | `for i in range(n)` → `for _ in range(n)` |
|
|
253
|
+
| PLR1714 | `x==1 or x==2` → `x in (1, 2)` |
|
|
254
|
+
| RET505 | Remove unnecessary `else` after `return` |
|
|
255
|
+
| SIM102 | `if x: if y:` → `if x and y:` |
|
|
256
|
+
| SIM105 | `try: pass` → `contextlib.suppress()` |
|
|
257
|
+
| SIM115 | `open()` → `with open()` |
|
|
258
|
+
| SIM116 | `if k in d: return d[k]` → `d.get(k)` |
|
|
259
|
+
| SIM118 | `x in d.keys()` → `x in d` |
|
|
260
|
+
| SIM210 | `True if x else False` → `bool(x)` |
|
|
261
|
+
| SIM212 | `if x: return True else: return False` → `return x` |
|
|
262
|
+
| UP030 | `"{}".format(x)` → `f"{x}"` |
|
|
263
|
+
|
|
264
|
+
**Example:**
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
# Quick fix without API key (deterministic is the default)
|
|
268
|
+
pyfixer fix buggy.py
|
|
269
|
+
|
|
270
|
+
# See what would change
|
|
271
|
+
pyfixer fix buggy.py --diff
|
|
272
|
+
|
|
273
|
+
# Apply in place
|
|
274
|
+
pyfixer fix buggy.py --apply
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Exit codes: `0` OK · `1` findings survived the fix · `2` usage/no key.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## API
|
|
282
|
+
|
|
283
|
+
| Method | Endpoint | Description |
|
|
284
|
+
|---|---|---|
|
|
285
|
+
| GET | `/healthz` | Health check |
|
|
286
|
+
| POST | `/api/uploads` | Upload + auto-scan |
|
|
287
|
+
| GET | `/api/uploads` | List all files |
|
|
288
|
+
| GET | `/api/uploads/{id}` | Get file details |
|
|
289
|
+
| POST | `/api/uploads/{id}/fix` | Propose AI fix |
|
|
290
|
+
| POST | `/api/uploads/{id}/verify` | Verify fix works |
|
|
291
|
+
| POST | `/api/uploads/{id}/rollback` | Undo fix |
|
|
292
|
+
| GET | `/api/fixes/{id}/approve` | Approve fix |
|
|
293
|
+
| GET | `/api/fixes/{id}/reject` | Reject fix |
|
|
294
|
+
| GET | `/api/uploads/{id}/report` | Download report |
|
|
295
|
+
| GET | `/api/workers` | List all workers |
|
|
296
|
+
| GET | `/api/audit` | Audit trail |
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Scanner
|
|
301
|
+
|
|
302
|
+
PyFixer uses multiple scanners:
|
|
303
|
+
|
|
304
|
+
- **ruff** — Fast Python linter (16 rule sets)
|
|
305
|
+
- **bandit** — Security scanner
|
|
306
|
+
- **mypy** — Type checker
|
|
307
|
+
- **AST detector** — 16 LOGIC codes + 10 STYLE codes + 5+ ALGO/PERF codes
|
|
308
|
+
- **Custom checks** — 8 additional checks:
|
|
309
|
+
- ERR001: Missing error handling
|
|
310
|
+
- ERR002: Broad exception catching
|
|
311
|
+
- LOG001: print() instead of logging
|
|
312
|
+
- PERF001: Inefficient string concatenation
|
|
313
|
+
- MUT001: Mutable default arguments
|
|
314
|
+
- SHADOW001: Builtin name shadowing
|
|
315
|
+
- TYPE001: type() comparison instead of isinstance()
|
|
316
|
+
|
|
317
|
+
**Total: 50+ types of issues detected.**
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Testing
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
PYTHONPATH=. .venv/bin/pytest tests/ -v
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**862 tests passing** (incl. 9 opt-in corpus parity suites).
|
|
328
|
+
|
|
329
|
+
### Regression benchmarks (LLM-in-the-loop)
|
|
330
|
+
|
|
331
|
+
`benchmarks/` contains bug files with human-verified `correct.py` references
|
|
332
|
+
and `check.py` auto-checkers. The harness runs each through the REAL fix
|
|
333
|
+
pipeline and scores it — including clean **canary functions** that must keep
|
|
334
|
+
identical behavior (contract-regression guard).
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
# all cases (uses server key pool or PYFIXER_BENCH_KEY)
|
|
338
|
+
.venv/bin/python benchmarks/run_bench.py
|
|
339
|
+
|
|
340
|
+
# one case
|
|
341
|
+
.venv/bin/python benchmarks/run_bench.py --only logic_signs
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Cases: `algorithms`, `strings`, `mixed`, `security_basics`, `crash_edges`,
|
|
345
|
+
`logic_signs`, `perf_logging`. A manual GitHub Actions workflow
|
|
346
|
+
(`.github/workflows/regression.yml`) runs them on demand with a
|
|
347
|
+
`PYFIXER_KEYS` secret.
|
|
348
|
+
|
|
349
|
+
### Corpus regression (downstream suite parity)
|
|
350
|
+
|
|
351
|
+
`tests/test_corpus_regression.py` runs nine real projects' test suites against
|
|
352
|
+
both the pristine checkout and the deterministic rewrite, asserting identical
|
|
353
|
+
pass/fail signatures — the harness that caught the RET505 click corruption,
|
|
354
|
+
the ERR003 logging-import breakage, and the F401 `# noqa` side-effect import
|
|
355
|
+
deletion. Opt-in via `PYFIXER_CORPUS_DIR`:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
bash scripts/provision_corpus.sh /path/to/pyfixer-corpus # checkouts + venvs
|
|
359
|
+
PYFIXER_CORPUS_DIR=/path/to/pyfixer-corpus PYTHONPATH=. pytest tests/test_corpus_regression.py -m corpus -q
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Repos: click, httpie, requests, rich, flask, fastapi, attrs (2022-era),
|
|
363
|
+
httpx 1.0b0 (2025-era), pandas 2.2.3. Pandas is special: its compiled `_libs`
|
|
364
|
+
can't be PYTHONPATH-shadowed, so it runs as a dedicated **two-venv** parity
|
|
365
|
+
test (`venv-pandas` vs `venv-pandas-fixed`, both on numpy==1.26.4). A manual +
|
|
366
|
+
weekly workflow (`.github/workflows/corpus.yml`) runs the same in CI.
|
|
367
|
+
|
|
368
|
+
`tests/test_semantic_rules.py` locks the semantic tier (SEM003/SEM004/SEM006)
|
|
369
|
+
with a detector mutation oracle plus differential-execution coverage of the
|
|
370
|
+
SEM004 fixer; `tests/test_mock_llm_contract.py` locks the SEM003
|
|
371
|
+
detector -> per-function-LLM wiring with a stubbed model (no network).
|
|
372
|
+
|
|
373
|
+
## Code quality
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
# Lint + auto-format
|
|
377
|
+
.venv/bin/ruff check pyfixer
|
|
378
|
+
.venv/bin/ruff format pyfixer
|
|
379
|
+
|
|
380
|
+
# Static type checking
|
|
381
|
+
.venv/bin/mypy pyfixer
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
A GitHub Actions workflow (`.github/workflows/ci.yml`) runs `ruff check`,
|
|
385
|
+
`ruff format --check`, `mypy`, and the full pytest suite on every push and
|
|
386
|
+
pull request, so regressions are caught before merge.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Tech Stack
|
|
391
|
+
|
|
392
|
+
- **Backend**: FastAPI + SQLite + Python
|
|
393
|
+
- **Scanner**: ruff + bandit + mypy + custom AST checks
|
|
394
|
+
- **AI**: OpenAI, OpenRouter, Google, DeepSeek (BYOK)
|
|
395
|
+
- **Testing**: pytest + coverage
|
|
396
|
+
|
|
397
|
+
## Tier Semantics
|
|
398
|
+
|
|
399
|
+
PyFixer promotes fixes through four tier levels. The goal is honest tier assignment — a function's tier reflects the evidence that validates its rewrite.
|
|
400
|
+
|
|
401
|
+
| Tier | When it's assigned | Promotion path |
|
|
402
|
+
|---|---|---|
|
|
403
|
+
| `UNVERIFIED` | Default for any fixed function that has **no oracle** (no `func_test`). LOGIC‑only fixes (ast‑detector findings) always land here, regardless of `verify_mode`. | After the real project suite passes (`--verify` in `cli.py`), tiers are promoted by `_promote_tiers_to_verified` in `cli.py:1168/1276`. |
|
|
404
|
+
| `AI_VERIFIED` | A generated `func_test` exists and the rewrite passes `_valid_fix`. The rewrite was validated against the AI‑generated test. | Same real‑suite promotion; this is the interim tier after propose. |
|
|
405
|
+
| `VERIFIED` | Earned only after the **real project suite** (`--verify`) runs successfully via `_promote_tiers_to_verified`. No automatic path mint `VERIFIED` at propose time. | Manual / CI gate after suite runs. |
|
|
406
|
+
| `RETRIEVED` | Deterministic fixes that match an existing reference (e.g. RETRIEVED from known‑good repo). | Stays RETRIEVED; no further promotion. |
|
|
407
|
+
|
|
408
|
+
**Honesty invariant:** `verify_mode=True` does **not** mint `VERIFIED`. It only means the user passed `--verify`; a real suite still needs to run afterwards. Promotion to `VERIFIED` belongs to the real‑suite gate in `cli.py`, never `propose()`.
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## License
|
|
413
|
+
|
|
414
|
+
Copyright (c) 2026 Aziz. All rights reserved.
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Author
|
|
419
|
+
|
|
420
|
+
**Aziz** — Built PyFixer to fix Python code automatically.
|