@yemi33/minions 0.1.46 → 0.1.48

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.48 (2026-03-30)
4
+
5
+ ### Dashboard
6
+ - dashboard/js/render-plans.js
7
+
8
+ ## 0.1.47 (2026-03-30)
9
+
10
+ ### Dashboard
11
+ - dashboard/js/render-plans.js
12
+
3
13
  ## 0.1.46 (2026-03-29)
4
14
 
5
15
  ### Dashboard
@@ -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
- const pausedPlanFiles = new Set();
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
- if (p.status === 'paused') pausedPlanFiles.add(sourceKey);
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,34 @@ function renderPlans(plans) {
132
163
  countEl.textContent = activePlans.length + (archivedPlans.length ? ' + ' + archivedPlans.length + ' archived' : '');
133
164
 
134
165
  function renderPlanCard(p) {
135
- const status = p.status || 'active';
136
- const isWorking = workingPlanFiles.has(p.file);
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 : '');
140
167
  const isArchived = p.archived;
141
- // Check work item completion — if all items for this plan are done, it's completed
142
- // (matches the modal's logic, which also checks work items)
143
- const prdFileForCompletion = planToPrdFile[p.file] || (p.file.endsWith('.json') ? p.file : '');
144
- const planWi = allWi.filter(w => w.sourcePlan === prdFileForCompletion || w.sourcePlan === p.file);
145
- const allItemsDone = planWi.length > 0 && planWi.every(w => w.status === 'done' || w.status === 'in-pr' || w.status === 'implemented' || w.status === 'complete');
146
- const hasActiveWork = planWi.some(w => w.status === 'pending' || w.status === 'dispatched');
147
- const label = isArchived || (allItemsDone && !hasActiveWork)
148
- ? 'Completed'
149
- : isPrdAwaitingApproval && !allItemsDone
150
- ? 'Awaiting Approval'
151
- : isPrdPaused
152
- ? 'Paused'
153
- : isWorking
154
- ? 'In Progress'
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';
168
+
169
+ // For .md plans with a linked PRD, use the PRD's status as the authoritative intent
170
+ // (p.status for .md is 'draft'/'converted'/'active', not the PRD lifecycle status)
171
+ let prdJsonStatus = p.status || 'active';
172
+ if (prdFile && p.format !== 'prd') {
173
+ const linkedPrd = plans.find(pp => pp.file === prdFile && pp.format === 'prd');
174
+ if (linkedPrd) prdJsonStatus = linkedPrd.status || prdJsonStatus;
175
+ }
176
+
177
+ // Single source of truth: derive status from work items
178
+ const effectiveStatus = isArchived ? 'completed' : derivePlanStatus(prdFile, p.file, prdJsonStatus, allWi);
179
+
180
+ const statusLabelsMap = {
181
+ 'completed': 'Completed', 'in-progress': 'In Progress', 'paused': 'Paused',
182
+ 'awaiting-approval': 'Awaiting Approval', 'approved': 'Approved', 'rejected': 'Rejected',
183
+ 'revision-requested': 'Revision Requested', 'has-failures': 'Has Failures', 'active': 'Active'
184
+ };
185
+ const label = statusLabelsMap[effectiveStatus] || effectiveStatus;
186
+ const needsAction = (effectiveStatus === 'awaiting-approval' || effectiveStatus === 'paused') && !isArchived;
187
+ const isRevision = effectiveStatus === 'revision-requested';
188
+ const isCompleted = effectiveStatus === 'completed';
189
+ const isDraft = (p.format === 'draft' || rawStatus === 'draft') && !isCompleted;
163
190
  // 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
191
 
166
192
  let actions = '';
167
- const resumeVisible = ((isPrdBlocked || isAwaitingApproval || isPaused) && prdFile && !isArchived);
168
- if (needsAction && !resumeVisible) {
193
+ if (needsAction) {
169
194
  actions = '<div class="plan-card-actions" onclick="event.stopPropagation()">' +
170
195
  '<button class="plan-btn approve" onclick="planApprove(\'' + escHtml(p.file) + '\')">Approve</button>' +
171
196
  '<button class="plan-btn" style="color:var(--blue);border-color:var(--blue)" onclick="planDiscuss(\'' + escHtml(p.file) + '\')">Discuss &amp; Revise</button>' +
@@ -182,27 +207,27 @@ function renderPlans(plans) {
182
207
  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
208
  }
184
209
 
185
- const executeBtn = isDraft && !isWorking && !isPrdBlocked && !isArchived && !prdFile ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green);font-weight:600" ' +
210
+ 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
211
  'onclick="event.stopPropagation();planExecute(\'' + escHtml(p.file) + '\',\'' + escHtml(p.project) + '\',this)">Execute</button>' : '';
187
- // Pause/Resume: target the PRD .json file if it exists, otherwise the plan itself
188
- const effectivelyPaused = isPrdBlocked || isAwaitingApproval || isPaused;
189
- const showPause = !effectivelyPaused && prdFile && !isArchived && !isCompleted;
190
- const showResume = effectivelyPaused && prdFile && !isArchived;
212
+ const showPause = effectiveStatus === 'in-progress' && prdFile && !isArchived;
213
+ const showResume = (effectiveStatus === 'paused' || effectiveStatus === 'awaiting-approval') && prdFile && !isArchived;
191
214
  const pauseBtn = showPause ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--yellow)" ' +
192
215
  'onclick="event.stopPropagation();planPause(\'' + escHtml(prdFile) + '\')">Pause</button>' : '';
193
216
  const resumeBtn = showResume
194
217
  ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--green)" ' +
