@ronkovic/aad 0.3.9 → 0.4.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.
- package/README.md +292 -12
- package/package.json +6 -1
- package/src/__tests__/e2e/pipeline-e2e.test.ts +1 -0
- package/src/__tests__/e2e/resume-e2e.test.ts +2 -0
- package/src/__tests__/integration/pipeline.test.ts +1 -0
- package/src/modules/claude-provider/__tests__/claude-sdk-real-env.test.ts +1 -1
- package/src/modules/claude-provider/__tests__/claude-sdk.adapter.test.ts +2 -0
- package/src/modules/claude-provider/__tests__/provider-registry.test.ts +1 -0
- package/src/modules/cli/__tests__/cleanup.test.ts +72 -0
- package/src/modules/cli/__tests__/resume.test.ts +1 -0
- package/src/modules/cli/__tests__/run.test.ts +1 -0
- package/src/modules/cli/commands/__tests__/task-dispatch-handler.test.ts +145 -0
- package/src/modules/cli/commands/cleanup.ts +26 -11
- package/src/modules/cli/commands/resume.ts +3 -2
- package/src/modules/cli/commands/run.ts +57 -7
- package/src/modules/cli/commands/task-dispatch-handler.ts +73 -3
- package/src/modules/dashboard/__tests__/api-graph.test.ts +332 -0
- package/src/modules/dashboard/__tests__/api-timeline.test.ts +461 -0
- package/src/modules/dashboard/routes/sse.ts +3 -2
- package/src/modules/dashboard/server.ts +1 -0
- package/src/modules/dashboard/services/sse-broadcaster.ts +29 -0
- package/src/modules/dashboard/ui/dashboard.html +143 -18
- package/src/modules/git-workspace/__tests__/branch-manager.test.ts +52 -0
- package/src/modules/git-workspace/__tests__/dependency-installer.test.ts +77 -0
- package/src/modules/git-workspace/__tests__/git-exec.test.ts +26 -0
- package/src/modules/git-workspace/__tests__/merge-service.test.ts +19 -0
- package/src/modules/git-workspace/__tests__/pr-manager.test.ts +80 -0
- package/src/modules/git-workspace/__tests__/template-copy.test.ts +189 -0
- package/src/modules/git-workspace/__tests__/worktree-cleanup.test.ts +29 -2
- package/src/modules/git-workspace/__tests__/worktree-manager.test.ts +64 -4
- package/src/modules/git-workspace/branch-manager.ts +24 -3
- package/src/modules/git-workspace/dependency-installer.ts +113 -0
- package/src/modules/git-workspace/git-exec.ts +3 -2
- package/src/modules/git-workspace/index.ts +10 -1
- package/src/modules/git-workspace/merge-service.ts +36 -2
- package/src/modules/git-workspace/pr-manager.ts +278 -0
- package/src/modules/git-workspace/template-copy.ts +302 -0
- package/src/modules/git-workspace/worktree-manager.ts +37 -11
- package/src/modules/planning/__tests__/planning-service.test.ts +1 -0
- package/src/modules/planning/__tests__/planning.service.test.ts +149 -0
- package/src/modules/planning/__tests__/project-detection.test.ts +7 -1
- package/src/modules/planning/planning.service.ts +16 -2
- package/src/modules/planning/project-detection.ts +4 -1
- package/src/modules/process-manager/__tests__/process-manager.test.ts +1 -0
- package/src/modules/task-execution/__tests__/executor.test.ts +86 -0
- package/src/modules/task-execution/__tests__/tester-verify.test.ts +4 -3
- package/src/modules/task-execution/executor.ts +87 -4
- package/src/modules/task-execution/phases/implementer-green.ts +22 -5
- package/src/modules/task-execution/phases/merge.ts +44 -2
- package/src/modules/task-execution/phases/tester-red.ts +22 -5
- package/src/modules/task-execution/phases/tester-verify.ts +22 -6
- package/src/modules/task-queue/dispatcher.ts +50 -1
- package/src/shared/__tests__/prerequisites.test.ts +176 -0
- package/src/shared/config.ts +6 -0
- package/src/shared/prerequisites.ts +190 -0
- package/src/shared/types.ts +13 -0
- package/templates/CLAUDE.md +122 -0
- package/templates/settings.json +117 -0
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-81991/workers/worker-2.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-82469/workers/worker-2.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-85301/workers/worker-2.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-85759/workers/worker-2.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-86184/workers/worker-2.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-88026/workers/worker-2.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-89475/workers/worker-2.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/progress.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/completed/task-getall-2.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-1.json +0 -13
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-getall-1.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/queue/pending/task-status-change.json +0 -10
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-1.json +0 -5
- package/src/modules/persistence/__tests__/.tmp-stores-test-89924/workers/worker-2.json +0 -5
|
@@ -10,9 +10,15 @@
|
|
|
10
10
|
header{background:#2d2d2d;padding:20px 24px;border-radius:10px;margin-bottom:20px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px}
|
|
11
11
|
header h1{font-size:22px;font-weight:700;color:#4fc3f7}
|
|
12
12
|
header p{color:#9e9e9e;font-size:13px}
|
|
13
|
-
.
|
|
14
|
-
.
|
|
15
|
-
.
|
|
13
|
+
.activity-cell{font-size:11px;font-weight:600}
|
|
14
|
+
.activity-connected{color:#66bb6a}
|
|
15
|
+
.activity-reconnecting{color:#ffa726}
|
|
16
|
+
.activity-disconnected{color:#ef5350}
|
|
17
|
+
.activity-phase-red{color:#ef5350}
|
|
18
|
+
.activity-phase-green{color:#66bb6a}
|
|
19
|
+
.activity-phase-verify{color:#42a5f5}
|
|
20
|
+
.activity-phase-review{color:#ab47bc}
|
|
21
|
+
.activity-phase-merge{color:#ffa726}
|
|
16
22
|
|
|
17
23
|
/* Progress bar */
|
|
18
24
|
.progress-section{background:#2d2d2d;border-radius:10px;padding:20px 24px;margin-bottom:20px}
|
|
@@ -95,7 +101,6 @@
|
|
|
95
101
|
<body>
|
|
96
102
|
<header>
|
|
97
103
|
<div><h1>AAD Dashboard</h1><p>Real-time Task Orchestrator Monitor</p></div>
|
|
98
|
-
<span class="conn-badge disconnected" id="conn-status">Disconnected</span>
|
|
99
104
|
</header>
|
|
100
105
|
|
|
101
106
|
<!-- Progress Bar -->
|
|
@@ -130,8 +135,8 @@
|
|
|
130
135
|
<h2>Tasks <span class="count" id="tasks-count"></span></h2>
|
|
131
136
|
<div class="task-table-wrap">
|
|
132
137
|
<table>
|
|
133
|
-
<thead><tr><th>ID</th><th>Title</th><th>Status</th><th>Priority</th><th>Dependencies</th></tr></thead>
|
|
134
|
-
<tbody id="task-tbody"><tr><td colspan="
|
|
138
|
+
<thead><tr><th>ID</th><th>Title</th><th>Status</th><th>Activity</th><th>Priority</th><th>Dependencies</th></tr></thead>
|
|
139
|
+
<tbody id="task-tbody"><tr><td colspan="6" class="empty-msg">No tasks</td></tr></tbody>
|
|
135
140
|
</table>
|
|
136
141
|
</div>
|
|
137
142
|
</div>
|
|
@@ -150,6 +155,8 @@
|
|
|
150
155
|
<label><input type="checkbox" checked data-level="warn"> Warn</label>
|
|
151
156
|
<label><input type="checkbox" checked data-level="error"> Error</label>
|
|
152
157
|
<select id="svc-filter"><option value="">All Services</option></select>
|
|
158
|
+
<input type="text" id="log-search" placeholder="Search logs..." style="background:#3a3a3a;color:#e0e0e0;border:1px solid #555;border-radius:4px;padding:4px 10px;font-size:12px;width:160px">
|
|
159
|
+
<button id="filter-reset" style="background:#4fc3f7;color:#1a1a1a;border:none;border-radius:4px;padding:4px 10px;font-size:12px;font-weight:600;cursor:pointer;transition:background .2s">Reset</button>
|
|
153
160
|
</div>
|
|
154
161
|
<div class="log-entries" id="log-entries"></div>
|
|
155
162
|
</div>
|
|
@@ -159,7 +166,10 @@
|
|
|
159
166
|
let allTasks = [];
|
|
160
167
|
let logEntries = [];
|
|
161
168
|
let knownServices = new Set();
|
|
162
|
-
let logFilters = { info: true, warn: true, error: true, service: '' };
|
|
169
|
+
let logFilters = { info: true, warn: true, error: true, service: '', search: '' };
|
|
170
|
+
let taskPhases = {}; // { [taskId]: { phase, status } }
|
|
171
|
+
let sseStatus = 'disconnected'; // Global SSE connection state
|
|
172
|
+
let reconnectAttempts = 0;
|
|
163
173
|
|
|
164
174
|
// --- Data fetching ---
|
|
165
175
|
async function loadAll() {
|
|
@@ -212,13 +222,49 @@
|
|
|
212
222
|
function renderTasks() {
|
|
213
223
|
const tbody = document.getElementById('task-tbody');
|
|
214
224
|
document.getElementById('tasks-count').textContent = '(' + allTasks.length + ')';
|
|
215
|
-
if (!allTasks.length) { tbody.innerHTML = '<tr><td colspan="
|
|
225
|
+
if (!allTasks.length) { tbody.innerHTML = '<tr><td colspan="6" class="empty-msg">No tasks</td></tr>'; return; }
|
|
216
226
|
tbody.innerHTML = allTasks.map(t => {
|
|
217
227
|
const deps = (t.dependsOn || []).map(d => esc(String(d))).join(', ') || '—';
|
|
218
|
-
|
|
228
|
+
const activity = getActivityDisplay(t.taskId, t.status);
|
|
229
|
+
return '<tr><td style="font-family:monospace;font-size:11px">' + esc(String(t.taskId)) + '</td><td>' + esc(t.title || '—') + '</td><td><span class="badge badge-' + t.status + '">' + t.status + '</span></td><td class="activity-cell">' + activity + '</td><td class="priority">' + (t.priority || 0) + '</td><td class="deps">' + deps + '</td></tr>';
|
|
219
230
|
}).join('');
|
|
220
231
|
}
|
|
221
232
|
|
|
233
|
+
function getActivityDisplay(taskId, status) {
|
|
234
|
+
if (status === 'running') {
|
|
235
|
+
const phaseInfo = taskPhases[taskId];
|
|
236
|
+
if (phaseInfo) {
|
|
237
|
+
const phaseLabels = { red: 'Red (Tests)', green: 'Green (Impl)', verify: 'Verify', review: 'Review', merge: 'Merge' };
|
|
238
|
+
const phaseLabel = phaseLabels[phaseInfo.phase] || phaseInfo.phase;
|
|
239
|
+
if (phaseInfo.status === 'running') {
|
|
240
|
+
return '<span class="activity-phase-' + phaseInfo.phase + '">' + esc(phaseLabel) + '</span>';
|
|
241
|
+
} else if (phaseInfo.status === 'completed') {
|
|
242
|
+
return '<span class="activity-phase-' + phaseInfo.phase + '">✓ ' + esc(phaseLabel) + '</span>';
|
|
243
|
+
} else if (phaseInfo.status === 'failed') {
|
|
244
|
+
return '<span class="activity-phase-' + phaseInfo.phase + '">✗ ' + esc(phaseLabel) + '</span>';
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return '<span class="activity-phase-green">Running</span>';
|
|
248
|
+
} else {
|
|
249
|
+
// Non-RUNNING: show SSE connection status
|
|
250
|
+
if (sseStatus === 'connected') {
|
|
251
|
+
return '<span class="activity-connected">Connected</span>';
|
|
252
|
+
} else if (sseStatus === 'reconnecting') {
|
|
253
|
+
return '<span class="activity-reconnecting">Reconnecting...</span>';
|
|
254
|
+
} else {
|
|
255
|
+
return '<span class="activity-disconnected">Disconnected</span>';
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function updateActivityColumn(taskId) {
|
|
261
|
+
renderTasks();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function updateAllActivityColumns() {
|
|
265
|
+
renderTasks();
|
|
266
|
+
}
|
|
267
|
+
|
|
222
268
|
function updateTaskInList(taskId, updates) {
|
|
223
269
|
const idx = allTasks.findIndex(t => t.taskId === taskId);
|
|
224
270
|
if (idx >= 0) Object.assign(allTasks[idx], updates);
|
|
@@ -340,30 +386,94 @@
|
|
|
340
386
|
}
|
|
341
387
|
|
|
342
388
|
function applyLogFilterToEntry(div) {
|
|
343
|
-
|
|
389
|
+
let show = logFilters[div.dataset.level] && (!logFilters.service || div.dataset.service === logFilters.service);
|
|
390
|
+
if (show && logFilters.search) {
|
|
391
|
+
const searchLower = logFilters.search.toLowerCase();
|
|
392
|
+
const text = div.textContent.toLowerCase();
|
|
393
|
+
show = text.includes(searchLower);
|
|
394
|
+
}
|
|
344
395
|
div.classList.toggle('log-hidden', !show);
|
|
345
|
-
updateLogCount();
|
|
346
396
|
}
|
|
347
397
|
|
|
348
398
|
function updateLogCount() {
|
|
399
|
+
const total = logEntries.length;
|
|
349
400
|
const visible = document.querySelectorAll('#log-entries .log-entry:not(.log-hidden)').length;
|
|
350
|
-
document.getElementById('log-count').textContent = '(' + visible + ')';
|
|
401
|
+
document.getElementById('log-count').textContent = '(Showing ' + visible + '/' + total + ')';
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Load filters from localStorage
|
|
405
|
+
function loadFiltersFromStorage() {
|
|
406
|
+
try {
|
|
407
|
+
const saved = localStorage.getItem('aad-log-filters');
|
|
408
|
+
if (saved) {
|
|
409
|
+
const parsed = JSON.parse(saved);
|
|
410
|
+
logFilters = { ...logFilters, ...parsed };
|
|
411
|
+
document.querySelectorAll('.log-filters input[data-level]').forEach(cb => {
|
|
412
|
+
cb.checked = logFilters[cb.dataset.level] !== false;
|
|
413
|
+
});
|
|
414
|
+
const svcSelect = document.getElementById('svc-filter');
|
|
415
|
+
if (svcSelect && logFilters.service) svcSelect.value = logFilters.service;
|
|
416
|
+
const searchInput = document.getElementById('log-search');
|
|
417
|
+
if (searchInput && logFilters.search) searchInput.value = logFilters.search;
|
|
418
|
+
}
|
|
419
|
+
} catch (e) { console.error('Failed to load filters:', e); }
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Save filters to localStorage
|
|
423
|
+
function saveFiltersToStorage() {
|
|
424
|
+
try {
|
|
425
|
+
localStorage.setItem('aad-log-filters', JSON.stringify(logFilters));
|
|
426
|
+
} catch (e) { console.error('Failed to save filters:', e); }
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Reset filters
|
|
430
|
+
function resetFilters() {
|
|
431
|
+
logFilters = { info: true, warn: true, error: true, service: '', search: '' };
|
|
432
|
+
document.querySelectorAll('.log-filters input[data-level]').forEach(cb => {
|
|
433
|
+
cb.checked = true;
|
|
434
|
+
});
|
|
435
|
+
document.getElementById('svc-filter').value = '';
|
|
436
|
+
document.getElementById('log-search').value = '';
|
|
437
|
+
saveFiltersToStorage();
|
|
438
|
+
applyLogFilters();
|
|
351
439
|
}
|
|
352
440
|
|
|
353
441
|
// Filter event listeners
|
|
354
442
|
document.querySelectorAll('.log-filters input[data-level]').forEach(cb => {
|
|
355
|
-
cb.addEventListener('change', () => {
|
|
443
|
+
cb.addEventListener('change', () => {
|
|
444
|
+
logFilters[cb.dataset.level] = cb.checked;
|
|
445
|
+
saveFiltersToStorage();
|
|
446
|
+
applyLogFilters();
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
document.getElementById('svc-filter').addEventListener('change', function() {
|
|
450
|
+
logFilters.service = this.value;
|
|
451
|
+
saveFiltersToStorage();
|
|
452
|
+
applyLogFilters();
|
|
356
453
|
});
|
|
357
|
-
document.getElementById('
|
|
454
|
+
document.getElementById('log-search').addEventListener('input', function() {
|
|
455
|
+
logFilters.search = this.value;
|
|
456
|
+
saveFiltersToStorage();
|
|
457
|
+
applyLogFilters();
|
|
458
|
+
});
|
|
459
|
+
document.getElementById('filter-reset').addEventListener('click', resetFilters);
|
|
358
460
|
|
|
359
461
|
// --- SSE ---
|
|
360
462
|
let evtSource;
|
|
361
463
|
function connectSSE() {
|
|
362
464
|
if (evtSource) { try { evtSource.close(); } catch(e){} }
|
|
363
465
|
evtSource = new EventSource('/events/all');
|
|
364
|
-
const badge = document.getElementById('conn-status');
|
|
365
466
|
|
|
366
|
-
evtSource.addEventListener('open', () => {
|
|
467
|
+
evtSource.addEventListener('open', () => {
|
|
468
|
+
reconnectAttempts = 0;
|
|
469
|
+
sseStatus = 'connected';
|
|
470
|
+
updateAllActivityColumns();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
evtSource.addEventListener('heartbeat', () => {
|
|
474
|
+
sseStatus = 'connected';
|
|
475
|
+
updateAllActivityColumns();
|
|
476
|
+
});
|
|
367
477
|
|
|
368
478
|
evtSource.addEventListener('message', (e) => {
|
|
369
479
|
try {
|
|
@@ -371,6 +481,18 @@
|
|
|
371
481
|
switch (ev.type) {
|
|
372
482
|
case 'log:entry': addLogEntry(ev.entry || ev); break;
|
|
373
483
|
case 'progress:updated': updateProgress(ev.state ? { progress: ev.state, percentComplete: ev.percentComplete } : ev); break;
|
|
484
|
+
case 'execution:phase:started':
|
|
485
|
+
taskPhases[ev.taskId] = { phase: ev.phase, status: 'running' };
|
|
486
|
+
updateActivityColumn(ev.taskId);
|
|
487
|
+
break;
|
|
488
|
+
case 'execution:phase:completed':
|
|
489
|
+
taskPhases[ev.taskId] = { phase: ev.phase, status: 'completed' };
|
|
490
|
+
updateActivityColumn(ev.taskId);
|
|
491
|
+
break;
|
|
492
|
+
case 'execution:phase:failed':
|
|
493
|
+
taskPhases[ev.taskId] = { phase: ev.phase, status: 'failed' };
|
|
494
|
+
updateActivityColumn(ev.taskId);
|
|
495
|
+
break;
|
|
374
496
|
case 'task:dispatched':
|
|
375
497
|
case 'task:completed':
|
|
376
498
|
case 'task:failed':
|
|
@@ -388,9 +510,11 @@
|
|
|
388
510
|
});
|
|
389
511
|
|
|
390
512
|
evtSource.addEventListener('error', () => {
|
|
391
|
-
|
|
513
|
+
reconnectAttempts++;
|
|
514
|
+
sseStatus = reconnectAttempts > 3 ? 'disconnected' : 'reconnecting';
|
|
515
|
+
updateAllActivityColumns();
|
|
392
516
|
evtSource.close();
|
|
393
|
-
setTimeout(connectSSE, 3000);
|
|
517
|
+
setTimeout(connectSSE, Math.min(reconnectAttempts * 3000, 9000));
|
|
394
518
|
});
|
|
395
519
|
}
|
|
396
520
|
|
|
@@ -398,6 +522,7 @@
|
|
|
398
522
|
function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
|
399
523
|
|
|
400
524
|
// Init
|
|
525
|
+
loadFiltersFromStorage();
|
|
401
526
|
loadAll();
|
|
402
527
|
connectSSE();
|
|
403
528
|
</script>
|
|
@@ -199,6 +199,58 @@ describe("BranchManager", () => {
|
|
|
199
199
|
);
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
+
test("excludes branches matching excludePatterns (default: /parent)", async () => {
|
|
203
|
+
const branches = ["feat/run-001/task-001", "feat/run-001/parent", "aad-task-x"];
|
|
204
|
+
let callCount = 0;
|
|
205
|
+
mockGitOps.gitExec = mock(async (args: string[]) => {
|
|
206
|
+
callCount++;
|
|
207
|
+
if (args[0] === "branch" && args[1] === "--list") {
|
|
208
|
+
// Only return branches for the first conventional prefix pattern
|
|
209
|
+
if (callCount === 1) {
|
|
210
|
+
return { stdout: branches.slice(0, 2).join("\n"), stderr: "", exitCode: 0 };
|
|
211
|
+
}
|
|
212
|
+
// aad-* pattern
|
|
213
|
+
if (args[2] === "aad-*") {
|
|
214
|
+
return { stdout: branches[2] ?? "", stderr: "", exitCode: 0 };
|
|
215
|
+
}
|
|
216
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
217
|
+
}
|
|
218
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const deleted = await branchManager.cleanupOrphanBranches();
|
|
222
|
+
// feat/run-001/parent はスキップ、他2つは削除
|
|
223
|
+
expect(deleted).not.toContain("feat/run-001/parent");
|
|
224
|
+
expect(deleted.length).toBe(2);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("deletes all branches when excludePatterns is empty array", async () => {
|
|
228
|
+
const branches = ["feat/run-001/parent", "feat/run-001/task-001"];
|
|
229
|
+
mockGitOps.gitExec = mock(async (args: string[]) => {
|
|
230
|
+
if (args[0] === "branch" && args[1] === "--list") {
|
|
231
|
+
if (args[2]?.startsWith("feat/")) {
|
|
232
|
+
return { stdout: branches.join("\n"), stderr: "", exitCode: 0 };
|
|
233
|
+
}
|
|
234
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
235
|
+
}
|
|
236
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const deleted = await branchManager.cleanupOrphanBranches(undefined, {
|
|
240
|
+
force: false,
|
|
241
|
+
excludePatterns: [],
|
|
242
|
+
});
|
|
243
|
+
// parent含めて全削除
|
|
244
|
+
expect(deleted).toContain("feat/run-001/parent");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("supports legacy boolean argument (backward compat)", async () => {
|
|
248
|
+
mockGitOps.gitExec = mock(async () => ({ stdout: "", stderr: "", exitCode: 0 }));
|
|
249
|
+
const deleted = await branchManager.cleanupOrphanBranches(undefined, true);
|
|
250
|
+
expect(deleted).toEqual([]);
|
|
251
|
+
// No error = backward compat OK
|
|
252
|
+
});
|
|
253
|
+
|
|
202
254
|
test("cleans up orphan branches for specific runId", async () => {
|
|
203
255
|
const runId = createRunId("run-001");
|
|
204
256
|
const branches = ["aad-task-run-001-01", "aad-run-run-001"];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { buildInstallCommand } from "../dependency-installer";
|
|
3
|
+
import type { WorkspaceInfo } from "../../../shared/types";
|
|
4
|
+
|
|
5
|
+
describe("buildInstallCommand", () => {
|
|
6
|
+
// Helper to create minimal WorkspaceInfo
|
|
7
|
+
const createWorkspace = (packageManager: WorkspaceInfo["packageManager"]): WorkspaceInfo => ({
|
|
8
|
+
path: "/tmp/test",
|
|
9
|
+
language: "typescript",
|
|
10
|
+
packageManager,
|
|
11
|
+
framework: "none",
|
|
12
|
+
testFramework: "bun-test",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("Node.js ecosystem", () => {
|
|
16
|
+
test("bun → bun install --frozen-lockfile", () => {
|
|
17
|
+
const result = buildInstallCommand(createWorkspace("bun"));
|
|
18
|
+
expect(result).toEqual(["bun", "install", "--frozen-lockfile"]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("npm → npm ci", () => {
|
|
22
|
+
const result = buildInstallCommand(createWorkspace("npm"));
|
|
23
|
+
expect(result).toEqual(["npm", "ci"]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("yarn → yarn install --frozen-lockfile", () => {
|
|
27
|
+
const result = buildInstallCommand(createWorkspace("yarn"));
|
|
28
|
+
expect(result).toEqual(["yarn", "install", "--frozen-lockfile"]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("pnpm → pnpm install --frozen-lockfile", () => {
|
|
32
|
+
const result = buildInstallCommand(createWorkspace("pnpm"));
|
|
33
|
+
expect(result).toEqual(["pnpm", "install", "--frozen-lockfile"]);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("Python ecosystem", () => {
|
|
38
|
+
test("uv → uv sync", () => {
|
|
39
|
+
const result = buildInstallCommand(createWorkspace("uv"));
|
|
40
|
+
expect(result).toEqual(["uv", "sync"]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("poetry → poetry install --no-interaction", () => {
|
|
44
|
+
const result = buildInstallCommand(createWorkspace("poetry"));
|
|
45
|
+
expect(result).toEqual(["poetry", "install", "--no-interaction"]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("pipenv → pipenv install", () => {
|
|
49
|
+
const result = buildInstallCommand(createWorkspace("pipenv"));
|
|
50
|
+
expect(result).toEqual(["pipenv", "install"]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("pip → pip install -r requirements.txt", () => {
|
|
54
|
+
const result = buildInstallCommand(createWorkspace("pip"));
|
|
55
|
+
expect(result).toEqual(["pip", "install", "-r", "requirements.txt"]);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("Go/Rust ecosystem", () => {
|
|
60
|
+
test("go → go mod download", () => {
|
|
61
|
+
const result = buildInstallCommand(createWorkspace("go"));
|
|
62
|
+
expect(result).toEqual(["go", "mod", "download"]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("cargo → cargo fetch", () => {
|
|
66
|
+
const result = buildInstallCommand(createWorkspace("cargo"));
|
|
67
|
+
expect(result).toEqual(["cargo", "fetch"]);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("Unknown/No-op package managers", () => {
|
|
72
|
+
test("unknown → null", () => {
|
|
73
|
+
const result = buildInstallCommand(createWorkspace("unknown"));
|
|
74
|
+
expect(result).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -88,4 +88,30 @@ describe("git-exec", () => {
|
|
|
88
88
|
expect(exists).toBe(false);
|
|
89
89
|
});
|
|
90
90
|
});
|
|
91
|
+
|
|
92
|
+
describe("allowNonZeroExit option", () => {
|
|
93
|
+
test("does not throw on non-zero exit when allowNonZeroExit is true", async () => {
|
|
94
|
+
// git diff --cached --quiet は変更なしで exit 0
|
|
95
|
+
const result = await gitExec(["diff", "--cached", "--quiet"], {
|
|
96
|
+
cwd: TEST_DIR,
|
|
97
|
+
allowNonZeroExit: true,
|
|
98
|
+
});
|
|
99
|
+
expect(result.exitCode).toBe(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("returns non-zero exitCode without throwing", async () => {
|
|
103
|
+
// 存在しないref → exit 128
|
|
104
|
+
const result = await gitExec(["rev-parse", "--verify", "nonexistent-ref"], {
|
|
105
|
+
cwd: TEST_DIR,
|
|
106
|
+
allowNonZeroExit: true,
|
|
107
|
+
});
|
|
108
|
+
expect(result.exitCode).not.toBe(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("still throws when allowNonZeroExit is false (default)", async () => {
|
|
112
|
+
await expect(
|
|
113
|
+
gitExec(["rev-parse", "--verify", "nonexistent-ref"], { cwd: TEST_DIR })
|
|
114
|
+
).rejects.toThrow("Git command failed");
|
|
115
|
+
});
|
|
116
|
+
});
|
|
91
117
|
});
|
|
@@ -111,6 +111,25 @@ describe("MergeService", () => {
|
|
|
111
111
|
expect(result.message).toContain("Successfully merged");
|
|
112
112
|
}, 60_000);
|
|
113
113
|
|
|
114
|
+
test("sets alreadyUpToDate when task branch has no new commits", async () => {
|
|
115
|
+
const mergeService = await resetWorktree();
|
|
116
|
+
|
|
117
|
+
// taskBranch = 現在のHEADと同じ → Already up to date
|
|
118
|
+
const taskBranch = "task-no-changes";
|
|
119
|
+
await gitExec(["checkout", DEFAULT_BRANCH], { cwd: PARENT_WORKTREE });
|
|
120
|
+
await gitExec(["branch", taskBranch], { cwd: PARENT_WORKTREE });
|
|
121
|
+
|
|
122
|
+
const result = await mergeService.mergeToParent(
|
|
123
|
+
createTaskId("task-noop"),
|
|
124
|
+
taskBranch,
|
|
125
|
+
DEFAULT_BRANCH,
|
|
126
|
+
PARENT_WORKTREE
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
expect(result.success).toBe(true);
|
|
130
|
+
expect(result.alreadyUpToDate).toBe(true);
|
|
131
|
+
}, 60_000);
|
|
132
|
+
|
|
114
133
|
test("detects merge conflicts and aborts merge", async () => {
|
|
115
134
|
const mergeService = await resetWorktree();
|
|
116
135
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from "bun:test";
|
|
2
|
+
import { PrManager } from "../pr-manager";
|
|
3
|
+
import type { PrManagerOptions, DraftPrOptions } from "../pr-manager";
|
|
4
|
+
import { GitWorkspaceError } from "@aad/shared/errors";
|
|
5
|
+
|
|
6
|
+
describe("PrManager", () => {
|
|
7
|
+
let options: PrManagerOptions;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
options = {
|
|
11
|
+
repoRoot: process.cwd(),
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("should create instance", () => {
|
|
16
|
+
const manager = new PrManager(options);
|
|
17
|
+
expect(manager).toBeDefined();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("should check gh installation", async () => {
|
|
21
|
+
const manager = new PrManager(options);
|
|
22
|
+
const installed = await manager.checkGhInstalled();
|
|
23
|
+
// May be true or false depending on environment
|
|
24
|
+
expect(typeof installed).toBe("boolean");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("should throw error if gh not installed when creating draft PR", async () => {
|
|
28
|
+
const manager = new PrManager(options);
|
|
29
|
+
|
|
30
|
+
// Mock checkGhInstalled to return false
|
|
31
|
+
manager.checkGhInstalled = async () => false;
|
|
32
|
+
|
|
33
|
+
const draftOptions: DraftPrOptions = {
|
|
34
|
+
title: "Test PR",
|
|
35
|
+
body: "Test body",
|
|
36
|
+
baseBranch: "main",
|
|
37
|
+
headBranch: "feature/test",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
await expect(manager.createDraftPr(draftOptions)).rejects.toThrow(GitWorkspaceError);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("should throw error if gh not installed when marking PR ready", async () => {
|
|
44
|
+
const manager = new PrManager(options);
|
|
45
|
+
|
|
46
|
+
// Mock checkGhInstalled to return false
|
|
47
|
+
manager.checkGhInstalled = async () => false;
|
|
48
|
+
|
|
49
|
+
await expect(manager.markPrReady(123)).rejects.toThrow(GitWorkspaceError);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("should return null for getPrInfo if gh not installed", async () => {
|
|
53
|
+
const manager = new PrManager(options);
|
|
54
|
+
|
|
55
|
+
// Mock checkGhInstalled to return false
|
|
56
|
+
manager.checkGhInstalled = async () => false;
|
|
57
|
+
|
|
58
|
+
const result = await manager.getPrInfo(123);
|
|
59
|
+
expect(result).toBeNull();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("should return null for findPrByBranch if gh not installed", async () => {
|
|
63
|
+
const manager = new PrManager(options);
|
|
64
|
+
|
|
65
|
+
// Mock checkGhInstalled to return false
|
|
66
|
+
manager.checkGhInstalled = async () => false;
|
|
67
|
+
|
|
68
|
+
const result = await manager.findPrByBranch("feature/test");
|
|
69
|
+
expect(result).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("should throw error if gh not installed when closing PR", async () => {
|
|
73
|
+
const manager = new PrManager(options);
|
|
74
|
+
|
|
75
|
+
// Mock checkGhInstalled to return false
|
|
76
|
+
manager.checkGhInstalled = async () => false;
|
|
77
|
+
|
|
78
|
+
await expect(manager.closePr(123)).rejects.toThrow(GitWorkspaceError);
|
|
79
|
+
});
|
|
80
|
+
});
|