etch-loop 0.4.1__tar.gz → 0.4.3__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.4.1 → etch_loop-0.4.3}/PKG-INFO +1 -1
- {etch_loop-0.4.1 → etch_loop-0.4.3}/pyproject.toml +1 -1
- etch_loop-0.4.3/src/etch/__init__.py +1 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/src/etch/display.py +2 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/src/etch/loop.py +44 -93
- {etch_loop-0.4.1 → etch_loop-0.4.3}/src/etch/report.py +1 -0
- etch_loop-0.4.1/src/etch/__init__.py +0 -1
- {etch_loop-0.4.1 → etch_loop-0.4.3}/.github/workflows/workflow.yml +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/README.md +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/src/etch/agent.py +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/src/etch/analyze.py +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/src/etch/cli.py +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/src/etch/git.py +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/src/etch/prompt.py +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/src/etch/signals.py +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/src/etch/templates/BREAK.md +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/src/etch/templates/ETCH.md +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/src/etch/templates/RUN.md +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/src/etch/templates/SCAN.md +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/tests/__init__.py +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/tests/test_git.py +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/tests/test_loop.py +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/tests/test_prompt.py +0 -0
- {etch_loop-0.4.1 → etch_loop-0.4.3}/tests/test_signals.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.3"
|
|
@@ -216,6 +216,8 @@ class EtchDisplay:
|
|
|
216
216
|
title = f"[{AMBER}]- stopped (max iterations)[/{AMBER}]"
|
|
217
217
|
elif reason == "no_changes":
|
|
218
218
|
title = f"[{GREEN}]+ clean — fixer found nothing[/{GREEN}]"
|
|
219
|
+
elif reason == "build_failed":
|
|
220
|
+
title = f"[{RED}]x build failed[/{RED}]"
|
|
219
221
|
else:
|
|
220
222
|
title = f"[{FG}]done[/{FG}]"
|
|
221
223
|
|
|
@@ -66,58 +66,11 @@ def run(
|
|
|
66
66
|
}
|
|
67
67
|
last_breaker_signal: str | None = None
|
|
68
68
|
last_breaker_output: str | None = None
|
|
69
|
-
last_runner_output: str | None = None
|
|
70
69
|
iteration_log: list[dict] = []
|
|
70
|
+
final_runner_entry: dict = {}
|
|
71
71
|
|
|
72
72
|
with display.EtchDisplay(target=str(prompt_path.parent)) as disp:
|
|
73
73
|
|
|
74
|
-
# ── Runner helper — called at every clean exit point ──────────────────
|
|
75
|
-
def try_runner(iter_entry: dict) -> str:
|
|
76
|
-
"""Run the runner phase if configured.
|
|
77
|
-
|
|
78
|
-
Returns:
|
|
79
|
-
"skip" — no RUN.md, proceed with clean exit
|
|
80
|
-
"clear" — runner passed, proceed with clean exit
|
|
81
|
-
"issues" — runner failed, continue the loop
|
|
82
|
-
"error" — agent error, break the loop
|
|
83
|
-
"""
|
|
84
|
-
nonlocal last_runner_output
|
|
85
|
-
if not run_text:
|
|
86
|
-
return "skip"
|
|
87
|
-
|
|
88
|
-
disp.start_phase("runner")
|
|
89
|
-
runner_start = time.monotonic()
|
|
90
|
-
try:
|
|
91
|
-
runner_output = agent.run(run_text, verbose=verbose)
|
|
92
|
-
except AgentError as exc:
|
|
93
|
-
disp.finish_phase("runner", status="error", detail=str(exc),
|
|
94
|
-
duration=time.monotonic() - runner_start, success=False)
|
|
95
|
-
return "error"
|
|
96
|
-
|
|
97
|
-
runner_duration = time.monotonic() - runner_start
|
|
98
|
-
runner_signal = signals.parse(runner_output)
|
|
99
|
-
runner_detail = (
|
|
100
|
-
signals.extract_summary(runner_output)
|
|
101
|
-
or signals.extract_finding(runner_output)
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
if runner_signal == "clear":
|
|
105
|
-
disp.finish_phase("runner", status="all clear",
|
|
106
|
-
detail=runner_detail or "build passed",
|
|
107
|
-
duration=runner_duration, success=True)
|
|
108
|
-
iter_entry["runner"] = {"status": "all clear", "detail": runner_detail}
|
|
109
|
-
last_runner_output = None
|
|
110
|
-
return "clear"
|
|
111
|
-
else:
|
|
112
|
-
disp.record_issue()
|
|
113
|
-
stats["issues"] += 1
|
|
114
|
-
disp.finish_phase("runner", status="build failed",
|
|
115
|
-
detail=runner_detail or "build failed",
|
|
116
|
-
duration=runner_duration, success=False)
|
|
117
|
-
iter_entry["runner"] = {"status": "build failed", "detail": runner_detail}
|
|
118
|
-
last_runner_output = runner_output
|
|
119
|
-
return "issues"
|
|
120
|
-
|
|
121
74
|
# ── Main loop ─────────────────────────────────────────────────────────
|
|
122
75
|
for iteration in range(1, max_iterations + 1):
|
|
123
76
|
stats["iterations"] = iteration
|
|
@@ -148,19 +101,9 @@ def run(
|
|
|
148
101
|
detail=scanner_detail or "nothing to fix",
|
|
149
102
|
duration=scanner_duration, success=True)
|
|
150
103
|
iter_entry["scanner"] = {"status": "all clear", "detail": scanner_detail}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
iteration_log.append(iter_entry)
|
|
155
|
-
break
|
|
156
|
-
elif runner_result == "issues":
|
|
157
|
-
stats["reason"] = "issues"
|
|
158
|
-
iteration_log.append(iter_entry)
|
|
159
|
-
continue
|
|
160
|
-
else: # "clear" or "skip"
|
|
161
|
-
stats["reason"] = "no_changes"
|
|
162
|
-
iteration_log.append(iter_entry)
|
|
163
|
-
break
|
|
104
|
+
stats["reason"] = "no_changes"
|
|
105
|
+
iteration_log.append(iter_entry)
|
|
106
|
+
break
|
|
164
107
|
|
|
165
108
|
disp.finish_phase("scanner", status="issues found",
|
|
166
109
|
detail=scanner_detail or "issues found",
|
|
@@ -179,12 +122,6 @@ def run(
|
|
|
179
122
|
f"{last_breaker_output.strip()}\n\n"
|
|
180
123
|
f"Also address these if not already covered above.\n"
|
|
181
124
|
)
|
|
182
|
-
if last_runner_output:
|
|
183
|
-
fixer_prompt += (
|
|
184
|
-
f"\n\n## Build/test failures from previous iteration\n\n"
|
|
185
|
-
f"{last_runner_output.strip()}\n\n"
|
|
186
|
-
f"Fix the underlying code issues causing these failures.\n"
|
|
187
|
-
)
|
|
188
125
|
|
|
189
126
|
# ── Fixer phase ───────────────────────────────────────────────────
|
|
190
127
|
disp.start_phase("fixer")
|
|
@@ -216,19 +153,9 @@ def run(
|
|
|
216
153
|
duration=fixer_duration, success=True)
|
|
217
154
|
iter_entry["fixer"] = {"status": "no changes", "detail": "nothing to fix"}
|
|
218
155
|
if last_breaker_signal != "issues":
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
iteration_log.append(iter_entry)
|
|
223
|
-
break
|
|
224
|
-
elif runner_result == "issues":
|
|
225
|
-
stats["reason"] = "issues"
|
|
226
|
-
iteration_log.append(iter_entry)
|
|
227
|
-
continue
|
|
228
|
-
else: # "clear" or "skip"
|
|
229
|
-
stats["reason"] = "no_changes"
|
|
230
|
-
iteration_log.append(iter_entry)
|
|
231
|
-
break
|
|
156
|
+
stats["reason"] = "no_changes"
|
|
157
|
+
iteration_log.append(iter_entry)
|
|
158
|
+
break
|
|
232
159
|
iteration_log.append(iter_entry)
|
|
233
160
|
# Fall through to breaker
|
|
234
161
|
|
|
@@ -284,19 +211,9 @@ def run(
|
|
|
284
211
|
detail=breaker_detail or "no issues found",
|
|
285
212
|
duration=breaker_duration, success=True)
|
|
286
213
|
iter_entry["breaker"] = {"status": "all clear", "detail": breaker_detail}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
iteration_log.append(iter_entry)
|
|
291
|
-
break
|
|
292
|
-
elif runner_result == "issues":
|
|
293
|
-
stats["reason"] = "issues"
|
|
294
|
-
iteration_log.append(iter_entry)
|
|
295
|
-
continue
|
|
296
|
-
else: # "clear" or "skip"
|
|
297
|
-
stats["reason"] = "clear"
|
|
298
|
-
iteration_log.append(iter_entry)
|
|
299
|
-
break
|
|
214
|
+
stats["reason"] = "clear"
|
|
215
|
+
iteration_log.append(iter_entry)
|
|
216
|
+
break
|
|
300
217
|
else:
|
|
301
218
|
disp.record_issue()
|
|
302
219
|
stats["issues"] += 1
|
|
@@ -310,6 +227,38 @@ def run(
|
|
|
310
227
|
else:
|
|
311
228
|
stats["reason"] = "max_iterations"
|
|
312
229
|
|
|
230
|
+
# ── Runner — final step, only when loop ended cleanly ─────────────────
|
|
231
|
+
if run_text and stats["reason"] in ("clear", "no_changes"):
|
|
232
|
+
disp.start_phase("runner")
|
|
233
|
+
runner_start = time.monotonic()
|
|
234
|
+
try:
|
|
235
|
+
runner_output = agent.run(run_text, verbose=verbose)
|
|
236
|
+
runner_duration = time.monotonic() - runner_start
|
|
237
|
+
runner_signal = signals.parse(runner_output)
|
|
238
|
+
runner_detail = (
|
|
239
|
+
signals.extract_summary(runner_output)
|
|
240
|
+
or signals.extract_finding(runner_output)
|
|
241
|
+
)
|
|
242
|
+
if runner_signal == "clear":
|
|
243
|
+
disp.finish_phase("runner", status="all clear",
|
|
244
|
+
detail=runner_detail or "build passed",
|
|
245
|
+
duration=runner_duration, success=True)
|
|
246
|
+
final_runner_entry = {"status": "all clear", "detail": runner_detail}
|
|
247
|
+
else:
|
|
248
|
+
disp.record_issue()
|
|
249
|
+
stats["issues"] += 1
|
|
250
|
+
disp.finish_phase("runner", status="build failed",
|
|
251
|
+
detail=runner_detail or "build failed",
|
|
252
|
+
duration=runner_duration, success=False)
|
|
253
|
+
final_runner_entry = {"status": "build failed", "detail": runner_detail}
|
|
254
|
+
stats["reason"] = "build_failed"
|
|
255
|
+
except AgentError as exc:
|
|
256
|
+
disp.finish_phase("runner", status="error",
|
|
257
|
+
detail=str(exc),
|
|
258
|
+
duration=time.monotonic() - runner_start, success=False)
|
|
259
|
+
final_runner_entry = {"status": "error", "detail": str(exc)}
|
|
260
|
+
stats["reason"] = "agent_error"
|
|
261
|
+
|
|
313
262
|
stats["elapsed"] = time.monotonic() - start_time
|
|
314
263
|
|
|
315
264
|
# Live panel is fully closed before printing anything below
|
|
@@ -317,6 +266,8 @@ def run(
|
|
|
317
266
|
|
|
318
267
|
# ── Write report ──────────────────────────────────────────────────────────
|
|
319
268
|
try:
|
|
269
|
+
if final_runner_entry:
|
|
270
|
+
iteration_log.append({"n": "runner", "runner": final_runner_entry})
|
|
320
271
|
report_path = report.write(stats, iteration_log, output_dir=prompt_path.parent)
|
|
321
272
|
if not no_git and not no_commit and stats["fixes"] > 0:
|
|
322
273
|
try:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|