@yemi33/minions 0.1.2190 → 0.1.2191

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 CHANGED
@@ -1269,6 +1269,12 @@ function _scanProjectLocalHarnessFootgun(project) {
1269
1269
  encoding: 'utf8',
1270
1270
  stdio: ['ignore', 'pipe', 'ignore'],
1271
1271
  timeout: 10000,
1272
+ // W-mqecdoot — MUST set windowsHide. This runs per-project on every
1273
+ // GET /api/harness/diagnostics hit (Tools/Harness page, polled), and a
1274
+ // detached dashboard has no console — so without this each call pops a
1275
+ // visible git.exe console window per project ("infinite git windows").
1276
+ // This was the only git spawn in the codebase missing windowsHide.
1277
+ windowsHide: true,
1272
1278
  });
1273
1279
  } catch { return out; }
1274
1280
  if (!raw) return out;
package/engine/cleanup.js CHANGED
@@ -637,7 +637,30 @@ async function runCleanup(config, verbose = false) {
637
637
  for (const entry of entries) {
638
638
  if (!entry.isDirectory()) continue;
639
639
  const full = path.resolve(wtRoot, entry.name);
640
+ // Defensive boundary: only ever delete strictly inside the worktree
641
+ // root we enumerated. readdirSync never yields `..`, so this is always
642
+ // true today — but it makes the "never escape wtRoot" invariant
643
+ // explicit and immune to a future refactor of how `full` is derived.
644
+ if (full !== wtRoot && !full.startsWith(wtRoot + path.sep)) continue;
640
645
  if (registered.has(full)) continue;
646
+ // W-mqecdoot — ownership gate, parity with the out-of-root worktree GC
647
+ // (worktree-gc.pruneOrphanWorktreesFromGitRegistry). Only ever reap a
648
+ // dir the engine positively created (carries the `.minions-worktree`
649
+ // marker stamped right after `git worktree add`). A dir with NO marker
650
+ // is a human's hand-made worktree (`git worktree add ../worktrees/mine`)
651
+ // or some unrelated folder dropped into the worktree root — KEEP it,
652
+ // never delete. Fail-open: leak a husk rather than nuke someone's
653
+ // unpushed work. This closes the same data-loss class the marker fixed
654
+ // for the registry sweep, for this on-disk-dir sweep too. The cost is
655
+ // that empty leftover husks whose marker was already removed leak
656
+ // instead of being reaped — an acceptable trade for "absolutely never
657
+ // delete foreign data".
658
+ let owned = false;
659
+ try { owned = !!shared.hasWorktreeOwnerMarker(full); } catch { owned = false; }
660
+ if (!owned) {
661
+ log('debug', `Cleanup: keeping orphan worktree dir ${full} — no engine ownership marker (foreign/hand-made worktree)`);
662
+ continue;
663
+ }
641
664
  let stat; try { stat = fs.statSync(full); } catch { continue; }
642
665
  if (stat.mtimeMs >= _twoHoursAgo) continue;
643
666
  // W-mq5rwwss000f30a7 — even an "orphan" dir (one git doesn't know
@@ -27,6 +27,7 @@ function _execImpl(cmd) {
27
27
  encoding: 'utf8',
28
28
  stdio: ['ignore', 'pipe', 'ignore'],
29
29
  timeout: 5000,
30
+ windowsHide: true,
30
31
  })).trim();
31
32
  } catch {
32
33
  return '';
package/engine/shared.js CHANGED
@@ -7715,6 +7715,37 @@ function removeWorktree(wtPath, gitRoot, worktreeRoot, opts = {}) {
7715
7715
  log('warn', `removeWorktree: refusing to remove ${wtPath} — not under ${worktreeRoot}`);
7716
7716
  return false;
7717
7717
  }
7718
+ // ── Data-loss hardening (W-mqecdoot) — NEVER recursively delete a real git
7719
+ // main repository. ──────────────────────────────────────────────────────
7720
+ // The containment check above only proves `resolved` is *under* the
7721
+ // caller-supplied `worktreeRoot`. Several callers derive that root from
7722
+ // `config.engine.worktreeRoot` (default '../worktrees') — e.g. the cleanup
7723
+ // orphan-reaper and projects.removeProject §3 readdir the root and call us on
7724
+ // every child. A misconfigured `worktreeRoot` like '..' (or a project
7725
+ // localPath placed one level too high) makes a sibling REAL repo pass
7726
+ // containment; `git worktree remove --force` then FAILS (it isn't a linked
7727
+ // worktree) and we fall through to fs.rmSync / `rd /s /q`, nuking the repo
7728
+ // and its history. This is the reported incident (.git + repo dir gone). Two
7729
+ // refusals below, both with zero false positives for legitimate linked-
7730
+ // worktree removal:
7731
+ // 1. The target IS the git root we were handed (deleting the repo itself).
7732
+ // 2. The target carries its own `.git` DIRECTORY. A linked worktree's
7733
+ // `.git` is ALWAYS a FILE (a `gitdir:` pointer); only a main checkout
7734
+ // has a `.git` directory — so this can never block a real worktree wipe.
7735
+ try {
7736
+ if (gitRoot && resolved === path.resolve(String(gitRoot))) {
7737
+ log('warn', `removeWorktree: refusing to remove ${wtPath} — it is the project git root, not a worktree`);
7738
+ return false;
7739
+ }
7740
+ } catch { /* bad gitRoot — fall through to the .git probe */ }
7741
+ try {
7742
+ const st = fs.lstatSync(path.join(resolved, '.git'));
7743
+ if (st && st.isDirectory()) {
7744
+ log('warn', `removeWorktree: refusing to remove ${wtPath} — it is a real git repo (.git is a directory, not a linked-worktree pointer)`);
7745
+ try { bumpWorktreeGcMetric('refusedRealRepo'); } catch { /* metric optional */ }
7746
+ return false;
7747
+ }
7748
+ } catch { /* no .git, or unreadable — normal worktree husk; continue */ }
7718
7749
  // W-mq5rwwss000f30a7 — never wipe a worktree while an agent is actively
7719
7750
  // dispatched inside it. isWorktreePathLive fails OPEN (returns true) when
7720
7751
  // the dispatches table is unreachable, so we err on the side of leaking
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2190",
3
+ "version": "0.1.2191",
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"