@sideboard-ai/core 0.1.50 → 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 (46) hide show
  1. package/dist/{agents-LUB3Q773.js → agents-ON6RKKND.js} +3 -3
  2. package/dist/{agents-3P4N6KHC.js → agents-YKSS6VBO.js} +3 -3
  3. package/dist/{chunk-RQI4TOXK.js → chunk-6QTZVJ7A.js} +7 -5
  4. package/dist/{chunk-AJ6ROGD7.js → chunk-7LNGIP3X.js} +20 -2
  5. package/dist/chunk-B3SJXYIJ.js +24 -0
  6. package/dist/{chunk-ILQK4P5R.js → chunk-BXJ76RHF.js} +4 -4
  7. package/dist/{chunk-SS34TVSY.js → chunk-D3METLRW.js} +7 -5
  8. package/dist/{chunk-FV6FN6V5.js → chunk-DZFH2KLT.js} +340 -22
  9. package/dist/chunk-FKOIHGKV.js +21 -0
  10. package/dist/{chunk-3NAS4MWH.js → chunk-GNML24AW.js} +2 -2
  11. package/dist/{chunk-E35APBHV.js → chunk-H6GGDLYS.js} +1 -1
  12. package/dist/{chunk-EOYDCKQC.js → chunk-HLEX5AQ6.js} +4 -0
  13. package/dist/{chunk-ZQCQIWIP.js → chunk-LRLKJM3O.js} +2 -1
  14. package/dist/chunk-N5PM7HGQ.js +103 -0
  15. package/dist/chunk-QTUESPAW.js +101 -0
  16. package/dist/{chunk-O6W3P7V3.js → chunk-TSRXOSVD.js} +4 -0
  17. package/dist/{chunk-GZXBYHJC.js → chunk-UEAHMGHW.js} +2 -1
  18. package/dist/{chunk-ZVV5EEQN.js → chunk-XOU6HNQJ.js} +305 -22
  19. package/dist/{chunk-5WYEJ3F3.js → chunk-XX5BB7NV.js} +3 -3
  20. package/dist/{chunk-V4JRXYYU.js → chunk-YDXQ72MD.js} +2 -2
  21. package/dist/{chunk-DF5VQQKA.js → chunk-YOWIYAVA.js} +3 -3
  22. package/dist/{chunk-ISY4EGF6.js → chunk-ZNM4MAN4.js} +20 -2
  23. package/dist/{connected-teams-UBXHZGO4.js → connected-teams-2ANNZCP5.js} +2 -2
  24. package/dist/{connected-teams-GF52Q7LB.js → connected-teams-ONC4666A.js} +2 -2
  25. package/dist/{coordinator-prompt-VG4BZ5JL.js → coordinator-prompt-6FXVTSFN.js} +5 -4
  26. package/dist/{coordinator-prompt-SPGDT7J5.js → coordinator-prompt-S6JZD5EF.js} +5 -4
  27. package/dist/{global-workspace-OJF4ENH4.js → global-workspace-EV4G2WMQ.js} +6 -5
  28. package/dist/{global-workspace-KSR3E63K.js → global-workspace-MSX2K27Y.js} +6 -5
  29. package/dist/index.cjs +1369 -155
  30. package/dist/index.d.cts +397 -14
  31. package/dist/index.d.ts +397 -14
  32. package/dist/index.js +811 -90
  33. package/dist/mcp/run-stdio.cjs +1136 -147
  34. package/dist/mcp/run-stdio.js +635 -79
  35. package/dist/plan-file-6O7G4VPQ.js +23 -0
  36. package/dist/plan-file-PHVKUAEE.js +25 -0
  37. package/dist/{run-HMRSRG3U.js → run-H3IMNOYN.js} +3 -1
  38. package/dist/run-ZJKYHXGN.js +12 -0
  39. package/dist/{thread-store-XICUWFNM.js → thread-store-GHOADGL2.js} +1 -1
  40. package/dist/{thread-store-OV2X6PYO.js → thread-store-UJIGMI5J.js} +1 -1
  41. package/dist/{workspaces-OZE7LRDO.js → workspaces-3RQQZQRO.js} +7 -6
  42. package/dist/{workspaces-UJX7JIGH.js → workspaces-AYTBR6KQ.js} +7 -6
  43. package/dist/{worktree-XG5PCLZY.js → worktree-5KEQWSAF.js} +8 -3
  44. package/dist/{worktree-TEDAJ57S.js → worktree-RWGL7FUV.js} +8 -3
  45. package/package.json +1 -1
  46. package/dist/run-LF6E5IKL.js +0 -10
