@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
package/dist/index.cjs CHANGED
@@ -385,6 +385,38 @@ var init_paths = __esm({
385
385
  }
386
386
  });
387
387
 
388
+ // src/hook/nested-electron-env.ts
389
+ function isNestedElectronEnvKey(key) {
390
+ return NESTED_ELECTRON_ENV_PREFIXES.some((prefix) => key.startsWith(prefix));
391
+ }
392
+ function stripNestedElectronEnv(env) {
393
+ const out = { ...env };
394
+ for (const key of Object.keys(out)) {
395
+ if (isNestedElectronEnvKey(key)) delete out[key];
396
+ }
397
+ return out;
398
+ }
399
+ function wrapElectronAsNodeLaunch(file, args) {
400
+ if (process.platform === "win32") return { file, args };
401
+ return {
402
+ file: "/bin/sh",
403
+ args: ["-c", STRIP_NESTED_ELECTRON_THEN_EXEC, "sh", file, ...args]
404
+ };
405
+ }
406
+ var NESTED_ELECTRON_ENV_PREFIXES, STRIP_NESTED_ELECTRON_THEN_EXEC;
407
+ var init_nested_electron_env = __esm({
408
+ "src/hook/nested-electron-env.ts"() {
409
+ "use strict";
410
+ NESTED_ELECTRON_ENV_PREFIXES = ["ELECTRON_", "CHROME_"];
411
+ STRIP_NESTED_ELECTRON_THEN_EXEC = [
412
+ "vars=`printenv | awk -F= '/^(ELECTRON_|CHROME_)/{print $1}'`",
413
+ '[ -n "$vars" ] && unset $vars',
414
+ "export ELECTRON_RUN_AS_NODE=1",
415
+ 'exec "$@"'
416
+ ].join("; ");
417
+ }
418
+ });
419
+
388
420
  // src/store/private-file.ts
389
421
  function writePrivateFile(path2, contents) {
390
422
  (0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(path2), { recursive: true });
@@ -1499,7 +1531,7 @@ function applyAppEnvironment(target = process.env, settings = loadAppSettings())
1499
1531
  }
