daveloop 1.1.0__py3-none-any.whl → 1.3.0__py3-none-any.whl
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.
- {daveloop-1.1.0.dist-info → daveloop-1.3.0.dist-info}/METADATA +10 -2
- daveloop-1.3.0.dist-info/RECORD +7 -0
- {daveloop-1.1.0.dist-info → daveloop-1.3.0.dist-info}/WHEEL +1 -1
- daveloop.py +591 -168
- daveloop-1.1.0.dist-info/RECORD +0 -7
- {daveloop-1.1.0.dist-info → daveloop-1.3.0.dist-info}/entry_points.txt +0 -0
- {daveloop-1.1.0.dist-info → daveloop-1.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: daveloop
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Self-healing debug agent powered by Claude Code CLI
|
|
5
5
|
Home-page: https://github.com/davebruzil/DaveLoop
|
|
6
6
|
Author: Dave Bruzil
|
|
@@ -19,6 +19,14 @@ Classifier: Topic :: Software Development :: Debuggers
|
|
|
19
19
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
20
|
Requires-Python: >=3.7
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
|
+
Dynamic: author
|
|
23
|
+
Dynamic: classifier
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: description-content-type
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: keywords
|
|
28
|
+
Dynamic: requires-python
|
|
29
|
+
Dynamic: summary
|
|
22
30
|
|
|
23
31
|
# DaveLoop
|
|
24
32
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
daveloop.py,sha256=Htq9-ga4EPeyfIUW2Llq6yz7TJi4umfWD7-zlQPGZLs,49406
|
|
2
|
+
daveloop_swebench.py,sha256=iD9AU3XRiMQpt7TknFNlvnmPCNp64V-JaTfqTFgsGBM,15996
|
|
3
|
+
daveloop-1.3.0.dist-info/METADATA,sha256=eOrj3xTjeHWdrS9_Y87_H3KfHYa3KOtv5fXBDDC485w,10463
|
|
4
|
+
daveloop-1.3.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
5
|
+
daveloop-1.3.0.dist-info/entry_points.txt,sha256=QcFAZgFrDfPtIikNQb7eW9DxOpBK7T-qWrKqbGAS9Ww,86
|
|
6
|
+
daveloop-1.3.0.dist-info/top_level.txt,sha256=36DiYt70m4DIK8t7IhV_y6hAzUIyeb5-qDUf3-gbDdg,27
|
|
7
|
+
daveloop-1.3.0.dist-info/RECORD,,
|
daveloop.py
CHANGED
|
@@ -11,6 +11,7 @@ import argparse
|
|
|
11
11
|
import threading
|
|
12
12
|
import time
|
|
13
13
|
import itertools
|
|
14
|
+
import json
|
|
14
15
|
from datetime import datetime
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
|
|
@@ -79,70 +80,81 @@ if sys.platform == "win32":
|
|
|
79
80
|
# ============================================================================
|
|
80
81
|
# ASCII Art Banner
|
|
81
82
|
# ============================================================================
|
|
82
|
-
BANNER = f"""
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
{C.
|
|
92
|
-
|
|
93
|
-
|
|
83
|
+
BANNER = f"""{C.BRIGHT_BLUE} ❄ · ✦ · ❄ · ✦ · ❄ · ✦ · ❄ · ✦ · ❄ · ✦ · ❄ · ✦ · ❄ · ✦ · ❄ · ✦ · ❄{C.RESET}
|
|
84
|
+
|
|
85
|
+
{C.BRIGHT_BLUE}{C.BOLD} ██████╗ █████╗ ██╗ ██╗███████╗██╗ ██████╗ ██████╗ ██████╗
|
|
86
|
+
██╔══██╗██╔══██╗██║ ██║██╔════╝██║ ██╔═══██╗██╔═══██╗██╔══██╗
|
|
87
|
+
██║ ██║███████║██║ ██║█████╗ ██║ ██║ ██║██║ ██║██████╔╝
|
|
88
|
+
██║ ██║██╔══██║╚██╗ ██╔╝██╔══╝ ██║ ██║ ██║██║ ██║██╔═══╝
|
|
89
|
+
██████╔╝██║ ██║ ╚████╔╝ ███████╗███████╗╚██████╔╝╚██████╔╝██║
|
|
90
|
+
╚═════╝ ╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝{C.RESET}
|
|
91
|
+
|
|
92
|
+
{C.BRIGHT_BLUE} ❄ · ✦ · ❄ · ✦ · ❄ · ✦ · ❄ · ✦ · ❄ · ✦ · ❄ · ✦ · ❄ · ✦ · ❄ · ✦ · ❄{C.RESET}
|
|
93
|
+
|
|
94
|
+
{C.BRIGHT_WHITE}{C.BOLD} Self-Healing Debug Agent{C.RESET}
|
|
95
|
+
{C.DIM} Powered by Claude Code · Autonomous{C.RESET}"""
|
|
94
96
|
|
|
95
97
|
# ============================================================================
|
|
96
98
|
# UI Components
|
|
97
99
|
# ============================================================================
|
|
98
100
|
def print_header_box(title: str, color: str = C.BRIGHT_BLUE):
|
|
99
101
|
"""Print a header."""
|
|
100
|
-
print(f"
|
|
101
|
-
print(f"{color}{'─'*len(title)}{C.RESET}\n")
|
|
102
|
+
print(f"{color}{C.BOLD}┌─ {title} {'─' * (66 - len(title))}┐{C.RESET}")
|
|
102
103
|
|
|
103
104
|
def print_section(title: str, color: str = C.BRIGHT_BLUE):
|
|
104
105
|
"""Print a section divider."""
|
|
105
|
-
print(f"\n{color}{C.BOLD}{title}{C.RESET}")
|
|
106
|
-
print(f"{color}{'─'*
|
|
106
|
+
print(f"\n{color}{C.BOLD}◆ {title}{C.RESET}")
|
|
107
|
+
print(f"{color}{'─' * 70}{C.RESET}")
|
|
107
108
|
|
|
108
109
|
def print_status(label: str, value: str, color: str = C.WHITE):
|
|
109
110
|
"""Print a status line."""
|
|
110
|
-
print(f" {C.
|
|
111
|
+
print(f" {C.BRIGHT_BLUE}│{C.RESET} {C.DIM}{label}:{C.RESET} {color}{value}{C.RESET}")
|
|
111
112
|
|
|
112
113
|
def print_iteration_header(iteration: int, max_iter: int):
|
|
113
114
|
"""Print the iteration header with visual progress."""
|
|
114
115
|
progress = iteration / max_iter
|
|
115
|
-
bar_width =
|
|
116
|
+
bar_width = 25
|
|
116
117
|
filled = int(bar_width * progress)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
iteration_text = f"ITERATION {iteration}/{max_iter}"
|
|
120
|
-
percentage_text = f"{int(progress*100)}%"
|
|
118
|
+
empty = bar_width - filled
|
|
119
|
+
bar = '█' * filled + '░' * empty
|
|
121
120
|
|
|
122
|
-
print(f"
|
|
121
|
+
print(f"""
|
|
122
|
+
{C.BRIGHT_BLUE} ──────────────────────────────────────────────────────────────────────{C.RESET}
|
|
123
|
+
{C.BRIGHT_WHITE}{C.BOLD} ITERATION {iteration}/{max_iter}{C.RESET}
|
|
124
|
+
{C.BRIGHT_BLUE} [{bar}] {int(progress*100)}%{C.RESET}
|
|
125
|
+
{C.BRIGHT_BLUE} ──────────────────────────────────────────────────────────────────────{C.RESET}
|
|
126
|
+
""")
|
|
123
127
|
|
|
124
|
-
def print_success_box(message: str):
|
|
128
|
+
def print_success_box(message: str = ""):
|
|
125
129
|
"""Print an epic success message."""
|
|
126
|
-
print(f"
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
print(f" {C.WHITE}{message}{C.RESET}")
|
|
137
|
-
print(f"{C.RESET}\n")
|
|
130
|
+
print(f"""
|
|
131
|
+
{C.BRIGHT_GREEN}{C.BOLD} ███████╗██╗ ██╗ ██████╗ ██████╗███████╗███████╗███████╗
|
|
132
|
+
██╔════╝██║ ██║██╔════╝██╔════╝██╔════╝██╔════╝██╔════╝
|
|
133
|
+
███████╗██║ ██║██║ ██║ █████╗ ███████╗███████╗
|
|
134
|
+
╚════██║██║ ██║██║ ██║ ██╔══╝ ╚════██║╚════██║
|
|
135
|
+
███████║╚██████╔╝╚██████╗╚██████╗███████╗███████║███████║
|
|
136
|
+
╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝╚══════╝╚══════╝╚══════╝{C.RESET}
|
|
137
|
+
|
|
138
|
+
{C.BRIGHT_WHITE}{C.BOLD} ✓ BUG SUCCESSFULLY RESOLVED{C.RESET}
|
|
139
|
+
""")
|
|
138
140
|
|
|
139
141
|
def print_error_box(message: str):
|
|
140
142
|
"""Print an error message."""
|
|
141
|
-
print(f"
|
|
143
|
+
print(f"""
|
|
144
|
+
{C.BRIGHT_RED}╭{'─' * 70}╮
|
|
145
|
+
│{C.RESET} {C.BOLD}{C.BRIGHT_RED}✗ ERROR{C.RESET} {C.BRIGHT_RED}│
|
|
146
|
+
│{C.RESET} {C.WHITE}{message[:66]}{C.RESET} {C.BRIGHT_RED}│
|
|
147
|
+
╰{'─' * 70}╯{C.RESET}
|
|
148
|
+
""")
|
|
142
149
|
|
|
143
150
|
def print_warning_box(message: str):
|
|
144
151
|
"""Print a warning message."""
|
|
145
|
-
print(f"
|
|
152
|
+
print(f"""
|
|
153
|
+
{C.BRIGHT_YELLOW}╭{'─' * 70}╮
|
|
154
|
+
│{C.RESET} {C.BOLD}{C.BRIGHT_YELLOW}⚠ WARNING{C.RESET} {C.BRIGHT_YELLOW}│
|
|
155
|
+
│{C.RESET} {C.WHITE}{message[:66]}{C.RESET} {C.BRIGHT_YELLOW}│
|
|
156
|
+
╰{'─' * 70}╯{C.RESET}
|
|
157
|
+
""")
|
|
146
158
|
|
|
147
159
|
# ============================================================================
|
|
148
160
|
# Spinner Animation
|
|
@@ -184,6 +196,144 @@ class Spinner:
|
|
|
184
196
|
sys.stdout.write(f"\r {C.GREEN}✓{C.RESET} {self.message} complete {C.DIM}({elapsed:.1f}s){C.RESET} \n")
|
|
185
197
|
sys.stdout.flush()
|
|
186
198
|
|
|
199
|
+
# ============================================================================
|
|
200
|
+
# Task Queue
|
|
201
|
+
# ============================================================================
|
|
202
|
+
class TaskQueue:
|
|
203
|
+
"""Manages multiple bug tasks in sequence."""
|
|
204
|
+
|
|
205
|
+
def __init__(self):
|
|
206
|
+
self.tasks = [] # list of {"description": str, "status": "pending"|"active"|"done"|"failed"}
|
|
207
|
+
|
|
208
|
+
def add(self, description: str):
|
|
209
|
+
"""Add a new task with pending status."""
|
|
210
|
+
self.tasks.append({"description": description, "status": "pending"})
|
|
211
|
+
|
|
212
|
+
def next(self):
|
|
213
|
+
"""Find first pending task, set it to active, return it. None if no pending tasks."""
|
|
214
|
+
for task in self.tasks:
|
|
215
|
+
if task["status"] == "pending":
|
|
216
|
+
task["status"] = "active"
|
|
217
|
+
return task
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
def current(self):
|
|
221
|
+
"""Return the task with status active, or None."""
|
|
222
|
+
for task in self.tasks:
|
|
223
|
+
if task["status"] == "active":
|
|
224
|
+
return task
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
def mark_done(self):
|
|
228
|
+
"""Set current active task to done."""
|
|
229
|
+
task = self.current()
|
|
230
|
+
if task:
|
|
231
|
+
task["status"] = "done"
|
|
232
|
+
|
|
233
|
+
def mark_failed(self):
|
|
234
|
+
"""Set current active task to failed."""
|
|
235
|
+
task = self.current()
|
|
236
|
+
if task:
|
|
237
|
+
task["status"] = "failed"
|
|
238
|
+
|
|
239
|
+
def remaining(self) -> int:
|
|
240
|
+
"""Count of pending tasks."""
|
|
241
|
+
return sum(1 for t in self.tasks if t["status"] == "pending")
|
|
242
|
+
|
|
243
|
+
def all(self):
|
|
244
|
+
"""Return all tasks."""
|
|
245
|
+
return self.tasks
|
|
246
|
+
|
|
247
|
+
def summary_display(self):
|
|
248
|
+
"""Print a nice box showing all tasks with status icons."""
|
|
249
|
+
active_count = sum(1 for t in self.tasks if t["status"] == "active")
|
|
250
|
+
done_count = sum(1 for t in self.tasks if t["status"] == "done")
|
|
251
|
+
total = len(self.tasks)
|
|
252
|
+
active_idx = next((i for i, t in enumerate(self.tasks) if t["status"] == "active"), 0)
|
|
253
|
+
|
|
254
|
+
print(f"\n{C.BRIGHT_BLUE}{C.BOLD}◆ TASK QUEUE ({active_idx + 1}/{total} active){C.RESET}")
|
|
255
|
+
print(f"{C.BRIGHT_BLUE}{'─' * 70}{C.RESET}")
|
|
256
|
+
for task in self.tasks:
|
|
257
|
+
desc = task["description"][:50]
|
|
258
|
+
if task["status"] == "done":
|
|
259
|
+
print(f" {C.BRIGHT_GREEN}✓{C.RESET} {C.WHITE}{desc}{C.RESET}")
|
|
260
|
+
elif task["status"] == "active":
|
|
261
|
+
print(f" {C.BRIGHT_CYAN}▶{C.RESET} {C.BRIGHT_WHITE}{desc}{C.RESET} {C.DIM}(active){C.RESET}")
|
|
262
|
+
elif task["status"] == "pending":
|
|
263
|
+
print(f" {C.DIM}○{C.RESET} {C.DIM}{desc}{C.RESET} {C.DIM}(pending){C.RESET}")
|
|
264
|
+
elif task["status"] == "failed":
|
|
265
|
+
print(f" {C.BRIGHT_RED}✗{C.RESET} {C.RED}{desc}{C.RESET}")
|
|
266
|
+
print()
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# ============================================================================
|
|
270
|
+
# Session Memory
|
|
271
|
+
# ============================================================================
|
|
272
|
+
def load_history(working_dir: str) -> dict:
|
|
273
|
+
"""Read .daveloop_history.json from working_dir. Return default if missing or corrupted."""
|
|
274
|
+
history_file = Path(working_dir) / ".daveloop_history.json"
|
|
275
|
+
if not history_file.exists():
|
|
276
|
+
return {"sessions": []}
|
|
277
|
+
try:
|
|
278
|
+
data = json.loads(history_file.read_text(encoding="utf-8"))
|
|
279
|
+
if not isinstance(data, dict) or "sessions" not in data:
|
|
280
|
+
print_warning_box("Corrupted history file - resetting")
|
|
281
|
+
return {"sessions": []}
|
|
282
|
+
return data
|
|
283
|
+
except (json.JSONDecodeError, ValueError):
|
|
284
|
+
print_warning_box("Corrupted history JSON - resetting")
|
|
285
|
+
return {"sessions": []}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def save_history(working_dir: str, history_data: dict):
|
|
289
|
+
"""Write to .daveloop_history.json. Keep only last 20 sessions."""
|
|
290
|
+
history_file = Path(working_dir) / ".daveloop_history.json"
|
|
291
|
+
history_data["sessions"] = history_data["sessions"][-20:]
|
|
292
|
+
history_file.write_text(json.dumps(history_data, indent=2), encoding="utf-8")
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def summarize_session(bug: str, outcome: str, iterations: int) -> dict:
|
|
296
|
+
"""Return a dict summarizing a session."""
|
|
297
|
+
now = datetime.now()
|
|
298
|
+
return {
|
|
299
|
+
"session_id": now.strftime("%Y%m%d_%H%M%S"),
|
|
300
|
+
"bug": bug,
|
|
301
|
+
"outcome": outcome,
|
|
302
|
+
"iterations": iterations,
|
|
303
|
+
"timestamp": now.isoformat()
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def format_history_context(sessions: list) -> str:
|
|
308
|
+
"""Return markdown string summarizing recent sessions for Claude context."""
|
|
309
|
+
if not sessions:
|
|
310
|
+
return ""
|
|
311
|
+
lines = ["## Previous DaveLoop Sessions"]
|
|
312
|
+
for s in sessions[-10:]: # Show last 10
|
|
313
|
+
outcome = s.get("outcome", "UNKNOWN")
|
|
314
|
+
bug = s.get("bug", "unknown")[:60]
|
|
315
|
+
iters = s.get("iterations", "?")
|
|
316
|
+
lines.append(f"- [{outcome}] \"{bug}\" ({iters} iterations)")
|
|
317
|
+
return "\n".join(lines)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def print_history_box(sessions: list):
|
|
321
|
+
"""Print a nice UI box showing loaded history."""
|
|
322
|
+
if not sessions:
|
|
323
|
+
return
|
|
324
|
+
print(f"\n{C.BRIGHT_BLUE}{C.BOLD}◆ SESSION HISTORY{C.RESET}")
|
|
325
|
+
print(f"{C.BRIGHT_BLUE}{'─' * 70}{C.RESET}")
|
|
326
|
+
for s in sessions[-10:]:
|
|
327
|
+
outcome = s.get("outcome", "UNKNOWN")
|
|
328
|
+
bug = s.get("bug", "unknown")[:55]
|
|
329
|
+
iters = s.get("iterations", "?")
|
|
330
|
+
if outcome == "RESOLVED":
|
|
331
|
+
print(f" {C.BRIGHT_GREEN}✓{C.RESET} {C.WHITE}{bug}{C.RESET} {C.DIM}({iters} iter){C.RESET}")
|
|
332
|
+
else:
|
|
333
|
+
print(f" {C.BRIGHT_RED}✗{C.RESET} {C.WHITE}{bug}{C.RESET} {C.DIM}({iters} iter){C.RESET}")
|
|
334
|
+
print()
|
|
335
|
+
|
|
336
|
+
|
|
187
337
|
# ============================================================================
|
|
188
338
|
# Output Formatter
|
|
189
339
|
# ============================================================================
|
|
@@ -263,6 +413,74 @@ def format_claude_output(output: str) -> str:
|
|
|
263
413
|
|
|
264
414
|
return '\n'.join(formatted)
|
|
265
415
|
|
|
416
|
+
# ============================================================================
|
|
417
|
+
# Input Monitor
|
|
418
|
+
# ============================================================================
|
|
419
|
+
class InputMonitor:
|
|
420
|
+
"""Daemon thread that reads stdin for commands while Claude is running.
|
|
421
|
+
|
|
422
|
+
After detecting a command, stops reading stdin so that input() calls
|
|
423
|
+
in the main thread can safely read without a race condition.
|
|
424
|
+
Call resume_reading() after the main thread is done with input().
|
|
425
|
+
"""
|
|
426
|
+
|
|
427
|
+
VALID_COMMANDS = ("wait", "pause", "add", "done")
|
|
428
|
+
|
|
429
|
+
def __init__(self):
|
|
430
|
+
self._command = None
|
|
431
|
+
self._lock = threading.Lock()
|
|
432
|
+
self._thread = threading.Thread(target=self._read_loop, daemon=True)
|
|
433
|
+
self._running = False
|
|
434
|
+
self._read_gate = threading.Event()
|
|
435
|
+
self._read_gate.set() # Start with reading enabled
|
|
436
|
+
|
|
437
|
+
def start(self):
|
|
438
|
+
"""Start monitoring stdin."""
|
|
439
|
+
self._running = True
|
|
440
|
+
self._thread.start()
|
|
441
|
+
|
|
442
|
+
def stop(self):
|
|
443
|
+
"""Stop monitoring stdin."""
|
|
444
|
+
self._running = False
|
|
445
|
+
self._read_gate.set() # Unblock the thread so it can exit
|
|
446
|
+
|
|
447
|
+
def resume_reading(self):
|
|
448
|
+
"""Resume reading stdin after an interrupt has been handled."""
|
|
449
|
+
self._read_gate.set()
|
|
450
|
+
|
|
451
|
+
def _read_loop(self):
|
|
452
|
+
"""Read lines from stdin, looking for valid commands."""
|
|
453
|
+
while self._running:
|
|
454
|
+
# Wait until reading is enabled (blocks after a command is detected)
|
|
455
|
+
self._read_gate.wait()
|
|
456
|
+
if not self._running:
|
|
457
|
+
break
|
|
458
|
+
try:
|
|
459
|
+
line = sys.stdin.readline()
|
|
460
|
+
if not line:
|
|
461
|
+
break
|
|
462
|
+
cmd = line.strip().lower()
|
|
463
|
+
if cmd in self.VALID_COMMANDS:
|
|
464
|
+
with self._lock:
|
|
465
|
+
self._command = cmd
|
|
466
|
+
# Stop reading so input() in the main thread has no competition
|
|
467
|
+
self._read_gate.clear()
|
|
468
|
+
except (EOFError, OSError):
|
|
469
|
+
break
|
|
470
|
+
|
|
471
|
+
def has_command(self) -> bool:
|
|
472
|
+
"""Check if a command has been received."""
|
|
473
|
+
with self._lock:
|
|
474
|
+
return self._command is not None
|
|
475
|
+
|
|
476
|
+
def get_command(self) -> str:
|
|
477
|
+
"""Get and clear the current command."""
|
|
478
|
+
with self._lock:
|
|
479
|
+
cmd = self._command
|
|
480
|
+
self._command = None
|
|
481
|
+
return cmd
|
|
482
|
+
|
|
483
|
+
|
|
266
484
|
# ============================================================================
|
|
267
485
|
# Core Functions
|
|
268
486
|
# ============================================================================
|
|
@@ -316,11 +534,12 @@ def find_claude_cli():
|
|
|
316
534
|
return None
|
|
317
535
|
|
|
318
536
|
|
|
319
|
-
def run_claude_code(prompt: str, working_dir: str = None, continue_session: bool = False, stream: bool = True, timeout: int = DEFAULT_TIMEOUT) -> str:
|
|
537
|
+
def run_claude_code(prompt: str, working_dir: str = None, continue_session: bool = False, stream: bool = True, timeout: int = DEFAULT_TIMEOUT, input_monitor=None) -> str:
|
|
320
538
|
"""Execute Claude Code CLI with the given prompt.
|
|
321
539
|
|
|
322
540
|
If stream=True, output is printed in real-time and also returned.
|
|
323
541
|
timeout is in seconds (default 600 = 10 minutes).
|
|
542
|
+
input_monitor: optional InputMonitor to check for user commands during execution.
|
|
324
543
|
"""
|
|
325
544
|
claude_cmd = find_claude_cli()
|
|
326
545
|
if not claude_cmd:
|
|
@@ -360,28 +579,14 @@ def run_claude_code(prompt: str, working_dir: str = None, continue_session: bool
|
|
|
360
579
|
process.stdin.write(prompt)
|
|
361
580
|
process.stdin.close()
|
|
362
581
|
|
|
363
|
-
#
|
|
582
|
+
# Track start time
|
|
364
583
|
start_time = time.time()
|
|
365
|
-
heartbeat_active = True
|
|
366
|
-
|
|
367
|
-
def heartbeat():
|
|
368
|
-
while heartbeat_active:
|
|
369
|
-
elapsed = int(time.time() - start_time)
|
|
370
|
-
print(f"\r {C.BLUE}[{elapsed}s elapsed...]{C.RESET} ", end='')
|
|
371
|
-
sys.stdout.flush()
|
|
372
|
-
time.sleep(3)
|
|
373
|
-
|
|
374
|
-
heartbeat_thread = threading.Thread(target=heartbeat, daemon=True)
|
|
375
|
-
heartbeat_thread.start()
|
|
376
584
|
|
|
377
585
|
# Read and display JSON stream output
|
|
378
|
-
import json
|
|
379
586
|
output_lines = []
|
|
380
587
|
full_text = []
|
|
381
588
|
|
|
382
589
|
for line in process.stdout:
|
|
383
|
-
# Clear heartbeat line
|
|
384
|
-
print(f"\r{' '*40}\r", end='')
|
|
385
590
|
|
|
386
591
|
line = line.strip()
|
|
387
592
|
if not line:
|
|
@@ -391,6 +596,7 @@ def run_claude_code(prompt: str, working_dir: str = None, continue_session: bool
|
|
|
391
596
|
data = json.loads(line)
|
|
392
597
|
msg_type = data.get("type", "")
|
|
393
598
|
|
|
599
|
+
|
|
394
600
|
# Handle different message types
|
|
395
601
|
if msg_type == "assistant":
|
|
396
602
|
# Assistant text message
|
|
@@ -402,6 +608,43 @@ def run_claude_code(prompt: str, working_dir: str = None, continue_session: bool
|
|
|
402
608
|
formatted = format_output_line(line_text)
|
|
403
609
|
print(formatted)
|
|
404
610
|
full_text.append(text)
|
|
611
|
+
elif block.get("type") == "tool_use":
|
|
612
|
+
# Tool being called - show what Claude is doing
|
|
613
|
+
tool_name = block.get("name", "unknown")
|
|
614
|
+
tool_input = block.get("input", {})
|
|
615
|
+
|
|
616
|
+
# Format tool call based on type
|
|
617
|
+
if tool_name == "Bash":
|
|
618
|
+
cmd = tool_input.get("command", "")
|
|
619
|
+
cmd_display = cmd[:50] + "..." if len(cmd) > 50 else cmd
|
|
620
|
+
tool_display = f"{C.BRIGHT_BLUE}Bash{C.RESET}({C.WHITE}{cmd_display}{C.RESET})"
|
|
621
|
+
elif tool_name == "Read":
|
|
622
|
+
file_path = tool_input.get("file_path", "")
|
|
623
|
+
filename = file_path.split("\\")[-1].split("/")[-1]
|
|
624
|
+
tool_display = f"{C.BRIGHT_BLUE}Read{C.RESET}({C.WHITE}{filename}{C.RESET})"
|
|
625
|
+
elif tool_name == "Write":
|
|
626
|
+
file_path = tool_input.get("file_path", "")
|
|
627
|
+
filename = file_path.split("\\")[-1].split("/")[-1]
|
|
628
|
+
tool_display = f"{C.BRIGHT_BLUE}Write{C.RESET}({C.WHITE}{filename}{C.RESET})"
|
|
629
|
+
elif tool_name == "Edit":
|
|
630
|
+
file_path = tool_input.get("file_path", "")
|
|
631
|
+
filename = file_path.split("\\")[-1].split("/")[-1]
|
|
632
|
+
tool_display = f"{C.BRIGHT_BLUE}Edit{C.RESET}({C.WHITE}{filename}{C.RESET})"
|
|
633
|
+
elif tool_name == "Grep":
|
|
634
|
+
pattern = tool_input.get("pattern", "")
|
|
635
|
+
pattern_display = pattern[:25] + "..." if len(pattern) > 25 else pattern
|
|
636
|
+
tool_display = f"{C.BRIGHT_BLUE}Grep{C.RESET}({C.WHITE}{pattern_display}{C.RESET})"
|
|
637
|
+
elif tool_name == "Glob":
|
|
638
|
+
pattern = tool_input.get("pattern", "")
|
|
639
|
+
tool_display = f"{C.BRIGHT_BLUE}Glob{C.RESET}({C.WHITE}{pattern}{C.RESET})"
|
|
640
|
+
elif tool_name == "Task":
|
|
641
|
+
desc = tool_input.get("description", "")
|
|
642
|
+
tool_display = f"{C.BRIGHT_BLUE}Task{C.RESET}({C.WHITE}{desc}{C.RESET})"
|
|
643
|
+
else:
|
|
644
|
+
tool_display = f"{C.BRIGHT_BLUE}{tool_name}{C.RESET}"
|
|
645
|
+
|
|
646
|
+
print(f" {C.BRIGHT_BLUE}▶{C.RESET} {tool_display}")
|
|
647
|
+
sys.stdout.flush()
|
|
405
648
|
|
|
406
649
|
elif msg_type == "content_block_delta":
|
|
407
650
|
# Streaming text delta
|
|
@@ -412,21 +655,58 @@ def run_claude_code(prompt: str, working_dir: str = None, continue_session: bool
|
|
|
412
655
|
full_text.append(text)
|
|
413
656
|
|
|
414
657
|
elif msg_type == "tool_use":
|
|
415
|
-
# Tool being used
|
|
658
|
+
# Tool being used - show what Claude is doing
|
|
416
659
|
tool_name = data.get("name", "unknown")
|
|
417
|
-
|
|
660
|
+
tool_input = data.get("input", {})
|
|
661
|
+
|
|
662
|
+
# Format tool call based on type
|
|
663
|
+
if tool_name == "Bash":
|
|
664
|
+
cmd = tool_input.get("command", "")
|
|
665
|
+
cmd_display = cmd[:50] + "..." if len(cmd) > 50 else cmd
|
|
666
|
+
tool_display = f"{C.BRIGHT_BLUE}Bash{C.RESET}({C.WHITE}{cmd_display}{C.RESET})"
|
|
667
|
+
elif tool_name == "Read":
|
|
668
|
+
file_path = tool_input.get("file_path", "")
|
|
669
|
+
filename = file_path.split("\\")[-1].split("/")[-1]
|
|
670
|
+
tool_display = f"{C.BRIGHT_BLUE}Read{C.RESET}({C.WHITE}{filename}{C.RESET})"
|
|
671
|
+
elif tool_name == "Write":
|
|
672
|
+
file_path = tool_input.get("file_path", "")
|
|
673
|
+
filename = file_path.split("\\")[-1].split("/")[-1]
|
|
674
|
+
tool_display = f"{C.BRIGHT_BLUE}Write{C.RESET}({C.WHITE}{filename}{C.RESET})"
|
|
675
|
+
elif tool_name == "Edit":
|
|
676
|
+
file_path = tool_input.get("file_path", "")
|
|
677
|
+
filename = file_path.split("\\")[-1].split("/")[-1]
|
|
678
|
+
tool_display = f"{C.BRIGHT_BLUE}Edit{C.RESET}({C.WHITE}{filename}{C.RESET})"
|
|
679
|
+
elif tool_name == "Grep":
|
|
680
|
+
pattern = tool_input.get("pattern", "")
|
|
681
|
+
pattern_display = pattern[:25] + "..." if len(pattern) > 25 else pattern
|
|
682
|
+
tool_display = f"{C.BRIGHT_BLUE}Grep{C.RESET}({C.WHITE}{pattern_display}{C.RESET})"
|
|
683
|
+
elif tool_name == "Glob":
|
|
684
|
+
pattern = tool_input.get("pattern", "")
|
|
685
|
+
tool_display = f"{C.BRIGHT_BLUE}Glob{C.RESET}({C.WHITE}{pattern}{C.RESET})"
|
|
686
|
+
elif tool_name == "Task":
|
|
687
|
+
desc = tool_input.get("description", "")
|
|
688
|
+
tool_display = f"{C.BRIGHT_BLUE}Task{C.RESET}({C.WHITE}{desc}{C.RESET})"
|
|
689
|
+
else:
|
|
690
|
+
tool_display = f"{C.BRIGHT_BLUE}{tool_name}{C.RESET}"
|
|
691
|
+
|
|
692
|
+
print(f" {C.BRIGHT_BLUE}▶{C.RESET} {tool_display}")
|
|
693
|
+
sys.stdout.flush()
|
|
418
694
|
|
|
419
695
|
elif msg_type == "tool_result":
|
|
420
|
-
# Tool
|
|
421
|
-
print(f" {C.
|
|
696
|
+
# Tool completed
|
|
697
|
+
print(f" {C.BRIGHT_BLUE}└─{C.RESET} {C.GREEN}✓{C.RESET}")
|
|
698
|
+
|
|
699
|
+
elif msg_type == "user":
|
|
700
|
+
# Tool results come back as user messages
|
|
701
|
+
content = data.get("message", {}).get("content", [])
|
|
702
|
+
for block in content:
|
|
703
|
+
if block.get("type") == "tool_result":
|
|
704
|
+
print(f" {C.BRIGHT_BLUE}└─{C.RESET} {C.GREEN}✓{C.RESET}")
|
|
422
705
|
|
|
423
706
|
elif msg_type == "result":
|
|
424
|
-
# Final result
|
|
707
|
+
# Final result - skip printing as it duplicates streamed content
|
|
425
708
|
text = data.get("result", "")
|
|
426
709
|
if text:
|
|
427
|
-
for line_text in text.split('\n'):
|
|
428
|
-
formatted = format_output_line(line_text)
|
|
429
|
-
print(formatted)
|
|
430
710
|
full_text.append(text)
|
|
431
711
|
|
|
432
712
|
elif msg_type == "error":
|
|
@@ -442,8 +722,15 @@ def run_claude_code(prompt: str, working_dir: str = None, continue_session: bool
|
|
|
442
722
|
|
|
443
723
|
output_lines.append(line)
|
|
444
724
|
|
|
445
|
-
|
|
446
|
-
|
|
725
|
+
# Check for user commands from InputMonitor
|
|
726
|
+
if input_monitor and input_monitor.has_command():
|
|
727
|
+
user_cmd = input_monitor.get_command()
|
|
728
|
+
try:
|
|
729
|
+
process.terminate()
|
|
730
|
+
process.wait(timeout=5)
|
|
731
|
+
except Exception:
|
|
732
|
+
pass
|
|
733
|
+
return f"[DAVELOOP:INTERRUPTED:{user_cmd}]"
|
|
447
734
|
|
|
448
735
|
process.wait(timeout=timeout)
|
|
449
736
|
return '\n'.join(full_text)
|
|
@@ -476,36 +763,40 @@ def format_output_line(line: str) -> str:
|
|
|
476
763
|
"""Format a single line of Claude's output with colors."""
|
|
477
764
|
# Reasoning markers
|
|
478
765
|
if "=== DAVELOOP REASONING ===" in line:
|
|
479
|
-
return f"
|
|
766
|
+
return f"""
|
|
767
|
+
{C.BRIGHT_BLUE} ┌─ 🧠 REASONING ─────────────────────────────────────────────────────┐{C.RESET}"""
|
|
480
768
|
if "===========================" in line:
|
|
481
|
-
return f"{C.BRIGHT_BLUE}{
|
|
769
|
+
return f"{C.BRIGHT_BLUE} └─────────────────────────────────────────────────────────────────────┘{C.RESET}"
|
|
482
770
|
|
|
483
771
|
# Reasoning labels
|
|
484
772
|
if line.startswith("KNOWN:"):
|
|
485
|
-
return f" {C.
|
|
773
|
+
return f" {C.BRIGHT_BLUE}│{C.RESET} {C.BRIGHT_BLUE}KNOWN:{C.RESET} {C.WHITE}{line[6:]}{C.RESET}"
|
|
486
774
|
if line.startswith("UNKNOWN:"):
|
|
487
|
-
return f" {C.
|
|
775
|
+
return f" {C.BRIGHT_BLUE}│{C.RESET} {C.BRIGHT_BLUE}UNKNOWN:{C.RESET} {C.WHITE}{line[8:]}{C.RESET}"
|
|
488
776
|
if line.startswith("HYPOTHESIS:"):
|
|
489
|
-
return f" {C.
|
|
777
|
+
return f" {C.BRIGHT_BLUE}│{C.RESET} {C.BRIGHT_BLUE}HYPOTHESIS:{C.RESET} {C.WHITE}{line[11:]}{C.RESET}"
|
|
490
778
|
if line.startswith("NEXT ACTION:"):
|
|
491
|
-
return f" {C.
|
|
779
|
+
return f" {C.BRIGHT_BLUE}│{C.RESET} {C.BRIGHT_BLUE}NEXT:{C.RESET} {C.WHITE}{line[12:]}{C.RESET}"
|
|
492
780
|
if line.startswith("WHY:"):
|
|
493
|
-
return f" {C.
|
|
781
|
+
return f" {C.BRIGHT_BLUE}│{C.RESET} {C.BRIGHT_BLUE}WHY:{C.RESET} {C.WHITE}{line[4:]}{C.RESET}"
|
|
494
782
|
|
|
495
|
-
# Exit signals -
|
|
496
|
-
# The actual success/error boxes will be shown after iteration completes
|
|
783
|
+
# Exit signals - hide them, the success/error box will show
|
|
497
784
|
if "[DAVELOOP:RESOLVED]" in line:
|
|
498
|
-
return
|
|
785
|
+
return ""
|
|
499
786
|
if "[DAVELOOP:BLOCKED]" in line:
|
|
500
|
-
return
|
|
787
|
+
return ""
|
|
501
788
|
if "[DAVELOOP:CLARIFY]" in line:
|
|
502
|
-
return
|
|
789
|
+
return ""
|
|
503
790
|
|
|
504
|
-
# Code blocks
|
|
791
|
+
# Code blocks - hide the markers
|
|
505
792
|
if line.strip().startswith("```"):
|
|
506
|
-
return
|
|
793
|
+
return ""
|
|
507
794
|
|
|
508
|
-
#
|
|
795
|
+
# Empty lines - minimal spacing
|
|
796
|
+
if not line.strip():
|
|
797
|
+
return ""
|
|
798
|
+
|
|
799
|
+
# Default - white text with subtle indent
|
|
509
800
|
return f" {C.WHITE}{line}{C.RESET}"
|
|
510
801
|
|
|
511
802
|
|
|
@@ -539,7 +830,7 @@ def main():
|
|
|
539
830
|
description="DaveLoop - Self-Healing Debug Agent",
|
|
540
831
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
|
541
832
|
)
|
|
542
|
-
parser.add_argument("bug", nargs="
|
|
833
|
+
parser.add_argument("bug", nargs="*", help="Bug description(s) or error message(s)")
|
|
543
834
|
parser.add_argument("-f", "--file", help="Read bug description from file")
|
|
544
835
|
parser.add_argument("-d", "--dir", help="Working directory for Claude Code")
|
|
545
836
|
parser.add_argument("-m", "--max-iterations", type=int, default=MAX_ITERATIONS)
|
|
@@ -553,16 +844,19 @@ def main():
|
|
|
553
844
|
os.system('cls' if os.name == 'nt' else 'clear')
|
|
554
845
|
print(BANNER)
|
|
555
846
|
|
|
556
|
-
#
|
|
847
|
+
# Collect bug descriptions
|
|
848
|
+
bug_descriptions = []
|
|
557
849
|
if args.file:
|
|
558
|
-
|
|
850
|
+
bug_descriptions.append(Path(args.file).read_text(encoding="utf-8"))
|
|
559
851
|
elif args.bug:
|
|
560
|
-
|
|
852
|
+
bug_descriptions.extend(args.bug)
|
|
561
853
|
else:
|
|
562
854
|
print(f" {C.CYAN}Describe the bug (Ctrl+D or Ctrl+Z to finish):{C.RESET}")
|
|
563
|
-
|
|
855
|
+
stdin_input = sys.stdin.read().strip()
|
|
856
|
+
if stdin_input:
|
|
857
|
+
bug_descriptions.append(stdin_input)
|
|
564
858
|
|
|
565
|
-
if not
|
|
859
|
+
if not bug_descriptions:
|
|
566
860
|
print_error_box("No bug description provided")
|
|
567
861
|
return 1
|
|
568
862
|
|
|
@@ -571,30 +865,61 @@ def main():
|
|
|
571
865
|
system_prompt = load_prompt()
|
|
572
866
|
working_dir = args.dir or os.getcwd()
|
|
573
867
|
|
|
868
|
+
# Load session history
|
|
869
|
+
history_data = load_history(working_dir)
|
|
870
|
+
if history_data["sessions"]:
|
|
871
|
+
print_history_box(history_data["sessions"])
|
|
872
|
+
|
|
574
873
|
# Session info
|
|
575
874
|
print_header_box(f"SESSION: {session_id}", C.BRIGHT_BLUE)
|
|
576
|
-
print_status("
|
|
577
|
-
print_status("
|
|
578
|
-
print_status("Timeout", f"{args.timeout // 60}
|
|
579
|
-
print_status("
|
|
580
|
-
print_status("
|
|
581
|
-
print()
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
for
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
print()
|
|
590
|
-
|
|
591
|
-
|
|
875
|
+
print_status("Directory", working_dir, C.WHITE)
|
|
876
|
+
print_status("Iterations", str(args.max_iterations), C.WHITE)
|
|
877
|
+
print_status("Timeout", f"{args.timeout // 60}m per iteration", C.WHITE)
|
|
878
|
+
print_status("Tasks", str(len(bug_descriptions)), C.WHITE)
|
|
879
|
+
print_status("Mode", "Autonomous", C.WHITE)
|
|
880
|
+
print(f"{C.BRIGHT_BLUE}└{'─' * 70}┘{C.RESET}")
|
|
881
|
+
|
|
882
|
+
# Build task queue
|
|
883
|
+
task_queue = TaskQueue()
|
|
884
|
+
for desc in bug_descriptions:
|
|
885
|
+
task_queue.add(desc)
|
|
886
|
+
|
|
887
|
+
# Print controls hint
|
|
888
|
+
print(f"\n{C.BRIGHT_BLUE}{C.BOLD}┌─ CONTROLS {'─' * 58}┐{C.RESET}")
|
|
889
|
+
print(f"{C.BRIGHT_BLUE}│{C.RESET} Type while running: {C.BRIGHT_WHITE}wait{C.RESET} {C.DIM}·{C.RESET} {C.BRIGHT_WHITE}pause{C.RESET} {C.DIM}·{C.RESET} {C.BRIGHT_WHITE}add{C.RESET} {C.DIM}·{C.RESET} {C.BRIGHT_WHITE}done{C.RESET} {C.BRIGHT_BLUE}│{C.RESET}")
|
|
890
|
+
print(f"{C.BRIGHT_BLUE}└{'─' * 70}┘{C.RESET}")
|
|
891
|
+
|
|
892
|
+
# Start input monitor
|
|
893
|
+
input_monitor = InputMonitor()
|
|
894
|
+
input_monitor.start()
|
|
895
|
+
|
|
896
|
+
# Build history context for initial prompt
|
|
897
|
+
history_context = ""
|
|
898
|
+
if history_data["sessions"]:
|
|
899
|
+
history_context = "\n\n" + format_history_context(history_data["sessions"])
|
|
900
|
+
|
|
901
|
+
# === OUTER LOOP: iterate over tasks ===
|
|
902
|
+
while True:
|
|
903
|
+
task = task_queue.next()
|
|
904
|
+
if task is None:
|
|
905
|
+
break
|
|
906
|
+
|
|
907
|
+
bug_input = task["description"]
|
|
908
|
+
task_queue.summary_display()
|
|
909
|
+
|
|
910
|
+
print_section("BUG REPORT", C.BRIGHT_RED)
|
|
911
|
+
for line in bug_input.split('\n')[:8]:
|
|
912
|
+
print(f" {C.BRIGHT_RED}{line[:70]}{C.RESET}")
|
|
913
|
+
if len(bug_input.split('\n')) > 8:
|
|
914
|
+
print(f" {C.RED}... +{len(bug_input.split(chr(10))) - 8} more lines{C.RESET}")
|
|
915
|
+
sys.stdout.flush()
|
|
592
916
|
|
|
593
|
-
|
|
594
|
-
|
|
917
|
+
# Initial context for this task
|
|
918
|
+
context = f"""
|
|
595
919
|
## Bug Report
|
|
596
920
|
|
|
597
921
|
{bug_input}
|
|
922
|
+
{history_context}
|
|
598
923
|
|
|
599
924
|
## Instructions
|
|
600
925
|
|
|
@@ -602,68 +927,143 @@ Analyze this bug. Gather whatever logs/information you need to understand it.
|
|
|
602
927
|
Then fix it. Use the reasoning protocol before each action.
|
|
603
928
|
"""
|
|
604
929
|
|
|
605
|
-
|
|
930
|
+
iteration_history = []
|
|
606
931
|
|
|
607
|
-
|
|
608
|
-
|
|
932
|
+
# === INNER LOOP: iterations for current task ===
|
|
933
|
+
for iteration in range(1, args.max_iterations + 1):
|
|
609
934
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
935
|
+
if iteration == 1:
|
|
936
|
+
full_prompt = f"{system_prompt}\n\n---\n\n{context}"
|
|
937
|
+
continue_session = False
|
|
938
|
+
else:
|
|
939
|
+
full_prompt = context
|
|
940
|
+
continue_session = True
|
|
616
941
|
|
|
617
|
-
|
|
618
|
-
|
|
942
|
+
if args.verbose:
|
|
943
|
+
print(f" {C.DIM}[DEBUG] Prompt: {len(full_prompt)} chars, continue={continue_session}{C.RESET}")
|
|
619
944
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
945
|
+
# Show "Claude is working" indicator
|
|
946
|
+
print(f"\n {C.BRIGHT_BLUE}◆ Agent active...{C.RESET}\n")
|
|
947
|
+
sys.stdout.flush()
|
|
948
|
+
|
|
949
|
+
# Run Claude with real-time streaming output
|
|
950
|
+
output = run_claude_code(
|
|
951
|
+
full_prompt, working_dir,
|
|
952
|
+
continue_session=continue_session,
|
|
953
|
+
stream=True, timeout=args.timeout,
|
|
954
|
+
input_monitor=input_monitor
|
|
955
|
+
)
|
|
623
956
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
957
|
+
print(f"\n{C.BRIGHT_BLUE} {'─' * 70}{C.RESET}")
|
|
958
|
+
|
|
959
|
+
# Save log
|
|
960
|
+
save_log(iteration, output, session_id)
|
|
961
|
+
iteration_history.append(output)
|
|
962
|
+
|
|
963
|
+
# Check for user interrupt commands
|
|
964
|
+
if "[DAVELOOP:INTERRUPTED:" in output:
|
|
965
|
+
# Extract the command name
|
|
966
|
+
cmd_start = output.index("[DAVELOOP:INTERRUPTED:") + len("[DAVELOOP:INTERRUPTED:")
|
|
967
|
+
cmd_end = output.index("]", cmd_start)
|
|
968
|
+
user_cmd = output[cmd_start:cmd_end]
|
|
969
|
+
|
|
970
|
+
if user_cmd in ("wait", "pause"):
|
|
971
|
+
# Pause and get user correction
|
|
972
|
+
print(f"\n{C.BRIGHT_YELLOW}{C.BOLD} \u23f8 PAUSED - DaveLoop is waiting for your input{C.RESET}")
|
|
973
|
+
print(f"{C.BRIGHT_YELLOW} {'─' * 70}{C.RESET}")
|
|
974
|
+
print(f" {C.WHITE} Type your correction or additional context:{C.RESET}")
|
|
975
|
+
try:
|
|
976
|
+
human_input = input(f" {C.WHITE}> {C.RESET}")
|
|
977
|
+
except EOFError:
|
|
978
|
+
human_input = ""
|
|
979
|
+
input_monitor.resume_reading()
|
|
980
|
+
context = f"""
|
|
981
|
+
## Human Correction (pause/wait command)
|
|
982
|
+
|
|
983
|
+
{human_input}
|
|
984
|
+
|
|
985
|
+
Continue debugging with this corrected context. Use the reasoning protocol before each action.
|
|
986
|
+
"""
|
|
987
|
+
continue
|
|
988
|
+
|
|
989
|
+
elif user_cmd == "add":
|
|
990
|
+
# Prompt for new task, then resume current
|
|
991
|
+
print(f"\n {C.BRIGHT_CYAN}Enter new task description:{C.RESET}")
|
|
992
|
+
try:
|
|
993
|
+
new_desc = input(f" {C.WHITE}> {C.RESET}")
|
|
994
|
+
except EOFError:
|
|
995
|
+
new_desc = ""
|
|
996
|
+
input_monitor.resume_reading()
|
|
997
|
+
if new_desc.strip():
|
|
998
|
+
task_queue.add(new_desc.strip())
|
|
999
|
+
print(f" {C.GREEN}✓{C.RESET} Task added to queue")
|
|
1000
|
+
task_queue.summary_display()
|
|
1001
|
+
# Resume current task with --continue
|
|
1002
|
+
context = f"""
|
|
1003
|
+
## Continuing after user added a new task to the queue
|
|
1004
|
+
|
|
1005
|
+
Continue the current debugging task. Use the reasoning protocol before each action.
|
|
1006
|
+
"""
|
|
1007
|
+
continue
|
|
1008
|
+
|
|
1009
|
+
elif user_cmd == "done":
|
|
1010
|
+
# Clean exit
|
|
1011
|
+
input_monitor.stop()
|
|
1012
|
+
session_entry = summarize_session(bug_input, "DONE_BY_USER", iteration)
|
|
1013
|
+
history_data["sessions"].append(session_entry)
|
|
1014
|
+
save_history(working_dir, history_data)
|
|
1015
|
+
print(f"\n {C.GREEN}✓{C.RESET} Session saved. Exiting by user request.")
|
|
1016
|
+
return 0
|
|
1017
|
+
|
|
1018
|
+
# Check exit condition
|
|
1019
|
+
signal, should_exit = check_exit_condition(output)
|
|
1020
|
+
|
|
1021
|
+
if should_exit:
|
|
1022
|
+
if signal == "RESOLVED":
|
|
1023
|
+
print_success_box("")
|
|
1024
|
+
print(f" {C.DIM}Session: {session_id}{C.RESET}")
|
|
1025
|
+
print(f" {C.DIM}Logs: {LOG_DIR}{C.RESET}\n")
|
|
1026
|
+
task_queue.mark_done()
|
|
1027
|
+
session_entry = summarize_session(bug_input, "RESOLVED", iteration)
|
|
1028
|
+
history_data["sessions"].append(session_entry)
|
|
1029
|
+
save_history(working_dir, history_data)
|
|
1030
|
+
break # Move to next task
|
|
1031
|
+
elif signal == "CLARIFY":
|
|
1032
|
+
print_warning_box("Claude needs clarification")
|
|
1033
|
+
print(f"\n {C.BLUE}Your response:{C.RESET}")
|
|
1034
|
+
try:
|
|
1035
|
+
human_input = input(f" {C.WHITE}> {C.RESET}")
|
|
1036
|
+
except EOFError:
|
|
1037
|
+
human_input = ""
|
|
1038
|
+
input_monitor.resume_reading()
|
|
1039
|
+
context = f"""
|
|
648
1040
|
## Human Clarification
|
|
649
1041
|
|
|
650
1042
|
{human_input}
|
|
651
1043
|
|
|
652
1044
|
Continue debugging with this information. Use the reasoning protocol before each action.
|
|
653
1045
|
"""
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1046
|
+
continue
|
|
1047
|
+
elif signal == "BLOCKED":
|
|
1048
|
+
print_error_box("Claude is blocked - needs human help")
|
|
1049
|
+
print_status("Session", session_id, C.WHITE)
|
|
1050
|
+
print_status("Logs", str(LOG_DIR), C.WHITE)
|
|
1051
|
+
print()
|
|
1052
|
+
task_queue.mark_failed()
|
|
1053
|
+
session_entry = summarize_session(bug_input, "BLOCKED", iteration)
|
|
1054
|
+
history_data["sessions"].append(session_entry)
|
|
1055
|
+
save_history(working_dir, history_data)
|
|
1056
|
+
break # Move to next task
|
|
1057
|
+
else:
|
|
1058
|
+
print_error_box(f"Error occurred: {signal}")
|
|
1059
|
+
task_queue.mark_failed()
|
|
1060
|
+
session_entry = summarize_session(bug_input, "ERROR", iteration)
|
|
1061
|
+
history_data["sessions"].append(session_entry)
|
|
1062
|
+
save_history(working_dir, history_data)
|
|
1063
|
+
break # Move to next task
|
|
1064
|
+
|
|
1065
|
+
# Prepare context for next iteration
|
|
1066
|
+
context = f"""
|
|
667
1067
|
## Iteration {iteration + 1}
|
|
668
1068
|
|
|
669
1069
|
The bug is NOT yet resolved. You have full context from previous iterations.
|
|
@@ -671,23 +1071,46 @@ The bug is NOT yet resolved. You have full context from previous iterations.
|
|
|
671
1071
|
Continue debugging. Analyze what happened, determine next steps, and proceed.
|
|
672
1072
|
Use the reasoning protocol before each action.
|
|
673
1073
|
"""
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
1074
|
+
else:
|
|
1075
|
+
# Max iterations reached for this task (for-else)
|
|
1076
|
+
print_warning_box(f"Max iterations ({args.max_iterations}) reached for current task")
|
|
1077
|
+
task_queue.mark_failed()
|
|
1078
|
+
session_entry = summarize_session(bug_input, "MAX_ITERATIONS", args.max_iterations)
|
|
1079
|
+
history_data["sessions"].append(session_entry)
|
|
1080
|
+
save_history(working_dir, history_data)
|
|
1081
|
+
|
|
1082
|
+
# Save iteration summary for this task
|
|
1083
|
+
LOG_DIR.mkdir(exist_ok=True)
|
|
1084
|
+
summary = f"# DaveLoop Session {session_id}\n\n"
|
|
1085
|
+
summary += f"Bug: {bug_input[:200]}...\n\n"
|
|
1086
|
+
summary += f"Iterations: {len(iteration_history)}\n\n"
|
|
1087
|
+
summary += "## Iteration History\n\n"
|
|
1088
|
+
for i, hist in enumerate(iteration_history, 1):
|
|
1089
|
+
summary += f"### Iteration {i}\n```\n{hist[:500]}...\n```\n\n"
|
|
1090
|
+
(LOG_DIR / f"{session_id}_summary.md").write_text(summary, encoding="utf-8")
|
|
1091
|
+
|
|
1092
|
+
# === All tasks done - print final summary ===
|
|
1093
|
+
input_monitor.stop()
|
|
1094
|
+
|
|
1095
|
+
print(f"\n{C.BRIGHT_BLUE}{C.BOLD}◆ ALL TASKS COMPLETE{C.RESET}")
|
|
1096
|
+
print(f"{C.BRIGHT_BLUE}{'─' * 70}{C.RESET}")
|
|
1097
|
+
for task in task_queue.all():
|
|
1098
|
+
desc = task["description"][:55]
|
|
1099
|
+
status = task["status"]
|
|
1100
|
+
if status == "done":
|
|
1101
|
+
print(f" {C.BRIGHT_GREEN}✓{C.RESET} {C.WHITE}{desc}{C.RESET}")
|
|
1102
|
+
elif status == "failed":
|
|
1103
|
+
print(f" {C.BRIGHT_RED}✗{C.RESET} {C.RED}{desc}{C.RESET}")
|
|
1104
|
+
else:
|
|
1105
|
+
print(f" {C.DIM}○ {desc}{C.RESET}")
|
|
679
1106
|
print()
|
|
680
1107
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
summary += f"Bug: {bug_input[:200]}...\n\n"
|
|
684
|
-
summary += f"Iterations: {args.max_iterations}\n\n"
|
|
685
|
-
summary += "## Iteration History\n\n"
|
|
686
|
-
for i, hist in enumerate(iteration_history, 1):
|
|
687
|
-
summary += f"### Iteration {i}\n```\n{hist[:500]}...\n```\n\n"
|
|
688
|
-
(LOG_DIR / f"{session_id}_summary.md").write_text(summary, encoding="utf-8")
|
|
1108
|
+
print(f" {C.DIM}Session: {session_id}{C.RESET}")
|
|
1109
|
+
print(f" {C.DIM}Logs: {LOG_DIR}{C.RESET}\n")
|
|
689
1110
|
|
|
690
|
-
|
|
1111
|
+
# Return 0 if all tasks done, 1 if any failed
|
|
1112
|
+
all_done = all(t["status"] == "done" for t in task_queue.all())
|
|
1113
|
+
return 0 if all_done else 1
|
|
691
1114
|
|
|
692
1115
|
|
|
693
1116
|
if __name__ == "__main__":
|
daveloop-1.1.0.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
daveloop.py,sha256=qSdddbGuqeAbwpKRrloy9BHfReRp5KqemJoTkO4OgeQ,28496
|
|
2
|
-
daveloop_swebench.py,sha256=iD9AU3XRiMQpt7TknFNlvnmPCNp64V-JaTfqTFgsGBM,15996
|
|
3
|
-
daveloop-1.1.0.dist-info/METADATA,sha256=AQ6vfEzvM2yGDxvuHROHjki3QS_w_CpCFJ1KE6D482M,10285
|
|
4
|
-
daveloop-1.1.0.dist-info/WHEEL,sha256=hPN0AlP2dZM_3ZJZWP4WooepkmU9wzjGgCLCeFjkHLA,92
|
|
5
|
-
daveloop-1.1.0.dist-info/entry_points.txt,sha256=QcFAZgFrDfPtIikNQb7eW9DxOpBK7T-qWrKqbGAS9Ww,86
|
|
6
|
-
daveloop-1.1.0.dist-info/top_level.txt,sha256=36DiYt70m4DIK8t7IhV_y6hAzUIyeb5-qDUf3-gbDdg,27
|
|
7
|
-
daveloop-1.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|