@@ -1,13 +1,17 @@
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";
7
10
  import {
8
11
  gh,
9
- git
10
- } from "./chunk-AJ6ROGD7.js";
12
+ git,
13
+ resolveGhAuthToken
14
+ } from "./chunk-7LNGIP3X.js";
11
15
 
12
16
  // src/git/worktree.ts
13
17
  import { existsSync, readdirSync } from "fs";
@@ -969,6 +973,206 @@ function buildMergeGateChecks(gate, opts = {}) {
969
973
  return rows;
970
974
  }
971
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
+
972
1176
  // src/git/worktree.ts
973
1177
  async function lookupGithubGraphqlReset(cwd) {
974
1178
  const result = await gh(["api", "rate_limit"], cwd, { reject: false });
@@ -1476,18 +1680,31 @@ async function fetchPrHead(repoPath, number, localBranch) {
1476
1680
  const slug = await resolveGithubRepoSlug(repoPath);
1477
1681
  const refspec = `+pull/${number}/head:${localBranch}`;
1478
1682
  const errors = [];
1479
- const tryFetch = async (remote) => {
1480
- const result = await git(["fetch", remote, refspec], repoPath, {
1481
- reject: false
1482
- });
1683
+ const httpsRemote = slug ? `https://github.com/${slug}.git` : null;
1684
+ const tryFetch = async (remote, opts) => {
1685
+ const label = opts?.ghAuth ? `${remote} (gh auth)` : remote;
1686
+ const gitOpts = { reject: false };
1687
+ if (opts?.ghAuth) {
1688
+ const token = await resolveGhAuthToken(repoPath);
1689
+ if (!token) {
1690
+ errors.push(`${label}: gh auth token unavailable`);
1691
+ return false;
1692
+ }
1693
+ gitOpts.env = { GIT_TERMINAL_PROMPT: "0" };
1694
+ gitOpts.config = {
1695
+ "http.extraHeader": `AUTHORIZATION: bearer ${token}`
1696
+ };
1697
+ }
1698
+ const result = await git(["fetch", remote, refspec], repoPath, gitOpts);
1483
1699
  if (result.exitCode === 0) return true;
1484
1700
  const detail = (result.stderr || result.stdout).trim();
1485
- if (detail) errors.push(`${remote}: ${detail}`);
1701
+ if (detail) errors.push(`${label}: ${detail}`);
1486
1702
  return false;
1487
1703
  };
1488
1704
  let fetched = await tryFetch("origin");
1489
- if (!fetched && slug) {
1490
- fetched = await tryFetch(`https://github.com/${slug}.git`);
1705
+ if (!fetched && httpsRemote) {
1706
+ fetched = await tryFetch(httpsRemote);
1707
+ if (!fetched) fetched = await tryFetch(httpsRemote, { ghAuth: true });
1491
1708
  }
1492
1709
  const localOk = async () => {
1493
1710
  const verify = await git(["rev-parse", "--verify", localBranch], repoPath, {
@@ -1516,18 +1733,28 @@ async function fetchPrHead(repoPath, number, localBranch) {
1516
1733
  }
1517
1734
  }
1518
1735
  if (oid) {
1519
- const ensureOid = async (remote) => {
1520
- const got = await git(["fetch", remote, oid], repoPath, {
1521
- reject: false
1522
- });
1736
+ const ensureOid = async (remote, opts) => {
1737
+ const gitOpts = { reject: false };
1738
+ if (opts?.ghAuth) {
1739
+ const token = await resolveGhAuthToken(repoPath);
1740
+ if (!token) return false;
1741
+ gitOpts.env = { GIT_TERMINAL_PROMPT: "0" };
1742
+ gitOpts.config = {
1743
+ "http.extraHeader": `AUTHORIZATION: bearer ${token}`
1744
+ };
1745
+ }
1746
+ const got = await git(["fetch", remote, oid], repoPath, gitOpts);
1523
1747
  return got.exitCode === 0;
1524
1748
  };
1525
1749
  let haveObject = (await git(["cat-file", "-e", `${oid}^{commit}`], repoPath, {
1526
1750
  reject: false
1527
1751
  })).exitCode === 0;
1528
1752
  if (!haveObject) haveObject = await ensureOid("origin");
1529
- if (!haveObject && slug) {
1530
- haveObject = await ensureOid(`https://github.com/${slug}.git`);
1753
+ if (!haveObject && httpsRemote) {
1754
+ haveObject = await ensureOid(httpsRemote);
1755
+ if (!haveObject) {
1756
+ haveObject = await ensureOid(httpsRemote, { ghAuth: true });
1757
+ }
1531
1758
  }
1532
1759
  if (haveObject) {
1533
1760
  const branched = await git(["branch", "-f", localBranch, oid], repoPath, {
@@ -1622,6 +1849,52 @@ ${add.stdout}`;
1622
1849
  await ensureGhPreferOrigin(worktreePath);
1623
1850
  return { branchName, worktreePath };
1624
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
+ }
1625
1898
  async function removeWorktree(repoPath, worktreePath, opts) {
1626
1899
  await git(["worktree", "remove", "--force", worktreePath], repoPath, {
1627
1900
  reject: false
@@ -1651,8 +1924,27 @@ async function listWorktrees(repoPath) {
1651
1924
  return entries;
1652
1925
  }
1653
1926
  async function isDirty(worktreePath) {
1654
- const { stdout } = await git(["status", "--porcelain"], worktreePath);
1655
- return stdout.trim().length > 0;
1927
+ const { stdout } = await git(["status", "--porcelain", "-uall"], worktreePath);
1928
+ for (const line of stdout.split("\n")) {
1929
+ if (!line.trim()) continue;
1930
+ const rel = porcelainStatusPath(line);
1931
+ if (!rel || isSideboardScratchPath(rel)) continue;
1932
+ return true;
1933
+ }
1934
+ return false;
1935
+ }
1936
+ function isSideboardScratchPath(relativePath) {
1937
+ return isWorkspaceScratchPath(relativePath);
1938
+ }
1939
+ function porcelainStatusPath(line) {
1940
+ const rest = line.length >= 3 ? line.slice(3) : "";
1941
+ if (!rest) return "";
1942
+ const arrow = rest.lastIndexOf(" -> ");
1943
+ const raw = arrow >= 0 ? rest.slice(arrow + 4) : rest;
1944
+ if (raw.startsWith('"') && raw.endsWith('"')) {
1945
+ return raw.slice(1, -1).replace(/\\([\\"])/g, "$1");
1946
+ }
1947
+ return raw;
1656
1948
  }
1657
1949
  async function currentBranch(worktreePath) {
1658
1950
  const { stdout } = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath);
@@ -1670,7 +1962,7 @@ async function pushBranch(worktreePath, branchName) {
1670
1962
  }
1671
1963
  async function mergePr(cwd, selector, opts) {
1672
1964
  const slug = await resolveGithubRepoSlug(cwd);
1673
- const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft"];
1965
+ const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft,number"];
1674
1966
  if (slug) viewArgs.push("--repo", slug);
1675
1967
  const before = await gh(viewArgs, cwd, { reject: false });
1676
1968
  if (before.exitCode !== 0 || !before.stdout.trim()) {
@@ -1678,16 +1970,29 @@ async function mergePr(cwd, selector, opts) {
1678
1970
  }
1679
1971
  let url = "";
1680
1972
  let isDraft = false;
1973
+ let prNumber = null;
1681
1974
  try {
1682
1975
  const parsed = JSON.parse(before.stdout);
1683
1976
  url = String(parsed.url ?? "");
1684
1977
  isDraft = Boolean(parsed.isDraft);
1978
+ prNumber = typeof parsed.number === "number" && Number.isFinite(parsed.number) ? parsed.number : null;
1685
1979
  if (String(parsed.state ?? "").toUpperCase() === "MERGED") {
1686
1980
  return { url, state: "MERGED" };
1687
1981
  }
1688
1982
  } catch {
1689
1983
  throw new Error("Could not parse pull request details");
1690
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
+ }
1691
1996
  if (isDraft) {
1692
1997
  const readyArgs = ["pr", "ready", selector];
1693
1998
  if (slug) readyArgs.push("--repo", slug);
@@ -1709,10 +2014,10 @@ async function mergePr(cwd, selector, opts) {
1709
2014
  const after = await gh(viewArgs, cwd, { reject: false });
1710
2015
  if (after.exitCode === 0 && after.stdout.trim()) {
1711
2016
  try {
1712
- const parsed = JSON.parse(after.stdout);
2017
+ const parsed = after.stdout ? JSON.parse(after.stdout) : null;
1713
2018
  return {
1714
- url: String(parsed.url ?? url),
1715
- state: String(parsed.state ?? "MERGED")
2019
+ url: String(parsed?.url ?? url),
2020
+ state: String(parsed?.state ?? "MERGED")
1716
2021
  };
1717
2022
  } catch {
1718
2023
  }
@@ -1866,6 +2171,17 @@ export {
1866
2171
  extractGhErrorDetail,
1867
2172
  formatGhLandError,
1868
2173
  formatIpcInvokeError,
2174
+ detectGhStack,
2175
+ resetGhStackDetectCache,
2176
+ parseGhStackViewJson,
2177
+ stackMergeReadiness,
2178
+ getPrStack,
2179
+ isInPrStack,
2180
+ mergePrStack,
2181
+ initPrStack,
2182
+ addPrStackLayer,
2183
+ submitPrStack,
2184
+ checkoutPrStackLayer,
1869
2185
  slugify,
1870
2186
  resolveRepoRoot,
1871
2187
  parseGithubSlugFromRemoteUrl,
@@ -1887,9 +2203,11 @@ export {
1887
2203
  fetchPrHead,
1888
2204
  resolveWorktreeStartPoint,
1889
2205
  createThreadWorktree,
2206
+ createExistingBranchWorktree,
1890
2207
  removeWorktree,
1891
2208
  listWorktrees,
1892
2209
  isDirty,
2210
+ isSideboardScratchPath,
1893
2211
  currentBranch,
1894
2212
  commitAll,
1895
2213
  pushBranch,
@@ -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-5WYEJ3F3.js";
3
+ } from "./chunk-XX5BB7NV.js";
4
4
  import {
5
5
  ensureGhPreferOrigin,
6
6
  resolveRepoRoot
7
- } from "./chunk-FV6FN6V5.js";
7
+ } from "./chunk-DZFH2KLT.js";
8
8
  import {
9
9
  appDataDir
10
10
  } from "./chunk-M37RITA6.js";
@@ -2,7 +2,7 @@
2
2
 
3
3
  import {
4
4
  run
5
- } from "./chunk-ISY4EGF6.js";
5
+ } from "./chunk-ZNM4MAN4.js";
6
6
  import {
7
7
  appDataDir
8
8
  } from "./chunk-7MV3RXSC.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-FV6FN6V5.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
+ };