1500
1532
  function childEnvWithAppSettings(extra) {
1501
1533
  const settings = loadAppSettings();
1502
- const env = { ...process.env };
1534
+ const env = stripNestedElectronEnv({ ...process.env });
1503
1535
  applyAppEnvironment(env, settings);
1504
1536
  if (extra) {
1505
1537
  for (const [k, v] of Object.entries(extra)) {
@@ -1520,6 +1552,7 @@ var init_app_settings = __esm({
1520
1552
  import_node_os3 = require("os");
1521
1553
  import_node_path6 = require("path");
1522
1554
  init_thinking_effort();
1555
+ init_nested_electron_env();
1523
1556
  init_paths();
1524
1557
  init_private_file();
1525
1558
  init_secret_vault();
@@ -2852,11 +2885,25 @@ function formatGhLandError(raw, opts) {
2852
2885
  }
2853
2886
  return detail || "Failed to create or update pull request";
2854
2887
  }
2888
+ function isPrNotMergeableError(text3) {
2889
+ 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(
2890
+ text3
2891
+ );
2892
+ }
2893
+ function formatMergePrError(raw) {
2894
+ const trimmed = raw.trim();
2895
+ if (!trimmed) return "gh pr merge failed";
2896
+ if (isPrNotMergeableError(trimmed)) {
2897
+ return "This pull request cannot merge cleanly into the base branch. Resolve conflicts or update the branch, then retry.";
2898
+ }
2899
+ return extractGhErrorDetail(trimmed) || trimmed;
2900
+ }
2855
2901
  function formatIpcInvokeError(err) {
2856
2902
  let msg = err instanceof Error ? err.message : String(err);
2857
2903
  msg = msg.replace(/^Error invoking remote method '[^']+':\s*/i, "");
2858
2904
  msg = msg.replace(/^ExecaError:\s*/i, "");
2859
2905
  msg = msg.replace(/^Error:\s*/i, "");
2906
+ if (isPrNotMergeableError(msg)) return formatMergePrError(msg);
2860
2907
  return formatGhLandError(msg);
2861
2908
  }
2862
2909
  var init_gh_errors = __esm({
@@ -3839,6 +3886,41 @@ ${result.stderr}`;
3839
3886
  ].slice(0, 20) : [];
3840
3887
  return { conflicting, base: baseName, files };
3841
3888
  }
3889
+ async function probeLocalMergeGate(cwd, gate) {
3890
+ const mergeable = (gate?.mergeable ?? "").toUpperCase();
3891
+ const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
3892
+ const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
3893
+ const inQueue = gate ? prIsInMergeQueue(gate) : false;
3894
+ if (alreadyConflicting || inQueue) return { gate, files: [] };
3895
+ try {
3896
+ const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
3897
+ if (local.conflicting) {
3898
+ return {
3899
+ gate: {
3900
+ mergeable: "CONFLICTING",
3901
+ mergeStateStatus: "DIRTY",
3902
+ reviewDecision: gate?.reviewDecision ?? null,
3903
+ baseRefName: local.base,
3904
+ url: gate?.url ?? null,
3905
+ isInMergeQueue: false
3906
+ },
3907
+ files: local.files
3908
+ };
3909
+ }
3910
+ if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
3911
+ return {
3912
+ gate: {
3913
+ ...gate,
3914
+ mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
3915
+ mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
3916
+ },
3917
+ files: []
3918
+ };
3919
+ }
3920
+ } catch {
3921
+ }
3922
+ return { gate, files: [] };
3923
+ }
3842
3924
  async function getPrChecks(cwd, selector) {
3843
3925
  const slug = await resolveGithubRepoSlug(cwd);
3844
3926
  const args = [
@@ -3874,47 +3956,9 @@ async function getPrChecks(cwd, selector) {
3874
3956
  }
3875
3957
  }
3876
3958
  let gate = await fetchPrMergeGate(cwd, selector, slug);
3877
- const mergeable = (gate?.mergeable ?? "").toUpperCase();
3878
- const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
3879
- const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
3880
- const inQueue = gate ? prIsInMergeQueue(gate) : false;
3881
- const needsLocalProbe = !alreadyConflicting && !inQueue;
3882
- if (needsLocalProbe) {
3883
- try {
3884
- const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
3885
- if (local.conflicting) {
3886
- const fileHint = local.files.length > 0 ? ` Conflicting paths: ${local.files.slice(0, 8).join(", ")}${local.files.length > 8 ? "\u2026" : ""}.` : "";
3887
- gate = {
3888
- mergeable: "CONFLICTING",
3889
- mergeStateStatus: "DIRTY",
3890
- reviewDecision: gate?.reviewDecision ?? null,
3891
- baseRefName: local.base,
3892
- url: gate?.url ?? null,
3893
- isInMergeQueue: false
3894
- };
3895
- const ciFailed2 = ciChecks.some((c) => c.bucket === "fail");
3896
- const review2 = (gate.reviewDecision ?? "").toUpperCase();
3897
- const hasReviewRow2 = review2 === "CHANGES_REQUESTED" || review2 === "REVIEW_REQUIRED";
3898
- const gateRows2 = buildMergeGateChecks(gate, {
3899
- suppressGenericBlocked: ciFailed2 || hasReviewRow2
3900
- }).map(
3901
- (row) => row.kind === "mergeability" && row.name === "Merge conflicts" ? {
3902
- ...row,
3903
- description: `${row.description ?? ""}${fileHint}`.trim()
3904
- } : row
3905
- );
3906
- return [...gateRows2, ...ciChecks];
3907
- }
3908
- if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
3909
- gate = {
3910
- ...gate,
3911
- mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
3912
- mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
3913
- };
3914
- }
3915
- } catch {
3916
- }
3917
- }
3959
+ const probed = await probeLocalMergeGate(cwd, gate);
3960
+ gate = probed.gate;
3961
+ const fileHint = probed.files.length > 0 ? ` Conflicting paths: ${probed.files.slice(0, 8).join(", ")}${probed.files.length > 8 ? "\u2026" : ""}.` : "";
3918
3962
  if (!gate) {
3919
3963
  return ciChecks;
3920
3964
  }
@@ -3924,7 +3968,12 @@ async function getPrChecks(cwd, selector) {
3924
3968
  const gateRows = buildMergeGateChecks(gate, {
3925
3969
  // Avoid duplicating "blocked" when CI failures or review rows already explain it.
3926
3970
  suppressGenericBlocked: ciFailed || hasReviewRow
3927
- });
3971
+ }).map(
3972
+ (row) => fileHint && row.kind === "mergeability" && row.name === "Merge conflicts" ? {
3973
+ ...row,
3974
+ description: `${row.description ?? ""}${fileHint}`.trim()
3975
+ } : row
3976
+ );
3928
3977
  return [...gateRows, ...ciChecks];
3929
3978
  }
3930
3979
  async function getPrMeta(cwd, selector) {
@@ -3934,7 +3983,7 @@ async function getPrMeta(cwd, selector) {
3934
3983
  "view",
3935
3984
  selector,
3936
3985
  "--json",
3937
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus"
3986
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus,mergeable"
3938
3987
  ];
3939
3988
  if (slug) viewArgs.push("--repo", slug);
3940
3989
  let { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
@@ -3944,7 +3993,7 @@ async function getPrMeta(cwd, selector) {
3944
3993
  "view",
3945
3994
  selector,
3946
3995
  "--json",
3947
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus"
3996
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus,mergeable"
3948
3997
  ];
3949
3998
  if (slug) retry.push("--repo", slug);
3950
3999
  ({ stdout, exitCode, stderr } = await gh(retry, cwd, { reject: false }));
@@ -3955,16 +4004,28 @@ async function getPrMeta(cwd, selector) {
3955
4004
  }
3956
4005
  try {
3957
4006
  const view = JSON.parse(stdout);
4007
+ const gate = {
4008
+ mergeable: typeof view.mergeable === "string" && view.mergeable ? view.mergeable : null,
4009
+ mergeStateStatus: typeof view.mergeStateStatus === "string" && view.mergeStateStatus ? view.mergeStateStatus : null,
4010
+ reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
4011
+ baseRefName: String(view.baseRefName ?? ""),
4012
+ url: String(view.url ?? ""),
4013
+ isInMergeQueue: parseInMergeQueue(view)
4014
+ };
4015
+ const probed = await probeLocalMergeGate(cwd, gate);
4016
+ const resolved = probed.gate ?? gate;
3958
4017
  return {
3959
4018
  number: Number(view.number),
3960
4019
  title: String(view.title ?? ""),
3961
4020
  url: String(view.url ?? ""),
3962
4021
  state: String(view.state ?? ""),
3963
4022
  isDraft: Boolean(view.isDraft),
3964
- reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
3965
- baseRefName: String(view.baseRefName ?? ""),
4023
+ reviewDecision: resolved.reviewDecision,
4024
+ baseRefName: resolved.baseRefName || String(view.baseRefName ?? ""),
3966
4025
  headRefName: String(view.headRefName ?? ""),
3967
- isInMergeQueue: parseInMergeQueue(view)
4026
+ isInMergeQueue: Boolean(resolved.isInMergeQueue),
4027
+ mergeable: resolved.mergeable,
4028
+ mergeStateStatus: resolved.mergeStateStatus
3968
4029
  };
3969
4030
  } catch {
3970
4031
  return null;
@@ -4432,7 +4493,9 @@ async function mergePr(cwd, selector, opts) {
4432
4493
  if (slug) args.push("--repo", slug);
4433
4494
  const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
4434
4495
  if (exitCode !== 0) {
4435
- throw new Error(stderr.trim() || stdout.trim() || "gh pr merge failed");
4496
+ throw new Error(
4497
+ formatMergePrError(stderr.trim() || stdout.trim() || "gh pr merge failed")
4498
+ );
4436
4499
  }
4437
4500
  const after = await gh(viewArgs, cwd, { reject: false });
4438
4501
  if (after.exitCode === 0 && after.stdout.trim()) {
@@ -4769,6 +4832,7 @@ var init_coordinator_prompt = __esm({
4769
4832
  "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
4770
4833
  "- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
4771
4834
  "- list_threads / get_thread \u2014 fleet status (what is going on)",
4835
+ "- 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.",
4772
4836
  "- 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.",
4773
4837
  "Workspaces:",
4774
4838
  "- add_workspace / remove_workspace \u2014 register or unregister a git repo",
@@ -4788,7 +4852,11 @@ var init_coordinator_prompt = __esm({
4788
4852
  '- 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',
4789
4853
  "- 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.",
4790
4854
  '- 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.',
4791
- '- 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.',
4855
+ '- 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.',
4856
+ "Process guides:",
4857
+ "- 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.",
4858
+ "- 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.",
4859
+ "- 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.",
4792
4860
  "Human-only (do not attempt): ready-for-review land (confirm_land), purge_thread.",
4793
4861
  "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.",
4794
4862
  "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."
@@ -6136,6 +6204,13 @@ var init_profile = __esm({
6136
6204
  function isAsarPath(filePath) {
6137
6205
  return /\.asar([/\\]|$)/.test(filePath);
6138
6206
  }
6207
+ function applyNodeLaunch(launch, args) {
6208
+ if (!launch.env.ELECTRON_RUN_AS_NODE) {
6209
+ return { file: launch.file, args, env: launch.env };
6210
+ }
6211
+ const wrapped = wrapElectronAsNodeLaunch(launch.file, args);
6212
+ return { file: wrapped.file, args: wrapped.args, env: launch.env };
6213
+ }
6139
6214
  async function resolveNodeLaunch(scriptPath) {
6140
6215
  if (isAsarPath(scriptPath)) {
6141
6216
  return {
@@ -6156,6 +6231,7 @@ async function resolveNodeLaunch(scriptPath) {
6156
6231
  var init_node_launch = __esm({
6157
6232
  "src/agents/node-launch.ts"() {
6158
6233
  "use strict";
6234
+ init_nested_electron_env();
6159
6235
  init_run();
6160
6236
  }
6161
6237
  });
@@ -6282,11 +6358,11 @@ async function resolveSideboardMcpServer() {
6282
6358
  if (entry) {
6283
6359
  const isCli = /[/\\]cli[/\\]dist[/\\]index\.js$/.test(entry);
6284
6360
  const scriptArgs = isCli ? [entry, "mcp"] : [entry];
6285
- const launch = await resolveNodeLaunch(entry);
6361
+ const launch = applyNodeLaunch(await resolveNodeLaunch(entry), scriptArgs);
6286
6362
  return {
6287
6363
  name: "sideboard",
6288
6364
  command: launch.file,
6289
- args: scriptArgs,
6365
+ args: launch.args,
6290
6366
  ...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
6291
6367
  };
6292
6368
  }
@@ -7498,10 +7574,13 @@ var init_cursor = __esm({
7498
7574
  };
7499
7575
  const runner = cursorRunnerPath();
7500
7576
  const isTs = runner.endsWith(".ts");
7501
- const launch = await resolveNodeLaunch(runner);
7577
+ const launch = applyNodeLaunch(
7578
+ await resolveNodeLaunch(runner),
7579
+ isTs ? ["--import", "tsx", runner] : [runner]
7580
+ );
7502
7581
  return {
7503
7582
  file: launch.file,
7504
- args: isTs ? ["--import", "tsx", runner] : [runner],
7583
+ args: launch.args,
7505
7584
  cwd: thread.worktreePath,
7506
7585
  stdin: JSON.stringify(req),
7507
7586
  env: {
@@ -8575,6 +8654,7 @@ __export(index_exports, {
8575
8654
  REVIEW_REQUEST_NAME: () => REVIEW_REQUEST_NAME,
8576
8655
  REVIEW_REQUEST_PATH: () => REVIEW_REQUEST_PATH,
8577
8656
  REVIEW_REQUEST_PREFILL: () => REVIEW_REQUEST_PREFILL,
8657
+ REVIEW_REQUEST_TEMPLATE: () => REVIEW_REQUEST_TEMPLATE,
8578
8658
  SIDEBOARD_FORCE_STOP: () => SIDEBOARD_FORCE_STOP,
8579
8659
  SIDEBOARD_MCP_ALLOWED_TOOLS: () => SIDEBOARD_MCP_ALLOWED_TOOLS,
8580
8660
  SIDEBOARD_MCP_PROFILE_ENV: () => SIDEBOARD_MCP_PROFILE_ENV,
@@ -8723,9 +8803,11 @@ __export(index_exports, {
8723
8803
  formatGhLandError: () => formatGhLandError,
8724
8804
  formatGitAuthModeDirective: () => formatGitAuthModeDirective,
8725
8805
  formatIpcInvokeError: () => formatIpcInvokeError,
8806
+ formatMergePrError: () => formatMergePrError,
8726
8807
  formatMessagesAsTranscript: () => formatMessagesAsTranscript,
8727
8808
  formatPlanQuestionAnswers: () => formatPlanQuestionAnswers,
8728
8809
  formatPlanQuestionsForChat: () => formatPlanQuestionsForChat,
8810
+ formatProcessGuideDirective: () => formatProcessGuideDirective,
8729
8811
  formatRateLimitResetHint: () => formatRateLimitResetHint,
8730
8812
  formatRenameBranchDirective: () => formatRenameBranchDirective,
8731
8813
  formatSlackExternalReplyPrompt: () => formatSlackExternalReplyPrompt,
@@ -8733,6 +8815,7 @@ __export(index_exports, {
8733
8815
  formatSlackRepliesForTurn: () => formatSlackRepliesForTurn,
8734
8816
  formatSlackSignedReply: () => formatSlackSignedReply,
8735
8817
  formatTranscriptMarkdown: () => formatTranscriptMarkdown,
8818
+ formatUiReminder: () => formatUiReminder,
8736
8819
  formatWorkspaceInventory: () => formatWorkspaceInventory,
8737
8820
  formatWorktreeDirective: () => formatWorktreeDirective,
8738
8821
  formatWorktreeReminder: () => formatWorktreeReminder,
@@ -8807,6 +8890,7 @@ __export(index_exports, {
8807
8890
  isOrchestratorThread: () => isOrchestratorThread,
8808
8891
  isPidAlive: () => isPidAlive,
8809
8892
  isPlaceholderBranch: () => isPlaceholderBranch,
8893
+ isPrNotMergeableError: () => isPrNotMergeableError,
8810
8894
  isPresentPlanToolName: () => isPresentPlanToolName,
8811
8895
  isSessionQuotaLimit: () => isSessionQuotaLimit,
8812
8896
  isSideboardScratchPath: () => isSideboardScratchPath,
@@ -9073,7 +9157,8 @@ function agentGitPrompt(action, opts) {
9073
9157
  return "Commit, push, and open a PR in the browser.";
9074
9158
  case "resolve-conflicts": {
9075
9159
  const base = opts?.prBase?.trim().replace(/^refs\/heads\//, "");
9076
- return base ? `Merge origin/${base} into this branch. Then push.` : "Fix merge conflicts.";
9160
+ const named = base ? ` (${base})` : "";
9161
+ return `Merge the remote branch${named} into your branch and resolve conflicts. Then, commit and push your changes.`;
9077
9162
  }
9078
9163
  case "merge":
9079
9164
  return "Merge PR.";
@@ -10249,7 +10334,7 @@ function formatWorktreeDirective(thread, opts) {
10249
10334
  '- "Fix CI: <name>." \u2192 investigate that failing check, fix it, commit, and push.'
10250
10335
  );
10251
10336
  lines.push(
10252
- '- "Update the branch." / "Fix merge conflicts." / "Merge origin/<base> into this branch. Then push." \u2192 sync with the PR base (merge or rebase), resolve conflicts carefully, commit, and push until the PR is mergeable.'
10337
+ '- "Merge the remote branch (<base>) into your branch and resolve conflicts. Then, commit and push your changes." \u2192 fetch the PR base, merge it into this branch, resolve conflicts carefully, commit, and push until the PR is mergeable.'
10253
10338
  );
10254
10339
  lines.push(
10255
10340
  '- "Address review comments." \u2192 read PR review feedback, make the requested changes, commit, and push.'
@@ -10257,11 +10342,22 @@ function formatWorktreeDirective(thread, opts) {
10257
10342
  lines.push(
10258
10343
  '- "Merge PR." \u2192 merge this thread\'s open pull request on GitHub (this phrase is the explicit ask). If `gh stack view` shows a stack, use `gh stack merge`; otherwise `gh pr merge` (respect repo defaults / squash vs merge). Do not force-push main/master or merge locally into the main checkout.'
10259
10344
  );
10345
+ lines.push("");
10346
+ lines.push(formatProcessGuideDirective());
10260
10347
  return lines.join("\n");
10261
10348
  }
10262
10349
  function formatWorktreeReminder() {
10263
10350
  return "Sideboard worktree: stay in this cwd for all file and git work. Push and open PRs against origin, never upstream. Do not edit the main repo checkout.";
10264
10351
  }
10352
+ function formatProcessGuideDirective() {
10353
+ return [
10354
+ "Process guides (recurring work only):",
10355
+ "- If `.claude/skills/graph-engineering/SKILL.md` exists, follow it (`/graph-engineering`) for migrations, ports, batch fixes, and other fan-out. Judge first; state on disk; grow the rulebook; do not patch around it.",
10356
+ "- If this same shape of work will happen again, write `.claude/skills/<kebab-name>/SKILL.md` in this worktree (Claude Code project skill). Sideboard `/name`, Claude Code, and `attach` all load that path. Do not leave the method only in chat.",
10357
+ "- Do not write new skills under `.sideboard/skills` \u2014 native agents do not scan it. Point Codex/OpenCode at the file from `AGENTS.md`. Optional: symlink `.cursor/skills/<name>` to the Claude skill.",
10358
+ "- Skip a guide for a one-off. If a matching skill exists, follow it. Same miss twice \u2192 edit the skill (or `.sideboard/review.md`), do not patch around it."
10359
+ ].join("\n");
10360
+ }
10265
10361
  function formatArtifactDirective() {
10266
10362
  return [
10267
10363
  "Sideboard side column (desktop UI):",
@@ -10279,9 +10375,22 @@ function formatArtifactDirective() {
10279
10375
  "Files / media browser (CMS file manager column):",
10280
10376
  "4) Call Sideboard MCP `present_files` with optional title, path, and datasource (brightsy|memory).",
10281
10377
  " Opens the Files column for browse/upload/pick. Do NOT say a file manager UI is missing.",
10282
- "Never say artifacts, CMS UI, or the Files column are unavailable. Prefer present_schema for list/edit/publish; present_files for storage UI; html fences for standalone pages."
10378
+ "Multiple-choice questions (any mode \u2014 not only Plan):",
10379
+ "5) Call Sideboard MCP `ask_user` when the user should pick from predefined options (approach forks, requirements, which API). First write a short chat message that explains the decision and what each option means (tradeoffs, when to pick it). Include a description on every option. After calling, stop and wait for their next message with answers. Do not ask multiple-choice questions as plain chat bullets \u2014 use ask_user so Sideboard shows the composer picker.",
10380
+ "Never say artifacts, CMS UI, or the Files column are unavailable. Prefer present_schema for list/edit/publish; present_files for storage UI; html fences for standalone pages; ask_user for predefined-option questions."
10283
10381
  ].join("\n");
10284
10382
  }
10383
+ function formatUiReminder() {
10384
+ return [
10385
+ "Sideboard side column (important):",
10386
+ "There is no claude.ai Artifact tool here \u2014 that is normal.",
10387
+ "HTML/SVG/markdown: ```html fence or MCP present_artifact.",
10388
+ "CMS forms/tables: MCP present_schema (Brightsy resource_id or inline schema+schemaUi).",
10389
+ "Files column: MCP present_files (brightsy account storage or memory).",
10390
+ "Do not say artifacts/CMS UI are unavailable.",
10391
+ "Multiple-choice questions: MCP ask_user (composer picker, any mode). Explain options in chat first, then wait for answers."
10392
+ ].join(" ");
10393
+ }
10285
10394
  var FILES_BY_AGENT = {
10286
10395
  claude: [
10287
10396
  "CLAUDE.md",
@@ -10395,6 +10504,8 @@ var import_node_path18 = require("path");
10395
10504
  var import_execa3 = require("execa");
10396
10505
  var import_node_readline2 = require("readline");
10397
10506
  init_settings();
10507
+ init_nested_electron_env();
10508
+ init_nested_electron_env();
10398
10509
  var PORT_RANGE_SIZE = 10;
10399
10510
  function matchSimpleGlob(pattern, name) {
10400
10511
  const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
@@ -10478,16 +10589,6 @@ async function captureLoginEnv() {
10478
10589
  cachedLoginEnv = { ...process.env };
10479
10590
  return { ...cachedLoginEnv };
10480
10591
  }
10481
- var NESTED_ELECTRON_ENV_PREFIXES = ["ELECTRON_", "CHROME_"];
10482
- function stripNestedElectronEnv(env) {
10483
- const out = { ...env };
10484
- for (const key of Object.keys(out)) {
10485
- if (NESTED_ELECTRON_ENV_PREFIXES.some((prefix) => key.startsWith(prefix))) {
10486
- delete out[key];
10487
- }
10488
- }
10489
- return out;
10490
- }
10491
10592
  function buildWorkspaceScriptEnv(opts, baseEnv) {
10492
10593
  const env = stripNestedElectronEnv({
10493
10594
  ...baseEnv ?? process.env
@@ -14278,6 +14379,10 @@ File: src/client/frontends/desktop/core/UserData.ts
14278
14379
 
14279
14380
  **Approve** \u2014 Diff is scoped, behavior looks correct, and there are no blocking issues. Safe to merge.
14280
14381
  </example>
14382
+
14383
+ ## Growing the rules
14384
+
14385
+ If a blocking issue is a missing or ambiguous repo rule that will recur, say so and propose one sentence for \`.sideboard/review.md\` or a \`.claude/skills/<name>/SKILL.md\`. Do not only patch this diff when the same miss will happen again. New skills go under \`.claude/skills\` (Claude Code / attach) \u2014 not \`.sideboard/skills\`.
14281
14386
  `;
14282
14387
 
14283
14388
  // src/review/request-review.ts
@@ -15078,14 +15183,7 @@ var Orchestrator = class {
15078
15183
  SLACK_REPLY_FORMATTING
15079
15184
  ].join("\n") : null
15080
15185
  ].filter(Boolean).join("\n") : null;
15081
- const artifactReminder = thread.agent !== "brightsy" ? [
15082
- "Sideboard side column (important):",
15083
- "There is no claude.ai Artifact tool here \u2014 that is normal.",
15084
- "HTML/SVG/markdown: ```html fence or MCP present_artifact.",
15085
- "CMS forms/tables: MCP present_schema (Brightsy resource_id or inline schema+schemaUi).",
15086
- "Files column: MCP present_files (brightsy account storage or memory).",
15087
- "Do not say artifacts/CMS UI are unavailable."
15088
- ].join(" ") : null;
15186
+ const artifactReminder = thread.agent !== "brightsy" ? formatUiReminder() : null;
15089
15187
  const worktreeReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatWorktreeReminder() : null;
15090
15188
  const slackReplyContext = formatSlackRepliesForTurn(
15091
15189
  pendingSlackExternalReplies(thread.messages)
@@ -15779,7 +15877,9 @@ var Orchestrator = class {
15779
15877
  reviewDecision: null,
15780
15878
  baseRefName: "",
15781
15879
  headRefName: "",
15782
- isInMergeQueue: false
15880
+ isInMergeQueue: false,
15881
+ mergeable: null,
15882
+ mergeStateStatus: null
15783
15883
  };
15784
15884
  await this.persistPrMetaAndMaybeArchive(thread, metaLike);
15785
15885
  return { url: metaLike.url, state };
@@ -17141,7 +17241,7 @@ async function startMcpServer() {
17141
17241
  );
17142
17242
  server.tool(
17143
17243
  "ask_user",
17144
- "Ask the user clarifying multiple-choice questions in Sideboard\u2019s composer (plan mode). Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. Use for approach forks and requirements \u2014 not for \u201Cis the plan ready?\u201D. After calling, stop and wait for their next message with answers.",
17244
+ "Ask the user clarifying multiple-choice questions in Sideboard\u2019s composer (any mode \u2014 not only Plan). Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. Use when the user should pick from predefined options (approach forks, requirements, which API) \u2014 not for \u201Cis the plan ready?\u201D. After calling, stop and wait for their next message with answers.",
17145
17245
  {
17146
17246
  questions: import_zod3.z.array(
17147
17247
  import_zod3.z.object({
@@ -20018,6 +20118,7 @@ async function startSlackRelayServer(opts) {
20018
20118
  REVIEW_REQUEST_NAME,
20019
20119
  REVIEW_REQUEST_PATH,
20020
20120
  REVIEW_REQUEST_PREFILL,
20121
+ REVIEW_REQUEST_TEMPLATE,
20021
20122
  SIDEBOARD_FORCE_STOP,
20022
20123
  SIDEBOARD_MCP_ALLOWED_TOOLS,
20023
20124
  SIDEBOARD_MCP_PROFILE_ENV,
@@ -20166,9 +20267,11 @@ async function startSlackRelayServer(opts) {
20166
20267
  formatGhLandError,
20167
20268
  formatGitAuthModeDirective,
20168
20269
  formatIpcInvokeError,
20270
+ formatMergePrError,
20169
20271
  formatMessagesAsTranscript,
20170
20272
  formatPlanQuestionAnswers,
20171
20273
  formatPlanQuestionsForChat,
20274
+ formatProcessGuideDirective,
20172
20275
  formatRateLimitResetHint,
20173
20276
  formatRenameBranchDirective,
20174
20277
  formatSlackExternalReplyPrompt,
@@ -20176,6 +20279,7 @@ async function startSlackRelayServer(opts) {
20176
20279
  formatSlackRepliesForTurn,
20177
20280
  formatSlackSignedReply,
20178
20281
  formatTranscriptMarkdown,
20282
+ formatUiReminder,
20179
20283
  formatWorkspaceInventory,
20180
20284
  formatWorktreeDirective,
20181
20285
  formatWorktreeReminder,
@@ -20250,6 +20354,7 @@ async function startSlackRelayServer(opts) {
20250
20354
  isOrchestratorThread,
20251
20355
  isPidAlive,
20252
20356
  isPlaceholderBranch,
20357
+ isPrNotMergeableError,
20253
20358
  isPresentPlanToolName,
20254
20359
  isSessionQuotaLimit,
20255
20360
  isSideboardScratchPath,