@sideboard-ai/core 0.1.81 → 0.1.83

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.
Files changed (34) hide show
  1. package/dist/agents/cursor-runner.cjs +18 -0
  2. package/dist/agents/cursor-runner.js +4 -0
  3. package/dist/{agents-UQ7D6U6L.js → agents-3YPUZME7.js} +6 -5
  4. package/dist/{agents-EBKJ55PN.js → agents-4KRD46KG.js} +5 -5
  5. package/dist/{app-settings-NILNWNNQ.js → app-settings-GVSOIJLZ.js} +1 -1
  6. package/dist/{app-settings-6IOQYGBK.js → app-settings-MQXL7OUF.js} +2 -1
  7. package/dist/{chunk-P5VDPGAN.js → chunk-DJFGX4RT.js} +3 -3
  8. package/dist/{chunk-CQBQWX73.js → chunk-E2MIA7DO.js} +8 -3
  9. package/dist/{chunk-4HJK57XU.js → chunk-ERJS3ZDP.js} +8 -3
  10. package/dist/{chunk-YVVXWWCC.js → chunk-GD2FM6FN.js} +81 -49
  11. package/dist/{chunk-FUUVPN4A.js → chunk-HBBXS2FR.js} +78 -49
  12. package/dist/chunk-IZ7RPF54.js +36 -0
  13. package/dist/{chunk-4AVU4QXO.js → chunk-JMRJ4F5B.js} +19 -8
  14. package/dist/{chunk-XNECBC6T.js → chunk-JW6YFPQE.js} +20 -7
  15. package/dist/{chunk-K553XK7M.js → chunk-LVTNWH7B.js} +2 -2
  16. package/dist/{chunk-6F2TQFJE.js → chunk-NXXT5SE3.js} +3 -3
  17. package/dist/{chunk-TDJ43HUD.js → chunk-RS54WYYH.js} +2 -2
  18. package/dist/{chunk-XKNBSYA7.js → chunk-UHGN4KCL.js} +35 -1
  19. package/dist/{chunk-IFPN6TER.js → chunk-YO3CYL6B.js} +4 -1
  20. package/dist/{coordinator-prompt-JQF72JKX.js → coordinator-prompt-46JE4NQR.js} +4 -3
  21. package/dist/{coordinator-prompt-NYNEFKIV.js → coordinator-prompt-6ICA54JR.js} +3 -3
  22. package/dist/{global-workspace-DZM4ZFGU.js → global-workspace-GSLCEB5E.js} +5 -4
  23. package/dist/{global-workspace-NUMUJRWG.js → global-workspace-OBRWUPZG.js} +4 -4
  24. package/dist/index.cjs +182 -77
  25. package/dist/index.d.cts +36 -9
  26. package/dist/index.d.ts +36 -9
  27. package/dist/index.js +67 -43
  28. package/dist/mcp/run-stdio.cjs +180 -77
  29. package/dist/mcp/run-stdio.js +59 -42
  30. package/dist/{workspaces-QLRCOXAY.js → workspaces-7B3PTEF4.js} +6 -5
  31. package/dist/{workspaces-SUU2GO3A.js → workspaces-RJIWQB6J.js} +5 -5
  32. package/dist/{worktree-5VLLFI5W.js → worktree-HSN5LWY6.js} +3 -2
  33. package/dist/{worktree-UNSOCYZU.js → worktree-U3UIID2K.js} +2 -2
  34. package/package.json +1 -1
@@ -7,6 +7,23 @@ var import_node_fs3 = require("fs");
7
7
  var import_node_path4 = require("path");
8
8
  var import_node_readline = require("readline");
9
9
 
10
+ // src/hook/nested-electron-env.ts
11
+ var NESTED_ELECTRON_ENV_PREFIXES = ["ELECTRON_", "CHROME_"];
12
+ function isNestedElectronEnvKey(key) {
13
+ return NESTED_ELECTRON_ENV_PREFIXES.some((prefix) => key.startsWith(prefix));
14
+ }
15
+ function dropNestedElectronEnvFromProcess(env = process.env) {
16
+ for (const key of Object.keys(env)) {
17
+ if (isNestedElectronEnvKey(key)) delete env[key];
18
+ }
19
+ }
20
+ var STRIP_NESTED_ELECTRON_THEN_EXEC = [
21
+ "vars=`printenv | awk -F= '/^(ELECTRON_|CHROME_)/{print $1}'`",
22
+ '[ -n "$vars" ] && unset $vars',
23
+ "export ELECTRON_RUN_AS_NODE=1",
24
+ 'exec "$@"'
25
+ ].join("; ");
26
+
10
27
  // src/store/paths.ts
11
28
  var import_node_path2 = require("path");
