daveloop 1.0.0__py3-none-any.whl → 1.1.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/METADATA +383 -0
- daveloop-1.1.0.dist-info/RECORD +7 -0
- {daveloop-1.0.0.dist-info → daveloop-1.1.0.dist-info}/WHEEL +1 -1
- daveloop.py +694 -716
- daveloop-1.0.0.dist-info/METADATA +0 -78
- daveloop-1.0.0.dist-info/RECORD +0 -7
- {daveloop-1.0.0.dist-info → daveloop-1.1.0.dist-info}/entry_points.txt +0 -0
- {daveloop-1.0.0.dist-info → daveloop-1.1.0.dist-info}/top_level.txt +0 -0
daveloop.py
CHANGED
|
@@ -1,716 +1,694 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
DaveLoop - Self-Healing Debug Agent
|
|
4
|
-
Orchestrates Claude Code CLI in a feedback loop until bugs are resolved.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import subprocess
|
|
8
|
-
import sys
|
|
9
|
-
import os
|
|
10
|
-
import argparse
|
|
11
|
-
import threading
|
|
12
|
-
import time
|
|
13
|
-
import itertools
|
|
14
|
-
from datetime import datetime
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
|
|
17
|
-
# Configuration
|
|
18
|
-
MAX_ITERATIONS = 20
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
C
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
{C.
|
|
118
|
-
|
|
119
|
-
""
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def
|
|
125
|
-
"""Print
|
|
126
|
-
print(f"\n{
|
|
127
|
-
print(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
"
|
|
131
|
-
print(
|
|
132
|
-
print(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
print(f"
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
"""
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
"
|
|
329
|
-
"
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
return "[
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
""
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
if "
|
|
506
|
-
return f"{C.
|
|
507
|
-
|
|
508
|
-
#
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
if
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if "[DAVELOOP:
|
|
523
|
-
return
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
def
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
for
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
"""
|
|
696
|
-
|
|
697
|
-
# Max iterations reached
|
|
698
|
-
print_warning_box(f"Max iterations ({args.max_iterations}) reached")
|
|
699
|
-
print_status("Session", session_id, C.WHITE)
|
|
700
|
-
print_status("Logs", str(LOG_DIR), C.WHITE)
|
|
701
|
-
print()
|
|
702
|
-
|
|
703
|
-
# Save summary
|
|
704
|
-
summary = f"# DaveLoop Session {session_id}\n\n"
|
|
705
|
-
summary += f"Bug: {bug_input[:200]}...\n\n"
|
|
706
|
-
summary += f"Iterations: {args.max_iterations}\n\n"
|
|
707
|
-
summary += "## Iteration History\n\n"
|
|
708
|
-
for i, hist in enumerate(iteration_history, 1):
|
|
709
|
-
summary += f"### Iteration {i}\n```\n{hist[:500]}...\n```\n\n"
|
|
710
|
-
(LOG_DIR / f"{session_id}_summary.md").write_text(summary, encoding="utf-8")
|
|
711
|
-
|
|
712
|
-
return 1
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
if __name__ == "__main__":
|
|
716
|
-
sys.exit(main())
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
DaveLoop - Self-Healing Debug Agent
|
|
4
|
+
Orchestrates Claude Code CLI in a feedback loop until bugs are resolved.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import os
|
|
10
|
+
import argparse
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
import itertools
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
# Configuration
|
|
18
|
+
MAX_ITERATIONS = 20
|
|
19
|
+
DEFAULT_TIMEOUT = 600 # 10 minutes in seconds
|
|
20
|
+
SCRIPT_DIR = Path(__file__).parent
|
|
21
|
+
PROMPT_FILE = SCRIPT_DIR / "daveloop_prompt.md"
|
|
22
|
+
LOG_DIR = SCRIPT_DIR / "logs"
|
|
23
|
+
|
|
24
|
+
# Exit signals from Claude Code
|
|
25
|
+
SIGNAL_RESOLVED = "[DAVELOOP:RESOLVED]"
|
|
26
|
+
SIGNAL_BLOCKED = "[DAVELOOP:BLOCKED]"
|
|
27
|
+
SIGNAL_CLARIFY = "[DAVELOOP:CLARIFY]"
|
|
28
|
+
|
|
29
|
+
# ============================================================================
|
|
30
|
+
# ANSI Color Codes
|
|
31
|
+
# ============================================================================
|
|
32
|
+
class Colors:
|
|
33
|
+
RESET = "\033[0m"
|
|
34
|
+
BOLD = "\033[1m"
|
|
35
|
+
DIM = "\033[2m"
|
|
36
|
+
|
|
37
|
+
# Foreground
|
|
38
|
+
BLACK = "\033[30m"
|
|
39
|
+
RED = "\033[31m"
|
|
40
|
+
GREEN = "\033[32m"
|
|
41
|
+
YELLOW = "\033[33m"
|
|
42
|
+
BLUE = "\033[34m"
|
|
43
|
+
MAGENTA = "\033[35m"
|
|
44
|
+
CYAN = "\033[36m"
|
|
45
|
+
WHITE = "\033[37m"
|
|
46
|
+
|
|
47
|
+
# Bright foreground
|
|
48
|
+
BRIGHT_RED = "\033[91m"
|
|
49
|
+
BRIGHT_GREEN = "\033[92m"
|
|
50
|
+
BRIGHT_YELLOW = "\033[93m"
|
|
51
|
+
BRIGHT_BLUE = "\033[94m"
|
|
52
|
+
BRIGHT_MAGENTA = "\033[95m"
|
|
53
|
+
BRIGHT_CYAN = "\033[96m"
|
|
54
|
+
BRIGHT_WHITE = "\033[97m"
|
|
55
|
+
|
|
56
|
+
# Background
|
|
57
|
+
BG_BLACK = "\033[40m"
|
|
58
|
+
BG_RED = "\033[41m"
|
|
59
|
+
BG_GREEN = "\033[42m"
|
|
60
|
+
BG_BLUE = "\033[44m"
|
|
61
|
+
BG_MAGENTA = "\033[45m"
|
|
62
|
+
BG_CYAN = "\033[46m"
|
|
63
|
+
|
|
64
|
+
C = Colors # Shorthand
|
|
65
|
+
|
|
66
|
+
# Enable ANSI and UTF-8 on Windows
|
|
67
|
+
if sys.platform == "win32":
|
|
68
|
+
os.system("chcp 65001 >nul 2>&1") # Set console to UTF-8
|
|
69
|
+
os.system("") # Enables ANSI escape sequences in Windows terminal
|
|
70
|
+
# Force UTF-8 encoding for stdout/stderr (only if not already wrapped)
|
|
71
|
+
import io
|
|
72
|
+
if not isinstance(sys.stdout, io.TextIOWrapper) or sys.stdout.encoding != 'utf-8':
|
|
73
|
+
if hasattr(sys.stdout, 'buffer'):
|
|
74
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
|
75
|
+
if not isinstance(sys.stderr, io.TextIOWrapper) or sys.stderr.encoding != 'utf-8':
|
|
76
|
+
if hasattr(sys.stderr, 'buffer'):
|
|
77
|
+
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
|
78
|
+
|
|
79
|
+
# ============================================================================
|
|
80
|
+
# ASCII Art Banner
|
|
81
|
+
# ============================================================================
|
|
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
|
+
"""
|
|
94
|
+
|
|
95
|
+
# ============================================================================
|
|
96
|
+
# UI Components
|
|
97
|
+
# ============================================================================
|
|
98
|
+
def print_header_box(title: str, color: str = C.BRIGHT_BLUE):
|
|
99
|
+
"""Print a header."""
|
|
100
|
+
print(f"\n{color}{C.BOLD}{title}{C.RESET}")
|
|
101
|
+
print(f"{color}{'─'*len(title)}{C.RESET}\n")
|
|
102
|
+
|
|
103
|
+
def print_section(title: str, color: str = C.BRIGHT_BLUE):
|
|
104
|
+
"""Print a section divider."""
|
|
105
|
+
print(f"\n{color}{C.BOLD}{title}{C.RESET}")
|
|
106
|
+
print(f"{color}{'─'*len(title)}{C.RESET}\n")
|
|
107
|
+
|
|
108
|
+
def print_status(label: str, value: str, color: str = C.WHITE):
|
|
109
|
+
"""Print a status line."""
|
|
110
|
+
print(f" {C.WHITE}{label}:{C.RESET} {color}{value}{C.RESET}")
|
|
111
|
+
|
|
112
|
+
def print_iteration_header(iteration: int, max_iter: int):
|
|
113
|
+
"""Print the iteration header with visual progress."""
|
|
114
|
+
progress = iteration / max_iter
|
|
115
|
+
bar_width = 30
|
|
116
|
+
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)}%"
|
|
121
|
+
|
|
122
|
+
print(f"\n{C.BOLD}{C.WHITE}{iteration_text}{C.RESET} {bar} {C.BRIGHT_BLUE}{percentage_text}{C.RESET}\n")
|
|
123
|
+
|
|
124
|
+
def print_success_box(message: str):
|
|
125
|
+
"""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")
|
|
138
|
+
|
|
139
|
+
def print_error_box(message: str):
|
|
140
|
+
"""Print an error message."""
|
|
141
|
+
print(f"\n{C.BRIGHT_RED}{C.BOLD}✗ ERROR: {C.WHITE}{message}{C.RESET}\n")
|
|
142
|
+
|
|
143
|
+
def print_warning_box(message: str):
|
|
144
|
+
"""Print a warning message."""
|
|
145
|
+
print(f"\n{C.BRIGHT_YELLOW}{C.BOLD}⚠ WARNING: {C.WHITE}{message}{C.RESET}\n")
|
|
146
|
+
|
|
147
|
+
# ============================================================================
|
|
148
|
+
# Spinner Animation
|
|
149
|
+
# ============================================================================
|
|
150
|
+
class Spinner:
|
|
151
|
+
"""Animated spinner for showing work in progress."""
|
|
152
|
+
|
|
153
|
+
def __init__(self, message: str = "Processing"):
|
|
154
|
+
self.message = message
|
|
155
|
+
self.running = False
|
|
156
|
+
self.thread = None
|
|
157
|
+
self.frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
158
|
+
self.start_time = None
|
|
159
|
+
|
|
160
|
+
def spin(self):
|
|
161
|
+
idx = 0
|
|
162
|
+
while self.running:
|
|
163
|
+
elapsed = time.time() - self.start_time
|
|
164
|
+
frame = self.frames[idx % len(self.frames)]
|
|
165
|
+
sys.stdout.write(f"\r {C.BRIGHT_CYAN}{frame}{C.RESET} {C.BOLD}{self.message}{C.RESET} {C.DIM}({elapsed:.0f}s){C.RESET} ")
|
|
166
|
+
sys.stdout.flush()
|
|
167
|
+
idx += 1
|
|
168
|
+
time.sleep(0.1)
|
|
169
|
+
|
|
170
|
+
def start(self):
|
|
171
|
+
self.running = True
|
|
172
|
+
self.start_time = time.time()
|
|
173
|
+
self.thread = threading.Thread(target=self.spin)
|
|
174
|
+
self.thread.start()
|
|
175
|
+
|
|
176
|
+
def stop(self, final_message: str = None):
|
|
177
|
+
self.running = False
|
|
178
|
+
if self.thread:
|
|
179
|
+
self.thread.join()
|
|
180
|
+
elapsed = time.time() - self.start_time
|
|
181
|
+
if final_message:
|
|
182
|
+
sys.stdout.write(f"\r {C.GREEN}✓{C.RESET} {final_message} {C.DIM}({elapsed:.1f}s){C.RESET} \n")
|
|
183
|
+
else:
|
|
184
|
+
sys.stdout.write(f"\r {C.GREEN}✓{C.RESET} {self.message} complete {C.DIM}({elapsed:.1f}s){C.RESET} \n")
|
|
185
|
+
sys.stdout.flush()
|
|
186
|
+
|
|
187
|
+
# ============================================================================
|
|
188
|
+
# Output Formatter
|
|
189
|
+
# ============================================================================
|
|
190
|
+
def format_claude_output(output: str) -> str:
|
|
191
|
+
"""Format Claude's output with colors and sections."""
|
|
192
|
+
lines = output.split('\n')
|
|
193
|
+
formatted = []
|
|
194
|
+
in_reasoning = False
|
|
195
|
+
in_code = False
|
|
196
|
+
|
|
197
|
+
for line in lines:
|
|
198
|
+
# Reasoning block
|
|
199
|
+
if "=== DAVELOOP REASONING ===" in line:
|
|
200
|
+
in_reasoning = True
|
|
201
|
+
formatted.append(f"\n{C.BRIGHT_YELLOW}┌{'─'*50}┐{C.RESET}")
|
|
202
|
+
formatted.append(f"{C.BRIGHT_YELLOW}│{C.BOLD} 🧠 REASONING{C.RESET}")
|
|
203
|
+
formatted.append(f"{C.BRIGHT_YELLOW}├{'─'*50}┤{C.RESET}")
|
|
204
|
+
continue
|
|
205
|
+
elif "===========================" in line and in_reasoning:
|
|
206
|
+
in_reasoning = False
|
|
207
|
+
formatted.append(f"{C.BRIGHT_YELLOW}└{'─'*50}┘{C.RESET}\n")
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
# Verification block
|
|
211
|
+
if "=== VERIFICATION ===" in line:
|
|
212
|
+
formatted.append(f"\n{C.BRIGHT_GREEN}┌{'─'*50}┐{C.RESET}")
|
|
213
|
+
formatted.append(f"{C.BRIGHT_GREEN}│{C.BOLD} ✓ VERIFICATION{C.RESET}")
|
|
214
|
+
formatted.append(f"{C.BRIGHT_GREEN}├{'─'*50}┤{C.RESET}")
|
|
215
|
+
continue
|
|
216
|
+
elif "====================" in line:
|
|
217
|
+
formatted.append(f"{C.BRIGHT_GREEN}└{'─'*50}┘{C.RESET}\n")
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
# Code blocks
|
|
221
|
+
if line.strip().startswith("```"):
|
|
222
|
+
in_code = not in_code
|
|
223
|
+
if in_code:
|
|
224
|
+
formatted.append(f"{C.DIM}┌─ code ────────────────────────────────{C.RESET}")
|
|
225
|
+
else:
|
|
226
|
+
formatted.append(f"{C.DIM}└───────────────────────────────────────{C.RESET}")
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
# Reasoning labels
|
|
230
|
+
if in_reasoning:
|
|
231
|
+
if line.startswith("KNOWN:"):
|
|
232
|
+
formatted.append(f"{C.BRIGHT_YELLOW}│{C.RESET} {C.CYAN}KNOWN:{C.RESET}{line[6:]}")
|
|
233
|
+
elif line.startswith("UNKNOWN:"):
|
|
234
|
+
formatted.append(f"{C.BRIGHT_YELLOW}│{C.RESET} {C.MAGENTA}UNKNOWN:{C.RESET}{line[8:]}")
|
|
235
|
+
elif line.startswith("HYPOTHESIS:"):
|
|
236
|
+
formatted.append(f"{C.BRIGHT_YELLOW}│{C.RESET} {C.YELLOW}HYPOTHESIS:{C.RESET}{line[11:]}")
|
|
237
|
+
elif line.startswith("NEXT ACTION:"):
|
|
238
|
+
formatted.append(f"{C.BRIGHT_YELLOW}│{C.RESET} {C.GREEN}NEXT ACTION:{C.RESET}{line[12:]}")
|
|
239
|
+
elif line.startswith("WHY:"):
|
|
240
|
+
formatted.append(f"{C.BRIGHT_YELLOW}│{C.RESET} {C.BLUE}WHY:{C.RESET}{line[4:]}")
|
|
241
|
+
else:
|
|
242
|
+
formatted.append(f"{C.BRIGHT_YELLOW}│{C.RESET} {line}")
|
|
243
|
+
continue
|
|
244
|
+
|
|
245
|
+
# Exit signals - dim them out, don't make prominent
|
|
246
|
+
if "[DAVELOOP:RESOLVED]" in line:
|
|
247
|
+
formatted.append(f" {C.DIM}→ [Exit signal: RESOLVED]{C.RESET}")
|
|
248
|
+
continue
|
|
249
|
+
elif "[DAVELOOP:BLOCKED]" in line:
|
|
250
|
+
formatted.append(f" {C.DIM}→ [Exit signal: BLOCKED]{C.RESET}")
|
|
251
|
+
continue
|
|
252
|
+
elif "[DAVELOOP:CLARIFY]" in line:
|
|
253
|
+
formatted.append(f" {C.DIM}→ [Exit signal: CLARIFY]{C.RESET}")
|
|
254
|
+
continue
|
|
255
|
+
|
|
256
|
+
# Code content
|
|
257
|
+
if in_code:
|
|
258
|
+
formatted.append(f"{C.DIM}│{C.RESET} {C.WHITE}{line}{C.RESET}")
|
|
259
|
+
continue
|
|
260
|
+
|
|
261
|
+
# Regular content
|
|
262
|
+
formatted.append(f" {line}")
|
|
263
|
+
|
|
264
|
+
return '\n'.join(formatted)
|
|
265
|
+
|
|
266
|
+
# ============================================================================
|
|
267
|
+
# Core Functions
|
|
268
|
+
# ============================================================================
|
|
269
|
+
def load_prompt() -> str:
|
|
270
|
+
"""Load the DaveLoop system prompt."""
|
|
271
|
+
if PROMPT_FILE.exists():
|
|
272
|
+
return PROMPT_FILE.read_text(encoding="utf-8")
|
|
273
|
+
else:
|
|
274
|
+
print_warning_box(f"Prompt file not found: {PROMPT_FILE}")
|
|
275
|
+
return "You are debugging. Fix the bug. Output [DAVELOOP:RESOLVED] when done."
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def find_claude_cli():
|
|
279
|
+
"""Find Claude CLI executable path."""
|
|
280
|
+
import platform
|
|
281
|
+
import shutil
|
|
282
|
+
|
|
283
|
+
# 1. Check environment variable (highest priority)
|
|
284
|
+
env_path = os.environ.get('CLAUDE_CLI_PATH')
|
|
285
|
+
if env_path and os.path.exists(env_path):
|
|
286
|
+
return env_path
|
|
287
|
+
|
|
288
|
+
# 2. Try common installation paths
|
|
289
|
+
is_windows = platform.system() == "Windows"
|
|
290
|
+
if is_windows:
|
|
291
|
+
common_paths = [
|
|
292
|
+
os.path.expanduser("~\\AppData\\Local\\Programs\\claude\\claude.cmd"),
|
|
293
|
+
os.path.expanduser("~\\AppData\\Roaming\\npm\\claude.cmd"),
|
|
294
|
+
"C:\\Program Files\\Claude\\claude.cmd",
|
|
295
|
+
"C:\\Program Files (x86)\\Claude\\claude.cmd",
|
|
296
|
+
]
|
|
297
|
+
for path in common_paths:
|
|
298
|
+
if os.path.exists(path):
|
|
299
|
+
return path
|
|
300
|
+
else:
|
|
301
|
+
common_paths = [
|
|
302
|
+
"/usr/local/bin/claude",
|
|
303
|
+
"/usr/bin/claude",
|
|
304
|
+
os.path.expanduser("~/.local/bin/claude"),
|
|
305
|
+
]
|
|
306
|
+
for path in common_paths:
|
|
307
|
+
if os.path.exists(path):
|
|
308
|
+
return path
|
|
309
|
+
|
|
310
|
+
# 3. Check if it's in PATH
|
|
311
|
+
claude_name = "claude.cmd" if is_windows else "claude"
|
|
312
|
+
if shutil.which(claude_name):
|
|
313
|
+
return claude_name
|
|
314
|
+
|
|
315
|
+
# 4. Not found
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def run_claude_code(prompt: str, working_dir: str = None, continue_session: bool = False, stream: bool = True, timeout: int = DEFAULT_TIMEOUT) -> str:
|
|
320
|
+
"""Execute Claude Code CLI with the given prompt.
|
|
321
|
+
|
|
322
|
+
If stream=True, output is printed in real-time and also returned.
|
|
323
|
+
timeout is in seconds (default 600 = 10 minutes).
|
|
324
|
+
"""
|
|
325
|
+
claude_cmd = find_claude_cli()
|
|
326
|
+
if not claude_cmd:
|
|
327
|
+
error_msg = (
|
|
328
|
+
"Claude CLI not found!\n\n"
|
|
329
|
+
"Please install Claude Code CLI or set CLAUDE_CLI_PATH environment variable:\n"
|
|
330
|
+
" Windows: set CLAUDE_CLI_PATH=C:\\path\\to\\claude.cmd\n"
|
|
331
|
+
" Linux/Mac: export CLAUDE_CLI_PATH=/path/to/claude\n\n"
|
|
332
|
+
"Install from: https://github.com/anthropics/claude-code"
|
|
333
|
+
)
|
|
334
|
+
print_error_box(error_msg)
|
|
335
|
+
return "[DAVELOOP:ERROR] Claude CLI not found"
|
|
336
|
+
|
|
337
|
+
cmd = [claude_cmd]
|
|
338
|
+
|
|
339
|
+
if continue_session:
|
|
340
|
+
cmd.append("--continue")
|
|
341
|
+
|
|
342
|
+
cmd.extend(["-p", "--verbose", "--output-format", "stream-json", "--allowedTools", "Bash,Read,Write,Edit,Glob,Grep,Task"])
|
|
343
|
+
|
|
344
|
+
try:
|
|
345
|
+
if stream:
|
|
346
|
+
# Stream output in real-time
|
|
347
|
+
process = subprocess.Popen(
|
|
348
|
+
cmd,
|
|
349
|
+
stdin=subprocess.PIPE,
|
|
350
|
+
stdout=subprocess.PIPE,
|
|
351
|
+
stderr=subprocess.STDOUT,
|
|
352
|
+
text=True,
|
|
353
|
+
encoding='utf-8',
|
|
354
|
+
errors='replace',
|
|
355
|
+
cwd=working_dir,
|
|
356
|
+
bufsize=1 # Line buffered
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Send prompt and close stdin
|
|
360
|
+
process.stdin.write(prompt)
|
|
361
|
+
process.stdin.close()
|
|
362
|
+
|
|
363
|
+
# Heartbeat thread to show we're alive
|
|
364
|
+
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
|
+
|
|
377
|
+
# Read and display JSON stream output
|
|
378
|
+
import json
|
|
379
|
+
output_lines = []
|
|
380
|
+
full_text = []
|
|
381
|
+
|
|
382
|
+
for line in process.stdout:
|
|
383
|
+
# Clear heartbeat line
|
|
384
|
+
print(f"\r{' '*40}\r", end='')
|
|
385
|
+
|
|
386
|
+
line = line.strip()
|
|
387
|
+
if not line:
|
|
388
|
+
continue
|
|
389
|
+
|
|
390
|
+
try:
|
|
391
|
+
data = json.loads(line)
|
|
392
|
+
msg_type = data.get("type", "")
|
|
393
|
+
|
|
394
|
+
# Handle different message types
|
|
395
|
+
if msg_type == "assistant":
|
|
396
|
+
# Assistant text message
|
|
397
|
+
content = data.get("message", {}).get("content", [])
|
|
398
|
+
for block in content:
|
|
399
|
+
if block.get("type") == "text":
|
|
400
|
+
text = block.get("text", "")
|
|
401
|
+
for line_text in text.split('\n'):
|
|
402
|
+
formatted = format_output_line(line_text)
|
|
403
|
+
print(formatted)
|
|
404
|
+
full_text.append(text)
|
|
405
|
+
|
|
406
|
+
elif msg_type == "content_block_delta":
|
|
407
|
+
# Streaming text delta
|
|
408
|
+
delta = data.get("delta", {})
|
|
409
|
+
if delta.get("type") == "text_delta":
|
|
410
|
+
text = delta.get("text", "")
|
|
411
|
+
print(text, end='')
|
|
412
|
+
full_text.append(text)
|
|
413
|
+
|
|
414
|
+
elif msg_type == "tool_use":
|
|
415
|
+
# Tool being used
|
|
416
|
+
tool_name = data.get("name", "unknown")
|
|
417
|
+
print(f"\n {C.BLUE}🔧 Using tool: {tool_name}{C.RESET}")
|
|
418
|
+
|
|
419
|
+
elif msg_type == "tool_result":
|
|
420
|
+
# Tool result
|
|
421
|
+
print(f" {C.BLUE}✓ Tool completed{C.RESET}\n")
|
|
422
|
+
|
|
423
|
+
elif msg_type == "result":
|
|
424
|
+
# Final result
|
|
425
|
+
text = data.get("result", "")
|
|
426
|
+
if text:
|
|
427
|
+
for line_text in text.split('\n'):
|
|
428
|
+
formatted = format_output_line(line_text)
|
|
429
|
+
print(formatted)
|
|
430
|
+
full_text.append(text)
|
|
431
|
+
|
|
432
|
+
elif msg_type == "error":
|
|
433
|
+
error_msg = data.get("error", {}).get("message", "Unknown error")
|
|
434
|
+
print(f" {C.RED}ERROR: {error_msg}{C.RESET}")
|
|
435
|
+
|
|
436
|
+
sys.stdout.flush()
|
|
437
|
+
|
|
438
|
+
except json.JSONDecodeError:
|
|
439
|
+
# Not JSON, just print as-is
|
|
440
|
+
print(f" {line}")
|
|
441
|
+
full_text.append(line)
|
|
442
|
+
|
|
443
|
+
output_lines.append(line)
|
|
444
|
+
|
|
445
|
+
heartbeat_active = False
|
|
446
|
+
print(f"\r{' '*40}\r", end='') # Clear final heartbeat
|
|
447
|
+
|
|
448
|
+
process.wait(timeout=timeout)
|
|
449
|
+
return '\n'.join(full_text)
|
|
450
|
+
else:
|
|
451
|
+
# Non-streaming mode
|
|
452
|
+
result = subprocess.run(
|
|
453
|
+
cmd,
|
|
454
|
+
input=prompt,
|
|
455
|
+
capture_output=True,
|
|
456
|
+
text=True,
|
|
457
|
+
encoding='utf-8',
|
|
458
|
+
errors='replace',
|
|
459
|
+
cwd=working_dir,
|
|
460
|
+
timeout=timeout
|
|
461
|
+
)
|
|
462
|
+
output = result.stdout
|
|
463
|
+
if result.stderr:
|
|
464
|
+
output += f"\n{C.RED}[STDERR]{C.RESET}\n{result.stderr}"
|
|
465
|
+
return output
|
|
466
|
+
|
|
467
|
+
except subprocess.TimeoutExpired:
|
|
468
|
+
return f"[DAVELOOP:TIMEOUT] Claude Code iteration timed out after {timeout // 60} minutes"
|
|
469
|
+
except FileNotFoundError:
|
|
470
|
+
return "[DAVELOOP:ERROR] Claude Code CLI not found. Is it installed?"
|
|
471
|
+
except Exception as e:
|
|
472
|
+
return f"[DAVELOOP:ERROR] {str(e)}"
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def format_output_line(line: str) -> str:
|
|
476
|
+
"""Format a single line of Claude's output with colors."""
|
|
477
|
+
# Reasoning markers
|
|
478
|
+
if "=== DAVELOOP REASONING ===" in line:
|
|
479
|
+
return f"\n{C.BRIGHT_BLUE}{'─'*50}\n 🧠 REASONING\n{'─'*50}{C.RESET}"
|
|
480
|
+
if "===========================" in line:
|
|
481
|
+
return f"{C.BRIGHT_BLUE}{'─'*50}{C.RESET}\n"
|
|
482
|
+
|
|
483
|
+
# Reasoning labels
|
|
484
|
+
if line.startswith("KNOWN:"):
|
|
485
|
+
return f" {C.BLUE}KNOWN:{C.RESET}{C.WHITE}{line[6:]}{C.RESET}"
|
|
486
|
+
if line.startswith("UNKNOWN:"):
|
|
487
|
+
return f" {C.BLUE}UNKNOWN:{C.RESET}{C.WHITE}{line[8:]}{C.RESET}"
|
|
488
|
+
if line.startswith("HYPOTHESIS:"):
|
|
489
|
+
return f" {C.BLUE}HYPOTHESIS:{C.RESET}{C.WHITE}{line[11:]}{C.RESET}"
|
|
490
|
+
if line.startswith("NEXT ACTION:"):
|
|
491
|
+
return f" {C.BLUE}NEXT ACTION:{C.RESET}{C.WHITE}{line[12:]}{C.RESET}"
|
|
492
|
+
if line.startswith("WHY:"):
|
|
493
|
+
return f" {C.BLUE}WHY:{C.RESET}{C.WHITE}{line[4:]}{C.RESET}"
|
|
494
|
+
|
|
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
|
|
497
|
+
if "[DAVELOOP:RESOLVED]" in line:
|
|
498
|
+
return f" {C.DIM}→ [Exit signal detected: RESOLVED]{C.RESET}"
|
|
499
|
+
if "[DAVELOOP:BLOCKED]" in line:
|
|
500
|
+
return f" {C.DIM}→ [Exit signal detected: BLOCKED]{C.RESET}"
|
|
501
|
+
if "[DAVELOOP:CLARIFY]" in line:
|
|
502
|
+
return f" {C.DIM}→ [Exit signal detected: CLARIFY]{C.RESET}"
|
|
503
|
+
|
|
504
|
+
# Code blocks
|
|
505
|
+
if line.strip().startswith("```"):
|
|
506
|
+
return f"{C.BLUE}{'─'*40}{C.RESET}"
|
|
507
|
+
|
|
508
|
+
# Default - white text
|
|
509
|
+
return f" {C.WHITE}{line}{C.RESET}"
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def check_exit_condition(output: str) -> tuple[str, bool]:
|
|
513
|
+
"""Check if we should exit the loop."""
|
|
514
|
+
if SIGNAL_RESOLVED in output:
|
|
515
|
+
return "RESOLVED", True
|
|
516
|
+
if SIGNAL_BLOCKED in output:
|
|
517
|
+
return "BLOCKED", True
|
|
518
|
+
if SIGNAL_CLARIFY in output:
|
|
519
|
+
return "CLARIFY", True
|
|
520
|
+
if "[DAVELOOP:ERROR]" in output:
|
|
521
|
+
return "ERROR", True
|
|
522
|
+
if "[DAVELOOP:TIMEOUT]" in output:
|
|
523
|
+
return "TIMEOUT", False
|
|
524
|
+
return "CONTINUE", False
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def save_log(iteration: int, content: str, session_id: str):
|
|
528
|
+
"""Save iteration log to file."""
|
|
529
|
+
LOG_DIR.mkdir(exist_ok=True)
|
|
530
|
+
log_file = LOG_DIR / f"{session_id}_iteration_{iteration:02d}.log"
|
|
531
|
+
log_file.write_text(content, encoding="utf-8")
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
# ============================================================================
|
|
535
|
+
# Main Entry Point
|
|
536
|
+
# ============================================================================
|
|
537
|
+
def main():
|
|
538
|
+
parser = argparse.ArgumentParser(
|
|
539
|
+
description="DaveLoop - Self-Healing Debug Agent",
|
|
540
|
+
formatter_class=argparse.RawDescriptionHelpFormatter
|
|
541
|
+
)
|
|
542
|
+
parser.add_argument("bug", nargs="?", help="Bug description or error message")
|
|
543
|
+
parser.add_argument("-f", "--file", help="Read bug description from file")
|
|
544
|
+
parser.add_argument("-d", "--dir", help="Working directory for Claude Code")
|
|
545
|
+
parser.add_argument("-m", "--max-iterations", type=int, default=MAX_ITERATIONS)
|
|
546
|
+
parser.add_argument("-t", "--timeout", type=int, default=DEFAULT_TIMEOUT,
|
|
547
|
+
help="Timeout per iteration in seconds (default: 600)")
|
|
548
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
|
|
549
|
+
|
|
550
|
+
args = parser.parse_args()
|
|
551
|
+
|
|
552
|
+
# Clear screen and show banner
|
|
553
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
|
554
|
+
print(BANNER)
|
|
555
|
+
|
|
556
|
+
# Get bug description
|
|
557
|
+
if args.file:
|
|
558
|
+
bug_input = Path(args.file).read_text(encoding="utf-8")
|
|
559
|
+
elif args.bug:
|
|
560
|
+
bug_input = args.bug
|
|
561
|
+
else:
|
|
562
|
+
print(f" {C.CYAN}Describe the bug (Ctrl+D or Ctrl+Z to finish):{C.RESET}")
|
|
563
|
+
bug_input = sys.stdin.read().strip()
|
|
564
|
+
|
|
565
|
+
if not bug_input:
|
|
566
|
+
print_error_box("No bug description provided")
|
|
567
|
+
return 1
|
|
568
|
+
|
|
569
|
+
# Setup
|
|
570
|
+
session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
571
|
+
system_prompt = load_prompt()
|
|
572
|
+
working_dir = args.dir or os.getcwd()
|
|
573
|
+
|
|
574
|
+
# Session info
|
|
575
|
+
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()
|
|
592
|
+
|
|
593
|
+
# Initial context
|
|
594
|
+
context = f"""
|
|
595
|
+
## Bug Report
|
|
596
|
+
|
|
597
|
+
{bug_input}
|
|
598
|
+
|
|
599
|
+
## Instructions
|
|
600
|
+
|
|
601
|
+
Analyze this bug. Gather whatever logs/information you need to understand it.
|
|
602
|
+
Then fix it. Use the reasoning protocol before each action.
|
|
603
|
+
"""
|
|
604
|
+
|
|
605
|
+
iteration_history = []
|
|
606
|
+
|
|
607
|
+
for iteration in range(1, args.max_iterations + 1):
|
|
608
|
+
print_iteration_header(iteration, args.max_iterations)
|
|
609
|
+
|
|
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
|
|
616
|
+
|
|
617
|
+
if args.verbose:
|
|
618
|
+
print(f" {C.DIM}[DEBUG] Prompt: {len(full_prompt)} chars, continue={continue_session}{C.RESET}")
|
|
619
|
+
|
|
620
|
+
# Show "Claude is working" indicator
|
|
621
|
+
print(f"\n {C.BRIGHT_BLUE}▶ Claude is working...{C.RESET}\n")
|
|
622
|
+
sys.stdout.flush()
|
|
623
|
+
|
|
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"""
|
|
648
|
+
## Human Clarification
|
|
649
|
+
|
|
650
|
+
{human_input}
|
|
651
|
+
|
|
652
|
+
Continue debugging with this information. Use the reasoning protocol before each action.
|
|
653
|
+
"""
|
|
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"""
|
|
667
|
+
## Iteration {iteration + 1}
|
|
668
|
+
|
|
669
|
+
The bug is NOT yet resolved. You have full context from previous iterations.
|
|
670
|
+
|
|
671
|
+
Continue debugging. Analyze what happened, determine next steps, and proceed.
|
|
672
|
+
Use the reasoning protocol before each action.
|
|
673
|
+
"""
|
|
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)
|
|
679
|
+
print()
|
|
680
|
+
|
|
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")
|
|
689
|
+
|
|
690
|
+
return 1
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
if __name__ == "__main__":
|
|
694
|
+
sys.exit(main())
|