fixforward 0.1.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.
- fixforward-0.1.0/LICENSE +21 -0
- fixforward-0.1.0/PKG-INFO +264 -0
- fixforward-0.1.0/README.md +243 -0
- fixforward-0.1.0/fixforward/__init__.py +3 -0
- fixforward-0.1.0/fixforward/__main__.py +5 -0
- fixforward-0.1.0/fixforward/classifier.py +133 -0
- fixforward-0.1.0/fixforward/cli.py +222 -0
- fixforward-0.1.0/fixforward/copilot.py +287 -0
- fixforward-0.1.0/fixforward/detector.py +146 -0
- fixforward-0.1.0/fixforward/display.py +359 -0
- fixforward-0.1.0/fixforward/parsers/__init__.py +5 -0
- fixforward-0.1.0/fixforward/parsers/cargo_parser.py +123 -0
- fixforward-0.1.0/fixforward/parsers/npm_parser.py +154 -0
- fixforward-0.1.0/fixforward/parsers/pytest_parser.py +158 -0
- fixforward-0.1.0/fixforward/patcher.py +130 -0
- fixforward-0.1.0/fixforward/reporter.py +103 -0
- fixforward-0.1.0/fixforward/state.py +103 -0
- fixforward-0.1.0/fixforward/verifier.py +93 -0
- fixforward-0.1.0/fixforward.egg-info/PKG-INFO +264 -0
- fixforward-0.1.0/fixforward.egg-info/SOURCES.txt +24 -0
- fixforward-0.1.0/fixforward.egg-info/dependency_links.txt +1 -0
- fixforward-0.1.0/fixforward.egg-info/entry_points.txt +2 -0
- fixforward-0.1.0/fixforward.egg-info/requires.txt +1 -0
- fixforward-0.1.0/fixforward.egg-info/top_level.txt +1 -0
- fixforward-0.1.0/pyproject.toml +36 -0
- fixforward-0.1.0/setup.cfg +4 -0
fixforward-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aliza Ali
|
|
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,264 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fixforward
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Incident-to-PR autopilot: detect failures, generate fixes with GitHub Copilot CLI, and ship PRs.
|
|
5
|
+
Author: Aliza Ali
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/stackmasteraliza/fixforward
|
|
8
|
+
Project-URL: Repository, https://github.com/stackmasteraliza/fixforward
|
|
9
|
+
Keywords: cli,copilot,testing,autopilot,fix,github,developer-tools
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development :: Testing
|
|
15
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: rich>=13.0.0
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
<div align="center">
|
|
23
|
+
|
|
24
|
+
# FixForward
|
|
25
|
+
|
|
26
|
+
### Incident-to-PR autopilot powered by GitHub Copilot CLI.
|
|
27
|
+
|
|
28
|
+
[](https://pypi.org/project/fixforward/)
|
|
29
|
+
[](https://pypi.org/project/fixforward/)
|
|
30
|
+
[](https://opensource.org/licenses/MIT)
|
|
31
|
+
|
|
32
|
+
**One command to go from broken build to ready-to-merge PR.**
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
pip install fixforward
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
When builds or tests fail, most devs lose time figuring out root cause and writing the fix. FixForward turns one command into a full recovery workflow:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
fixforward run
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
It detects failures, classifies the issue, asks GitHub Copilot CLI to generate a minimal patch, applies it on a safe branch, re-runs tests to verify, and generates a PR description — all in your terminal.
|
|
49
|
+
|
|
50
|
+
## Demo
|
|
51
|
+
|
|
52
|
+
<!-- Replace with actual demo GIF -->
|
|
53
|
+
<!--  -->
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
$ fixforward run --path ./my-broken-project
|
|
57
|
+
|
|
58
|
+
[1/6] Detecting project ecosystem... → Python / pytest
|
|
59
|
+
[2/6] Running tests to capture failures... → 1 failed / 4 passed
|
|
60
|
+
[3/6] Classifying failures... → assertion (85% confidence)
|
|
61
|
+
[4/6] Asking GitHub Copilot for a fix... → Patch generated
|
|
62
|
+
[5/6] Applying patch on a safe branch... → fixforward/auto-20260215-130944
|
|
63
|
+
[6/6] Re-running tests to verify fix... → 0 failed / 5 passed ✓
|
|
64
|
+
|
|
65
|
+
Confidence: ███████████████████░ 95%
|
|
66
|
+
All tests passing after fix!
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## How It Works
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
tests fail → parse output → classify failure → Copilot generates fix → apply on branch → verify → PR report
|
|
73
|
+
│ │ │ │ │ │ │
|
|
74
|
+
pytest extract regex gh copilot -p git branch re-run markdown
|
|
75
|
+
npm test failures heuristics "fix this bug..." safe commit tests PR body
|
|
76
|
+
cargo test diff
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
1. **Detect** — finds `pytest.ini`, `package.json`, or `Cargo.toml` to identify your ecosystem
|
|
80
|
+
2. **Run tests** — executes the test suite and captures the raw output
|
|
81
|
+
3. **Parse** — extracts individual test failures with file paths, line numbers, and error messages
|
|
82
|
+
4. **Classify** — categorizes failures (dependency, syntax, assertion, API change, env mismatch, lint, flaky)
|
|
83
|
+
5. **Generate patch** — sends failure context to GitHub Copilot CLI (`gh copilot -p`) for a minimal fix
|
|
84
|
+
6. **Apply** — creates a `fixforward/auto-*` branch, writes the fix, commits
|
|
85
|
+
7. **Verify** — re-runs the test suite, shows before/after comparison with confidence score
|
|
86
|
+
8. **Report** — generates PR title and body with what changed and why
|
|
87
|
+
|
|
88
|
+
## Supported Ecosystems
|
|
89
|
+
|
|
90
|
+
| Ecosystem | Test Command | Parser |
|
|
91
|
+
|-----------|-------------|--------|
|
|
92
|
+
| **Python** | `pytest --tb=long -v` | Extracts failures, tracebacks, assertion details |
|
|
93
|
+
| **Node.js** | `npm test` | Supports Jest and Mocha output formats |
|
|
94
|
+
| **Rust** | `cargo test` | Parses panics, `assert_eq!` failures, test summaries |
|
|
95
|
+
|
|
96
|
+
## Features
|
|
97
|
+
|
|
98
|
+
| Feature | Description |
|
|
99
|
+
|---------|-------------|
|
|
100
|
+
| **Auto-detect** | Identifies Python, Node, or Rust projects automatically |
|
|
101
|
+
| **Smart classification** | Categorizes failures: dependency, syntax, assertion, API change, env, lint, flaky |
|
|
102
|
+
| **Copilot-powered fixes** | Uses `gh copilot -p` to generate minimal patches |
|
|
103
|
+
| **Safe branches** | Always applies fixes on a `fixforward/auto-*` branch, never touches your working branch |
|
|
104
|
+
| **Before/after verification** | Re-runs tests and shows what changed |
|
|
105
|
+
| **Confidence scoring** | 0-100% score based on how many failures were fixed |
|
|
106
|
+
| **PR report generation** | Ready-to-use PR title and body in Markdown |
|
|
107
|
+
| **Dry-run mode** | Preview the diagnosis without applying any changes |
|
|
108
|
+
| **Rollback** | One command to undo everything: `fixforward rollback` |
|
|
109
|
+
| **Patch preview** | See the exact diff before confirming |
|
|
110
|
+
|
|
111
|
+
## Quick Start
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pip install fixforward
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Or install from source:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git clone https://github.com/stackmasteraliza/fixforward.git
|
|
121
|
+
cd fixforward
|
|
122
|
+
pip install -e .
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Prerequisites
|
|
126
|
+
|
|
127
|
+
- Python 3.9+
|
|
128
|
+
- [GitHub CLI](https://cli.github.com/) (`gh`) installed and authenticated
|
|
129
|
+
- [GitHub Copilot CLI](https://gh.io/copilot-cli) — `gh copilot` must be available
|
|
130
|
+
- Git installed and in PATH
|
|
131
|
+
|
|
132
|
+
## Usage
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Full autopilot: detect, fix, verify, report
|
|
136
|
+
fixforward run
|
|
137
|
+
|
|
138
|
+
# Specify a project path
|
|
139
|
+
fixforward run --path ~/projects/my-broken-app
|
|
140
|
+
|
|
141
|
+
# Dry run: see diagnosis without applying fixes
|
|
142
|
+
fixforward run --dry-run
|
|
143
|
+
|
|
144
|
+
# Skip confirmation prompt
|
|
145
|
+
fixforward run --no-confirm
|
|
146
|
+
|
|
147
|
+
# Diagnose only: detect and classify failures
|
|
148
|
+
fixforward diagnose
|
|
149
|
+
|
|
150
|
+
# Diagnose with JSON output
|
|
151
|
+
fixforward diagnose --json
|
|
152
|
+
|
|
153
|
+
# Undo the last fix
|
|
154
|
+
fixforward rollback
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
You can also run it as a Python module:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
python -m fixforward run --path ./my-project
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## CLI Options
|
|
164
|
+
|
|
165
|
+
### `fixforward run`
|
|
166
|
+
|
|
167
|
+
| Flag | Description |
|
|
168
|
+
|------|-------------|
|
|
169
|
+
| `--path`, `-p` | Path to the project (default: `.`) |
|
|
170
|
+
| `--dry-run`, `-n` | Show diagnosis without applying fixes |
|
|
171
|
+
| `--no-confirm` | Skip the patch confirmation prompt |
|
|
172
|
+
| `--no-animate` | Disable loading animations |
|
|
173
|
+
| `--verbose` | Show raw Copilot CLI output |
|
|
174
|
+
|
|
175
|
+
### `fixforward diagnose`
|
|
176
|
+
|
|
177
|
+
| Flag | Description |
|
|
178
|
+
|------|-------------|
|
|
179
|
+
| `--path`, `-p` | Path to the project (default: `.`) |
|
|
180
|
+
| `--json` | Output classification as JSON |
|
|
181
|
+
| `--no-animate` | Disable loading animations |
|
|
182
|
+
|
|
183
|
+
### `fixforward rollback`
|
|
184
|
+
|
|
185
|
+
| Flag | Description |
|
|
186
|
+
|------|-------------|
|
|
187
|
+
| `--path`, `-p` | Path to the project (default: `.`) |
|
|
188
|
+
|
|
189
|
+
## Failure Categories
|
|
190
|
+
|
|
191
|
+
FixForward classifies failures using regex heuristics for instant, deterministic results:
|
|
192
|
+
|
|
193
|
+
| Category | Examples | Confidence |
|
|
194
|
+
|----------|----------|------------|
|
|
195
|
+
| **syntax_error** | `SyntaxError`, `IndentationError`, `Unexpected token` | 95% |
|
|
196
|
+
| **dependency** | `ModuleNotFoundError`, `Cannot find module`, `ImportError` | 90% |
|
|
197
|
+
| **api_change** | `AttributeError`, `TypeError` (wrong args), `has no member` | 85% |
|
|
198
|
+
| **assertion** | `AssertionError`, `assert_eq!`, `expect().toEqual()` | 85% |
|
|
199
|
+
| **env_mismatch** | Version conflicts, missing commands, engine incompatible | 80% |
|
|
200
|
+
| **lint** | Flake8, ESLint, Clippy warnings | 75% |
|
|
201
|
+
| **flaky_test** | Timeouts, connection refused, intermittent | 60% |
|
|
202
|
+
|
|
203
|
+
## Safety
|
|
204
|
+
|
|
205
|
+
FixForward is designed to be safe by default:
|
|
206
|
+
|
|
207
|
+
- **Never modifies your working branch** — all fixes go on `fixforward/auto-*` branches
|
|
208
|
+
- **Stashes dirty state** — if you have uncommitted changes, they're stashed and restored on rollback
|
|
209
|
+
- **Patch preview** — see the exact diff before confirming
|
|
210
|
+
- **Dry-run mode** — diagnose without touching anything
|
|
211
|
+
- **One-command rollback** — `fixforward rollback` restores everything
|
|
212
|
+
- **State persistence** — rollback info stored at `~/.fixforward/state.json`
|
|
213
|
+
|
|
214
|
+
## Built With GitHub Copilot CLI
|
|
215
|
+
|
|
216
|
+
This project was built using [GitHub Copilot CLI](https://gh.io/copilot-cli) as part of the GitHub Copilot CLI Challenge on DEV. Copilot CLI is central to the tool — it powers the actual fix generation via `gh copilot -p`:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# How FixForward uses Copilot CLI internally:
|
|
220
|
+
gh copilot -- -p "I have a python project with failing tests.
|
|
221
|
+
The test test_divide in test_app.py fails with AssertionError:
|
|
222
|
+
assert 3.333 == 3. Generate a minimal fix..." \
|
|
223
|
+
--allow-all-tools --add-dir ./project --silent
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Copilot CLI reads the source files, understands the context, and generates the smallest possible code change. FixForward then applies it, verifies it, and reports the result.
|
|
227
|
+
|
|
228
|
+
## Architecture
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
fixforward/
|
|
232
|
+
├── cli.py # argparse CLI with run/diagnose/rollback commands
|
|
233
|
+
├── detector.py # Ecosystem detection + test runner (subprocess)
|
|
234
|
+
├── classifier.py # Regex heuristic failure classification
|
|
235
|
+
├── copilot.py # GitHub Copilot CLI integration (gh copilot -p)
|
|
236
|
+
├── patcher.py # Safe branch creation + file patching
|
|
237
|
+
├── verifier.py # Test re-run + confidence scoring
|
|
238
|
+
├── reporter.py # PR title/body generation
|
|
239
|
+
├── display.py # Rich-based terminal UI
|
|
240
|
+
├── state.py # Rollback state persistence
|
|
241
|
+
└── parsers/
|
|
242
|
+
├── pytest_parser.py # pytest output parser
|
|
243
|
+
├── npm_parser.py # Jest/Mocha output parser
|
|
244
|
+
└── cargo_parser.py # cargo test output parser
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Only dependency:** [`rich`](https://github.com/Textualize/rich) — everything else is Python stdlib.
|
|
248
|
+
|
|
249
|
+
## Requirements
|
|
250
|
+
|
|
251
|
+
- Python 3.9+
|
|
252
|
+
- Git installed and in PATH
|
|
253
|
+
- GitHub CLI (`gh`) with Copilot access
|
|
254
|
+
- Terminal with color support
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
<div align="center">
|
|
259
|
+
|
|
260
|
+
MIT License — see [LICENSE](LICENSE) for details.
|
|
261
|
+
|
|
262
|
+
**If you like this project, give it a star!**
|
|
263
|
+
|
|
264
|
+
</div>
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# FixForward
|
|
4
|
+
|
|
5
|
+
### Incident-to-PR autopilot powered by GitHub Copilot CLI.
|
|
6
|
+
|
|
7
|
+
[](https://pypi.org/project/fixforward/)
|
|
8
|
+
[](https://pypi.org/project/fixforward/)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
|
|
11
|
+
**One command to go from broken build to ready-to-merge PR.**
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
pip install fixforward
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
When builds or tests fail, most devs lose time figuring out root cause and writing the fix. FixForward turns one command into a full recovery workflow:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
fixforward run
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
It detects failures, classifies the issue, asks GitHub Copilot CLI to generate a minimal patch, applies it on a safe branch, re-runs tests to verify, and generates a PR description — all in your terminal.
|
|
28
|
+
|
|
29
|
+
## Demo
|
|
30
|
+
|
|
31
|
+
<!-- Replace with actual demo GIF -->
|
|
32
|
+
<!--  -->
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
$ fixforward run --path ./my-broken-project
|
|
36
|
+
|
|
37
|
+
[1/6] Detecting project ecosystem... → Python / pytest
|
|
38
|
+
[2/6] Running tests to capture failures... → 1 failed / 4 passed
|
|
39
|
+
[3/6] Classifying failures... → assertion (85% confidence)
|
|
40
|
+
[4/6] Asking GitHub Copilot for a fix... → Patch generated
|
|
41
|
+
[5/6] Applying patch on a safe branch... → fixforward/auto-20260215-130944
|
|
42
|
+
[6/6] Re-running tests to verify fix... → 0 failed / 5 passed ✓
|
|
43
|
+
|
|
44
|
+
Confidence: ███████████████████░ 95%
|
|
45
|
+
All tests passing after fix!
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## How It Works
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
tests fail → parse output → classify failure → Copilot generates fix → apply on branch → verify → PR report
|
|
52
|
+
│ │ │ │ │ │ │
|
|
53
|
+
pytest extract regex gh copilot -p git branch re-run markdown
|
|
54
|
+
npm test failures heuristics "fix this bug..." safe commit tests PR body
|
|
55
|
+
cargo test diff
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
1. **Detect** — finds `pytest.ini`, `package.json`, or `Cargo.toml` to identify your ecosystem
|
|
59
|
+
2. **Run tests** — executes the test suite and captures the raw output
|
|
60
|
+
3. **Parse** — extracts individual test failures with file paths, line numbers, and error messages
|
|
61
|
+
4. **Classify** — categorizes failures (dependency, syntax, assertion, API change, env mismatch, lint, flaky)
|
|
62
|
+
5. **Generate patch** — sends failure context to GitHub Copilot CLI (`gh copilot -p`) for a minimal fix
|
|
63
|
+
6. **Apply** — creates a `fixforward/auto-*` branch, writes the fix, commits
|
|
64
|
+
7. **Verify** — re-runs the test suite, shows before/after comparison with confidence score
|
|
65
|
+
8. **Report** — generates PR title and body with what changed and why
|
|
66
|
+
|
|
67
|
+
## Supported Ecosystems
|
|
68
|
+
|
|
69
|
+
| Ecosystem | Test Command | Parser |
|
|
70
|
+
|-----------|-------------|--------|
|
|
71
|
+
| **Python** | `pytest --tb=long -v` | Extracts failures, tracebacks, assertion details |
|
|
72
|
+
| **Node.js** | `npm test` | Supports Jest and Mocha output formats |
|
|
73
|
+
| **Rust** | `cargo test` | Parses panics, `assert_eq!` failures, test summaries |
|
|
74
|
+
|
|
75
|
+
## Features
|
|
76
|
+
|
|
77
|
+
| Feature | Description |
|
|
78
|
+
|---------|-------------|
|
|
79
|
+
| **Auto-detect** | Identifies Python, Node, or Rust projects automatically |
|
|
80
|
+
| **Smart classification** | Categorizes failures: dependency, syntax, assertion, API change, env, lint, flaky |
|
|
81
|
+
| **Copilot-powered fixes** | Uses `gh copilot -p` to generate minimal patches |
|
|
82
|
+
| **Safe branches** | Always applies fixes on a `fixforward/auto-*` branch, never touches your working branch |
|
|
83
|
+
| **Before/after verification** | Re-runs tests and shows what changed |
|
|
84
|
+
| **Confidence scoring** | 0-100% score based on how many failures were fixed |
|
|
85
|
+
| **PR report generation** | Ready-to-use PR title and body in Markdown |
|
|
86
|
+
| **Dry-run mode** | Preview the diagnosis without applying any changes |
|
|
87
|
+
| **Rollback** | One command to undo everything: `fixforward rollback` |
|
|
88
|
+
| **Patch preview** | See the exact diff before confirming |
|
|
89
|
+
|
|
90
|
+
## Quick Start
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pip install fixforward
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Or install from source:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
git clone https://github.com/stackmasteraliza/fixforward.git
|
|
100
|
+
cd fixforward
|
|
101
|
+
pip install -e .
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Prerequisites
|
|
105
|
+
|
|
106
|
+
- Python 3.9+
|
|
107
|
+
- [GitHub CLI](https://cli.github.com/) (`gh`) installed and authenticated
|
|
108
|
+
- [GitHub Copilot CLI](https://gh.io/copilot-cli) — `gh copilot` must be available
|
|
109
|
+
- Git installed and in PATH
|
|
110
|
+
|
|
111
|
+
## Usage
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Full autopilot: detect, fix, verify, report
|
|
115
|
+
fixforward run
|
|
116
|
+
|
|
117
|
+
# Specify a project path
|
|
118
|
+
fixforward run --path ~/projects/my-broken-app
|
|
119
|
+
|
|
120
|
+
# Dry run: see diagnosis without applying fixes
|
|
121
|
+
fixforward run --dry-run
|
|
122
|
+
|
|
123
|
+
# Skip confirmation prompt
|
|
124
|
+
fixforward run --no-confirm
|
|
125
|
+
|
|
126
|
+
# Diagnose only: detect and classify failures
|
|
127
|
+
fixforward diagnose
|
|
128
|
+
|
|
129
|
+
# Diagnose with JSON output
|
|
130
|
+
fixforward diagnose --json
|
|
131
|
+
|
|
132
|
+
# Undo the last fix
|
|
133
|
+
fixforward rollback
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
You can also run it as a Python module:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
python -m fixforward run --path ./my-project
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## CLI Options
|
|
143
|
+
|
|
144
|
+
### `fixforward run`
|
|
145
|
+
|
|
146
|
+
| Flag | Description |
|
|
147
|
+
|------|-------------|
|
|
148
|
+
| `--path`, `-p` | Path to the project (default: `.`) |
|
|
149
|
+
| `--dry-run`, `-n` | Show diagnosis without applying fixes |
|
|
150
|
+
| `--no-confirm` | Skip the patch confirmation prompt |
|
|
151
|
+
| `--no-animate` | Disable loading animations |
|
|
152
|
+
| `--verbose` | Show raw Copilot CLI output |
|
|
153
|
+
|
|
154
|
+
### `fixforward diagnose`
|
|
155
|
+
|
|
156
|
+
| Flag | Description |
|
|
157
|
+
|------|-------------|
|
|
158
|
+
| `--path`, `-p` | Path to the project (default: `.`) |
|
|
159
|
+
| `--json` | Output classification as JSON |
|
|
160
|
+
| `--no-animate` | Disable loading animations |
|
|
161
|
+
|
|
162
|
+
### `fixforward rollback`
|
|
163
|
+
|
|
164
|
+
| Flag | Description |
|
|
165
|
+
|------|-------------|
|
|
166
|
+
| `--path`, `-p` | Path to the project (default: `.`) |
|
|
167
|
+
|
|
168
|
+
## Failure Categories
|
|
169
|
+
|
|
170
|
+
FixForward classifies failures using regex heuristics for instant, deterministic results:
|
|
171
|
+
|
|
172
|
+
| Category | Examples | Confidence |
|
|
173
|
+
|----------|----------|------------|
|
|
174
|
+
| **syntax_error** | `SyntaxError`, `IndentationError`, `Unexpected token` | 95% |
|
|
175
|
+
| **dependency** | `ModuleNotFoundError`, `Cannot find module`, `ImportError` | 90% |
|
|
176
|
+
| **api_change** | `AttributeError`, `TypeError` (wrong args), `has no member` | 85% |
|
|
177
|
+
| **assertion** | `AssertionError`, `assert_eq!`, `expect().toEqual()` | 85% |
|
|
178
|
+
| **env_mismatch** | Version conflicts, missing commands, engine incompatible | 80% |
|
|
179
|
+
| **lint** | Flake8, ESLint, Clippy warnings | 75% |
|
|
180
|
+
| **flaky_test** | Timeouts, connection refused, intermittent | 60% |
|
|
181
|
+
|
|
182
|
+
## Safety
|
|
183
|
+
|
|
184
|
+
FixForward is designed to be safe by default:
|
|
185
|
+
|
|
186
|
+
- **Never modifies your working branch** — all fixes go on `fixforward/auto-*` branches
|
|
187
|
+
- **Stashes dirty state** — if you have uncommitted changes, they're stashed and restored on rollback
|
|
188
|
+
- **Patch preview** — see the exact diff before confirming
|
|
189
|
+
- **Dry-run mode** — diagnose without touching anything
|
|
190
|
+
- **One-command rollback** — `fixforward rollback` restores everything
|
|
191
|
+
- **State persistence** — rollback info stored at `~/.fixforward/state.json`
|
|
192
|
+
|
|
193
|
+
## Built With GitHub Copilot CLI
|
|
194
|
+
|
|
195
|
+
This project was built using [GitHub Copilot CLI](https://gh.io/copilot-cli) as part of the GitHub Copilot CLI Challenge on DEV. Copilot CLI is central to the tool — it powers the actual fix generation via `gh copilot -p`:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# How FixForward uses Copilot CLI internally:
|
|
199
|
+
gh copilot -- -p "I have a python project with failing tests.
|
|
200
|
+
The test test_divide in test_app.py fails with AssertionError:
|
|
201
|
+
assert 3.333 == 3. Generate a minimal fix..." \
|
|
202
|
+
--allow-all-tools --add-dir ./project --silent
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Copilot CLI reads the source files, understands the context, and generates the smallest possible code change. FixForward then applies it, verifies it, and reports the result.
|
|
206
|
+
|
|
207
|
+
## Architecture
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
fixforward/
|
|
211
|
+
├── cli.py # argparse CLI with run/diagnose/rollback commands
|
|
212
|
+
├── detector.py # Ecosystem detection + test runner (subprocess)
|
|
213
|
+
├── classifier.py # Regex heuristic failure classification
|
|
214
|
+
├── copilot.py # GitHub Copilot CLI integration (gh copilot -p)
|
|
215
|
+
├── patcher.py # Safe branch creation + file patching
|
|
216
|
+
├── verifier.py # Test re-run + confidence scoring
|
|
217
|
+
├── reporter.py # PR title/body generation
|
|
218
|
+
├── display.py # Rich-based terminal UI
|
|
219
|
+
├── state.py # Rollback state persistence
|
|
220
|
+
└── parsers/
|
|
221
|
+
├── pytest_parser.py # pytest output parser
|
|
222
|
+
├── npm_parser.py # Jest/Mocha output parser
|
|
223
|
+
└── cargo_parser.py # cargo test output parser
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Only dependency:** [`rich`](https://github.com/Textualize/rich) — everything else is Python stdlib.
|
|
227
|
+
|
|
228
|
+
## Requirements
|
|
229
|
+
|
|
230
|
+
- Python 3.9+
|
|
231
|
+
- Git installed and in PATH
|
|
232
|
+
- GitHub CLI (`gh`) with Copilot access
|
|
233
|
+
- Terminal with color support
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
<div align="center">
|
|
238
|
+
|
|
239
|
+
MIT License — see [LICENSE](LICENSE) for details.
|
|
240
|
+
|
|
241
|
+
**If you like this project, give it a star!**
|
|
242
|
+
|
|
243
|
+
</div>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Failure classification engine using regex heuristics."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
from fixforward.detector import TestFailure, Ecosystem
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FailureCategory(Enum):
|
|
12
|
+
SYNTAX_ERROR = "syntax_error"
|
|
13
|
+
DEPENDENCY = "dependency"
|
|
14
|
+
ENV_MISMATCH = "env_mismatch"
|
|
15
|
+
API_CHANGE = "api_change"
|
|
16
|
+
LINT = "lint"
|
|
17
|
+
FLAKY_TEST = "flaky_test"
|
|
18
|
+
ASSERTION = "assertion"
|
|
19
|
+
UNKNOWN = "unknown"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ClassifiedFailure:
|
|
24
|
+
failure: TestFailure
|
|
25
|
+
category: FailureCategory
|
|
26
|
+
confidence: float
|
|
27
|
+
summary: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Classification rules in priority order (first match wins).
|
|
31
|
+
# Each rule: (category, confidence, [(pattern, summary_template)])
|
|
32
|
+
RULES = [
|
|
33
|
+
(FailureCategory.SYNTAX_ERROR, 0.95, [
|
|
34
|
+
(r"SyntaxError:\s*(.+)", "Syntax error: {0}"),
|
|
35
|
+
(r"IndentationError:\s*(.+)", "Indentation error: {0}"),
|
|
36
|
+
(r"Unexpected token\s*(.+)", "Unexpected token: {0}"),
|
|
37
|
+
(r"parse error", "Parse error"),
|
|
38
|
+
(r"expected\s+.+,\s+found\s+(.+)", "Expected/found mismatch: {0}"),
|
|
39
|
+
]),
|
|
40
|
+
(FailureCategory.DEPENDENCY, 0.90, [
|
|
41
|
+
(r"ModuleNotFoundError:\s*No module named '(\S+)'", "Missing module: {0}"),
|
|
42
|
+
(r"ImportError:\s*(.+)", "Import error: {0}"),
|
|
43
|
+
(r"Cannot find module '(\S+)'", "Missing Node module: {0}"),
|
|
44
|
+
(r"No module named '?(\S+?)'?", "Missing module: {0}"),
|
|
45
|
+
(r"unresolved import `(\S+)`", "Unresolved import: {0}"),
|
|
46
|
+
(r"package `(\S+)`.+not found", "Missing Rust crate: {0}"),
|
|
47
|
+
(r"Could not find a version that satisfies", "Dependency version conflict"),
|
|
48
|
+
]),
|
|
49
|
+
(FailureCategory.ENV_MISMATCH, 0.80, [
|
|
50
|
+
(r"version mismatch", "Version mismatch"),
|
|
51
|
+
(r"requires Python\s*([\d.]+)", "Requires Python {0}"),
|
|
52
|
+
(r"engine .+ is incompatible", "Engine incompatible"),
|
|
53
|
+
(r"ENOENT.+?'(\S+)'", "Command not found: {0}"),
|
|
54
|
+
(r"command not found:\s*(\S+)", "Command not found: {0}"),
|
|
55
|
+
(r"minimum supported rust version", "Rust version too old"),
|
|
56
|
+
]),
|
|
57
|
+
(FailureCategory.API_CHANGE, 0.85, [
|
|
58
|
+
(r"AttributeError:\s*'?(\w+)'?\s+object has no attribute '(\w+)'",
|
|
59
|
+
"{0} has no attribute '{1}'"),
|
|
60
|
+
(r"TypeError:\s*(\w+)\(\) (?:got an unexpected|missing \d+ required|takes \d+)",
|
|
61
|
+
"Wrong arguments for {0}()"),
|
|
62
|
+
(r"missing \d+ required (?:positional )?argument", "Missing required argument"),
|
|
63
|
+
(r"has no member named `(\w+)`", "No member: {0}"),
|
|
64
|
+
(r"no method named `(\w+)`", "No method: {0}"),
|
|
65
|
+
(r"is not a function", "Not a function"),
|
|
66
|
+
(r"is not defined", "Not defined"),
|
|
67
|
+
]),
|
|
68
|
+
(FailureCategory.LINT, 0.75, [
|
|
69
|
+
(r"flake8", "Flake8 lint error"),
|
|
70
|
+
(r"eslint", "ESLint error"),
|
|
71
|
+
(r"clippy", "Clippy warning"),
|
|
72
|
+
(r"warning\[(\w+)\]", "Compiler warning: {0}"),
|
|
73
|
+
(r"formatting.+differ", "Formatting difference"),
|
|
74
|
+
]),
|
|
75
|
+
(FailureCategory.FLAKY_TEST, 0.60, [
|
|
76
|
+
(r"timeout|timed?\s*out", "Test timed out"),
|
|
77
|
+
(r"flaky", "Flaky test"),
|
|
78
|
+
(r"intermittent", "Intermittent failure"),
|
|
79
|
+
(r"connection refused", "Connection refused"),
|
|
80
|
+
(r"ECONNRESET", "Connection reset"),
|
|
81
|
+
(r"Resource temporarily unavailable", "Resource unavailable"),
|
|
82
|
+
]),
|
|
83
|
+
(FailureCategory.ASSERTION, 0.85, [
|
|
84
|
+
(r"AssertionError:\s*assert\s+(.+)", "Assertion failed: {0}"),
|
|
85
|
+
(r"AssertionError:\s*(.+)", "Assertion: {0}"),
|
|
86
|
+
(r"assert\s+[\d.]+\s*==\s*[\d.]+", "Assertion: value mismatch"),
|
|
87
|
+
(r"assert\s+(.+?)\s*==\s*(.+)", "Assertion: {0} != {1}"),
|
|
88
|
+
(r"assert_eq!.+left:\s*`(.+?)`,\s*right:\s*`(.+?)`",
|
|
89
|
+
"assert_eq! left={0}, right={1}"),
|
|
90
|
+
(r"expect\(.+\)\.to(?:Equal|Be)\((.+?)\)", "Expected {0}"),
|
|
91
|
+
(r"Expected\s+(.+?)\s+to (?:equal|be)\s+(.+)", "Expected {1}, got {0}"),
|
|
92
|
+
(r"expected:\s*(.+?)\s+but was:\s*(.+)", "Expected {0}, got {1}"),
|
|
93
|
+
(r"!=\s", "Value mismatch"),
|
|
94
|
+
(r"AssertionError", "Assertion error"),
|
|
95
|
+
]),
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def classify(
|
|
100
|
+
failures: List[TestFailure],
|
|
101
|
+
ecosystem: Ecosystem,
|
|
102
|
+
) -> List[ClassifiedFailure]:
|
|
103
|
+
"""Classify each test failure by category."""
|
|
104
|
+
return [_classify_one(f) for f in failures]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _classify_one(failure: TestFailure) -> ClassifiedFailure:
|
|
108
|
+
"""Classify a single failure."""
|
|
109
|
+
text = f"{failure.error_message}\n{failure.full_output}"
|
|
110
|
+
|
|
111
|
+
for category, base_confidence, patterns in RULES:
|
|
112
|
+
for pattern, summary_template in patterns:
|
|
113
|
+
m = re.search(pattern, text, re.IGNORECASE)
|
|
114
|
+
if m:
|
|
115
|
+
try:
|
|
116
|
+
summary = summary_template.format(*m.groups())
|
|
117
|
+
except (IndexError, KeyError):
|
|
118
|
+
summary = summary_template
|
|
119
|
+
return ClassifiedFailure(
|
|
120
|
+
failure=failure,
|
|
121
|
+
category=category,
|
|
122
|
+
confidence=base_confidence,
|
|
123
|
+
summary=summary,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# No match
|
|
127
|
+
error_preview = failure.error_message[:80] if failure.error_message else "Unknown error"
|
|
128
|
+
return ClassifiedFailure(
|
|
129
|
+
failure=failure,
|
|
130
|
+
category=FailureCategory.UNKNOWN,
|
|
131
|
+
confidence=0.3,
|
|
132
|
+
summary=error_preview,
|
|
133
|
+
)
|