12
29
  var import_node_os2 = require("os");
@@ -224,6 +241,7 @@ function cursorSdkMessageToEvents(msg) {
224
241
  }
225
242
 
226
243
  // src/agents/cursor-runner.ts
244
+ dropNestedElectronEnvFromProcess();
227
245
  function emit(event) {
228
246
  process.stdout.write(`${JSON.stringify(event)}
229
247
  `);
@@ -3,6 +3,9 @@ import {
3
3
  cursorSdkMessageToEvents,
4
4
  formatUnknownDetail
5
5
  } from "../chunk-SEOICVGB.js";
6
+ import {
7
+ dropNestedElectronEnvFromProcess
8
+ } from "../chunk-IZ7RPF54.js";
6
9
  import {
7
10
  appDataDir
8
11
  } from "../chunk-M37RITA6.js";
@@ -12,6 +15,7 @@ import { Agent, AgentBusyError, CursorAgentError, JsonlLocalAgentStore } from "@
12
15
  import { mkdirSync } from "fs";
13
16
  import { join } from "path";
14
17
  import { createInterface } from "readline";
18
+ dropNestedElectronEnvFromProcess();
15
19
  function emit(event) {
16
20
  process.stdout.write(`${JSON.stringify(event)}
17
21
  `);
@@ -28,22 +28,23 @@ import {
28
28
  resolveCursorModelId,
29
29
  resolveLoginCommand,
30
30
  resolveQuotaFallbackAgent
31
- } from "./chunk-XNECBC6T.js";
31
+ } from "./chunk-JW6YFPQE.js";
32
32
  import {
33
33
  ORCHESTRATOR_AGENT_KINDS,
34
34
  assertOrchestratorCapableAgent,
35
35
  coerceOrchestratorAgent,
36
36
  isOrchestratorCapableAgent
37
- } from "./chunk-6F2TQFJE.js";
38
- import "./chunk-CQBQWX73.js";
37
+ } from "./chunk-NXXT5SE3.js";
38
+ import "./chunk-E2MIA7DO.js";
39
39
  import "./chunk-BTL7EMGT.js";
40
40
  import {
41
41
  cursorSdkMessageToEvents,
42
42
  parseCursorRunnerLine
43
43
  } from "./chunk-SEOICVGB.js";
44
- import "./chunk-YVVXWWCC.js";
44
+ import "./chunk-GD2FM6FN.js";
45
45
  import "./chunk-FKOIHGKV.js";
46
- import "./chunk-IFPN6TER.js";
46
+ import "./chunk-YO3CYL6B.js";
47
+ import "./chunk-IZ7RPF54.js";
47
48
  import "./chunk-JT7R45JB.js";
48
49
  import "./chunk-FWJYLBO6.js";
49
50
  import "./chunk-77WWLBCI.js";
@@ -32,16 +32,16 @@ import {
32
32
  resolveCursorModelId,
33
33
  resolveLoginCommand,
34
34
  resolveQuotaFallbackAgent
35
- } from "./chunk-4AVU4QXO.js";
35
+ } from "./chunk-JMRJ4F5B.js";
36
36
  import "./chunk-VROPG6QF.js";
37
37
  import {
38
38
  ORCHESTRATOR_AGENT_KINDS,
39
39
  assertOrchestratorCapableAgent,
40
40
  coerceOrchestratorAgent,
41
41
  isOrchestratorCapableAgent
42
- } from "./chunk-P5VDPGAN.js";
43
- import "./chunk-4HJK57XU.js";
44
- import "./chunk-FUUVPN4A.js";
42
+ } from "./chunk-DJFGX4RT.js";
43
+ import "./chunk-ERJS3ZDP.js";
44
+ import "./chunk-HBBXS2FR.js";
45
45
  import "./chunk-B3SJXYIJ.js";
46
46
  import "./chunk-7FD7COKE.js";
47
47
  import {
@@ -53,7 +53,7 @@ import {
53
53
  resolveCommandBinarySync,
54
54
  withExportedPath
55
55
  } from "./chunk-AE5VBOFE.js";
56
- import "./chunk-XKNBSYA7.js";
56
+ import "./chunk-UHGN4KCL.js";
57
57
  import "./chunk-NSTQ6QKD.js";
58
58
  import "./chunk-KGZBYWZ3.js";
59
59
  import "./chunk-7MV3RXSC.js";
@@ -54,7 +54,7 @@ import {
54
54
  updateDefaultsSettings,
55
55
  updateIntegrationsSettings,
56
56
  updateOpencodeSettings
57
- } from "./chunk-XKNBSYA7.js";
57
+ } from "./chunk-UHGN4KCL.js";
58
58
  import "./chunk-NSTQ6QKD.js";
59
59
  import "./chunk-KGZBYWZ3.js";
