@yemi33/minions 0.1.829 → 0.1.831

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,17 +1,19 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.829 (2026-04-11)
3
+ ## 0.1.831 (2026-04-11)
4
4
 
5
5
  ### Features
6
6
  - stale PRD shows Regenerate PRD + Resume as-is buttons
7
7
 
8
8
  ### Fixes
9
+ - only 'updated' re-opens done work items, remove 'planned' status
9
10
  - revert doc-chat MAX_HEIGHT to 500px
10
11
  - hide plan card action buttons when stale banner is showing
11
12
  - hide conflicting action buttons when stale PRD banner is showing
12
13
  - diff-aware plan-to-prd triggered by Resume, not auto-dispatch
13
14
 
14
15
  ### Other
16
+ - refactor: add PRD_ITEM_STATUS/PRD_MATERIALIZABLE constants, extract queuePlanToPrd
15
17
  - refactor: remove dead criteria variable, DRY plan resume functions
16
18
 
17
19
  ## 0.1.823 (2026-04-11)
package/dashboard.js CHANGED
@@ -2247,34 +2247,21 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
2247
2247
  const projectName = plan.project || body.file.replace(/-\d{4}-\d{2}-\d{2}\.json$/, '');
2248
2248
  const targetProject = PROJECTS.find(p => p.name?.toLowerCase() === projectName.toLowerCase()) || PROJECTS[0];
2249
2249
  if (targetProject) {
2250
- const centralWiPath = path.join(MINIONS_DIR, 'work-items.json');
2251
- mutateJsonFileLocked(centralWiPath, items => {
2252
- if (!Array.isArray(items)) items = [];
2253
- if (items.some(w => w.type === 'plan-to-prd' && w.planFile === plan.source_plan && (w.status === 'pending' || w.status === 'dispatched'))) return items;
2254
- items.push({
2255
- id: 'W-' + shared.uid(),
2256
- title: `Update PRD from revised plan: ${plan.source_plan}`,
2257
- type: 'plan-to-prd',
2258
- priority: 'high',
2259
- description: `Plan file: plans/${plan.source_plan}\nPRD file: prd/${body.file}\n\n` +
2260
- `Source plan was revised. Compare the updated plan against existing PRD and produce an updated version.\n\n` +
2261
- `**Current PRD implementation state:**\n${implContext}\n\n` +
2262
- `**Rules for updating:**\n` +
2263
- `- Items that are done and unchanged in the plan → keep status "done" (preserve their ID)\n` +
2264
- `- Items that are done but modified in the plan (new requirements) → set status "updated" (engine will re-open)\n` +
2265
- `- New items in the plan → set status "missing" (engine will materialize)\n` +
2266
- `- Items removed from the plan → drop from PRD (engine will cancel pending WIs)\n` +
2267
- `- Preserve all existing item IDs for unchanged/modified items — do NOT generate new IDs for them`,
2268
- status: 'pending',
2269
- created: new Date().toISOString(),
2270
- createdBy: 'dashboard:plan-resume',
2271
- project: targetProject.name,
2272
- planFile: plan.source_plan,
2273
- _existingPrdFile: body.file,
2274
- });
2275
- diffAwareQueued = true;
2276
- return items;
2277
- }, { defaultValue: [] });
2250
+ diffAwareQueued = shared.queuePlanToPrd({
2251
+ planFile: plan.source_plan, prdFile: body.file,
2252
+ title: `Update PRD from revised plan: ${plan.source_plan}`,
2253
+ description: `Plan file: plans/${plan.source_plan}\nPRD file: prd/${body.file}\n\n` +
2254
+ `Source plan was revised. Compare the updated plan against existing PRD and produce an updated version.\n\n` +
2255
+ `**Current PRD implementation state:**\n${implContext}\n\n` +
2256
+ `**Rules for updating:**\n` +
2257
+ `- Items that are done and unchanged in the plan → keep status "done" (preserve their ID)\n` +
2258
+ `- Items that are done but modified in the plan (new requirements) → set status "updated" (engine will re-open)\n` +
2259
+ `- New items in the plan set status "missing" (engine will materialize)\n` +
2260
+ `- Items removed from the plan drop from PRD (engine will cancel pending WIs)\n` +
2261
+ `- Preserve all existing item IDs for unchanged/modified items — do NOT generate new IDs for them`,
2262
+ project: targetProject.name, createdBy: 'dashboard:plan-resume',
2263
+ extra: { _existingPrdFile: body.file },
2264
+ });
2278
2265
  }
