@yemi33/minions 0.1.563 → 0.1.565

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,11 +1,15 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.563 (2026-04-08)
3
+ ## 0.1.565 (2026-04-08)
4
4
 
5
5
  ### Fixes
6
+ - worktree audit followups — delete ordering, path traversal guard
6
7
  - worktree lifecycle — fail on dep merge, fallback rm, cleanup on plan delete
7
8
  - auto-reset CC session on resume failure, add error logging
8
9
 
10
+ ### Other
11
+ - perf: cache claude binary path across spawns in spawn-agent.js
12
+
9
13
  ## 0.1.561 (2026-04-08)
10
14
 
11
15
  ### Fixes
package/dashboard.js CHANGED
@@ -2392,11 +2392,17 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
2392
2392
  shared.sanitizePath(body.file, body.file.endsWith('.json') ? PRD_DIR : PLANS_DIR);
2393
2393
  const planPath = resolvePlanPath(body.file);
2394
2394
  if (!fs.existsSync(planPath)) return jsonReply(res, 404, { error: 'plan not found' });
2395
- // Read PRD content before deleting to get source_plan for cleanup
2395
+ // Read plan content before deleting needed for worktree cleanup and source_plan
2396
+ let planObj = null;
2396
2397
  let prdSourcePlan = null;
2397
2398
  if (body.file.endsWith('.json')) {
2398
- try { prdSourcePlan = safeJsonObj(planPath).source_plan || null; } catch {}
2399
+ try { planObj = safeJsonObj(planPath); prdSourcePlan = planObj?.source_plan || null; } catch {}
2399
2400
  }
2401
+ // Clean up worktrees before deleting work items (needs branch info from work items)
2402
+ try {
2403
+ const { cleanupPlanWorktrees } = require('./engine/lifecycle');
2404
+ cleanupPlanWorktrees(body.file, planObj || {}, PROJECTS, getConfig());
2405
+ } catch (e) { console.error('plan worktree cleanup:', e.message); }
2400
2406
  safeUnlink(planPath);
2401
2407
 
2402
2408
  // Clean up materialized work items from all projects + central
