@yemi33/minions 0.1.545 → 0.1.547

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,12 +1,18 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.545 (2026-04-07)
3
+ ## 0.1.547 (2026-04-07)
4
+
5
+ ### Fixes
6
+ - remap sequential PRD item IDs to prevent cross-PRD collisions (#474)
7
+
8
+ ## 0.1.546 (2026-04-07)
4
9
 
5
10
  ### Features
6
11
  - add DEFAULT_AGENT_METRICS constant to shared.js (#464)
7
12
  - remove inline require('fs') calls from engine/shared.js (#463)
8
13
 
9
14
  ### Fixes
15
+ - make restart reliably kill orphan processes on all OS
10
16
  - make discoverFromPrs/discoverWork async to match await usage
11
17
  - await checkLiveReviewStatus in pre-dispatch vote check (closes #462) (#469)
12
18
 
package/bin/minions.js CHANGED
@@ -39,7 +39,7 @@ function killByPort(port) {
39
39
  const pid = line.trim().split(/\s+/).pop();
40
40
  if (pid && /^\d+$/.test(pid) && pid !== '0' && pid !== String(process.pid)) pids.add(pid);
41
41
  }
42
- for (const pid of pids) try { process.kill(parseInt(pid)); } catch {}
42
+ for (const pid of pids) try { execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore', timeout: 5000, windowsHide: true }); } catch {}
43
43
  } else {
44
44
  execSync(`lsof -ti:${port} | xargs kill -9 2>/dev/null`, { timeout: 5000 });
45
45
  }
@@ -54,7 +54,7 @@ function killMinionsProcesses(patterns) {
54
54
  for (const line of out.split('\n')) {
55
55
  if (patterns.some(p => line.includes(p))) {
56
56
  const pid = line.split(',').pop()?.trim();
57
- if (pid && /^\d+$/.test(pid) && pid !== String(process.pid)) try { process.kill(parseInt(pid)); } catch {}
57
+ if (pid && /^\d+$/.test(pid) && pid !== String(process.pid)) try { execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore', timeout: 5000, windowsHide: true }); } catch {}
58
58
  }
59
59
  }
60
60
  } else {
@@ -529,10 +529,11 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
529
529
  } else if (cmd === 'restart') {
530
530
  // Start both engine and dashboard — the go-to command after a reboot
531
531
  ensureInstalled();
532
- // Stop engine if running
532
+ // Stop engine if running (graceful attempt)
533
533
  try { execSync(`node "${path.join(MINIONS_HOME, 'engine.js')}" stop`, { stdio: 'ignore', cwd: MINIONS_HOME }); } catch {}
534
- // Kill existing dashboard — port-based is reliable across all setups
534
+ // Kill all existing engine/dashboard processes handles crashed engines and orphan dashboards
535
535
  killByPort(7331);
536
+ killMinionsProcesses(['engine.js', 'dashboard.js']);
536
537
  const engineProc = spawn(process.execPath, [path.join(MINIONS_HOME, 'engine.js'), 'start'], {
537
538
  cwd: MINIONS_HOME, stdio: 'ignore', detached: true, windowsHide: true
538
539
  });
package/engine.js CHANGED
@@ -1077,10 +1077,39 @@ function materializePlansAsWorkItems(config) {
1077
1077
  let planFiles;
1078
1078
  try { planFiles = fs.readdirSync(PRD_DIR).filter(f => f.endsWith('.json')); } catch { return; }
1079
1079
 
1080
+ // Regex for detecting sequential PRD item IDs (P-001, P-002) — hoisted outside loop
1081
+ const SEQUENTIAL_ID_RE = /^P-?\d+$/;
1082
+
1080
1083
  for (const file of planFiles) {
1081
1084
  const plan = safeJson(path.join(PRD_DIR, file));
1082
1085
  if (!plan?.missing_features) continue;
1083
1086
 
1087
+ // ID collision prevention: remap sequential IDs (P-001, P-002) to globally unique P-<uid> IDs.
1088
+ // Agents are instructed to use P-<uuid> format but sometimes generate sequential IDs,
1089
+ // which collide across PRDs causing phantom done/dispatched status (see #466).
1090
+ try {
1091
+ if (plan.missing_features.some(f => SEQUENTIAL_ID_RE.test(f.id))) {
1092
+ const allWorkItems = queries.getWorkItems(config);
1093
+ const anyMaterialized = plan.missing_features.some(f =>
1094
+ SEQUENTIAL_ID_RE.test(f.id) && allWorkItems.some(w => w.id === f.id && w.sourcePlan === file));
1095
+ if (!anyMaterialized) {
1096
+ const idMap = new Map();
1097
+ for (const f of plan.missing_features) {
1098
+ if (SEQUENTIAL_ID_RE.test(f.id)) {
1099
+ const newId = 'P-' + shared.uid();
1100
+ idMap.set(f.id, newId);
1101
+ f.id = newId;
1102
+ }
1103
+ }
1104
+ for (const f of plan.missing_features) {
1105
+ if (f.depends_on) f.depends_on = f.depends_on.map(d => idMap.get(d) || d);
1106
+ }
1107
+ safeWrite(path.join(PRD_DIR, file), plan);
1108
+ log('info', `Remapped ${idMap.size} sequential ID(s) in ${file} to prevent cross-PRD collisions`);
1109
+ }
1110
+ }
1111
+ } catch (e) { log('warn', `Sequential ID remapping failed for ${file}: ${e.message}`); }
1112
+
1084
1113
  // Plan staleness: if source_plan .md was modified since last sync, auto-clean and re-sync
1085
1114
  if (plan.source_plan) {
1086
1115
  const sourcePlanPath = path.join(PLANS_DIR, plan.source_plan);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.545",
3
+ "version": "0.1.547",
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"