2279
2266
  }
2280
2267
 
package/engine/shared.js CHANGED
@@ -587,6 +587,8 @@ const PLAN_STATUS = {
587
587
  PAUSED: 'paused', REJECTED: 'rejected', COMPLETED: 'completed',
588
588
  REVISION_REQUESTED: 'revision-requested',
589
589
  };
590
+ const PRD_ITEM_STATUS = { MISSING: 'missing', UPDATED: 'updated', DONE: 'done' };
591
+ const PRD_MATERIALIZABLE = new Set([PRD_ITEM_STATUS.MISSING, PRD_ITEM_STATUS.UPDATED]);
590
592
  const PR_STATUS = { ACTIVE: 'active', MERGED: 'merged', ABANDONED: 'abandoned', CLOSED: 'closed', LINKED: 'linked' };
591
593
  // PRs eligible for polling (status/build/comment checks) — excludes terminal statuses
592
594
  const PR_POLLABLE_STATUSES = new Set([PR_STATUS.ACTIVE, PR_STATUS.LINKED]);
@@ -605,6 +607,32 @@ function trackReviewMetric(pr, newReviewStatus, config) {
605
607
  });
606
608
  } catch (err) { log('warn', `Metrics update: ${err.message}`); }
607
609
  }
610
+
611
+ /** Queue a plan-to-prd work item with dedup check inside lock. Returns true if queued. */
612
+ function queuePlanToPrd({ planFile, prdFile, title, description, project, createdBy, extra }) {
613
+ const centralWiPath = path.join(__dirname, '..', 'work-items.json');
614
+ let queued = false;
615
+ mutateJsonFileLocked(centralWiPath, items => {
616
+ if (!Array.isArray(items)) items = [];
617
+ if (items.some(w => w.type === 'plan-to-prd' && w.planFile === planFile && (w.status === 'pending' || w.status === 'dispatched'))) return items;
618
+ items.push({
619
+ id: 'W-' + uid(),
620
+ title,
621
+ type: 'plan-to-prd',
622
+ priority: 'high',
623
+ description,
624
+ status: 'pending',
625
+ created: new Date().toISOString(),
626
+ createdBy,
627
+ project,
628
+ planFile,
629
+ ...(extra || {}),
630
+ });
631
+ queued = true;
632
+ return items;
633
+ }, { defaultValue: [] });
634
+ return queued;
635
+ }
608
636
  const DISPATCH_RESULT = { SUCCESS: 'success', ERROR: 'error', TIMEOUT: 'timeout' };