@@ -2439,13 +2445,6 @@ If nothing to do: { "duplicates": [], "reclassify": [], "remove": [] }`;
2439
2445
  } catch (e) { console.error('plan-to-prd cleanup:', e.message); }
2440
2446
  }
2441
2447
 
2442
- // Clean up worktrees associated with this plan
2443
- try {
2444
- const plan = body.file.endsWith('.json') ? (safeJsonObj(path.join(PRD_DIR, 'archive', body.file)) || safeJsonObj(path.join(PRD_DIR, body.file)) || {}) : {};
2445
- const { cleanupPlanWorktrees } = require('./engine/lifecycle');
2446
- cleanupPlanWorktrees(body.file, plan, PROJECTS, getConfig());
2447
- } catch (e) { console.error('plan worktree cleanup:', e.message); }
2448
-
2449
2448
  invalidateStatusCache();
2450
2449
  return jsonReply(res, 200, { ok: true, cleanedWorkItems: cleaned, cleanedDispatches: dispatchCleaned });
2451
2450
  } catch (e) { return jsonReply(res, 400, { error: e.message }); }
package/engine/shared.js CHANGED
@@ -808,7 +808,7 @@ function mutatePullRequests(filePath, mutator) {
808
808
  */
809
809
  function removeWorktree(wtPath, gitRoot, worktreeRoot) {
810
810
  const resolved = path.resolve(wtPath);
811
- const resolvedRoot = path.resolve(worktreeRoot);
811
+ const resolvedRoot = path.resolve(worktreeRoot) + path.sep;
812
812
  if (!resolved.startsWith(resolvedRoot)) {
813
813
  log('warn', `removeWorktree: refusing to remove ${wtPath} — not under ${worktreeRoot}`);
814
814
  return false;
@@ -25,9 +25,21 @@ const env = cleanChildEnv();
25
25
  // Resolve claude binary — supports both npm install (cli.js) and native installer (binary on PATH)
26
26
  let claudeBin;
27
27
  let claudeIsNative = false; // true = native binary, false = node cli.js
28
+ const capsCachePath = path.join(__dirname, 'claude-caps.json');
29
+ let _sysPromptFileSupported = null;
28
30
 
29
- // Strategy 1: Check if `claude` is on PATH (native installer or npm global bin)
31
+ // Fast path: use cached binary path if it still exists on disk
30
32
  try {
33
+ const caps = JSON.parse(fs.readFileSync(capsCachePath, 'utf8'));
34
+ if (caps.claudeBin && fs.existsSync(caps.claudeBin)) {
35
+ claudeBin = caps.claudeBin;
36
+ claudeIsNative = !!caps.claudeIsNative;
37
+ _sysPromptFileSupported = caps.sysPromptFile ?? null;
38
+ }
39
+ } catch {}
40
+
41
+ // Strategy 1: Check if `claude` is on PATH (native installer or npm global bin)
42
+ if (!claudeBin) try {
31
43
  const isWin = process.platform === 'win32';
32
44
  const cmd = isWin ? 'where claude 2>NUL' : 'which claude 2>/dev/null';
33
45
  const which = exec(cmd, { encoding: 'utf8', env, timeout: 10000 }).trim().split('\n')[0].trim();
@@ -79,11 +91,11 @@ if (!claudeBin) {
79
91
  } catch { /* optional */ }
80
92
  }
81
93
 
82
- // Debug log
94
+ // Debug log (async — not on critical path)
83
95
  const tmpDir = path.join(__dirname, 'tmp');
84
96
  if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
85
97
  const debugPath = path.join(tmpDir, 'spawn-debug.log');
86
- fs.writeFileSync(debugPath, `spawn-agent.js at ${ts()}\nclaudeBin=${claudeBin || 'not found'}\nnative=${claudeIsNative}\nprompt=${promptFile}\nsysPrompt=${sysPromptFile}\nextraArgs=${extraArgs.join(' ')}\n`);
98
+ fs.writeFile(debugPath, `spawn-agent.js at ${ts()}\nclaudeBin=${claudeBin || 'not found'}\nnative=${claudeIsNative}\nprompt=${promptFile}\nsysPrompt=${sysPromptFile}\nextraArgs=${extraArgs.join(' ')}\n`, () => {});
87
99
 
88
100
  // When resuming a session, skip system prompt (it's baked into the session)
89
101
  const isResume = extraArgs.includes('--resume');
@@ -104,14 +116,8 @@ if (!claudeBin) {
104
116
  process.exit(78); // 78 = configuration error (distinct from runtime failures)
105
117
  }
106
118
 
107
- // Check if --system-prompt-file is supported (cached to avoid spawning claude --help every call)
119
+ // Check if --system-prompt-file is supported (cached alongside binary path above)
108
120
  let actualArgs = cliArgs;
109
- const capsCachePath = path.join(__dirname, 'claude-caps.json');
110
- let _sysPromptFileSupported = null;
111
- try {
112
- const caps = JSON.parse(fs.readFileSync(capsCachePath, 'utf8'));
113
- if (caps.claudeBin === claudeBin) _sysPromptFileSupported = caps.sysPromptFile;
114
- } catch {}
115
121
  if (_sysPromptFileSupported === null) {
116
122
  try {
117
123
  const { spawnSync } = require('child_process');
@@ -119,8 +125,9 @@ if (_sysPromptFileSupported === null) {
119
125
  ? spawnSync(claudeBin, ['--help'], { encoding: 'utf8', timeout: 10000, windowsHide: true })
120
126
  : spawnSync(process.execPath, [claudeBin, '--help'], { encoding: 'utf8', timeout: 10000, windowsHide: true });
121
127
  _sysPromptFileSupported = (testResult.stdout || '').includes('system-prompt-file');
122
- try { fs.writeFileSync(capsCachePath, JSON.stringify({ claudeBin, sysPromptFile: _sysPromptFileSupported, checkedAt: ts() })); } catch { /* optional */ }
123
128
  } catch { _sysPromptFileSupported = true; /* assume supported */ }
129
+ // Save binary path + capability flag together
130
+ try { fs.writeFileSync(capsCachePath, JSON.stringify({ claudeBin, claudeIsNative, sysPromptFile: _sysPromptFileSupported, checkedAt: ts() })); } catch {}
124
131
  }
125
132
  if (!isResume) try {
126
133
  if (!_sysPromptFileSupported) {
@@ -147,7 +154,7 @@ const proc = claudeIsNative
147
154
  ? runFile(claudeBin, actualArgs, { stdio: ['pipe', 'pipe', 'pipe'], env })
148
155
  : runFile(process.execPath, [claudeBin, ...actualArgs], { stdio: ['pipe', 'pipe', 'pipe'], env });
149
156
 
150
- fs.appendFileSync(debugPath, `PID=${proc.pid || 'none'}\nargs=${actualArgs.join(' ').slice(0, 500)}\n`);
157
+ fs.appendFile(debugPath, `PID=${proc.pid || 'none'}\nargs=${actualArgs.join(' ').slice(0, 500)}\n`, () => {});
151
158
 
152
159
  // Write PID file for parent engine to verify spawn
153
160
  const pidFile = promptFile.replace(/prompt-/, 'pid-').replace(/\.md$/, '.pid');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.563",
3
+ "version": "0.1.565",
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"