60
60
  import "./chunk-7MV3RXSC.js";
@@ -52,7 +52,8 @@ import {
52
52
  updateDefaultsSettings,
53
53
  updateIntegrationsSettings,
54
54
  updateOpencodeSettings
55
- } from "./chunk-IFPN6TER.js";
55
+ } from "./chunk-YO3CYL6B.js";
56
+ import "./chunk-IZ7RPF54.js";
56
57
  import "./chunk-JT7R45JB.js";
57
58
  import "./chunk-77WWLBCI.js";
58
59
  import "./chunk-M37RITA6.js";
@@ -2,12 +2,12 @@
2
2
 
3
3
  import {
4
4
  ensureGlobalCoordinatorCwd
5
- } from "./chunk-4HJK57XU.js";
5
+ } from "./chunk-ERJS3ZDP.js";
6
6
  import {
7
7
  allocateTeamName,
8
8
  takenSlugsFromThread,
9
9
  teamSlugFromName
10
- } from "./chunk-FUUVPN4A.js";
10
+ } from "./chunk-HBBXS2FR.js";
11
11
  import {
12
12
  createEmptyThread,
13
13
  listThreads,
@@ -17,7 +17,7 @@ import {
17
17
  import {
18
18
  resolveNewThreadOptions,
19
19
  resolveThreadDefaults
20
- } from "./chunk-XKNBSYA7.js";
20
+ } from "./chunk-UHGN4KCL.js";
21
21
  import {
22
22
  globalAgentCwd
23
23
  } from "./chunk-7MV3RXSC.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  resolveGithubRepoSlug
3
- } from "./chunk-YVVXWWCC.js";
3
+ } from "./chunk-GD2FM6FN.js";
4
4
  import {
5
5
  resolveThreadDefaults
6
- } from "./chunk-IFPN6TER.js";
6
+ } from "./chunk-YO3CYL6B.js";
7
7
  import {
8
8
  globalAgentCwd,
9
9
  sideboardReposDir
@@ -51,6 +51,7 @@ var COORDINATOR_TOOL_PLAYBOOK = [
51
51
  "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
52
52
  "- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
53
53
  "- list_threads / get_thread \u2014 fleet status (what is going on)",
54
+ "- ask_user \u2014 multiple-choice questions in the composer (any mode, not only Plan). Explain options in chat first, include a description on every option, then wait for answers.",
54
55
  "- set_caffeinate \u2014 keep this Mac awake across turns (macOS caffeinate). Turn on for Slack / away-from-keyboard work. Turn OFF when the user says they are done, wrapping up, going to sleep, or no longer need the machine awake. Closing this chat also releases it.",
55
56
  "Workspaces:",
56
57
  "- add_workspace / remove_workspace \u2014 register or unregister a git repo",
@@ -70,7 +71,11 @@ var COORDINATOR_TOOL_PLAYBOOK = [
70
71
  '- request_review \u2014 open a Review chat tab on a worktree thread (attaches .sideboard/review.md when present, else local guidelines; sends "Review changes in this workspace."); then wait_for_turn / get_turn_result on the returned id',
71
72
  "- ask_git \u2014 commit & push, open a draft PR, resolve conflicts, or merge. When the worktree is clean, Sideboard pushes / opens the PR itself. When dirty, it queues the worktree agent \u2014 then wait_for_turn. Prefer this over paraphrasing.",
72
73
  '- Merge (`ask_git` action=merge / send_to_thread "Merge PR.") only when the user explicitly asked to merge that PR. Do not merge because the work looks done, CI is green, or a typical flow includes it.',
73
- '- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Fix merge conflicts.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
74
+ '- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Merge the remote branch (main) into your branch and resolve conflicts. Then, commit and push your changes.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
75
+ "Process guides:",
76
+ "- Recurring multi-item / fan-out: if the child worktree has `.claude/skills/graph-engineering/SKILL.md`, tell the worker to follow it (`/graph-engineering`). Judge first; state on disk; grow the rulebook; do not patch three threads.",
77
+ "- Recurring shapes: have the worktree agent write `.claude/skills/<kebab-name>/SKILL.md` (commit it) so later threads and native Claude Code / attach see it. Do not use `.sideboard/skills` for new guides. Codex/OpenCode: one line in AGENTS.md pointing at that file.",
78
+ "- One-offs: no guide. Same miss across threads: edit the skill or `.sideboard/review.md`, then rerun the batch \u2014 do not patch three threads.",
74
79
  "Human-only (do not attempt): ready-for-review land (confirm_land), purge_thread.",
75
80
  "Thread links in replies: when mentioning a chat/thread for the user, include a markdown link `[Title](sideboard://thread/<id>)` using the full id (or the link field from create_thread / list_threads). Sideboard renders these as clickable opens.",
76
81
  "Bash / Read / etc: allowed for (1) inspecting target worktrees / registered repo paths from MCP, and (2) greenfield setup under ~/sideboard/repos (git clone, gh repo create, git init+remote). Never git init/clone *inside* this synthetic home cwd \u2014 emptiness here is expected, not a bug."
@@ -2,10 +2,10 @@
2
2
 
3
3
  import {
4
4
  resolveGithubRepoSlug
5
- } from "./chunk-FUUVPN4A.js";
5
+ } from "./chunk-HBBXS2FR.js";
6
6
  import {
7
7
  resolveThreadDefaults
8
- } from "./chunk-XKNBSYA7.js";
8
+ } from "./chunk-UHGN4KCL.js";
9
9
  import {
10
10
  globalAgentCwd,
11
11
  sideboardReposDir
@@ -53,6 +53,7 @@ var COORDINATOR_TOOL_PLAYBOOK = [
53
53
  "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
54
54
  "- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
55
55
  "- list_threads / get_thread \u2014 fleet status (what is going on)",
56
+ "- ask_user \u2014 multiple-choice questions in the composer (any mode, not only Plan). Explain options in chat first, include a description on every option, then wait for answers.",
56
57
  "- set_caffeinate \u2014 keep this Mac awake across turns (macOS caffeinate). Turn on for Slack / away-from-keyboard work. Turn OFF when the user says they are done, wrapping up, going to sleep, or no longer need the machine awake. Closing this chat also releases it.",
57
58
  "Workspaces:",
58
59
  "- add_workspace / remove_workspace \u2014 register or unregister a git repo",
@@ -72,7 +73,11 @@ var COORDINATOR_TOOL_PLAYBOOK = [
72
73
  '- request_review \u2014 open a Review chat tab on a worktree thread (attaches .sideboard/review.md when present, else local guidelines; sends "Review changes in this workspace."); then wait_for_turn / get_turn_result on the returned id',
73
74
  "- ask_git \u2014 commit & push, open a draft PR, resolve conflicts, or merge. When the worktree is clean, Sideboard pushes / opens the PR itself. When dirty, it queues the worktree agent \u2014 then wait_for_turn. Prefer this over paraphrasing.",
74
75
  '- Merge (`ask_git` action=merge / send_to_thread "Merge PR.") only when the user explicitly asked to merge that PR. Do not merge because the work looks done, CI is green, or a typical flow includes it.',
75
- '- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Fix merge conflicts.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
76
+ '- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Merge the remote branch (main) into your branch and resolve conflicts. Then, commit and push your changes.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
77
+ "Process guides:",
78
+ "- Recurring multi-item / fan-out: if the child worktree has `.claude/skills/graph-engineering/SKILL.md`, tell the worker to follow it (`/graph-engineering`). Judge first; state on disk; grow the rulebook; do not patch three threads.",
79
+ "- Recurring shapes: have the worktree agent write `.claude/skills/<kebab-name>/SKILL.md` (commit it) so later threads and native Claude Code / attach see it. Do not use `.sideboard/skills` for new guides. Codex/OpenCode: one line in AGENTS.md pointing at that file.",
80
+ "- One-offs: no guide. Same miss across threads: edit the skill or `.sideboard/review.md`, then rerun the batch \u2014 do not patch three threads.",
76
81
  "Human-only (do not attempt): ready-for-review land (confirm_land), purge_thread.",
77
82
  "Thread links in replies: when mentioning a chat/thread for the user, include a markdown link `[Title](sideboard://thread/<id>)` using the full id (or the link field from create_thread / list_threads). Sideboard renders these as clickable opens.",
78
83
  "Bash / Read / etc: allowed for (1) inspecting target worktrees / registered repo paths from MCP, and (2) greenfield setup under ~/sideboard/repos (git clone, gh repo create, git init+remote). Never git init/clone *inside* this synthetic home cwd \u2014 emptiness here is expected, not a bug."
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  getGithubGitAuthMode,
6
6
  getGithubPat
7
- } from "./chunk-IFPN6TER.js";
7
+ } from "./chunk-YO3CYL6B.js";
8
8
  import {
9
9
  listThreads
10
10
  } from "./chunk-FWJYLBO6.js";
@@ -882,11 +882,25 @@ function formatGhLandError(raw, opts) {
882
882
  }
883
883
  return detail || "Failed to create or update pull request";
884
884
  }
885
+ function isPrNotMergeableError(text) {
886
+ return /not mergeable|cannot be cleanly created|cannot merge cleanly|Merge conflict|\bCONFLICTING\b|must be (updated|rebased)|branch is out of date|needs? to be (updated|rebased)|Resolve conflicts or update the branch/i.test(
887
+ text
888
+ );
889
+ }
890
+ function formatMergePrError(raw) {
891
+ const trimmed = raw.trim();
892
+ if (!trimmed) return "gh pr merge failed";
893
+ if (isPrNotMergeableError(trimmed)) {
894
+ return "This pull request cannot merge cleanly into the base branch. Resolve conflicts or update the branch, then retry.";
895
+ }
896
+ return extractGhErrorDetail(trimmed) || trimmed;
897
+ }
885
898
  function formatIpcInvokeError(err) {
886
899
  let msg = err instanceof Error ? err.message : String(err);
887
900
  msg = msg.replace(/^Error invoking remote method '[^']+':\s*/i, "");
888
901
  msg = msg.replace(/^ExecaError:\s*/i, "");
889
902
  msg = msg.replace(/^Error:\s*/i, "");
903
+ if (isPrNotMergeableError(msg)) return formatMergePrError(msg);
890
904
  return formatGhLandError(msg);
891
905
  }
892
906
 
@@ -1599,6 +1613,41 @@ ${result.stderr}`;
1599
1613
  ].slice(0, 20) : [];
1600
1614
  return { conflicting, base: baseName, files };
1601
1615
  }
1616
+ async function probeLocalMergeGate(cwd, gate) {
1617
+ const mergeable = (gate?.mergeable ?? "").toUpperCase();
1618
+ const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
1619
+ const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
1620
+ const inQueue = gate ? prIsInMergeQueue(gate) : false;
1621
+ if (alreadyConflicting || inQueue) return { gate, files: [] };
1622
+ try {
1623
+ const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
1624
+ if (local.conflicting) {
1625
+ return {
1626
+ gate: {
1627
+ mergeable: "CONFLICTING",
1628
+ mergeStateStatus: "DIRTY",
1629
+ reviewDecision: gate?.reviewDecision ?? null,
1630
+ baseRefName: local.base,
1631
+ url: gate?.url ?? null,
1632
+ isInMergeQueue: false
1633
+ },
1634
+ files: local.files
1635
+ };
1636
+ }
1637
+ if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
1638
+ return {
1639
+ gate: {
1640
+ ...gate,
1641
+ mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
1642
+ mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
1643
+ },
1644
+ files: []
1645
+ };
1646
+ }
1647
+ } catch {
1648
+ }
1649
+ return { gate, files: [] };
1650
+ }
1602
1651
  async function getPrChecks(cwd, selector) {
1603
1652
  const slug = await resolveGithubRepoSlug(cwd);
1604
1653
  const args = [
@@ -1634,47 +1683,9 @@ async function getPrChecks(cwd, selector) {
1634
1683
  }
1635
1684
  }
1636
1685
  let gate = await fetchPrMergeGate(cwd, selector, slug);
1637
- const mergeable = (gate?.mergeable ?? "").toUpperCase();
1638
- const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
1639
- const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
1640
- const inQueue = gate ? prIsInMergeQueue(gate) : false;
1641
- const needsLocalProbe = !alreadyConflicting && !inQueue;
1642
- if (needsLocalProbe) {
1643
- try {
1644
- const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
1645
- if (local.conflicting) {
1646
- const fileHint = local.files.length > 0 ? ` Conflicting paths: ${local.files.slice(0, 8).join(", ")}${local.files.length > 8 ? "\u2026" : ""}.` : "";
1647
- gate = {
1648
- mergeable: "CONFLICTING",
1649
- mergeStateStatus: "DIRTY",
1650
- reviewDecision: gate?.reviewDecision ?? null,
1651
- baseRefName: local.base,
1652
- url: gate?.url ?? null,
1653
- isInMergeQueue: false
1654
- };
1655
- const ciFailed2 = ciChecks.some((c) => c.bucket === "fail");
1656
- const review2 = (gate.reviewDecision ?? "").toUpperCase();
1657
- const hasReviewRow2 = review2 === "CHANGES_REQUESTED" || review2 === "REVIEW_REQUIRED";
1658
- const gateRows2 = buildMergeGateChecks(gate, {
1659
- suppressGenericBlocked: ciFailed2 || hasReviewRow2
1660
- }).map(
1661
- (row) => row.kind === "mergeability" && row.name === "Merge conflicts" ? {
1662
- ...row,
1663
- description: `${row.description ?? ""}${fileHint}`.trim()
1664
- } : row
1665
- );
1666
- return [...gateRows2, ...ciChecks];
1667
- }
1668
- if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
1669
- gate = {
1670
- ...gate,
1671
- mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
1672
- mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
1673
- };
1674
- }
1675
- } catch {
1676
- }
1677
- }
1686
+ const probed = await probeLocalMergeGate(cwd, gate);
1687
+ gate = probed.gate;
1688
+ const fileHint = probed.files.length > 0 ? ` Conflicting paths: ${probed.files.slice(0, 8).join(", ")}${probed.files.length > 8 ? "\u2026" : ""}.` : "";
1678
1689
  if (!gate) {
1679
1690
  return ciChecks;
1680
1691
  }
@@ -1684,7 +1695,12 @@ async function getPrChecks(cwd, selector) {
1684
1695
  const gateRows = buildMergeGateChecks(gate, {
1685
1696
  // Avoid duplicating "blocked" when CI failures or review rows already explain it.
1686
1697
  suppressGenericBlocked: ciFailed || hasReviewRow
1687
- });
1698
+ }).map(
1699
+ (row) => fileHint && row.kind === "mergeability" && row.name === "Merge conflicts" ? {
1700
+ ...row,
1701
+ description: `${row.description ?? ""}${fileHint}`.trim()
1702
+ } : row
1703
+ );
1688
1704
  return [...gateRows, ...ciChecks];
1689
1705
  }
1690
1706
  async function getPrMeta(cwd, selector) {
@@ -1694,7 +1710,7 @@ async function getPrMeta(cwd, selector) {
1694
1710
  "view",
1695
1711
  selector,
1696
1712
  "--json",
1697
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus"
1713
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus,mergeable"
1698
1714
  ];
1699
1715
  if (slug) viewArgs.push("--repo", slug);
1700
1716
  let { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
@@ -1704,7 +1720,7 @@ async function getPrMeta(cwd, selector) {
1704
1720
  "view",
1705
1721
  selector,
1706
1722
  "--json",
1707
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus"
1723
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus,mergeable"
1708
1724
  ];
1709
1725
  if (slug) retry.push("--repo", slug);
1710
1726
  ({ stdout, exitCode, stderr } = await gh(retry, cwd, { reject: false }));
@@ -1715,16 +1731,28 @@ async function getPrMeta(cwd, selector) {
1715
1731
  }
1716
1732
  try {
1717
1733
  const view = JSON.parse(stdout);
1734
+ const gate = {
1735
+ mergeable: typeof view.mergeable === "string" && view.mergeable ? view.mergeable : null,
1736
+ mergeStateStatus: typeof view.mergeStateStatus === "string" && view.mergeStateStatus ? view.mergeStateStatus : null,
1737
+ reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
1738
+ baseRefName: String(view.baseRefName ?? ""),
1739
+ url: String(view.url ?? ""),
1740
+ isInMergeQueue: parseInMergeQueue(view)
1741
+ };
1742
+ const probed = await probeLocalMergeGate(cwd, gate);
1743
+ const resolved = probed.gate ?? gate;
1718
1744
  return {
1719
1745
  number: Number(view.number),
1720
1746
  title: String(view.title ?? ""),
1721
1747
  url: String(view.url ?? ""),
1722
1748
  state: String(view.state ?? ""),
1723
1749
  isDraft: Boolean(view.isDraft),
1724
- reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
1725
- baseRefName: String(view.baseRefName ?? ""),
1750
+ reviewDecision: resolved.reviewDecision,
1751
+ baseRefName: resolved.baseRefName || String(view.baseRefName ?? ""),
1726
1752
  headRefName: String(view.headRefName ?? ""),
1727
- isInMergeQueue: parseInMergeQueue(view)
1753
+ isInMergeQueue: Boolean(resolved.isInMergeQueue),
1754
+ mergeable: resolved.mergeable,
1755
+ mergeStateStatus: resolved.mergeStateStatus
1728
1756
  };
1729
1757
  } catch {
1730
1758
  return null;
@@ -2192,7 +2220,9 @@ async function mergePr(cwd, selector, opts) {
2192
2220
  if (slug) args.push("--repo", slug);
2193
2221
  const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
2194
2222
  if (exitCode !== 0) {
2195
- throw new Error(stderr.trim() || stdout.trim() || "gh pr merge failed");
2223
+ throw new Error(
2224
+ formatMergePrError(stderr.trim() || stdout.trim() || "gh pr merge failed")
2225
+ );
2196
2226
  }
2197
2227
  const after = await gh(viewArgs, cwd, { reject: false });
2198
2228
  if (after.exitCode === 0 && after.stdout.trim()) {
@@ -2353,6 +2383,8 @@ export {
2353
2383
  formatRateLimitResetHint,
2354
2384
  extractGhErrorDetail,
2355
2385
  formatGhLandError,
2386
+ isPrNotMergeableError,
2387
+ formatMergePrError,
2356
2388
  formatIpcInvokeError,
2357
2389
  appendIndexedGitConfig,
2358
2390
  githubAgentGitEnv,
@@ -14,7 +14,7 @@ import {
14
14
  import {
15
15
  getGithubGitAuthMode,
16
16
  getGithubPat
17
- } from "./chunk-XKNBSYA7.js";
17
+ } from "./chunk-UHGN4KCL.js";
18
18
  import {
19
19
  worktreesRoot
20
20
  } from "./chunk-7MV3RXSC.js";
@@ -884,6 +884,19 @@ function formatGhLandError(raw, opts) {
884
884
  }
885
885
  return detail || "Failed to create or update pull request";
886
886
  }
887
+ function isPrNotMergeableError(text) {
888
+ return /not mergeable|cannot be cleanly created|cannot merge cleanly|Merge conflict|\bCONFLICTING\b|must be (updated|rebased)|branch is out of date|needs? to be (updated|rebased)|Resolve conflicts or update the branch/i.test(
889
+ text
890
+ );
891
+ }
892
+ function formatMergePrError(raw) {
893
+ const trimmed = raw.trim();
894
+ if (!trimmed) return "gh pr merge failed";
895
+ if (isPrNotMergeableError(trimmed)) {
896
+ return "This pull request cannot merge cleanly into the base branch. Resolve conflicts or update the branch, then retry.";
897
+ }
898
+ return extractGhErrorDetail(trimmed) || trimmed;
899
+ }
887
900
 
888
901
  // src/git/git-auth-mode.ts
889
902
  var HTTPS_REWRITE = [
@@ -1566,6 +1579,41 @@ ${result.stderr}`;
1566
1579
  ].slice(0, 20) : [];