609
637
  const PIPELINE_STATUS = {
610
638
  PENDING: 'pending', RUNNING: 'running', COMPLETED: 'completed',
@@ -1021,7 +1049,7 @@ module.exports = {
1021
1049
  KB_CATEGORIES,
1022
1050
  classifyInboxItem,
1023
1051
  ENGINE_DEFAULTS,
1024
- WI_STATUS, DONE_STATUSES, PLAN_TERMINAL_STATUSES, WORK_TYPE, PLAN_STATUS, PR_STATUS, PR_POLLABLE_STATUSES, DISPATCH_RESULT, trackReviewMetric,
1052
+ WI_STATUS, DONE_STATUSES, PLAN_TERMINAL_STATUSES, WORK_TYPE, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, PR_POLLABLE_STATUSES, DISPATCH_RESULT, trackReviewMetric, queuePlanToPrd,
1025
1053
  PIPELINE_STATUS, STAGE_TYPE, MEETING_STATUS, AGENT_STATUS,
1026
1054
  FAILURE_CLASS, ESCALATION_POLICY, COMPLETION_FIELDS,
1027
1055
  DEFAULT_AGENT_METRICS,
package/engine.js CHANGED
@@ -25,7 +25,7 @@ const fs = require('fs');
25
25
  const path = require('path');
26
26
  const shared = require('./engine/shared');
27
27
  const { exec, execAsync, execSilent, runFile, ts, ENGINE_DEFAULTS: DEFAULTS,
28
- WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, PR_STATUS, DISPATCH_RESULT, AGENT_STATUS } = shared;
28
+ WI_STATUS, DONE_STATUSES, WORK_TYPE, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, DISPATCH_RESULT, AGENT_STATUS } = shared;
29
29
  const queries = require('./engine/queries');
30
30
 
31
31
  // ─── Paths ──────────────────────────────────────────────────────────────────
@@ -1358,26 +1358,12 @@ function materializePlansAsWorkItems(config) {
1358
1358
  const allProjects = getProjects(config);
1359
1359
  const targetProject = allProjects.find(p => p.name?.toLowerCase() === projectName.toLowerCase()) || allProjects[0];
1360
1360
  if (targetProject) {
1361
- const centralWiPath = path.join(MINIONS_DIR, 'work-items.json');
1362
- let queued = false;
1363
- mutateJsonFileLocked(centralWiPath, items => {
1364
- if (!Array.isArray(items)) items = [];
1365
- if (items.some(w => w.type === WORK_TYPE.PLAN_TO_PRD && w.planFile === plan.source_plan && (w.status === WI_STATUS.PENDING || w.status === WI_STATUS.DISPATCHED))) return items;
1366
- items.push({
1367
- id: 'W-' + shared.uid(),
1368
- title: `Regenerate PRD from revised plan: ${plan.source_plan}`,
1369
- type: 'plan-to-prd',
1370
- priority: 'high',
1371
- description: `Plan file: plans/${plan.source_plan}\nSource plan was revised (PRD was ${prdStatus}) — regenerating.${completedContext}`,
1372
- status: 'pending',
1373
- created: ts(),
1374
- createdBy: 'engine:plan-revision',
1375
- project: targetProject.name,
1376
- planFile: plan.source_plan,
1377
- });
1378
- queued = true;
1379
- return items;
1380
- }, { defaultValue: [] });
1361
+ const queued = shared.queuePlanToPrd({
1362
+ planFile: plan.source_plan, prdFile: file,
1363
+ title: `Generate PRD from plan: ${plan.source_plan}`,
1364
+ description: `Plan file: plans/${plan.source_plan}\nSource plan was updated while PRD was awaiting approval — generating fresh PRD.${completedContext}`,
1365
+ project: targetProject.name, createdBy: 'engine:plan-revision',
1366
+ });
1381
1367
  if (queued) log('info', `Queued plan-to-prd regeneration for revised plan ${plan.source_plan} (${completedItems.length} completed items to carry over)`);
1382
1368
  }
1383
1369
  }
@@ -1417,14 +1403,14 @@ function materializePlansAsWorkItems(config) {
1417
1403
  // No project found — use central work-items.json (engine works without projects)
1418
1404
  const useCentral = !defaultProject;
1419
1405
 
1420
- const statusFilter = ['missing', 'planned', 'updated'];
1406
+ const statusFilter = PRD_MATERIALIZABLE;
1421
1407
  // Also materialize in-pr/done items that never got a work item (race with PR status sync)
1422
1408
  const allExistingWiIds = new Set();
1423
1409
  for (const w of queries.getWorkItems()) {
1424
1410
  if (w.id) allExistingWiIds.add(w.id);
1425
1411
  }
1426
1412
  const items = plan.missing_features.filter(f =>
1427
- statusFilter.includes(f.status) ||
1413
+ statusFilter.has(f.status) ||
1428
1414
  (DONE_STATUSES.has(f.status) && f.id && !allExistingWiIds.has(f.id))
1429
1415
  );
1430
1416
 
@@ -1472,9 +1458,9 @@ function materializePlansAsWorkItems(config) {
1472
1458
 
1473
1459
  mutateWorkItems(wiPath, existingItems => {
1474
1460
  for (const item of projItems) {
1475
- // Re-open: PRD item set back to missing/planned/updated but work item is done reset to pending
1461
+ // Re-open: only 'updated' re-opens a done work item (missing = new item, not re-open)
1476
1462
  const existingWi = existingItems.find(w => w.id === item.id);
1477
- const shouldReopen = item.status === 'missing' || item.status === 'planned' || item.status === 'updated';
1463
+ const shouldReopen = item.status === PRD_ITEM_STATUS.UPDATED;
1478
1464
  if (existingWi && DONE_STATUSES.has(existingWi.status) && shouldReopen) {
1479
1465
  existingWi.status = WI_STATUS.PENDING;
1480
1466
  existingWi._reopened = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.829",
3
+ "version": "0.1.831",
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"