@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
@@ -31,6 +31,43 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  mod
32
32
  ));
33
33
 
34
+ // src/hook/nested-electron-env.ts
35
+ function isNestedElectronEnvKey(key) {
36
+ return NESTED_ELECTRON_ENV_PREFIXES.some((prefix) => key.startsWith(prefix));
37
+ }
38
+ function stripNestedElectronEnv(env) {
39
+ const out = { ...env };
40
+ for (const key of Object.keys(out)) {
41
+ if (isNestedElectronEnvKey(key)) delete out[key];
42
+ }
43
+ return out;
44
+ }
45
+ function dropNestedElectronEnvFromProcess(env = process.env) {
46
+ for (const key of Object.keys(env)) {
47
+ if (isNestedElectronEnvKey(key)) delete env[key];
48
+ }
49
+ }
50
+ function wrapElectronAsNodeLaunch(file, args) {
51
+ if (process.platform === "win32") return { file, args };
52
+ return {
53
+ file: "/bin/sh",
54
+ args: ["-c", STRIP_NESTED_ELECTRON_THEN_EXEC, "sh", file, ...args]
55
+ };
56
+ }
57
+ var NESTED_ELECTRON_ENV_PREFIXES, STRIP_NESTED_ELECTRON_THEN_EXEC;
58
+ var init_nested_electron_env = __esm({
59
+ "src/hook/nested-electron-env.ts"() {
60
+ "use strict";
61
+ NESTED_ELECTRON_ENV_PREFIXES = ["ELECTRON_", "CHROME_"];
62
+ STRIP_NESTED_ELECTRON_THEN_EXEC = [
63
+ "vars=`printenv | awk -F= '/^(ELECTRON_|CHROME_)/{print $1}'`",
64
+ '[ -n "$vars" ] && unset $vars',
65
+ "export ELECTRON_RUN_AS_NODE=1",
66
+ 'exec "$@"'
67
+ ].join("; ");
68
+ }
69
+ });
70
+
34
71
  // src/hook/settings.ts
