@yemi33/minions 0.1.2109 → 0.1.2110

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  A fast, hands-on walkthrough for new contributors. Follow the eight steps below in order. Each step is independent enough that you can stop, take a break, and resume without losing state.
4
4
 
5
- > **Prerequisites:** Node.js 18+, Git, and a working Claude Code CLI (`npm install -g @anthropic-ai/claude-code`) authenticated against your Anthropic API key or Claude Max subscription. See the top-level [README.md](../README.md#prerequisites) for the full list.
5
+ > **Prerequisites:** Node.js 22.5+ (required by `package.json#engines` for the `node:sqlite` runtime backing the state DB), Git, and a working Claude Code CLI (`npm install -g @anthropic-ai/claude-code`) authenticated against your Anthropic API key or Claude Max subscription. See the top-level [README.md](../README.md#prerequisites) for the full list.
6
6
 
7
7
  > **Screenshots:** This walkthrough is intentionally text-only on the first pass. If you want annotated dashboard screenshots, run the [`capture-demos`](../README.md#dashboard) skill after Step 5; it drives Playwright over the running dashboard and writes images you can drop alongside each section.
8
8
 
@@ -15,7 +15,7 @@ You only need to clone if you intend to modify Minions itself. End users normall
15
15
  ```bash
16
16
  git clone https://github.com/yemi33/minions.git ~/minions-dev
17
17
  cd ~/minions-dev
18
- npm install # installs dev tooling (Playwright); engine itself has zero deps
18
+ npm install # installs dev tooling (Playwright) + the lone runtime dep (@azure-devops/mcp); engine is otherwise built on Node built-ins
19
19
  ```
20
20
 
21
21
  You should now have:
package/engine.js CHANGED
@@ -877,7 +877,15 @@ async function pruneStaleWorktreeForBranch(rootDir, branchName, gitOpts) {
877
877
  // can recover any unpushed work.
878
878
  // - Otherwise (filesystem-only dirty) reset --hard + clean -fd, then
879
879
  // re-verify.
880
- // 4. Return { clean, healed, reason, dirtyFiles, ahead, behind,
880
+ // 4. W-mpykcky7: when the branch has no upstream AND the worktree is
881
+ // filesystem-clean AND HEAD is at the project main branch tip (caller
882
+ // passes opts.mainBranch), treat as safe-to-reuse — there are no
883
+ // local-only commits to lose. Without this, every reused worktree
884
+ // whose previous agent exited without making any commits gets
885
+ // quarantined in a false-positive loop. Any uncertainty (rev-list
886
+ // error, main missing, commitsAheadOfMain > 0) falls through to the
887
+ // conservative quarantine path.
888
+ // 5. Return { clean, healed, reason, dirtyFiles, ahead, behind,
881
889
  // quarantined, quarantinedPath, backupRef } so the caller can fail
882
890
  // fast with a first-class DIRTY_WORKTREE / WORKTREE_DIRTY /
883
891
  // WORKTREE_DIVERGENT reason and retry semantics.
@@ -955,7 +963,42 @@ async function assertCleanSharedWorktree(rootDir, worktreePath, branchName, disp
955
963
  return result;
956
964
  }
957
965
 
958
- // 4. Unpushed-commit check refuse to reset when local work would be lost.
966
+ // 4. No-upstream-but-at-main safe-path (W-mpykcky7). When the worktree's
967
+ // branch has no upstream (never pushed), the original #2996 gate
968
+ // conservatively assumed unpushed work and quarantined. But if the
969
+ // worktree is filesystem-clean AND HEAD is at the project main branch
970
+ // tip (no local-only commits beyond main), there is nothing to lose —
971
+ // reuse is safe. Common case: a previous agent exited without making
972
+ // any commits, leaving HEAD == origin/<mainBranch>. Falls through to
973
+ // the conservative quarantine path on any uncertainty (rev-list error,
974
+ // main branch missing, commitsAheadOfMain > 0).
975
+ // Invariants preserved from #2996:
976
+ // - filesystemDirty still quarantines (we're past that gate only if
977
+ // statusOut was empty).
978
+ // - upstreamKnown && ahead > 0 still quarantines below (untouched).
979
+ // - other-dispatch-active (above) still returns without quarantine.
980
+ if (!filesystemDirty && !upstreamKnown && opts.mainBranch) {
981
+ let commitsAheadOfMain = null;
982
+ try {
983
+ const r = await execAsync(
984
+ `git rev-list ${opts.mainBranch}..HEAD --count`,
985
+ { ...gitOpts, cwd: worktreePath, timeout: 10000 },
986
+ );
987
+ const parsed = parseInt(String(r || '').trim(), 10);
988
+ if (Number.isFinite(parsed)) commitsAheadOfMain = parsed;
989
+ } catch {
990
+ // rev-list error (main missing locally, fetch failed, etc.) — fall
991
+ // through to the conservative quarantine path. Don't loosen on
992
+ // uncertainty.
993
+ }
994
+ if (commitsAheadOfMain === 0) {
995
+ result.clean = true;
996
+ result.reason = 'no-upstream-but-at-main';
997
+ return result;
998
+ }
999
+ }
1000
+
1001
+ // 5. Unpushed-commit check — refuse to reset when local work would be lost.
959
1002
  // Use the ahead count we already computed (cheaper + correct vs.
960
1003
  // `git log @{u}..HEAD` which needs an upstream config and silently
961
1004
  // returns empty when missing).
@@ -985,7 +1028,7 @@ async function assertCleanSharedWorktree(rootDir, worktreePath, branchName, disp
985
1028
  return result;
986
1029
  }
987
1030
 
988
- // 5. Safe to self-heal (filesystem-dirty only, no unpushed commits, no
1031
+ // 6. Safe to self-heal (filesystem-dirty only, no unpushed commits, no
989
1032
  // other active dispatch): reset + clean.
990
1033
  try {
991
1034
  await execAsync('git reset --hard HEAD', { ...gitOpts, cwd: worktreePath, timeout: 30000 });
@@ -996,7 +1039,7 @@ async function assertCleanSharedWorktree(rootDir, worktreePath, branchName, disp
996
1039
  return result;
997
1040
  }
998
1041
 
999
- // 6. Re-verify
1042
+ // 7. Re-verify
1000
1043
  try {
1001
1044
  const r2 = await execAsync('git status --porcelain', { ...gitOpts, cwd: worktreePath, timeout: 10000 });
1002
1045
  const after = (r2 || '').toString().trim();
@@ -1759,7 +1802,10 @@ async function spawnAgent(dispatchItem, config) {
1759
1802
  // implementation failures and cascading dependent items.
1760
1803
  if (worktreePath && fs.existsSync(worktreePath) && meta?.branchStrategy === 'shared-branch' && branchName) {
1761
1804
  _phaseT.cleanCheckStart = Date.now();
1762
- const cleanResult = await assertCleanSharedWorktree(rootDir, worktreePath, branchName, id, _gitOpts);
1805
+ const cleanResult = await assertCleanSharedWorktree(
1806
+ rootDir, worktreePath, branchName, id, _gitOpts,
1807
+ { mainBranch: shared.resolveMainBranch(rootDir, project.mainBranch) },
1808
+ );
1763
1809
  _phaseT.cleanCheckEnd = Date.now();
1764
1810
  if (!cleanResult.clean) {
1765
1811
  const previewFiles = (cleanResult.dirtyFiles || []).slice(0, 5).join(', ');
@@ -1817,7 +1863,7 @@ async function spawnAgent(dispatchItem, config) {
1817
1863
  _phaseT.dirtyReusedCheckStart = Date.now();
1818
1864
  const cleanResult = await assertCleanSharedWorktree(
1819
1865
  rootDir, worktreePath, branchName, id, _gitOpts,
1820
- { quarantineOnUnsafe: true },
1866
+ { quarantineOnUnsafe: true, mainBranch: shared.resolveMainBranch(rootDir, project.mainBranch) },
1821
1867
  );
1822
1868
  _phaseT.dirtyReusedCheckEnd = Date.now();
1823
1869
  if (!cleanResult.clean) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2109",
3
+ "version": "0.1.2110",
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"