1567
1580
  return { conflicting, base: baseName, files };
1568
1581
  }
1582
+ async function probeLocalMergeGate(cwd, gate) {
1583
+ const mergeable = (gate?.mergeable ?? "").toUpperCase();
1584
+ const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
1585
+ const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
1586
+ const inQueue = gate ? prIsInMergeQueue(gate) : false;
1587
+ if (alreadyConflicting || inQueue) return { gate, files: [] };
1588
+ try {
1589
+ const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
1590
+ if (local.conflicting) {
1591
+ return {
1592
+ gate: {
1593
+ mergeable: "CONFLICTING",
1594
+ mergeStateStatus: "DIRTY",
1595
+ reviewDecision: gate?.reviewDecision ?? null,
1596
+ baseRefName: local.base,
1597
+ url: gate?.url ?? null,
1598
+ isInMergeQueue: false
1599
+ },
1600
+ files: local.files
1601
+ };
1602
+ }
1603
+ if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
1604
+ return {
1605
+ gate: {
1606
+ ...gate,
1607
+ mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
1608
+ mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
1609
+ },
1610
+ files: []
1611
+ };
1612
+ }
1613
+ } catch {
1614
+ }
1615
+ return { gate, files: [] };
1616
+ }
1569
1617
  async function getPrChecks(cwd, selector) {
1570
1618
  const slug = await resolveGithubRepoSlug(cwd);
1571
1619
  const args = [
@@ -1601,47 +1649,9 @@ async function getPrChecks(cwd, selector) {
1601
1649
  }
1602
1650
  }
1603
1651
  let gate = await fetchPrMergeGate(cwd, selector, slug);
1604
- const mergeable = (gate?.mergeable ?? "").toUpperCase();
1605
- const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
1606
- const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
1607
- const inQueue = gate ? prIsInMergeQueue(gate) : false;
1608
- const needsLocalProbe = !alreadyConflicting && !inQueue;
1609
- if (needsLocalProbe) {
1610
- try {
1611
- const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
1612
- if (local.conflicting) {
1613
- const fileHint = local.files.length > 0 ? ` Conflicting paths: ${local.files.slice(0, 8).join(", ")}${local.files.length > 8 ? "\u2026" : ""}.` : "";
1614
- gate = {
1615
- mergeable: "CONFLICTING",
1616
- mergeStateStatus: "DIRTY",
1617
- reviewDecision: gate?.reviewDecision ?? null,
1618
- baseRefName: local.base,
1619
- url: gate?.url ?? null,
1620
- isInMergeQueue: false
1621
- };
1622
- const ciFailed2 = ciChecks.some((c) => c.bucket === "fail");
1623
- const review2 = (gate.reviewDecision ?? "").toUpperCase();
1624
- const hasReviewRow2 = review2 === "CHANGES_REQUESTED" || review2 === "REVIEW_REQUIRED";
1625
- const gateRows2 = buildMergeGateChecks(gate, {
1626
- suppressGenericBlocked: ciFailed2 || hasReviewRow2
1627
- }).map(
1628
- (row) => row.kind === "mergeability" && row.name === "Merge conflicts" ? {
1629
- ...row,
1630
- description: `${row.description ?? ""}${fileHint}`.trim()
1631
- } : row
1632
- );
1633
- return [...gateRows2, ...ciChecks];
1634
- }
1635
- if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
1636
- gate = {
1637
- ...gate,
1638
- mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
1639
- mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
1640
- };
1641
- }
1642
- } catch {
1643
- }
1644
- }
1652
+ const probed = await probeLocalMergeGate(cwd, gate);
1653
+ gate = probed.gate;
1654
+ const fileHint = probed.files.length > 0 ? ` Conflicting paths: ${probed.files.slice(0, 8).join(", ")}${probed.files.length > 8 ? "\u2026" : ""}.` : "";
1645
1655
  if (!gate) {
1646
1656
  return ciChecks;
1647
1657
  }
