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.
@@ -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
+ [![PyPI version](https://img.shields.io/pypi/v/fixforward?color=brightgreen&label=PyPI)](https://pypi.org/project/fixforward/)
29
+ [![Python](https://img.shields.io/pypi/pyversions/fixforward?color=blue)](https://pypi.org/project/fixforward/)
30
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ <!-- ![fixforward demo](screenshots/demo.gif) -->
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
+ [![PyPI version](https://img.shields.io/pypi/v/fixforward?color=brightgreen&label=PyPI)](https://pypi.org/project/fixforward/)
8
+ [![Python](https://img.shields.io/pypi/pyversions/fixforward?color=blue)](https://pypi.org/project/fixforward/)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ <!-- ![fixforward demo](screenshots/demo.gif) -->
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,3 @@
1
+ """FixForward: incident-to-PR autopilot powered by GitHub Copilot CLI."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ """Allow running as python -m fixforward."""
2
+
3
+ from fixforward.cli import main
4
+
5
+ main()
@@ -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
+ )