@yemi33/minions 0.1.2157 → 0.1.2159
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/dashboard/js/detail-panel.js +1 -1
- package/dashboard/js/fre.js +5 -12
- package/dashboard/js/qa.js +13 -13
- package/dashboard/js/render-agents.js +1 -1
- package/dashboard/js/render-inbox.js +1 -1
- package/dashboard/js/render-kb.js +1 -1
- package/dashboard/js/render-meetings.js +8 -8
- package/dashboard/js/render-other.js +5 -5
- package/dashboard/js/render-pinned.js +3 -3
- package/dashboard/js/render-pipelines.js +6 -6
- package/dashboard/js/render-plans.js +2 -2
- package/dashboard/js/render-prd.js +4 -4
- package/dashboard/js/render-prs.js +2 -2
- package/dashboard/js/render-schedules.js +3 -3
- package/dashboard/js/render-watches.js +5 -5
- package/dashboard/js/render-work-items.js +4 -4
- package/dashboard/js/settings.js +2 -4
- package/dashboard/layout.html +1 -1
- package/dashboard/pages/qa.html +1 -1
- package/dashboard/styles.css +170 -14
- package/docs/completion-reports.md +39 -0
- package/engine/playbook.js +21 -1
- package/engine.js +8 -0
- package/package.json +1 -1
- package/playbooks/_pr-description-audit.md +142 -0
- package/playbooks/fix.md +71 -0
- package/playbooks/implement.md +71 -0
|
@@ -118,7 +118,7 @@ function renderDetailContent(detail, tab) {
|
|
|
118
118
|
el.innerHTML =
|
|
119
119
|
'<div style="display:flex;gap:6px;margin-bottom:8px">' +
|
|
120
120
|
'<button class="pr-pager-btn" id="charter-edit-btn" style="font-size:var(--text-sm);padding:2px 10px" onclick="_toggleCharterEdit()">Edit</button>' +
|
|
121
|
-
'<button class="
|
|
121
|
+
'<button class="modal-copy is-success" id="charter-save-btn" style="display:none" onclick="_saveCharter()">Save</button>' +
|
|
122
122
|
'<button class="pr-pager-btn" id="charter-cancel-btn" style="font-size:var(--text-sm);padding:2px 10px;display:none" onclick="_cancelCharterEdit()">Cancel</button>' +
|
|
123
123
|
'</div>' +
|
|
124
124
|
'<div id="charter-view" class="section">' + renderMd(charterContent || 'No charter found. Click Edit to create one.') + '</div>' +
|
package/dashboard/js/fre.js
CHANGED
|
@@ -120,17 +120,10 @@ function renderFre(statusOrProjects) {
|
|
|
120
120
|
'flex-shrink:0',
|
|
121
121
|
].join(';');
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
'border:none',
|
|
128
|
-
'border-radius:var(--radius-sm)',
|
|
129
|
-
'cursor:pointer',
|
|
130
|
-
'font-size:var(--text-md)',
|
|
131
|
-
'font-weight:600',
|
|
132
|
-
'margin-top:8px',
|
|
133
|
-
].join(';');
|
|
123
|
+
// W-mq57qetc0009ed48: blue-primary CTA inline blob removed; "+ Add Project"
|
|
124
|
+
// now uses the shared .btn-primary-solid primitive. Only the layout-only
|
|
125
|
+
// margin-top:8px stays inline.
|
|
126
|
+
const btnPrimaryMarginTop = 'margin-top:8px';
|
|
134
127
|
|
|
135
128
|
const btnGhost = [
|
|
136
129
|
'padding:6px 14px',
|
|
@@ -187,7 +180,7 @@ function renderFre(statusOrProjects) {
|
|
|
187
180
|
'<div style="color:var(--muted);font-size:var(--text-md);line-height:1.5">' +
|
|
188
181
|
'Projects map a local git worktree to a remote repo. Without a project, agents have nowhere to run.' +
|
|
189
182
|
'</div>' +
|
|
190
|
-
'<button onclick="addProject()" style="' +
|
|
183
|
+
'<button onclick="addProject()" class="btn-primary-solid" style="' + btnPrimaryMarginTop + '">+ Add Project</button>' +
|
|
191
184
|
'</div>' +
|
|
192
185
|
'</div>' +
|
|
193
186
|
|
package/dashboard/js/qa.js
CHANGED
|
@@ -229,8 +229,8 @@ async function loadQaRunbooks() {
|
|
|
229
229
|
'</div>' +
|
|
230
230
|
'</div>' +
|
|
231
231
|
'<div class="qa-runbook-actions" style="display:flex;gap:var(--space-2)">' +
|
|
232
|
-
'<button class="
|
|
233
|
-
'<button class="
|
|
232
|
+
'<button class="btn-primary-solid qa-runbook-run-btn" data-runbook-id="' + escHtml(id) + '" onclick="qaRunRunbook(this.dataset.runbookId)">Run</button>' +
|
|
233
|
+
'<button class="btn-ghost qa-btn-danger qa-runbook-delete-btn" data-runbook-id="' + escHtml(id) + '" onclick="qaDeleteRunbook(this.dataset.runbookId)">Delete</button>' +
|
|
234
234
|
'</div>' +
|
|
235
235
|
'</div>';
|
|
236
236
|
}).join('');
|
|
@@ -350,7 +350,7 @@ function _qaRenderRunRow(run) {
|
|
|
350
350
|
// to click.
|
|
351
351
|
let deleteBtn = '';
|
|
352
352
|
if (_qaIsTerminalStatus(status)) {
|
|
353
|
-
deleteBtn = '<button class="
|
|
353
|
+
deleteBtn = '<button class="btn-ghost qa-run-delete-btn" ' +
|
|
354
354
|
'data-run-id="' + escHtml(id) + '" ' +
|
|
355
355
|
'onclick="qaDeleteRun(this.dataset.runId)">Delete</button>';
|
|
356
356
|
}
|
|
@@ -731,14 +731,14 @@ function _qaRenderSessionCard(s) {
|
|
|
731
731
|
// - terminal → no actions
|
|
732
732
|
let actions = '';
|
|
733
733
|
if (state === 'awaiting-approval') {
|
|
734
|
-
actions += '<button class="
|
|
735
|
-
actions += '<button class="
|
|
736
|
-
actions += '<button class="
|
|
737
|
-
actions += '<button class="
|
|
734
|
+
actions += '<button class="btn-ghost" onclick="qaSessionReviewDraft(\'' + escHtml(id) + '\')">Review draft</button>';
|
|
735
|
+
actions += '<button class="btn-primary-solid" onclick="qaSessionApprove(\'' + escHtml(id) + '\')">Approve & run</button>';
|
|
736
|
+
actions += '<button class="btn-ghost" onclick="qaSessionEditPrompt(\'' + escHtml(id) + '\')">Edit</button>';
|
|
737
|
+
actions += '<button class="btn-ghost" onclick="qaSessionCancel(\'' + escHtml(id) + '\')">Cancel</button>';
|
|
738
738
|
}
|
|
739
739
|
if (!isTerminal) {
|
|
740
|
-
actions += '<button class="
|
|
741
|
-
actions += '<button class="
|
|
740
|
+
actions += '<button class="btn-ghost" onclick="qaSessionDismiss(\'' + escHtml(id) + '\')">Dismiss</button>';
|
|
741
|
+
actions += '<button class="btn-ghost qa-btn-danger" onclick="qaSessionKill(\'' + escHtml(id) + '\')">Kill spawn</button>';
|
|
742
742
|
}
|
|
743
743
|
|
|
744
744
|
let meta = '';
|
|
@@ -976,7 +976,7 @@ function _qaBuildModalShell(sessionId) {
|
|
|
976
976
|
header.appendChild(title);
|
|
977
977
|
const closeBtn = document.createElement('button');
|
|
978
978
|
closeBtn.type = 'button';
|
|
979
|
-
closeBtn.className = '
|
|
979
|
+
closeBtn.className = 'btn-ghost qa-modal-close';
|
|
980
980
|
closeBtn.setAttribute('aria-label', 'Close');
|
|
981
981
|
closeBtn.textContent = '×';
|
|
982
982
|
closeBtn.addEventListener('click', _qaModalClose);
|
|
@@ -1080,7 +1080,7 @@ function _qaModalRenderBody(payload) {
|
|
|
1080
1080
|
if (!payload.truncated) {
|
|
1081
1081
|
const approve = document.createElement('button');
|
|
1082
1082
|
approve.type = 'button';
|
|
1083
|
-
approve.className = '
|
|
1083
|
+
approve.className = 'btn-primary-solid';
|
|
1084
1084
|
approve.textContent = 'Approve & run';
|
|
1085
1085
|
approve.addEventListener('click', () => {
|
|
1086
1086
|
_qaModalClose();
|
|
@@ -1090,7 +1090,7 @@ function _qaModalRenderBody(payload) {
|
|
|
1090
1090
|
}
|
|
1091
1091
|
const editBtn = document.createElement('button');
|
|
1092
1092
|
editBtn.type = 'button';
|
|
1093
|
-
editBtn.className = '
|
|
1093
|
+
editBtn.className = 'btn-ghost';
|
|
1094
1094
|
editBtn.textContent = 'Request edit';
|
|
1095
1095
|
editBtn.addEventListener('click', () => {
|
|
1096
1096
|
_qaModalClose();
|
|
@@ -1099,7 +1099,7 @@ function _qaModalRenderBody(payload) {
|
|
|
1099
1099
|
footer.appendChild(editBtn);
|
|
1100
1100
|
const closeBtn = document.createElement('button');
|
|
1101
1101
|
closeBtn.type = 'button';
|
|
1102
|
-
closeBtn.className = '
|
|
1102
|
+
closeBtn.className = 'btn-ghost';
|
|
1103
1103
|
closeBtn.textContent = 'Close';
|
|
1104
1104
|
closeBtn.addEventListener('click', _qaModalClose);
|
|
1105
1105
|
footer.appendChild(closeBtn);
|
|
@@ -143,7 +143,7 @@ async function openAgentDetail(id) {
|
|
|
143
143
|
document.getElementById('detail-content').innerHTML =
|
|
144
144
|
'<div style="padding:24px;text-align:center">' +
|
|
145
145
|
'<div style="color:var(--red);margin-bottom:12px">Error loading agent detail: ' + escapeHtml(e.message) + '</div>' +
|
|
146
|
-
'<button data-agent-id="' + escapeHtml(id) + '" onclick="openAgentDetail(this.dataset.agentId)"
|
|
146
|
+
'<button data-agent-id="' + escapeHtml(id) + '" onclick="openAgentDetail(this.dataset.agentId)" class="btn-primary-lg">Retry</button>' +
|
|
147
147
|
' <button onclick="closeDetail()" style="padding:6px 16px;background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm);cursor:pointer;font-size:var(--text-md);color:var(--text)">Close</button>' +
|
|
148
148
|
'</div>';
|
|
149
149
|
}
|
|
@@ -270,7 +270,7 @@ function openQuickNoteModal() {
|
|
|
270
270
|
'<textarea id="note-content" rows="6" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);resize:vertical;font-family:inherit" placeholder="Write your note... Agents will see this after consolidation."></textarea></label>' +
|
|
271
271
|
'<div style="display:flex;justify-content:flex-end;gap:8px">' +
|
|
272
272
|
'<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
|
|
273
|
-
'<button onclick="submitQuickNote()"
|
|
273
|
+
'<button onclick="submitQuickNote()" class="btn-primary-lg">Save Note</button>' +
|
|
274
274
|
'</div>' +
|
|
275
275
|
'</div>';
|
|
276
276
|
document.getElementById('modal').classList.add('open');
|
|
@@ -222,7 +222,7 @@ function openCreateKbModal() {
|
|
|
222
222
|
'<textarea id="kb-new-content" rows="8" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);resize:vertical;font-family:inherit" placeholder="Write your knowledge entry..."></textarea></label>' +
|
|
223
223
|
'<div style="display:flex;justify-content:flex-end;gap:8px">' +
|
|
224
224
|
'<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
|
|
225
|
-
'<button onclick="submitKbEntry(event)"
|
|
225
|
+
'<button onclick="submitKbEntry(event)" class="btn-primary-lg">Save</button>' +
|
|
226
226
|
'</div>' +
|
|
227
227
|
'</div>';
|
|
228
228
|
document.getElementById('modal').classList.add('open');
|
|
@@ -123,7 +123,7 @@ function renderMeetings(meetings, opts) {
|
|
|
123
123
|
(m.status === 'archived'
|
|
124
124
|
? '<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:1px 6px" onclick="event.stopPropagation();_unarchiveMeeting(\'' + escHtml(m.id) + '\')">Unarchive</button>'
|
|
125
125
|
: (m.status === 'completed' ? '<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:1px 6px" onclick="event.stopPropagation();_archiveMeeting(\'' + escHtml(m.id) + '\')">Archive</button>' : '')) +
|
|
126
|
-
'<button class="
|
|
126
|
+
'<button class="btn-destructive" onclick="event.stopPropagation();_deleteMeeting(\'' + escHtml(m.id) + '\')">Delete</button>' +
|
|
127
127
|
'</div>' +
|
|
128
128
|
'</div>' +
|
|
129
129
|
(timeStr ? '<div style="margin-top:4px;font-size:var(--text-sm);color:var(--muted)">' + escHtml(timeStr) + '</div>' : '') +
|
|
@@ -246,17 +246,17 @@ function _renderMeetingDetail(m) {
|
|
|
246
246
|
if (m.status === 'archived') {
|
|
247
247
|
html += '<div style="display:flex;gap:8px;border-top:1px solid var(--border);padding-top:8px">' +
|
|
248
248
|
'<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:2px 8px" onclick="_unarchiveMeeting(\'' + escHtml(m.id) + '\')">Unarchive</button>' +
|
|
249
|
-
'<button class="
|
|
249
|
+
'<button class="btn-destructive btn-destructive-md" onclick="_deleteMeeting(\'' + escHtml(m.id) + '\')">Delete</button>' +
|
|
250
250
|
'</div>';
|
|
251
251
|
} else if (m.status === 'completed') {
|
|
252
252
|
const linkedPlan = _findLinkedPlan(m);
|
|
253
253
|
html += '<div style="display:flex;gap:8px;flex-wrap:wrap;border-top:1px solid var(--border);padding-top:8px">' +
|
|
254
254
|
(linkedPlan
|
|
255
255
|
? '<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:2px 8px;color:var(--blue);border-color:var(--blue)" onclick="_viewPlanWithBack(\'' + escHtml(linkedPlan.file) + '\',\'' + escHtml(m.id) + '\')">View Plan</button>' +
|
|
256
|
-
'<button class="
|
|
257
|
-
: '<button class="
|
|
256
|
+
'<button class="btn-action btn-action-md" onclick="_createPlanFromMeeting(\'' + escHtml(m.id) + '\',this)">New Plan</button>'
|
|
257
|
+
: '<button class="btn-action btn-action-md" onclick="_createPlanFromMeeting(\'' + escHtml(m.id) + '\',this)">Create Plan from Meeting</button>') +
|
|
258
258
|
'<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:2px 8px" onclick="_archiveMeeting(\'' + escHtml(m.id) + '\')">Archive</button>' +
|
|
259
|
-
'<button class="
|
|
259
|
+
'<button class="btn-destructive btn-destructive-md" onclick="_deleteMeeting(\'' + escHtml(m.id) + '\')">Delete</button>' +
|
|
260
260
|
'</div>' +
|
|
261
261
|
'<div id="meeting-plan-toast" class="cmd-toast cmd-toast-inline" style="margin-top:6px"></div>' +
|
|
262
262
|
'<div style="font-size:var(--text-xs);color:var(--muted);margin-top:4px">Use the Q&A below to discuss action items' + (linkedPlan ? '' : ', then create a plan to execute them') + '.</div>';
|
|
@@ -267,8 +267,8 @@ function _renderMeetingDetail(m) {
|
|
|
267
267
|
'</div>' +
|
|
268
268
|
'<div style="display:flex;gap:8px;margin-top:4px">' +
|
|
269
269
|
'<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:2px 8px;color:var(--yellow);border-color:var(--yellow)" onclick="_advanceMeeting(\'' + escHtml(m.id) + '\',this)">Skip to Next Round</button>' +
|
|
270
|
-
'<button class="
|
|
271
|
-
'<button class="
|
|
270
|
+
'<button class="btn-destructive btn-destructive-md" onclick="_endMeeting(\'' + escHtml(m.id) + '\',this)">End Meeting</button>' +
|
|
271
|
+
'<button class="btn-destructive btn-destructive-md" onclick="_deleteMeeting(\'' + escHtml(m.id) + '\')">Delete</button>' +
|
|
272
272
|
'</div>';
|
|
273
273
|
}
|
|
274
274
|
|
|
@@ -347,7 +347,7 @@ function openCreateMeetingModal() {
|
|
|
347
347
|
'<div style="color:var(--text);font-size:var(--text-md)">Participants<div style="display:flex;flex-direction:column;gap:4px;margin-top:4px" id="mtg-participants">' + agentOpts + '</div></div>' +
|
|
348
348
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:4px">' +
|
|
349
349
|
'<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
|
|
350
|
-
'<button onclick="_submitCreateMeeting(event)"
|
|
350
|
+
'<button onclick="_submitCreateMeeting(event)" class="btn-primary-lg">Start Meeting</button>' +
|
|
351
351
|
'</div>' +
|
|
352
352
|
'</div>';
|
|
353
353
|
document.getElementById('modal').classList.add('open');
|
|
@@ -5,8 +5,8 @@ function renderProjects(projects) {
|
|
|
5
5
|
const visible = (projects || []).filter(p => typeof isDeleted === 'function' ? !isDeleted('project:' + p.name) : true);
|
|
6
6
|
if (!visible.length) {
|
|
7
7
|
list.innerHTML = '<span style="color:var(--muted);font-style:italic">No projects linked.</span>' +
|
|
8
|
-
'<span onclick="addProject()"
|
|
9
|
-
'<span onclick="openScanProjectsModal()"
|
|
8
|
+
'<span onclick="addProject()" class="empty-state-add" style="margin-left:8px">+ Add Project</span>' +
|
|
9
|
+
'<span onclick="openScanProjectsModal()" class="empty-state-add empty-state-add-blue">Scan</span>';
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: project name, path, branch metadata)
|
|
@@ -17,8 +17,8 @@ function renderProjects(projects) {
|
|
|
17
17
|
'<span onclick="event.stopPropagation();projectChipRemove(\'' + escHtml(p.name) + '\')" title="Remove project (cancels pending work, archives data dir)" style="color:var(--muted);font-weight:600;cursor:pointer;padding:0 2px;line-height:1" onmouseover="this.style.color=\'var(--red)\'" onmouseout="this.style.color=\'var(--muted)\'">×</span>' +
|
|
18
18
|
'</span>'
|
|
19
19
|
).join('') +
|
|
20
|
-
'<span onclick="addProject()"
|
|
21
|
-
'<span onclick="openScanProjectsModal()"
|
|
20
|
+
'<span onclick="addProject()" class="empty-state-add empty-state-add-muted">+ Add</span>' +
|
|
21
|
+
'<span onclick="openScanProjectsModal()" class="empty-state-add empty-state-add-blue">Scan</span>';
|
|
22
22
|
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -422,7 +422,7 @@ async function openScanProjectsModal() {
|
|
|
422
422
|
'<label style="width:60px;color:var(--text);font-size:var(--text-md)">Depth' +
|
|
423
423
|
'<input id="scan-depth" type="number" value="3" min="1" max="6" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);font-size:var(--text-md)">' +
|
|
424
424
|
'</label>' +
|
|
425
|
-
'<button onclick="_runProjectScan()"
|
|
425
|
+
'<button onclick="_runProjectScan()" class="btn-primary-lg" style="white-space:nowrap">Scan</button>' +
|
|
426
426
|
'</div>' +
|
|
427
427
|
'<div id="scan-results" style="color:var(--muted);font-size:var(--text-md)">Click Scan to find git repos in the directory.</div>' +
|
|
428
428
|
'<div class="cmd-toast" id="scan-toast" style="margin-top:0"></div>' +
|
|
@@ -17,7 +17,7 @@ function renderPinned(entries) {
|
|
|
17
17
|
';border-radius:4px;cursor:pointer" onclick="if(shouldIgnoreSelectionClick(event))return;openPinnedView(' + i + ')">' +
|
|
18
18
|
'<div style="display:flex;justify-content:space-between;align-items:center">' +
|
|
19
19
|
'<strong style="font-size:var(--text-md)">' + escHtml(e.title) + '</strong>' +
|
|
20
|
-
'<button class="
|
|
20
|
+
'<button class="btn-destructive" data-pin-title="' + escHtml(e.title) + '" onclick="event.stopPropagation();removePinnedNote(this.dataset.pinTitle)">Unpin</button>' +
|
|
21
21
|
'</div>' +
|
|
22
22
|
'<div style="font-size:var(--text-sm);color:var(--muted);margin-top:4px">' + renderMd(e.content.slice(0, 200)) + (e.content.length > 200 ? '...' : '') + '</div>' +
|
|
23
23
|
'</div>'
|
|
@@ -44,7 +44,7 @@ function openPinNoteModal() {
|
|
|
44
44
|
'<label style="color:var(--text);font-size:var(--text-md)">Title<input id="pin-title" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text)" placeholder="e.g. API freeze until Friday"></label>' +
|
|
45
45
|
'<label style="color:var(--text);font-size:var(--text-md)">Content<textarea id="pin-content" rows="4" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);resize:vertical;font-family:inherit" placeholder="Context all agents should see..."></textarea></label>' +
|
|
46
46
|
'<label style="color:var(--text);font-size:var(--text-md)">Level<select id="pin-level" style="display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text)"><option value="info">Info</option><option value="warning">Warning</option><option value="critical">Critical</option></select></label>' +
|
|
47
|
-
'<div style="display:flex;justify-content:flex-end;gap:8px"><button onclick="closeModal()" class="pr-pager-btn">Cancel</button><button onclick="submitPinnedNote(event)"
|
|
47
|
+
'<div style="display:flex;justify-content:flex-end;gap:8px"><button onclick="closeModal()" class="pr-pager-btn">Cancel</button><button onclick="submitPinnedNote(event)" class="btn-primary-lg">Pin</button></div>' +
|
|
48
48
|
'</div>';
|
|
49
49
|
document.getElementById('modal').classList.add('open');
|
|
50
50
|
}
|
|
@@ -194,7 +194,7 @@ function openPinnedEdit(idx) {
|
|
|
194
194
|
cancelBtn.textContent = 'Cancel';
|
|
195
195
|
cancelBtn.onclick = function() { openPinnedView(idx); };
|
|
196
196
|
const saveBtn = document.createElement('button');
|
|
197
|
-
saveBtn.
|
|
197
|
+
saveBtn.className = 'btn-primary-lg';
|
|
198
198
|
saveBtn.textContent = 'Save';
|
|
199
199
|
saveBtn.onclick = function(ev) { submitPinnedEdit(originalTitle, ev); };
|
|
200
200
|
actions.appendChild(cancelBtn);
|
|
@@ -399,12 +399,12 @@ function openPipelineDetail(id) {
|
|
|
399
399
|
'<span style="font-size:var(--text-sm);color:var(--muted)">' + _renderPipelineTriggerLabel(p.trigger?.cron) + ' · ' + escHtml(_getPipelineStageLabel(p)) + '</span>' +
|
|
400
400
|
'<div style="display:flex;gap:6px">' +
|
|
401
401
|
(activeRun
|
|
402
|
-
? '<button class="
|
|
402
|
+
? '<button class="btn-destructive btn-destructive-md" onclick="_abortPipeline(\'' + escHtml(id) + '\',this)">Abort</button>' +
|
|
403
403
|
'<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:2px 8px;color:var(--yellow);border-color:var(--yellow)" onclick="_retriggerPipeline(\'' + escHtml(id) + '\',this)">Retrigger</button>'
|
|
404
|
-
: '<button class="
|
|
404
|
+
: '<button class="btn-action btn-action-md" onclick="_triggerPipeline(\'' + escHtml(id) + '\',this)">Run Now</button>') +
|
|
405
405
|
'<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:2px 8px;color:var(--blue);border-color:var(--blue)" onclick="openEditPipelineModal(\'' + escHtml(id) + '\')">Edit</button>' +
|
|
406
406
|
'<button class="pr-pager-btn' + (_pipelineToggleInFlight.has(id) ? ' disabled' : '') + '" style="font-size:var(--text-xs);padding:2px 8px" onclick="_togglePipelineEnabled(\'' + escHtml(id) + '\',' + !p.enabled + ',this)">' + (p.enabled !== false ? 'Disable' : 'Enable') + '</button>' +
|
|
407
|
-
'<button class="
|
|
407
|
+
'<button class="btn-destructive btn-destructive-md" onclick="_deletePipelineConfirm(\'' + escHtml(id) + '\')">Delete</button>' +
|
|
408
408
|
'</div>' +
|
|
409
409
|
'</div>';
|
|
410
410
|
|
|
@@ -454,7 +454,7 @@ function openPipelineDetail(id) {
|
|
|
454
454
|
_renderMonitoredResources(s.monitoredResources || []) +
|
|
455
455
|
_renderArtifactLinks(stageRun.artifacts, id) +
|
|
456
456
|
(stageRun.output ? '<div class="pipeline-stage-output" style="margin-top:6px;font-size:var(--text-base);max-height:150px;overflow-y:auto">' + renderMd(stageRun.output.slice(0, 500)) + '</div>' : '') +
|
|
457
|
-
(stageStatus === 'waiting-human' ? '<button class="
|
|
457
|
+
(stageStatus === 'waiting-human' ? '<button class="btn-action btn-action-md" style="margin-top:6px" onclick="_continuePipeline(\'' + escHtml(id) + '\',\'' + escHtml(s.id) + '\',this)">Continue</button>' : '') +
|
|
458
458
|
'</div>';
|
|
459
459
|
});
|
|
460
460
|
|
|
@@ -726,7 +726,7 @@ function openCreatePipelineModal() {
|
|
|
726
726
|
'<label style="color:var(--text);font-size:var(--text-md)">Stages (JSON array)<textarea id="pl-stages" rows="10" style="' + inputStyle + ';resize:vertical;font-family:Consolas,monospace" placeholder=\'[{"id":"audit","type":"task","title":"Audit codebase","taskType":"explore"},{"id":"discuss","type":"meeting","title":"Discuss findings","dependsOn":["audit"],"participants":["all"]}]\'></textarea></label>' +
|
|
727
727
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:4px">' +
|
|
728
728
|
'<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
|
|
729
|
-
'<button onclick="_submitCreatePipeline()"
|
|
729
|
+
'<button onclick="_submitCreatePipeline()" class="btn-primary-lg">Create Pipeline</button>' +
|
|
730
730
|
'</div>' +
|
|
731
731
|
'</div>';
|
|
732
732
|
document.getElementById('modal').classList.add('open');
|
|
@@ -828,7 +828,7 @@ function openEditPipelineModal(id) {
|
|
|
828
828
|
'<label style="color:var(--text);font-size:var(--text-md)">Stages (JSON array)<textarea id="pl-stages" rows="10" style="' + inputStyle + ';resize:vertical;font-family:Consolas,monospace">' + escHtml(JSON.stringify(p.stages || [], null, 2)) + '</textarea></label>' +
|
|
829
829
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:4px">' +
|
|
830
830
|
'<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
|
|
831
|
-
'<button onclick="_submitEditPipeline()"
|
|
831
|
+
'<button onclick="_submitEditPipeline()" class="btn-primary-lg">Save</button>' +
|
|
832
832
|
'</div>' +
|
|
833
833
|
'</div>';
|
|
834
834
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
@@ -22,7 +22,7 @@ function openCreatePlanModal() {
|
|
|
22
22
|
'<div style="font-size:var(--text-base);color:var(--muted)">After creating, click Execute on the plan card to have an agent convert it into a PRD with work items.</div>' +
|
|
23
23
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:4px">' +
|
|
24
24
|
'<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
|
|
25
|
-
'<button onclick="_submitCreatePlan(event)"
|
|
25
|
+
'<button onclick="_submitCreatePlan(event)" class="btn-primary-lg">Create Plan</button>' +
|
|
26
26
|
'</div>' +
|
|
27
27
|
'</div>';
|
|
28
28
|
document.getElementById('modal').classList.add('open');
|
|
@@ -468,7 +468,7 @@ function planReexecuteModal(file, project) {
|
|
|
468
468
|
'<div style="font-size:var(--text-base);color:var(--muted)">Leave blank to re-run without steering. The agent will read the existing PRD and rewrite it.</div>' +
|
|
469
469
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:4px">' +
|
|
470
470
|
'<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
|
|
471
|
-
'<button onclick="planReexecuteSubmit(\'' + escapeHtml(file) + '\',\'' + escapeHtml(project || '') + '\')"
|
|
471
|
+
'<button onclick="planReexecuteSubmit(\'' + escapeHtml(file) + '\',\'' + escapeHtml(project || '') + '\')" class="btn-primary-lg">Re-execute</button>' +
|
|
472
472
|
'</div>' +
|
|
473
473
|
'</div>';
|
|
474
474
|
document.getElementById('modal').classList.add('open');
|
|
@@ -105,15 +105,15 @@ function renderPrd(prd, prog) {
|
|
|
105
105
|
let actions = '';
|
|
106
106
|
if (prdFile && !headerStale) {
|
|
107
107
|
if (effectiveStatus === 'awaiting-approval') {
|
|
108
|
-
actions = ' <button class="
|
|
108
|
+
actions = ' <button class="btn-action" style="margin-left:4px" onclick="planApprove(\'' + escHtml(prdFile) + '\',this)">Approve</button>';
|
|
109
109
|
} else if (effectiveStatus === 'completed') {
|
|
110
110
|
const hasVerifyWi = allWi.some(w => w.itemType === 'verify' && w.sourcePlan === prdFile);
|
|
111
|
-
actions = (hasVerifyWi ? '' : ' <button class="
|
|
111
|
+
actions = (hasVerifyWi ? '' : ' <button class="btn-action" style="margin-left:4px" onclick="triggerVerify(\'' + escHtml(prdFile) + '\',this)">Verify</button>') +
|
|
112
112
|
' <button class="pr-pager-btn" style="font-size:var(--text-xs);padding:1px 6px;margin-left:4px" onclick="planArchive(\'' + escHtml(prdFile) + '\',this)">Archive</button>';
|
|
113
113
|
} else if (effectiveStatus === 'dispatched') {
|
|
114
114
|
actions = ' <button class="pr-pager-btn" style="font-size:var(--text-xs);padding:1px 6px;color:var(--yellow);border-color:var(--yellow);margin-left:4px" onclick="planPause(\'' + escHtml(prdFile) + '\',this)">Pause</button>';
|
|
115
115
|
} else if (effectiveStatus === 'paused') {
|
|
116
|
-
actions = ' <button class="
|
|
116
|
+
actions = ' <button class="btn-action" style="margin-left:4px" onclick="planApprove(\'' + escHtml(prdFile) + '\',this)">Resume</button>' +
|
|
117
117
|
' <button class="pr-pager-btn" style="font-size:var(--text-xs);padding:1px 6px;margin-left:4px" onclick="planArchive(\'' + escHtml(prdFile) + '\',this)">Archive</button>';
|
|
118
118
|
}
|
|
119
119
|
}
|
|
@@ -823,7 +823,7 @@ async function prdItemEdit(source, itemId) {
|
|
|
823
823
|
'<label style="font-size:var(--text-base);color:var(--muted)">Description</label>' +
|
|
824
824
|
'<div style="display:flex;gap:6px">' +
|
|
825
825
|
'<button id="prd-desc-edit-btn" type="button" class="pr-pager-btn" aria-label="Edit description" style="font-size:var(--text-sm);padding:2px 10px" onclick="_togglePrdDescEdit()">Edit</button>' +
|
|
826
|
-
'<button id="prd-desc-save-btn" type="button" class="
|
|
826
|
+
'<button id="prd-desc-save-btn" type="button" class="btn-action btn-action-lg" aria-label="Save description" style="display:none" onclick="_savePrdDesc(\'' + escHtml(source) + '\',\'' + escHtml(itemId) + '\')">Save</button>' +
|
|
827
827
|
'<button id="prd-desc-cancel-btn" type="button" class="pr-pager-btn" aria-label="Cancel description edit" style="font-size:var(--text-sm);padding:2px 10px;display:none" onclick="_cancelPrdDescEdit()">Cancel</button>' +
|
|
828
828
|
'</div>' +
|
|
829
829
|
'</div>' +
|
|
@@ -204,7 +204,7 @@ function prRow(pr) {
|
|
|
204
204
|
'<td><span class="pr-badge ' + statusClass + '" title="' + escapeHtml(statusLabel) + '">' + escapeHtml(statusLabel) + '</span></td>' +
|
|
205
205
|
'<td>' + (observeBtn || '<span style="color:var(--muted);font-size:var(--text-base)">—</span>') + '</td>' +
|
|
206
206
|
'<td><span class="pr-date" title="' + escapeHtml(createdLabel) + '">' + escapeHtml(createdLabel) + '</span></td>' +
|
|
207
|
-
'<td><button class="
|
|
207
|
+
'<td><button class="btn-destructive" style="padding:1px 5px" data-pr-id="' + escapeHtml(String(prId)) + '" onclick="event.stopPropagation();unlinkPr(this.dataset.prId)" title="Remove from tracking">x</button></td>' +
|
|
208
208
|
'</tr>';
|
|
209
209
|
}
|
|
210
210
|
|
|
@@ -350,7 +350,7 @@ function openAddPrModal() {
|
|
|
350
350
|
'<div style="font-size:var(--text-base);color:var(--muted);margin-top:-4px;padding-left:24px">Off = context only (e.g. teammate\'s PR). On = agents actively monitor and fix issues.</div>' +
|
|
351
351
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px">' +
|
|
352
352
|
'<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
|
|
353
|
-
'<button onclick="_submitLinkPr(event)"
|
|
353
|
+
'<button onclick="_submitLinkPr(event)" class="btn-primary-lg">Link PR</button>' +
|
|
354
354
|
'</div>' +
|
|
355
355
|
'</div>';
|
|
356
356
|
document.getElementById('modal').classList.add('open');
|
|
@@ -352,10 +352,10 @@ function renderSchedules(schedules, opts) {
|
|
|
352
352
|
'<td>' + enabledBadge + '</td>' +
|
|
353
353
|
'<td><span class="pr-date">' + escHtml(lastRun) + '</span></td>' +
|
|
354
354
|
'<td style="white-space:nowrap">' +
|
|
355
|
-
'<button class="
|
|
355
|
+
'<button class="btn-action" style="margin-right:4px" onclick="event.stopPropagation();runScheduleNow(\'' + escHtml(s.id) + '\',this)" title="Run now">Run now</button>' +
|
|
356
356
|
'<button class="pr-pager-btn' + (_schedToggleInFlight.has(s.id) ? ' disabled' : '') + '" style="font-size:var(--text-xs);padding:1px 6px;color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + ';border-color:' + (s.enabled ? 'var(--yellow)' : 'var(--green)') + ';margin-right:4px" onclick="event.stopPropagation();toggleScheduleEnabled(\'' + escHtml(s.id) + '\',' + !s.enabled + ')" title="' + (s.enabled ? 'Disable' : 'Enable') + '">' + (s.enabled ? '⏸' : '▶') + '</button>' +
|
|
357
357
|
'<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:1px 6px;color:var(--blue);border-color:var(--blue);margin-right:4px" onclick="event.stopPropagation();openEditScheduleModal(\'' + escHtml(s.id) + '\')" title="Edit">✎</button>' +
|
|
358
|
-
'<button class="
|
|
358
|
+
'<button class="btn-destructive" onclick="event.stopPropagation();deleteSchedule(\'' + escHtml(s.id) + '\')" title="Delete">✕</button>' +
|
|
359
359
|
'</td>' +
|
|
360
360
|
'</tr>';
|
|
361
361
|
}
|
|
@@ -515,7 +515,7 @@ function _scheduleFormHtml(sched, isEdit) {
|
|
|
515
515
|
'</label>' +
|
|
516
516
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px">' +
|
|
517
517
|
'<button onclick="closeModal()" class="pr-pager-btn" style="padding:6px 16px;font-size:var(--text-md)">Cancel</button>' +
|
|
518
|
-
'<button onclick="submitSchedule(' + isEdit + ',event)"
|
|
518
|
+
'<button onclick="submitSchedule(' + isEdit + ',event)" class="btn-primary-lg">' + (isEdit ? 'Save' : 'Create') + '</button>' +
|
|
519
519
|
'</div>' +
|
|
520
520
|
'</div>';
|
|
521
521
|
}
|
|
@@ -216,11 +216,11 @@ function renderWatches(watchesData, opts) {
|
|
|
216
216
|
if (w.status === 'active') {
|
|
217
217
|
html += '<button class="pr-pager-btn' + pauseBusyCls + '" style="font-size:var(--text-xs);padding:1px 6px;color:var(--yellow);border-color:var(--yellow);margin-right:4px" onclick="event.stopPropagation();toggleWatchPause(\'' + escHtml(w.id) + '\',true)" title="Pause">⏸</button>';
|
|
218
218
|
} else if (w.status === 'paused') {
|
|
219
|
-
html += '<button class="
|
|
219
|
+
html += '<button class="btn-action' + pauseBusyCls + '" style="margin-right:4px" onclick="event.stopPropagation();toggleWatchPause(\'' + escHtml(w.id) + '\',false)" title="Resume">▶</button>';
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
// Delete button
|
|
223
|
-
html += '<button class="
|
|
223
|
+
html += '<button class="btn-destructive" onclick="event.stopPropagation();deleteWatch(\'' + escHtml(w.id) + '\')" title="Delete">✕</button>';
|
|
224
224
|
html += '</td></tr>';
|
|
225
225
|
}
|
|
226
226
|
|
|
@@ -548,7 +548,7 @@ function _addWatchStepRow(initial) {
|
|
|
548
548
|
row.innerHTML =
|
|
549
549
|
'<div style="display:flex;align-items:center;justify-content:space-between;gap:8px">' +
|
|
550
550
|
'<select id="watch-step-' + idx + '-type" style="' + inputStyle + ';margin-top:0;flex:1" onchange="_updateWatchStepParamsHint(' + idx + ')">' + actionOpts + '</select>' +
|
|
551
|
-
'<button type="button" class="
|
|
551
|
+
'<button type="button" class="btn-destructive btn-destructive-lg" onclick="_removeWatchStepRow(' + idx + ')">Remove</button>' +
|
|
552
552
|
'</div>' +
|
|
553
553
|
'<label style="color:var(--text);font-size:var(--text-base)">Params (JSON) <span style="font-size:var(--text-sm);color:var(--muted)" id="watch-step-' + idx + '-params-hint"></span>' +
|
|
554
554
|
'<textarea id="watch-step-' + idx + '-params" rows="3" placeholder=\'{"title": "Re-check {{target}} after merge"}\' style="' + inputStyle + ';font-family:monospace"></textarea>' +
|
|
@@ -612,7 +612,7 @@ function _addRequireRow(initial) {
|
|
|
612
612
|
'<label style="color:var(--text);font-size:var(--text-base);margin:0">Target<input id="watch-require-' + idx + '-target" placeholder="e.g. PR-123, pipeline-X" style="' + inputStyle + '"></label>' +
|
|
613
613
|
'<label style="color:var(--text);font-size:var(--text-base);margin:0">Target Type<select id="watch-require-' + idx + '-target-type" style="' + inputStyle + '" onchange="_updateRequireConditionOptions(' + idx + ')">' + ttOpts + '</select></label>' +
|
|
614
614
|
'<label style="color:var(--text);font-size:var(--text-base);margin:0">Condition<select id="watch-require-' + idx + '-condition" style="' + inputStyle + '"></select></label>' +
|
|
615
|
-
'<button type="button" class="
|
|
615
|
+
'<button type="button" class="btn-destructive btn-destructive-lg" style="padding:6px 8px" onclick="_removeRequireRow(' + idx + ')">Remove</button>';
|
|
616
616
|
container.appendChild(row);
|
|
617
617
|
if (initTarget) document.getElementById('watch-require-' + idx + '-target').value = initTarget;
|
|
618
618
|
if (initTT) document.getElementById('watch-require-' + idx + '-target-type').value = initTT;
|
|
@@ -655,7 +655,7 @@ function _updateWatchConditionOptions() {
|
|
|
655
655
|
|
|
656
656
|
function _renderCreateWatchModal() {
|
|
657
657
|
document.getElementById('modal-title').innerHTML = 'Create Watch' +
|
|
658
|
-
' <button class="
|
|
658
|
+
' <button class="btn-action btn-action-lg" style="padding:2px 12px;margin-left:8px" onclick="submitWatch()">Create</button>';
|
|
659
659
|
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() by _watchFormHtml() (fields: target/action registries, project names, agent ids/names)
|
|
660
660
|
document.getElementById('modal-body').innerHTML = _watchFormHtml();
|
|
661
661
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
@@ -246,9 +246,9 @@ function wiRow(item) {
|
|
|
246
246
|
'<td style="white-space:nowrap">' +
|
|
247
247
|
((item.status === 'pending' || item.status === 'failed') ? '<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:1px 6px;color:var(--blue);border-color:var(--blue);margin-right:4px" onclick="event.stopPropagation();editWorkItem(\'' + escapeHtml(item.id) + '\',\'' + escapeHtml(item._source || '') + '\')" title="Edit work item">✎</button>' : '') +
|
|
248
248
|
((item.status === 'done' || item.status === 'failed') ? '<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:1px 6px;color:var(--muted);border-color:var(--border);margin-right:4px" onclick="event.stopPropagation();archiveWorkItem(\'' + escapeHtml(item.id) + '\',\'' + escapeHtml(item._source || '') + '\')" title="Archive work item">📦</button>' : '') +
|
|
249
|
-
((item.status === 'done' || item.status === 'failed') && !item._humanFeedback ? '<button class="
|
|
249
|
+
((item.status === 'done' || item.status === 'failed') && !item._humanFeedback ? '<button class="btn-action" style="margin-right:4px" onclick="event.stopPropagation();feedbackWorkItem(\'' + escapeHtml(item.id) + '\',\'' + escapeHtml(item._source || '') + '\')" title="Give feedback">👍👎</button>' : (item._humanFeedback ? '<span style="font-size:var(--text-xs)" title="Feedback given">' + (item._humanFeedback.rating === 'up' ? '👍' : '👎') + '</span> ' : '')) +
|
|
250
250
|
((item.status === 'pending' || item.status === 'dispatched' || item.status === 'queued' || item.status === 'failed') ? '<button class="pr-pager-btn" style="font-size:var(--text-xs);padding:1px 6px;color:var(--orange);border-color:var(--orange);margin-right:4px" onclick="event.stopPropagation();cancelWorkItem(\'' + escapeHtml(item.id) + '\',\'' + escapeHtml(item._source || '') + '\')" title="Cancel work item and kill agent">🚫</button>' : '') +
|
|
251
|
-
'<button class="
|
|
251
|
+
'<button class="btn-destructive" onclick="event.stopPropagation();deleteWorkItem(\'' + escapeHtml(item.id) + '\',\'' + escapeHtml(item._source || '') + '\')" title="Delete work item and kill agent">✕</button>' +
|
|
252
252
|
'</td>' +
|
|
253
253
|
'</tr>';
|
|
254
254
|
}
|
|
@@ -380,7 +380,7 @@ async function editWorkItem(id, source) {
|
|
|
380
380
|
'</label>' +
|
|
381
381
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px">' +
|
|
382
382
|
'<button onclick="closeModal()" class="pr-pager-btn" style="padding:6px 16px;font-size:var(--text-md)">Cancel</button>' +
|
|
383
|
-
'<button onclick="submitWorkItemEdit(\'' + escapeHtml(id) + '\',\'' + escapeHtml(source || '') + '\',event)"
|
|
383
|
+
'<button onclick="submitWorkItemEdit(\'' + escapeHtml(id) + '\',\'' + escapeHtml(source || '') + '\',event)" class="btn-primary-lg">Save</button>' +
|
|
384
384
|
'</div>' +
|
|
385
385
|
'</div>';
|
|
386
386
|
document.getElementById('modal').classList.add('open');
|
|
@@ -601,7 +601,7 @@ function openCreateWorkItemModal() {
|
|
|
601
601
|
'<label id="wi-new-skippr-row" style="color:var(--text);font-size:var(--text-md);display:flex;gap:8px;align-items:center;cursor:pointer"><input type="checkbox" id="wi-new-skippr"> Skip PR creation (push branch only)</label>' +
|
|
602
602
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:4px">' +
|
|
603
603
|
'<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
|
|
604
|
-
'<button onclick="_submitCreateWorkItem(event)"
|
|
604
|
+
'<button onclick="_submitCreateWorkItem(event)" class="btn-primary-lg">Create</button>' +
|
|
605
605
|
'</div>' +
|
|
606
606
|
'</div>';
|
|
607
607
|
document.getElementById('modal').classList.add('open');
|
package/dashboard/js/settings.js
CHANGED
|
@@ -488,8 +488,7 @@ async function openSettings() {
|
|
|
488
488
|
if (!document.getElementById('modal-settings-reset')) {
|
|
489
489
|
const resetBtn = document.createElement('button');
|
|
490
490
|
resetBtn.id = 'modal-settings-reset';
|
|
491
|
-
resetBtn.className = 'modal-copy';
|
|
492
|
-
resetBtn.style.cssText = 'color:var(--red);border-color:var(--red)';
|
|
491
|
+
resetBtn.className = 'modal-copy is-danger';
|
|
493
492
|
resetBtn.textContent = 'Reset';
|
|
494
493
|
resetBtn.onclick = resetSettingsToDefaults;
|
|
495
494
|
actions.insertBefore(resetBtn, actions.lastElementChild);
|
|
@@ -497,8 +496,7 @@ async function openSettings() {
|
|
|
497
496
|
if (!document.getElementById('modal-settings-save')) {
|
|
498
497
|
const saveBtn = document.createElement('button');
|
|
499
498
|
saveBtn.id = 'modal-settings-save';
|
|
500
|
-
saveBtn.className = 'modal-copy';
|
|
501
|
-
saveBtn.style.cssText = 'color:var(--green);border-color:var(--green)';
|
|
499
|
+
saveBtn.className = 'modal-copy is-success';
|
|
502
500
|
saveBtn.innerHTML = '<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"/></svg> Save';
|
|
503
501
|
saveBtn.onclick = saveSettings;
|
|
504
502
|
actions.insertBefore(saveBtn, actions.lastElementChild);
|
package/dashboard/layout.html
CHANGED
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
<h3 id="modal-title">—</h3>
|
|
123
123
|
<div class="modal-header-actions">
|
|
124
124
|
<button class="modal-copy" id="modal-edit-btn" onclick="modalToggleEdit()" title="Edit" style="display:none"><svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M11.013 1.427a1.75 1.75 0 012.474 0l1.086 1.086a1.75 1.75 0 010 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 01-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61zM12.9 2.97L4.288 11.58l-.537 1.878 1.878-.537L14.242 4.31 12.9 2.97z"/></svg> Edit</button>
|
|
125
|
-
<button class="modal-copy" id="modal-save-btn" onclick="modalSaveEdit()" title="Save" style="display:none
|
|
125
|
+
<button class="modal-copy is-success" id="modal-save-btn" onclick="modalSaveEdit()" title="Save" style="display:none"><svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"/></svg> Save</button>
|
|
126
126
|
<button class="modal-copy" id="modal-cancel-edit-btn" onclick="modalCancelEdit()" title="Cancel edit" style="display:none">Cancel</button>
|
|
127
127
|
<button class="modal-copy pin-btn" id="modal-pin-btn" onclick="toggleModalPin()" title="Pin to top" style="display:none">Pin</button>
|
|
128
128
|
<button class="modal-copy" id="modal-copy-btn" onclick="copyModalContent()" title="Copy to clipboard"><svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 010 1.5h-1.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-1.5a.75.75 0 011.5 0v1.5A1.75 1.75 0 019.25 16h-7.5A1.75 1.75 0 010 14.25z"/><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0114.25 11h-7.5A1.75 1.75 0 015 9.25zm1.75-.25a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25z"/></svg> Copy</button>
|
package/dashboard/pages/qa.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
</ul>
|
|
16
16
|
</div>
|
|
17
17
|
<div style="display:flex;gap:var(--space-3);align-items:center;margin-top:var(--space-2)">
|
|
18
|
-
<button type="button" id="qa-cc-pitch-open" class="
|
|
18
|
+
<button type="button" id="qa-cc-pitch-open" class="btn-primary-solid" onclick="qaFocusCommandCenter()">Open Command Center →</button>
|
|
19
19
|
<a href="/state/docs/qa-runbook-lifecycle.md" target="_blank" rel="noopener" class="qa-cc-pitch-docs" style="color:var(--blue);font-size:var(--text-base)">Docs →</a>
|
|
20
20
|
</div>
|
|
21
21
|
</div>
|
package/dashboard/styles.css
CHANGED
|
@@ -488,6 +488,170 @@
|
|
|
488
488
|
.btn-add.disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
|
|
489
489
|
.btn-add.btn-add-lg { font-size: var(--text-sm); padding: 2px 8px; }
|
|
490
490
|
|
|
491
|
+
/* W-mq57qetc0009ed48 — green-bordered action button family WITHOUT a
|
|
492
|
+
leading "+" glyph (Approve / Verify / Resume / Save / Run Now /
|
|
493
|
+
Continue / Create / New Plan / Trigger Verify / etc). Sibling to
|
|
494
|
+
.btn-add — same green/surface2 visual identity, but semantically a
|
|
495
|
+
verb-action rather than a creator. Three padding+size variants cover
|
|
496
|
+
every callsite that was previously inline `style="font-size:…;
|
|
497
|
+
padding:…;color:var(--green);border-color:var(--green)"` on top of
|
|
498
|
+
.pr-pager-btn:
|
|
499
|
+
.btn-action — compact text-xs / 1px 6px (inline PRD
|
|
500
|
+
chips, work-item feedback)
|
|
501
|
+
.btn-action.btn-action-md — text-xs / 2px 8px (meetings, pipelines,
|
|
502
|
+
watches resume, schedule run-now chip)
|
|
503
|
+
.btn-action.btn-action-lg — text-sm / 2px 10px (modal Save, watch
|
|
504
|
+
Create, charter Save, schedule modal Run
|
|
505
|
+
now)
|
|
506
|
+
Positional/layout-only inline styles (e.g. margin-left:8px, margin-
|
|
507
|
+
right:4px, margin-top:6px) stay at the callsite. Do NOT re-create the
|
|
508
|
+
green inline blob — extend this primitive instead. */
|
|
509
|
+
.btn-action {
|
|
510
|
+
background: var(--surface2);
|
|
511
|
+
border: 1px solid var(--green);
|
|
512
|
+
color: var(--green);
|
|
513
|
+
font-size: var(--text-xs);
|
|
514
|
+
padding: 1px 6px;
|
|
515
|
+
border-radius: var(--radius-sm);
|
|
516
|
+
cursor: pointer;
|
|
517
|
+
transition: background var(--transition-base);
|
|
518
|
+
}
|
|
519
|
+
.btn-action:hover { background: rgba(63,185,80,0.1); }
|
|
520
|
+
.btn-action:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }
|
|
521
|
+
.btn-action:disabled,
|
|
522
|
+
.btn-action.disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
|
|
523
|
+
.btn-action.btn-action-md { padding: 2px 8px; }
|
|
524
|
+
.btn-action.btn-action-lg { font-size: var(--text-sm); padding: 2px 10px; }
|
|
525
|
+
|
|
526
|
+
/* W-mq57qetc0009ed48 — red-bordered destructive button family (Delete /
|
|
527
|
+
Abort / Remove / Unpin / unlink-x). Visual sibling to .btn-action —
|
|
528
|
+
same surface2 background, same padding scale, same hover-tint pattern,
|
|
529
|
+
just swapped to var(--red). Three size variants:
|
|
530
|
+
.btn-destructive — compact text-xs / 1px 6px (work-item
|
|
531
|
+
delete, watch delete, schedule delete,
|
|
532
|
+
unpin, unlink-x in PR row)
|
|
533
|
+
.btn-destructive.btn-destructive-md — text-xs / 2px 8px (pipelines
|
|
534
|
+
Abort + Delete)
|
|
535
|
+
.btn-destructive.btn-destructive-lg — text-sm / 2px 10px (watch
|
|
536
|
+
builder Remove step / Remove
|
|
537
|
+
requirement)
|
|
538
|
+
Positional / outlier inline overrides (padding:1px 5px on the PR
|
|
539
|
+
unlink-x chip, padding:6px 8px on the requirement-row Remove) stay
|
|
540
|
+
at the callsite. Do NOT re-create the red inline blob — extend this
|
|
541
|
+
primitive instead. */
|
|
542
|
+
.btn-destructive {
|
|
543
|
+
background: var(--surface2);
|
|
544
|
+
border: 1px solid var(--red);
|
|
545
|
+
color: var(--red);
|
|
546
|
+
font-size: var(--text-xs);
|
|
547
|
+
padding: 1px 6px;
|
|
548
|
+
border-radius: var(--radius-sm);
|
|
549
|
+
cursor: pointer;
|
|
550
|
+
transition: background var(--transition-base);
|
|
551
|
+
}
|
|
552
|
+
.btn-destructive:hover { background: rgba(248,81,73,0.1); }
|
|
553
|
+
.btn-destructive:focus-visible { outline: 2px solid var(--red); outline-offset: 1px; }
|
|
554
|
+
.btn-destructive:disabled,
|
|
555
|
+
.btn-destructive.disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
|
|
556
|
+
.btn-destructive.btn-destructive-md { padding: 2px 8px; }
|
|
557
|
+
.btn-destructive.btn-destructive-lg { font-size: var(--text-sm); padding: 2px 10px; }
|
|
558
|
+
|
|
559
|
+
/* W-mq57qetc0009ed48 — solid-blue primary CTA. Single source of truth
|
|
560
|
+
for the filled-blue rectangle that gates submission flows (QA "Start
|
|
561
|
+
QA Session", "+ New runbook", "Save" in the QA runbook editor, FRE
|
|
562
|
+
"+ Add Project"). Replaces the standalone .qa-btn-primary (now
|
|
563
|
+
removed — all callsites migrated) and the ad-hoc
|
|
564
|
+
`background:var(--blue);color:#fff;border:none;…` style blob in
|
|
565
|
+
fre.js. */
|
|
566
|
+
.btn-primary-solid {
|
|
567
|
+
background: var(--blue);
|
|
568
|
+
color: #fff;
|
|
569
|
+
border: none;
|
|
570
|
+
border-radius: var(--radius-sm);
|
|
571
|
+
padding: var(--space-3) var(--space-7);
|
|
572
|
+
font-size: var(--text-md);
|
|
573
|
+
font-weight: 600;
|
|
574
|
+
cursor: pointer;
|
|
575
|
+
transition: opacity var(--transition-base);
|
|
576
|
+
}
|
|
577
|
+
.btn-primary-solid:hover { opacity: 0.9; }
|
|
578
|
+
.btn-primary-solid:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
|
|
579
|
+
.btn-primary-solid:disabled,
|
|
580
|
+
.btn-primary-solid.disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
|
|
581
|
+
|
|
582
|
+
/* W-mq57qetc0009ed48 — solid-blue modal CTA. Lighter-weight sibling to
|
|
583
|
+
.btn-primary-solid: same filled-blue rectangle, padding:6px 16px, but
|
|
584
|
+
without the font-weight:600 used by qa/fre primary CTAs. Used for the
|
|
585
|
+
"Save" / "Create Plan" / "Re-execute" / "Save Note" buttons inside
|
|
586
|
+
dashboard modals (render-inbox, render-kb, render-plans) and the
|
|
587
|
+
"Retry" button in the agent-detail error state. Replaces the ad-hoc
|
|
588
|
+
`background:var(--blue);color:#fff;border:none;border-radius:sm;
|
|
589
|
+
padding:6px 16px` blob that was duplicated across five callsites. */
|
|
590
|
+
.btn-primary-lg {
|
|
591
|
+
background: var(--blue);
|
|
592
|
+
color: #fff;
|
|
593
|
+
border: none;
|
|
594
|
+
border-radius: var(--radius-sm);
|
|
595
|
+
padding: 6px 16px;
|
|
596
|
+
font-size: var(--text-md);
|
|
597
|
+
cursor: pointer;
|
|
598
|
+
transition: opacity var(--transition-base);
|
|
599
|
+
}
|
|
600
|
+
.btn-primary-lg:hover { opacity: 0.9; }
|
|
601
|
+
.btn-primary-lg:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
|
|
602
|
+
.btn-primary-lg:disabled,
|
|
603
|
+
.btn-primary-lg.disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
|
|
604
|
+
|
|
605
|
+
/* W-mq57qetc0009ed48 — muted ghost button. Transparent background,
|
|
606
|
+
bordered, muted text. Used for secondary actions next to a primary
|
|
607
|
+
CTA (QA Cancel / Edit / Dismiss / "− remove artifact row" / "+ Add
|
|
608
|
+
artifact"). Replaces the standalone .qa-btn-ghost (now removed —
|
|
609
|
+
all callsites migrated). */
|
|
610
|
+
.btn-ghost {
|
|
611
|
+
background: transparent;
|
|
612
|
+
color: var(--muted);
|
|
613
|
+
border: 1px solid var(--border);
|
|
614
|
+
border-radius: var(--radius-sm);
|
|
615
|
+
padding: var(--space-2) var(--space-5);
|
|
616
|
+
font-size: var(--text-base);
|
|
617
|
+
cursor: pointer;
|
|
618
|
+
transition: color var(--transition-base), background var(--transition-base);
|
|
619
|
+
}
|
|
620
|
+
.btn-ghost:hover { color: var(--text); background: var(--surface); }
|
|
621
|
+
.btn-ghost:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; }
|
|
622
|
+
.btn-ghost:disabled,
|
|
623
|
+
.btn-ghost.disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
|
|
624
|
+
|
|
625
|
+
/* W-mq57qetc0009ed48 — dashed empty-state chip ("+ Add Project" in
|
|
626
|
+
the projects empty-state). Same chip footprint as the project chips
|
|
627
|
+
in render-other.js, but with a dashed border + green text to read as
|
|
628
|
+
a placeholder/call-to-action. */
|
|
629
|
+
.empty-state-add {
|
|
630
|
+
display: inline-block;
|
|
631
|
+
background: var(--surface2);
|
|
632
|
+
border: 1px dashed var(--border);
|
|
633
|
+
color: var(--green);
|
|
634
|
+
font-weight: 500;
|
|
635
|
+
padding: 3px 10px;
|
|
636
|
+
border-radius: var(--radius-sm);
|
|
637
|
+
cursor: pointer;
|
|
638
|
+
transition: background var(--transition-base);
|
|
639
|
+
}
|
|
640
|
+
.empty-state-add:hover { background: rgba(63,185,80,0.1); }
|
|
641
|
+
.empty-state-add:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }
|
|
642
|
+
.empty-state-add:disabled,
|
|
643
|
+
.empty-state-add.disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
|
|
644
|
+
/* Muted variant used when the chip sits next to existing project chips
|
|
645
|
+
(so it reads as a secondary "add another" affordance rather than the
|
|
646
|
+
primary empty-state CTA). */
|
|
647
|
+
.empty-state-add.empty-state-add-muted { color: var(--muted); }
|
|
648
|
+
.empty-state-add.empty-state-add-muted:hover { background: var(--surface); }
|
|
649
|
+
/* Blue variant used for the "Scan" affordance that sits next to "+ Add
|
|
650
|
+
Project" in the projects empty-state — same dashed chip, but text-sm and
|
|
651
|
+
blue text instead of green. */
|
|
652
|
+
.empty-state-add.empty-state-add-blue { color: var(--blue); font-size: var(--text-sm); }
|
|
653
|
+
.empty-state-add.empty-state-add-blue:hover { background: rgba(88,166,255,0.1); }
|
|
654
|
+
|
|
491
655
|
/* Badge — unified badge/pill base */
|
|
492
656
|
.badge {
|
|
493
657
|
font-size: var(--text-sm); font-weight: 600;
|
|
@@ -796,6 +960,8 @@
|
|
|
796
960
|
.modal-copy { background: var(--surface2); border: 1px solid var(--border); color: var(--muted); font-size: var(--text-base); cursor: pointer; padding: var(--space-2) var(--space-5); border-radius: var(--radius-sm); display: flex; align-items: center; gap: var(--space-2); transition: all var(--transition-base); }
|
|
797
961
|
.modal-copy:hover { color: var(--text); border-color: var(--text); }
|
|
798
962
|
.modal-copy.copied { color: var(--green); border-color: var(--green); }
|
|
963
|
+
.modal-copy.is-success { color: var(--green); border-color: var(--green); }
|
|
964
|
+
.modal-copy.is-danger { color: var(--red); border-color: var(--red); }
|
|
799
965
|
.modal-close { background: none; border: none; color: var(--muted); font-size: var(--text-2xl); cursor: pointer; padding: var(--space-2) var(--space-4); }
|
|
800
966
|
.modal-close:hover { color: var(--text); }
|
|
801
967
|
|
|
@@ -1019,20 +1185,10 @@
|
|
|
1019
1185
|
.qa-add-artifact-btn { align-self: flex-start; margin-top: var(--space-2); }
|
|
1020
1186
|
.qa-form-actions { flex-direction: row; align-items: center; gap: var(--space-4); }
|
|
1021
1187
|
.qa-form-msg { font-size: var(--text-base); margin-left: var(--space-3); }
|
|
1022
|
-
.qa-btn-primary
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
}
|
|
1027
|
-
.qa-btn-primary:hover { opacity: 0.9; }
|
|
1028
|
-
.qa-btn-primary:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
1029
|
-
.qa-btn-ghost {
|
|
1030
|
-
background: transparent; color: var(--muted);
|
|
1031
|
-
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
|
1032
|
-
padding: var(--space-2) var(--space-5); font-size: var(--text-base);
|
|
1033
|
-
cursor: pointer;
|
|
1034
|
-
}
|
|
1035
|
-
.qa-btn-ghost:hover { color: var(--text); background: var(--surface); }
|
|
1188
|
+
/* .qa-btn-primary and .qa-btn-ghost were removed in W-mq57qetc0009ed48
|
|
1189
|
+
(PR #80 follow-up). All callsites migrated to the shared .btn-primary-solid
|
|
1190
|
+
and .btn-ghost primitives defined near .btn-add. .qa-btn-danger is kept
|
|
1191
|
+
as a color-only modifier composable with .btn-ghost. */
|
|
1036
1192
|
|
|
1037
1193
|
.qa-runbooks-list { display: flex; flex-direction: column; gap: var(--space-3); }
|
|
1038
1194
|
.qa-runbook-row {
|
|
@@ -86,6 +86,7 @@ Do **not** invent, regenerate, or share the nonce across dispatches — each spa
|
|
|
86
86
|
| `followups` | array | Optional. PR-comment follow-up work items the agent dispatched via `POST /api/work-items` with `meta.pr_followup` set. Each entry: `{wi_id, title, reason, parent_comment_id}`. See [PR-comment follow-ups](#pr-comment-follow-ups). |
|
|
87
87
|
| `meta.review` | object | Optional, review tasks only. Records project-local review-skill outcome — see [Review skill outcomes](#review-skill-outcomes). Aliased by the generalized `meta.skill` (W-mq1cczi90006b21f). |
|
|
88
88
|
| `meta.skill` | object | Optional. Records project-local skill outcome for ANY playbook type that surfaces a `## Project skills` block (implement / fix / plan / review / etc.). See [Project skill outcomes](#project-skill-outcomes). |
|
|
89
|
+
| `meta.descriptionAudit` | object | Optional, `fix` / `implement` dispatches that push commits. Records the PR description audit + screenshot-refresh outcome — see [PR description audit](#pr-description-audit). |
|
|
89
90
|
|
|
90
91
|
## Project skill outcomes
|
|
91
92
|
|
|
@@ -150,6 +151,44 @@ All `meta.review` fields are optional and backward-compatible — older agents t
|
|
|
150
151
|
|
|
151
152
|
Dispatchers can suppress the block entirely by setting `meta.skipProjectReviewSkills: true` on the review work item — the playbook then renders identically to the pre-W-mq16xtdx 8-step contract. Use this for meta-reviews of the review skill itself; the engine still accepts `meta.review.skillSkipped` in the report regardless. (W-mq1cczi90006b21f generalized this to `meta.skipProjectSkills`, which suppresses the block on every dispatch type — both flags are honored.)
|
|
152
153
|
|
|
154
|
+
## PR description audit
|
|
155
|
+
|
|
156
|
+
W-mq5l3f2u000i9045. The `fix` and `implement` playbooks inline a `## PR description audit` section that the agent runs **after** pushing commits to the PR's source branch and **before** marking the work item done. The audit re-reads the PR description, compares it against the diff of the newly-pushed commits, and patches stale sections (feature lists, config tables, file/line counts, ASCII diagrams, embedded screenshots) so reviewers don't end up reading outdated prose.
|
|
157
|
+
|
|
158
|
+
The audit is best-effort:
|
|
159
|
+
|
|
160
|
+
- Screenshot recapture requires a runnable dev server (detected via `package.json` scripts named `dev`, `start`, or `serve`) and Playwright MCP. When either is missing or fails, the agent skips the recapture without failing the work item.
|
|
161
|
+
- GitHub does not expose a documented REST endpoint for PR-comment image uploads; the agent skips the recapture on GitHub with `result: "screenshots-refreshed-skipped (gh-attachment-unavailable)"` instead of failing.
|
|
162
|
+
- Raw PNGs are saved under `agents/<id>/screenshots/` only. **Screenshots NEVER land in the repo** — adding any code path that `git add`s a PNG is a release-blocker bug.
|
|
163
|
+
- The audit never introduces screenshots to a PR that didn't already have any (refresh only), never modifies the PR title, never toggles draft state, and never posts a separate PR comment narrating the description change.
|
|
164
|
+
|
|
165
|
+
Record the outcome under `meta.descriptionAudit`. All fields are optional and backward-compatible — older agents that never set them stay valid.
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"status": "success",
|
|
170
|
+
"meta": {
|
|
171
|
+
"descriptionAudit": {
|
|
172
|
+
"ran": true,
|
|
173
|
+
"result": "description-patched",
|
|
174
|
+
"oldScreenshots": [],
|
|
175
|
+
"newScreenshots": [],
|
|
176
|
+
"patchedSections": ["feature-bullets", "config-table"]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
| Field | Type | Notes |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| `meta.descriptionAudit.ran` | boolean | `true` if the agent executed the audit (whether or not it edited the description). `false` when the audit was suppressed (no commits pushed, no-op completion, `meta.skipDescriptionAudit` set, or the playbook skipped the audit for any reason). Omit `meta.descriptionAudit` entirely when the audit was not even considered. |
|
|
185
|
+
| `meta.descriptionAudit.result` | string | One of: `"no-changes-needed"`, `"description-patched"`, `"screenshots-refreshed"`, `"description-patched+screenshots-refreshed"`, or `"skipped:<reason>"` (e.g. `"skipped:meta-flag"`, `"skipped:no-commits"`, `"skipped:noop"`). The `"screenshots-refreshed-skipped (gh-attachment-unavailable)"` shape is also valid when GitHub's attachment limitation forces a partial-skip. |
|
|
186
|
+
| `meta.descriptionAudit.oldScreenshots` | string[] | URLs of image refs the agent replaced. Omit or `[]` when no screenshots changed. |
|
|
187
|
+
| `meta.descriptionAudit.newScreenshots` | string[] | URLs of newly uploaded image attachments. Omit or `[]` when no screenshots changed. |
|
|
188
|
+
| `meta.descriptionAudit.patchedSections` | string[] | Human-readable identifiers of the description sections that changed (e.g. `"feature-bullets"`, `"config-table"`, `"file-list"`, `"line-counts"`, `"ascii-diagram"`, `"screenshots"`). Omit or `[]` when no sections changed. |
|
|
189
|
+
|
|
190
|
+
Dispatchers can suppress the audit entirely by setting `meta.skipDescriptionAudit: true` on the work item — the playbook then renders an explicit "audit suppressed" notice instead of the audit steps. Use this for skill-meta updates, doc-only fixes whose PR description text won't be affected by the diff, and follow-up dispatches that explicitly own the description themselves.
|
|
191
|
+
|
|
153
192
|
## `failure_class` enum
|
|
154
193
|
|
|
155
194
|
Defined in `engine/shared.js` as `FAILURE_CLASS`. Use the canonical hyphenated string.
|
package/engine/playbook.js
CHANGED
|
@@ -307,6 +307,14 @@ const PLAYBOOK_OPTIONAL_VARS = new Set([
|
|
|
307
307
|
// projects legitimately resolve it to ''.
|
|
308
308
|
'project_review_skills_block',
|
|
309
309
|
'skip_project_review_skills',
|
|
310
|
+
// W-mq5l3f2u000i9045 — opt-out flag for the PR description audit + screenshot
|
|
311
|
+
// refresh fragment inlined into fix.md / implement.md. Truthy (set when
|
|
312
|
+
// item.meta.skipDescriptionAudit is true) renders the "audit suppressed"
|
|
313
|
+
// notice; falsy/empty leaves the notice block out and the agent runs the
|
|
314
|
+
// standard audit. Optional because (a) every other playbook ignores the var
|
|
315
|
+
// and (b) the default state is the empty string, which the playbook's
|
|
316
|
+
// conditional block treats as "not skipped".
|
|
317
|
+
'skip_description_audit',
|
|
310
318
|
// P-e6b3c2d8 — QA Session template vars. session_id / target_kind /
|
|
311
319
|
// flows_raw / managed_spawn_name are required (declared in
|
|
312
320
|
// PLAYBOOK_REQUIRED_VARS['qa-session-setup']); these target_* sub-fields
|
|
@@ -435,7 +443,12 @@ function isSafePlaybookType(playbookType) {
|
|
|
435
443
|
!name.includes('..') &&
|
|
436
444
|
!/[\\/]/.test(name) &&
|
|
437
445
|
!path.isAbsolute(name) &&
|
|
438
|
-
!/^[a-zA-Z]:/.test(name)
|
|
446
|
+
!/^[a-zA-Z]:/.test(name) &&
|
|
447
|
+
// W-mq5l3f2u000i9045 — files named `_*.md` are shared fragments inlined
|
|
448
|
+
// into dispatching playbooks (e.g. `_pr-description-audit.md`). They are
|
|
449
|
+
// not standalone playbooks; reject `_…` types so a dispatch can never
|
|
450
|
+
// resolve to a fragment file.
|
|
451
|
+
!name.startsWith('_');
|
|
439
452
|
}
|
|
440
453
|
|
|
441
454
|
function resolvePlaybookPath(projectName, playbookType) {
|
|
@@ -1171,7 +1184,14 @@ function buildPrDispatch(agentId, config, project, pr, type, extraVars, taskLabe
|
|
|
1171
1184
|
// (skipProjectReviewSkills is the PR-82 alias). Default OFF: review/fix
|
|
1172
1185
|
// dispatches surface the project-local skills block.
|
|
1173
1186
|
skip_project_review_skills: !!(meta && meta.skipProjectReviewSkills),
|
|
1187
|
+
// W-mq1cczi90006b21f — generalized project-skills suppression flag.
|
|
1188
|
+
// Honors either the new `meta.skipProjectSkills` or the PR-82
|
|
1189
|
+
// `meta.skipProjectReviewSkills` alias so older work items keep working.
|
|
1174
1190
|
skip_project_skills: !!(meta && (meta.skipProjectSkills || meta.skipProjectReviewSkills)),
|
|
1191
|
+
// W-mq5l3f2u000i9045 — opt-out for the inlined PR description audit
|
|
1192
|
+
// section in fix.md. Truthy renders the "audit suppressed" notice; falsy
|
|
1193
|
+
// (the default) leaves the standard audit instructions in place.
|
|
1194
|
+
skip_description_audit: !!(meta && meta.skipDescriptionAudit),
|
|
1175
1195
|
...extraVars,
|
|
1176
1196
|
task_id: dispatchId,
|
|
1177
1197
|
};
|
package/engine.js
CHANGED
|
@@ -6121,6 +6121,14 @@ function renderProjectWorkItemPromptForAgent(item, workType, agentId, config, pr
|
|
|
6121
6121
|
managed_spawn_ttl_minutes: item.meta && Number.isFinite(Number(item.meta.managed_spawn_ttl_minutes))
|
|
6122
6122
|
? Math.floor(Number(item.meta.managed_spawn_ttl_minutes))
|
|
6123
6123
|
: '',
|
|
6124
|
+
// W-mq5l3f2u000i9045 — opt-out flag for the inlined PR description audit
|
|
6125
|
+
// section in fix.md / implement.md. When the dispatcher sets
|
|
6126
|
+
// item.meta.skipDescriptionAudit (skill-meta updates, doc-only fixes,
|
|
6127
|
+
// follow-up dispatches that explicitly own the description), the playbook
|
|
6128
|
+
// renders the "audit suppressed" notice instead of the standard audit
|
|
6129
|
+
// steps. Default off — every fix/implement dispatch that pushes commits
|
|
6130
|
+
// audits the PR description by default.
|
|
6131
|
+
skip_description_audit: !!(item.meta && item.meta.skipDescriptionAudit),
|
|
6124
6132
|
// W-mpeiwz6k0005bf34-c — opt-in qa-validate context. The dispatch handler
|
|
6125
6133
|
// POST /api/qa/runbooks/run stamps meta.qaRunId + meta.qaRunbook (full
|
|
6126
6134
|
// spec) + meta.qaTarget (managed-process snapshot) on the work item;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2159",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# PR description audit + screenshot refresh (shared fragment)
|
|
2
|
+
|
|
3
|
+
> **Naming convention.** Files named `_*.md` under `playbooks/` are **shared
|
|
4
|
+
> fragments**. The engine renderer (`engine/playbook.js`) does not (yet)
|
|
5
|
+
> implement an `<include>` directive, so each consuming playbook inlines this
|
|
6
|
+
> fragment verbatim and the two copies are kept in sync by hand. If you change
|
|
7
|
+
> this file, also update the inlined copies in `playbooks/fix.md` and
|
|
8
|
+
> `playbooks/implement.md` (and any other playbook that pushes commits to a
|
|
9
|
+
> tracked PR). The matching test in `test/unit/config-and-playbooks.test.js`
|
|
10
|
+
> ("PR description audit fragment is inlined consistently") fails fast when the
|
|
11
|
+
> copies drift.
|
|
12
|
+
|
|
13
|
+
## When to run
|
|
14
|
+
|
|
15
|
+
Run this audit **after** you push new commits to the PR's source branch and
|
|
16
|
+
**before** you mark the work item done.
|
|
17
|
+
|
|
18
|
+
Skip the audit entirely when:
|
|
19
|
+
|
|
20
|
+
- This dispatch was invoked with `meta.skipDescriptionAudit: true` (the
|
|
21
|
+
engine renders an explicit "audit suppressed" notice when the flag is set).
|
|
22
|
+
- You did not push any new commits this dispatch (no behavior change → no
|
|
23
|
+
description drift).
|
|
24
|
+
- The dispatch produced a no-op completion (`noop: true`).
|
|
25
|
+
|
|
26
|
+
When you skip, still report it: set `meta.descriptionAudit.ran = false` and
|
|
27
|
+
`meta.descriptionAudit.result = "skipped:<reason>"` in the completion JSON.
|
|
28
|
+
|
|
29
|
+
## Audit steps
|
|
30
|
+
|
|
31
|
+
1. **Fetch the current PR description.**
|
|
32
|
+
- GitHub: `gh pr view <num> --repo <owner>/<repo> --json body --jq .body`
|
|
33
|
+
- Azure DevOps: `az repos pr show --id <num> --query description -o tsv`
|
|
34
|
+
2. **Compute the diff for the commits you just pushed.**
|
|
35
|
+
- `git log --stat <baseline>..HEAD` where `<baseline>` is `origin/main` for
|
|
36
|
+
the first push or `origin/<branch>@{1}` (pre-push tip) for follow-up
|
|
37
|
+
pushes. `git log --oneline <baseline>..HEAD` if you only need the SHA list.
|
|
38
|
+
3. **Compare description sections against the diff.** For each section,
|
|
39
|
+
evaluate whether the new commits change anything it claims. Focus
|
|
40
|
+
specifically on:
|
|
41
|
+
- Feature lists / "what changed" bullet points
|
|
42
|
+
- Config / option / matrix tables
|
|
43
|
+
- File or path lists
|
|
44
|
+
- Line counts, before/after metrics, perf numbers, table cell values
|
|
45
|
+
- ASCII diagrams that depict layout the diff touched
|
|
46
|
+
- Embedded markdown image refs (``) — see the screenshot
|
|
47
|
+
subsection below
|
|
48
|
+
4. **Decide the result.**
|
|
49
|
+
- Description is consistent with the diff → no-op. Record
|
|
50
|
+
`meta.descriptionAudit = { ran: true, result: "no-changes-needed" }`
|
|
51
|
+
in the completion report and stop here.
|
|
52
|
+
- Description needs targeted edits → build the patched body. **Preserve prose voice**. Edit only the stale parts. Do **not** rewrite. Do **not**
|
|
53
|
+
add new sections that weren't there before. Do **not** change the PR
|
|
54
|
+
title. Do **not** flip draft state, close/reopen, or post a separate PR
|
|
55
|
+
comment narrating the description edit.
|
|
56
|
+
5. **Push the patched description.**
|
|
57
|
+
- GitHub: write the new body to a temp file, then
|
|
58
|
+
`gh pr edit <num> --repo <owner>/<repo> --body-file <file>`
|
|
59
|
+
- Azure DevOps:
|
|
60
|
+
`az repos pr update --id <num> --description "$(Get-Content -Raw <file>)"`
|
|
61
|
+
(PowerShell) or the POSIX equivalent. ADO's description field also
|
|
62
|
+
truncates around ~4 KB on the `--description` flag — if you hit that,
|
|
63
|
+
fall back to `az rest --method patch` against
|
|
64
|
+
`…/pullRequests/<id>?api-version=7.1` with `{ "description": "<body>" }`.
|
|
65
|
+
6. **GET-verify the description post-patch** by re-running the fetch from
|
|
66
|
+
step 1 and confirming the body matches what you sent.
|
|
67
|
+
|
|
68
|
+
## Screenshot handling
|
|
69
|
+
|
|
70
|
+
This applies only when the **existing** description embeds markdown image
|
|
71
|
+
refs. **Never introduce screenshots to a PR that didn't already have any** —
|
|
72
|
+
this audit is a refresh, not an introduction.
|
|
73
|
+
|
|
74
|
+
1. Extract every `` from the current description.
|
|
75
|
+
2. For each image, decide whether it depicts a view this dispatch's diff
|
|
76
|
+
touched. Heuristics (any one is enough):
|
|
77
|
+
- `alt` text or filename mentions a component / route / view name that
|
|
78
|
+
appears in the changed files.
|
|
79
|
+
- The image URL path contains an issue/PR id matching this PR.
|
|
80
|
+
- The description text immediately around the image references files
|
|
81
|
+
present in the diff.
|
|
82
|
+
3. If the view was touched **and** the project has a runnable dev server
|
|
83
|
+
(detect via `package.json` scripts named `dev`, `start`, or `serve`)
|
|
84
|
+
**and** Playwright MCP is available, recapture:
|
|
85
|
+
1. Spin up the dev server with a detached handoff (see
|
|
86
|
+
`shared-rules.md` → "Long-Running Commands"). Capture PID + log path +
|
|
87
|
+
URL + stop command.
|
|
88
|
+
2. Drive Playwright MCP to the relevant route and screenshot the same
|
|
89
|
+
view. Save raw PNGs to `agents/<id>/screenshots/` only. **NEVER `git add` a PNG** — adding any code path that commits a screenshot is a
|
|
90
|
+
release-blocker bug.
|
|
91
|
+
3. Upload as a PR attachment:
|
|
92
|
+
- **ADO**: `PUT /pullRequests/<id>/attachments/<filename>` with the
|
|
93
|
+
PNG bytes, then reference the returned URL in the description.
|
|
94
|
+
- **GitHub**: there is no documented REST endpoint for PR-comment
|
|
95
|
+
image uploads. The browser drag-drop path uses
|
|
96
|
+
`github.com/upload/asset`, which `gh` does not wrap. If you cannot
|
|
97
|
+
find a reliable scripted upload path, **skip** this screenshot,
|
|
98
|
+
leave the old image URL in place, and record
|
|
99
|
+
`meta.descriptionAudit.result = "screenshots-refreshed-skipped (gh-attachment-unavailable)"`.
|
|
100
|
+
4. Patch the description to swap old image URLs for new.
|
|
101
|
+
5. Stop the dev server using the recorded stop command.
|
|
102
|
+
4. If the dev server fails to start, the route 404s, Playwright fails, or
|
|
103
|
+
the host's attachment API is unreachable → **skip that screenshot**
|
|
104
|
+
without failing the work item. Record the skip and the reason in
|
|
105
|
+
`meta.descriptionAudit`.
|
|
106
|
+
|
|
107
|
+
## Out-of-scope guardrails
|
|
108
|
+
|
|
109
|
+
- Don't add screenshots that weren't already there.
|
|
110
|
+
- Don't rewrite description prose beyond the targeted stale edits.
|
|
111
|
+
- Don't modify the PR title.
|
|
112
|
+
- Don't toggle draft state, close/reopen the PR, or post a separate PR
|
|
113
|
+
comment narrating the description change.
|
|
114
|
+
- Don't fail the work item on a screenshot or attachment failure — degrade
|
|
115
|
+
gracefully and record the skip.
|
|
116
|
+
- Screenshots **never** land in the repo. Save raw PNGs only under
|
|
117
|
+
`agents/<id>/screenshots/`. No `git add` of any PNG, ever.
|
|
118
|
+
|
|
119
|
+
## Completion-report shape
|
|
120
|
+
|
|
121
|
+
When the audit ran (whether it edited the description or no-op'd), include
|
|
122
|
+
this block in the JSON completion report (see
|
|
123
|
+
`docs/completion-reports.md` → "PR description audit"):
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"meta": {
|
|
128
|
+
"descriptionAudit": {
|
|
129
|
+
"ran": true,
|
|
130
|
+
"result": "description-patched",
|
|
131
|
+
"oldScreenshots": [],
|
|
132
|
+
"newScreenshots": [],
|
|
133
|
+
"patchedSections": ["feature-bullets", "config-table"]
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Omit `meta.descriptionAudit` entirely when the audit did not run (no
|
|
140
|
+
commits pushed, no-op completion, etc.). When `meta.skipDescriptionAudit`
|
|
141
|
+
suppressed the audit, set `ran: false` and
|
|
142
|
+
`result: "skipped:meta-flag"` so the audit history stays auditable.
|
package/playbooks/fix.md
CHANGED
|
@@ -99,6 +99,77 @@ Do NOT remove the worktree — the engine handles cleanup automatically.
|
|
|
99
99
|
- content: Explain what was fixed, reference each review finding, include build/test status
|
|
100
100
|
- Sign: `Fixed by Minions ({{agent_name}} — {{agent_role}} · {{agent_model}})`
|
|
101
101
|
|
|
102
|
+
## PR description audit (mandatory unless `meta.skipDescriptionAudit`)
|
|
103
|
+
|
|
104
|
+
<!-- Inlined from playbooks/_pr-description-audit.md — keep in sync (enforced by test/unit/config-and-playbooks.test.js). -->
|
|
105
|
+
|
|
106
|
+
{{#skip_description_audit}}
|
|
107
|
+
> **Audit suppressed.** This dispatch was invoked with `meta.skipDescriptionAudit: true`.
|
|
108
|
+
> Record `meta.descriptionAudit = { ran: false, result: "skipped:meta-flag" }` in your completion report
|
|
109
|
+
> and skip the audit steps below entirely.
|
|
110
|
+
{{/skip_description_audit}}
|
|
111
|
+
|
|
112
|
+
After you push commits to the PR's source branch and BEFORE you mark the work item done, audit the PR description and refresh it to match the new diff. Skip the audit when you pushed no commits this dispatch or when the dispatch is a no-op completion; record the skip in `meta.descriptionAudit`.
|
|
113
|
+
|
|
114
|
+
**Audit steps**
|
|
115
|
+
|
|
116
|
+
1. Fetch the current PR description.
|
|
117
|
+
- GitHub: `gh pr view <num> --repo <owner>/<repo> --json body --jq .body`
|
|
118
|
+
- Azure DevOps: `az repos pr show --id <num> --query description -o tsv`
|
|
119
|
+
2. Compute the diff for the commits you just pushed: `git log --stat <baseline>..HEAD` where `<baseline>` is `origin/<branch>@{1}` for follow-up pushes (or `origin/main` for the very first push).
|
|
120
|
+
3. For each section of the description, evaluate whether the new diff invalidates anything it claims. Focus on: feature lists / "what changed" bullets, config/option tables, file or path lists, line counts, before/after metrics, ASCII diagrams, and embedded markdown image refs.
|
|
121
|
+
4. **No drift** → record `meta.descriptionAudit = { ran: true, result: "no-changes-needed" }` and stop.
|
|
122
|
+
5. **Drift detected** → build a patched body. **Preserve prose voice.** Edit only the stale parts. Do NOT rewrite. Do NOT add new sections. Do NOT change the PR title. Do NOT toggle draft state. Do NOT post a separate PR comment narrating the description change.
|
|
123
|
+
6. Push the patched description.
|
|
124
|
+
- GitHub: write the new body to a temp file, then `gh pr edit <num> --repo <owner>/<repo> --body-file <file>`.
|
|
125
|
+
- Azure DevOps: `az repos pr update --id <num> --description "$(Get-Content -Raw <file>)"`. If the body exceeds ~4 KB, fall back to `az rest --method patch` against `…/pullRequests/<id>?api-version=7.1` with `{ "description": "<body>" }`.
|
|
126
|
+
7. GET-verify the description post-patch by re-running step 1 and confirming the body matches what you sent.
|
|
127
|
+
|
|
128
|
+
**Screenshot handling (refresh-only — never introduce screenshots)**
|
|
129
|
+
|
|
130
|
+
This applies only when the **existing** description embeds markdown image refs (``). NEVER add screenshots to a PR that didn't already have any.
|
|
131
|
+
|
|
132
|
+
1. Extract every `` from the current description.
|
|
133
|
+
2. Decide which images depict views this dispatch's diff touched (heuristic: alt text / filename / surrounding prose mentions a component / route / view name present in the changed files).
|
|
134
|
+
3. If the view was touched AND the project has a runnable dev server (detect via `package.json` scripts named `dev`, `start`, or `serve`) AND Playwright MCP is available, recapture:
|
|
135
|
+
- Spin up the dev server with a detached handoff per `shared-rules.md` → "Long-Running Commands". Record PID + log path + URL + stop command.
|
|
136
|
+
- Drive Playwright MCP to the relevant route and screenshot the view. Save raw PNGs to `agents/<id>/screenshots/` ONLY. **NEVER `git add` a PNG** — committing a screenshot is a release-blocker bug.
|
|
137
|
+
- Upload as a PR attachment:
|
|
138
|
+
- **ADO**: `PUT /pullRequests/<id>/attachments/<filename>` with the PNG bytes, then reference the returned URL in the description.
|
|
139
|
+
- **GitHub**: no documented REST endpoint exists for PR-comment image uploads (the browser drag-drop uses `github.com/upload/asset`, which `gh` does not wrap). If you cannot find a reliable scripted upload path, **skip** the recapture, leave the old image URL in place, and record `meta.descriptionAudit.result = "screenshots-refreshed-skipped (gh-attachment-unavailable)"`.
|
|
140
|
+
- Patch the description to swap old image URLs for new.
|
|
141
|
+
- Stop the dev server using the recorded stop command.
|
|
142
|
+
4. If the dev server fails to start, the route 404s, Playwright fails, or the host's attachment API is unreachable → **skip that screenshot** without failing the work item. Record the skip and the reason in `meta.descriptionAudit`.
|
|
143
|
+
|
|
144
|
+
**Out-of-scope guardrails**
|
|
145
|
+
|
|
146
|
+
- Don't add screenshots that weren't already there. Refresh only.
|
|
147
|
+
- Don't rewrite description prose beyond the targeted stale edits.
|
|
148
|
+
- Don't modify the PR title; don't toggle draft state, close/reopen the PR.
|
|
149
|
+
- Don't post a separate PR comment summarizing the description change.
|
|
150
|
+
- Don't fail the work item on a screenshot or attachment failure — degrade gracefully and record the skip.
|
|
151
|
+
- Screenshots NEVER land in the repo. `agents/<id>/screenshots/` only; never `git add` a PNG.
|
|
152
|
+
|
|
153
|
+
**Completion-report shape**
|
|
154
|
+
|
|
155
|
+
When the audit ran (whether it edited the description or no-op'd), include this in the completion JSON (full schema: `docs/completion-reports.md` → "PR description audit"):
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"meta": {
|
|
160
|
+
"descriptionAudit": {
|
|
161
|
+
"ran": true,
|
|
162
|
+
"result": "description-patched",
|
|
163
|
+
"oldScreenshots": [],
|
|
164
|
+
"newScreenshots": [],
|
|
165
|
+
"patchedSections": ["feature-bullets", "config-table"]
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Omit `meta.descriptionAudit` entirely when the audit did not run (no commits pushed, no-op completion). When `meta.skipDescriptionAudit` suppressed the audit, set `ran: false` and `result: "skipped:meta-flag"`.
|
|
172
|
+
|
|
102
173
|
## Resolve Review Comments
|
|
103
174
|
|
|
104
175
|
After pushing, respond to each review comment/thread. **Every disposition requires a reply before you resolve, close, or mark-fixed the thread — no exceptions.** Resolving a thread silently is a process violation (see "Resolving Review Threads — No Silent Closures" in `shared-rules.md`).
|
package/playbooks/implement.md
CHANGED
|
@@ -101,6 +101,77 @@ Create the PR for implement tasks — the engine tracks review and completion fr
|
|
|
101
101
|
|
|
102
102
|
Include build/test status and run instructions in the PR description. If the project has a runnable app, include the localhost URL.
|
|
103
103
|
|
|
104
|
+
## PR description audit (mandatory unless `meta.skipDescriptionAudit`)
|
|
105
|
+
|
|
106
|
+
<!-- Inlined from playbooks/_pr-description-audit.md — keep in sync (enforced by test/unit/config-and-playbooks.test.js). -->
|
|
107
|
+
|
|
108
|
+
{{#skip_description_audit}}
|
|
109
|
+
> **Audit suppressed.** This dispatch was invoked with `meta.skipDescriptionAudit: true`.
|
|
110
|
+
> Record `meta.descriptionAudit = { ran: false, result: "skipped:meta-flag" }` in your completion report
|
|
111
|
+
> and skip the audit steps below entirely.
|
|
112
|
+
{{/skip_description_audit}}
|
|
113
|
+
|
|
114
|
+
After you push commits to the PR's source branch and BEFORE you mark the work item done, audit the PR description and refresh it to match the new diff. Skip the audit when you pushed no commits this dispatch or when the dispatch is a no-op completion; record the skip in `meta.descriptionAudit`.
|
|
115
|
+
|
|
116
|
+
**Audit steps**
|
|
117
|
+
|
|
118
|
+
1. Fetch the current PR description.
|
|
119
|
+
- GitHub: `gh pr view <num> --repo <owner>/<repo> --json body --jq .body`
|
|
120
|
+
- Azure DevOps: `az repos pr show --id <num> --query description -o tsv`
|
|
121
|
+
2. Compute the diff for the commits you just pushed: `git log --stat <baseline>..HEAD` where `<baseline>` is `origin/<branch>@{1}` for follow-up pushes (or `origin/main` for the very first push).
|
|
122
|
+
3. For each section of the description, evaluate whether the new diff invalidates anything it claims. Focus on: feature lists / "what changed" bullets, config/option tables, file or path lists, line counts, before/after metrics, ASCII diagrams, and embedded markdown image refs.
|
|
123
|
+
4. **No drift** → record `meta.descriptionAudit = { ran: true, result: "no-changes-needed" }` and stop.
|
|
124
|
+
5. **Drift detected** → build a patched body. **Preserve prose voice.** Edit only the stale parts. Do NOT rewrite. Do NOT add new sections. Do NOT change the PR title. Do NOT toggle draft state. Do NOT post a separate PR comment narrating the description change.
|
|
125
|
+
6. Push the patched description.
|
|
126
|
+
- GitHub: write the new body to a temp file, then `gh pr edit <num> --repo <owner>/<repo> --body-file <file>`.
|
|
127
|
+
- Azure DevOps: `az repos pr update --id <num> --description "$(Get-Content -Raw <file>)"`. If the body exceeds ~4 KB, fall back to `az rest --method patch` against `…/pullRequests/<id>?api-version=7.1` with `{ "description": "<body>" }`.
|
|
128
|
+
7. GET-verify the description post-patch by re-running step 1 and confirming the body matches what you sent.
|
|
129
|
+
|
|
130
|
+
**Screenshot handling (refresh-only — never introduce screenshots)**
|
|
131
|
+
|
|
132
|
+
This applies only when the **existing** description embeds markdown image refs (``). NEVER add screenshots to a PR that didn't already have any.
|
|
133
|
+
|
|
134
|
+
1. Extract every `` from the current description.
|
|
135
|
+
2. Decide which images depict views this dispatch's diff touched (heuristic: alt text / filename / surrounding prose mentions a component / route / view name present in the changed files).
|
|
136
|
+
3. If the view was touched AND the project has a runnable dev server (detect via `package.json` scripts named `dev`, `start`, or `serve`) AND Playwright MCP is available, recapture:
|
|
137
|
+
- Spin up the dev server with a detached handoff per `shared-rules.md` → "Long-Running Commands". Record PID + log path + URL + stop command.
|
|
138
|
+
- Drive Playwright MCP to the relevant route and screenshot the view. Save raw PNGs to `agents/<id>/screenshots/` ONLY. **NEVER `git add` a PNG** — committing a screenshot is a release-blocker bug.
|
|
139
|
+
- Upload as a PR attachment:
|
|
140
|
+
- **ADO**: `PUT /pullRequests/<id>/attachments/<filename>` with the PNG bytes, then reference the returned URL in the description.
|
|
141
|
+
- **GitHub**: no documented REST endpoint exists for PR-comment image uploads (the browser drag-drop uses `github.com/upload/asset`, which `gh` does not wrap). If you cannot find a reliable scripted upload path, **skip** the recapture, leave the old image URL in place, and record `meta.descriptionAudit.result = "screenshots-refreshed-skipped (gh-attachment-unavailable)"`.
|
|
142
|
+
- Patch the description to swap old image URLs for new.
|
|
143
|
+
- Stop the dev server using the recorded stop command.
|
|
144
|
+
4. If the dev server fails to start, the route 404s, Playwright fails, or the host's attachment API is unreachable → **skip that screenshot** without failing the work item. Record the skip and the reason in `meta.descriptionAudit`.
|
|
145
|
+
|
|
146
|
+
**Out-of-scope guardrails**
|
|
147
|
+
|
|
148
|
+
- Don't add screenshots that weren't already there. Refresh only.
|
|
149
|
+
- Don't rewrite description prose beyond the targeted stale edits.
|
|
150
|
+
- Don't modify the PR title; don't toggle draft state, close/reopen the PR.
|
|
151
|
+
- Don't post a separate PR comment summarizing the description change.
|
|
152
|
+
- Don't fail the work item on a screenshot or attachment failure — degrade gracefully and record the skip.
|
|
153
|
+
- Screenshots NEVER land in the repo. `agents/<id>/screenshots/` only; never `git add` a PNG.
|
|
154
|
+
|
|
155
|
+
**Completion-report shape**
|
|
156
|
+
|
|
157
|
+
When the audit ran (whether it edited the description or no-op'd), include this in the completion JSON (full schema: `docs/completion-reports.md` → "PR description audit"):
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"meta": {
|
|
162
|
+
"descriptionAudit": {
|
|
163
|
+
"ran": true,
|
|
164
|
+
"result": "description-patched",
|
|
165
|
+
"oldScreenshots": [],
|
|
166
|
+
"newScreenshots": [],
|
|
167
|
+
"patchedSections": ["feature-bullets", "config-table"]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Omit `meta.descriptionAudit` entirely when the audit did not run (no commits pushed, no-op completion). When `meta.skipDescriptionAudit` suppressed the audit, set `ran: false` and `result: "skipped:meta-flag"`.
|
|
174
|
+
|
|
104
175
|
## When to Stop
|
|
105
176
|
|
|
106
177
|
Your task is complete when the requested implementation is delivered, the validation story is truthful and sufficient for review, the branch is pushed, and the PR exists. Include the PR URL in your final message so the engine can track it.
|