@yemi33/minions 0.1.1104 → 0.1.1106

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.1104 (2026-04-18)
3
+ ## 0.1.1106 (2026-04-18)
4
4
 
5
5
  ### Features
6
6
  - seed realActivityMap at spawn time, stamp pid in live-output (#1200)
7
7
 
8
8
  ### Fixes
9
+ - preserve buildErrorLog through transient build states (#1232) (#1274)
10
+ - annotate fast-exit empty-output failures with diagnostic hint (#1276)
11
+ - invalidate PRD cache on pr-links.json change + guard aggregate PRs (#1220) (#1272)
12
+ - pass --add-dir for minions + ~/.claude to agents (#1271)
9
13
  - preserve VERDICT marker by tail-slicing agent output (#1234) (#1270)
10
14
  - PRD info cache staleness and aggregate PR bleed-through (#1222)
11
15
  - avoid no-op work item writes
package/engine/ado.js CHANGED
@@ -465,6 +465,11 @@ async function pollPrStatus(config) {
465
465
  } catch (e) { log('warn', `ADO build query for ${pr.id}: ${e.message}`); }
466
466
  }
467
467
 
468
+ // Record actual poll time — makes lastBuildCheck reflect when the engine last
469
+ // talked to ADO, not when the agent was dispatched. Issue #1233.
470
+ pr.lastBuildCheck = ts();
471
+ updated = true;
472
+
468
473
  if (pr.buildStatus !== buildStatus) {
469
474
  log('info', `PR ${pr.id} build: ${pr.buildStatus || 'none'} → ${buildStatus}${buildFailReason ? ' (' + buildFailReason + ')' : ''}`);
470
475
  pr.buildStatus = buildStatus;
@@ -475,9 +480,17 @@ async function pollPrStatus(config) {
475
480
  if (buildStatus === 'failing') delete pr._autoCompleted;
476
481
  if (buildStatus !== 'failing') {
477
482
  delete pr._buildFailNotified;
478
- delete pr.buildErrorLog;
479
- // Reset build fix retry counter on recovery allows fresh auto-fix cycles if build breaks again
480
- if (pr.buildFixAttempts) { delete pr.buildFixAttempts; delete pr.buildFixEscalated; }
483
+ // Preserve buildErrorLog + buildFixAttempts through transient 'none'/'running'
484
+ // transitions only clear on confirmed 'passing' recovery. Issue #1232: 'none'
485
+ // can also occur when ADO recomputes the merge commit after a target-branch
486
+ // update but no new builds have been triggered yet (filter by sourceVersion
487
+ // returns []), which previously wiped the last known error log and caused
488
+ // fix agents to be dispatched blind.
489
+ if (buildStatus === 'passing') {
490
+ delete pr.buildErrorLog;
491
+ // Reset build fix retry counter on recovery — allows fresh auto-fix cycles if build breaks again
492
+ if (pr.buildFixAttempts) { delete pr.buildFixAttempts; delete pr.buildFixEscalated; }
493
+ }
481
494
  }
482
495
  updated = true;
483
496
 
package/engine/github.js CHANGED
@@ -422,6 +422,10 @@ async function pollPrStatus(config) {
422
422
  if (prData.state === 'open' && prData.head?.sha) {
423
423
  const checksData = await ghApi(`/commits/${prData.head.sha}/check-runs`, slug);
424
424
  if (checksData && checksData.check_runs) {
425
+ // Record actual poll time — makes lastBuildCheck reflect when the engine last
426
+ // talked to GitHub, not when the agent was dispatched. Issue #1233.
427
+ pr.lastBuildCheck = ts();
428
+ updated = true;
425
429
  const runs = checksData.check_runs;
426
430
  let buildStatus = 'none';
427
431
  let buildFailReason = '';
@@ -452,9 +456,15 @@ async function pollPrStatus(config) {
452
456
  if (buildStatus === 'failing') delete pr._autoCompleted; // allow re-merge after fix
453
457
  if (buildStatus !== 'failing') {
454
458
  delete pr._buildFailNotified;
455
- delete pr.buildErrorLog;
456
- // Reset build fix retry counter on recovery allows fresh auto-fix cycles if build breaks again
457
- if (pr.buildFixAttempts) { delete pr.buildFixAttempts; delete pr.buildFixEscalated; }
459
+ // Preserve buildErrorLog + buildFixAttempts through transient 'none'/'running'
460
+ // transitions only clear on confirmed 'passing' recovery. Issue #1232:
461
+ // clearing on every non-failing transition blinded the next fix agent
462
+ // while a queued build was still running.
463
+ if (buildStatus === 'passing') {
464
+ delete pr.buildErrorLog;
465
+ // Reset build fix retry counter on recovery — allows fresh auto-fix cycles if build breaks again
466
+ if (pr.buildFixAttempts) { delete pr.buildFixAttempts; delete pr.buildFixEscalated; }
467
+ }
458
468
  }
459
469
  updated = true;
460
470
 
@@ -2118,6 +2118,28 @@ function classifyFailure(code, stdout = '', stderr = '') {
2118
2118
  return FAILURE_CLASS.UNKNOWN;
2119
2119
  }
2120
2120
 
2121
+ /**
2122
+ * When a claude CLI agent exits in <3s with code 1 and no output, the raw
2123
+ * EMPTY_OUTPUT class tells us "no meaningful output" but nothing about WHY.
2124
+ * This helper detects the fast-exit pattern and returns a diagnostic hint
2125
+ * listing likely root causes (machine sleep, network loss, auth failure).
2126
+ *
2127
+ * The hint is propagated as the dispatch `reason` and into the work-item
2128
+ * `failReason`, so humans and other agents can triage without digging into
2129
+ * `agents/*\/output-<id>.log`.
2130
+ *
2131
+ * @param {string|undefined} failureClass — one of FAILURE_CLASS values
2132
+ * @param {number} code — process exit code
2133
+ * @param {number} elapsedMs — milliseconds between spawn and close
2134
+ * @returns {string|null} — annotated hint, or null when the pattern doesn't match
2135
+ */
2136
+ function diagnoseEmptyOutput(failureClass, code, elapsedMs) {
2137
+ if (failureClass !== FAILURE_CLASS.EMPTY_OUTPUT) return null;
2138
+ if (code !== 1) return null;
2139
+ if (!Number.isFinite(elapsedMs) || elapsedMs < 0 || elapsedMs >= 3000) return null;
2140
+ return `[empty-output: process exited in ${elapsedMs}ms \u2014 possible causes: machine sleep, network unavailability, auth failure]`;
2141
+ }
2142
+
2121
2143
  module.exports = {
2122
2144
  checkPlanCompletion,
2123
2145
  archivePlan,
@@ -2142,6 +2164,7 @@ module.exports = {
2142
2164
  resolveWorkItemPath,
2143
2165
  isItemCompleted,
2144
2166
  classifyFailure,
2167
+ diagnoseEmptyOutput,
2145
2168
  processPendingRebases,
2146
2169
  findDependentActivePrs,
2147
2170
  };
package/engine/queries.js CHANGED
@@ -1028,7 +1028,7 @@ function getPrdInfo(config) {
1028
1028
  const pr = prById[prId];
1029
1029
  // Skip aggregate / E2E PRs from per-item mapping — they link to multiple items
1030
1030
  // (or are typed as verify) and would bleed through as duplicate entries on every
1031
- // constituent item. They are surfaced via renderE2eSection instead.
1031
+ // constituent item. They are surfaced via renderE2eSection instead. (#1220)
1032
1032
  if ((itemIds || []).length > 1 || pr?.itemType === 'verify' || pr?.title?.startsWith('[E2E]')) continue;
1033
1033
  const project = projects.find(p => p.name === pr?._project) || projects[0] || null;
1034
1034
  const prNumber = shared.getPrNumber(pr || prId);
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  const fs = require('fs');
10
+ const os = require('os');
10
11
  const path = require('path');
11
12
  const { exec, runFile, cleanChildEnv, killGracefully, killImmediate, ts, safeJson, safeWrite } = require('./shared');
12
13
 
@@ -103,16 +104,27 @@ if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
103
104
  const debugPath = path.join(tmpDir, 'spawn-debug.log');
104
105
  fs.writeFile(debugPath, `spawn-agent.js at ${ts()}\nclaudeBin=${claudeBin || 'not found'}\nnative=${claudeIsNative}\nprompt=${promptFile}\nsysPrompt=${sysPromptFile}\nextraArgs=${extraArgs.join(' ')}\n`, () => {});
105
106
 
107
+ // Skill discovery dirs — agents run with CWD set to an external repo worktree,
108
+ // so skills in the minions repo and the user's global ~/.claude dir are otherwise
109
+ // invisible. Pass them via --add-dir on every invocation (resume included, since
110
+ // each resume spawns a fresh CLI process with the same CWD). See issue #1231.
111
+ const minionsDir = path.resolve(__dirname, '..');
112
+ const userClaudeDir = path.join(os.homedir(), '.claude');
113
+ const addDirArgs = ['--add-dir', minionsDir];
114
+ if (fs.existsSync(userClaudeDir) && path.resolve(userClaudeDir) !== path.resolve(minionsDir)) {
115
+ addDirArgs.push('--add-dir', userClaudeDir);
116
+ }
117
+
106
118
  // When resuming a session, skip system prompt (it's baked into the session)
107
119
  const isResume = extraArgs.includes('--resume');
108
120
  const sysTmpPath = sysPromptFile + '.tmp';
109
121
  let cliArgs;
110
122
  if (isResume) {
111
- cliArgs = ['-p', ...extraArgs];
123
+ cliArgs = ['-p', ...addDirArgs, ...extraArgs];
112
124
  } else {
113
125
  // Pass system prompt via file to avoid ENAMETOOLONG on Windows (32KB arg limit)
114
126
  fs.writeFileSync(sysTmpPath, sysPrompt);
115
- cliArgs = ['-p', '--system-prompt-file', sysTmpPath, ...extraArgs];
127
+ cliArgs = ['-p', '--system-prompt-file', sysTmpPath, ...addDirArgs, ...extraArgs];
116
128
  }
117
129
 
118
130
  if (!claudeBin) {
package/engine.js CHANGED
@@ -138,7 +138,7 @@ const { renderPlaybook, validatePlaybookVars, PLAYBOOK_REQUIRED_VARS,
138
138
  const { runPostCompletionHooks, updateWorkItemStatus, syncPrdItemStatus, reconcilePrdStatuses, handlePostMerge, checkPlanCompletion,
139
139
  syncPrsFromOutput, updatePrAfterReview, updatePrAfterFix, checkForLearnings, extractSkillsFromOutput,
140
140
  updateAgentHistory, updateMetrics, createReviewFeedbackForAuthor, parseAgentOutput, syncPrdFromPrs,
141
- isItemCompleted, classifyFailure, processPendingRebases, resolveWorkItemPath } = require('./engine/lifecycle');
141
+ isItemCompleted, classifyFailure, diagnoseEmptyOutput, processPendingRebases, resolveWorkItemPath } = require('./engine/lifecycle');
142
142
 
143
143
  // ─── Agent Spawner ──────────────────────────────────────────────────────────
144
144
 
@@ -1231,6 +1231,13 @@ async function spawnAgent(dispatchItem, config) {
1231
1231
  let errorReason = '';
1232
1232
  if (effectiveResult === DISPATCH_RESULT.ERROR) {
1233
1233
  errorReason = stderr.split('\n').filter(l => l.trim()).slice(-5).join(' | ').trim().slice(0, 300);
1234
+ // W-mo3zul9pirjb — when claude CLI exits in <3s with code 1 and no output (the
1235
+ // "silent crash" pattern seen during scheduled tasks when the box went to sleep
1236
+ // or lost network), prepend a diagnostic hint so failReason/dispatch log carry
1237
+ // the actual elapsed time and likely root causes rather than a bare "[empty-output]".
1238
+ const elapsedMs = Date.now() - Date.parse(startedAt);
1239
+ const hint = diagnoseEmptyOutput(failureClass, code, elapsedMs);
1240
+ if (hint) errorReason = errorReason ? `${hint} ${errorReason}` : hint;
1234
1241
  }
1235
1242
  completeDispatch(id, effectiveResult, errorReason, resultSummary, completeOpts);
1236
1243
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1104",
3
+ "version": "0.1.1106",
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"