@yemi33/minions 0.1.651 → 0.1.652

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,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.652 (2026-04-09)
4
+
5
+ ### Fixes
6
+ - stop retrying locked worktree removal after 3 failures
7
+
3
8
  ## 0.1.651 (2026-04-09)
4
9
 
5
10
  ### Fixes
package/engine/shared.js CHANGED
@@ -303,9 +303,9 @@ function writeToInbox(agentId, slug, content, _inboxDir) {
303
303
  if (existing) return false;
304
304
  const noteId = `NOTE-${uid()}`;
305
305
  // Inject structured ID as YAML frontmatter if content doesn't already have it
306
- const hasFrontmatter = content.trimStart().startsWith('---');
306
+ const hasFrontmatter = /^\s*---[\r\n]/.test(content);
307
307
  const tagged = hasFrontmatter
308
- ? content.replace(/^---[\r\n]+/, `---\nid: ${noteId}\n`)
308
+ ? content.replace(/^\s*---[\r\n]+/, `---\nid: ${noteId}\n`)
309
309
  : `---\nid: ${noteId}\nagent: ${agentId}\ndate: ${dateStamp()}\n---\n\n${content}`;
310
310
  const filePath = path.join(inboxDir, `${prefix}.md`);
311
311
  safeWrite(filePath, tagged);
@@ -825,7 +825,9 @@ function mutatePullRequests(filePath, mutator) {
825
825
  /**
826
826
  * Remove a git worktree, falling back to fs.rmSync if git fails (e.g., locked on Windows).
827
827
  * Only removes directories under worktreeRoot to prevent accidental deletion.
828
+ * Tracks persistent failures to avoid retrying locked paths every cleanup cycle.
828
829
  */
830
+ const _removeWorktreeFailures = new Map(); // path → { count, lastAttempt }
829
831
  function removeWorktree(wtPath, gitRoot, worktreeRoot) {
830
832
  const resolved = path.resolve(wtPath);
831
833
  const resolvedRoot = path.resolve(worktreeRoot) + path.sep;
@@ -833,13 +835,19 @@ function removeWorktree(wtPath, gitRoot, worktreeRoot) {
833
835
  log('warn', `removeWorktree: refusing to remove ${wtPath} — not under ${worktreeRoot}`);
834
836
  return false;
835
837
  }
838
+ // Skip paths that failed 3+ times — retry after 1 hour cooldown
839
+ const prior = _removeWorktreeFailures.get(resolved);
840
+ if (prior && prior.count >= 3 && Date.now() - prior.lastAttempt < 3600000) return false;
841
+
836
842
  try {
837
843
  exec(`git worktree remove "${wtPath}" --force`, { cwd: gitRoot, stdio: 'pipe', timeout: 15000, windowsHide: true });
844
+ _removeWorktreeFailures.delete(resolved);
838
845
  return true;
839
846
  } catch (gitErr) {
840
847
  try {
841
848
  fs.rmSync(resolved, { recursive: true, force: true });
842
849
  try { exec('git worktree prune', { cwd: gitRoot, stdio: 'pipe', timeout: 10000, windowsHide: true }); } catch {}
850
+ _removeWorktreeFailures.delete(resolved);
843
851
  return true;
844
852
  } catch (rmErr) {
845
853
  // Windows EPERM: a process may hold file handles — try rd /s /q as fallback
@@ -847,15 +855,28 @@ function removeWorktree(wtPath, gitRoot, worktreeRoot) {
847
855
  try {
848
856
  exec(`rd /s /q "${resolved}"`, { stdio: 'pipe', timeout: 15000, windowsHide: true });
849
857
  try { exec('git worktree prune', { cwd: gitRoot, stdio: 'pipe', timeout: 10000, windowsHide: true }); } catch {}
858
+ _removeWorktreeFailures.delete(resolved);
850
859
  return true;
851
860
  } catch {}
852
861
  }
853
- log('warn', `removeWorktree: failed for ${wtPath}: ${rmErr.message}`);
862
+ const fail = _removeWorktreeFailures.get(resolved) || { count: 0, lastAttempt: 0 };
863
+ fail.count++;
864
+ fail.lastAttempt = Date.now();
865
+ _removeWorktreeFailures.set(resolved, fail);
866
+ if (fail.count <= 3) log('warn', `removeWorktree: failed for ${wtPath} (attempt ${fail.count}/3): ${rmErr.message}`);
854
867
  return false;
855
868
  }
856
869
  }
857
870
  }
858
871
 
872
+ function slugify(text, maxLen = 50) {
873
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, maxLen);
874
+ }
875
+
876
+ function formatTranscriptEntry(t) {
877
+ return '### ' + (t.agent || 'agent') + ' (' + (t.type || '') + ', Round ' + (t.round || '?') + ')\n\n' + (t.content || '');
878
+ }
879
+
859
880
  module.exports = {
860
881
  MINIONS_DIR,
861
882
  PR_LINKS_PATH,
@@ -912,6 +933,8 @@ module.exports = {
912
933
  removeWorktree,
913
934
  LOCK_STALE_MS,
914
935
  flushLogs,
936
+ slugify,
937
+ formatTranscriptEntry,
915
938
  _logBuffer, // exported for testing
916
939
  };
917
940
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.651",
3
+ "version": "0.1.652",
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"