@yemi33/minions 0.1.2090 → 0.1.2092

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.
@@ -222,25 +222,39 @@ function _isCachedBinUsable(cached) {
222
222
  }
223
223
 
224
224
  /**
225
- * Probe `gh extension list` for the gh-copilot extension. Returns the absolute
226
- * path of the `gh` binary when found, null otherwise.
225
+ * Probe `gh` for Copilot support. Returns the absolute path of the `gh`
226
+ * binary when `gh copilot` is invokable, null otherwise.
227
227
  *
228
- * `gh extension list` exits 0 with a list of extensions on stdout. We grep for
229
- * `gh-copilot`, the extension's repository slug. If `gh` isn't on PATH the
230
- * outer try-catch swallows the ENOENT.
228
+ * Two install paths produce a working `gh copilot` subcommand and we accept
229
+ * both:
230
+ * 1. gh ≥ ~2.90 ships Copilot as a built-in preview command. `gh extension
231
+ * list` does NOT list it (it's not an extension) — and attempting to
232
+ * install the legacy `github/gh-copilot` extension is rejected because
233
+ * gh already provides the command. Detecting via the extension list
234
+ * would falsely report "copilot not installed" on every recent gh.
235
+ * 2. gh < ~2.90 with the legacy `gh-copilot` extension installed. The
236
+ * extension also responds to `gh copilot --help` with exit 0.
237
+ *
238
+ * `gh copilot --help` exits 0 in both cases and non-zero ("unknown command")
239
+ * when neither path is available, so it's a clean unified signal. The probe
240
+ * runs with --no-update-notifier (silences gh's update banner so a slow
241
+ * network doesn't drag the probe over the timeout). If `gh` isn't on PATH
242
+ * the outer try-catch swallows the ENOENT.
231
243
  */
232
- function _findGhCopilotExtension(env) {
244
+ function _findGhCopilotExtension(env, { execSyncCapture = _execSyncCapture } = {}) {
233
245
  let ghPath = null;
234
246
  try {
235
247
  const cmd = isWin ? 'where gh 2>NUL' : 'which gh 2>/dev/null';
236
- const which = _execSyncCapture(cmd, env).trim().split('\n')[0].trim();
248
+ const which = execSyncCapture(cmd, env).trim().split('\n')[0].trim();
237
249
  if (!which) return null;
238
250
  ghPath = which;
239
251
  } catch { return null; }
240
252
  try {
241
- const out = _execSyncCapture('gh extension list', env);
242
- if (/gh-copilot/i.test(out)) return ghPath;
243
- } catch { /* `gh` may have no extensions or be misconfigured */ }
253
+ // execSync throws on non-zero exit; reaching the return means `gh copilot`
254
+ // is a valid subcommand on this gh version (builtin or legacy extension).
255
+ execSyncCapture('gh copilot --help', env, 5000);
256
+ return ghPath;
257
+ } catch { /* gh present but no copilot subcommand */ }
244
258
  return null;
245
259
  }
246
260
 
