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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: daveloop
3
- Version: 1.1.0
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.46.3)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
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
- {C.BRIGHT_BLUE}{C.BOLD}
84
- ██████╗ █████╗ ██╗ ██╗███████╗██╗ ██████╗ ██████╗ ██████╗
85
- ██╔══██╗██╔══██╗██║ ██║██╔════╝██║ ██╔═══██╗██╔═══██╗██╔══██╗
86
- ██║ ██║███████║██║ ██║█████╗ ██║ ██║ ██║██║ ██║██████╔╝
87
- ██║ ██║██╔══██║╚██╗ ██╔╝██╔══╝ ██║ ██║ ██║██║ ██║██╔═══╝
88
- ██████╔╝██║ ██║ ╚████╔╝ ███████╗███████╗╚██████╔╝╚██████╔╝██║
89
- ╚═════╝ ╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝
90
- {C.RESET}
91
- {C.BRIGHT_WHITE}{C.BOLD} Self-Healing Debug Agent{C.RESET}
92
- {C.WHITE} Powered by Claude Code - Autonomous Mode{C.RESET}
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"\n{color}{C.BOLD}{title}{C.RESET}")
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}{'─'*len(title)}{C.RESET}\n")
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.WHITE}{label}:{C.RESET} {color}{value}{C.RESET}")
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 = 30
116
+ bar_width = 25
116
117
  filled = int(bar_width * progress)
117
- bar = f"{C.BLUE}{'█' * filled}{C.DIM}{'░' * (bar_width - filled)}{C.RESET}"
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"\n{C.BOLD}{C.WHITE}{iteration_text}{C.RESET} {bar} {C.BRIGHT_BLUE}{percentage_text}{C.RESET}\n")
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"\n{C.BRIGHT_GREEN}{C.BOLD}")
127
- print(" ███████╗ ██╗ ██╗ ██████╗ ██████╗ ███████╗ ███████╗ ███████╗")
128
- print(" ██╔════╝ ██║ ██║ ██╔════╝ ██╔════╝ ██╔════╝ ██╔════╝ ██╔════╝")
129
- print(" ███████╗ ██║ ██║ ██║ ██║ █████╗ ███████╗ ███████╗")
130
- print(" ╚════██║ ██║ ██║ ██║ ██║ ██╔══╝ ╚════██║ ╚════██║")
131
- print(" ███████║ ╚██████╔╝ ╚██████╗ ╚██████╗ ███████╗ ███████║ ███████║")
132
- print(" ╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚══════╝ ╚══════╝")
133
- print()
134
- print(f" {C.BRIGHT_YELLOW}★ ★ ★{C.RESET}{C.BRIGHT_GREEN}{C.BOLD} {C.BRIGHT_WHITE}BUG SUCCESSFULLY RESOLVED{C.RESET}{C.BRIGHT_GREEN}{C.BOLD} {C.BRIGHT_YELLOW}★ ★ ★{C.RESET}")
135
- print()
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"\n{C.BRIGHT_RED}{C.BOLD}✗ ERROR: {C.WHITE}{message}{C.RESET}\n")
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"\n{C.BRIGHT_YELLOW}{C.BOLD}⚠ WARNING: {C.WHITE}{message}{C.RESET}\n")
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
- # Heartbeat thread to show we're alive
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
- print(f"\n {C.BLUE}🔧 Using tool: {tool_name}{C.RESET}")
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 result
421
- print(f" {C.BLUE} Tool completed{C.RESET}\n")
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
- heartbeat_active = False
446
- print(f"\r{' '*40}\r", end='') # Clear final heartbeat
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"\n{C.BRIGHT_BLUE}{'─'*50}\n 🧠 REASONING\n{'─'*50}{C.RESET}"
766
+ return f"""
767
+ {C.BRIGHT_BLUE} ┌─ 🧠 REASONING ─────────────────────────────────────────────────────┐{C.RESET}"""
480
768
  if "===========================" in line:
481
- return f"{C.BRIGHT_BLUE}{'─'*50}{C.RESET}\n"
769
+ return f"{C.BRIGHT_BLUE} └─────────────────────────────────────────────────────────────────────┘{C.RESET}"
482
770
 
483
771
  # Reasoning labels
484
772
  if line.startswith("KNOWN:"):
485
- return f" {C.BLUE}KNOWN:{C.RESET}{C.WHITE}{line[6:]}{C.RESET}"
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.BLUE}UNKNOWN:{C.RESET}{C.WHITE}{line[8:]}{C.RESET}"
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.BLUE}HYPOTHESIS:{C.RESET}{C.WHITE}{line[11:]}{C.RESET}"
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.BLUE}NEXT ACTION:{C.RESET}{C.WHITE}{line[12:]}{C.RESET}"
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.BLUE}WHY:{C.RESET}{C.WHITE}{line[4:]}{C.RESET}"
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 - just dim them out in the stream, don't make them prominent
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 f" {C.DIM}→ [Exit signal detected: RESOLVED]{C.RESET}"
785
+ return ""
499
786
  if "[DAVELOOP:BLOCKED]" in line:
500
- return f" {C.DIM}→ [Exit signal detected: BLOCKED]{C.RESET}"
787
+ return ""
501
788
  if "[DAVELOOP:CLARIFY]" in line:
502
- return f" {C.DIM}→ [Exit signal detected: CLARIFY]{C.RESET}"
789
+ return ""
503
790
 
504
- # Code blocks
791
+ # Code blocks - hide the markers
505
792
  if line.strip().startswith("```"):
506
- return f"{C.BLUE}{'─'*40}{C.RESET}"
793
+ return ""
507
794
 
508
- # Default - white text
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="?", help="Bug description or error message")
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
- # Get bug description
847
+ # Collect bug descriptions
848
+ bug_descriptions = []
557
849
  if args.file:
558
- bug_input = Path(args.file).read_text(encoding="utf-8")
850
+ bug_descriptions.append(Path(args.file).read_text(encoding="utf-8"))
559
851
  elif args.bug:
560
- bug_input = args.bug
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
- bug_input = sys.stdin.read().strip()
855
+ stdin_input = sys.stdin.read().strip()
856
+ if stdin_input:
857
+ bug_descriptions.append(stdin_input)
564
858
 
565
- if not bug_input:
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("Working Directory", working_dir, C.WHITE)
577
- print_status("Max Iterations", str(args.max_iterations), C.WHITE)
578
- print_status("Timeout", f"{args.timeout // 60} min ({args.timeout}s) per iteration", C.WHITE)
579
- print_status("Context Mode", "PERSISTENT (--continue)", C.WHITE)
580
- print_status("System Prompt", f"{len(system_prompt)} chars loaded", C.WHITE)
581
- print()
582
-
583
- print_section("BUG REPORT", C.BRIGHT_RED)
584
- # Wrap bug input nicely
585
- for line in bug_input.split('\n')[:10]:
586
- print(f" {C.RED}{line[:80]}{C.RESET}")
587
- if len(bug_input.split('\n')) > 10:
588
- print(f" {C.RED}... ({len(bug_input.split(chr(10))) - 10} more lines){C.RESET}")
589
- print()
590
-
591
- sys.stdout.flush()
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
- # Initial context
594
- context = f"""
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
- iteration_history = []
930
+ iteration_history = []
606
931
 
607
- for iteration in range(1, args.max_iterations + 1):
608
- print_iteration_header(iteration, args.max_iterations)
932
+ # === INNER LOOP: iterations for current task ===
933
+ for iteration in range(1, args.max_iterations + 1):
609
934
 
610
- if iteration == 1:
611
- full_prompt = f"{system_prompt}\n\n---\n\n{context}"
612
- continue_session = False
613
- else:
614
- full_prompt = context
615
- continue_session = True
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
- if args.verbose:
618
- print(f" {C.DIM}[DEBUG] Prompt: {len(full_prompt)} chars, continue={continue_session}{C.RESET}")
942
+ if args.verbose:
943
+ print(f" {C.DIM}[DEBUG] Prompt: {len(full_prompt)} chars, continue={continue_session}{C.RESET}")
619
944
 
620
- # Show "Claude is working" indicator
621
- print(f"\n {C.BRIGHT_BLUE} Claude is working...{C.RESET}\n")
622
- sys.stdout.flush()
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
- # Run Claude with real-time streaming output
625
- output = run_claude_code(full_prompt, working_dir, continue_session=continue_session, stream=True, timeout=args.timeout)
626
-
627
- print(f"\n {C.BLUE}✓ Iteration complete{C.RESET}\n")
628
-
629
- # Save log
630
- save_log(iteration, output, session_id)
631
- iteration_history.append(output)
632
-
633
- # Check exit condition
634
- signal, should_exit = check_exit_condition(output)
635
-
636
- if should_exit:
637
- if signal == "RESOLVED":
638
- print_success_box(f"Bug fixed in {iteration} iteration(s)!")
639
- print_status("Session", session_id, C.WHITE)
640
- print_status("Logs", str(LOG_DIR), C.WHITE)
641
- print()
642
- return 0
643
- elif signal == "CLARIFY":
644
- print_warning_box("Claude needs clarification")
645
- print(f"\n {C.BLUE}Your response:{C.RESET}")
646
- human_input = input(f" {C.WHITE}> {C.RESET}")
647
- context = f"""
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
- continue
655
- elif signal == "BLOCKED":
656
- print_error_box(f"Claude is blocked - needs human help")
657
- print_status("Session", session_id, C.WHITE)
658
- print_status("Logs", str(LOG_DIR), C.WHITE)
659
- print()
660
- return 1
661
- else:
662
- print_error_box(f"Error occurred: {signal}")
663
- return 1
664
-
665
- # Prepare context for next iteration
666
- context = f"""
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
- # Max iterations reached
676
- print_warning_box(f"Max iterations ({args.max_iterations}) reached")
677
- print_status("Session", session_id, C.WHITE)
678
- print_status("Logs", str(LOG_DIR), C.WHITE)
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
- # Save summary
682
- summary = f"# DaveLoop Session {session_id}\n\n"
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
- return 1
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__":
@@ -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,,