@yemi33/minions 0.1.2242 → 0.1.2243

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.
@@ -125,20 +125,25 @@ async function prepareLiveCheckout(opts = {}) {
125
125
  const exists = (typeof _exists === 'function') ? _exists : fs.existsSync;
126
126
  const baseOpts = { cwd: localPath, ...(gitOpts || {}) };
127
127
 
128
- // ── Step 1: git status --porcelain. Bail early on dirty tree. ─────────
129
- // Porcelain format begins each line with a two-char XY status code (e.g.
128
+ // ── Step 1: git status --porcelain=v1 -b. Bail early on dirty tree. ─────
129
+ // Porcelain v1 -b adds a `## <branch>` header line as the first output line
130
+ // so callers get branch diagnostics for free alongside the file-status lines.
131
+ // The file-status lines still use the two-char XY status code (e.g.
130
132
  // " M file.js", "?? new.js", "MM stage+unstage.js"). The leading space on
131
133
  // unstaged-only lines is SIGNIFICANT, so we split first and strip ONLY
132
- // trailing whitespace/CR per line — outer-trimming the whole blob would
133
- // eat the leading XY space on the first line.
134
- const statusRaw = await git(['status', '--porcelain'], baseOpts);
134
+ // trailing whitespace/CR per line — outer-trimming the whole blob would eat
135
+ // the leading XY space on the first line. The `## ` header is separated out
136
+ // before the dirtyFiles check so callers receive it as `branchInfo`.
137
+ const statusRaw = await git(['status', '--porcelain=v1', '-b'], baseOpts);
135
138
  const statusStr = typeof statusRaw === 'string' ? statusRaw : '';
136
- const dirtyFiles = statusStr
139
+ const statusLines = statusStr
137
140
  .split(/\r?\n/)
138
141
  .map((line) => line.replace(/\s+$/, ''))
139
142
  .filter((line) => line.length > 0);
143
+ const branchInfo = statusLines.find((line) => line.startsWith('## ')) || '';
144
+ const dirtyFiles = statusLines.filter((line) => !line.startsWith('## '));
140
145
  if (dirtyFiles.length > 0) {
141
- return { ok: false, reason: 'dirty', dirtyFiles };
146
+ return { ok: false, reason: 'dirty', dirtyFiles, branchInfo };
142
147
  }
143
148
 
144
149
  // ── Step 2: mid-operation / detached-HEAD preflight (P-b2e8d4a6). ──────
package/engine.js CHANGED
@@ -2359,6 +2359,14 @@ async function spawnAgent(dispatchItem, config) {
2359
2359
  }
2360
2360
  if (_liveResult && _liveResult.ok === false && _liveResult.reason === 'dirty') {
2361
2361
  const _dirtyFiles = Array.isArray(_liveResult.dirtyFiles) ? _liveResult.dirtyFiles : [];
2362
+ const _branchInfo = typeof _liveResult.branchInfo === 'string' ? _liveResult.branchInfo : '';
2363
+ // #329: Check if this is a retry after a prior dirty failure. If the WI
2364
+ // already carries `_pendingReason:'live_checkout_dirty'` from a previous
2365
+ // dispatch, the dirty state is persistent (user-owned) and we mark it
2366
+ // non-retryable. First-attempt dirty failures are retried once so a
2367
+ // transient or engine-owned dirty state can clear before permanently
2368
+ // failing the plan item.
2369
+ const _alreadyDirtyFailed = dispatchItem.meta?.item?._pendingReason === 'live_checkout_dirty';
2362
2370
  const _alertBody = [
2363
2371
  '# Live-checkout refused: dirty worktree',
2364
2372
  '',
@@ -2367,16 +2375,19 @@ async function spawnAgent(dispatchItem, config) {
2367
2375
  `**Branch:** ${branchName}`,
2368
2376
  `**Work item:** ${_wiIdForAlert}`,
2369
2377
  `**Dispatch:** ${id}`,
2378
+ ...(_branchInfo ? ['', `**Current checkout:** \`${_branchInfo}\``] : []),
2370
2379
  '',
2371
2380
  `The engine refused to spawn the agent because \`${cwd}\` has uncommitted changes. Live-checkout mode runs in your project directly — the engine never \`git reset\` or \`git clean\` your tree.`,
2372
2381
  '',
2373
- '## Dirty files (`git status --porcelain`)',
2382
+ '## Dirty files (`git status --porcelain=v1 -b`)',
2374
2383
  '',
2375
2384
  '```',
2376
2385
  ...(_dirtyFiles.length > 0 ? _dirtyFiles : ['(none reported)']),
2377
2386
  '```',
2378
2387
  '',
2379
- 'Recovery: commit, stash, or discard the changes in the project checkout, then re-dispatch the work item.',
2388
+ _alreadyDirtyFailed
2389
+ ? 'Recovery: dirty state persisted across retries — commit, stash, or discard the changes in the project checkout, then re-dispatch the work item.'
2390
+ : 'This dispatch will be retried once automatically in case the dirty state is transient. If dirty again on retry, commit, stash, or discard the changes, then re-dispatch.',
2380
2391
  ].join('\n');
2381
2392
  try { writeInboxAlert(`live-checkout-dirty-${_wiIdForAlert}`, _alertBody); }
2382
2393
  catch (e) { log('warn', `live-checkout: writeInboxAlert failed: ${e.message}`); }
@@ -2391,14 +2402,19 @@ async function spawnAgent(dispatchItem, config) {
2391
2402
  });
2392
2403
  }
2393
2404
  } catch (e) { log('warn', `live-checkout: failed to stamp _pendingReason: ${e.message}`); }
2405
+ const _dirtyMsg = _branchInfo
2406
+ ? `live-checkout refused: ${_dirtyFiles.length} dirty file(s) in ${cwd} (${_branchInfo.replace(/^## /, '')})`
2407
+ : `live-checkout refused: ${_dirtyFiles.length} dirty file(s) in ${cwd}`;
2394
2408
  log('error', `spawnAgent: live-checkout refused for ${id}: ${_dirtyFiles.length} dirty file(s) in ${cwd}`);
2395
2409
  _cleanupPromptFiles();
2396
2410
  completeDispatch(
2397
2411
  id,
2398
2412
  DISPATCH_RESULT.ERROR,
2399
- `live-checkout refused: ${_dirtyFiles.length} dirty file(s) in ${cwd}`.slice(0, 800),
2400
- 'Live-checkout mode requires a clean tree; engine refuses to overwrite operator changes. Commit/stash/discard and re-dispatch.',
2401
- { failureClass: FAILURE_CLASS.LIVE_CHECKOUT_DIRTY, agentRetryable: false },
2413
+ _dirtyMsg.slice(0, 800),
2414
+ _alreadyDirtyFailed
2415
+ ? 'Live-checkout dirty state persisted across retries — operator must commit/stash/discard before re-dispatch.'
2416
+ : 'Live-checkout dirty tree retried once automatically; if dirty persists, operator must commit/stash/discard.',
2417
+ { failureClass: FAILURE_CLASS.LIVE_CHECKOUT_DIRTY, agentRetryable: !_alreadyDirtyFailed },
2402
2418
  );
2403
2419
  cleanupTempAgent(agentId);
2404
2420
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2242",
3
+ "version": "0.1.2243",
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"