@yemi33/minions 0.1.46 → 0.1.47
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/CHANGELOG.md +5 -0
- package/dashboard/js/render-plans.js +60 -42
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -53,6 +53,39 @@ async function refreshPlans() {
|
|
|
53
53
|
} catch {}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Derive effective plan/PRD status from work items (single source of truth).
|
|
58
|
+
* PRD JSON status is treated as user intent (approved, paused, rejected),
|
|
59
|
+
* but completion/progress is always derived from actual work item state.
|
|
60
|
+
*/
|
|
61
|
+
function derivePlanStatus(prdFile, mdFile, prdJsonStatus, workItems) {
|
|
62
|
+
const wi = workItems.filter(w =>
|
|
63
|
+
w.sourcePlan === prdFile || w.sourcePlan === mdFile ||
|
|
64
|
+
(w.type === 'plan-to-prd' && (w.planFile === prdFile || w.planFile === mdFile))
|
|
65
|
+
);
|
|
66
|
+
const implementWi = wi.filter(w => w.type !== 'plan-to-prd' && w.type !== 'verify');
|
|
67
|
+
const hasPendingPrd = wi.some(w => w.type === 'plan-to-prd' && (w.status === 'pending' || w.status === 'dispatched'));
|
|
68
|
+
const hasActiveWork = implementWi.some(w => w.status === 'pending' || w.status === 'dispatched');
|
|
69
|
+
const allDone = implementWi.length > 0 && implementWi.every(w =>
|
|
70
|
+
w.status === 'done' || w.status === 'in-pr' || w.status === 'implemented' || w.status === 'complete'
|
|
71
|
+
);
|
|
72
|
+
const hasFailed = implementWi.some(w => w.status === 'failed');
|
|
73
|
+
|
|
74
|
+
// User-set statuses take priority when no work has started
|
|
75
|
+
if (prdJsonStatus === 'rejected') return 'rejected';
|
|
76
|
+
if (prdJsonStatus === 'paused' && !allDone) return 'paused';
|
|
77
|
+
if (prdJsonStatus === 'revision-requested') return 'revision-requested';
|
|
78
|
+
|
|
79
|
+
// Derive from work item progress
|
|
80
|
+
if (allDone && !hasActiveWork) return 'completed';
|
|
81
|
+
if (hasActiveWork || hasPendingPrd) return 'in-progress';
|
|
82
|
+
if (hasFailed && !hasActiveWork) return 'has-failures';
|
|
83
|
+
if (prdJsonStatus === 'awaiting-approval' && implementWi.length === 0) return 'awaiting-approval';
|
|
84
|
+
if (prdJsonStatus === 'approved' && implementWi.length === 0) return 'approved';
|
|
85
|
+
|
|
86
|
+
return prdJsonStatus || 'active';
|
|
87
|
+
}
|
|
88
|
+
|
|
56
89
|
function renderPlans(plans) {
|
|
57
90
|
const el = document.getElementById('plans-list');
|
|
58
91
|
const countEl = document.getElementById('plans-count');
|
|
@@ -112,8 +145,7 @@ function renderPlans(plans) {
|
|
|
112
145
|
}
|
|
113
146
|
|
|
114
147
|
// Track which .md plans have a paused PRD, and map .md → PRD .json
|
|
115
|
-
|
|
116
|
-
const awaitingApprovalPlanFiles = new Set();
|
|
148
|
+
// (status derived from work items via derivePlanStatus — no separate tracking needed)
|
|
117
149
|
const planToPrdFile = {}; // .md filename → .json PRD filename
|
|
118
150
|
for (const p of plans) {
|
|
119
151
|
if (p.format === 'prd' && !p.archived && p.sourcePlan) {
|
|
@@ -121,8 +153,7 @@ function renderPlans(plans) {
|
|
|
121
153
|
for (const sourceKey of sourceKeys) {
|
|
122
154
|
if (!sourceKey) continue;
|
|
123
155
|
planToPrdFile[sourceKey] = p.file;
|
|
124
|
-
|
|
125
|
-
if (p.status === 'awaiting-approval') awaitingApprovalPlanFiles.add(sourceKey);
|
|
156
|
+
// Status derived via derivePlanStatus — planToPrdFile mapping is all we need
|
|
126
157
|
}
|
|
127
158
|
}
|
|
128
159
|
}
|
|
@@ -132,40 +163,27 @@ function renderPlans(plans) {
|
|
|
132
163
|
countEl.textContent = activePlans.length + (archivedPlans.length ? ' + ' + archivedPlans.length + ' archived' : '');
|
|
133
164
|
|
|
134
165
|
function renderPlanCard(p) {
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const isPrdPaused = pausedPlanFiles.has(p.file);
|
|
138
|
-
const isPrdAwaitingApproval = awaitingApprovalPlanFiles.has(p.file);
|
|
139
|
-
const isPrdBlocked = isPrdPaused || isPrdAwaitingApproval;
|
|
166
|
+
const prdFile = planToPrdFile[p.file] || (p.file.endsWith('.json') ? p.file : '');
|
|
167
|
+
const rawStatus = p.status || 'active';
|
|
140
168
|
const isArchived = p.archived;
|
|
141
|
-
|
|
142
|
-
//
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
: (statusLabels[status] || status);
|
|
156
|
-
const needsAction = (status === 'awaiting-approval' || status === 'paused' || isPrdAwaitingApproval || isPrdPaused) && !isArchived && !allItemsDone;
|
|
157
|
-
const isRevision = status === 'revision-requested' && !isArchived;
|
|
158
|
-
const isCompleted = status === 'completed' || (allItemsDone && !hasActiveWork);
|
|
159
|
-
const isDraft = (p.format === 'draft' || status === 'draft') && !isCompleted;
|
|
160
|
-
const isAwaitingApproval = status === 'awaiting-approval';
|
|
161
|
-
const isPaused = status === 'paused';
|
|
162
|
-
const isApproved = status === 'approved' || status === 'active';
|
|
169
|
+
|
|
170
|
+
// Single source of truth: derive status from work items
|
|
171
|
+
const effectiveStatus = isArchived ? 'completed' : derivePlanStatus(prdFile, p.file, rawStatus, allWi);
|
|
172
|
+
|
|
173
|
+
const statusLabelsMap = {
|
|
174
|
+
'completed': 'Completed', 'in-progress': 'In Progress', 'paused': 'Paused',
|
|
175
|
+
'awaiting-approval': 'Awaiting Approval', 'approved': 'Approved', 'rejected': 'Rejected',
|
|
176
|
+
'revision-requested': 'Revision Requested', 'has-failures': 'Has Failures', 'active': 'Active'
|
|
177
|
+
};
|
|
178
|
+
const label = statusLabelsMap[effectiveStatus] || effectiveStatus;
|
|
179
|
+
const needsAction = (effectiveStatus === 'awaiting-approval' || effectiveStatus === 'paused') && !isArchived;
|
|
180
|
+
const isRevision = effectiveStatus === 'revision-requested';
|
|
181
|
+
const isCompleted = effectiveStatus === 'completed';
|
|
182
|
+
const isDraft = (p.format === 'draft' || rawStatus === 'draft') && !isCompleted;
|
|
163
183
|
// For .md drafts: show Execute only if no PRD exists yet (not already executed)
|
|
164
|
-
const prdFile = planToPrdFile[p.file] || (p.file.endsWith('.json') ? p.file : '');
|
|
165
184
|
|
|
166
185
|
let actions = '';
|
|
167
|
-
|
|
168
|
-
if (needsAction && !resumeVisible) {
|
|
186
|
+
if (needsAction) {
|
|
169
187
|
actions = '<div class="plan-card-actions" onclick="event.stopPropagation()">' +
|
|
170
188
|
'<button class="plan-btn approve" onclick="planApprove(\'' + escHtml(p.file) + '\')">Approve</button>' +
|
|
171
189
|
'<button class="plan-btn" style="color:var(--blue);border-color:var(--blue)" onclick="planDiscuss(\'' + escHtml(p.file) + '\')">Discuss & Revise</button>' +
|
|
@@ -182,27 +200,27 @@ function renderPlans(plans) {
|
|
|
182
200
|
actions = '<div class="plan-card-meta" style="margin-top:6px;color:var(--purple,#a855f7)">Revision in progress: ' + escHtml((p.revisionFeedback || '').slice(0, 100)) + '</div>';
|
|
183
201
|
}
|
|
184
202
|
|
|
185
|
-
const executeBtn = isDraft &&
|
|
203
|
+
const executeBtn = isDraft && effectiveStatus === 'active' && !isArchived && !prdFile ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green);font-weight:600" ' +
|
|
186
204
|
'onclick="event.stopPropagation();planExecute(\'' + escHtml(p.file) + '\',\'' + escHtml(p.project) + '\',this)">Execute</button>' : '';
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
const showPause = !effectivelyPaused && prdFile && !isArchived && !isCompleted;
|
|
190
|
-
const showResume = effectivelyPaused && prdFile && !isArchived;
|
|
205
|
+
const showPause = effectiveStatus === 'in-progress' && prdFile && !isArchived;
|
|
206
|
+
const showResume = (effectiveStatus === 'paused' || effectiveStatus === 'awaiting-approval') && prdFile && !isArchived;
|
|
191
207
|
const pauseBtn = showPause ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--yellow)" ' +
|
|
192
208
|
'onclick="event.stopPropagation();planPause(\'' + escHtml(prdFile) + '\')">Pause</button>' : '';
|
|
193
209
|
const resumeBtn = showResume
|
|
194
210
|
? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green)" ' +
|
|
195
|
-
'onclick="event.stopPropagation();planApprove(\'' + escHtml(prdFile) + '\')">' + (
|
|
211
|
+
'onclick="event.stopPropagation();planApprove(\'' + escHtml(prdFile) + '\')">' + (effectiveStatus === 'awaiting-approval' ? 'Approve' : 'Resume') + '</button>'
|
|
196
212
|
: '';
|
|
197
213
|
const deleteBtn = !isArchived ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red)" ' +
|
|
198
214
|
'onclick="event.stopPropagation();planDelete(\'' + escHtml(p.file) + '\')">Delete</button>' : '';
|
|
199
215
|
|
|
200
216
|
const versionBadge = p.version ? ' <span style="font-size:9px;font-weight:700;padding:1px 5px;border-radius:3px;background:rgba(56,139,253,0.15);color:var(--blue);vertical-align:middle">v' + p.version + '</span>' : '';
|
|
201
|
-
|
|
217
|
+
const statusColors = { 'completed': 'var(--green)', 'in-progress': 'var(--blue)', 'paused': 'var(--muted)', 'awaiting-approval': 'var(--yellow)', 'approved': 'var(--green)', 'rejected': 'var(--red)', 'has-failures': 'var(--red)', 'revision-requested': 'var(--purple,#a855f7)', 'active': 'var(--muted)' };
|
|
218
|
+
const cardClass = effectiveStatus === 'in-progress' ? 'working' : effectiveStatus === 'awaiting-approval' || effectiveStatus === 'paused' ? 'awaiting' : effectiveStatus;
|
|
219
|
+
return '<div class="plan-card ' + cardClass + '" data-file="plans/' + escHtml(p.file) + '" style="cursor:pointer' + (isArchived ? ';opacity:0.7' : '') + '" onclick="planView(\'' + escHtml(p.file) + '\')">' +
|
|
202
220
|
'<div class="plan-card-header">' +
|
|
203
221
|
'<div><div class="plan-card-title">' + escHtml(p.summary || p.file) + versionBadge + '</div>' +
|
|
204
222
|
'<div class="plan-card-meta">' +
|
|
205
|
-
'<span style="font-weight:600;color:' + (
|
|
223
|
+
'<span style="font-weight:600;color:' + (statusColors[effectiveStatus] || 'var(--muted)') + '">' + label + '</span>' +
|
|
206
224
|
'<span>' + escHtml(p.project) + '</span>' +
|
|
207
225
|
'<span>' + p.itemCount + ' items</span>' +
|
|
208
226
|
(p.updatedAt ? '<span title="Last updated: ' + p.updatedAt + '">Updated ' + timeAgo(p.updatedAt) + '</span>' : '') +
|
package/package.json
CHANGED