@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 +6 -0
- package/engine/cleanup.js +23 -0
- package/engine/operator-identity.js +1 -0
- package/engine/shared.js +31 -0
- package/package.json +1 -1
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
|
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.
|
|
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"
|