@sideboard-ai/core 0.1.51 → 0.1.52

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 (37) hide show
  1. package/dist/{agents-HIEJL3UV.js → agents-ON6RKKND.js} +1 -1
  2. package/dist/{agents-5ROTZNCX.js → agents-YKSS6VBO.js} +1 -1
  3. package/dist/{chunk-7EUWSBWR.js → chunk-6QTZVJ7A.js} +5 -3
  4. package/dist/chunk-B3SJXYIJ.js +24 -0
  5. package/dist/{chunk-5ZPSH7VI.js → chunk-D3METLRW.js} +5 -3
  6. package/dist/{chunk-ZH5QZ4CR.js → chunk-DZFH2KLT.js} +280 -7
  7. package/dist/chunk-FKOIHGKV.js +21 -0
  8. package/dist/{chunk-E4VVEKAM.js → chunk-GNML24AW.js} +2 -2
  9. package/dist/{chunk-EOYDCKQC.js → chunk-HLEX5AQ6.js} +4 -0
  10. package/dist/{chunk-K3WMKGFY.js → chunk-LRLKJM3O.js} +2 -1
  11. package/dist/chunk-N5PM7HGQ.js +103 -0
  12. package/dist/chunk-QTUESPAW.js +101 -0
  13. package/dist/{chunk-O6W3P7V3.js → chunk-TSRXOSVD.js} +4 -0
  14. package/dist/{chunk-F4Q3IM6V.js → chunk-UEAHMGHW.js} +2 -1
  15. package/dist/{chunk-XRSAGVRW.js → chunk-XOU6HNQJ.js} +245 -7
  16. package/dist/{chunk-O5DOO7DP.js → chunk-XX5BB7NV.js} +3 -3
  17. package/dist/{chunk-QN7XNQAT.js → chunk-YDXQ72MD.js} +2 -2
  18. package/dist/{chunk-YFJ4FG2P.js → chunk-YOWIYAVA.js} +3 -3
  19. package/dist/{coordinator-prompt-7HHJRO7B.js → coordinator-prompt-6FXVTSFN.js} +4 -3
  20. package/dist/{coordinator-prompt-WD7FAMA2.js → coordinator-prompt-S6JZD5EF.js} +4 -3
  21. package/dist/{global-workspace-OJEPGDXA.js → global-workspace-EV4G2WMQ.js} +5 -4
  22. package/dist/{global-workspace-ECYN2MKL.js → global-workspace-MSX2K27Y.js} +5 -4
  23. package/dist/index.cjs +1288 -140
  24. package/dist/index.d.cts +388 -15
  25. package/dist/index.d.ts +388 -15
  26. package/dist/index.js +801 -84
  27. package/dist/mcp/run-stdio.cjs +1059 -132
  28. package/dist/mcp/run-stdio.js +628 -73
  29. package/dist/plan-file-6O7G4VPQ.js +23 -0
  30. package/dist/plan-file-PHVKUAEE.js +25 -0
  31. package/dist/{thread-store-XICUWFNM.js → thread-store-GHOADGL2.js} +1 -1
  32. package/dist/{thread-store-OV2X6PYO.js → thread-store-UJIGMI5J.js} +1 -1
  33. package/dist/{workspaces-MUU7RGVV.js → workspaces-3RQQZQRO.js} +6 -5
  34. package/dist/{workspaces-ZWOOFZUV.js → workspaces-AYTBR6KQ.js} +6 -5
  35. package/dist/{worktree-DVNDMWZ7.js → worktree-5KEQWSAF.js} +5 -2
  36. package/dist/{worktree-GDV56MX4.js → worktree-RWGL7FUV.js} +5 -2
  37. package/package.json +1 -1
@@ -30,7 +30,7 @@ import {
30
30
  permissionMode,
31
31
  resolveCursorModelId,
32
32
  resolveQuotaFallbackAgent
33
- } from "./chunk-7EUWSBWR.js";
33
+ } from "./chunk-6QTZVJ7A.js";
34
34
  import "./chunk-H6GGDLYS.js";
35
35
  import "./chunk-HBJSHRY2.js";
