@yemi33/minions 0.1.1103 → 0.1.1105

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.1103 (2026-04-18)
3
+ ## 0.1.1105 (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
+ - annotate fast-exit empty-output failures with diagnostic hint (#1276)
10
+ - invalidate PRD cache on pr-links.json change + guard aggregate PRs (#1220) (#1272)
11
+ - pass --add-dir for minions + ~/.claude to agents (#1271)
12
+ - preserve VERDICT marker by tail-slicing agent output (#1234) (#1270)
9
13
  - PRD info cache staleness and aggregate PR bleed-through (#1222)
10
14
  - avoid no-op work item writes
11
15
  - resilient claude binary resolution + surface spawn errors
@@ -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/llm.js CHANGED
@@ -149,7 +149,9 @@ function _createStreamAccumulator({
149
149
  if (obj.session_id) sessionId = obj.session_id;
150
150
  if (obj.type === 'result') {
151
151
  if (typeof obj.result === 'string') {
152
- text = maxTextLength ? obj.result.slice(0, maxTextLength) : obj.result;
152
+ // Tail-slice: VERDICTs, completion blocks, and PR URLs live at the END
153
+ // of agent output. Head-slicing dropped them (#1234).
154
+ text = maxTextLength ? obj.result.slice(-maxTextLength) : obj.result;
153
155
  }
154
156
  if (obj.total_cost_usd || obj.usage) {
155
157
  usage = {
@@ -166,7 +168,8 @@ function _createStreamAccumulator({
166
168
  if (obj.type === 'assistant' && Array.isArray(obj.message?.content)) {
167
169
  for (const block of obj.message.content) {
168
170
  if (block?.type === 'text' && block.text) {
169
- text = maxTextLength ? block.text.slice(0, maxTextLength) : block.text;
171
+ // Tail-slice for consistency with the result branch (see #1234).
172
+ text = maxTextLength ? block.text.slice(-maxTextLength) : block.text;
170
173
  if (onChunk && block.text !== lastTextSent) {
171
174
  lastTextSent = block.text;
172
175
  onChunk(block.text);
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);
package/engine/shared.js CHANGED
@@ -608,7 +608,11 @@ function parseStreamJsonOutput(raw, { maxTextLength = 0 } = {}) {
608
608
 
609
609
  function extractResult(obj) {
610
610
  if (obj.type !== 'result') return false;
611
- if (obj.result) text = maxTextLength ? obj.result.slice(0, maxTextLength) : obj.result;
611
+ // Slice from the tail, not the head — review VERDICTs, structured completion
612
+ // blocks, PR URLs, and agent conclusions all appear at the END of the output.
613
+ // Head-slicing truncated VERDICTs and caused review work items to be
614
+ // re-dispatched up to maxRetries times despite successful completion (#1234).
615
+ if (obj.result) text = maxTextLength ? obj.result.slice(-maxTextLength) : obj.result;
612
616
  if (obj.session_id) sessionId = obj.session_id;
613
617
  if (obj.total_cost_usd || obj.usage) {
614
618
  usage = {
@@ -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.1103",
3
+ "version": "0.1.1105",
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"