35
72
  function expandHome(path) {
36
73
  if (path.startsWith("~/") || path === "~") {
@@ -1649,6 +1686,19 @@ function formatGhLandError(raw, opts) {
1649
1686
  }
1650
1687
  return detail || "Failed to create or update pull request";
1651
1688
  }
1689
+ function isPrNotMergeableError(text3) {
1690
+ 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(
1691
+ text3
1692
+ );
1693
+ }
1694
+ function formatMergePrError(raw) {
1695
+ const trimmed = raw.trim();
1696
+ if (!trimmed) return "gh pr merge failed";
1697
+ if (isPrNotMergeableError(trimmed)) {
1698
+ return "This pull request cannot merge cleanly into the base branch. Resolve conflicts or update the branch, then retry.";
1699
+ }
1700
+ return extractGhErrorDetail(trimmed) || trimmed;
1701
+ }
1652
1702
  var init_gh_errors = __esm({
1653
1703
  "src/git/gh-errors.ts"() {
1654
1704
  "use strict";
@@ -2855,7 +2905,7 @@ function applyAppEnvironment(target = process.env, settings = loadAppSettings())
2855
2905
  }
2856
2906
  function childEnvWithAppSettings(extra) {
2857
2907
  const settings = loadAppSettings();
2858
- const env = { ...process.env };
2908
+ const env = stripNestedElectronEnv({ ...process.env });
2859
2909
  applyAppEnvironment(env, settings);
2860
2910
  if (extra) {
2861
2911
  for (const [k, v] of Object.entries(extra)) {
@@ -2876,6 +2926,7 @@ var init_app_settings = __esm({
2876
2926
  import_node_os4 = require("os");
2877
2927
  import_node_path10 = require("path");
2878
2928
  init_thinking_effort();
2929
+ init_nested_electron_env();
2879
2930
  init_paths();
2880
2931
  init_private_file();
2881
2932
  init_secret_vault();
@@ -3615,6 +3666,41 @@ ${result.stderr}`;
3615
3666
  ].slice(0, 20) : [];
3616
3667
  return { conflicting, base: baseName, files };
3617
3668
  }
3669
+ async function probeLocalMergeGate(cwd, gate) {
3670
+ const mergeable = (gate?.mergeable ?? "").toUpperCase();
3671
+ const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
3672
+ const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
3673
+ const inQueue = gate ? prIsInMergeQueue(gate) : false;
3674
+ if (alreadyConflicting || inQueue) return { gate, files: [] };
3675
+ try {
3676
+ const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
3677
+ if (local.conflicting) {
3678
+ return {
3679
+ gate: {
3680
+ mergeable: "CONFLICTING",
3681
+ mergeStateStatus: "DIRTY",
3682
+ reviewDecision: gate?.reviewDecision ?? null,
3683
+ baseRefName: local.base,
3684
+ url: gate?.url ?? null,
3685
+ isInMergeQueue: false
3686
+ },
3687
+ files: local.files
3688
+ };
3689
+ }
3690
+ if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
3691
+ return {
3692
+ gate: {
3693
+ ...gate,
3694
+ mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
3695
+ mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
3696
+ },
3697
+ files: []
3698
+ };
3699
+ }
3700
+ } catch {
3701
+ }
3702
+ return { gate, files: [] };
3703
+ }
3618
3704
  async function getPrChecks(cwd, selector) {
3619
3705
  const slug = await resolveGithubRepoSlug(cwd);
3620
3706
  const args = [
@@ -3650,47 +3736,9 @@ async function getPrChecks(cwd, selector) {
3650
3736
  }
3651
3737
  }
3652
3738
  let gate = await fetchPrMergeGate(cwd, selector, slug);
3653
- const mergeable = (gate?.mergeable ?? "").toUpperCase();
3654
- const mergeState = (gate?.mergeStateStatus ?? "").toUpperCase();
3655
- const alreadyConflicting = mergeable === "CONFLICTING" || mergeState === "DIRTY";
3656
- const inQueue = gate ? prIsInMergeQueue(gate) : false;
3657
- const needsLocalProbe = !alreadyConflicting && !inQueue;
3658
- if (needsLocalProbe) {
3659
- try {
3660
- const local = await detectLocalMergeConflicts(cwd, gate?.baseRefName ?? null);
3661
- if (local.conflicting) {
3662
- const fileHint = local.files.length > 0 ? ` Conflicting paths: ${local.files.slice(0, 8).join(", ")}${local.files.length > 8 ? "\u2026" : ""}.` : "";
3663
- gate = {
3664
- mergeable: "CONFLICTING",
3665
- mergeStateStatus: "DIRTY",
3666
- reviewDecision: gate?.reviewDecision ?? null,
3667
- baseRefName: local.base,
3668
- url: gate?.url ?? null,
3669
- isInMergeQueue: false
3670
- };
3671
- const ciFailed2 = ciChecks.some((c) => c.bucket === "fail");
3672
- const review2 = (gate.reviewDecision ?? "").toUpperCase();
3673
- const hasReviewRow2 = review2 === "CHANGES_REQUESTED" || review2 === "REVIEW_REQUIRED";
3674
- const gateRows2 = buildMergeGateChecks(gate, {
3675
- suppressGenericBlocked: ciFailed2 || hasReviewRow2
3676
- }).map(
3677
- (row) => row.kind === "mergeability" && row.name === "Merge conflicts" ? {
3678
- ...row,
3679
- description: `${row.description ?? ""}${fileHint}`.trim()
3680
- } : row
3681
- );
3682
- return [...gateRows2, ...ciChecks];
3683
- }
3684
- if (gate && (mergeable === "UNKNOWN" || mergeState === "UNKNOWN")) {
3685
- gate = {
3686
- ...gate,
3687
- mergeable: gate.mergeable === "UNKNOWN" ? "MERGEABLE" : gate.mergeable,
3688
- mergeStateStatus: gate.mergeStateStatus === "UNKNOWN" ? "CLEAN" : gate.mergeStateStatus
3689
- };
3690
- }
3691
- } catch {
3692
- }
3693
- }
3739
+ const probed = await probeLocalMergeGate(cwd, gate);
3740
+ gate = probed.gate;
3741
+ const fileHint = probed.files.length > 0 ? ` Conflicting paths: ${probed.files.slice(0, 8).join(", ")}${probed.files.length > 8 ? "\u2026" : ""}.` : "";
3694
3742
  if (!gate) {
3695
3743
  return ciChecks;
3696
3744
  }
@@ -3700,7 +3748,12 @@ async function getPrChecks(cwd, selector) {
3700
3748
  const gateRows = buildMergeGateChecks(gate, {
3701
3749
  // Avoid duplicating "blocked" when CI failures or review rows already explain it.
3702
3750
  suppressGenericBlocked: ciFailed || hasReviewRow
3703
- });
3751
+ }).map(
3752
+ (row) => fileHint && row.kind === "mergeability" && row.name === "Merge conflicts" ? {
3753
+ ...row,
3754
+ description: `${row.description ?? ""}${fileHint}`.trim()
3755
+ } : row
3756
+ );
3704
3757
  return [...gateRows, ...ciChecks];
3705
3758
  }
3706
3759
  async function getPrMeta(cwd, selector) {
@@ -3710,7 +3763,7 @@ async function getPrMeta(cwd, selector) {
3710
3763
  "view",
3711
3764
  selector,
3712
3765
  "--json",
3713
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus"
3766
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,isInMergeQueue,mergeStateStatus,mergeable"
3714
3767
  ];
3715
3768
  if (slug) viewArgs.push("--repo", slug);
3716
3769
  let { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
@@ -3720,7 +3773,7 @@ async function getPrMeta(cwd, selector) {
3720
3773
  "view",
3721
3774
  selector,
3722
3775
  "--json",
3723
- "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus"
3776
+ "number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,mergeStateStatus,mergeable"
3724
3777
  ];
3725
3778
  if (slug) retry.push("--repo", slug);
3726
3779
  ({ stdout, exitCode, stderr } = await gh(retry, cwd, { reject: false }));
@@ -3731,16 +3784,28 @@ async function getPrMeta(cwd, selector) {
3731
3784
  }
3732
3785
  try {
3733
3786
  const view = JSON.parse(stdout);
3787
+ const gate = {
3788
+ mergeable: typeof view.mergeable === "string" && view.mergeable ? view.mergeable : null,
3789
+ mergeStateStatus: typeof view.mergeStateStatus === "string" && view.mergeStateStatus ? view.mergeStateStatus : null,
3790
+ reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
3791
+ baseRefName: String(view.baseRefName ?? ""),
3792
+ url: String(view.url ?? ""),
3793
+ isInMergeQueue: parseInMergeQueue(view)
3794
+ };
3795
+ const probed = await probeLocalMergeGate(cwd, gate);
3796
+ const resolved = probed.gate ?? gate;
3734
3797
  return {
3735
3798
  number: Number(view.number),
3736
3799
  title: String(view.title ?? ""),
3737
3800
  url: String(view.url ?? ""),
3738
3801
  state: String(view.state ?? ""),
3739
3802
  isDraft: Boolean(view.isDraft),
3740
- reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
3741
- baseRefName: String(view.baseRefName ?? ""),
3803
+ reviewDecision: resolved.reviewDecision,
3804
+ baseRefName: resolved.baseRefName || String(view.baseRefName ?? ""),
3742
3805
  headRefName: String(view.headRefName ?? ""),
3743
- isInMergeQueue: parseInMergeQueue(view)
3806
+ isInMergeQueue: Boolean(resolved.isInMergeQueue),
3807
+ mergeable: resolved.mergeable,
3808
+ mergeStateStatus: resolved.mergeStateStatus
3744
3809
  };
3745
3810
  } catch {
3746
3811
  return null;
@@ -4208,7 +4273,9 @@ async function mergePr(cwd, selector, opts) {
4208
4273
  if (slug) args.push("--repo", slug);
4209
4274
  const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
4210
4275
  if (exitCode !== 0) {
4211
- throw new Error(stderr.trim() || stdout.trim() || "gh pr merge failed");
4276
+ throw new Error(
4277
+ formatMergePrError(stderr.trim() || stdout.trim() || "gh pr merge failed")
4278
+ );
4212
4279
  }
4213
4280
  const after = await gh(viewArgs, cwd, { reject: false });
4214
4281
  if (after.exitCode === 0 && after.stdout.trim()) {
@@ -4603,6 +4670,7 @@ var init_coordinator_prompt = __esm({
4603
4670
  "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
4604
4671
  "- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
4605
4672
  "- list_threads / get_thread \u2014 fleet status (what is going on)",
4673
+ "- 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.",
4606
4674
  "- 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.",
4607
4675
  "Workspaces:",
4608
4676
  "- add_workspace / remove_workspace \u2014 register or unregister a git repo",
@@ -4622,7 +4690,11 @@ var init_coordinator_prompt = __esm({
4622
4690
  '- 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',
4623
4691
  "- 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.",
4624
4692
  '- 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.',
4625
- '- 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.',
4693
+ '- 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.',
4694
+ "Process guides:",
4695
+ "- 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.",
4696
+ "- 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.",
4697
+ "- 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.",
4626
4698
  "Human-only (do not attempt): ready-for-review land (confirm_land), purge_thread.",
4627
4699
  "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.",
4628
4700
  "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."
@@ -5528,6 +5600,13 @@ var init_profile = __esm({
5528
5600
  function isAsarPath(filePath) {
5529
5601
  return /\.asar([/\\]|$)/.test(filePath);
5530
5602
  }
5603
+ function applyNodeLaunch(launch, args) {
5604
+ if (!launch.env.ELECTRON_RUN_AS_NODE) {
5605
+ return { file: launch.file, args, env: launch.env };
5606
+ }
5607
+ const wrapped = wrapElectronAsNodeLaunch(launch.file, args);
5608
+ return { file: wrapped.file, args: wrapped.args, env: launch.env };
5609
+ }
5531
5610
  async function resolveNodeLaunch(scriptPath) {
5532
5611
  if (isAsarPath(scriptPath)) {
5533
5612
  return {
@@ -5548,6 +5627,7 @@ async function resolveNodeLaunch(scriptPath) {
5548
5627
  var init_node_launch = __esm({
5549
5628
  "src/agents/node-launch.ts"() {
5550
5629
  "use strict";
5630
+ init_nested_electron_env();
5551
5631
  init_run();
5552
5632
  }
5553
5633
  });
@@ -5674,11 +5754,11 @@ async function resolveSideboardMcpServer() {
5674
5754
  if (entry) {
5675
5755
  const isCli = /[/\\]cli[/\\]dist[/\\]index\.js$/.test(entry);
5676
5756
  const scriptArgs = isCli ? [entry, "mcp"] : [entry];
5677
- const launch = await resolveNodeLaunch(entry);
5757
+ const launch = applyNodeLaunch(await resolveNodeLaunch(entry), scriptArgs);
5678
5758
  return {
5679
5759
  name: "sideboard",
5680
5760
  command: launch.file,
5681
- args: scriptArgs,
5761
+ args: launch.args,
5682
5762
  ...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
5683
5763
  };
5684
5764
  }
@@ -6883,10 +6963,13 @@ var init_cursor = __esm({
6883
6963
  };
6884
6964
  const runner = cursorRunnerPath();
6885
6965
  const isTs = runner.endsWith(".ts");
6886
- const launch = await resolveNodeLaunch(runner);
6966
+ const launch = applyNodeLaunch(
6967
+ await resolveNodeLaunch(runner),
6968
+ isTs ? ["--import", "tsx", runner] : [runner]
6969
+ );
6887
6970
  return {
6888
6971
  file: launch.file,
6889
- args: isTs ? ["--import", "tsx", runner] : [runner],
6972
+ args: launch.args,
6890
6973
  cwd: thread.worktreePath,
6891
6974
  stdin: JSON.stringify(req),
6892
6975
  env: {
@@ -8175,6 +8258,9 @@ var init_cursor_recover = __esm({
8175
8258
  }
8176
8259
  });
8177
8260
 
8261
+ // src/mcp/run-stdio.ts
8262
+ init_nested_electron_env();
8263
+
8178
8264
  // src/mcp/server.ts
8179
8265
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
8180
8266
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
@@ -9150,7 +9236,8 @@ function agentGitPrompt(action, opts) {
9150
9236
  return "Commit, push, and open a PR in the browser.";
9151
9237
  case "resolve-conflicts": {
9152
9238
  const base = opts?.prBase?.trim().replace(/^refs\/heads\//, "");
9153
- return base ? `Merge origin/${base} into this branch. Then push.` : "Fix merge conflicts.";
9239
+ const named = base ? ` (${base})` : "";
9240
+ return `Merge the remote branch${named} into your branch and resolve conflicts. Then, commit and push your changes.`;
9154
9241
  }
9155
9242
  case "merge":
9156
9243
  return "Merge PR.";
@@ -9178,6 +9265,8 @@ var import_node_path18 = require("path");
9178
9265
  var import_execa3 = require("execa");
9179
9266
  var import_node_readline2 = require("readline");
9180
9267
  init_settings();
9268
+ init_nested_electron_env();
9269
+ init_nested_electron_env();
9181
9270
  var PORT_RANGE_SIZE = 10;
9182
9271
  function matchSimpleGlob(pattern, name) {
9183
9272
  const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
@@ -9261,16 +9350,6 @@ async function captureLoginEnv() {
9261
9350
  cachedLoginEnv = { ...process.env };
9262
9351
  return { ...cachedLoginEnv };
9263
9352
  }
9264
- var NESTED_ELECTRON_ENV_PREFIXES = ["ELECTRON_", "CHROME_"];
9265
- function stripNestedElectronEnv(env) {
9266
- const out = { ...env };
9267
- for (const key of Object.keys(out)) {
9268
- if (NESTED_ELECTRON_ENV_PREFIXES.some((prefix) => key.startsWith(prefix))) {
9269
- delete out[key];
9270
- }
9271
- }
9272
- return out;
9273
- }
9274
9353
  function buildWorkspaceScriptEnv(opts, baseEnv) {
9275
9354
  const env = stripNestedElectronEnv({
9276
9355
  ...baseEnv ?? process.env
@@ -10421,6 +10500,10 @@ File: src/client/frontends/desktop/core/UserData.ts
10421
10500
 
10422
10501
  **Approve** \u2014 Diff is scoped, behavior looks correct, and there are no blocking issues. Safe to merge.
10423
10502
  </example>
10503
+
10504
+ ## Growing the rules
10505
+
10506
+ 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\`.
10424
10507
  `;
10425
10508
 
10426
10509
  // src/review/request-review.ts
@@ -12590,7 +12673,7 @@ function formatWorktreeDirective(thread, opts) {
12590
12673
  '- "Fix CI: <name>." \u2192 investigate that failing check, fix it, commit, and push.'
12591
12674
  );
12592
12675
  lines.push(
12593
- '- "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.'
12676
+ '- "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.'
12594
12677
  );
12595
12678
  lines.push(
12596
12679
  '- "Address review comments." \u2192 read PR review feedback, make the requested changes, commit, and push.'
@@ -12598,11 +12681,22 @@ function formatWorktreeDirective(thread, opts) {
12598
12681
  lines.push(
12599
12682
  '- "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.'
12600
12683
  );
12684
+ lines.push("");
12685
+ lines.push(formatProcessGuideDirective());
12601
12686
  return lines.join("\n");
12602
12687
  }
12603
12688
  function formatWorktreeReminder() {
12604
12689
  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.";
12605
12690
  }
12691
+ function formatProcessGuideDirective() {
12692
+ return [
12693
+ "Process guides (recurring work only):",
12694
+ "- 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.",
12695
+ "- 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.",
12696
+ "- 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.",
12697
+ "- 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."
12698
+ ].join("\n");
12699
+ }
12606
12700
  function formatArtifactDirective() {
12607
12701
  return [
12608
12702
  "Sideboard side column (desktop UI):",
@@ -12620,9 +12714,22 @@ function formatArtifactDirective() {
12620
12714
  "Files / media browser (CMS file manager column):",
12621
12715
  "4) Call Sideboard MCP `present_files` with optional title, path, and datasource (brightsy|memory).",
12622
12716
  " Opens the Files column for browse/upload/pick. Do NOT say a file manager UI is missing.",
12623
- "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."
12717
+ "Multiple-choice questions (any mode \u2014 not only Plan):",
12718
+ "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.",
12719
+ "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."
12624
12720
  ].join("\n");
12625
12721
  }
12722
+ function formatUiReminder() {
12723
+ return [
12724
+ "Sideboard side column (important):",
12725
+ "There is no claude.ai Artifact tool here \u2014 that is normal.",
12726
+ "HTML/SVG/markdown: ```html fence or MCP present_artifact.",
12727
+ "CMS forms/tables: MCP present_schema (Brightsy resource_id or inline schema+schemaUi).",
12728
+ "Files column: MCP present_files (brightsy account storage or memory).",
12729
+ "Do not say artifacts/CMS UI are unavailable.",
12730
+ "Multiple-choice questions: MCP ask_user (composer picker, any mode). Explain options in chat first, then wait for answers."
12731
+ ].join(" ");
12732
+ }
12626
12733
 
12627
12734
  // src/orchestrator/orchestrator.ts
12628
12735
  init_types();
@@ -13166,14 +13273,7 @@ var Orchestrator = class {
13166
13273
  SLACK_REPLY_FORMATTING
13167
13274
  ].join("\n") : null
13168
13275
  ].filter(Boolean).join("\n") : null;
13169
- const artifactReminder = thread.agent !== "brightsy" ? [
13170
- "Sideboard side column (important):",
13171
- "There is no claude.ai Artifact tool here \u2014 that is normal.",
13172
- "HTML/SVG/markdown: ```html fence or MCP present_artifact.",
13173
- "CMS forms/tables: MCP present_schema (Brightsy resource_id or inline schema+schemaUi).",
13174
- "Files column: MCP present_files (brightsy account storage or memory).",
13175
- "Do not say artifacts/CMS UI are unavailable."
13176
- ].join(" ") : null;
13276
+ const artifactReminder = thread.agent !== "brightsy" ? formatUiReminder() : null;
13177
13277
  const worktreeReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatWorktreeReminder() : null;
13178
13278
  const slackReplyContext = formatSlackRepliesForTurn(
13179
13279
  pendingSlackExternalReplies(thread.messages)
@@ -13867,7 +13967,9 @@ var Orchestrator = class {
13867
13967
  reviewDecision: null,
13868
13968
  baseRefName: "",
13869
13969
  headRefName: "",
13870
- isInMergeQueue: false
13970
+ isInMergeQueue: false,
13971
+ mergeable: null,
13972
+ mergeStateStatus: null
13871
13973
  };
13872
13974
  await this.persistPrMetaAndMaybeArchive(thread, metaLike);
13873
13975
  return { url: metaLike.url, state };
@@ -15541,7 +15643,7 @@ async function startMcpServer() {
15541
15643
  );
15542
15644
  server.tool(
15543
15645
  "ask_user",
15544
- "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.",
15646
+ "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.",
15545
15647
  {
15546
15648
  questions: import_zod3.z.array(
15547
15649
  import_zod3.z.object({
@@ -16455,6 +16557,7 @@ async function startMcpServer() {
16455
16557
  }
16456
16558
 
16457
16559
  // src/mcp/run-stdio.ts
16560
+ dropNestedElectronEnvFromProcess();
16458
16561
  startMcpServer().catch((err) => {
16459
16562
  console.error(err);
16460
16563
  process.exitCode = 1;