@@ -1651,7 +1661,12 @@ async function getPrChecks(cwd, selector) {
1651
1661
  const gateRows = buildMergeGateChecks(gate, {
1652
1662
  // Avoid duplicating "blocked" when CI failures or review rows already explain it.
1653
1663
  suppressGenericBlocked: ciFailed || hasReviewRow
1654
- });
1664
+ }).map(
1665
+ (row) => fileHint && row.kind === "mergeability" && row.name === "Merge conflicts" ? {
1666
+ ...row,
1667
+ description: `${row.description ?? ""}${fileHint}`.trim()
1668
+ } : row
1669
+ );
1655
1670
  return [...gateRows, ...ciChecks];
1656
1671
  }
1657
1672
  async function getPrMeta(cwd, selector) {
@@ -1661,7 +1676,7 @@ async function getPrMeta(cwd, selector) {
1661
1676
  "view",
1662
1677
  selector,
1663
1678
  "--json",
1664
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus"
1679
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus,mergeable"
1665
1680
  ];
1666
1681
  if (slug) viewArgs.push("--repo", slug);
1667
1682
  let { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
@@ -1671,7 +1686,7 @@ async function getPrMeta(cwd, selector) {
1671
1686
  "view",
1672
1687
  selector,
1673
1688
  "--json",
1674
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus"
1689
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus,mergeable"
1675
1690
  ];
1676
1691
  if (slug) retry.push("--repo", slug);
1677
1692
  ({ stdout, exitCode, stderr } = await gh(retry, cwd, { reject: false }));
@@ -1682,16 +1697,28 @@ async function getPrMeta(cwd, selector) {
1682
1697
  }
1683
1698
  try {
1684
1699
  const view = JSON.parse(stdout);
1700
+ const gate = {
1701
+ mergeable: typeof view.mergeable === "string" && view.mergeable ? view.mergeable : null,
1702
+ mergeStateStatus: typeof view.mergeStateStatus === "string" && view.mergeStateStatus ? view.mergeStateStatus : null,
1703
+ reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
1704
+ baseRefName: String(view.baseRefName ?? ""),
1705
+ url: String(view.url ?? ""),
1706
+ isInMergeQueue: parseInMergeQueue(view)
1707
+ };
1708
+ const probed = await probeLocalMergeGate(cwd, gate);
1709
+ const resolved = probed.gate ?? gate;
1685
1710
  return {
1686
1711
  number: Number(view.number),
1687
1712
  title: String(view.title ?? ""),
1688
1713
  url: String(view.url ?? ""),
1689
1714
  state: String(view.state ?? ""),
1690
1715
  isDraft: Boolean(view.isDraft),
1691
- reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
1692
- baseRefName: String(view.baseRefName ?? ""),
1716
+ reviewDecision: resolved.reviewDecision,
1717
+ baseRefName: resolved.baseRefName || String(view.baseRefName ?? ""),
1693
1718
  headRefName: String(view.headRefName ?? ""),
1694
- isInMergeQueue: parseInMergeQueue(view)
1719
+ isInMergeQueue: Boolean(resolved.isInMergeQueue),
1720
+ mergeable: resolved.mergeable,
1721
+ mergeStateStatus: resolved.mergeStateStatus
1695
1722
  };
1696
1723
  } catch {
1697
1724
  return null;
@@ -2159,7 +2186,9 @@ async function mergePr(cwd, selector, opts) {
2159
2186
  if (slug) args.push("--repo", slug);
2160
2187
  const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
2161
2188
  if (exitCode !== 0) {
2162
- throw new Error(stderr.trim() || stdout.trim() || "gh pr merge failed");
2189
+ throw new Error(
2190
+ formatMergePrError(stderr.trim() || stdout.trim() || "gh pr merge failed")
2191
+ );
2163
2192
  }
2164
2193
  const after = await gh(viewArgs, cwd, { reject: false });
2165
2194
  if (after.exitCode === 0 && after.stdout.trim()) {