@rajat-rastogi/maestro 0.1.0

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.
Files changed (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +254 -0
  3. package/defaults/agents/documentation.txt +18 -0
  4. package/defaults/agents/implementation.txt +16 -0
  5. package/defaults/agents/quality.txt +19 -0
  6. package/defaults/agents/simplification.txt +19 -0
  7. package/defaults/agents/testing.txt +16 -0
  8. package/defaults/config +46 -0
  9. package/defaults/plan-tips.md +50 -0
  10. package/defaults/prompts/finalize.txt +14 -0
  11. package/defaults/prompts/hint_analysis.txt +38 -0
  12. package/defaults/prompts/plan_create.txt +36 -0
  13. package/defaults/prompts/review_first.txt +37 -0
  14. package/defaults/prompts/review_second.txt +36 -0
  15. package/defaults/prompts/task.txt +23 -0
  16. package/dist/backend/backend.d.ts +29 -0
  17. package/dist/backend/backend.d.ts.map +1 -0
  18. package/dist/backend/backend.js +9 -0
  19. package/dist/backend/backend.js.map +1 -0
  20. package/dist/backend/claude.d.ts +12 -0
  21. package/dist/backend/claude.d.ts.map +1 -0
  22. package/dist/backend/claude.js +155 -0
  23. package/dist/backend/claude.js.map +1 -0
  24. package/dist/backend/copilot.d.ts +11 -0
  25. package/dist/backend/copilot.d.ts.map +1 -0
  26. package/dist/backend/copilot.js +110 -0
  27. package/dist/backend/copilot.js.map +1 -0
  28. package/dist/backend/factory.d.ts +4 -0
  29. package/dist/backend/factory.d.ts.map +1 -0
  30. package/dist/backend/factory.js +13 -0
  31. package/dist/backend/factory.js.map +1 -0
  32. package/dist/config/configserver.d.ts +9 -0
  33. package/dist/config/configserver.d.ts.map +1 -0
  34. package/dist/config/configserver.js +141 -0
  35. package/dist/config/configserver.js.map +1 -0
  36. package/dist/config/dump.d.ts +2 -0
  37. package/dist/config/dump.d.ts.map +1 -0
  38. package/dist/config/dump.js +24 -0
  39. package/dist/config/dump.js.map +1 -0
  40. package/dist/config/loader.d.ts +11 -0
  41. package/dist/config/loader.d.ts.map +1 -0
  42. package/dist/config/loader.js +119 -0
  43. package/dist/config/loader.js.map +1 -0
  44. package/dist/config/prompts.d.ts +19 -0
  45. package/dist/config/prompts.d.ts.map +1 -0
  46. package/dist/config/prompts.js +128 -0
  47. package/dist/config/prompts.js.map +1 -0
  48. package/dist/config/reset.d.ts +3 -0
  49. package/dist/config/reset.d.ts.map +1 -0
  50. package/dist/config/reset.js +23 -0
  51. package/dist/config/reset.js.map +1 -0
  52. package/dist/config/template.d.ts +5 -0
  53. package/dist/config/template.d.ts.map +1 -0
  54. package/dist/config/template.js +11 -0
  55. package/dist/config/template.js.map +1 -0
  56. package/dist/config/types.d.ts +35 -0
  57. package/dist/config/types.d.ts.map +1 -0
  58. package/dist/config/types.js +42 -0
  59. package/dist/config/types.js.map +1 -0
  60. package/dist/config/write.d.ts +5 -0
  61. package/dist/config/write.d.ts.map +1 -0
  62. package/dist/config/write.js +59 -0
  63. package/dist/config/write.js.map +1 -0
  64. package/dist/dashboard/assets/config.html +1012 -0
  65. package/dist/dashboard/assets/dashboard.html +871 -0
  66. package/dist/dashboard/assets/help.html +657 -0
  67. package/dist/dashboard/assets.d.ts +2 -0
  68. package/dist/dashboard/assets.d.ts.map +1 -0
  69. package/dist/dashboard/assets.js +5 -0
  70. package/dist/dashboard/assets.js.map +1 -0
  71. package/dist/dashboard/event-bus.d.ts +11 -0
  72. package/dist/dashboard/event-bus.d.ts.map +1 -0
  73. package/dist/dashboard/event-bus.js +4 -0
  74. package/dist/dashboard/event-bus.js.map +1 -0
  75. package/dist/dashboard/replay-parser.d.ts +6 -0
  76. package/dist/dashboard/replay-parser.d.ts.map +1 -0
  77. package/dist/dashboard/replay-parser.js +56 -0
  78. package/dist/dashboard/replay-parser.js.map +1 -0
  79. package/dist/dashboard/server.d.ts +14 -0
  80. package/dist/dashboard/server.d.ts.map +1 -0
  81. package/dist/dashboard/server.js +178 -0
  82. package/dist/dashboard/server.js.map +1 -0
  83. package/dist/dashboard/watcher.d.ts +17 -0
  84. package/dist/dashboard/watcher.d.ts.map +1 -0
  85. package/dist/dashboard/watcher.js +73 -0
  86. package/dist/dashboard/watcher.js.map +1 -0
  87. package/dist/executor/hints.d.ts +21 -0
  88. package/dist/executor/hints.d.ts.map +1 -0
  89. package/dist/executor/hints.js +102 -0
  90. package/dist/executor/hints.js.map +1 -0
  91. package/dist/executor/task.d.ts +19 -0
  92. package/dist/executor/task.d.ts.map +1 -0
  93. package/dist/executor/task.js +119 -0
  94. package/dist/executor/task.js.map +1 -0
  95. package/dist/executor/validation.d.ts +18 -0
  96. package/dist/executor/validation.d.ts.map +1 -0
  97. package/dist/executor/validation.js +73 -0
  98. package/dist/executor/validation.js.map +1 -0
  99. package/dist/git/branch.d.ts +23 -0
  100. package/dist/git/branch.d.ts.map +1 -0
  101. package/dist/git/branch.js +64 -0
  102. package/dist/git/branch.js.map +1 -0
  103. package/dist/git/commit.d.ts +21 -0
  104. package/dist/git/commit.d.ts.map +1 -0
  105. package/dist/git/commit.js +37 -0
  106. package/dist/git/commit.js.map +1 -0
  107. package/dist/git/diff.d.ts +15 -0
  108. package/dist/git/diff.d.ts.map +1 -0
  109. package/dist/git/diff.js +26 -0
  110. package/dist/git/diff.js.map +1 -0
  111. package/dist/git/git.d.ts +9 -0
  112. package/dist/git/git.d.ts.map +1 -0
  113. package/dist/git/git.js +20 -0
  114. package/dist/git/git.js.map +1 -0
  115. package/dist/git/gitignore.d.ts +2 -0
  116. package/dist/git/gitignore.d.ts.map +1 -0
  117. package/dist/git/gitignore.js +66 -0
  118. package/dist/git/gitignore.js.map +1 -0
  119. package/dist/git/worktree.d.ts +3 -0
  120. package/dist/git/worktree.d.ts.map +1 -0
  121. package/dist/git/worktree.js +17 -0
  122. package/dist/git/worktree.js.map +1 -0
  123. package/dist/main.d.ts +3 -0
  124. package/dist/main.d.ts.map +1 -0
  125. package/dist/main.js +212 -0
  126. package/dist/main.js.map +1 -0
  127. package/dist/orchestrator/orchestrator.d.ts +21 -0
  128. package/dist/orchestrator/orchestrator.d.ts.map +1 -0
  129. package/dist/orchestrator/orchestrator.js +218 -0
  130. package/dist/orchestrator/orchestrator.js.map +1 -0
  131. package/dist/plan/creator.d.ts +27 -0
  132. package/dist/plan/creator.d.ts.map +1 -0
  133. package/dist/plan/creator.js +251 -0
  134. package/dist/plan/creator.js.map +1 -0
  135. package/dist/plan/parser.d.ts +11 -0
  136. package/dist/plan/parser.d.ts.map +1 -0
  137. package/dist/plan/parser.js +151 -0
  138. package/dist/plan/parser.js.map +1 -0
  139. package/dist/plan/types.d.ts +23 -0
  140. package/dist/plan/types.d.ts.map +1 -0
  141. package/dist/plan/types.js +2 -0
  142. package/dist/plan/types.js.map +1 -0
  143. package/dist/plan/updater.d.ts +16 -0
  144. package/dist/plan/updater.d.ts.map +1 -0
  145. package/dist/plan/updater.js +59 -0
  146. package/dist/plan/updater.js.map +1 -0
  147. package/dist/progress/colors.d.ts +9 -0
  148. package/dist/progress/colors.d.ts.map +1 -0
  149. package/dist/progress/colors.js +39 -0
  150. package/dist/progress/colors.js.map +1 -0
  151. package/dist/progress/logger.d.ts +17 -0
  152. package/dist/progress/logger.d.ts.map +1 -0
  153. package/dist/progress/logger.js +101 -0
  154. package/dist/progress/logger.js.map +1 -0
  155. package/dist/progress/notify.d.ts +2 -0
  156. package/dist/progress/notify.d.ts.map +1 -0
  157. package/dist/progress/notify.js +37 -0
  158. package/dist/progress/notify.js.map +1 -0
  159. package/dist/progress/timestamp.d.ts +5 -0
  160. package/dist/progress/timestamp.d.ts.map +1 -0
  161. package/dist/progress/timestamp.js +13 -0
  162. package/dist/progress/timestamp.js.map +1 -0
  163. package/dist/review/agents.d.ts +18 -0
  164. package/dist/review/agents.d.ts.map +1 -0
  165. package/dist/review/agents.js +43 -0
  166. package/dist/review/agents.js.map +1 -0
  167. package/dist/review/pipeline.d.ts +36 -0
  168. package/dist/review/pipeline.d.ts.map +1 -0
  169. package/dist/review/pipeline.js +210 -0
  170. package/dist/review/pipeline.js.map +1 -0
  171. package/dist/sea.d.ts +19 -0
  172. package/dist/sea.d.ts.map +1 -0
  173. package/dist/sea.js +41 -0
  174. package/dist/sea.js.map +1 -0
  175. package/package.json +57 -0
@@ -0,0 +1,871 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Maestro Dashboard</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+
10
+ :root {
11
+ --task: #00ff00;
12
+ --review: #00ffff;
13
+ --signal: #ff6464;
14
+ --warn: #ffff00;
15
+ --error: #ff0000;
16
+ --info: #b4b4b4;
17
+ --ts: #8a8a8a;
18
+ --bg: #1a1a1a;
19
+ --section-bg: #252525;
20
+ --border: #333;
21
+ --header-bg: #111;
22
+ --tab-active: #333;
23
+ }
24
+
25
+ body {
26
+ background: var(--bg);
27
+ color: var(--info);
28
+ font-family: 'Consolas', 'Courier New', monospace;
29
+ font-size: 13px;
30
+ line-height: 1.5;
31
+ overflow-x: hidden;
32
+ }
33
+
34
+ /* ── Header bar ── */
35
+ #header {
36
+ position: fixed;
37
+ top: 0; left: 0; right: 0;
38
+ height: 40px;
39
+ background: var(--header-bg);
40
+ border-bottom: 1px solid var(--border);
41
+ display: flex;
42
+ align-items: center;
43
+ padding: 0 12px;
44
+ gap: 12px;
45
+ z-index: 100;
46
+ }
47
+
48
+ #header-title {
49
+ font-weight: bold;
50
+ color: var(--task);
51
+ white-space: nowrap;
52
+ }
53
+
54
+ #header-meta {
55
+ color: var(--ts);
56
+ font-size: 12px;
57
+ flex: 1;
58
+ white-space: nowrap;
59
+ overflow: hidden;
60
+ text-overflow: ellipsis;
61
+ }
62
+
63
+ #header-right {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 10px;
67
+ white-space: nowrap;
68
+ }
69
+
70
+ #elapsed-total {
71
+ color: var(--ts);
72
+ font-size: 12px;
73
+ }
74
+
75
+ #phase-badge {
76
+ font-size: 11px;
77
+ padding: 2px 6px;
78
+ border-radius: 3px;
79
+ background: #333;
80
+ color: var(--info);
81
+ }
82
+
83
+ #btn-export {
84
+ background: #333;
85
+ border: 1px solid #555;
86
+ color: var(--info);
87
+ padding: 3px 10px;
88
+ border-radius: 3px;
89
+ cursor: pointer;
90
+ font-size: 12px;
91
+ }
92
+ #btn-export:hover { background: #444; }
93
+
94
+ /* ── Tab bar + search ── */
95
+ #toolbar {
96
+ position: fixed;
97
+ top: 40px; left: 0; right: 0;
98
+ height: 38px;
99
+ background: var(--header-bg);
100
+ border-bottom: 1px solid var(--border);
101
+ display: flex;
102
+ align-items: center;
103
+ padding: 0 12px;
104
+ gap: 8px;
105
+ z-index: 100;
106
+ }
107
+
108
+ /* Fix 1: toggle button now lives in toolbar — always visible */
109
+ #btn-toggle-sidebar {
110
+ background: transparent;
111
+ border: 1px solid var(--border);
112
+ color: var(--ts);
113
+ cursor: pointer;
114
+ font-size: 13px;
115
+ padding: 3px 8px;
116
+ border-radius: 3px;
117
+ font-family: inherit;
118
+ flex-shrink: 0;
119
+ }
120
+ #btn-toggle-sidebar:hover { border-color: #555; color: #ccc; }
121
+
122
+ #btn-expand-all, #btn-collapse-all {
123
+ background: transparent;
124
+ border: 1px solid var(--border);
125
+ color: var(--ts);
126
+ cursor: pointer;
127
+ font-size: 13px;
128
+ padding: 3px 8px;
129
+ border-radius: 3px;
130
+ font-family: inherit;
131
+ flex-shrink: 0;
132
+ }
133
+ #btn-expand-all:hover, #btn-collapse-all:hover { border-color: #555; color: #ccc; }
134
+
135
+ .phase-tab {
136
+ background: transparent;
137
+ border: 1px solid var(--border);
138
+ color: var(--info);
139
+ padding: 3px 10px;
140
+ border-radius: 3px;
141
+ cursor: pointer;
142
+ font-size: 12px;
143
+ font-family: inherit;
144
+ }
145
+ .phase-tab.active { background: var(--tab-active); border-color: #555; color: #fff; }
146
+ .phase-tab:hover:not(.active) { border-color: #555; }
147
+
148
+ #search-wrap {
149
+ flex: 1;
150
+ display: flex;
151
+ align-items: center;
152
+ gap: 6px;
153
+ max-width: 360px;
154
+ margin-left: auto;
155
+ }
156
+
157
+ #search-label { color: var(--ts); font-size: 12px; }
158
+
159
+ #search-input {
160
+ flex: 1;
161
+ background: #222;
162
+ border: 1px solid var(--border);
163
+ color: var(--info);
164
+ padding: 3px 8px;
165
+ border-radius: 3px;
166
+ font-family: inherit;
167
+ font-size: 12px;
168
+ outline: none;
169
+ }
170
+ #search-input:focus { border-color: #555; }
171
+
172
+ /* ── Layout ── */
173
+ #layout {
174
+ display: flex;
175
+ margin-top: 78px; /* header + toolbar */
176
+ min-height: calc(100vh - 78px);
177
+ }
178
+
179
+ /* ── Plan sidebar ── */
180
+ #sidebar {
181
+ width: 220px;
182
+ min-width: 220px;
183
+ background: var(--section-bg);
184
+ border-right: 1px solid var(--border);
185
+ padding: 10px 0;
186
+ position: sticky;
187
+ top: 78px;
188
+ height: calc(100vh - 78px);
189
+ overflow-y: auto;
190
+ transition: width 0.2s, min-width 0.2s;
191
+ }
192
+
193
+ #sidebar.collapsed {
194
+ width: 0;
195
+ min-width: 0;
196
+ overflow: hidden;
197
+ padding: 0;
198
+ }
199
+
200
+ #sidebar-header {
201
+ padding: 0 10px 8px;
202
+ color: var(--warn);
203
+ font-size: 12px;
204
+ font-weight: bold;
205
+ border-bottom: 1px solid var(--border);
206
+ margin-bottom: 6px;
207
+ }
208
+
209
+ .sidebar-task-group {
210
+ padding: 8px 0 6px;
211
+ border-bottom: 1px solid var(--border);
212
+ }
213
+ .sidebar-task-group:last-child { border-bottom: none; }
214
+
215
+ .sidebar-task-title {
216
+ display: flex;
217
+ align-items: flex-start;
218
+ gap: 6px;
219
+ padding: 2px 10px 4px;
220
+ font-size: 12px;
221
+ font-weight: bold;
222
+ color: var(--warn);
223
+ cursor: default;
224
+ }
225
+ .sidebar-task-title.done { color: var(--ts); text-decoration: line-through; }
226
+
227
+ .sidebar-task-check { color: var(--task); flex-shrink: 0; }
228
+ .sidebar-task-check.done { color: var(--ts); }
229
+
230
+ .sidebar-task {
231
+ padding: 2px 10px 2px 22px;
232
+ font-size: 12px;
233
+ color: var(--info);
234
+ cursor: default;
235
+ }
236
+ .sidebar-task.done { color: var(--ts); text-decoration: line-through; }
237
+ .sidebar-check { color: var(--task); margin-right: 4px; }
238
+
239
+ /* ── Log area ── */
240
+ #log-wrap {
241
+ flex: 1;
242
+ padding: 10px 12px 80px;
243
+ min-width: 0;
244
+ }
245
+
246
+ /* ── Replay panel ── */
247
+ #replay-panel {
248
+ flex: 1;
249
+ padding: 40px;
250
+ }
251
+ #replay-panel h2 {
252
+ color: var(--warn);
253
+ margin-bottom: 20px;
254
+ font-size: 16px;
255
+ font-weight: bold;
256
+ }
257
+ .session-row {
258
+ padding: 12px 16px;
259
+ margin-bottom: 8px;
260
+ background: var(--section-bg);
261
+ border: 1px solid var(--border);
262
+ border-radius: 4px;
263
+ cursor: pointer;
264
+ display: flex;
265
+ align-items: center;
266
+ gap: 10px;
267
+ }
268
+ .session-row:hover { border-color: #555; }
269
+ .session-name { color: #eee; flex: 1; }
270
+ .session-badge {
271
+ font-size: 11px;
272
+ padding: 1px 6px;
273
+ border-radius: 3px;
274
+ background: #003300;
275
+ color: var(--task);
276
+ }
277
+ #no-sessions { color: var(--ts); }
278
+
279
+ /* ── Section (collapsible) ── */
280
+ .section {
281
+ margin-bottom: 4px;
282
+ border: 1px solid var(--border);
283
+ border-radius: 4px;
284
+ background: var(--section-bg);
285
+ }
286
+
287
+ .section-header {
288
+ display: flex;
289
+ align-items: center;
290
+ gap: 8px;
291
+ padding: 5px 10px;
292
+ cursor: pointer;
293
+ user-select: none;
294
+ }
295
+
296
+ .section-toggle {
297
+ color: var(--ts);
298
+ font-size: 11px;
299
+ width: 14px;
300
+ }
301
+
302
+ .section-phase {
303
+ font-size: 11px;
304
+ padding: 1px 6px;
305
+ border-radius: 3px;
306
+ }
307
+ .section-phase.task { background: #003300; color: var(--task); }
308
+ .section-phase.review { background: #003333; color: var(--review); }
309
+ .section-phase.claude_eval{ background: #003333; color: var(--review); }
310
+ .section-phase.signal { background: #330000; color: var(--signal); }
311
+
312
+ .section-title {
313
+ flex: 1;
314
+ color: #eee;
315
+ font-size: 12px;
316
+ }
317
+
318
+ .section-elapsed {
319
+ color: var(--ts);
320
+ font-size: 11px;
321
+ white-space: nowrap;
322
+ }
323
+
324
+ .section-content {
325
+ padding: 4px 10px 8px 34px;
326
+ }
327
+
328
+ .section.collapsed .section-content { display: none; }
329
+
330
+ /* ── Log lines ── */
331
+ .log-line {
332
+ display: flex;
333
+ gap: 8px;
334
+ padding: 1px 0;
335
+ white-space: pre-wrap;
336
+ word-break: break-all;
337
+ }
338
+
339
+ .log-line.hidden { display: none !important; }
340
+ .log-line.search-hidden { display: none !important; }
341
+
342
+ .log-ts { color: var(--ts); flex-shrink: 0; }
343
+ .log-text { flex: 1; }
344
+
345
+ .log-text.task { color: var(--task); }
346
+ .log-text.review { color: var(--review); }
347
+ .log-text.claude_eval{ color: var(--review); }
348
+ .log-text.signal { color: var(--signal); }
349
+ .log-text.warn { color: var(--warn); }
350
+ .log-text.error { color: var(--error); }
351
+ .log-text.info { color: var(--info); }
352
+ .log-text.header { color: var(--warn); }
353
+
354
+ /* ── Scroll-to-bottom button ── */
355
+ #scroll-btn {
356
+ position: fixed;
357
+ bottom: 20px;
358
+ right: 24px;
359
+ background: #333;
360
+ border: 1px solid #555;
361
+ color: var(--info);
362
+ padding: 6px 14px;
363
+ border-radius: 4px;
364
+ cursor: pointer;
365
+ font-size: 13px;
366
+ z-index: 200;
367
+ }
368
+ #scroll-btn.hidden { display: none; }
369
+ #scroll-btn:hover { background: #444; }
370
+
371
+ /* ── Connecting overlay ── */
372
+ #connecting {
373
+ position: fixed;
374
+ top: 50%; left: 50%;
375
+ transform: translate(-50%, -50%);
376
+ color: var(--ts);
377
+ font-size: 14px;
378
+ }
379
+ </style>
380
+ </head>
381
+ <body>
382
+
383
+ <!-- Header bar -->
384
+ <div id="header">
385
+ <span id="header-title">Maestro</span>
386
+ <span id="header-meta">connecting…</span>
387
+ <div id="header-right">
388
+ <span id="elapsed-total"></span>
389
+ <span id="phase-badge"></span>
390
+ <button id="btn-export">Export</button>
391
+ </div>
392
+ </div>
393
+
394
+ <!-- Toolbar — sidebar toggle is first so it's always visible even when sidebar is closed -->
395
+ <div id="toolbar">
396
+ <button id="btn-toggle-sidebar" title="Toggle sidebar (P)">◄</button>
397
+ <button class="phase-tab active" data-phase="all">All</button>
398
+ <button class="phase-tab" data-phase="task">Task</button>
399
+ <button class="phase-tab" data-phase="review">Review</button>
400
+ <button id="btn-expand-all" title="Expand all sections">⊞</button>
401
+ <button id="btn-collapse-all" title="Collapse all sections">⊟</button>
402
+ <div id="search-wrap">
403
+ <span id="search-label">🔍</span>
404
+ <input id="search-input" type="text" placeholder="Search… (/)" autocomplete="off">
405
+ </div>
406
+ </div>
407
+
408
+ <div id="layout">
409
+ <!-- Sidebar -->
410
+ <div id="sidebar">
411
+ <div id="sidebar-header"><span>Plan</span></div>
412
+ <div id="sidebar-content"></div>
413
+ </div>
414
+
415
+ <!-- Replay session picker (shown in --replay mode) -->
416
+ <div id="replay-panel" style="display:none">
417
+ <h2>Previous Runs</h2>
418
+ <div id="session-list"></div>
419
+ </div>
420
+
421
+ <!-- Log area -->
422
+ <div id="log-wrap">
423
+ <div id="connecting">Connecting to event stream…</div>
424
+ <div id="log"></div>
425
+ </div>
426
+ </div>
427
+
428
+ <!-- Scroll to bottom -->
429
+ <button id="scroll-btn" class="hidden">Scroll to bottom ▼</button>
430
+
431
+ <script>
432
+ (function () {
433
+ 'use strict';
434
+
435
+ // ── State ──
436
+ let autoScroll = true;
437
+ let activePhase = 'all';
438
+ let sidebarOpen = true;
439
+ let currentSectionEl = null;
440
+ let sectionStart = 0;
441
+ let sessionStart = 0;
442
+ let elapsedInterval = null;
443
+ const sectionTimers = new Map(); // section-content element → { start, frozen, elEl }
444
+
445
+ // ── DOM refs ──
446
+ const logEl = document.getElementById('log');
447
+ const headerMeta = document.getElementById('header-meta');
448
+ const elapsedTotal = document.getElementById('elapsed-total');
449
+ const phaseBadge = document.getElementById('phase-badge');
450
+ const scrollBtn = document.getElementById('scroll-btn');
451
+ const searchInput = document.getElementById('search-input');
452
+ const sidebar = document.getElementById('sidebar');
453
+ const sidebarContent = document.getElementById('sidebar-content');
454
+ const btnToggle = document.getElementById('btn-toggle-sidebar');
455
+ const btnExport = document.getElementById('btn-export');
456
+ const connecting = document.getElementById('connecting');
457
+ const replayPanel = document.getElementById('replay-panel');
458
+ const logWrap = document.getElementById('log-wrap');
459
+
460
+ // ── Helpers ──
461
+ function formatElapsed(ms) {
462
+ const s = Math.floor(ms / 1000);
463
+ const m = Math.floor(s / 60);
464
+ const h = Math.floor(m / 60);
465
+ if (h > 0) return `${h}h ${m % 60}m ${s % 60}s`;
466
+ if (m > 0) return `${m}m ${s % 60}s`;
467
+ return `${s}s`;
468
+ }
469
+
470
+ function startElapsedTimer() {
471
+ if (elapsedInterval) return;
472
+ elapsedInterval = setInterval(() => {
473
+ if (sessionStart) {
474
+ elapsedTotal.textContent = formatElapsed(Date.now() - sessionStart);
475
+ }
476
+ for (const [, info] of sectionTimers) {
477
+ if (!info.frozen && info.elEl) {
478
+ info.elEl.textContent = formatElapsed(Date.now() - info.start);
479
+ }
480
+ }
481
+ }, 1000);
482
+ }
483
+
484
+ // Fix 2: stop all timers (called on PLAN_COMPLETE or SSE disconnect after run started)
485
+ function stopTimers() {
486
+ if (elapsedInterval) { clearInterval(elapsedInterval); elapsedInterval = null; }
487
+ for (const info of sectionTimers.values()) { info.frozen = true; }
488
+ }
489
+
490
+ // ── Phase matching ──
491
+ function matchesPhase(linePhase, filter) {
492
+ if (filter === 'all') return true;
493
+ if (filter === 'task') return ['task','warn','info','signal'].includes(linePhase);
494
+ if (filter === 'review') return ['review','claude_eval','warn','info','signal'].includes(linePhase);
495
+ return true;
496
+ }
497
+
498
+ // ── Section detection ──
499
+ function detectSectionPhase(text) {
500
+ const t = text.toLowerCase();
501
+ if (t.includes('task iteration')) return 'task';
502
+ if (t.includes('claude review') || t.includes('claude eval')) return 'claude_eval';
503
+ return 'review';
504
+ }
505
+
506
+ function openSection(ev) {
507
+ // Freeze elapsed on previous section
508
+ if (currentSectionEl && sectionTimers.has(currentSectionEl)) {
509
+ sectionTimers.get(currentSectionEl).frozen = true;
510
+ }
511
+
512
+ sectionStart = Date.now();
513
+ const phase = detectSectionPhase(ev.text);
514
+
515
+ const sectionDiv = document.createElement('div');
516
+ sectionDiv.className = 'section';
517
+ sectionDiv.dataset.phase = phase;
518
+
519
+ const hdr = document.createElement('div');
520
+ hdr.className = 'section-header';
521
+
522
+ const toggle = document.createElement('span');
523
+ toggle.className = 'section-toggle';
524
+ toggle.textContent = '▼';
525
+
526
+ const badge = document.createElement('span');
527
+ badge.className = `section-phase ${phase}`;
528
+ badge.textContent = phase === 'claude_eval' ? 'claude review' : phase;
529
+
530
+ const title = document.createElement('span');
531
+ title.className = 'section-title';
532
+ title.textContent = ev.text.replace(/^---\s*/, '');
533
+
534
+ const elapsedEl = document.createElement('span');
535
+ elapsedEl.className = 'section-elapsed';
536
+ elapsedEl.textContent = '0s';
537
+
538
+ hdr.appendChild(toggle);
539
+ hdr.appendChild(badge);
540
+ hdr.appendChild(title);
541
+ hdr.appendChild(elapsedEl);
542
+
543
+ const content = document.createElement('div');
544
+ content.className = 'section-content';
545
+
546
+ sectionDiv.appendChild(hdr);
547
+ sectionDiv.appendChild(content);
548
+ logEl.appendChild(sectionDiv);
549
+
550
+ hdr.addEventListener('click', () => {
551
+ sectionDiv.classList.toggle('collapsed');
552
+ toggle.textContent = sectionDiv.classList.contains('collapsed') ? '▶' : '▼';
553
+ });
554
+
555
+ currentSectionEl = content;
556
+ sectionTimers.set(content, { start: sectionStart, frozen: false, elEl: elapsedEl });
557
+
558
+ sectionDiv.classList.toggle('hidden', !matchesPhase(phase, activePhase));
559
+ }
560
+
561
+ // ── Append event ──
562
+ function appendEvent(ev) {
563
+ if (ev.level === 'header' && ev.text.startsWith('---')) {
564
+ openSection(ev);
565
+ return;
566
+ }
567
+
568
+ // Fix 2: freeze timers and mark done when run completes
569
+ if (ev.level === 'signal' && ev.text === 'PLAN_COMPLETE') {
570
+ stopTimers();
571
+ headerMeta.textContent += ' · Done';
572
+ }
573
+
574
+ const line = document.createElement('div');
575
+ line.className = 'log-line';
576
+ line.dataset.phase = ev.phase;
577
+ line.classList.toggle('hidden', !matchesPhase(ev.phase, activePhase));
578
+
579
+ const q = searchInput.value.toLowerCase();
580
+ if (q && !((ev.ts + ' ' + ev.text).toLowerCase().includes(q))) {
581
+ line.classList.add('search-hidden');
582
+ }
583
+
584
+ if (ev.ts) {
585
+ const tsSpan = document.createElement('span');
586
+ tsSpan.className = 'log-ts';
587
+ tsSpan.textContent = ev.ts;
588
+ line.appendChild(tsSpan);
589
+ }
590
+
591
+ const textSpan = document.createElement('span');
592
+ textSpan.className = `log-text ${ev.phase}`;
593
+ textSpan.textContent = ev.text;
594
+ line.appendChild(textSpan);
595
+
596
+ const target = currentSectionEl ?? logEl;
597
+ target.appendChild(line);
598
+
599
+ if (['task','review','claude_eval'].includes(ev.phase)) {
600
+ phaseBadge.textContent = ev.phase === 'claude_eval' ? 'REVIEW' : ev.phase.toUpperCase();
601
+ phaseBadge.style.color = ev.phase === 'task' ? 'var(--task)' : 'var(--review)';
602
+ }
603
+ }
604
+
605
+ // ── Phase tab filtering ──
606
+ function setPhaseFilter(phase) {
607
+ activePhase = phase;
608
+ for (const tab of document.querySelectorAll('.phase-tab')) {
609
+ tab.classList.toggle('active', tab.dataset.phase === phase);
610
+ }
611
+ for (const el of document.querySelectorAll('.log-line')) {
612
+ el.classList.toggle('hidden', !matchesPhase(el.dataset.phase, phase));
613
+ }
614
+ for (const el of document.querySelectorAll('.section')) {
615
+ el.classList.toggle('hidden', !matchesPhase(el.dataset.phase, phase));
616
+ }
617
+ }
618
+
619
+ for (const tab of document.querySelectorAll('.phase-tab')) {
620
+ tab.addEventListener('click', () => setPhaseFilter(tab.dataset.phase));
621
+ }
622
+
623
+ // ── Search ──
624
+ searchInput.addEventListener('input', () => {
625
+ const q = searchInput.value.toLowerCase();
626
+ for (const el of document.querySelectorAll('.log-line')) {
627
+ el.classList.toggle('search-hidden', q && !el.textContent.toLowerCase().includes(q));
628
+ }
629
+ });
630
+
631
+ // ── Auto-scroll + freeze ──
632
+ window.addEventListener('scroll', () => {
633
+ const atBottom = window.scrollY + window.innerHeight >= document.body.scrollHeight - 80;
634
+ autoScroll = atBottom;
635
+ scrollBtn.classList.toggle('hidden', atBottom);
636
+ });
637
+
638
+ scrollBtn.addEventListener('click', () => {
639
+ autoScroll = true;
640
+ window.scrollTo(0, document.body.scrollHeight);
641
+ scrollBtn.classList.add('hidden');
642
+ });
643
+
644
+ // ── Sidebar toggle (Fix 1: button is in toolbar so always visible) ──
645
+ function toggleSidebar() {
646
+ sidebarOpen = !sidebarOpen;
647
+ sidebar.classList.toggle('collapsed', !sidebarOpen);
648
+ btnToggle.textContent = sidebarOpen ? '◄' : '►';
649
+ }
650
+
651
+ btnToggle.addEventListener('click', toggleSidebar);
652
+
653
+ document.getElementById('btn-expand-all').addEventListener('click', () => {
654
+ for (const el of document.querySelectorAll('.section.collapsed')) {
655
+ el.classList.remove('collapsed');
656
+ el.querySelector('.section-toggle').textContent = '▼';
657
+ }
658
+ });
659
+ document.getElementById('btn-collapse-all').addEventListener('click', () => {
660
+ for (const el of document.querySelectorAll('.section:not(.collapsed)')) {
661
+ el.classList.add('collapsed');
662
+ el.querySelector('.section-toggle').textContent = '▶';
663
+ }
664
+ });
665
+
666
+ // ── Plan rendering ──
667
+ let planText = '';
668
+
669
+ function renderPlan(text) {
670
+ planText = text;
671
+ sidebarContent.innerHTML = '';
672
+
673
+ // Normalize Windows (CRLF) and old Mac (CR) line endings so regexes match correctly.
674
+ // Without this, `.` in `(.*)$` stops at \r and `$` fails to match before \r.
675
+ const lines = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
676
+
677
+ // Pass 1: collect task groups
678
+ const tasks = [];
679
+ let current = null;
680
+ for (const line of lines) {
681
+ const taskMatch = line.match(/^###\s+Task\s+\d+[:\s]*(.*)$/);
682
+ if (taskMatch) {
683
+ current = { title: line.replace(/^###\s+/, ''), items: [] };
684
+ tasks.push(current);
685
+ continue;
686
+ }
687
+ if (current) {
688
+ const doneMatch = line.match(/^-\s+\[x\]\s+(.*)/i);
689
+ const todoMatch = line.match(/^-\s+\[\s\]\s+(.*)/);
690
+ if (doneMatch) current.items.push({ text: doneMatch[1], done: true });
691
+ else if (todoMatch) current.items.push({ text: todoMatch[1], done: false });
692
+ }
693
+ }
694
+
695
+ // Pass 2: render
696
+ tasks.forEach((task) => {
697
+ const allDone = task.items.length > 0 && task.items.every(it => it.done);
698
+
699
+ const group = document.createElement('div');
700
+ group.className = 'sidebar-task-group';
701
+
702
+ // Task heading row
703
+ const heading = document.createElement('div');
704
+ heading.className = 'sidebar-task-title' + (allDone ? ' done' : '');
705
+
706
+ const taskCheck = document.createElement('span');
707
+ taskCheck.className = 'sidebar-task-check' + (allDone ? ' done' : '');
708
+ taskCheck.textContent = allDone ? '✓' : '○';
709
+ heading.appendChild(taskCheck);
710
+ heading.appendChild(document.createTextNode(task.title));
711
+ group.appendChild(heading);
712
+
713
+ // Sub-items
714
+ for (const item of task.items) {
715
+ const itemEl = document.createElement('div');
716
+ itemEl.className = 'sidebar-task' + (item.done ? ' done' : '');
717
+ const check = document.createElement('span');
718
+ check.className = 'sidebar-check';
719
+ check.textContent = item.done ? '☑' : '☐';
720
+ itemEl.appendChild(check);
721
+ itemEl.appendChild(document.createTextNode(item.text));
722
+ group.appendChild(itemEl);
723
+ }
724
+
725
+ sidebarContent.appendChild(group);
726
+ });
727
+ }
728
+
729
+ // ── Export ──
730
+ btnExport.addEventListener('click', () => {
731
+ const lines = [...document.querySelectorAll('.log-line')]
732
+ .map(el => el.textContent)
733
+ .join('\n');
734
+ const a = document.createElement('a');
735
+ a.href = URL.createObjectURL(new Blob([lines], { type: 'text/plain' }));
736
+ a.download = 'maestro-session.txt';
737
+ a.click();
738
+ });
739
+
740
+ // ── Keyboard shortcuts ──
741
+ document.addEventListener('keydown', e => {
742
+ if (e.target.tagName === 'INPUT') return;
743
+ if (e.key === '/') { e.preventDefault(); searchInput.focus(); }
744
+ if (e.key === 'p' || e.key === 'P') toggleSidebar();
745
+ });
746
+
747
+ searchInput.addEventListener('keydown', e => {
748
+ if (e.key === 'Escape') {
749
+ searchInput.value = '';
750
+ searchInput.dispatchEvent(new Event('input'));
751
+ searchInput.blur();
752
+ }
753
+ });
754
+
755
+ // ── Fix 3: Replay mode vs Live SSE mode ──
756
+ if (typeof window.__REPLAY_MODE__ !== 'undefined') {
757
+ // ── Replay mode — show session picker ──
758
+ connecting.style.display = 'none';
759
+ logWrap.style.display = 'none';
760
+ replayPanel.style.display = 'block';
761
+ headerMeta.textContent = 'Select a run to replay';
762
+
763
+ fetch('/sessions')
764
+ .then(r => r.json())
765
+ .then(sessions => {
766
+ const listEl = document.getElementById('session-list');
767
+ if (!sessions.length) {
768
+ const msg = document.createElement('div');
769
+ msg.id = 'no-sessions';
770
+ msg.textContent = 'No completed runs found in .maestro/progress/';
771
+ listEl.appendChild(msg);
772
+ return;
773
+ }
774
+ for (const s of sessions) {
775
+ const row = document.createElement('div');
776
+ row.className = 'session-row';
777
+ const name = document.createElement('span');
778
+ name.className = 'session-name';
779
+ name.textContent = s.planName;
780
+ row.appendChild(name);
781
+ if (s.active) {
782
+ const badge = document.createElement('span');
783
+ badge.className = 'session-badge';
784
+ badge.textContent = 'recent';
785
+ row.appendChild(badge);
786
+ }
787
+ row.addEventListener('click', () => loadReplay(s.id, s.planName));
788
+ listEl.appendChild(row);
789
+ }
790
+ })
791
+ .catch(() => {
792
+ document.getElementById('session-list').textContent = 'Failed to load sessions.';
793
+ });
794
+
795
+ function loadReplay(id, planName) {
796
+ replayPanel.style.display = 'none';
797
+ logWrap.style.display = '';
798
+ headerMeta.textContent = planName + ' · Replay';
799
+ fetch('/replay?id=' + encodeURIComponent(id))
800
+ .then(r => {
801
+ if (!r.ok) return r.text().then(t => { throw new Error(t || r.statusText); });
802
+ return r.json();
803
+ })
804
+ .then(data => {
805
+ if (data.planContent) renderPlan(data.planContent);
806
+ sessionStart = Date.now();
807
+ startElapsedTimer();
808
+ for (const ev of data.events) appendEvent(ev);
809
+ stopTimers();
810
+ if (autoScroll) window.scrollTo(0, document.body.scrollHeight);
811
+ })
812
+ .catch(err => {
813
+ logWrap.innerHTML = '<div style="color:var(--error);padding:20px">Failed to load replay data.'
814
+ + (err.message ? '<br><small style="color:var(--ts)">' + err.message + '</small>' : '')
815
+ + '</div>';
816
+ });
817
+ }
818
+
819
+ } else {
820
+ // ── Live SSE mode ──
821
+
822
+ async function fetchPlan() {
823
+ try {
824
+ const r = await fetch('/plan');
825
+ if (r.ok) {
826
+ const text = await r.text();
827
+ if (text !== planText) renderPlan(text);
828
+ if (headerMeta.textContent === 'connecting…') {
829
+ const firstLine = text.split('\n')[0] || '';
830
+ headerMeta.textContent = firstLine.replace(/^#\s+/, '');
831
+ }
832
+ }
833
+ } catch { /* ignore */ }
834
+ }
835
+
836
+ fetchPlan();
837
+ setInterval(fetchPlan, 5000);
838
+
839
+ const es = new EventSource('/events');
840
+
841
+ es.onopen = () => { connecting.style.display = 'none'; };
842
+
843
+ es.onmessage = ({ data }) => {
844
+ let ev;
845
+ try { ev = JSON.parse(data); } catch { return; }
846
+
847
+ if (!sessionStart) {
848
+ sessionStart = Date.now();
849
+ startElapsedTimer();
850
+ connecting.style.display = 'none';
851
+ }
852
+
853
+ appendEvent(ev);
854
+
855
+ if (autoScroll) window.scrollTo(0, document.body.scrollHeight);
856
+ };
857
+
858
+ // Fix 2: stop timers on disconnect if run had started
859
+ es.onerror = () => {
860
+ if (sessionStart) {
861
+ stopTimers();
862
+ } else {
863
+ connecting.textContent = 'Connection lost. Retrying…';
864
+ }
865
+ };
866
+ }
867
+
868
+ })();
869
+ </script>
870
+ </body>
871
+ </html>