195
- 'onclick="event.stopPropagation();planApprove(\'' + escHtml(prdFile) + '\')">' + (isPrdAwaitingApproval || isAwaitingApproval ? 'Approve' : 'Resume') + '</button>'
218
+ 'onclick="event.stopPropagation();planApprove(\'' + escHtml(prdFile) + '\')">' + (effectiveStatus === 'awaiting-approval' ? 'Approve' : 'Resume') + '</button>'
196
219
  : '';
197
220
  const deleteBtn = !isArchived ? '<button class="pr-pager-btn" style="font-size:9px;padding:2px 8px;color:var(--red)" ' +
198
221
  'onclick="event.stopPropagation();planDelete(\'' + escHtml(p.file) + '\')">Delete</button>' : '';
199
222
 
200
223
  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
- return '<div class="plan-card ' + statusClass(status) + (isWorking && !isPrdBlocked ? ' working' : '') + (isPrdBlocked ? ' awaiting' : '') + '" data-file="plans/' + escHtml(p.file) + '" style="cursor:pointer' + (isArchived ? ';opacity:0.7' : '') + '" onclick="planView(\'' + escHtml(p.file) + '\')">' +
224
+ 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)' };
225
+ const cardClass = effectiveStatus === 'in-progress' ? 'working' : effectiveStatus === 'awaiting-approval' || effectiveStatus === 'paused' ? 'awaiting' : effectiveStatus;
226
+ 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
227
  '<div class="plan-card-header">' +
203
228
  '<div><div class="plan-card-title">' + escHtml(p.summary || p.file) + versionBadge + '</div>' +
204
229
  '<div class="plan-card-meta">' +
205
- '<span style="font-weight:600;color:' + (isArchived || isCompleted ? 'var(--green)' : isPrdAwaitingApproval ? 'var(--yellow)' : isPrdPaused ? 'var(--muted)' : isWorking ? 'var(--blue)' : needsAction ? 'var(--yellow,#d29922)' : status === 'approved' ? 'var(--green)' : 'var(--muted)') + '">' + label + '</span>' +
230
+ '<span style="font-weight:600;color:' + (statusColors[effectiveStatus] || 'var(--muted)') + '">' + label + '</span>' +
206
231
  '<span>' + escHtml(p.project) + '</span>' +
207
232
  '<span>' + p.itemCount + ' items</span>' +
208
233
  (p.updatedAt ? '<span title="Last updated: ' + p.updatedAt + '">Updated ' + timeAgo(p.updatedAt) + '</span>' : '') +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
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"