@@ -1205,11 +1219,12 @@ const capabilities = {
1205
1219
 
1206
1220
  // Install hint surfaced when `resolveBinary()` returns null. Covers all
1207
1221
  // supported install paths so users on any platform see one actionable line.
1208
- // Standalone Copilot CLI (preferred path) is available via:
1209
- // - WinGet: winget install --id GitHub.cli && gh extension install github/gh-copilot
1210
- // - Homebrew: brew install gh && gh extension install github/gh-copilot
1211
- // - Direct: download from https://github.com/github/copilot-cli/releases
1212
- const INSTALL_HINT = 'install via WinGet (winget install --id GitHub.cli && gh extension install github/gh-copilot), Homebrew (brew install gh && gh extension install github/gh-copilot), or download standalone copilot from https://github.com/github/copilot-cli/releases';
1222
+ //
1223
+ // gh ~2.90 ships `gh copilot` as a built-in preview command, so the legacy
1224
+ // `gh extension install github/gh-copilot` flow is no longer required (and is
1225
+ // in fact rejected by recent gh, since the command is already provided). The
1226
+ // hint reflects that: get a modern gh, OR install the standalone Copilot CLI.
1227
+ const INSTALL_HINT = 'install GitHub CLI 2.90+ which ships `gh copilot` built in (WinGet: `winget install --id GitHub.cli`, Homebrew: `brew install gh`, or download from https://cli.github.com), or install the standalone Copilot CLI via `npm i -g @github/copilot`';
1213
1228
 
1214
1229
  function getUserAssetDirs({ homeDir = os.homedir() } = {}) {
1215
1230
  return [
@@ -1286,6 +1301,7 @@ module.exports = {
1286
1301
  _pickStandaloneCopilotFromOutput,
1287
1302
  _resolveNpmCopilotJsEntry,
1288
1303
  _isCachedBinUsable,
1304
+ _findGhCopilotExtension,
1289
1305
  // W-mpmwxni2000c25c7-a — invalid-model error-path helpers. `_warmModelCache`
1290
1306
  // populates the in-memory model catalog so parseError can enrich its
1291
1307
  // "Model X not available" message without a per-error HTTP round trip.
package/engine.js CHANGED
@@ -4816,7 +4816,28 @@ async function discoverFromPrs(config, project) {
4816
4816
  }
4817
4817
  const autoFixBuilds = config.engine?.autoFixBuilds ?? ENGINE_DEFAULTS.autoFixBuilds;
4818
4818
  if (pollEnabled && autoFixBuilds && pr.status === PR_STATUS.ACTIVE && pr.buildStatus === 'failing'
4819
+ && !fixDispatched
4819
4820
  && !isPrNoOpFixCauseSuppressed(pr, shared.PR_FIX_CAUSE.BUILD_FAILURE)) {
4821
+ // W-mpritzcr0004afc5 (#2955): "don't fan out a parallel build-failure
4822
+ // fix while the review-feedback path owns the PR" — implemented via
4823
+ // the `!fixDispatched` gate above (mirrors the merge-conflict block
4824
+ // below). This preserves W-mphnm6a1000281b8 / PR #57 starvation fix:
4825
+ // when the review-feedback dispatch is suppressed by its own cooldown,
4826
+ // `fixDispatched` stays false and the build-fix path still gets to
4827
+ // run. An earlier draft used a bare `reviewStatus === 'changes-requested'`
4828
+ // skip, which broke that starvation guarantee.
4829
+ //
4830
+ // W-mpritzcr0004afc5 (#2955): receive-side cap from lifecycle.js
4831
+ // (`updatePrAfterFix` BUILD_FAILURE branch) flagged this PR as
4832
+ // unrecoverable by auto-retry after `engine.maxBuildFixRetries`
4833
+ // consecutive unverified pushes. Honor the flag here so the dispatcher
4834
+ // stops queuing fix agents — a human must inspect the worktree or
4835
+ // branch protection. Cleared by the same lifecycle path on the next
4836
+ // verified push (lifecycle.js:~2184).
4837
+ if (pr._buildFixNeedsHumanRebase) {
4838
+ log('info', `Skipping build-fix for ${pr.id}: _buildFixNeedsHumanRebase is set — engine reached maxBuildFixRetries, awaiting human rescue`);
4839
+ continue;
4840
+ }
4820
4841
  // P-b7e1c4d2: skip when the most recent BUILD-FAILURE dispatch already
4821
4842
  // noop'd against the same head SHA — chronic across PRs #2315–#2323
4822
4843
  // where every fix agent rebutted "this is a pre-existing master baseline"
@@ -4907,8 +4928,16 @@ async function discoverFromPrs(config, project) {
4907
4928
  if (!prBranch) continue;
4908
4929
 
4909
4930
  const reviewNote = [
4931
+ `Cause: ${shared.PR_FIX_CAUSE.BUILD_FAILURE}.`,
4910
4932
  `Build is failing: ${pr.buildFailReason || 'check CI pipeline for details'}.`,
4911
4933
  'Inspect the live PR checks/build logs yourself, decide the root cause, fix it, run the relevant local validation, and push.',
4934
+ // W-mpritzcr0004afc5 (#2955): the engine-fetched build error log on the
4935
+ // PR record is intentionally not inlined here — the build-error-log
4936
+ // feature regression test in test/unit/build-error-log-feature.test.js
4937
+ // locks the "let the agent inspect live CI" design so the engine does
4938
+ // not have to stay in sync with each CI provider's log format. The
4939
+ // Cause label above plus the build-fail reason is enough to disambiguate
4940
+ // this dispatch from review-feedback / merge-conflict triggers.
4912
4941
  pr.url ? `PR URL: ${pr.url}` : '',
4913
4942
  ].filter(Boolean).join('\n');
4914
4943
 
@@ -4949,8 +4978,30 @@ async function discoverFromPrs(config, project) {
4949
4978
  const autoFixConflicts = config.engine?.autoFixConflicts ?? ENGINE_DEFAULTS.autoFixConflicts;
4950
4979
  if (pollEnabled && autoFixConflicts && pr.status === PR_STATUS.ACTIVE && pr._mergeConflict && !fixDispatched
4951
4980
  && !isPrNoOpFixCauseSuppressed(pr, shared.PR_FIX_CAUSE.MERGE_CONFLICT)) {
4981
+ // W-mpritzcr0004afc5 (#2955): "don't fan out a parallel conflict-fix
4982
+ // while the review-feedback path owns the PR" is already handled by
4983
+ // the existing `!fixDispatched` gate above. When review-feedback is
4984
+ // queued in this same iteration, fixDispatched=true and this block
4985
+ // skips. When review-feedback is suppressed by its own cooldown, this
4986
+ // block can still fire — preserving the W-mphnm6a1000281b8 / PR #57
4987
+ // starvation guarantee.
4952
4988
  const conflictCauseKey = getPrAutomationCauseKey('merge-conflict', pr);
4953
4989
  const key = getPrAutomationDispatchKey(`conflict-fix-${project?.name || 'default'}-${prDisplayId}`, conflictCauseKey);
4990
+ // W-mpritzcr0004afc5 (#2955): per-cause same-head guard mirroring the
4991
+ // build-failure block above. `_conflictFixedAt` is a 10-min wall-clock
4992
+ // suppression for ADO/GH mergeStatus lag; `_lastDispatchByCause` is a
4993
+ // headSha-pinned suppression for repeated agent noops on an unchanged
4994
+ // base+head pair. Both must fire (their windows are independent).
4995
+ const currentHeadSha = String(pr.headSha || pr._adoSourceCommit || pr._adoHeadCommit || '').trim();
4996
+ const lastConflictDispatch = pr._lastDispatchByCause?.[shared.PR_FIX_CAUSE.MERGE_CONFLICT];
4997
+ const skipConflictFix = !!(lastConflictDispatch?.outcome === 'noop'
4998
+ && lastConflictDispatch.headSha
4999
+ && currentHeadSha
5000
+ && lastConflictDispatch.headSha === currentHeadSha);
5001
+ if (skipConflictFix) {
5002
+ log('info', `Skipping conflict-fix for ${pr.id}: last merge-conflict dispatch was noop on the same head ${currentHeadSha.slice(0, 8)} (${(lastConflictDispatch.reason || '').slice(0, 120)})`);
5003
+ continue;
5004
+ }
4954
5005
  // Suppress re-dispatch for 10 min after last attempt — ADO/GitHub recomputes
4955
5006
  // mergeStatus asynchronously (1–5 min lag), so the flag may stay set even after
4956
5007
  // a successful push. _conflictFixedAt is cleared when the poller confirms clean status.
@@ -4985,9 +5036,14 @@ async function discoverFromPrs(config, project) {
4985
5036
  if (agentId) {
4986
5037
  const prBranch = ensurePrBranchForDispatch(project, pr, 'conflict-fix');
4987
5038
  if (!prBranch) continue;
5039
+ const conflictReviewNote = [
5040
+ `Cause: ${shared.PR_FIX_CAUSE.MERGE_CONFLICT}.`,
5041
+ 'This PR has merge conflicts with the target branch. Inspect the live PR and repository history, choose the safest merge/rebase/update strategy, resolve all conflicts, validate the result, and push the branch.',
5042
+ pr.url ? `PR URL: ${pr.url}` : '',
5043
+ ].filter(Boolean).join('\n');
4988
5044
  const item = buildPrDispatch(agentId, config, project, pr, 'fix', {
4989
5045
  pr_id: pr.id, pr_branch: prBranch,
4990
- review_note: `This PR has merge conflicts with the target branch. Inspect the live PR and repository history, choose the safest merge/rebase/update strategy, resolve all conflicts, validate the result, and push the branch.`,
5046
+ review_note: conflictReviewNote,
4991
5047
  }, `Fix merge conflicts on ${pr.id}: ${pr.title || ''}`, { dispatchKey: key, cooldownKey: key, automationCauseKey: conflictCauseKey, source: 'pr', pr, branch: prBranch, project: projMeta });
4992
5048
  if (item) {
4993
5049
  newWork.push(item);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2090",
3
+ "version": "0.1.2092",
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"