36
36
  import {
@@ -26,7 +26,7 @@ import {
26
26
  permissionMode,
27
27
  resolveCursorModelId,
28
28
  resolveQuotaFallbackAgent
29
- } from "./chunk-5ZPSH7VI.js";
29
+ } from "./chunk-D3METLRW.js";
30
30
  import {
31
31
  ORCHESTRATOR_AGENT_KINDS,
32
32
  assertOrchestratorCapableAgent,
@@ -534,7 +534,9 @@ var SIDEBOARD_MCP_ALLOWED_TOOLS = [
534
534
  var SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS = [
535
535
  "mcp__sideboard__present_artifact",
536
536
  "mcp__sideboard__present_schema",
537
- "mcp__sideboard__present_files"
537
+ "mcp__sideboard__present_files",
538
+ "mcp__sideboard__ask_user",
539
+ "mcp__sideboard__present_plan"
538
540
  ];
539
541
  var brightsyMcpCommandCache = null;
540
542
  async function resolveBrightsyMcpCommand() {
@@ -718,7 +720,7 @@ function writeMcpServersConfig(servers) {
718
720
  }
719
721
 
720
722
  // src/agents/types.ts
721
- var PLAN_MODE_INSTRUCTION = "Plan mode is active and must remain active until the user turns Plan mode off in the UI (or explicitly asks you to implement). Analyze the codebase, search and read files as needed, and produce or refine a clear implementation plan. Do not modify, create, or delete any files. Do not exit plan mode on your own.";
723
+ var PLAN_MODE_INSTRUCTION = "Plan mode is active and must remain active until the user turns Plan mode off in the UI (or Approves / Hands off the plan). Analyze the codebase, search and read files as needed, and produce or refine a clear implementation plan. Do not modify, create, or delete any project files except via Sideboard MCP present_plan (writes .context/attachments/plan.md). When you need a clarifying decision (approach forks, auth choice, scope): (1) first write a short chat message that explains the decision and what each option means (tradeoffs, when to pick it) \u2014 do not leave the user staring at bare labels; (2) then call Sideboard MCP ask_user with the same options, including a description on every option. Sideboard shows questions in the composer and mirrors them in chat. After ask_user, wait for the user's next message with their answers before finalizing the plan. When the plan is ready for approval: (1) call present_plan with the full markdown plan (title + content) so Sideboard saves .context/attachments/plan.md and shows it in chat for Approve / Hand off / Copy; (2) Claude should also call ExitPlanMode after present_plan. Do not skip present_plan \u2014 the plan must be a markdown file, not only chat prose.";
722
724
  function permissionMode(thread) {
723
725
  if (thread.planMode) {
724
726
  return {
@@ -899,7 +901,7 @@ var claudeAdapter = {
899
901
  );
900
902
  }
901
903
  const mode = permissionMode(thread);
902
- const { isOrchestratorThread } = await import("./global-workspace-OJEPGDXA.js");
904
+ const { isOrchestratorThread } = await import("./global-workspace-EV4G2WMQ.js");
903
905
  const isOrchestrator = isOrchestratorThread(thread);
904
906
  const injectedServers = await buildInjectedMcpServers({
905
907
  includeSideboard: true,
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+
3
+
4
+ // src/paths/workspace-scratch.ts
5
+ var ATTACHMENTS_DIR = ".context/attachments";
6
+ var LEGACY_ATTACHMENTS_DIR = ".sideboard/attachments";
7
+ var ATTACHMENTS_GITIGNORE = `# Sideboard / workspace attachments (local only)
8
+ *
9
+ !.gitignore
10
+ `;
11
+ function attachmentsGitignoreBody() {
12
+ return ATTACHMENTS_GITIGNORE;
13
+ }
14
+ function isWorkspaceScratchPath(relativePath) {
15
+ const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
16
+ return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".context" || p.startsWith(".context/");
17
+ }
18
+
19
+ export {
20
+ ATTACHMENTS_DIR,
21
+ LEGACY_ATTACHMENTS_DIR,
22
+ attachmentsGitignoreBody,
23
+ isWorkspaceScratchPath
24
+ };
@@ -465,7 +465,9 @@ var SIDEBOARD_MCP_ALLOWED_TOOLS = [
465
465
  var SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS = [
466
466
  "mcp__sideboard__present_artifact",
467
467
  "mcp__sideboard__present_schema",
468
- "mcp__sideboard__present_files"
468
+ "mcp__sideboard__present_files",
469
+ "mcp__sideboard__ask_user",
470
+ "mcp__sideboard__present_plan"
469
471
  ];
470
472
  var BRIGHTSY_MCP_ALLOWED_TOOLS = [
471
473
  "mcp__brightsy",
@@ -656,7 +658,7 @@ async function writeInjectedMcpConfig(opts) {
656
658
  }
657
659
 
658
660
  // src/agents/types.ts
659
- var PLAN_MODE_INSTRUCTION = "Plan mode is active and must remain active until the user turns Plan mode off in the UI (or explicitly asks you to implement). Analyze the codebase, search and read files as needed, and produce or refine a clear implementation plan. Do not modify, create, or delete any files. Do not exit plan mode on your own.";
661
+ var PLAN_MODE_INSTRUCTION = "Plan mode is active and must remain active until the user turns Plan mode off in the UI (or Approves / Hands off the plan). Analyze the codebase, search and read files as needed, and produce or refine a clear implementation plan. Do not modify, create, or delete any project files except via Sideboard MCP present_plan (writes .context/attachments/plan.md). When you need a clarifying decision (approach forks, auth choice, scope): (1) first write a short chat message that explains the decision and what each option means (tradeoffs, when to pick it) \u2014 do not leave the user staring at bare labels; (2) then call Sideboard MCP ask_user with the same options, including a description on every option. Sideboard shows questions in the composer and mirrors them in chat. After ask_user, wait for the user's next message with their answers before finalizing the plan. When the plan is ready for approval: (1) call present_plan with the full markdown plan (title + content) so Sideboard saves .context/attachments/plan.md and shows it in chat for Approve / Hand off / Copy; (2) Claude should also call ExitPlanMode after present_plan. Do not skip present_plan \u2014 the plan must be a markdown file, not only chat prose.";
660
662
  function permissionMode(thread) {
661
663
  if (thread.planMode) {
662
664
  return {
@@ -837,7 +839,7 @@ var claudeAdapter = {
837
839
  );
838
840
  }
839
841
  const mode = permissionMode(thread);
840
- const { isOrchestratorThread } = await import("./global-workspace-ECYN2MKL.js");
842
+ const { isOrchestratorThread } = await import("./global-workspace-MSX2K27Y.js");
841
843
  const isOrchestrator = isOrchestratorThread(thread);
842
844
  const injectedServers = await buildInjectedMcpServers({
843
845
  includeSideboard: true,
@@ -1,6 +1,9 @@
1
+ import {
2
+ isWorkspaceScratchPath
3
+ } from "./chunk-FKOIHGKV.js";
1
4
  import {
2
5
  listThreads
3
- } from "./chunk-O6W3P7V3.js";
6
+ } from "./chunk-TSRXOSVD.js";
4
7
  import {
5
8
  worktreesRoot
6
9
  } from "./chunk-M37RITA6.js";
@@ -970,6 +973,206 @@ function buildMergeGateChecks(gate, opts = {}) {
970
973
  return rows;
971
974
  }
972
975
 
976
+ // src/git/stack.ts
977
+ var cachedStatus = null;
978
+ var STATUS_TTL_MS = 6e4;
979
+ async function detectGhStack(cwd) {
980
+ const now = Date.now();
981
+ if (cachedStatus && now - cachedStatus.at < STATUS_TTL_MS) {
982
+ return cachedStatus.status;
983
+ }
984
+ const probe = await gh(["stack", "view", "--help"], cwd, { reject: false });
985
+ if (probe.exitCode === 0) {
986
+ const status2 = { available: true };
987
+ cachedStatus = { at: now, status: status2 };
988
+ return status2;
989
+ }
990
+ const err = `${probe.stderr}
991
+ ${probe.stdout}`;
992
+ const reason = /official extension|extension install|github\/gh-stack/i.test(err) ? "Install with: gh extension install github/gh-stack" : err.trim() || "gh stack is not available";
993
+ const status = { available: false, reason };
994
+ cachedStatus = { at: now, status };
995
+ return status;
996
+ }
997
+ function resetGhStackDetectCache() {
998
+ cachedStatus = null;
999
+ }
1000
+ function str(v) {
1001
+ return typeof v === "string" ? v : v == null ? "" : String(v);
1002
+ }
1003
+ function num(v) {
1004
+ if (typeof v === "number" && Number.isFinite(v)) return v;
1005
+ if (typeof v === "string" && v.trim() && Number.isFinite(Number(v))) {
1006
+ return Number(v);
1007
+ }
1008
+ return null;
1009
+ }
1010
+ function parseGhStackViewJson(raw) {
1011
+ let data;
1012
+ try {
1013
+ data = JSON.parse(raw);
1014
+ } catch {
1015
+ return null;
1016
+ }
1017
+ if (!Array.isArray(data.branches) || data.branches.length === 0) return null;
1018
+ const trunk = str(data.trunk) || "main";
1019
+ const currentBranch2 = str(data.currentBranch);
1020
+ const stackNumber = num(data.stackNumber) ?? num(data.number);
1021
+ const layers = [];
1022
+ for (let i = 0; i < data.branches.length; i++) {
1023
+ const b = data.branches[i];
1024
+ if (!b || typeof b !== "object") continue;
1025
+ const name = str(b.name);
1026
+ if (!name) continue;
1027
+ const pr = b.pr && typeof b.pr === "object" ? b.pr : null;
1028
+ layers.push({
1029
+ position: i + 1,
1030
+ branchName: name,
1031
+ headSha: str(b.head) || void 0,
1032
+ baseSha: str(b.base) || void 0,
1033
+ isCurrent: Boolean(b.isCurrent) || name === currentBranch2,
1034
+ isMerged: Boolean(b.isMerged),
1035
+ isQueued: Boolean(b.isQueued),
1036
+ needsRebase: Boolean(b.needsRebase),
1037
+ prNumber: pr ? num(pr.number) : null,
1038
+ prUrl: pr && str(pr.url) ? str(pr.url) : null,
1039
+ prState: pr && str(pr.state) ? str(pr.state).toUpperCase() : null,
1040
+ title: pr && str(pr.title) ? str(pr.title) : void 0
1041
+ });
1042
+ }
1043
+ if (!layers.length) return null;
1044
+ let currentIndex = layers.findIndex((l) => l.isCurrent);
1045
+ if (currentIndex < 0 && currentBranch2) {
1046
+ currentIndex = layers.findIndex((l) => l.branchName === currentBranch2);
1047
+ }
1048
+ const { readyToMerge, blockedReason } = stackMergeReadiness(layers, currentIndex);
1049
+ return {
1050
+ stackNumber,
1051
+ trunk,
1052
+ currentBranch: currentBranch2 || layers[currentIndex]?.branchName || layers[0].branchName,
1053
+ layers,
1054
+ currentIndex,
1055
+ readyToMerge,
1056
+ blockedReason
1057
+ };
1058
+ }
1059
+ function stackMergeReadiness(layers, throughIndex) {
1060
+ if (throughIndex < 0 || throughIndex >= layers.length) {
1061
+ return { readyToMerge: false, blockedReason: "Not on a stack layer" };
1062
+ }
1063
+ for (let i = 0; i <= throughIndex; i++) {
1064
+ const layer = layers[i];
1065
+ if (layer.isMerged) continue;
1066
+ if (!layer.prNumber) {
1067
+ return {
1068
+ readyToMerge: false,
1069
+ blockedReason: `Layer ${layer.branchName} has no pull request yet`
1070
+ };
1071
+ }
1072
+ if (layer.needsRebase) {
1073
+ return {
1074
+ readyToMerge: false,
1075
+ blockedReason: `PR #${layer.prNumber} needs rebase`
1076
+ };
1077
+ }
1078
+ const state = (layer.prState ?? "").toUpperCase();
1079
+ if (state && state !== "OPEN" && state !== "QUEUED") {
1080
+ return {
1081
+ readyToMerge: false,
1082
+ blockedReason: `PR #${layer.prNumber} is ${state}`
1083
+ };
1084
+ }
1085
+ }
1086
+ return { readyToMerge: true, blockedReason: null };
1087
+ }
1088
+ async function getPrStack(cwd) {
1089
+ const status = await detectGhStack(cwd);
1090
+ if (!status.available) return null;
1091
+ const result = await gh(["stack", "view", "--json"], cwd, { reject: false });
1092
+ if (result.exitCode === 2) return null;
1093
+ if (result.exitCode !== 0) {
1094
+ if (/not in a stack|no stack/i.test(`${result.stderr}
1095
+ ${result.stdout}`)) {
1096
+ return null;
1097
+ }
1098
+ if (result.exitCode === 9) return null;
1099
+ return null;
1100
+ }
1101
+ const json = result.stdout.trim();
1102
+ if (!json) return null;
1103
+ return parseGhStackViewJson(json);
1104
+ }
1105
+ async function isInPrStack(cwd) {
1106
+ return Boolean(await getPrStack(cwd));
1107
+ }
1108
+ async function mergePrStack(cwd, opts) {
1109
+ const status = await detectGhStack(cwd);
1110
+ if (!status.available) {
1111
+ throw new Error(status.reason);
1112
+ }
1113
+ const method = opts.method ?? "squash";
1114
+ const methodFlag = method === "rebase" ? "--rebase" : method === "merge" ? "--merge" : "--squash";
1115
+ const args = [
1116
+ "stack",
1117
+ "merge",
1118
+ String(opts.through),
1119
+ "--yes",
1120
+ methodFlag
1121
+ ];
1122
+ const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
1123
+ if (exitCode !== 0) {
1124
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack merge failed");
1125
+ }
1126
+ return { stdout };
1127
+ }
1128
+ async function initPrStack(cwd, branches, opts) {
1129
+ if (!branches.length) throw new Error("initPrStack requires at least one branch name");
1130
+ const status = await detectGhStack(cwd);
1131
+ if (!status.available) throw new Error(status.reason);
1132
+ const args = ["stack", "init"];
1133
+ if (opts?.base) args.push("--base", opts.base);
1134
+ args.push(...branches);
1135
+ const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
1136
+ if (exitCode !== 0) {
1137
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack init failed");
1138
+ }
1139
+ }
1140
+ async function addPrStackLayer(cwd, branchName) {
1141
+ if (!branchName.trim()) throw new Error("branch name required");
1142
+ const status = await detectGhStack(cwd);
1143
+ if (!status.available) throw new Error(status.reason);
1144
+ const { exitCode, stderr, stdout } = await gh(
1145
+ ["stack", "add", branchName.trim()],
1146
+ cwd,
1147
+ { reject: false }
1148
+ );
1149
+ if (exitCode !== 0) {
1150
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack add failed");
1151
+ }
1152
+ }
1153
+ async function submitPrStack(cwd, opts) {
1154
+ const status = await detectGhStack(cwd);
1155
+ if (!status.available) throw new Error(status.reason);
1156
+ const args = ["stack", "submit", "--auto"];
1157
+ if (opts?.open) args.push("--open");
1158
+ const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
1159
+ if (exitCode !== 0) {
1160
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack submit failed");
1161
+ }
1162
+ }
1163
+ async function checkoutPrStackLayer(cwd, target) {
1164
+ const status = await detectGhStack(cwd);
1165
+ if (!status.available) throw new Error(status.reason);
1166
+ const { exitCode, stderr, stdout } = await gh(
1167
+ ["stack", "checkout", String(target)],
1168
+ cwd,
1169
+ { reject: false }
1170
+ );
1171
+ if (exitCode !== 0) {
1172
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack checkout failed");
1173
+ }
1174
+ }
1175
+
973
1176
  // src/git/worktree.ts
974
1177
  async function lookupGithubGraphqlReset(cwd) {
975
1178
  const result = await gh(["api", "rate_limit"], cwd, { reject: false });
@@ -1646,6 +1849,52 @@ ${add.stdout}`;
1646
1849
  await ensureGhPreferOrigin(worktreePath);
1647
1850
  return { branchName, worktreePath };
1648
1851
  }
1852
+ async function createExistingBranchWorktree(opts) {
1853
+ const branchName = opts.branchName.trim();
1854
+ if (!branchName) throw new Error("branch name required");
1855
+ const worktreePath = join(worktreesRoot(opts.repoPath), opts.slug);
1856
+ if (existsSync(worktreePath)) {
1857
+ throw new Error(`Worktree already exists at ${worktreePath}`);
1858
+ }
1859
+ await ensureGhPreferOrigin(opts.repoPath);
1860
+ await git(["fetch", "origin", "--prune"], opts.repoPath, { reject: false });
1861
+ if (!branchName.startsWith("origin/") && !branchName.startsWith("refs/")) {
1862
+ await git(["fetch", "origin", branchName], opts.repoPath, { reject: false });
1863
+ }
1864
+ const existing = await listWorktrees(opts.repoPath);
1865
+ const already = existing.find((w) => w.branch === branchName);
1866
+ if (already?.path) {
1867
+ throw new Error(
1868
+ `Branch ${branchName} is already checked out at ${already.path}`
1869
+ );
1870
+ }
1871
+ const startPoint = await resolveWorktreeStartPoint(opts.repoPath, branchName);
1872
+ const add = await git(
1873
+ ["worktree", "add", worktreePath, startPoint],
1874
+ opts.repoPath,
1875
+ { reject: false }
1876
+ );
1877
+ if (add.exitCode !== 0) {
1878
+ const retry = await git(
1879
+ ["worktree", "add", worktreePath, branchName],
1880
+ opts.repoPath,
1881
+ { reject: false }
1882
+ );
1883
+ if (retry.exitCode !== 0) {
1884
+ throw new Error(
1885
+ `Failed to create worktree for ${branchName}: ${retry.stderr.trim() || add.stderr.trim() || retry.stdout.trim() || add.stdout.trim() || `exit ${add.exitCode}`}`
1886
+ );
1887
+ }
1888
+ }
1889
+ const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath, {
1890
+ reject: false
1891
+ });
1892
+ if (head.stdout.trim() === "HEAD" || head.stdout.trim() !== branchName) {
1893
+ await git(["checkout", "-B", branchName], worktreePath, { reject: false });
1894
+ }
1895
+ await ensureGhPreferOrigin(worktreePath);
1896
+ return { branchName, worktreePath };
1897
+ }
1649
1898
  async function removeWorktree(repoPath, worktreePath, opts) {
1650
1899
  await git(["worktree", "remove", "--force", worktreePath], repoPath, {
1651
1900
  reject: false
@@ -1685,8 +1934,7 @@ async function isDirty(worktreePath) {
1685
1934
  return false;
1686
1935
  }
1687
1936
  function isSideboardScratchPath(relativePath) {
1688
- const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
1689
- return p === ".sideboard/attachments" || p.startsWith(".sideboard/attachments/");
1937
+ return isWorkspaceScratchPath(relativePath);
1690
1938
  }
1691
1939
  function porcelainStatusPath(line) {
1692
1940
  const rest = line.length >= 3 ? line.slice(3) : "";
@@ -1714,7 +1962,7 @@ async function pushBranch(worktreePath, branchName) {
1714
1962
  }
1715
1963
  async function mergePr(cwd, selector, opts) {
1716
1964
  const slug = await resolveGithubRepoSlug(cwd);
1717
- const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft"];
1965
+ const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft,number"];
1718
1966
  if (slug) viewArgs.push("--repo", slug);
1719
1967
  const before = await gh(viewArgs, cwd, { reject: false });
1720
1968
  if (before.exitCode !== 0 || !before.stdout.trim()) {
@@ -1722,16 +1970,29 @@ async function mergePr(cwd, selector, opts) {
1722
1970
  }
1723
1971
  let url = "";
1724
1972
  let isDraft = false;
1973
+ let prNumber = null;
1725
1974
  try {
1726
1975
  const parsed = JSON.parse(before.stdout);
1727
1976
  url = String(parsed.url ?? "");
1728
1977
  isDraft = Boolean(parsed.isDraft);
1978
+ prNumber = typeof parsed.number === "number" && Number.isFinite(parsed.number) ? parsed.number : null;
1729
1979
  if (String(parsed.state ?? "").toUpperCase() === "MERGED") {
1730
1980
  return { url, state: "MERGED" };
1731
1981
  }
1732
1982
  } catch {
1733
1983
  throw new Error("Could not parse pull request details");
1734
1984
  }
1985
+ const stack = await getPrStack(cwd);
1986
+ const stackLayer = stack && prNumber != null ? stack.layers.find((l) => l.prNumber === prNumber) : null;
1987
+ if (stack && stackLayer && prNumber != null) {
1988
+ const throughIndex = stack.layers.findIndex((l) => l.prNumber === prNumber);
1989
+ const gate = stackMergeReadiness(stack.layers, throughIndex);
1990
+ if (!gate.readyToMerge) {
1991
+ throw new Error(gate.blockedReason || "Stack is not ready to merge");
1992
+ }
1993
+ await mergePrStack(cwd, { through: prNumber, method: opts?.method ?? "squash" });
1994
+ return { url, state: "MERGED" };
1995
+ }
1735
1996
  if (isDraft) {
1736
1997
  const readyArgs = ["pr", "ready", selector];
1737
1998
  if (slug) readyArgs.push("--repo", slug);
@@ -1753,10 +2014,10 @@ async function mergePr(cwd, selector, opts) {
1753
2014
  const after = await gh(viewArgs, cwd, { reject: false });
1754
2015
  if (after.exitCode === 0 && after.stdout.trim()) {
1755
2016
  try {
1756
- const parsed = JSON.parse(after.stdout);
2017
+ const parsed = after.stdout ? JSON.parse(after.stdout) : null;
1757
2018
  return {
1758
- url: String(parsed.url ?? url),
1759
- state: String(parsed.state ?? "MERGED")
2019
+ url: String(parsed?.url ?? url),
2020
+ state: String(parsed?.state ?? "MERGED")
1760
2021
  };
1761
2022
  } catch {
1762
2023
  }
@@ -1910,6 +2171,17 @@ export {
1910
2171
  extractGhErrorDetail,
1911
2172
  formatGhLandError,
1912
2173
  formatIpcInvokeError,
2174
+ detectGhStack,
2175
+ resetGhStackDetectCache,
2176
+ parseGhStackViewJson,
2177
+ stackMergeReadiness,
2178
+ getPrStack,
2179
+ isInPrStack,
2180
+ mergePrStack,
2181
+ initPrStack,
2182
+ addPrStackLayer,
2183
+ submitPrStack,
2184
+ checkoutPrStackLayer,
1913
2185
  slugify,
1914
2186
  resolveRepoRoot,
1915
2187
  parseGithubSlugFromRemoteUrl,
@@ -1931,6 +2203,7 @@ export {
1931
2203
  fetchPrHead,
1932
2204
  resolveWorktreeStartPoint,
1933
2205
  createThreadWorktree,
2206
+ createExistingBranchWorktree,
1934
2207
  removeWorktree,
1935
2208
  listWorktrees,
1936
2209
  isDirty,
@@ -0,0 +1,21 @@
1
+ // src/paths/workspace-scratch.ts
2
+ var ATTACHMENTS_DIR = ".context/attachments";
3
+ var LEGACY_ATTACHMENTS_DIR = ".sideboard/attachments";
4
+ var ATTACHMENTS_GITIGNORE = `# Sideboard / workspace attachments (local only)
5
+ *
6
+ !.gitignore
7
+ `;
8
+ function attachmentsGitignoreBody() {
9
+ return ATTACHMENTS_GITIGNORE;
10
+ }
11
+ function isWorkspaceScratchPath(relativePath) {
12
+ const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
13
+ return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".context" || p.startsWith(".context/");
14
+ }
15
+
16
+ export {
17
+ ATTACHMENTS_DIR,
18
+ LEGACY_ATTACHMENTS_DIR,
19
+ attachmentsGitignoreBody,
20
+ isWorkspaceScratchPath
21
+ };
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  isGlobalRepoPath
3
- } from "./chunk-O5DOO7DP.js";
3
+ } from "./chunk-XX5BB7NV.js";
4
4
  import {
5
5
  ensureGhPreferOrigin,
6
6
  resolveRepoRoot
7
- } from "./chunk-ZH5QZ4CR.js";
7
+ } from "./chunk-DZFH2KLT.js";
8
8
  import {
9
9
  appDataDir
10
10
  } from "./chunk-M37RITA6.js";
@@ -41,6 +41,8 @@ function normalizeThread(raw) {
41
41
  agentPid: raw.agentPid ?? null,
42
42
  attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
43
43
  prTitle: raw.prTitle ?? null,
44
+ stackId: raw.stackId ?? null,
45
+ stackLayer: raw.stackLayer ?? null,
44
46
  userSetTitle: Boolean(raw.userSetTitle),
45
47
  activeRuns: Array.isArray(raw.activeRuns) ? raw.activeRuns : [],
46
48
  quotaResumeAt: raw.quotaResumeAt ?? null,
@@ -65,6 +67,8 @@ function createEmptyThread(partial) {
65
67
  activeRuns: partial.activeRuns ?? [],
66
68
  prUrl: partial.prUrl ?? null,
67
69
  prTitle: partial.prTitle ?? null,
70
+ stackId: partial.stackId ?? null,
71
+ stackLayer: partial.stackLayer ?? null,
68
72
  userSetTitle: partial.userSetTitle ?? false,
69
73
  messages: partial.messages ?? [],
70
74
  attachments: partial.attachments ?? [],
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveGithubRepoSlug
3
- } from "./chunk-ZH5QZ4CR.js";
3
+ } from "./chunk-DZFH2KLT.js";
4
4
  import {
5
5
  globalAgentCwd,
6
6
  sideboardReposDir
@@ -42,6 +42,7 @@ var COORDINATOR_TOOL_PLAYBOOK = [
42
42
  "Discover:",
43
43
  "- list_workspaces \u2014 registered repos (path + github slug when known)",
44
44
  "- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces (issues: Linear API or GitHub Issues)",
45
+ "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
45
46
  "- list_models \u2014 only when you need a specific model (rare); otherwise leave model unset = Auto",
46
47
  "- list_threads / get_thread \u2014 fleet status (what is going on)",
47
48
  "Workspaces:",
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ ATTACHMENTS_DIR,
5
+ LEGACY_ATTACHMENTS_DIR,
6
+ attachmentsGitignoreBody
7
+ } from "./chunk-B3SJXYIJ.js";
8
+
9
+ // src/plan/plan-file.ts
10
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
11
+ import { dirname, join } from "path";
12
+
13
+ // src/plan/plan-present.ts
14
+ var PLAN_FILE_REL = `${ATTACHMENTS_DIR}/plan.md`;
15
+ var PLAN_FILE_NAME = "plan.md";
16
+ var LEGACY_PLAN_FILE_REL = ".sideboard/plan.md";
17
+ function asRecord(v) {
18
+ return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
19
+ }
20
+ function isPresentPlanToolName(name) {
21
+ if (!name) return false;
22
+ return /present_plan$/i.test(name) || /^mcp__sideboard__present_plan$/i.test(name);
23
+ }
24
+ function extractPresentedPlan(parts) {
25
+ if (!parts?.length) return null;
26
+ for (let i = parts.length - 1; i >= 0; i--) {
27
+ const p = parts[i];
28
+ if (p.type !== "tool" || !isPresentPlanToolName(p.name)) continue;
29
+ const input = asRecord(p.input) ?? {};
30
+ const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
31
+ if (!content.trim()) continue;
32
+ const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
33
+ const path = typeof input.path === "string" && input.path.trim() ? input.path.trim() : PLAN_FILE_REL;
34
+ return { title, content: content.trim(), path, source: "present_plan" };
35
+ }
36
+ return null;
37
+ }
38
+ function resolvePlanMarkdown(opts) {
39
+ const fromTool = extractPresentedPlan(opts.parts);
40
+ if (fromTool) return fromTool;
41
+ const file = opts.fileContent?.trim();
42
+ if (file) {
43
+ return {
44
+ title: "Plan",
45
+ content: file,
46
+ path: PLAN_FILE_REL,
47
+ source: "exit_plan"
48
+ };
49
+ }
50
+ const text = opts.text?.trim();
51
+ if (text && text.length >= 80) {
52
+ return {
53
+ title: "Plan",
54
+ content: text,
55
+ path: PLAN_FILE_REL,
56
+ source: "text"
57
+ };
58
+ }
59
+ return null;
60
+ }
61
+
62
+ // src/plan/plan-file.ts
63
+ function ensureAttachmentsGitignore(worktreePath) {
64
+ const gitignoreAbs = join(worktreePath, ATTACHMENTS_DIR, ".gitignore");
65
+ if (existsSync(gitignoreAbs)) return;
66
+ mkdirSync(dirname(gitignoreAbs), { recursive: true });
67
+ writeFileSync(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
68
+ }
69
+ function planFileAbs(worktreePath) {
70
+ return join(worktreePath, PLAN_FILE_REL);
71
+ }
72
+ function readTextIfPresent(abs) {
73
+ if (!existsSync(abs)) return null;
74
+ try {
75
+ const content = readFileSync(abs, "utf8");
76
+ return content.trim() ? content : null;
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+ function readPlanFile(worktreePath) {
82
+ return readTextIfPresent(planFileAbs(worktreePath)) ?? readTextIfPresent(join(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent(join(worktreePath, LEGACY_PLAN_FILE_REL));
83
+ }
84
+ function writePlanFile(worktreePath, content) {
85
+ ensureAttachmentsGitignore(worktreePath);
86
+ const abs = planFileAbs(worktreePath);
87
+ mkdirSync(dirname(abs), { recursive: true });
88
+ const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
89
+ writeFileSync(abs, body, "utf8");
90
+ return PLAN_FILE_REL;
91
+ }
92
+
93
+ export {
94
+ PLAN_FILE_REL,
95
+ PLAN_FILE_NAME,
96
+ LEGACY_PLAN_FILE_REL,
97
+ isPresentPlanToolName,
98
+ extractPresentedPlan,
99
+ resolvePlanMarkdown,
100
+ planFileAbs,
101
+ readPlanFile,
102
+ writePlanFile
103
+ };