etch-loop 0.5.4__tar.gz → 0.6.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.
- etch_loop-0.6.0/.gitignore +1 -0
- etch_loop-0.6.0/PKG-INFO +138 -0
- etch_loop-0.6.0/README.md +126 -0
- {etch_loop-0.5.4 → etch_loop-0.6.0}/pyproject.toml +1 -1
- etch_loop-0.6.0/src/etch/__init__.py +1 -0
- {etch_loop-0.5.4 → etch_loop-0.6.0}/src/etch/agent.py +19 -7
- {etch_loop-0.5.4 → etch_loop-0.6.0}/src/etch/analyze.py +4 -3
- {etch_loop-0.5.4 → etch_loop-0.6.0}/src/etch/cli.py +7 -0
- {etch_loop-0.5.4 → etch_loop-0.6.0}/src/etch/loop.py +35 -0
- {etch_loop-0.5.4 → etch_loop-0.6.0}/src/etch/prompt.py +5 -5
- {etch_loop-0.5.4 → etch_loop-0.6.0}/src/etch/templates/BREAK.md +1 -1
- {etch_loop-0.5.4 → etch_loop-0.6.0}/src/etch/templates/ETCH.md +1 -1
- {etch_loop-0.5.4 → etch_loop-0.6.0}/src/etch/templates/SCAN.md +1 -1
- {etch_loop-0.5.4 → etch_loop-0.6.0}/uv.lock +1 -1
- etch_loop-0.5.4/PKG-INFO +0 -117
- etch_loop-0.5.4/README.md +0 -105
- etch_loop-0.5.4/etch-loop/BREAK.md +0 -41
- etch_loop-0.5.4/etch-loop/ETCH.md +0 -40
- etch_loop-0.5.4/etch-loop/RUN.md +0 -36
- etch_loop-0.5.4/etch-loop/SCAN.md +0 -45
- etch_loop-0.5.4/src/etch/__init__.py +0 -1
- {etch_loop-0.5.4 → etch_loop-0.6.0}/.github/workflows/workflow.yml +0 -0
- {etch_loop-0.5.4 → etch_loop-0.6.0}/src/etch/display.py +0 -0
- {etch_loop-0.5.4 → etch_loop-0.6.0}/src/etch/git.py +0 -0
- {etch_loop-0.5.4 → etch_loop-0.6.0}/src/etch/report.py +0 -0
- {etch_loop-0.5.4 → etch_loop-0.6.0}/src/etch/signals.py +0 -0
- {etch_loop-0.5.4 → etch_loop-0.6.0}/src/etch/templates/RUN.md +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
etch-loop/
|
etch_loop-0.6.0/PKG-INFO
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: etch-loop
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Run Claude Code in a fix-break loop until your codebase is clean
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: rich
|
|
8
|
+
Requires-Dist: typer
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
███████╗████████╗ ██████╗██╗ ██╗
|
|
15
|
+
██╔════╝╚══██╔══╝██╔════╝██║ ██║
|
|
16
|
+
█████╗ ██║ ██║ ███████║
|
|
17
|
+
██╔══╝ ██║ ██║ ██╔══██║
|
|
18
|
+
███████╗ ██║ ╚██████╗██║ ██║
|
|
19
|
+
╚══════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ loop
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
> Run Claude Code in a scan-fix-break loop until your codebase is clean.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
╭───────────────────────── etch loop v0.5.5 my-project ─────────────────────────╮
|
|
28
|
+
│ - iteration 1 │
|
|
29
|
+
│ x scanner issues found 3 bugs — null deref auth.py:42, off-by-one... │
|
|
30
|
+
│ + fixer committed fixed 3 issues — null guard in auth.py, bounds... │
|
|
31
|
+
│ x breaker issues unguarded access still reachable in session.py │
|
|
32
|
+
│ - iteration 2 │
|
|
33
|
+
│ + scanner all clear no confirmed bugs found │
|
|
34
|
+
│ + runner all clear wrote 4 tests, all 31 passed │
|
|
35
|
+
╰───────────────── iterations 2 fixes 1 breaker issues 1 3m 12s elapsed ───╯
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uv tool install etch-loop
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or with pip:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install etch-loop
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## usage
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
etch init # analyze codebase, write prompt files to etch-loop/
|
|
56
|
+
etch run # start the loop
|
|
57
|
+
etch run "the auth module" # focus on a specific area
|
|
58
|
+
etch run -n 5 # max 5 iterations
|
|
59
|
+
etch run --no-commit # fix without committing
|
|
60
|
+
etch run --no-git # disable all git operations
|
|
61
|
+
etch run --dry-run # preview prompt, don't run
|
|
62
|
+
etch run --verbose # stream full Claude output
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## how it works
|
|
68
|
+
|
|
69
|
+
Each iteration runs four phases: **scan → fix → break**, then once everything is clean: **run**.
|
|
70
|
+
|
|
71
|
+
### 1. Scanner
|
|
72
|
+
Reads the codebase and produces a precise list of confirmed bugs — file paths, line numbers, one-line descriptions. Only genuine issues, no style notes.
|
|
73
|
+
|
|
74
|
+
### 2. Fixer
|
|
75
|
+
Receives the scanner's list and fixes exactly those issues. Commits each fix with a summary message. Does not refactor or touch code unrelated to the reported bugs.
|
|
76
|
+
|
|
77
|
+
### 3. Breaker
|
|
78
|
+
Adversarially reviews only the files the fixer just changed, looking for anything introduced or missed. If it finds nothing, the loop stops clean. If it finds issues, the next iteration's scanner re-checks those specific spots to confirm what's actually still broken.
|
|
79
|
+
|
|
80
|
+
### 4. Runner *(final step)*
|
|
81
|
+
Runs only when the loop exits cleanly. Writes targeted tests for what was changed, runs the full test suite, then deletes the test files it created. Reports pass/fail.
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
loop exits clean
|
|
85
|
+
│
|
|
86
|
+
▼
|
|
87
|
+
[ runner ] → writes tests → runs suite → cleans up → ETCH_ALL_CLEAR
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Each agent writes a short `<etch_summary>` that appears directly in the terminal dashboard. The fixer's summary doubles as the git commit message.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## etch init
|
|
95
|
+
|
|
96
|
+
`etch init` runs Claude against your codebase, detects languages and structure, then writes four prompt files tailored to your project into an `etch-loop/` subfolder. No placeholders to fill in.
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
╭─ etch init v0.5.5 ────────────────────────────────╮
|
|
100
|
+
│ > analyzing ░░░░░▓▒ ░░░░░░░░░░░░░░░░░░░░░░░░ │
|
|
101
|
+
│ + analyzed codebase │
|
|
102
|
+
│ + etch-loop/SCAN.md │
|
|
103
|
+
│ + etch-loop/ETCH.md │
|
|
104
|
+
│ + etch-loop/BREAK.md │
|
|
105
|
+
│ + etch-loop/RUN.md │
|
|
106
|
+
╰────────────────────────────────────────────────────╯
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
| File | Purpose |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `etch-loop/SCAN.md` | Scanner prompt — what to look for and how to report findings |
|
|
112
|
+
| `etch-loop/ETCH.md` | Fixer prompt — surgical fixes only, no refactoring |
|
|
113
|
+
| `etch-loop/BREAK.md` | Breaker prompt — adversarial review of changed files |
|
|
114
|
+
| `etch-loop/RUN.md` | Runner prompt — write tests, run suite, clean up |
|
|
115
|
+
|
|
116
|
+
All four files are editable. The `etch-loop/` directory is excluded from analysis so etch never reads its own files as part of your codebase.
|
|
117
|
+
|
|
118
|
+
Run reports are saved to `etch-loop/etch-reports/` after each run.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## reports
|
|
123
|
+
|
|
124
|
+
After every run, a markdown report is saved to `etch-loop/etch-reports/`:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
etch-loop/etch-reports/etch-report-2026-03-10-15-31.md
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
It contains the full iteration log — what each phase found, what was fixed, and runner results.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## requirements
|
|
135
|
+
|
|
136
|
+
- Python 3.11+
|
|
137
|
+
- [`claude`](https://claude.ai/code) CLI installed and authenticated
|
|
138
|
+
- A git repository (optional — use `--no-git` to skip all git operations)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
```
|
|
2
|
+
███████╗████████╗ ██████╗██╗ ██╗
|
|
3
|
+
██╔════╝╚══██╔══╝██╔════╝██║ ██║
|
|
4
|
+
█████╗ ██║ ██║ ███████║
|
|
5
|
+
██╔══╝ ██║ ██║ ██╔══██║
|
|
6
|
+
███████╗ ██║ ╚██████╗██║ ██║
|
|
7
|
+
╚══════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ loop
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
> Run Claude Code in a scan-fix-break loop until your codebase is clean.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
╭───────────────────────── etch loop v0.5.5 my-project ─────────────────────────╮
|
|
16
|
+
│ - iteration 1 │
|
|
17
|
+
│ x scanner issues found 3 bugs — null deref auth.py:42, off-by-one... │
|
|
18
|
+
│ + fixer committed fixed 3 issues — null guard in auth.py, bounds... │
|
|
19
|
+
│ x breaker issues unguarded access still reachable in session.py │
|
|
20
|
+
│ - iteration 2 │
|
|
21
|
+
│ + scanner all clear no confirmed bugs found │
|
|
22
|
+
│ + runner all clear wrote 4 tests, all 31 passed │
|
|
23
|
+
╰───────────────── iterations 2 fixes 1 breaker issues 1 3m 12s elapsed ───╯
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uv tool install etch-loop
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or with pip:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install etch-loop
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## usage
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
etch init # analyze codebase, write prompt files to etch-loop/
|
|
44
|
+
etch run # start the loop
|
|
45
|
+
etch run "the auth module" # focus on a specific area
|
|
46
|
+
etch run -n 5 # max 5 iterations
|
|
47
|
+
etch run --no-commit # fix without committing
|
|
48
|
+
etch run --no-git # disable all git operations
|
|
49
|
+
etch run --dry-run # preview prompt, don't run
|
|
50
|
+
etch run --verbose # stream full Claude output
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## how it works
|
|
56
|
+
|
|
57
|
+
Each iteration runs four phases: **scan → fix → break**, then once everything is clean: **run**.
|
|
58
|
+
|
|
59
|
+
### 1. Scanner
|
|
60
|
+
Reads the codebase and produces a precise list of confirmed bugs — file paths, line numbers, one-line descriptions. Only genuine issues, no style notes.
|
|
61
|
+
|
|
62
|
+
### 2. Fixer
|
|
63
|
+
Receives the scanner's list and fixes exactly those issues. Commits each fix with a summary message. Does not refactor or touch code unrelated to the reported bugs.
|
|
64
|
+
|
|
65
|
+
### 3. Breaker
|
|
66
|
+
Adversarially reviews only the files the fixer just changed, looking for anything introduced or missed. If it finds nothing, the loop stops clean. If it finds issues, the next iteration's scanner re-checks those specific spots to confirm what's actually still broken.
|
|
67
|
+
|
|
68
|
+
### 4. Runner *(final step)*
|
|
69
|
+
Runs only when the loop exits cleanly. Writes targeted tests for what was changed, runs the full test suite, then deletes the test files it created. Reports pass/fail.
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
loop exits clean
|
|
73
|
+
│
|
|
74
|
+
▼
|
|
75
|
+
[ runner ] → writes tests → runs suite → cleans up → ETCH_ALL_CLEAR
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Each agent writes a short `<etch_summary>` that appears directly in the terminal dashboard. The fixer's summary doubles as the git commit message.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## etch init
|
|
83
|
+
|
|
84
|
+
`etch init` runs Claude against your codebase, detects languages and structure, then writes four prompt files tailored to your project into an `etch-loop/` subfolder. No placeholders to fill in.
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
╭─ etch init v0.5.5 ────────────────────────────────╮
|
|
88
|
+
│ > analyzing ░░░░░▓▒ ░░░░░░░░░░░░░░░░░░░░░░░░ │
|
|
89
|
+
│ + analyzed codebase │
|
|
90
|
+
│ + etch-loop/SCAN.md │
|
|
91
|
+
│ + etch-loop/ETCH.md │
|
|
92
|
+
│ + etch-loop/BREAK.md │
|
|
93
|
+
│ + etch-loop/RUN.md │
|
|
94
|
+
╰────────────────────────────────────────────────────╯
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
| File | Purpose |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `etch-loop/SCAN.md` | Scanner prompt — what to look for and how to report findings |
|
|
100
|
+
| `etch-loop/ETCH.md` | Fixer prompt — surgical fixes only, no refactoring |
|
|
101
|
+
| `etch-loop/BREAK.md` | Breaker prompt — adversarial review of changed files |
|
|
102
|
+
| `etch-loop/RUN.md` | Runner prompt — write tests, run suite, clean up |
|
|
103
|
+
|
|
104
|
+
All four files are editable. The `etch-loop/` directory is excluded from analysis so etch never reads its own files as part of your codebase.
|
|
105
|
+
|
|
106
|
+
Run reports are saved to `etch-loop/etch-reports/` after each run.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## reports
|
|
111
|
+
|
|
112
|
+
After every run, a markdown report is saved to `etch-loop/etch-reports/`:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
etch-loop/etch-reports/etch-report-2026-03-10-15-31.md
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
It contains the full iteration log — what each phase found, what was fixed, and runner results.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## requirements
|
|
123
|
+
|
|
124
|
+
- Python 3.11+
|
|
125
|
+
- [`claude`](https://claude.ai/code) CLI installed and authenticated
|
|
126
|
+
- A git repository (optional — use `--no-git` to skip all git operations)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.0"
|
|
@@ -59,7 +59,7 @@ def run(
|
|
|
59
59
|
try:
|
|
60
60
|
process.stdin.write(prompt)
|
|
61
61
|
process.stdin.close()
|
|
62
|
-
except OSError as exc:
|
|
62
|
+
except (OSError, ValueError) as exc:
|
|
63
63
|
stdin_exc.append(exc)
|
|
64
64
|
|
|
65
65
|
stdin_writer = threading.Thread(target=write_stdin, daemon=True)
|
|
@@ -67,18 +67,24 @@ def run(
|
|
|
67
67
|
stdin_writer.join(timeout=30)
|
|
68
68
|
if stdin_writer.is_alive():
|
|
69
69
|
process.kill()
|
|
70
|
-
|
|
70
|
+
try:
|
|
71
|
+
process.wait(timeout=10)
|
|
72
|
+
except subprocess.TimeoutExpired:
|
|
73
|
+
pass
|
|
71
74
|
try:
|
|
72
75
|
process.stdin.close()
|
|
73
|
-
except OSError:
|
|
76
|
+
except (OSError, ValueError):
|
|
74
77
|
pass
|
|
75
78
|
raise AgentError("Timed out writing prompt to claude stdin")
|
|
76
79
|
if stdin_exc:
|
|
77
80
|
process.kill()
|
|
78
|
-
|
|
81
|
+
try:
|
|
82
|
+
process.wait(timeout=10)
|
|
83
|
+
except subprocess.TimeoutExpired:
|
|
84
|
+
pass
|
|
79
85
|
try:
|
|
80
86
|
process.stdin.close()
|
|
81
|
-
except OSError:
|
|
87
|
+
except (OSError, ValueError):
|
|
82
88
|
pass
|
|
83
89
|
raise AgentError(f"Failed to write prompt to claude stdin: {stdin_exc[0]}") from stdin_exc[0]
|
|
84
90
|
|
|
@@ -107,7 +113,10 @@ def run(
|
|
|
107
113
|
reader.join(timeout=timeout)
|
|
108
114
|
if reader.is_alive():
|
|
109
115
|
process.kill()
|
|
110
|
-
|
|
116
|
+
try:
|
|
117
|
+
process.wait(timeout=5)
|
|
118
|
+
except subprocess.TimeoutExpired:
|
|
119
|
+
pass
|
|
111
120
|
stderr_reader.join(timeout=10)
|
|
112
121
|
if stderr_reader.is_alive():
|
|
113
122
|
with lock:
|
|
@@ -118,7 +127,10 @@ def run(
|
|
|
118
127
|
process.wait(timeout=10)
|
|
119
128
|
except subprocess.TimeoutExpired:
|
|
120
129
|
process.kill()
|
|
121
|
-
|
|
130
|
+
try:
|
|
131
|
+
process.wait(timeout=5)
|
|
132
|
+
except subprocess.TimeoutExpired:
|
|
133
|
+
pass
|
|
122
134
|
raise AgentError("claude subprocess timed out waiting for exit")
|
|
123
135
|
|
|
124
136
|
stderr_reader.join(timeout=10)
|
|
@@ -50,6 +50,7 @@ _FRAMEWORK_HINTS: dict[str, str] = {
|
|
|
50
50
|
_SKIP_DIRS = {
|
|
51
51
|
".git", "node_modules", "__pycache__", ".venv", "venv", "env",
|
|
52
52
|
"dist", "build", ".next", "target", "vendor", ".cache",
|
|
53
|
+
"etch-loop", # etch metadata — never part of the codebase being analyzed
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
|
|
@@ -173,7 +174,7 @@ For each issue, include the file path, line number (if known), and a one-line de
|
|
|
173
174
|
## Rules
|
|
174
175
|
|
|
175
176
|
1. DO NOT edit any files — read only
|
|
176
|
-
**
|
|
177
|
+
**DO NOT read any file inside `etch-loop/`** — those are etch tool config files, not your codebase
|
|
177
178
|
2. Only report issues you are confident are genuine bugs — not observations, not style, not "could be cleaner"
|
|
178
179
|
3. If something is already handled correctly, do NOT report it — even if the handling is unusual
|
|
179
180
|
4. If you are unsure whether something is a bug, leave it out
|
|
@@ -214,7 +215,7 @@ Scan the codebase for:
|
|
|
214
215
|
|
|
215
216
|
## Rules
|
|
216
217
|
|
|
217
|
-
0. **
|
|
218
|
+
0. **DO NOT read or edit any file inside `etch-loop/`** — those are etch tool config files. Editing them corrupts the tool and is never a valid fix.
|
|
218
219
|
1. Fix only what you find — do not refactor, rename, or reorganize
|
|
219
220
|
2. Do not add comments explaining what you fixed
|
|
220
221
|
3. If you find nothing, make no changes
|
|
@@ -257,7 +258,7 @@ Be adversarial — think like someone actively trying to make this code fail.
|
|
|
257
258
|
## Rules
|
|
258
259
|
|
|
259
260
|
1. DO NOT edit any files — read only
|
|
260
|
-
**
|
|
261
|
+
**DO NOT read any file inside `etch-loop/`** — those are etch tool config files, not your codebase
|
|
261
262
|
2. Report your findings clearly, one per line
|
|
262
263
|
3. Before the signal token, write your summary in this exact format — it appears directly in the terminal:
|
|
263
264
|
`<etch_summary>2 issues — unguarded empty list in sorter.py:14, exception swallowed in loader.py:67</etch_summary>`
|
|
@@ -93,6 +93,12 @@ def run(
|
|
|
93
93
|
help="Stream agent output to the terminal.",
|
|
94
94
|
is_flag=True,
|
|
95
95
|
),
|
|
96
|
+
user: bool = typer.Option(
|
|
97
|
+
False,
|
|
98
|
+
"--user",
|
|
99
|
+
help="Add a user-perspective lens: scanner and breaker also look for realistic user inputs and sequences that the code doesn't handle.",
|
|
100
|
+
is_flag=True,
|
|
101
|
+
),
|
|
96
102
|
) -> None:
|
|
97
103
|
"""Run the fix-break loop against the current repository.
|
|
98
104
|
|
|
@@ -111,6 +117,7 @@ def run(
|
|
|
111
117
|
dry_run=dry_run,
|
|
112
118
|
verbose=verbose,
|
|
113
119
|
focus=focus,
|
|
120
|
+
user=user,
|
|
114
121
|
)
|
|
115
122
|
except KeyboardInterrupt:
|
|
116
123
|
display.print_interrupted()
|
|
@@ -11,6 +11,25 @@ from etch.git import GitError
|
|
|
11
11
|
from etch.prompt import PromptError, load_scan
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
_USER_PERSPECTIVE = """
|
|
15
|
+
## User perspective (additional lens)
|
|
16
|
+
|
|
17
|
+
Also think about this code from the perspective of a real end user interacting with it.
|
|
18
|
+
What inputs, sequences, or behaviors would a realistic user trigger that the code doesn't handle?
|
|
19
|
+
|
|
20
|
+
Look for:
|
|
21
|
+
- Empty, whitespace-only, or missing inputs where the code assumes content
|
|
22
|
+
- Inputs at the edges of what the interface allows (very long strings, zero, negative numbers)
|
|
23
|
+
- Unexpected but valid orderings of operations (calling things out of sequence)
|
|
24
|
+
- Malformed or non-UTF-8 data coming from files, network, or user input
|
|
25
|
+
- Concurrent use (two users/processes doing the same thing simultaneously)
|
|
26
|
+
- Users who skip optional steps, then trigger code that assumed they ran
|
|
27
|
+
- Realistic typos or near-valid inputs that bypass validation
|
|
28
|
+
|
|
29
|
+
Report only cases where the code would actually fail or behave incorrectly — not hypothetical misuse.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
14
33
|
def run(
|
|
15
34
|
prompt_path: str | Path,
|
|
16
35
|
max_iterations: int = 20,
|
|
@@ -19,6 +38,7 @@ def run(
|
|
|
19
38
|
dry_run: bool = False,
|
|
20
39
|
verbose: bool = False,
|
|
21
40
|
focus: str | None = None,
|
|
41
|
+
user: bool = False,
|
|
22
42
|
) -> None:
|
|
23
43
|
"""Run the scan-fix-break loop."""
|
|
24
44
|
prompt_path = Path(prompt_path)
|
|
@@ -60,6 +80,10 @@ def run(
|
|
|
60
80
|
scan_text += f"\n\n## User focus\n\nConcentrate on: {focus}\n"
|
|
61
81
|
break_text += f"\n\n## User focus\n\nConcentrate your adversarial review on: {focus}\n"
|
|
62
82
|
|
|
83
|
+
if user:
|
|
84
|
+
scan_text += _USER_PERSPECTIVE
|
|
85
|
+
break_text += _USER_PERSPECTIVE
|
|
86
|
+
|
|
63
87
|
start_time = time.monotonic()
|
|
64
88
|
stats: dict = {
|
|
65
89
|
"iterations": 0,
|
|
@@ -178,6 +202,17 @@ def run(
|
|
|
178
202
|
|
|
179
203
|
# ── Commit ────────────────────────────────────────────────────────
|
|
180
204
|
if no_git or changed:
|
|
205
|
+
_raw_summary = (
|
|
206
|
+
signals.extract_summary(_fixer_output)
|
|
207
|
+
or signals.extract_commit_message(_fixer_output, fallback="")
|
|
208
|
+
)
|
|
209
|
+
if no_git and (not _raw_summary or _raw_summary.lower().startswith("nothing")):
|
|
210
|
+
disp.finish_phase("fixer", status="no changes", detail="nothing to fix",
|
|
211
|
+
duration=fixer_duration, success=True)
|
|
212
|
+
iter_entry["fixer"] = {"status": "no changes", "detail": "nothing to fix"}
|
|
213
|
+
stats["reason"] = "stalled" if last_breaker_signal == "issues" else "no_changes"
|
|
214
|
+
iteration_log.append(iter_entry)
|
|
215
|
+
break
|
|
181
216
|
fixer_summary = (
|
|
182
217
|
signals.extract_summary(_fixer_output)
|
|
183
218
|
or signals.extract_commit_message(_fixer_output, fallback="")
|
|
@@ -27,8 +27,8 @@ def load(path: str | Path) -> str:
|
|
|
27
27
|
|
|
28
28
|
try:
|
|
29
29
|
content = p.read_text(encoding="utf-8")
|
|
30
|
-
except
|
|
31
|
-
raise PromptError(f"
|
|
30
|
+
except OSError as e:
|
|
31
|
+
raise PromptError(f"Cannot read prompt file: {p}: {e}") from e
|
|
32
32
|
if not content.strip():
|
|
33
33
|
raise PromptError(f"Prompt file is empty: {p}")
|
|
34
34
|
|
|
@@ -71,7 +71,7 @@ def load_break(path: str | Path | None = None) -> str:
|
|
|
71
71
|
if candidate.exists() and candidate.is_file():
|
|
72
72
|
try:
|
|
73
73
|
content = candidate.read_text(encoding="utf-8")
|
|
74
|
-
except
|
|
74
|
+
except OSError:
|
|
75
75
|
continue
|
|
76
76
|
if not content.strip():
|
|
77
77
|
raise PromptError(f"BREAK.md is empty: {candidate}")
|
|
@@ -105,7 +105,7 @@ def load_run(path: str | Path | None = None) -> str | None:
|
|
|
105
105
|
if candidate.exists() and candidate.is_file():
|
|
106
106
|
try:
|
|
107
107
|
content = candidate.read_text(encoding="utf-8")
|
|
108
|
-
except
|
|
108
|
+
except OSError:
|
|
109
109
|
continue
|
|
110
110
|
if not content.strip():
|
|
111
111
|
raise PromptError(f"RUN.md is empty: {candidate}")
|
|
@@ -137,7 +137,7 @@ def load_scan(path: str | Path | None = None) -> str:
|
|
|
137
137
|
if candidate.exists() and candidate.is_file():
|
|
138
138
|
try:
|
|
139
139
|
content = candidate.read_text(encoding="utf-8")
|
|
140
|
-
except
|
|
140
|
+
except OSError:
|
|
141
141
|
continue
|
|
142
142
|
if not content.strip():
|
|
143
143
|
raise PromptError(f"SCAN.md is empty: {candidate}")
|
|
@@ -18,7 +18,7 @@ Be adversarial — think like someone actively trying to make this code fail.
|
|
|
18
18
|
## Rules
|
|
19
19
|
|
|
20
20
|
1. DO NOT edit any files — read only
|
|
21
|
-
**
|
|
21
|
+
**DO NOT read any file inside `etch-loop/`** — those are etch tool config files, not your codebase
|
|
22
22
|
2. Report your findings clearly, one per line
|
|
23
23
|
3. Before the signal token, write your summary in this exact format — it appears directly in the terminal:
|
|
24
24
|
`<etch_summary>2 issues — unguarded empty list in sorter.py:14, exception swallowed in loader.py:67</etch_summary>`
|
|
@@ -14,7 +14,7 @@ Scan the codebase for:
|
|
|
14
14
|
|
|
15
15
|
## Rules
|
|
16
16
|
|
|
17
|
-
0. **
|
|
17
|
+
0. **DO NOT read or edit any file inside `etch-loop/`** — those are etch tool config files. Editing them corrupts the tool and is never a valid fix.
|
|
18
18
|
1. Fix only what you find — do not refactor, rename, or reorganize
|
|
19
19
|
2. Do not add comments explaining what you fixed
|
|
20
20
|
3. If you find nothing, make no changes
|
|
@@ -17,7 +17,7 @@ For each issue, include the file path, line number (if known), and a one-line de
|
|
|
17
17
|
## Rules
|
|
18
18
|
|
|
19
19
|
1. DO NOT edit any files — read only
|
|
20
|
-
**
|
|
20
|
+
**DO NOT read any file inside `etch-loop/`** — those are etch tool config files, not your codebase
|
|
21
21
|
2. Only report issues you are confident are genuine bugs — not observations, not style, not "could be cleaner"
|
|
22
22
|
3. If something is already handled correctly, do NOT report it — even if the handling is unusual
|
|
23
23
|
4. If you are unsure whether something is a bug, leave it out
|
etch_loop-0.5.4/PKG-INFO
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: etch-loop
|
|
3
|
-
Version: 0.5.4
|
|
4
|
-
Summary: Run Claude Code in a fix-break loop until your codebase is clean
|
|
5
|
-
License: MIT
|
|
6
|
-
Requires-Python: >=3.11
|
|
7
|
-
Requires-Dist: rich
|
|
8
|
-
Requires-Dist: typer
|
|
9
|
-
Provides-Extra: dev
|
|
10
|
-
Requires-Dist: pytest; extra == 'dev'
|
|
11
|
-
Description-Content-Type: text/markdown
|
|
12
|
-
|
|
13
|
-
```
|
|
14
|
-
███████╗████████╗ ██████╗██╗ ██╗
|
|
15
|
-
██╔════╝╚══██╔══╝██╔════╝██║ ██║
|
|
16
|
-
█████╗ ██║ ██║ ███████║
|
|
17
|
-
██╔══╝ ██║ ██║ ██╔══██║
|
|
18
|
-
███████╗ ██║ ╚██████╗██║ ██║
|
|
19
|
-
╚══════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ loop
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
> Run Claude Code in a scan-fix-break loop until your codebase is clean.
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
```
|
|
27
|
-
┌─ etch loop v0.2.0 . ───────────────────────────────────────────────┐
|
|
28
|
-
│ │
|
|
29
|
-
│ - iteration 1 │
|
|
30
|
-
│ + scanner issues found src/auth.py:42 — no empty token check │
|
|
31
|
-
│ + fixer committed fix(edge): guard empty token in auth │
|
|
32
|
-
│ x breaker issues unguarded access still reachable │
|
|
33
|
-
│ │
|
|
34
|
-
│ - iteration 2 │
|
|
35
|
-
│ + scanner issues found src/auth.py:61 — missing None check │
|
|
36
|
-
│ + fixer committed fix(edge): null guard on session obj │
|
|
37
|
-
│ > breaker running ░░░░░░▓▒ ░░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
|
38
|
-
│ │
|
|
39
|
-
├──────────────────────────────────────────────────────────────────────┤
|
|
40
|
-
│ iterations 2 fixes 2 breaker issues 1 1m 48s elapsed │
|
|
41
|
-
└──────────────────────────────────────────────────────────────────────┘
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## install
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
uv tool install etch-loop
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## usage
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
etch init # analyze codebase with Claude, write prompt files
|
|
56
|
-
etch run # start the loop
|
|
57
|
-
etch run "the auth module" # focus on a specific area
|
|
58
|
-
etch run -n 5 # max 5 iterations
|
|
59
|
-
etch run --dry-run # preview prompt, don't run
|
|
60
|
-
etch run --verbose # show full Claude output
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## how it works
|
|
66
|
-
|
|
67
|
-
Each iteration has three phases: **scan → fix → break**.
|
|
68
|
-
|
|
69
|
-
1. **Scanner** reads the codebase and outputs a specific list of issues — file paths, line numbers, descriptions
|
|
70
|
-
2. If the scanner finds nothing, the loop stops
|
|
71
|
-
3. **Fixer** receives the scanner's list and fixes those exact issues, then commits
|
|
72
|
-
4. **Breaker** adversarially reviews the full codebase, looking for anything missed or newly introduced
|
|
73
|
-
5. If the breaker finds nothing, the loop stops — clean pass
|
|
74
|
-
6. If the breaker finds something, it's fed back to the next iteration's fixer
|
|
75
|
-
|
|
76
|
-
```
|
|
77
|
-
┌─ done ───────────────────────────────────────────────────┐
|
|
78
|
-
│ │
|
|
79
|
-
│ iterations 3 │
|
|
80
|
-
│ fixes 3 │
|
|
81
|
-
│ breaker issues 1 │
|
|
82
|
-
│ elapsed 2m 44s │
|
|
83
|
-
│ │
|
|
84
|
-
└──────────────────────────────────────────────────────────┘
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## etch init
|
|
90
|
-
|
|
91
|
-
`etch init` runs Claude against your codebase before writing any files. It reads your source, detects the languages and structure, and generates three prompt files tailored to your project — no placeholders to edit.
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
-
┌─ etch init v0.2.0 ───────────────────────────────────────┐
|
|
95
|
-
│ > analyzing ░░░░░▓▒ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
|
96
|
-
│ + analyzed codebase │
|
|
97
|
-
│ + SCAN.md │
|
|
98
|
-
│ + ETCH.md │
|
|
99
|
-
│ + BREAK.md │
|
|
100
|
-
└──────────────────────────────────────────────────────────┘
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
**`SCAN.md`** — tells the scanner what to look for and how to report findings.
|
|
104
|
-
|
|
105
|
-
**`ETCH.md`** — tells the fixer how to fix things: surgical, no refactoring, one fix per commit.
|
|
106
|
-
|
|
107
|
-
**`BREAK.md`** — tells the breaker to scan the full codebase adversarially and report anything that could go wrong.
|
|
108
|
-
|
|
109
|
-
All three files are editable. Use `etch run "focus description"` to narrow the scope without editing files.
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## requirements
|
|
114
|
-
|
|
115
|
-
- Python 3.11+
|
|
116
|
-
- [`claude`](https://claude.ai/code) CLI installed and authenticated
|
|
117
|
-
- A git repository (etch-loop commits each fix automatically)
|
etch_loop-0.5.4/README.md
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
```
|
|
2
|
-
███████╗████████╗ ██████╗██╗ ██╗
|
|
3
|
-
██╔════╝╚══██╔══╝██╔════╝██║ ██║
|
|
4
|
-
█████╗ ██║ ██║ ███████║
|
|
5
|
-
██╔══╝ ██║ ██║ ██╔══██║
|
|
6
|
-
███████╗ ██║ ╚██████╗██║ ██║
|
|
7
|
-
╚══════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ loop
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
> Run Claude Code in a scan-fix-break loop until your codebase is clean.
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
┌─ etch loop v0.2.0 . ───────────────────────────────────────────────┐
|
|
16
|
-
│ │
|
|
17
|
-
│ - iteration 1 │
|
|
18
|
-
│ + scanner issues found src/auth.py:42 — no empty token check │
|
|
19
|
-
│ + fixer committed fix(edge): guard empty token in auth │
|
|
20
|
-
│ x breaker issues unguarded access still reachable │
|
|
21
|
-
│ │
|
|
22
|
-
│ - iteration 2 │
|
|
23
|
-
│ + scanner issues found src/auth.py:61 — missing None check │
|
|
24
|
-
│ + fixer committed fix(edge): null guard on session obj │
|
|
25
|
-
│ > breaker running ░░░░░░▓▒ ░░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
|
26
|
-
│ │
|
|
27
|
-
├──────────────────────────────────────────────────────────────────────┤
|
|
28
|
-
│ iterations 2 fixes 2 breaker issues 1 1m 48s elapsed │
|
|
29
|
-
└──────────────────────────────────────────────────────────────────────┘
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## install
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
uv tool install etch-loop
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## usage
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
etch init # analyze codebase with Claude, write prompt files
|
|
44
|
-
etch run # start the loop
|
|
45
|
-
etch run "the auth module" # focus on a specific area
|
|
46
|
-
etch run -n 5 # max 5 iterations
|
|
47
|
-
etch run --dry-run # preview prompt, don't run
|
|
48
|
-
etch run --verbose # show full Claude output
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## how it works
|
|
54
|
-
|
|
55
|
-
Each iteration has three phases: **scan → fix → break**.
|
|
56
|
-
|
|
57
|
-
1. **Scanner** reads the codebase and outputs a specific list of issues — file paths, line numbers, descriptions
|
|
58
|
-
2. If the scanner finds nothing, the loop stops
|
|
59
|
-
3. **Fixer** receives the scanner's list and fixes those exact issues, then commits
|
|
60
|
-
4. **Breaker** adversarially reviews the full codebase, looking for anything missed or newly introduced
|
|
61
|
-
5. If the breaker finds nothing, the loop stops — clean pass
|
|
62
|
-
6. If the breaker finds something, it's fed back to the next iteration's fixer
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
┌─ done ───────────────────────────────────────────────────┐
|
|
66
|
-
│ │
|
|
67
|
-
│ iterations 3 │
|
|
68
|
-
│ fixes 3 │
|
|
69
|
-
│ breaker issues 1 │
|
|
70
|
-
│ elapsed 2m 44s │
|
|
71
|
-
│ │
|
|
72
|
-
└──────────────────────────────────────────────────────────┘
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
## etch init
|
|
78
|
-
|
|
79
|
-
`etch init` runs Claude against your codebase before writing any files. It reads your source, detects the languages and structure, and generates three prompt files tailored to your project — no placeholders to edit.
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
┌─ etch init v0.2.0 ───────────────────────────────────────┐
|
|
83
|
-
│ > analyzing ░░░░░▓▒ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
|
|
84
|
-
│ + analyzed codebase │
|
|
85
|
-
│ + SCAN.md │
|
|
86
|
-
│ + ETCH.md │
|
|
87
|
-
│ + BREAK.md │
|
|
88
|
-
└──────────────────────────────────────────────────────────┘
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
**`SCAN.md`** — tells the scanner what to look for and how to report findings.
|
|
92
|
-
|
|
93
|
-
**`ETCH.md`** — tells the fixer how to fix things: surgical, no refactoring, one fix per commit.
|
|
94
|
-
|
|
95
|
-
**`BREAK.md`** — tells the breaker to scan the full codebase adversarially and report anything that could go wrong.
|
|
96
|
-
|
|
97
|
-
All three files are editable. Use `etch run "focus description"` to narrow the scope without editing files.
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## requirements
|
|
102
|
-
|
|
103
|
-
- Python 3.11+
|
|
104
|
-
- [`claude`](https://claude.ai/code) CLI installed and authenticated
|
|
105
|
-
- A git repository (etch-loop commits each fix automatically)
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# BREAK — breaker prompt
|
|
2
|
-
|
|
3
|
-
You are an adversarial code reviewer. Your job is to find anything that could go wrong.
|
|
4
|
-
|
|
5
|
-
## Your mission
|
|
6
|
-
|
|
7
|
-
Scan the entire codebase with fresh eyes. Do not limit yourself to recent changes.
|
|
8
|
-
|
|
9
|
-
Look for:
|
|
10
|
-
- Edge cases and boundary conditions that are unhandled anywhere in the code
|
|
11
|
-
- Functions that assume valid input without checking
|
|
12
|
-
- Error paths that are silently swallowed or ignored
|
|
13
|
-
- Race conditions, off-by-one errors, null/empty/zero not guarded
|
|
14
|
-
- Anything that would cause unexpected behavior in production
|
|
15
|
-
|
|
16
|
-
Be adversarial — think like someone actively trying to make this code fail.
|
|
17
|
-
|
|
18
|
-
## Rules
|
|
19
|
-
|
|
20
|
-
1. DO NOT edit any files — read only
|
|
21
|
-
**IGNORE the `etch-loop/` directory entirely** — it contains etch tool metadata, not production code
|
|
22
|
-
2. Report your findings clearly, one per line
|
|
23
|
-
3. Before the signal token, write your summary in this exact format — it appears directly in the terminal:
|
|
24
|
-
`<etch_summary>2 issues — unguarded empty list in sorter.py:14, exception swallowed in loader.py:67</etch_summary>`
|
|
25
|
-
`<etch_summary>no issues found — code looks solid</etch_summary>`
|
|
26
|
-
**IMPORTANT: write `<etch_summary>` ONLY in your text response — never inside any file you read or edit.**
|
|
27
|
-
4. End with EXACTLY one of these on its own line:
|
|
28
|
-
`ETCH_ISSUES_FOUND`
|
|
29
|
-
`ETCH_ALL_CLEAR`
|
|
30
|
-
|
|
31
|
-
## Scope
|
|
32
|
-
|
|
33
|
-
- `loop.py`: The no_git/no_commit branch logic at line 181 (`if no_git or changed`) can reach the commit block even when `changed` is False if `no_git=True`, silently skipping the `git.has_changes()` call entirely. The `last_breaker_signal` fall-through at line 174 (fixer sees "no changes" but breaker had prior issues) proceeds to breaker without committing — the iteration state machine has several interacting flags that can produce silent no-ops.
|
|
34
|
-
|
|
35
|
-
- `agent.py`: The `stderr_reader` thread is joined with a 10-second timeout but its aliveness is never checked; a hung stderr drain can silently drop error output. If `process.kill()` is called after the stdout reader times out, `process.wait()` is called but `stderr_reader` may still be running, causing a race on `stderr_lines`.
|
|
36
|
-
|
|
37
|
-
- `signals.py` / `extract_commit_message`: The heuristic line-picker can return a token string (e.g. `ETCH_ISSUES_FOUND`) as a commit message if the token appears after other text rather than on its own line, since `parse()` requires exact-line match but `extract_commit_message` does not exclude token lines.
|
|
38
|
-
|
|
39
|
-
- `git.py` / `has_changes`: Exit code 128 (no commits yet) falls through to `git status --porcelain`, which correctly handles new repos, but `git diff --quiet HEAD` on an empty repo also silently ignores any staged changes — newly staged files in a repo with no HEAD commit would still register as changes, so the two-step detection logic is correct but fragile.
|
|
40
|
-
|
|
41
|
-
- `prompt.py`: `load_break` and `load_scan` deduplicate the cwd candidate only when `path` is already cwd — if `path.parent == cwd`, both the sibling candidate and the cwd fallback point to the same file, causing it to be read twice in the loop (harmless but surprising). More critically, `load_run` returns `None` for an empty `RUN.md` rather than raising, silently skipping the runner phase with no feedback.
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# ETCH — fixer prompt
|
|
2
|
-
|
|
3
|
-
You are a surgical code reviewer focused on edge cases and robustness.
|
|
4
|
-
|
|
5
|
-
## Your mission
|
|
6
|
-
|
|
7
|
-
Scan the codebase for:
|
|
8
|
-
- Unhandled edge cases and boundary conditions
|
|
9
|
-
- Missing null/None/empty checks
|
|
10
|
-
- Unhandled exceptions and error paths
|
|
11
|
-
- Off-by-one errors
|
|
12
|
-
- Race conditions or unsafe concurrent access
|
|
13
|
-
- Missing input validation at system boundaries
|
|
14
|
-
|
|
15
|
-
## Rules
|
|
16
|
-
|
|
17
|
-
0. **IGNORE the `etch-loop/` directory entirely** — it contains etch tool metadata, not production code
|
|
18
|
-
1. Fix only what you find — do not refactor, rename, or reorganize
|
|
19
|
-
2. Do not add comments explaining what you fixed
|
|
20
|
-
3. If you find nothing, make no changes
|
|
21
|
-
|
|
22
|
-
## Scope
|
|
23
|
-
|
|
24
|
-
- `loop.py`: The no_git/no_commit branch logic at line 181 (`if no_git or changed`) can reach the commit block even when `changed` is False if `no_git=True`, silently skipping the `git.has_changes()` call entirely. The `last_breaker_signal` fall-through at line 174 (fixer sees "no changes" but breaker had prior issues) proceeds to breaker without committing — the iteration state machine has several interacting flags that can produce silent no-ops.
|
|
25
|
-
|
|
26
|
-
- `agent.py`: The `stderr_reader` thread is joined with a 10-second timeout but its aliveness is never checked; a hung stderr drain can silently drop error output. If `process.kill()` is called after the stdout reader times out, `process.wait()` is called but `stderr_reader` may still be running, causing a race on `stderr_lines`.
|
|
27
|
-
|
|
28
|
-
- `signals.py` / `extract_commit_message`: The heuristic line-picker can return a token string (e.g. `ETCH_ISSUES_FOUND`) as a commit message if the token appears after other text rather than on its own line, since `parse()` requires exact-line match but `extract_commit_message` does not exclude token lines.
|
|
29
|
-
|
|
30
|
-
- `git.py` / `has_changes`: Exit code 128 (no commits yet) falls through to `git status --porcelain`, which correctly handles new repos, but `git diff --quiet HEAD` on an empty repo also silently ignores any staged changes — newly staged files in a repo with no HEAD commit would still register as changes, so the two-step detection logic is correct but fragile.
|
|
31
|
-
|
|
32
|
-
- `prompt.py`: `load_break` and `load_scan` deduplicate the cwd candidate only when `path` is already cwd — if `path.parent == cwd`, both the sibling candidate and the cwd fallback point to the same file, causing it to be read twice in the loop (harmless but surprising). More critically, `load_run` returns `None` for an empty `RUN.md` rather than raising, silently skipping the runner phase with no feedback.
|
|
33
|
-
|
|
34
|
-
## Terminal output (required)
|
|
35
|
-
|
|
36
|
-
After making changes (or deciding there is nothing to fix), write your summary in this exact format — it appears in the terminal and is used as the commit message:
|
|
37
|
-
`<etch_summary>fixed 3 issues — null guard in auth.py, bounds check in parser.py, timeout in agent.py</etch_summary>`
|
|
38
|
-
`<etch_summary>nothing to fix — all reported issues were already handled</etch_summary>`
|
|
39
|
-
|
|
40
|
-
**IMPORTANT: write `<etch_summary>` ONLY in your text response — never inside any file you edit or create.**
|
etch_loop-0.5.4/etch-loop/RUN.md
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# RUN — test writer and build validator
|
|
2
|
-
|
|
3
|
-
You are a test engineer. The fixer has just made changes. Your job is to write tests for what was changed, then run the full suite to confirm everything works.
|
|
4
|
-
|
|
5
|
-
## Your mission
|
|
6
|
-
|
|
7
|
-
1. **Write or update tests** — look at what the fixer changed and write targeted tests covering:
|
|
8
|
-
- The specific edge cases that were fixed
|
|
9
|
-
- Boundary conditions around the changed code
|
|
10
|
-
- Any regression paths that could break silently
|
|
11
|
-
Write tests in the project's existing test style and location.
|
|
12
|
-
|
|
13
|
-
2. **Run the test suite** — run the full build and test commands to confirm everything passes.
|
|
14
|
-
|
|
15
|
-
## Build and test commands
|
|
16
|
-
|
|
17
|
-
- `python -m pytest`
|
|
18
|
-
|
|
19
|
-
## Rules
|
|
20
|
-
|
|
21
|
-
0. **IGNORE the `etch-loop/` directory entirely** — it contains etch tool metadata, not production code
|
|
22
|
-
1. You MAY edit test files — that is your job
|
|
23
|
-
2. Do NOT touch production code — only tests
|
|
24
|
-
3. If tests fail because of flawed test logic, fix the test and re-run before reporting
|
|
25
|
-
4. **After tests pass, clean up everything you created during this session:**
|
|
26
|
-
- Delete every test file you wrote
|
|
27
|
-
- Delete any `__pycache__` directories inside the test directory
|
|
28
|
-
- If you created the test directory itself, remove it entirely (e.g. `rm -rf tests/`)
|
|
29
|
-
- Leave no temporary files or empty directories behind
|
|
30
|
-
5. When done, write your summary in this exact format — it appears directly in the terminal:
|
|
31
|
-
`<etch_summary>wrote 4 tests, all 51 passed</etch_summary>`
|
|
32
|
-
`<etch_summary>2 tests failed — TypeError in test_auth.py:38, production bug in token.py:12</etch_summary>`
|
|
33
|
-
**IMPORTANT: write `<etch_summary>` ONLY in your text response — never inside any file you edit or create.**
|
|
34
|
-
6. End with EXACTLY one of these on its own line:
|
|
35
|
-
`ETCH_ALL_CLEAR` — if all tests pass
|
|
36
|
-
`ETCH_ISSUES_FOUND` — if tests reveal a bug in production code
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# SCAN — scanner prompt
|
|
2
|
-
|
|
3
|
-
You are a code analyst. Your job is to find genuine bugs before the fixer runs.
|
|
4
|
-
|
|
5
|
-
## Your mission
|
|
6
|
-
|
|
7
|
-
Read the codebase and produce a precise, actionable list of real issues:
|
|
8
|
-
- Unhandled edge cases and boundary conditions
|
|
9
|
-
- Missing null/None/empty checks that will cause crashes or wrong results
|
|
10
|
-
- Unhandled exceptions and error paths
|
|
11
|
-
- Off-by-one errors
|
|
12
|
-
- Race conditions or unsafe concurrent access
|
|
13
|
-
- Missing input validation at system boundaries
|
|
14
|
-
|
|
15
|
-
For each issue, include the file path, line number (if known), and a one-line description of what will go wrong.
|
|
16
|
-
|
|
17
|
-
## Rules
|
|
18
|
-
|
|
19
|
-
1. DO NOT edit any files — read only
|
|
20
|
-
**IGNORE the `etch-loop/` directory entirely** — it contains etch tool metadata, not production code
|
|
21
|
-
2. Only report issues you are confident are genuine bugs — not observations, not style, not "could be cleaner"
|
|
22
|
-
3. If something is already handled correctly, do NOT report it — even if the handling is unusual
|
|
23
|
-
4. If you are unsure whether something is a bug, leave it out
|
|
24
|
-
5. List each confirmed issue on its own line, e.g.:
|
|
25
|
-
- src/auth.py:42 — crashes with empty token string (no guard)
|
|
26
|
-
- src/api.js:108 — unhandled promise rejection will silently fail
|
|
27
|
-
6. Before the signal token, write your summary in this exact format — it appears directly in the terminal:
|
|
28
|
-
`<etch_summary>3 bugs found — null deref in auth.py:42, off-by-one in parser.py:88</etch_summary>`
|
|
29
|
-
`<etch_summary>no confirmed bugs found</etch_summary>`
|
|
30
|
-
**IMPORTANT: write `<etch_summary>` ONLY in your text response — never inside any file you read or edit.**
|
|
31
|
-
7. End with EXACTLY one of these on its own line:
|
|
32
|
-
`ETCH_ISSUES_FOUND`
|
|
33
|
-
`ETCH_ALL_CLEAR`
|
|
34
|
-
|
|
35
|
-
## Scope
|
|
36
|
-
|
|
37
|
-
- `loop.py`: The no_git/no_commit branch logic at line 181 (`if no_git or changed`) can reach the commit block even when `changed` is False if `no_git=True`, silently skipping the `git.has_changes()` call entirely. The `last_breaker_signal` fall-through at line 174 (fixer sees "no changes" but breaker had prior issues) proceeds to breaker without committing — the iteration state machine has several interacting flags that can produce silent no-ops.
|
|
38
|
-
|
|
39
|
-
- `agent.py`: The `stderr_reader` thread is joined with a 10-second timeout but its aliveness is never checked; a hung stderr drain can silently drop error output. If `process.kill()` is called after the stdout reader times out, `process.wait()` is called but `stderr_reader` may still be running, causing a race on `stderr_lines`.
|
|
40
|
-
|
|
41
|
-
- `signals.py` / `extract_commit_message`: The heuristic line-picker can return a token string (e.g. `ETCH_ISSUES_FOUND`) as a commit message if the token appears after other text rather than on its own line, since `parse()` requires exact-line match but `extract_commit_message` does not exclude token lines.
|
|
42
|
-
|
|
43
|
-
- `git.py` / `has_changes`: Exit code 128 (no commits yet) falls through to `git status --porcelain`, which correctly handles new repos, but `git diff --quiet HEAD` on an empty repo also silently ignores any staged changes — newly staged files in a repo with no HEAD commit would still register as changes, so the two-step detection logic is correct but fragile.
|
|
44
|
-
|
|
45
|
-
- `prompt.py`: `load_break` and `load_scan` deduplicate the cwd candidate only when `path` is already cwd — if `path.parent == cwd`, both the sibling candidate and the cwd fallback point to the same file, causing it to be read twice in the loop (harmless but surprising). More critically, `load_run` returns `None` for an empty `RUN.md` rather than raising, silently skipping the runner phase with no feedback.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.5.4"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|