@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
package/dist/index.cjs CHANGED
@@ -916,6 +916,8 @@ function normalizeThread(raw) {
916
916
  agentPid: raw.agentPid ?? null,
917
917
  attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
918
918
  prTitle: raw.prTitle ?? null,
919
+ stackId: raw.stackId ?? null,
920
+ stackLayer: raw.stackLayer ?? null,
919
921
  userSetTitle: Boolean(raw.userSetTitle),
920
922
  activeRuns: Array.isArray(raw.activeRuns) ? raw.activeRuns : [],
921
923
  quotaResumeAt: raw.quotaResumeAt ?? null,
@@ -940,6 +942,8 @@ function createEmptyThread(partial) {
940
942
  activeRuns: partial.activeRuns ?? [],
941
943
  prUrl: partial.prUrl ?? null,
942
944
  prTitle: partial.prTitle ?? null,
945
+ stackId: partial.stackId ?? null,
946
+ stackLayer: partial.stackLayer ?? null,
943
947
  userSetTitle: partial.userSetTitle ?? false,
944
948
  messages: partial.messages ?? [],
945
949
  attachments: partial.attachments ?? [],
@@ -2044,6 +2048,7 @@ var run_exports = {};
2044
2048
  __export(run_exports, {
2045
2049
  gh: () => gh,
2046
2050
  git: () => git,
2051
+ resolveGhAuthToken: () => resolveGhAuthToken,
2047
2052
  run: () => run
2048
2053
  });
2049
2054
  async function run(file, args, opts) {
@@ -2072,11 +2077,28 @@ async function run(file, args, opts) {
2072
2077
  }
2073
2078
  }
2074
2079
  async function git(args, cwd, opts) {
2075
- return run("git", args, { cwd, reject: opts?.reject });
2080
+ const prefix = [];
2081
+ if (opts?.config) {
2082
+ for (const [key, value] of Object.entries(opts.config)) {
2083
+ if (!key) continue;
2084
+ prefix.push("-c", `${key}=${value}`);
2085
+ }
2086
+ }
2087
+ return run("git", [...prefix, ...args], {
2088
+ cwd,
2089
+ reject: opts?.reject,
2090
+ env: opts?.env
2091
+ });
2076
2092
  }
2077
2093
  async function gh(args, cwd, opts) {
2078
2094
  return run("gh", args, { cwd, reject: opts?.reject });
2079
2095
  }
2096
+ async function resolveGhAuthToken(cwd) {
2097
+ const result = await gh(["auth", "token"], cwd, { reject: false });
2098
+ if (result.exitCode !== 0) return null;
2099
+ const token = result.stdout.trim();
2100
+ return token || null;
2101
+ }
2080
2102
  var import_execa;
2081
2103
  var init_run = __esm({
2082
2104
  "src/git/run.ts"() {
@@ -2178,6 +2200,234 @@ var init_pr_gates = __esm({
2178
2200
  }
2179
2201
  });
2180
2202
 
2203
+ // src/git/stack.ts
2204
+ async function detectGhStack(cwd) {
2205
+ const now = Date.now();
2206
+ if (cachedStatus && now - cachedStatus.at < STATUS_TTL_MS) {
2207
+ return cachedStatus.status;
2208
+ }
2209
+ const probe = await gh(["stack", "view", "--help"], cwd, { reject: false });
2210
+ if (probe.exitCode === 0) {
2211
+ const status2 = { available: true };
2212
+ cachedStatus = { at: now, status: status2 };
2213
+ return status2;
2214
+ }
2215
+ const err = `${probe.stderr}
2216
+ ${probe.stdout}`;
2217
+ 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";
2218
+ const status = { available: false, reason };
2219
+ cachedStatus = { at: now, status };
2220
+ return status;
2221
+ }
2222
+ function resetGhStackDetectCache() {
2223
+ cachedStatus = null;
2224
+ }
2225
+ function str(v) {
2226
+ return typeof v === "string" ? v : v == null ? "" : String(v);
2227
+ }
2228
+ function num(v) {
2229
+ if (typeof v === "number" && Number.isFinite(v)) return v;
2230
+ if (typeof v === "string" && v.trim() && Number.isFinite(Number(v))) {
2231
+ return Number(v);
2232
+ }
2233
+ return null;
2234
+ }
2235
+ function parseGhStackViewJson(raw) {
2236
+ let data;
2237
+ try {
2238
+ data = JSON.parse(raw);
2239
+ } catch {
2240
+ return null;
2241
+ }
2242
+ if (!Array.isArray(data.branches) || data.branches.length === 0) return null;
2243
+ const trunk = str(data.trunk) || "main";
2244
+ const currentBranch2 = str(data.currentBranch);
2245
+ const stackNumber = num(data.stackNumber) ?? num(data.number);
2246
+ const layers = [];
2247
+ for (let i = 0; i < data.branches.length; i++) {
2248
+ const b = data.branches[i];
2249
+ if (!b || typeof b !== "object") continue;
2250
+ const name = str(b.name);
2251
+ if (!name) continue;
2252
+ const pr = b.pr && typeof b.pr === "object" ? b.pr : null;
2253
+ layers.push({
2254
+ position: i + 1,
2255
+ branchName: name,
2256
+ headSha: str(b.head) || void 0,
2257
+ baseSha: str(b.base) || void 0,
2258
+ isCurrent: Boolean(b.isCurrent) || name === currentBranch2,
2259
+ isMerged: Boolean(b.isMerged),
2260
+ isQueued: Boolean(b.isQueued),
2261
+ needsRebase: Boolean(b.needsRebase),
2262
+ prNumber: pr ? num(pr.number) : null,
2263
+ prUrl: pr && str(pr.url) ? str(pr.url) : null,
2264
+ prState: pr && str(pr.state) ? str(pr.state).toUpperCase() : null,
2265
+ title: pr && str(pr.title) ? str(pr.title) : void 0
2266
+ });
2267
+ }
2268
+ if (!layers.length) return null;
2269
+ let currentIndex = layers.findIndex((l) => l.isCurrent);
2270
+ if (currentIndex < 0 && currentBranch2) {
2271
+ currentIndex = layers.findIndex((l) => l.branchName === currentBranch2);
2272
+ }
2273
+ const { readyToMerge, blockedReason } = stackMergeReadiness(layers, currentIndex);
2274
+ return {
2275
+ stackNumber,
2276
+ trunk,
2277
+ currentBranch: currentBranch2 || layers[currentIndex]?.branchName || layers[0].branchName,
2278
+ layers,
2279
+ currentIndex,
2280
+ readyToMerge,
2281
+ blockedReason
2282
+ };
2283
+ }
2284
+ function stackMergeReadiness(layers, throughIndex) {
2285
+ if (throughIndex < 0 || throughIndex >= layers.length) {
2286
+ return { readyToMerge: false, blockedReason: "Not on a stack layer" };
2287
+ }
2288
+ for (let i = 0; i <= throughIndex; i++) {
2289
+ const layer = layers[i];
2290
+ if (layer.isMerged) continue;
2291
+ if (!layer.prNumber) {
2292
+ return {
2293
+ readyToMerge: false,
2294
+ blockedReason: `Layer ${layer.branchName} has no pull request yet`
2295
+ };
2296
+ }
2297
+ if (layer.needsRebase) {
2298
+ return {
2299
+ readyToMerge: false,
2300
+ blockedReason: `PR #${layer.prNumber} needs rebase`
2301
+ };
2302
+ }
2303
+ const state = (layer.prState ?? "").toUpperCase();
2304
+ if (state && state !== "OPEN" && state !== "QUEUED") {
2305
+ return {
2306
+ readyToMerge: false,
2307
+ blockedReason: `PR #${layer.prNumber} is ${state}`
2308
+ };
2309
+ }
2310
+ }
2311
+ return { readyToMerge: true, blockedReason: null };
2312
+ }
2313
+ async function getPrStack(cwd) {
2314
+ const status = await detectGhStack(cwd);
2315
+ if (!status.available) return null;
2316
+ const result = await gh(["stack", "view", "--json"], cwd, { reject: false });
2317
+ if (result.exitCode === 2) return null;
2318
+ if (result.exitCode !== 0) {
2319
+ if (/not in a stack|no stack/i.test(`${result.stderr}
2320
+ ${result.stdout}`)) {
2321
+ return null;
2322
+ }
2323
+ if (result.exitCode === 9) return null;
2324
+ return null;
2325
+ }
2326
+ const json = result.stdout.trim();
2327
+ if (!json) return null;
2328
+ return parseGhStackViewJson(json);
2329
+ }
2330
+ async function isInPrStack(cwd) {
2331
+ return Boolean(await getPrStack(cwd));
2332
+ }
2333
+ async function mergePrStack(cwd, opts) {
2334
+ const status = await detectGhStack(cwd);
2335
+ if (!status.available) {
2336
+ throw new Error(status.reason);
2337
+ }
2338
+ const method = opts.method ?? "squash";
2339
+ const methodFlag = method === "rebase" ? "--rebase" : method === "merge" ? "--merge" : "--squash";
2340
+ const args = [
2341
+ "stack",
2342
+ "merge",
2343
+ String(opts.through),
2344
+ "--yes",
2345
+ methodFlag
2346
+ ];
2347
+ const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
2348
+ if (exitCode !== 0) {
2349
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack merge failed");
2350
+ }
2351
+ return { stdout };
2352
+ }
2353
+ async function initPrStack(cwd, branches, opts) {
2354
+ if (!branches.length) throw new Error("initPrStack requires at least one branch name");
2355
+ const status = await detectGhStack(cwd);
2356
+ if (!status.available) throw new Error(status.reason);
2357
+ const args = ["stack", "init"];
2358
+ if (opts?.base) args.push("--base", opts.base);
2359
+ args.push(...branches);
2360
+ const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
2361
+ if (exitCode !== 0) {
2362
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack init failed");
2363
+ }
2364
+ }
2365
+ async function addPrStackLayer(cwd, branchName) {
2366
+ if (!branchName.trim()) throw new Error("branch name required");
2367
+ const status = await detectGhStack(cwd);
2368
+ if (!status.available) throw new Error(status.reason);
2369
+ const { exitCode, stderr, stdout } = await gh(
2370
+ ["stack", "add", branchName.trim()],
2371
+ cwd,
2372
+ { reject: false }
2373
+ );
2374
+ if (exitCode !== 0) {
2375
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack add failed");
2376
+ }
2377
+ }
2378
+ async function submitPrStack(cwd, opts) {
2379
+ const status = await detectGhStack(cwd);
2380
+ if (!status.available) throw new Error(status.reason);
2381
+ const args = ["stack", "submit", "--auto"];
2382
+ if (opts?.open) args.push("--open");
2383
+ const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
2384
+ if (exitCode !== 0) {
2385
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack submit failed");
2386
+ }
2387
+ }
2388
+ async function checkoutPrStackLayer(cwd, target) {
2389
+ const status = await detectGhStack(cwd);
2390
+ if (!status.available) throw new Error(status.reason);
2391
+ const { exitCode, stderr, stdout } = await gh(
2392
+ ["stack", "checkout", String(target)],
2393
+ cwd,
2394
+ { reject: false }
2395
+ );
2396
+ if (exitCode !== 0) {
2397
+ throw new Error(stderr.trim() || stdout.trim() || "gh stack checkout failed");
2398
+ }
2399
+ }
2400
+ var cachedStatus, STATUS_TTL_MS;
2401
+ var init_stack = __esm({
2402
+ "src/git/stack.ts"() {
2403
+ "use strict";
2404
+ init_run();
2405
+ cachedStatus = null;
2406
+ STATUS_TTL_MS = 6e4;
2407
+ }
2408
+ });
2409
+
2410
+ // src/paths/workspace-scratch.ts
2411
+ function attachmentsGitignoreBody() {
2412
+ return ATTACHMENTS_GITIGNORE;
2413
+ }
2414
+ function isWorkspaceScratchPath(relativePath) {
2415
+ const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
2416
+ return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".context" || p.startsWith(".context/");
2417
+ }
2418
+ var ATTACHMENTS_DIR, LEGACY_ATTACHMENTS_DIR, ATTACHMENTS_GITIGNORE;
2419
+ var init_workspace_scratch = __esm({
2420
+ "src/paths/workspace-scratch.ts"() {
2421
+ "use strict";
2422
+ ATTACHMENTS_DIR = ".context/attachments";
2423
+ LEGACY_ATTACHMENTS_DIR = ".sideboard/attachments";
2424
+ ATTACHMENTS_GITIGNORE = `# Sideboard / workspace attachments (local only)
2425
+ *
2426
+ !.gitignore
2427
+ `;
2428
+ }
2429
+ });
2430
+
2181
2431
  // src/git/worktree.ts
2182
2432
  var worktree_exports = {};
2183
2433
  __export(worktree_exports, {
@@ -2187,6 +2437,7 @@ __export(worktree_exports, {
2187
2437
  branchDisplayLabel: () => branchDisplayLabel,
2188
2438
  collectTakenTeamSlugs: () => collectTakenTeamSlugs,
2189
2439
  commitAll: () => commitAll,
2440
+ createExistingBranchWorktree: () => createExistingBranchWorktree,
2190
2441
  createOrUpdatePr: () => createOrUpdatePr,
2191
2442
  createThreadWorktree: () => createThreadWorktree,
2192
2443
  currentBranch: () => currentBranch,
@@ -2201,6 +2452,7 @@ __export(worktree_exports, {
2201
2452
  ghRepoSelectArgs: () => ghRepoSelectArgs,
2202
2453
  isDirty: () => isDirty,
2203
2454
  isPlaceholderBranch: () => isPlaceholderBranch,
2455
+ isSideboardScratchPath: () => isSideboardScratchPath,
2204
2456
  listBranches: () => listBranches,
2205
2457
  listPrs: () => listPrs,
2206
2458
  listWorktrees: () => listWorktrees,
@@ -2730,18 +2982,31 @@ async function fetchPrHead(repoPath, number, localBranch) {
2730
2982
  const slug = await resolveGithubRepoSlug(repoPath);
2731
2983
  const refspec = `+pull/${number}/head:${localBranch}`;
2732
2984
  const errors = [];
2733
- const tryFetch = async (remote) => {
2734
- const result = await git(["fetch", remote, refspec], repoPath, {
2735
- reject: false
2736
- });
2985
+ const httpsRemote = slug ? `https://github.com/${slug}.git` : null;
2986
+ const tryFetch = async (remote, opts) => {
2987
+ const label = opts?.ghAuth ? `${remote} (gh auth)` : remote;
2988
+ const gitOpts = { reject: false };
2989
+ if (opts?.ghAuth) {
2990
+ const token = await resolveGhAuthToken(repoPath);
2991
+ if (!token) {
2992
+ errors.push(`${label}: gh auth token unavailable`);
2993
+ return false;
2994
+ }
2995
+ gitOpts.env = { GIT_TERMINAL_PROMPT: "0" };
2996
+ gitOpts.config = {
2997
+ "http.extraHeader": `AUTHORIZATION: bearer ${token}`
2998
+ };
2999
+ }
3000
+ const result = await git(["fetch", remote, refspec], repoPath, gitOpts);
2737
3001
  if (result.exitCode === 0) return true;
2738
3002
  const detail = (result.stderr || result.stdout).trim();
2739
- if (detail) errors.push(`${remote}: ${detail}`);
3003
+ if (detail) errors.push(`${label}: ${detail}`);
2740
3004
  return false;
2741
3005
  };
2742
3006
  let fetched = await tryFetch("origin");
2743
- if (!fetched && slug) {
2744
- fetched = await tryFetch(`https://github.com/${slug}.git`);
3007
+ if (!fetched && httpsRemote) {
3008
+ fetched = await tryFetch(httpsRemote);
3009
+ if (!fetched) fetched = await tryFetch(httpsRemote, { ghAuth: true });
2745
3010
  }
2746
3011
  const localOk = async () => {
2747
3012
  const verify = await git(["rev-parse", "--verify", localBranch], repoPath, {
@@ -2770,18 +3035,28 @@ async function fetchPrHead(repoPath, number, localBranch) {
2770
3035
  }
2771
3036
  }
2772
3037
  if (oid) {
2773
- const ensureOid = async (remote) => {
2774
- const got = await git(["fetch", remote, oid], repoPath, {
2775
- reject: false
2776
- });
3038
+ const ensureOid = async (remote, opts) => {
3039
+ const gitOpts = { reject: false };
3040
+ if (opts?.ghAuth) {
3041
+ const token = await resolveGhAuthToken(repoPath);
3042
+ if (!token) return false;
3043
+ gitOpts.env = { GIT_TERMINAL_PROMPT: "0" };
3044
+ gitOpts.config = {
3045
+ "http.extraHeader": `AUTHORIZATION: bearer ${token}`
3046
+ };
3047
+ }
3048
+ const got = await git(["fetch", remote, oid], repoPath, gitOpts);
2777
3049
  return got.exitCode === 0;
2778
3050
  };
2779
3051
  let haveObject = (await git(["cat-file", "-e", `${oid}^{commit}`], repoPath, {
2780
3052
  reject: false
2781
3053
  })).exitCode === 0;
2782
3054
  if (!haveObject) haveObject = await ensureOid("origin");
2783
- if (!haveObject && slug) {
2784
- haveObject = await ensureOid(`https://github.com/${slug}.git`);
3055
+ if (!haveObject && httpsRemote) {
3056
+ haveObject = await ensureOid(httpsRemote);
3057
+ if (!haveObject) {
3058
+ haveObject = await ensureOid(httpsRemote, { ghAuth: true });
3059
+ }
2785
3060
  }
2786
3061
  if (haveObject) {
2787
3062
  const branched = await git(["branch", "-f", localBranch, oid], repoPath, {
@@ -2876,6 +3151,52 @@ ${add.stdout}`;
2876
3151
  await ensureGhPreferOrigin(worktreePath);
2877
3152
  return { branchName, worktreePath };
2878
3153
  }
3154
+ async function createExistingBranchWorktree(opts) {
3155
+ const branchName = opts.branchName.trim();
3156
+ if (!branchName) throw new Error("branch name required");
3157
+ const worktreePath = (0, import_node_path6.join)(worktreesRoot(opts.repoPath), opts.slug);
3158
+ if ((0, import_node_fs5.existsSync)(worktreePath)) {
3159
+ throw new Error(`Worktree already exists at ${worktreePath}`);
3160
+ }
3161
+ await ensureGhPreferOrigin(opts.repoPath);
3162
+ await git(["fetch", "origin", "--prune"], opts.repoPath, { reject: false });
3163
+ if (!branchName.startsWith("origin/") && !branchName.startsWith("refs/")) {
3164
+ await git(["fetch", "origin", branchName], opts.repoPath, { reject: false });
3165
+ }
3166
+ const existing = await listWorktrees(opts.repoPath);
3167
+ const already = existing.find((w) => w.branch === branchName);
3168
+ if (already?.path) {
3169
+ throw new Error(
3170
+ `Branch ${branchName} is already checked out at ${already.path}`
3171
+ );
3172
+ }
3173
+ const startPoint = await resolveWorktreeStartPoint(opts.repoPath, branchName);
3174
+ const add = await git(
3175
+ ["worktree", "add", worktreePath, startPoint],
3176
+ opts.repoPath,
3177
+ { reject: false }
3178
+ );
3179
+ if (add.exitCode !== 0) {
3180
+ const retry = await git(
3181
+ ["worktree", "add", worktreePath, branchName],
3182
+ opts.repoPath,
3183
+ { reject: false }
3184
+ );
3185
+ if (retry.exitCode !== 0) {
3186
+ throw new Error(
3187
+ `Failed to create worktree for ${branchName}: ${retry.stderr.trim() || add.stderr.trim() || retry.stdout.trim() || add.stdout.trim() || `exit ${add.exitCode}`}`
3188
+ );
3189
+ }
3190
+ }
3191
+ const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath, {
3192
+ reject: false
3193
+ });
3194
+ if (head.stdout.trim() === "HEAD" || head.stdout.trim() !== branchName) {
3195
+ await git(["checkout", "-B", branchName], worktreePath, { reject: false });
3196
+ }
3197
+ await ensureGhPreferOrigin(worktreePath);
3198
+ return { branchName, worktreePath };
3199
+ }
2879
3200
  async function removeWorktree(repoPath, worktreePath, opts) {
2880
3201
  await git(["worktree", "remove", "--force", worktreePath], repoPath, {
2881
3202
  reject: false
@@ -2905,8 +3226,27 @@ async function listWorktrees(repoPath) {
2905
3226
  return entries;
2906
3227
  }
2907
3228
  async function isDirty(worktreePath) {
2908
- const { stdout } = await git(["status", "--porcelain"], worktreePath);
2909
- return stdout.trim().length > 0;
3229
+ const { stdout } = await git(["status", "--porcelain", "-uall"], worktreePath);
3230
+ for (const line of stdout.split("\n")) {
3231
+ if (!line.trim()) continue;
3232
+ const rel = porcelainStatusPath(line);
3233
+ if (!rel || isSideboardScratchPath(rel)) continue;
3234
+ return true;
3235
+ }
3236
+ return false;
3237
+ }
3238
+ function isSideboardScratchPath(relativePath) {
3239
+ return isWorkspaceScratchPath(relativePath);
3240
+ }
3241
+ function porcelainStatusPath(line) {
3242
+ const rest = line.length >= 3 ? line.slice(3) : "";
3243
+ if (!rest) return "";
3244
+ const arrow = rest.lastIndexOf(" -> ");
3245
+ const raw = arrow >= 0 ? rest.slice(arrow + 4) : rest;
3246
+ if (raw.startsWith('"') && raw.endsWith('"')) {
3247
+ return raw.slice(1, -1).replace(/\\([\\"])/g, "$1");
3248
+ }
3249
+ return raw;
2910
3250
  }
2911
3251
  async function currentBranch(worktreePath) {
2912
3252
  const { stdout } = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath);
@@ -2924,7 +3264,7 @@ async function pushBranch(worktreePath, branchName) {
2924
3264
  }
2925
3265
  async function mergePr(cwd, selector, opts) {
2926
3266
  const slug = await resolveGithubRepoSlug(cwd);
2927
- const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft"];
3267
+ const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft,number"];
2928
3268
  if (slug) viewArgs.push("--repo", slug);
2929
3269
  const before = await gh(viewArgs, cwd, { reject: false });
2930
3270
  if (before.exitCode !== 0 || !before.stdout.trim()) {
@@ -2932,16 +3272,29 @@ async function mergePr(cwd, selector, opts) {
2932
3272
  }
2933
3273
  let url = "";
2934
3274
  let isDraft = false;
3275
+ let prNumber = null;
2935
3276
  try {
2936
3277
  const parsed = JSON.parse(before.stdout);
2937
3278
  url = String(parsed.url ?? "");
2938
3279
  isDraft = Boolean(parsed.isDraft);
3280
+ prNumber = typeof parsed.number === "number" && Number.isFinite(parsed.number) ? parsed.number : null;
2939
3281
  if (String(parsed.state ?? "").toUpperCase() === "MERGED") {
2940
3282
  return { url, state: "MERGED" };
2941
3283
  }
2942
3284
  } catch {
2943
3285
  throw new Error("Could not parse pull request details");
2944
3286
  }
3287
+ const stack = await getPrStack(cwd);
3288
+ const stackLayer = stack && prNumber != null ? stack.layers.find((l) => l.prNumber === prNumber) : null;
3289
+ if (stack && stackLayer && prNumber != null) {
3290
+ const throughIndex = stack.layers.findIndex((l) => l.prNumber === prNumber);
3291
+ const gate = stackMergeReadiness(stack.layers, throughIndex);
3292
+ if (!gate.readyToMerge) {
3293
+ throw new Error(gate.blockedReason || "Stack is not ready to merge");
3294
+ }
3295
+ await mergePrStack(cwd, { through: prNumber, method: opts?.method ?? "squash" });
3296
+ return { url, state: "MERGED" };
3297
+ }
2945
3298
  if (isDraft) {
2946
3299
  const readyArgs = ["pr", "ready", selector];
2947
3300
  if (slug) readyArgs.push("--repo", slug);
@@ -2963,10 +3316,10 @@ async function mergePr(cwd, selector, opts) {
2963
3316
  const after = await gh(viewArgs, cwd, { reject: false });
2964
3317
  if (after.exitCode === 0 && after.stdout.trim()) {
2965
3318
  try {
2966
- const parsed = JSON.parse(after.stdout);
3319
+ const parsed = after.stdout ? JSON.parse(after.stdout) : null;
2967
3320
  return {
2968
- url: String(parsed.url ?? url),
2969
- state: String(parsed.state ?? "MERGED")
3321
+ url: String(parsed?.url ?? url),
3322
+ state: String(parsed?.state ?? "MERGED")
2970
3323
  };
2971
3324
  } catch {
2972
3325
  }
@@ -3114,7 +3467,9 @@ var init_worktree = __esm({
3114
3467
  init_gh_errors();
3115
3468
  init_run();
3116
3469
  init_pr_gates();
3470
+ init_stack();
3117
3471
  init_teams();
3472
+ init_workspace_scratch();
3118
3473
  init_worktree_labels();
3119
3474
  }
3120
3475
  });
@@ -3240,6 +3595,7 @@ var init_coordinator_prompt = __esm({
3240
3595
  "Discover:",
3241
3596
  "- list_workspaces \u2014 registered repos (path + github slug when known)",
3242
3597
  "- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces (issues: Linear API or GitHub Issues)",
3598
+ "- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
3243
3599
  "- list_models \u2014 only when you need a specific model (rare); otherwise leave model unset = Auto",
3244
3600
  "- list_threads / get_thread \u2014 fleet status (what is going on)",
3245
3601
  "Workspaces:",
@@ -4651,7 +5007,9 @@ var init_injected_mcp = __esm({
4651
5007
  SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS = [
4652
5008
  "mcp__sideboard__present_artifact",
4653
5009
  "mcp__sideboard__present_schema",
4654
- "mcp__sideboard__present_files"
5010
+ "mcp__sideboard__present_files",
5011
+ "mcp__sideboard__ask_user",
5012
+ "mcp__sideboard__present_plan"
4655
5013
  ];
4656
5014
  BRIGHTSY_MCP_ALLOWED_TOOLS = [
4657
5015
  "mcp__brightsy",
@@ -4694,7 +5052,7 @@ var PLAN_MODE_INSTRUCTION;
4694
5052
  var init_types = __esm({
4695
5053
  "src/agents/types.ts"() {
4696
5054
  "use strict";
4697
- 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.";
5055
+ 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.";
4698
5056
  }
4699
5057
  });
4700
5058
 
@@ -6283,6 +6641,116 @@ var init_agents = __esm({
6283
6641
  }
6284
6642
  });
6285
6643
 
6644
+ // src/plan/plan-present.ts
6645
+ function asRecord2(v) {
6646
+ return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
6647
+ }
6648
+ function isPresentPlanToolName(name) {
6649
+ if (!name) return false;
6650
+ return /present_plan$/i.test(name) || /^mcp__sideboard__present_plan$/i.test(name);
6651
+ }
6652
+ function extractPresentedPlan(parts) {
6653
+ if (!parts?.length) return null;
6654
+ for (let i = parts.length - 1; i >= 0; i--) {
6655
+ const p = parts[i];
6656
+ if (p.type !== "tool" || !isPresentPlanToolName(p.name)) continue;
6657
+ const input = asRecord2(p.input) ?? {};
6658
+ const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
6659
+ if (!content.trim()) continue;
6660
+ const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
6661
+ const path = typeof input.path === "string" && input.path.trim() ? input.path.trim() : PLAN_FILE_REL;
6662
+ return { title, content: content.trim(), path, source: "present_plan" };
6663
+ }
6664
+ return null;
6665
+ }
6666
+ function resolvePlanMarkdown(opts) {
6667
+ const fromTool = extractPresentedPlan(opts.parts);
6668
+ if (fromTool) return fromTool;
6669
+ const file = opts.fileContent?.trim();
6670
+ if (file) {
6671
+ return {
6672
+ title: "Plan",
6673
+ content: file,
6674
+ path: PLAN_FILE_REL,
6675
+ source: "exit_plan"
6676
+ };
6677
+ }
6678
+ const text = opts.text?.trim();
6679
+ if (text && text.length >= 80) {
6680
+ return {
6681
+ title: "Plan",
6682
+ content: text,
6683
+ path: PLAN_FILE_REL,
6684
+ source: "text"
6685
+ };
6686
+ }
6687
+ return null;
6688
+ }
6689
+ var PLAN_FILE_REL, PLAN_FILE_NAME, LEGACY_PLAN_FILE_REL;
6690
+ var init_plan_present = __esm({
6691
+ "src/plan/plan-present.ts"() {
6692
+ "use strict";
6693
+ init_workspace_scratch();
6694
+ PLAN_FILE_REL = `${ATTACHMENTS_DIR}/plan.md`;
6695
+ PLAN_FILE_NAME = "plan.md";
6696
+ LEGACY_PLAN_FILE_REL = ".sideboard/plan.md";
6697
+ }
6698
+ });
6699
+
6700
+ // src/plan/plan-file.ts
6701
+ var plan_file_exports = {};
6702
+ __export(plan_file_exports, {
6703
+ LEGACY_PLAN_FILE_REL: () => LEGACY_PLAN_FILE_REL,
6704
+ PLAN_FILE_NAME: () => PLAN_FILE_NAME,
6705
+ PLAN_FILE_REL: () => PLAN_FILE_REL,
6706
+ extractPresentedPlan: () => extractPresentedPlan,
6707
+ isPresentPlanToolName: () => isPresentPlanToolName,
6708
+ planFileAbs: () => planFileAbs,
6709
+ readPlanFile: () => readPlanFile,
6710
+ resolvePlanMarkdown: () => resolvePlanMarkdown,
6711
+ writePlanFile: () => writePlanFile
6712
+ });
6713
+ function ensureAttachmentsGitignore2(worktreePath) {
6714
+ const gitignoreAbs = (0, import_node_path24.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
6715
+ if ((0, import_node_fs26.existsSync)(gitignoreAbs)) return;
6716
+ (0, import_node_fs26.mkdirSync)((0, import_node_path24.dirname)(gitignoreAbs), { recursive: true });
6717
+ (0, import_node_fs26.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
6718
+ }
6719
+ function planFileAbs(worktreePath) {
6720
+ return (0, import_node_path24.join)(worktreePath, PLAN_FILE_REL);
6721
+ }
6722
+ function readTextIfPresent2(abs) {
6723
+ if (!(0, import_node_fs26.existsSync)(abs)) return null;
6724
+ try {
6725
+ const content = (0, import_node_fs26.readFileSync)(abs, "utf8");
6726
+ return content.trim() ? content : null;
6727
+ } catch {
6728
+ return null;
6729
+ }
6730
+ }
6731
+ function readPlanFile(worktreePath) {
6732
+ return readTextIfPresent2(planFileAbs(worktreePath)) ?? readTextIfPresent2((0, import_node_path24.join)(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent2((0, import_node_path24.join)(worktreePath, LEGACY_PLAN_FILE_REL));
6733
+ }
6734
+ function writePlanFile(worktreePath, content) {
6735
+ ensureAttachmentsGitignore2(worktreePath);
6736
+ const abs = planFileAbs(worktreePath);
6737
+ (0, import_node_fs26.mkdirSync)((0, import_node_path24.dirname)(abs), { recursive: true });
6738
+ const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
6739
+ (0, import_node_fs26.writeFileSync)(abs, body, "utf8");
6740
+ return PLAN_FILE_REL;
6741
+ }
6742
+ var import_node_fs26, import_node_path24;
6743
+ var init_plan_file = __esm({
6744
+ "src/plan/plan-file.ts"() {
6745
+ "use strict";
6746
+ import_node_fs26 = require("fs");
6747
+ import_node_path24 = require("path");
6748
+ init_workspace_scratch();
6749
+ init_plan_present();
6750
+ init_plan_present();
6751
+ }
6752
+ });
6753
+
6286
6754
  // src/agents/cursor-recover.ts
6287
6755
  var cursor_recover_exports = {};
6288
6756
  __export(cursor_recover_exports, {
@@ -6291,10 +6759,10 @@ __export(cursor_recover_exports, {
6291
6759
  function recoverFinishedCursorRun(opts) {
6292
6760
  const agentId = opts.agentId.trim();
6293
6761
  if (!agentId) return null;
6294
- const runsPath = (0, import_node_path24.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
6295
- if (!(0, import_node_fs25.existsSync)(runsPath)) return null;
6762
+ const runsPath = (0, import_node_path25.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
6763
+ if (!(0, import_node_fs27.existsSync)(runsPath)) return null;
6296
6764
  try {
6297
- const lines = (0, import_node_fs25.readFileSync)(runsPath, "utf8").split("\n");
6765
+ const lines = (0, import_node_fs27.readFileSync)(runsPath, "utf8").split("\n");
6298
6766
  let best = null;
6299
6767
  for (const line of lines) {
6300
6768
  const trimmed = line.trim();
@@ -6320,12 +6788,12 @@ function recoverFinishedCursorRun(opts) {
6320
6788
  return null;
6321
6789
  }
6322
6790
  }
6323
- var import_node_fs25, import_node_path24;
6791
+ var import_node_fs27, import_node_path25;
6324
6792
  var init_cursor_recover = __esm({
6325
6793
  "src/agents/cursor-recover.ts"() {
6326
6794
  "use strict";
6327
- import_node_fs25 = require("fs");
6328
- import_node_path24 = require("path");
6795
+ import_node_fs27 = require("fs");
6796
+ import_node_path25 = require("path");
6329
6797
  init_paths();
6330
6798
  }
6331
6799
  });
@@ -6364,6 +6832,7 @@ var init_title = __esm({
6364
6832
  // src/index.ts
6365
6833
  var index_exports = {};
6366
6834
  __export(index_exports, {
6835
+ ATTACHMENTS_DIR: () => ATTACHMENTS_DIR,
6367
6836
  BRIGHTSY_MCP_ALLOWED_TOOLS: () => BRIGHTSY_MCP_ALLOWED_TOOLS,
6368
6837
  BrightsySideboardApi: () => BrightsySideboardApi,
6369
6838
  CLAUDE_MODEL_CATALOG: () => CLAUDE_MODEL_CATALOG,
@@ -6379,11 +6848,16 @@ __export(index_exports, {
6379
6848
  FAMOUS_SOCCER_TEAMS: () => FAMOUS_SOCCER_TEAMS,
6380
6849
  GLOBAL_WORKSPACE_ID: () => GLOBAL_WORKSPACE_ID,
6381
6850
  HARNESS_ENV_KEYS: () => HARNESS_ENV_KEYS,
6851
+ LEGACY_ATTACHMENTS_DIR: () => LEGACY_ATTACHMENTS_DIR,
6852
+ LEGACY_PLAN_FILE_REL: () => LEGACY_PLAN_FILE_REL,
6853
+ LEGACY_REVIEW_REQUEST_PATH: () => LEGACY_REVIEW_REQUEST_PATH,
6382
6854
  MAX_ANTHROPIC_CACHE_CONTROL_BLOCKS: () => MAX_ANTHROPIC_CACHE_CONTROL_BLOCKS,
6383
6855
  ORCHESTRATOR_AGENT_KINDS: () => ORCHESTRATOR_AGENT_KINDS,
6384
6856
  Orchestrator: () => Orchestrator,
6385
6857
  PASTE_ATTACH_MIN_CHARS: () => PASTE_ATTACH_MIN_CHARS,
6386
6858
  PASTE_ATTACH_MIN_LINES: () => PASTE_ATTACH_MIN_LINES,
6859
+ PLAN_FILE_NAME: () => PLAN_FILE_NAME,
6860
+ PLAN_FILE_REL: () => PLAN_FILE_REL,
6387
6861
  PLAN_MODE_INSTRUCTION: () => PLAN_MODE_INSTRUCTION,
6388
6862
  REPO_REVIEW_NAME: () => REPO_REVIEW_NAME,
6389
6863
  REPO_REVIEW_PATH: () => REPO_REVIEW_PATH,
@@ -6393,6 +6867,8 @@ __export(index_exports, {
6393
6867
  SIDEBOARD_FORCE_STOP: () => SIDEBOARD_FORCE_STOP,
6394
6868
  SIDEBOARD_MCP_ALLOWED_TOOLS: () => SIDEBOARD_MCP_ALLOWED_TOOLS,
6395
6869
  THINKING_EFFORTS: () => THINKING_EFFORTS,
6870
+ addPrStackLayer: () => addPrStackLayer,
6871
+ addStackLayerFromThread: () => addStackLayerFromThread,
6396
6872
  addWorkspace: () => addWorkspace,
6397
6873
  adoptThread: () => adoptThread,
6398
6874
  allAdapters: () => allAdapters,
@@ -6411,6 +6887,7 @@ __export(index_exports, {
6411
6887
  attachmentFromAbsolutePath: () => attachmentFromAbsolutePath,
6412
6888
  attachmentsFromBuffers: () => attachmentsFromBuffers,
6413
6889
  attachmentsFromWorktreePaths: () => attachmentsFromWorktreePaths,
6890
+ attachmentsGitignoreBody: () => attachmentsGitignoreBody,
6414
6891
  autoCleanupOrphansEnabled: () => autoCleanupOrphansEnabled,
6415
6892
  autoRenameBranchEnabled: () => autoRenameBranchEnabled,
6416
6893
  autoRunAfterSetupEnabled: () => autoRunAfterSetupEnabled,
@@ -6433,6 +6910,7 @@ __export(index_exports, {
6433
6910
  caffeinateWhileRunningEnabled: () => caffeinateWhileRunningEnabled,
6434
6911
  captureLoginEnv: () => captureLoginEnv,
6435
6912
  captureTurnBaseline: () => captureTurnBaseline,
6913
+ checkoutPrStackLayer: () => checkoutPrStackLayer,
6436
6914
  childEnvWithAppSettings: () => childEnvWithAppSettings,
6437
6915
  claudeAdapter: () => claudeAdapter,
6438
6916
  claudeChromeEnabled: () => claudeChromeEnabled,
@@ -6452,8 +6930,10 @@ __export(index_exports, {
6452
6930
  countCacheControlBlocks: () => countCacheControlBlocks,
6453
6931
  createChatTab: () => createChatTab,
6454
6932
  createEmptyThread: () => createEmptyThread,
6933
+ createExistingBranchWorktree: () => createExistingBranchWorktree,
6455
6934
  createGlobalChat: () => createGlobalChat,
6456
6935
  createOrUpdatePr: () => createOrUpdatePr,
6936
+ createPrStack: () => createPrStack,
6457
6937
  createThread: () => createThread,
6458
6938
  createThreadWorktree: () => createThreadWorktree,
6459
6939
  currentBranch: () => currentBranch,
@@ -6463,6 +6943,7 @@ __export(index_exports, {
6463
6943
  deleteBranchOnPurgeEnabled: () => deleteBranchOnPurgeEnabled,
6464
6944
  deleteThreadRecord: () => deleteThreadRecord,
6465
6945
  detectAgents: () => detectAgents,
6946
+ detectGhStack: () => detectGhStack,
6466
6947
  detectLocalMergeConflicts: () => detectLocalMergeConflicts,
6467
6948
  disconnectBrightsyTeam: () => disconnectBrightsyTeam,
6468
6949
  discoverSkills: () => discoverSkills,
@@ -6478,12 +6959,15 @@ __export(index_exports, {
6478
6959
  estimateThreadChars: () => estimateThreadChars,
6479
6960
  expandComposerPrompt: () => expandComposerPrompt,
6480
6961
  extractGhErrorDetail: () => extractGhErrorDetail,
6962
+ extractPendingPlanQuestions: () => extractPendingPlanQuestions,
6963
+ extractPresentedPlan: () => extractPresentedPlan,
6481
6964
  extractiveSummary: () => extractiveSummary,
6482
6965
  fetchPrHead: () => fetchPrHead,
6483
6966
  finalizeParts: () => finalizeParts,
6484
6967
  findInvalidCacheControlTtlOrder: () => findInvalidCacheControlTtlOrder,
6485
6968
  findOrphanWorktrees: () => findOrphanWorktrees,
6486
6969
  findThreadByRef: () => findThreadByRef,
6970
+ findThreadForStackLayer: () => findThreadForStackLayer,
6487
6971
  flattenTurnInput: () => flattenTurnInput,
6488
6972
  forkChatTab: () => forkChatTab,
6489
6973
  forkMessageSlice: () => forkMessageSlice,
@@ -6494,6 +6978,8 @@ __export(index_exports, {
6494
6978
  formatGhLandError: () => formatGhLandError,
6495
6979
  formatIpcInvokeError: () => formatIpcInvokeError,
6496
6980
  formatMessagesAsTranscript: () => formatMessagesAsTranscript,
6981
+ formatPlanQuestionAnswers: () => formatPlanQuestionAnswers,
6982
+ formatPlanQuestionsForChat: () => formatPlanQuestionsForChat,
6497
6983
  formatRateLimitResetHint: () => formatRateLimitResetHint,
6498
6984
  formatRenameBranchDirective: () => formatRenameBranchDirective,
6499
6985
  formatTranscriptMarkdown: () => formatTranscriptMarkdown,
@@ -6517,6 +7003,7 @@ __export(index_exports, {
6517
7003
  getPrChecks: () => getPrChecks,
6518
7004
  getPrDetails: () => getPrDetails,
6519
7005
  getPrMeta: () => getPrMeta,
7006
+ getPrStack: () => getPrStack,
6520
7007
  getRepoSetupInfo: () => getRepoSetupInfo,
6521
7008
  getRunMode: () => getRunMode,
6522
7009
  getRunScript: () => getRunScript,
@@ -6533,9 +7020,12 @@ __export(index_exports, {
6533
7020
  healOrchestrationSoccerTitles: () => healOrchestrationSoccerTitles,
6534
7021
  importConductorWorkspace: () => importConductorWorkspace,
6535
7022
  importConductorWorkspaceAsync: () => importConductorWorkspaceAsync,
7023
+ initPrStack: () => initPrStack,
7024
+ initStackFromThread: () => initStackFromThread,
6536
7025
  initializeGitRepository: () => initializeGitRepository,
6537
7026
  inspectGitWorktree: () => inspectGitWorktree,
6538
7027
  installAgent: () => installAgent,
7028
+ isAskUserToolName: () => isAskUserToolName,
6539
7029
  isBrightsyConnected: () => isBrightsyConnected,
6540
7030
  isBrightsyNdjsonLine: () => isBrightsyNdjsonLine,
6541
7031
  isCloudCoordinatorThread: () => isCloudCoordinatorThread,
@@ -6545,13 +7035,17 @@ __export(index_exports, {
6545
7035
  isGlobalRepoPath: () => isGlobalRepoPath,
6546
7036
  isGlobalThread: () => isGlobalThread,
6547
7037
  isImageFilePath: () => isImageFilePath,
7038
+ isInPrStack: () => isInPrStack,
6548
7039
  isLinearConnected: () => isLinearConnected,
6549
7040
  isOrchestratorCapableAgent: () => isOrchestratorCapableAgent,
6550
7041
  isOrchestratorThread: () => isOrchestratorThread,
6551
7042
  isPidAlive: () => isPidAlive,
6552
7043
  isPlaceholderBranch: () => isPlaceholderBranch,
7044
+ isPresentPlanToolName: () => isPresentPlanToolName,
6553
7045
  isSessionQuotaLimit: () => isSessionQuotaLimit,
7046
+ isSideboardScratchPath: () => isSideboardScratchPath,
6554
7047
  isThinkingEffort: () => isThinkingEffort,
7048
+ isWorkspaceScratchPath: () => isWorkspaceScratchPath,
6555
7049
  listAgentSetupInfo: () => listAgentSetupInfo,
6556
7050
  listBranchCommits: () => listBranchCommits,
6557
7051
  listBranches: () => listBranches,
@@ -6588,6 +7082,7 @@ __export(index_exports, {
6588
7082
  mcpAllowTools: () => mcpAllowTools,
6589
7083
  mcpAuthWarnings: () => mcpAuthWarnings,
6590
7084
  mergePr: () => mergePr,
7085
+ mergePrStack: () => mergePrStack,
6591
7086
  mergeUsage: () => mergeUsage,
6592
7087
  nextPastedTextName: () => nextPastedTextName,
6593
7088
  nextThinkingEffort: () => nextThinkingEffort,
@@ -6597,6 +7092,8 @@ __export(index_exports, {
6597
7092
  normalizeTurnInput: () => normalizeTurnInput,
6598
7093
  normalizeWorktreePath: () => normalizeWorktreePath,
6599
7094
  openInSystemTerminal: () => openInSystemTerminal,
7095
+ openPrStackLayers: () => openPrStackLayers,
7096
+ openStackLayer: () => openStackLayer,
6600
7097
  opencodeAdapter: () => opencodeAdapter,
6601
7098
  orchestrationQuotaFallbackAgent: () => orchestrationQuotaFallbackAgent,
6602
7099
  orchestrationQuotaOnLimit: () => orchestrationQuotaOnLimit,
@@ -6605,15 +7102,19 @@ __export(index_exports, {
6605
7102
  originGhRepoEnv: () => originGhRepoEnv,
6606
7103
  parseCursorRunnerLine: () => parseCursorRunnerLine,
6607
7104
  parseForceStopMessage: () => parseForceStopMessage,
7105
+ parseGhStackViewJson: () => parseGhStackViewJson,
6608
7106
  parseGithubSlugFromRemoteUrl: () => parseGithubSlugFromRemoteUrl,
6609
7107
  parseMcpList: () => parseMcpList,
7108
+ parsePlanQuestionsInput: () => parsePlanQuestionsInput,
6610
7109
  parseSessionQuotaResetAt: () => parseSessionQuotaResetAt,
6611
7110
  partsToAssistantText: () => partsToAssistantText,
6612
7111
  pastedTextStats: () => pastedTextStats,
6613
7112
  permissionMode: () => permissionMode,
7113
+ planFileAbs: () => planFileAbs,
6614
7114
  previewLand: () => previewLand,
6615
7115
  pushBranch: () => pushBranch,
6616
7116
  readExistingReviewRequestFile: () => readExistingReviewRequestFile,
7117
+ readPlanFile: () => readPlanFile,
6617
7118
  readSkillBody: () => readSkillBody,
6618
7119
  readThread: () => readThread,
6619
7120
  readWorktreeFile: () => readWorktreeFile,
@@ -6625,6 +7126,7 @@ __export(index_exports, {
6625
7126
  repoSlug: () => repoSlug,
6626
7127
  requestReview: () => requestReview,
6627
7128
  requireAgent: () => requireAgent,
7129
+ resetGhStackDetectCache: () => resetGhStackDetectCache,
6628
7130
  resolveClaudeExecutable: () => resolveClaudeExecutable,
6629
7131
  resolveConductorCursorAgentId: () => resolveConductorCursorAgentId,
6630
7132
  resolveCursorModelId: () => resolveCursorModelId,
@@ -6632,7 +7134,9 @@ __export(index_exports, {
6632
7134
  resolveDiffBaseRef: () => resolveDiffBaseRef,
6633
7135
  resolveEffectiveIssueSource: () => resolveEffectiveIssueSource,
6634
7136
  resolveFilesToCopy: () => resolveFilesToCopy,
7137
+ resolveGhAuthToken: () => resolveGhAuthToken,
6635
7138
  resolveGithubRepoSlug: () => resolveGithubRepoSlug,
7139
+ resolvePlanMarkdown: () => resolvePlanMarkdown,
6636
7140
  resolvePrSelector: () => resolvePrSelector,
6637
7141
  resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent,
6638
7142
  resolveRepoRoot: () => resolveRepoRoot,
@@ -6660,12 +7164,16 @@ __export(index_exports, {
6660
7164
  slugify: () => slugify,
6661
7165
  spawnAgentTurn: () => spawnAgentTurn,
6662
7166
  splitForCompaction: () => splitForCompaction,
7167
+ stackAgentDefaultsFrom: () => stackAgentDefaultsFrom,
7168
+ stackIdFrom: () => stackIdFrom,
7169
+ stackMergeReadiness: () => stackMergeReadiness,
6663
7170
  stageAbsolutePathsAsAttachments: () => stageAbsolutePathsAsAttachments,
6664
7171
  stageBuffersAsAttachments: () => stageBuffersAsAttachments,
6665
7172
  startDevServer: () => startDevServer,
6666
7173
  startMcpServer: () => startMcpServer,
6667
7174
  startOrchestration: () => startOrchestration,
6668
7175
  stripBrightsyNdjsonNoise: () => stripBrightsyNdjsonNoise,
7176
+ submitPrStack: () => submitPrStack,
6669
7177
  suggestSlug: () => suggestSlug,
6670
7178
  summarizeConversation: () => summarizeConversation,
6671
7179
  switchBrightsyAccount: () => switchBrightsyAccount,
@@ -6701,6 +7209,7 @@ __export(index_exports, {
6701
7209
  worktreeNameFromPath: () => worktreeNameFromPath,
6702
7210
  worktreesRoot: () => worktreesRoot,
6703
7211
  writeInjectedMcpConfig: () => writeInjectedMcpConfig,
7212
+ writePlanFile: () => writePlanFile,
6704
7213
  writeThread: () => writeThread,
6705
7214
  writeWorktreeFile: () => writeWorktreeFile
6706
7215
  });
@@ -6714,6 +7223,7 @@ init_global_workspace();
6714
7223
  init_run();
6715
7224
  init_gh_errors();
6716
7225
  init_worktree();
7226
+ init_stack();
6717
7227
 
6718
7228
  // src/integrations/github.ts
6719
7229
  init_run();
@@ -6910,18 +7420,18 @@ function asRecord(input) {
6910
7420
  }
6911
7421
  return void 0;
6912
7422
  }
6913
- function str(v) {
7423
+ function str2(v) {
6914
7424
  return typeof v === "string" && v.trim() ? v : void 0;
6915
7425
  }
6916
7426
  function toolDetail(name, input) {
6917
7427
  if (!input) return void 0;
6918
- const command = str(input.command) ?? str(input.cmd);
7428
+ const command = str2(input.command) ?? str2(input.cmd);
6919
7429
  if (command) return command;
6920
- const path = str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
7430
+ const path = str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
6921
7431
  if (path) return path;
6922
- const pattern = str(input.pattern) ?? str(input.glob) ?? str(input.glob_pattern);
7432
+ const pattern = str2(input.pattern) ?? str2(input.glob) ?? str2(input.glob_pattern);
6923
7433
  if (pattern) return pattern;
6924
- const query = str(input.query) ?? str(input.prompt);
7434
+ const query = str2(input.query) ?? str2(input.prompt);
6925
7435
  if (query) return query.length > 80 ? `${query.slice(0, 77)}\u2026` : query;
6926
7436
  try {
6927
7437
  const raw = JSON.stringify(input);
@@ -6934,23 +7444,26 @@ function toolDescription(name, input) {
6934
7444
  const n = name.replace(/^mcp__/, "").replace(/__/g, " \xB7 ");
6935
7445
  if (/^get_record_types$|recordTypes/i.test(name)) return "List record types";
6936
7446
  if (/connectedAgentRequest/i.test(name)) {
6937
- return str(input?.agent_id) ? `Ask connected agent` : "Ask connected agent";
7447
+ return str2(input?.agent_id) ? `Ask connected agent` : "Ask connected agent";
6938
7448
  }
6939
7449
  if (/present_artifact$/i.test(name)) {
6940
- return str(input?.title) ? `Present ${str(input?.title)}` : "Present artifact";
7450
+ return str2(input?.title) ? `Present ${str2(input?.title)}` : "Present artifact";
7451
+ }
7452
+ if (/present_plan$/i.test(name)) {
7453
+ return str2(input?.title) ? `Plan ${str2(input?.title)}` : "Present plan";
6941
7454
  }
6942
7455
  if (/present_schema$/i.test(name)) {
6943
- return str(input?.title) ? `Schema ${str(input?.title)}` : "Present schema";
7456
+ return str2(input?.title) ? `Schema ${str2(input?.title)}` : "Present schema";
6944
7457
  }
6945
7458
  if (/present_files$/i.test(name)) {
6946
- return str(input?.title) ? `Files ${str(input?.title)}` : "Present files";
7459
+ return str2(input?.title) ? `Files ${str2(input?.title)}` : "Present files";
6947
7460
  }
6948
7461
  if (/^(create|update)_artifact$/i.test(name.replace(/^mcp__[^_]+__/, ""))) {
6949
- return str(input?.title) ? `Artifact ${str(input?.title)}` : "Artifact";
7462
+ return str2(input?.title) ? `Artifact ${str2(input?.title)}` : "Artifact";
6950
7463
  }
6951
7464
  if (!input) return n;
6952
- if (/bash|shell|terminal/i.test(name) && str(input.command)) {
6953
- const cmd = str(input.command);
7465
+ if (/bash|shell|terminal/i.test(name) && str2(input.command)) {
7466
+ const cmd = str2(input.command);
6954
7467
  if (/git\s+fetch/i.test(cmd)) return "Fetch latest from origin and check status";
6955
7468
  if (/git\s+status/i.test(cmd)) return "Check git status";
6956
7469
  if (/git\s+log/i.test(cmd)) return "Inspect recent commits";
@@ -6959,11 +7472,11 @@ function toolDescription(name, input) {
6959
7472
  return "Run shell command";
6960
7473
  }
6961
7474
  if (/edit|write|apply/i.test(name)) {
6962
- const path = str(input.file_path) ?? str(input.path);
7475
+ const path = str2(input.file_path) ?? str2(input.path);
6963
7476
  return path ? `Edit ${path.split("/").pop()}` : "Edit file";
6964
7477
  }
6965
7478
  if (/read/i.test(name)) {
6966
- const path = str(input.file_path) ?? str(input.path);
7479
+ const path = str2(input.file_path) ?? str2(input.path);
6967
7480
  return path ? `Read ${path.split("/").pop()}` : "Read file";
6968
7481
  }
6969
7482
  if (/grep/i.test(name)) return "Search files";
@@ -6972,7 +7485,7 @@ function toolDescription(name, input) {
6972
7485
  }
6973
7486
  function toolFilePath(input) {
6974
7487
  if (!input) return void 0;
6975
- return str(input.file_path) ?? str(input.path) ?? str(input.filePath) ?? str(input.filename);
7488
+ return str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
6976
7489
  }
6977
7490
  function countLines(text) {
6978
7491
  if (!text) return 0;
@@ -6980,8 +7493,8 @@ function countLines(text) {
6980
7493
  }
6981
7494
  function diffFromInput(input) {
6982
7495
  if (!input) return {};
6983
- const oldS = str(input.old_string) ?? str(input.oldString);
6984
- const newS = str(input.new_string) ?? str(input.newString) ?? str(input.content);
7496
+ const oldS = str2(input.old_string) ?? str2(input.oldString);
7497
+ const newS = str2(input.new_string) ?? str2(input.newString) ?? str2(input.content);
6985
7498
  if (oldS != null || newS != null) {
6986
7499
  return {
6987
7500
  additions: countLines(newS),
@@ -7990,7 +8503,7 @@ async function listUntrackedPaths(worktreePath) {
7990
8503
  worktreePath,
7991
8504
  { reject: false }
7992
8505
  );
7993
- return stdout.split("\0").filter(Boolean);
8506
+ return stdout.split("\0").filter(Boolean).filter((p) => !isSideboardScratchPath(p));
7994
8507
  }
7995
8508
  async function listUntrackedFiles(worktreePath, maxHunk) {
7996
8509
  const paths = await listUntrackedPaths(worktreePath);
@@ -8148,33 +8661,33 @@ async function getDiff(worktreePath, repoPath, opts) {
8148
8661
  let combinedDiff = "";
8149
8662
  let labelBase = base;
8150
8663
  if (scope === "staged") {
8151
- const [ns, num, diff] = await Promise.all([
8664
+ const [ns, num2, diff] = await Promise.all([
8152
8665
  git(["diff", "--name-status", "--cached"], worktreePath, { reject: false }),
8153
8666
  git(["diff", "--numstat", "--cached"], worktreePath, { reject: false }),
8154
8667
  git(["diff", "--cached"], worktreePath, { reject: false })
8155
8668
  ]);
8156
8669
  nameStatus = ns.stdout;
8157
- numstat = num.stdout;
8670
+ numstat = num2.stdout;
8158
8671
  combinedDiff = diff.stdout;
8159
8672
  labelBase = "staged";
8160
8673
  } else if (scope === "unstaged") {
8161
- const [ns, num, diff] = await Promise.all([
8674
+ const [ns, num2, diff] = await Promise.all([
8162
8675
  git(["diff", "--name-status"], worktreePath, { reject: false }),
8163
8676
  git(["diff", "--numstat"], worktreePath, { reject: false }),
8164
8677
  git(["diff"], worktreePath, { reject: false })
8165
8678
  ]);
8166
8679
  nameStatus = ns.stdout;
8167
- numstat = num.stdout;
8680
+ numstat = num2.stdout;
8168
8681
  combinedDiff = diff.stdout;
8169
8682
  labelBase = "unstaged";
8170
8683
  } else if (scope === "uncommitted") {
8171
- const [ns, num, diff] = await Promise.all([
8684
+ const [ns, num2, diff] = await Promise.all([
8172
8685
  git(["diff", "--name-status", "HEAD"], worktreePath, { reject: false }),
8173
8686
  git(["diff", "--numstat", "HEAD"], worktreePath, { reject: false }),
8174
8687
  git(["diff", "HEAD"], worktreePath, { reject: false })
8175
8688
  ]);
8176
8689
  nameStatus = ns.stdout;
8177
- numstat = num.stdout;
8690
+ numstat = num2.stdout;
8178
8691
  combinedDiff = diff.stdout;
8179
8692
  labelBase = "HEAD";
8180
8693
  } else if (scope === "last_turn") {
@@ -8193,34 +8706,34 @@ async function getDiff(worktreePath, repoPath, opts) {
8193
8706
  scopeStats
8194
8707
  };
8195
8708
  }
8196
- const [ns, num, diff] = await Promise.all([
8709
+ const [ns, num2, diff] = await Promise.all([
8197
8710
  git(["diff", "--name-status", lastTurnBase], worktreePath, { reject: false }),
8198
8711
  git(["diff", "--numstat", lastTurnBase], worktreePath, { reject: false }),
8199
8712
  git(["diff", lastTurnBase], worktreePath, { reject: false })
8200
8713
  ]);
8201
8714
  nameStatus = ns.stdout;
8202
- numstat = num.stdout;
8715
+ numstat = num2.stdout;
8203
8716
  combinedDiff = diff.stdout;
8204
8717
  labelBase = "last turn";
8205
8718
  } else if (scope === "commits" && commitSha) {
8206
8719
  const range = `${commitSha}^!`;
8207
- const [ns, num, diff] = await Promise.all([
8720
+ const [ns, num2, diff] = await Promise.all([
8208
8721
  git(["diff", "--name-status", range], worktreePath, { reject: false }),
8209
8722
  git(["diff", "--numstat", range], worktreePath, { reject: false }),
8210
8723
  git(["diff", range], worktreePath, { reject: false })
8211
8724
  ]);
8212
8725
  nameStatus = ns.stdout;
8213
- numstat = num.stdout;
8726
+ numstat = num2.stdout;
8214
8727
  combinedDiff = diff.stdout;
8215
8728
  labelBase = commitSha.slice(0, 7);
8216
8729
  } else {
8217
- const [ns, num, diff] = await Promise.all([
8730
+ const [ns, num2, diff] = await Promise.all([
8218
8731
  git(["diff", "--name-status", mergeBase], worktreePath, { reject: false }),
8219
8732
  git(["diff", "--numstat", mergeBase], worktreePath, { reject: false }),
8220
8733
  git(["diff", mergeBase], worktreePath, { reject: false })
8221
8734
  ]);
8222
8735
  nameStatus = ns.stdout;
8223
- numstat = num.stdout;
8736
+ numstat = num2.stdout;
8224
8737
  combinedDiff = diff.stdout;
8225
8738
  labelBase = base;
8226
8739
  }
@@ -8653,6 +9166,7 @@ function buildDiffCommentAttachment(input) {
8653
9166
  var import_node_fs19 = require("fs");
8654
9167
  var import_node_path19 = require("path");
8655
9168
  var import_node_crypto2 = require("crypto");
9169
+ init_workspace_scratch();
8656
9170
  var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
8657
9171
  "png",
8658
9172
  "jpg",
@@ -8673,11 +9187,6 @@ var IMAGE_MIME_BY_EXT = {
8673
9187
  bmp: "image/bmp",
8674
9188
  ico: "image/x-icon"
8675
9189
  };
8676
- var ATTACHMENTS_DIR = ".sideboard/attachments";
8677
- var ATTACHMENTS_GITIGNORE = `# Sideboard review / composer attachments (local only)
8678
- *
8679
- !.gitignore
8680
- `;
8681
9190
  var MAX_INLINE_BYTES = 4e5;
8682
9191
  var MAX_PREVIEW_BYTES = 5e6;
8683
9192
  function fileExtension(filePath) {
@@ -8695,7 +9204,7 @@ function ensureAttachmentsDir(worktreePath) {
8695
9204
  (0, import_node_fs19.mkdirSync)(dir, { recursive: true });
8696
9205
  const gi = (0, import_node_path19.join)(dir, ".gitignore");
8697
9206
  if (!(0, import_node_fs19.existsSync)(gi)) {
8698
- (0, import_node_fs19.writeFileSync)(gi, ATTACHMENTS_GITIGNORE, "utf8");
9207
+ (0, import_node_fs19.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
8699
9208
  }
8700
9209
  return dir;
8701
9210
  }
@@ -9355,7 +9864,7 @@ var import_node_fs20 = require("fs");
9355
9864
  init_worktree();
9356
9865
  init_thread_store();
9357
9866
  init_workspaces();
9358
- async function createThread(input, onSetupLine) {
9867
+ async function createThread(input, _onSetupLine) {
9359
9868
  await requireAgent(input.agent);
9360
9869
  const repoPath = await resolveRepoRoot(input.repoPath);
9361
9870
  if (!(0, import_node_fs20.existsSync)(repoPath)) {
@@ -9365,10 +9874,10 @@ async function createThread(input, onSetupLine) {
9365
9874
  let sourceIsFork = false;
9366
9875
  let prUrl = null;
9367
9876
  if (input.sourceType === "pr") {
9368
- const num = Number(input.sourceRef.replace(/^#/, ""));
9369
- if (!Number.isFinite(num)) throw new Error(`Invalid PR number: ${input.sourceRef}`);
9370
- const pr = await getPr(repoPath, num);
9371
- if (!pr) throw new Error(`PR #${num} not found`);
9877
+ const num2 = Number(input.sourceRef.replace(/^#/, ""));
9878
+ if (!Number.isFinite(num2)) throw new Error(`Invalid PR number: ${input.sourceRef}`);
9879
+ const pr = await getPr(repoPath, num2);
9880
+ if (!pr) throw new Error(`PR #${num2} not found`);
9372
9881
  sourceIsFork = pr.isCrossRepository;
9373
9882
  prUrl = pr.url;
9374
9883
  const localFetchBranch = `sideboard-pr-${pr.number}`;
@@ -9413,22 +9922,6 @@ async function createThread(input, onSetupLine) {
9413
9922
  });
9414
9923
  writeThread(thread);
9415
9924
  await ensureWorkspace(repoPath);
9416
- try {
9417
- let setup = await runSetupScript(repoPath, worktreePath, onSetupLine);
9418
- if (!setup.ran) {
9419
- setup = await runCursorWorktreeSetup(repoPath, worktreePath, onSetupLine);
9420
- }
9421
- if (setup.ran && setup.exitCode !== 0 && setup.exitCode !== null) {
9422
- updateThread(thread.id, {
9423
- lastError: `Setup exited ${setup.exitCode} (thread is still usable)`
9424
- });
9425
- }
9426
- } catch (err) {
9427
- const message = err instanceof Error ? err.message : String(err);
9428
- updateThread(thread.id, {
9429
- lastError: `Setup failed: ${message}`
9430
- });
9431
- }
9432
9925
  return readThread(thread.id) ?? thread;
9433
9926
  }
9434
9927
  async function listLinearIssues(agent, repoPath) {
@@ -9481,7 +9974,9 @@ function worktreeBindingFrom(from) {
9481
9974
  sourceIsFork: from.sourceIsFork,
9482
9975
  parentThreadId: from.parentThreadId,
9483
9976
  prUrl: from.prUrl,
9484
- prTitle: from.prTitle
9977
+ prTitle: from.prTitle,
9978
+ stackId: from.stackId,
9979
+ stackLayer: from.stackLayer
9485
9980
  };
9486
9981
  }
9487
9982
  function threadsSharingWorktree(worktreePath) {
@@ -9596,9 +10091,308 @@ async function forkThreadWorktree(input, onSetupLine) {
9596
10091
  return thread;
9597
10092
  }
9598
10093
 
10094
+ // src/threads/stack-layers.ts
10095
+ var import_node_fs21 = require("fs");
10096
+ init_run();
10097
+ init_stack();
10098
+ init_worktree();
10099
+ init_thread_store();
10100
+ init_workspaces();
10101
+ function stackIdFrom(stack) {
10102
+ if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
10103
+ const key = stack.layers.map((l) => l.branchName).join("|");
10104
+ if (!key) return null;
10105
+ return `gh-stack-local-${hashShort(key)}`;
10106
+ }
10107
+ function hashShort(s) {
10108
+ let h = 0;
10109
+ for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
10110
+ return Math.abs(h).toString(36);
10111
+ }
10112
+ function requireThread3(idOrRef) {
10113
+ const thread = findThreadByRef(idOrRef) ?? readThread(idOrRef);
10114
+ if (!thread) throw new Error(`Thread not found: ${idOrRef}`);
10115
+ return thread;
10116
+ }
10117
+ function sanitizeSlugPart(name) {
10118
+ return name.trim().replace(/^refs\/heads\//, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "layer";
10119
+ }
10120
+ function layerSlug(stackId, layer) {
10121
+ const stackPart = stackId.replace(/^gh-stack-/, "s");
10122
+ return sanitizeSlugPart(`${stackPart}-L${layer.position}-${layer.branchName}`);
10123
+ }
10124
+ function findThreadForStackLayer(repoPath, stackId, layer) {
10125
+ const threads = listThreads({ includeArchived: false }).filter(
10126
+ (t) => t.repoPath === repoPath && t.stackId === stackId
10127
+ );
10128
+ const byLayer = threads.find((t) => t.stackLayer === layer.position);
10129
+ if (byLayer) return byLayer;
10130
+ return threads.find((t) => t.branchName === layer.branchName) ?? listThreads({ includeArchived: false }).find(
10131
+ (t) => t.repoPath === repoPath && t.branchName === layer.branchName
10132
+ ) ?? null;
10133
+ }
10134
+ async function openStackLayer(input, _onSetupLine) {
10135
+ await requireAgent(input.agent);
10136
+ const repoPath = await resolveRepoRoot(input.repoPath);
10137
+ const stackId = stackIdFrom(input.stack);
10138
+ if (!stackId) throw new Error("Cannot open stack layer without a stack id");
10139
+ const existing = findThreadForStackLayer(repoPath, stackId, input.layer);
10140
+ if (existing) {
10141
+ const patch = {};
10142
+ if (existing.stackId !== stackId) patch.stackId = stackId;
10143
+ if (existing.stackLayer !== input.layer.position) {
10144
+ patch.stackLayer = input.layer.position;
10145
+ }
10146
+ if (input.layer.prUrl && existing.prUrl !== input.layer.prUrl) {
10147
+ patch.prUrl = input.layer.prUrl;
10148
+ }
10149
+ if (input.layer.title && existing.prTitle !== input.layer.title) {
10150
+ patch.prTitle = input.layer.title;
10151
+ }
10152
+ if (Object.keys(patch).length > 0) updateThread(existing.id, patch);
10153
+ return {
10154
+ thread: readThread(existing.id) ?? existing,
10155
+ createdWorktree: false
10156
+ };
10157
+ }
10158
+ let worktreePath = null;
10159
+ let branchName = input.layer.branchName;
10160
+ let createdWorktree = false;
10161
+ const trees = await listWorktrees(repoPath);
10162
+ const checkedOut = trees.find((w) => w.branch === branchName);
10163
+ if (checkedOut?.path && (0, import_node_fs21.existsSync)(checkedOut.path)) {
10164
+ if (input.reuseExistingWorktree !== false) {
10165
+ worktreePath = checkedOut.path;
10166
+ } else {
10167
+ throw new Error(
10168
+ `Branch ${branchName} is already checked out at ${checkedOut.path}`
10169
+ );
10170
+ }
10171
+ }
10172
+ if (!worktreePath) {
10173
+ const slug = layerSlug(stackId, input.layer);
10174
+ const created = await createExistingBranchWorktree({
10175
+ repoPath,
10176
+ branchName,
10177
+ slug
10178
+ });
10179
+ worktreePath = created.worktreePath;
10180
+ branchName = created.branchName;
10181
+ copyConfiguredFiles(repoPath, worktreePath);
10182
+ createdWorktree = true;
10183
+ }
10184
+ const title = input.layer.title?.trim() || (input.layer.prNumber != null ? `PR #${input.layer.prNumber}` : input.layer.branchName);
10185
+ const thread = createEmptyThread({
10186
+ title,
10187
+ userSetTitle: Boolean(input.layer.title?.trim()),
10188
+ sourceType: input.layer.prNumber != null ? "pr" : "branch",
10189
+ sourceRef: input.layer.prNumber != null ? String(input.layer.prNumber) : input.layer.branchName,
10190
+ branchName,
10191
+ worktreePath,
10192
+ repoPath,
10193
+ agent: input.agent,
10194
+ autonomy: input.autonomy ?? "default",
10195
+ model: input.model ?? null,
10196
+ effort: input.effort ?? "high",
10197
+ fast: Boolean(input.fast),
10198
+ planMode: Boolean(input.planMode),
10199
+ parentThreadId: input.parentThreadId ?? null,
10200
+ status: "idle",
10201
+ prUrl: input.layer.prUrl,
10202
+ prTitle: input.layer.title ?? null,
10203
+ stackId,
10204
+ stackLayer: input.layer.position
10205
+ });
10206
+ writeThread(thread);
10207
+ await ensureWorkspace(repoPath);
10208
+ return { thread: readThread(thread.id) ?? thread, createdWorktree };
10209
+ }
10210
+ async function openPrStackLayers(input, onSetupLine) {
10211
+ const from = requireThread3(input.threadRef);
10212
+ if (!from.worktreePath?.trim() || !from.repoPath?.trim()) {
10213
+ throw new Error("Thread has no worktree");
10214
+ }
10215
+ const stack = await getPrStack(from.worktreePath);
10216
+ if (!stack) throw new Error("Current branch is not part of a GitHub PR stack");
10217
+ const layers = input.layer != null ? stack.layers.filter((l) => l.position === input.layer) : stack.layers;
10218
+ if (!layers.length) {
10219
+ throw new Error(
10220
+ input.layer != null ? `No stack layer at position ${input.layer}` : "Stack has no layers"
10221
+ );
10222
+ }
10223
+ const threads = [];
10224
+ const createdThreadIds = [];
10225
+ for (const layer of layers) {
10226
+ const { thread, createdWorktree } = await openStackLayer(
10227
+ {
10228
+ repoPath: from.repoPath,
10229
+ stack,
10230
+ layer,
10231
+ agent: from.agent,
10232
+ autonomy: from.autonomy,
10233
+ model: from.model,
10234
+ effort: from.effort,
10235
+ fast: from.fast,
10236
+ planMode: from.planMode,
10237
+ parentThreadId: from.id
10238
+ },
10239
+ onSetupLine
10240
+ );
10241
+ threads.push(thread);
10242
+ if (createdWorktree) createdThreadIds.push(thread.id);
10243
+ }
10244
+ const stackId = stackIdFrom(stack);
10245
+ const current = stack.layers[stack.currentIndex];
10246
+ if (stackId) {
10247
+ updateThread(from.id, {
10248
+ stackId,
10249
+ stackLayer: current?.position ?? from.stackLayer
10250
+ });
10251
+ }
10252
+ return { stack, threads, createdThreadIds };
10253
+ }
10254
+ async function addStackLayerFromThread(input, onSetupLine) {
10255
+ const from = requireThread3(input.threadRef);
10256
+ if (!from.worktreePath?.trim() || !from.repoPath?.trim()) {
10257
+ throw new Error("Thread has no worktree");
10258
+ }
10259
+ const status = await detectGhStack(from.worktreePath);
10260
+ if (!status.available) throw new Error(status.reason);
10261
+ await addPrStackLayer(from.worktreePath, input.branchName);
10262
+ const stack = await getPrStack(from.worktreePath);
10263
+ if (!stack) throw new Error("Stack not found after adding layer");
10264
+ const layer = stack.layers.find((l) => l.branchName === input.branchName.trim()) ?? stack.layers[stack.layers.length - 1];
10265
+ if (!layer) throw new Error("New stack layer not found");
10266
+ if (input.title?.trim()) {
10267
+ layer.title = input.title.trim();
10268
+ }
10269
+ const thread = await openStackLayer(
10270
+ {
10271
+ repoPath: from.repoPath,
10272
+ stack,
10273
+ layer,
10274
+ agent: from.agent,
10275
+ autonomy: from.autonomy,
10276
+ model: from.model,
10277
+ effort: from.effort,
10278
+ fast: from.fast,
10279
+ planMode: from.planMode,
10280
+ parentThreadId: from.id
10281
+ },
10282
+ onSetupLine
10283
+ );
10284
+ return { stack, thread: thread.thread, createdWorktree: thread.createdWorktree };
10285
+ }
10286
+ async function initStackFromThread(input, onSetupLine) {
10287
+ const from = requireThread3(input.threadRef);
10288
+ if (!from.worktreePath?.trim() || !from.branchName?.trim() || !from.repoPath?.trim()) {
10289
+ throw new Error("Thread has no worktree/branch");
10290
+ }
10291
+ const status = await detectGhStack(from.worktreePath);
10292
+ if (!status.available) throw new Error(status.reason);
10293
+ const branches = [
10294
+ from.branchName,
10295
+ ...(input.additionalBranches ?? []).map((b) => b.trim()).filter(Boolean)
10296
+ ];
10297
+ await initPrStack(from.worktreePath, branches, {
10298
+ base: input.base ?? await resolveDefaultBranch(from.repoPath)
10299
+ });
10300
+ return openPrStackLayers({ threadRef: from.id }, onSetupLine);
10301
+ }
10302
+ async function createPrStack(input, onSetupLine) {
10303
+ await requireAgent(input.agent);
10304
+ const repoPath = await resolveRepoRoot(input.repoPath);
10305
+ if (!(0, import_node_fs21.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
10306
+ if (!input.branches.length) throw new Error("At least one branch name required");
10307
+ const status = await detectGhStack(repoPath);
10308
+ if (!status.available) throw new Error(status.reason);
10309
+ const team = allocateTeamSlug(repoPath);
10310
+ const base = input.base ?? await resolveDefaultBranch(repoPath);
10311
+ const bootstrap = await createThreadWorktree({
10312
+ repoPath,
10313
+ sourceRef: base,
10314
+ slug: `${team.slug}-stack-init`
10315
+ });
10316
+ copyConfiguredFiles(repoPath, bootstrap.worktreePath);
10317
+ try {
10318
+ await initPrStack(bootstrap.worktreePath, input.branches, { base });
10319
+ const bottom = input.branches[0];
10320
+ const co = await git(["checkout", bottom], bootstrap.worktreePath, {
10321
+ reject: false
10322
+ });
10323
+ if (co.exitCode !== 0) {
10324
+ throw new Error(
10325
+ co.stderr.trim() || co.stdout.trim() || `Could not check out ${bottom} after stack init`
10326
+ );
10327
+ }
10328
+ } catch (err) {
10329
+ try {
10330
+ await removeWorktree(repoPath, bootstrap.worktreePath, {
10331
+ deleteBranch: bootstrap.branchName
10332
+ });
10333
+ } catch {
10334
+ }
10335
+ throw err;
10336
+ }
10337
+ const stack = await getPrStack(bootstrap.worktreePath);
10338
+ if (!stack) {
10339
+ try {
10340
+ await removeWorktree(repoPath, bootstrap.worktreePath, {
10341
+ deleteBranch: bootstrap.branchName
10342
+ });
10343
+ } catch {
10344
+ }
10345
+ throw new Error("Stack init succeeded but gh stack view returned no stack");
10346
+ }
10347
+ const threads = [];
10348
+ const createdThreadIds = [];
10349
+ for (const layer of stack.layers) {
10350
+ const title = layer.position === 1 && input.title?.trim() ? input.title.trim() : layer.title;
10351
+ const opened = await openStackLayer(
10352
+ {
10353
+ repoPath,
10354
+ stack,
10355
+ layer: title ? { ...layer, title } : layer,
10356
+ agent: input.agent,
10357
+ autonomy: input.autonomy,
10358
+ model: input.model,
10359
+ effort: input.effort,
10360
+ fast: input.fast,
10361
+ planMode: input.planMode,
10362
+ reuseExistingWorktree: true
10363
+ },
10364
+ onSetupLine
10365
+ );
10366
+ threads.push(opened.thread);
10367
+ if (opened.createdWorktree || opened.thread.worktreePath === bootstrap.worktreePath) {
10368
+ createdThreadIds.push(opened.thread.id);
10369
+ }
10370
+ }
10371
+ const claimed = new Set(threads.map((t) => t.worktreePath));
10372
+ if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs21.existsSync)(bootstrap.worktreePath)) {
10373
+ try {
10374
+ await removeWorktree(repoPath, bootstrap.worktreePath, {
10375
+ deleteBranch: bootstrap.branchName
10376
+ });
10377
+ } catch {
10378
+ }
10379
+ }
10380
+ return { stack, threads, createdThreadIds };
10381
+ }
10382
+ function stackAgentDefaultsFrom(input) {
10383
+ return {
10384
+ agent: input.agent,
10385
+ autonomy: input.autonomy,
10386
+ model: input.model,
10387
+ effort: input.effort,
10388
+ fast: input.fast,
10389
+ planMode: input.planMode
10390
+ };
10391
+ }
10392
+
9599
10393
  // src/threads/adopt.ts
9600
10394
  var import_node_child_process = require("child_process");
9601
- var import_node_fs21 = require("fs");
10395
+ var import_node_fs22 = require("fs");
9602
10396
  var import_node_os9 = require("os");
9603
10397
  var import_node_path20 = require("path");
9604
10398
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
@@ -9623,21 +10417,21 @@ function mapAgentType(raw) {
9623
10417
  return null;
9624
10418
  }
9625
10419
  function resolveConductorCursorAgentId(workspacePath) {
9626
- if (!workspacePath || !(0, import_node_fs21.existsSync)(CURSOR_SDK_STORE)) return null;
10420
+ if (!workspacePath || !(0, import_node_fs22.existsSync)(CURSOR_SDK_STORE)) return null;
9627
10421
  const normalized = workspacePath.replace(/\/$/, "");
9628
10422
  let best = null;
9629
10423
  let hashes;
9630
10424
  try {
9631
- hashes = (0, import_node_fs21.readdirSync)(CURSOR_SDK_STORE);
10425
+ hashes = (0, import_node_fs22.readdirSync)(CURSOR_SDK_STORE);
9632
10426
  } catch {
9633
10427
  return null;
9634
10428
  }
9635
10429
  for (const hash of hashes) {
9636
10430
  const agentsFile = (0, import_node_path20.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
9637
- if (!(0, import_node_fs21.existsSync)(agentsFile)) continue;
10431
+ if (!(0, import_node_fs22.existsSync)(agentsFile)) continue;
9638
10432
  let text;
9639
10433
  try {
9640
- text = (0, import_node_fs21.readFileSync)(agentsFile, "utf8");
10434
+ text = (0, import_node_fs22.readFileSync)(agentsFile, "utf8");
9641
10435
  } catch {
9642
10436
  continue;
9643
10437
  }
@@ -9661,7 +10455,7 @@ function resolveConductorCursorAgentId(workspacePath) {
9661
10455
  return best?.agentId ?? null;
9662
10456
  }
9663
10457
  async function adoptThread(input) {
9664
- if (!(0, import_node_fs21.existsSync)(input.worktreePath)) {
10458
+ if (!(0, import_node_fs22.existsSync)(input.worktreePath)) {
9665
10459
  throw new Error(`Worktree not found: ${input.worktreePath}`);
9666
10460
  }
9667
10461
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -9688,18 +10482,18 @@ function conductorDbPath() {
9688
10482
  return CONDUCTOR_DB;
9689
10483
  }
9690
10484
  function listConductorWorkspaces() {
9691
- if (!(0, import_node_fs21.existsSync)(CONDUCTOR_DB)) {
10485
+ if (!(0, import_node_fs22.existsSync)(CONDUCTOR_DB)) {
9692
10486
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
9693
10487
  }
9694
- const tmp = (0, import_node_fs21.mkdtempSync)((0, import_node_path20.join)((0, import_node_os9.tmpdir)(), "sideboard-conductor-"));
10488
+ const tmp = (0, import_node_fs22.mkdtempSync)((0, import_node_path20.join)((0, import_node_os9.tmpdir)(), "sideboard-conductor-"));
9695
10489
  const snapshot = (0, import_node_path20.join)(tmp, "conductor.db");
9696
10490
  try {
9697
- (0, import_node_fs21.copyFileSync)(CONDUCTOR_DB, snapshot);
10491
+ (0, import_node_fs22.copyFileSync)(CONDUCTOR_DB, snapshot);
9698
10492
  for (const suffix of ["-wal", "-shm"]) {
9699
10493
  const src = `${CONDUCTOR_DB}${suffix}`;
9700
- if ((0, import_node_fs21.existsSync)(src)) {
10494
+ if ((0, import_node_fs22.existsSync)(src)) {
9701
10495
  try {
9702
- (0, import_node_fs21.copyFileSync)(src, `${snapshot}${suffix}`);
10496
+ (0, import_node_fs22.copyFileSync)(src, `${snapshot}${suffix}`);
9703
10497
  } catch {
9704
10498
  }
9705
10499
  }
@@ -9775,22 +10569,22 @@ function listConductorWorkspaces() {
9775
10569
  db.close();
9776
10570
  }
9777
10571
  } finally {
9778
- (0, import_node_fs21.rmSync)(tmp, { recursive: true, force: true });
10572
+ (0, import_node_fs22.rmSync)(tmp, { recursive: true, force: true });
9779
10573
  }
9780
10574
  }
9781
10575
  function importConductorWorkspace(workspaceId) {
9782
- if (!(0, import_node_fs21.existsSync)(CONDUCTOR_DB)) {
10576
+ if (!(0, import_node_fs22.existsSync)(CONDUCTOR_DB)) {
9783
10577
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
9784
10578
  }
9785
- const tmp = (0, import_node_fs21.mkdtempSync)((0, import_node_path20.join)((0, import_node_os9.tmpdir)(), "sideboard-conductor-"));
10579
+ const tmp = (0, import_node_fs22.mkdtempSync)((0, import_node_path20.join)((0, import_node_os9.tmpdir)(), "sideboard-conductor-"));
9786
10580
  const snapshot = (0, import_node_path20.join)(tmp, "conductor.db");
9787
10581
  try {
9788
- (0, import_node_fs21.copyFileSync)(CONDUCTOR_DB, snapshot);
10582
+ (0, import_node_fs22.copyFileSync)(CONDUCTOR_DB, snapshot);
9789
10583
  for (const suffix of ["-wal", "-shm"]) {
9790
10584
  const src = `${CONDUCTOR_DB}${suffix}`;
9791
- if ((0, import_node_fs21.existsSync)(src)) {
10585
+ if ((0, import_node_fs22.existsSync)(src)) {
9792
10586
  try {
9793
- (0, import_node_fs21.copyFileSync)(src, `${snapshot}${suffix}`);
10587
+ (0, import_node_fs22.copyFileSync)(src, `${snapshot}${suffix}`);
9794
10588
  } catch {
9795
10589
  }
9796
10590
  }
@@ -9808,7 +10602,7 @@ function importConductorWorkspace(workspaceId) {
9808
10602
  ).get(workspaceId);
9809
10603
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
9810
10604
  const worktreePath = String(row.workspacePath);
9811
- if (!(0, import_node_fs21.existsSync)(worktreePath)) {
10605
+ if (!(0, import_node_fs22.existsSync)(worktreePath)) {
9812
10606
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
9813
10607
  }
9814
10608
  let sessionId = null;
@@ -9871,7 +10665,7 @@ function importConductorWorkspace(workspaceId) {
9871
10665
  db.close();
9872
10666
  }
9873
10667
  } finally {
9874
- (0, import_node_fs21.rmSync)(tmp, { recursive: true, force: true });
10668
+ (0, import_node_fs22.rmSync)(tmp, { recursive: true, force: true });
9875
10669
  }
9876
10670
  }
9877
10671
  async function importConductorWorkspaceAsync(workspaceId) {
@@ -9880,13 +10674,14 @@ async function importConductorWorkspaceAsync(workspaceId) {
9880
10674
 
9881
10675
  // src/orchestrator/orchestrator.ts
9882
10676
  var import_node_events = require("events");
9883
- var import_node_fs26 = require("fs");
10677
+ var import_node_fs28 = require("fs");
9884
10678
  init_error_detail();
9885
10679
  init_agents();
9886
10680
  init_worktree();
10681
+ init_stack();
9887
10682
 
9888
10683
  // src/git/orphan-cleanup.ts
9889
- var import_node_fs22 = require("fs");
10684
+ var import_node_fs23 = require("fs");
9890
10685
  var import_node_path21 = require("path");
9891
10686
  init_worktree();
9892
10687
  init_thread_store();
@@ -9902,9 +10697,9 @@ async function findOrphanWorktrees(repoPaths) {
9902
10697
  repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
9903
10698
  );
9904
10699
  const homeRoot = sideboardWorkspacesDir();
9905
- if ((0, import_node_fs22.existsSync)(homeRoot)) {
10700
+ if ((0, import_node_fs23.existsSync)(homeRoot)) {
9906
10701
  try {
9907
- for (const entry of (0, import_node_fs22.readdirSync)(homeRoot, { withFileTypes: true })) {
10702
+ for (const entry of (0, import_node_fs23.readdirSync)(homeRoot, { withFileTypes: true })) {
9908
10703
  if (!entry.isDirectory()) continue;
9909
10704
  void entry;
9910
10705
  }
@@ -9914,7 +10709,7 @@ async function findOrphanWorktrees(repoPaths) {
9914
10709
  const orphans = [];
9915
10710
  const seen = /* @__PURE__ */ new Set();
9916
10711
  for (const repoPath of repos) {
9917
- if (!repoPath || !(0, import_node_fs22.existsSync)(repoPath)) continue;
10712
+ if (!repoPath || !(0, import_node_fs23.existsSync)(repoPath)) continue;
9918
10713
  try {
9919
10714
  const wts = await listWorktrees(repoPath);
9920
10715
  for (const wt of wts) {
@@ -9925,7 +10720,7 @@ async function findOrphanWorktrees(repoPaths) {
9925
10720
  seen.add(path);
9926
10721
  let mtimeMs = 0;
9927
10722
  try {
9928
- mtimeMs = (0, import_node_fs22.statSync)(path).mtimeMs;
10723
+ mtimeMs = (0, import_node_fs23.statSync)(path).mtimeMs;
9929
10724
  } catch {
9930
10725
  mtimeMs = 0;
9931
10726
  }
@@ -9935,16 +10730,16 @@ async function findOrphanWorktrees(repoPaths) {
9935
10730
  }
9936
10731
  try {
9937
10732
  const root = worktreesRoot(repoPath);
9938
- if ((0, import_node_fs22.existsSync)(root)) {
9939
- for (const entry of (0, import_node_fs22.readdirSync)(root, { withFileTypes: true })) {
10733
+ if ((0, import_node_fs23.existsSync)(root)) {
10734
+ for (const entry of (0, import_node_fs23.readdirSync)(root, { withFileTypes: true })) {
9940
10735
  if (!entry.isDirectory()) continue;
9941
10736
  const path = (0, import_node_path21.join)(root, entry.name).replace(/\/$/, "");
9942
10737
  if (known.has(path) || seen.has(path)) continue;
9943
- if (!(0, import_node_fs22.existsSync)((0, import_node_path21.join)(path, ".git"))) continue;
10738
+ if (!(0, import_node_fs23.existsSync)((0, import_node_path21.join)(path, ".git"))) continue;
9944
10739
  seen.add(path);
9945
10740
  let mtimeMs = 0;
9946
10741
  try {
9947
- mtimeMs = (0, import_node_fs22.statSync)(path).mtimeMs;
10742
+ mtimeMs = (0, import_node_fs23.statSync)(path).mtimeMs;
9948
10743
  } catch {
9949
10744
  mtimeMs = Date.now();
9950
10745
  }
@@ -10090,7 +10885,7 @@ async function applyThreadIntoMain(thread, opts) {
10090
10885
  }
10091
10886
 
10092
10887
  // src/git/clone-repo.ts
10093
- var import_node_fs23 = require("fs");
10888
+ var import_node_fs24 = require("fs");
10094
10889
  var import_node_path22 = require("path");
10095
10890
  var import_execa6 = require("execa");
10096
10891
  init_paths();
@@ -10106,7 +10901,7 @@ async function cloneRepoIntoSideboard(opts) {
10106
10901
  }
10107
10902
  name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
10108
10903
  const dest = (0, import_node_path22.join)(sideboardReposDir(), name);
10109
- if ((0, import_node_fs23.existsSync)(dest)) {
10904
+ if ((0, import_node_fs24.existsSync)(dest)) {
10110
10905
  const repoPath2 = await resolveRepoRoot(dest);
10111
10906
  const workspace2 = await ensureWorkspace(repoPath2);
10112
10907
  return { repoPath: repoPath2, workspace: workspace2 };
@@ -10128,7 +10923,7 @@ init_orchestrator_capable();
10128
10923
 
10129
10924
  // src/review/request-review.ts
10130
10925
  var import_node_crypto5 = require("crypto");
10131
- var import_node_fs24 = require("fs");
10926
+ var import_node_fs25 = require("fs");
10132
10927
  var import_node_path23 = require("path");
10133
10928
  init_global_workspace();
10134
10929
  init_thread_store();
@@ -10253,15 +11048,13 @@ File: src/client/frontends/desktop/core/UserData.ts
10253
11048
  `;
10254
11049
 
10255
11050
  // src/review/request-review.ts
11051
+ init_workspace_scratch();
10256
11052
  var REPO_REVIEW_PATH = ".sideboard/review.md";
10257
11053
  var REPO_REVIEW_NAME = "review.md";
10258
- var REVIEW_REQUEST_PATH = ".sideboard/attachments/Review request.md";
11054
+ var REVIEW_REQUEST_PATH = `${ATTACHMENTS_DIR}/Review request.md`;
11055
+ var LEGACY_REVIEW_REQUEST_PATH = `${LEGACY_ATTACHMENTS_DIR}/Review request.md`;
10259
11056
  var REVIEW_REQUEST_NAME = "Review request.md";
10260
11057
  var REVIEW_REQUEST_PREFILL = "Review.";
10261
- var ATTACHMENTS_GITIGNORE2 = `# Sideboard review / composer attachments (local only)
10262
- *
10263
- !.gitignore
10264
- `;
10265
11058
  var LEGACY_REVIEW_TEMPLATE_MARKERS = [
10266
11059
  "You are acting as a reviewer for a proposed code change made by another engineer.",
10267
11060
  "HOW MANY FINDINGS TO RETURN:"
@@ -10275,19 +11068,19 @@ function shouldRefreshReviewRequestTemplate(content) {
10275
11068
  return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
10276
11069
  }
10277
11070
  function readTextIfPresent(abs) {
10278
- if (!(0, import_node_fs24.existsSync)(abs)) return null;
11071
+ if (!(0, import_node_fs25.existsSync)(abs)) return null;
10279
11072
  try {
10280
- const content = (0, import_node_fs24.readFileSync)(abs, "utf8");
11073
+ const content = (0, import_node_fs25.readFileSync)(abs, "utf8");
10281
11074
  return content.trim() ? content : null;
10282
11075
  } catch {
10283
11076
  return null;
10284
11077
  }
10285
11078
  }
10286
11079
  function ensureAttachmentsGitignore(worktreePath) {
10287
- const gitignoreAbs = (0, import_node_path23.join)(worktreePath, ".sideboard", "attachments", ".gitignore");
10288
- if ((0, import_node_fs24.existsSync)(gitignoreAbs)) return;
10289
- (0, import_node_fs24.mkdirSync)((0, import_node_path23.dirname)(gitignoreAbs), { recursive: true });
10290
- (0, import_node_fs24.writeFileSync)(gitignoreAbs, ATTACHMENTS_GITIGNORE2, "utf8");
11080
+ const gitignoreAbs = (0, import_node_path23.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
11081
+ if ((0, import_node_fs25.existsSync)(gitignoreAbs)) return;
11082
+ (0, import_node_fs25.mkdirSync)((0, import_node_path23.dirname)(gitignoreAbs), { recursive: true });
11083
+ (0, import_node_fs25.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
10291
11084
  }
10292
11085
  function resolveReviewGuidelines(worktreePath) {
10293
11086
  const repoAbs = (0, import_node_path23.join)(worktreePath, REPO_REVIEW_PATH);
@@ -10310,9 +11103,19 @@ function resolveReviewGuidelines(worktreePath) {
10310
11103
  source: "local"
10311
11104
  };
10312
11105
  }
11106
+ const legacyAbs = (0, import_node_path23.join)(worktreePath, LEGACY_REVIEW_REQUEST_PATH);
11107
+ const legacyContent = readTextIfPresent(legacyAbs);
11108
+ if (legacyContent && !shouldRefreshReviewRequestTemplate(legacyContent)) {
11109
+ return {
11110
+ path: LEGACY_REVIEW_REQUEST_PATH,
11111
+ name: REVIEW_REQUEST_NAME,
11112
+ content: legacyContent,
11113
+ source: "local"
11114
+ };
11115
+ }
10313
11116
  ensureAttachmentsGitignore(worktreePath);
10314
- (0, import_node_fs24.mkdirSync)((0, import_node_path23.dirname)(localAbs), { recursive: true });
10315
- (0, import_node_fs24.writeFileSync)(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
11117
+ (0, import_node_fs25.mkdirSync)((0, import_node_path23.dirname)(localAbs), { recursive: true });
11118
+ (0, import_node_fs25.writeFileSync)(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
10316
11119
  return {
10317
11120
  path: REVIEW_REQUEST_PATH,
10318
11121
  name: REVIEW_REQUEST_NAME,
@@ -10332,17 +11135,18 @@ function ensureReviewRequestFile(worktreePath) {
10332
11135
  };
10333
11136
  }
10334
11137
  const localAbs = (0, import_node_path23.join)(worktreePath, REVIEW_REQUEST_PATH);
10335
- const localContent = readTextIfPresent(localAbs);
11138
+ const localContent = readTextIfPresent(localAbs) ?? readTextIfPresent((0, import_node_path23.join)(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
10336
11139
  if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
11140
+ const path = (0, import_node_fs25.existsSync)(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
10337
11141
  return {
10338
- path: REVIEW_REQUEST_PATH,
11142
+ path,
10339
11143
  name: REVIEW_REQUEST_NAME,
10340
11144
  content: localContent,
10341
11145
  source: "local"
10342
11146
  };
10343
11147
  }
10344
- (0, import_node_fs24.mkdirSync)((0, import_node_path23.dirname)(repoAbs), { recursive: true });
10345
- (0, import_node_fs24.writeFileSync)(repoAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
11148
+ (0, import_node_fs25.mkdirSync)((0, import_node_path23.dirname)(repoAbs), { recursive: true });
11149
+ (0, import_node_fs25.writeFileSync)(repoAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
10346
11150
  return {
10347
11151
  path: REPO_REVIEW_PATH,
10348
11152
  name: REPO_REVIEW_NAME,
@@ -10362,7 +11166,7 @@ function buildReviewRequestAttachment(content, opts) {
10362
11166
  };
10363
11167
  }
10364
11168
  function readExistingReviewRequestFile(worktreePath) {
10365
- return readTextIfPresent((0, import_node_path23.join)(worktreePath, REPO_REVIEW_PATH)) ?? readTextIfPresent((0, import_node_path23.join)(worktreePath, REVIEW_REQUEST_PATH));
11169
+ return readTextIfPresent((0, import_node_path23.join)(worktreePath, REPO_REVIEW_PATH)) ?? readTextIfPresent((0, import_node_path23.join)(worktreePath, REVIEW_REQUEST_PATH)) ?? readTextIfPresent((0, import_node_path23.join)(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
10366
11170
  }
10367
11171
  async function requestReview(threadRef, send) {
10368
11172
  const from = findThreadByRef(threadRef);
@@ -10501,6 +11305,7 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
10501
11305
 
10502
11306
  // src/orchestrator/orchestrator.ts
10503
11307
  init_types();
11308
+ init_plan_file();
10504
11309
  init_settings();
10505
11310
 
10506
11311
  // src/threads/sync-branch.ts
@@ -10620,7 +11425,7 @@ var Orchestrator = class {
10620
11425
  }
10621
11426
  continue;
10622
11427
  }
10623
- if (!(0, import_node_fs26.existsSync)(thread.worktreePath)) {
11428
+ if (!(0, import_node_fs28.existsSync)(thread.worktreePath)) {
10624
11429
  setStatus(thread.id, "broken", "Worktree missing on disk");
10625
11430
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
10626
11431
  continue;
@@ -10765,14 +11570,9 @@ var Orchestrator = class {
10765
11570
  return findThreadByRef(idOrRef) ?? readThread(idOrRef);
10766
11571
  }
10767
11572
  async createThread(input) {
10768
- let thread = await createThread(input, (line) => {
10769
- this.emit({
10770
- type: "turn_output",
10771
- threadId: "pending",
10772
- event: { type: "stdout", data: line }
10773
- });
10774
- });
11573
+ let thread = await createThread(input);
10775
11574
  this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
11575
+ await this.runSetupAfterCreate(thread.id);
10776
11576
  const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
10777
11577
  if (autoRunAfterSetupEnabled2()) {
10778
11578
  try {
@@ -10786,6 +11586,19 @@ var Orchestrator = class {
10786
11586
  }
10787
11587
  return thread;
10788
11588
  }
11589
+ /** Run workspace setup after a new worktree is created (no-op if none configured). */
11590
+ async runSetupAfterCreate(threadId) {
11591
+ try {
11592
+ await this.runSetup(threadId);
11593
+ } catch (err) {
11594
+ const message = err instanceof Error ? err.message : String(err);
11595
+ if (/no setup script/i.test(message)) return;
11596
+ if (/already running/i.test(message)) return;
11597
+ updateThread(threadId, {
11598
+ lastError: `Setup failed: ${message}`
11599
+ });
11600
+ }
11601
+ }
10789
11602
  listWorkspaces() {
10790
11603
  const fromThreads = listThreads({ includeArchived: false }).map((t) => t.repoPath);
10791
11604
  return syncWorkspacesFromThreads(fromThreads);
@@ -11134,6 +11947,19 @@ var Orchestrator = class {
11134
11947
  });
11135
11948
  }
11136
11949
  const afterTurn = this.requireThread(threadId);
11950
+ if (afterTurn.planMode && afterTurn.worktreePath?.trim()) {
11951
+ const presented = extractPresentedPlan(parts);
11952
+ const exited = parts.some(
11953
+ (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
11954
+ );
11955
+ if (presented?.content) {
11956
+ writePlanFile(afterTurn.worktreePath, presented.content);
11957
+ } else if (exited || chatText && chatText.trim().length >= 400) {
11958
+ if (!readPlanFile(afterTurn.worktreePath) && chatText?.trim()) {
11959
+ writePlanFile(afterTurn.worktreePath, chatText.trim());
11960
+ }
11961
+ }
11962
+ }
11137
11963
  if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
11138
11964
  (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
11139
11965
  )) {
@@ -11615,6 +12441,82 @@ var Orchestrator = class {
11615
12441
  }
11616
12442
  return meta;
11617
12443
  }
12444
+ async getPrStack(threadRef) {
12445
+ const thread = this.requireThread(threadRef);
12446
+ if (!thread.worktreePath?.trim()) return null;
12447
+ const stack = await getPrStack(thread.worktreePath);
12448
+ if (!stack) return null;
12449
+ const current = stack.currentIndex >= 0 ? stack.layers[stack.currentIndex] : null;
12450
+ const patch = {};
12451
+ if (stack.stackNumber != null) {
12452
+ const id = `gh-stack-${stack.stackNumber}`;
12453
+ if (thread.stackId !== id) patch.stackId = id;
12454
+ }
12455
+ if (current?.position != null && thread.stackLayer !== current.position) {
12456
+ patch.stackLayer = current.position;
12457
+ }
12458
+ if (current?.prUrl && current.prUrl !== thread.prUrl) patch.prUrl = current.prUrl;
12459
+ if (current?.title && current.title !== thread.prTitle) patch.prTitle = current.title;
12460
+ if (current?.branchName && current.branchName !== thread.branchName) {
12461
+ patch.branchName = current.branchName;
12462
+ }
12463
+ if (Object.keys(patch).length > 0) updateThread(thread.id, patch);
12464
+ return stack;
12465
+ }
12466
+ /** Open worktrees for all (or one) stack layers discovered from a thread. */
12467
+ async openPrStackLayers(threadRef, opts) {
12468
+ const result = await openPrStackLayers({ threadRef, layer: opts?.layer });
12469
+ for (const t of result.threads) {
12470
+ this.emit({ type: "status_changed", threadId: t.id, status: t.status });
12471
+ }
12472
+ for (const id of result.createdThreadIds) {
12473
+ await this.runSetupAfterCreate(id);
12474
+ }
12475
+ return { stack: result.stack, threads: result.threads };
12476
+ }
12477
+ /** Add a branch on top of the thread's stack and open its worktree. */
12478
+ async addStackLayer(threadRef, branchName, opts) {
12479
+ const result = await addStackLayerFromThread({
12480
+ threadRef,
12481
+ branchName,
12482
+ title: opts?.title
12483
+ });
12484
+ this.emit({
12485
+ type: "status_changed",
12486
+ threadId: result.thread.id,
12487
+ status: result.thread.status
12488
+ });
12489
+ if (result.createdWorktree) {
12490
+ await this.runSetupAfterCreate(result.thread.id);
12491
+ }
12492
+ return { stack: result.stack, thread: result.thread };
12493
+ }
12494
+ /** Initialize a stack from the current thread branch (optional extra layers). */
12495
+ async initStackFromThread(threadRef, opts) {
12496
+ const result = await initStackFromThread({
12497
+ threadRef,
12498
+ additionalBranches: opts?.additionalBranches,
12499
+ base: opts?.base
12500
+ });
12501
+ for (const t of result.threads) {
12502
+ this.emit({ type: "status_changed", threadId: t.id, status: t.status });
12503
+ }
12504
+ for (const id of result.createdThreadIds) {
12505
+ await this.runSetupAfterCreate(id);
12506
+ }
12507
+ return { stack: result.stack, threads: result.threads };
12508
+ }
12509
+ /** Create a new multi-layer stack with one worktree per layer. */
12510
+ async createPrStack(input) {
12511
+ const result = await createPrStack(input);
12512
+ for (const t of result.threads) {
12513
+ this.emit({ type: "status_changed", threadId: t.id, status: t.status });
12514
+ }
12515
+ for (const id of result.createdThreadIds) {
12516
+ await this.runSetupAfterCreate(id);
12517
+ }
12518
+ return { stack: result.stack, threads: result.threads };
12519
+ }
11618
12520
  async getPrDetails(threadRef) {
11619
12521
  const { thread, selector, cwd } = await this.withPrSelector(threadRef);
11620
12522
  if (!selector) return null;
@@ -11677,14 +12579,9 @@ var Orchestrator = class {
11677
12579
  return forkChatTab(input);
11678
12580
  }
11679
12581
  async forkThreadWorktree(input) {
11680
- const thread = await forkThreadWorktree(input, (line) => {
11681
- this.emit({
11682
- type: "turn_output",
11683
- threadId: "pending",
11684
- event: { type: "stdout", data: line }
11685
- });
11686
- });
12582
+ const thread = await forkThreadWorktree(input);
11687
12583
  this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
12584
+ await this.runSetupAfterCreate(thread.id);
11688
12585
  return thread;
11689
12586
  }
11690
12587
  renameThread(threadRef, title) {
@@ -11698,7 +12595,7 @@ var Orchestrator = class {
11698
12595
  }
11699
12596
  /**
11700
12597
  * Stage OS / worktree files into composer attachments (copies external files
11701
- * into `.sideboard/attachments/` so agents can Read images and binaries).
12598
+ * into `.context/attachments/` so agents can Read images and binaries).
11702
12599
  */
11703
12600
  attachComposerFiles(threadRef, opts) {
11704
12601
  const thread = this.requireThread(threadRef);
@@ -11772,7 +12669,7 @@ var Orchestrator = class {
11772
12669
  updateThread(thread.id, { worktreePath: globalAgentCwd2() });
11773
12670
  return setStatus(thread.id, "idle");
11774
12671
  }
11775
- if (!(0, import_node_fs26.existsSync)(thread.worktreePath)) {
12672
+ if (!(0, import_node_fs28.existsSync)(thread.worktreePath)) {
11776
12673
  const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
11777
12674
  const { execa: execa7 } = await import("execa");
11778
12675
  const slug = thread.worktreePath.split("/").pop();
@@ -11880,13 +12777,116 @@ async function startOrchestration(opts) {
11880
12777
  }
11881
12778
 
11882
12779
  // src/index.ts
12780
+ init_workspace_scratch();
12781
+
12782
+ // src/plan/ask-user.ts
12783
+ function asRecord3(v) {
12784
+ return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
12785
+ }
12786
+ function parseOptions(raw) {
12787
+ if (!Array.isArray(raw)) return [];
12788
+ const out = [];
12789
+ for (const item of raw) {
12790
+ if (typeof item === "string" && item.trim()) {
12791
+ out.push({ label: item.trim() });
12792
+ continue;
12793
+ }
12794
+ const o = asRecord3(item);
12795
+ if (!o) continue;
12796
+ const label = typeof o.label === "string" ? o.label.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.title === "string" ? o.title.trim() : "";
12797
+ if (!label) continue;
12798
+ const description = typeof o.description === "string" && o.description.trim() ? o.description.trim() : void 0;
12799
+ out.push({ label, description });
12800
+ }
12801
+ return out;
12802
+ }
12803
+ function parseOneQuestion(raw) {
12804
+ const o = asRecord3(raw);
12805
+ if (!o) return null;
12806
+ const question = typeof o.question === "string" ? o.question.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.prompt === "string" ? o.prompt.trim() : "";
12807
+ if (!question) return null;
12808
+ const options = parseOptions(o.options);
12809
+ if (options.length < 1) return null;
12810
+ const header = typeof o.header === "string" && o.header.trim() ? o.header.trim() : void 0;
12811
+ const multiSelect = Boolean(o.multiSelect ?? o.multi_select);
12812
+ return { question, header, multiSelect, options };
12813
+ }
12814
+ function parsePlanQuestionsInput(input) {
12815
+ const root = asRecord3(input);
12816
+ if (!root) return [];
12817
+ const list = Array.isArray(root.questions) ? root.questions : Array.isArray(root.question) ? root.question : root.question || root.prompt ? [root] : [];
12818
+ const out = [];
12819
+ for (const item of list) {
12820
+ const q = parseOneQuestion(item);
12821
+ if (q) out.push(q);
12822
+ }
12823
+ return out;
12824
+ }
12825
+ var ASK_USER_TOOL_RE = /^(AskUserQuestion|ask_user|mcp__sideboard__ask_user)$/i;
12826
+ function isAskUserToolName(name) {
12827
+ if (!name) return false;
12828
+ const base = name.replace(/^mcp__sideboard__/i, "");
12829
+ return ASK_USER_TOOL_RE.test(name) || /^ask_user$/i.test(base);
12830
+ }
12831
+ function extractPendingPlanQuestions(parts) {
12832
+ if (!parts?.length) return null;
12833
+ for (let i = parts.length - 1; i >= 0; i--) {
12834
+ const p = parts[i];
12835
+ if (p.type !== "tool" || !isAskUserToolName(p.name)) continue;
12836
+ const questions = parsePlanQuestionsInput(p.input);
12837
+ if (!questions.length) continue;
12838
+ return {
12839
+ id: p.id || `ask-${i}`,
12840
+ questions,
12841
+ source: p.name || "ask_user"
12842
+ };
12843
+ }
12844
+ return null;
12845
+ }
12846
+ function formatPlanQuestionAnswers(questions, answers) {
12847
+ const lines = ["Answers to your questions:"];
12848
+ for (let i = 0; i < questions.length; i++) {
12849
+ const q = questions[i];
12850
+ const a = answers.find((x) => x.questionIndex === i);
12851
+ const header = q.header ? `**${q.header}** \u2014 ` : "";
12852
+ const parts = [];
12853
+ if (a?.selected.length) parts.push(a.selected.join(", "));
12854
+ if (a?.other?.trim()) parts.push(a.other.trim());
12855
+ const body = parts.length ? parts.join(" \xB7 ") : "(no answer)";
12856
+ lines.push(`${i + 1}. ${header}${q.question}`);
12857
+ lines.push(` \u2192 ${body}`);
12858
+ }
12859
+ return lines.join("\n");
12860
+ }
12861
+ function formatPlanQuestionsForChat(questions) {
12862
+ const lines = ["### Questions for you", ""];
12863
+ for (let i = 0; i < questions.length; i++) {
12864
+ const q = questions[i];
12865
+ const header = q.header ? `**${q.header}** \u2014 ` : "";
12866
+ lines.push(`${i + 1}. ${header}${q.question}`);
12867
+ for (let oi = 0; oi < q.options.length; oi++) {
12868
+ const opt = q.options[oi];
12869
+ const desc = opt.description?.trim();
12870
+ lines.push(
12871
+ desc ? ` - **${opt.label}** \u2014 ${desc}` : ` - **${opt.label}**`
12872
+ );
12873
+ }
12874
+ lines.push("");
12875
+ }
12876
+ lines.push("_Answer in the composer below._");
12877
+ return lines.join("\n").trimEnd();
12878
+ }
12879
+
12880
+ // src/index.ts
12881
+ init_plan_present();
12882
+ init_plan_file();
11883
12883
  init_coordinator_prompt();
11884
12884
 
11885
12885
  // src/mcp/server.ts
11886
12886
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
11887
12887
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
11888
12888
  var import_zod = require("zod");
11889
- var import_node_path25 = require("path");
12889
+ var import_node_path26 = require("path");
11890
12890
  init_worktree();
11891
12891
  init_global_workspace();
11892
12892
  init_list_models();
@@ -11935,7 +12935,7 @@ async function startMcpServer() {
11935
12935
  async () => {
11936
12936
  const threads = orch.getThreads(true);
11937
12937
  const lines = threads.map((t) => {
11938
- const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path25.basename)(t.repoPath) || t.repoPath;
12938
+ const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path26.basename)(t.repoPath) || t.repoPath;
11939
12939
  return `${t.id.slice(0, 8)} ${t.status.padEnd(9)} ${t.agent.padEnd(8)} ${repo} ${t.sourceType}:${t.sourceRef} ${t.title} sideboard://thread/${t.id}${t.devPort ? ` http://localhost:${t.devPort}` : ""}`;
11940
12940
  });
11941
12941
  return {
@@ -11997,6 +12997,59 @@ async function startMcpServer() {
11997
12997
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
11998
12998
  }
11999
12999
  );
13000
+ server.tool(
13001
+ "ask_user",
13002
+ "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.",
13003
+ {
13004
+ questions: import_zod.z.array(
13005
+ import_zod.z.object({
13006
+ question: import_zod.z.string().describe("Full question text ending with ?"),
13007
+ header: import_zod.z.string().max(24).optional().describe("Short label shown above the question"),
13008
+ multiSelect: import_zod.z.boolean().optional().describe("Allow selecting multiple options"),
13009
+ options: import_zod.z.array(
13010
+ import_zod.z.object({
13011
+ label: import_zod.z.string(),
13012
+ description: import_zod.z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
13013
+ })
13014
+ ).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
13015
+ })
13016
+ ).min(1).max(4).describe("1\u20134 questions")
13017
+ },
13018
+ async ({ questions }) => {
13019
+ const payload = {
13020
+ ok: true,
13021
+ questions,
13022
+ message: "Questions shown in Sideboard\u2019s composer. Wait for the user\u2019s next message with their answers before continuing."
13023
+ };
13024
+ return { content: [{ type: "text", text: JSON.stringify(payload) }] };
13025
+ }
13026
+ );
13027
+ server.tool(
13028
+ "present_plan",
13029
+ "Save the implementation plan as markdown to .context/attachments/plan.md and show it in Sideboard chat for user approval (Copy / Hand off / Approve). Call this when the plan is ready \u2014 required in plan mode. Pass the full plan body in content. Then Claude should call ExitPlanMode.",
13030
+ {
13031
+ title: import_zod.z.string().optional().describe("Short plan title (defaults to Plan)"),
13032
+ content: import_zod.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
13033
+ thread_id: import_zod.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
13034
+ },
13035
+ async ({ title, content, thread_id }) => {
13036
+ const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
13037
+ let root = process.cwd();
13038
+ if (thread_id?.trim()) {
13039
+ const t = orch.getThread(thread_id.trim());
13040
+ if (t?.worktreePath?.trim()) root = t.worktreePath;
13041
+ }
13042
+ const path = writePlanFile2(root, content);
13043
+ const payload = {
13044
+ ok: true,
13045
+ path,
13046
+ title: title?.trim() || "Plan",
13047
+ content,
13048
+ message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
13049
+ };
13050
+ return { content: [{ type: "text", text: JSON.stringify(payload) }] };
13051
+ }
13052
+ );
12000
13053
  server.tool(
12001
13054
  "present_schema",
12002
13055
  "Open Sideboard\u2019s schema-driven CMS side column (filterable table and/or form). Pass JSON Schema + schemaUi (Brightsy extensions supported). Use datasource=brightsy with resource_id (record type UUID) when logged into Brightsy; use datasource=inline with embedded resource/records for any other source.",
@@ -12575,6 +13628,126 @@ async function startMcpServer() {
12575
13628
  };
12576
13629
  }
12577
13630
  );
13631
+ server.tool(
13632
+ "get_pr_stack",
13633
+ "Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before mergePr on stacked PRs.",
13634
+ { ref: import_zod.z.string() },
13635
+ async ({ ref }) => {
13636
+ const stack = await orch.getPrStack(ref);
13637
+ return {
13638
+ content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
13639
+ };
13640
+ }
13641
+ );
13642
+ server.tool(
13643
+ "open_pr_stack_layers",
13644
+ "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
13645
+ {
13646
+ ref: import_zod.z.string(),
13647
+ layer: import_zod.z.number().int().positive().optional()
13648
+ },
13649
+ async ({ ref, layer }) => {
13650
+ const result = await orch.openPrStackLayers(ref, { layer });
13651
+ return {
13652
+ content: [
13653
+ {
13654
+ type: "text",
13655
+ text: JSON.stringify(
13656
+ {
13657
+ stackNumber: result.stack.stackNumber,
13658
+ trunk: result.stack.trunk,
13659
+ threads: result.threads.map((t) => ({
13660
+ id: t.id,
13661
+ title: t.title,
13662
+ branchName: t.branchName,
13663
+ stackLayer: t.stackLayer,
13664
+ worktreePath: t.worktreePath,
13665
+ prUrl: t.prUrl,
13666
+ link: `sideboard://thread/${t.id}`
13667
+ }))
13668
+ },
13669
+ null,
13670
+ 2
13671
+ )
13672
+ }
13673
+ ]
13674
+ };
13675
+ }
13676
+ );
13677
+ server.tool(
13678
+ "add_stack_layer",
13679
+ "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
13680
+ {
13681
+ ref: import_zod.z.string(),
13682
+ branchName: import_zod.z.string(),
13683
+ title: import_zod.z.string().optional()
13684
+ },
13685
+ async ({ ref, branchName, title }) => {
13686
+ const result = await orch.addStackLayer(ref, branchName, { title });
13687
+ return {
13688
+ content: [
13689
+ {
13690
+ type: "text",
13691
+ text: JSON.stringify(
13692
+ {
13693
+ id: result.thread.id,
13694
+ title: result.thread.title,
13695
+ branchName: result.thread.branchName,
13696
+ stackLayer: result.thread.stackLayer,
13697
+ worktreePath: result.thread.worktreePath,
13698
+ link: `sideboard://thread/${result.thread.id}`
13699
+ },
13700
+ null,
13701
+ 2
13702
+ )
13703
+ }
13704
+ ]
13705
+ };
13706
+ }
13707
+ );
13708
+ server.tool(
13709
+ "create_pr_stack",
13710
+ "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
13711
+ {
13712
+ repoPath: import_zod.z.string(),
13713
+ branches: import_zod.z.array(import_zod.z.string()).min(1),
13714
+ agent: import_zod.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
13715
+ base: import_zod.z.string().optional(),
13716
+ title: import_zod.z.string().optional()
13717
+ },
13718
+ async (args) => {
13719
+ const result = await orch.createPrStack({
13720
+ repoPath: args.repoPath,
13721
+ branches: args.branches,
13722
+ agent: args.agent,
13723
+ base: args.base,
13724
+ title: args.title
13725
+ });
13726
+ return {
13727
+ content: [
13728
+ {
13729
+ type: "text",
13730
+ text: JSON.stringify(
13731
+ {
13732
+ stackNumber: result.stack.stackNumber,
13733
+ trunk: result.stack.trunk,
13734
+ threads: result.threads.map((t) => ({
13735
+ id: t.id,
13736
+ title: t.title,
13737
+ branchName: t.branchName,
13738
+ stackLayer: t.stackLayer,
13739
+ worktreePath: t.worktreePath,
13740
+ link: `sideboard://thread/${t.id}`
13741
+ }))
13742
+ },
13743
+ null,
13744
+ 2
13745
+ )
13746
+ }
13747
+ ]
13748
+ };
13749
+ }
13750
+ );
12578
13751
  server.tool(
12579
13752
  "list_issues",
12580
13753
  "List issues from Sideboard Account connections (Linear API or GitHub Issues; Linear\u2192GitHub fallback when Linear is not connected). Pass repoPath from list_workspaces \u2014 GitHub Issues are scoped to that repo. Then create_thread with sourceType=ticket.",
@@ -12944,6 +14117,7 @@ init_connected_teams();
12944
14117
  init_injected_mcp();
12945
14118
  // Annotate the CommonJS export names for ESM import in node:
12946
14119
  0 && (module.exports = {
14120
+ ATTACHMENTS_DIR,
12947
14121
  BRIGHTSY_MCP_ALLOWED_TOOLS,
12948
14122
  BrightsySideboardApi,
12949
14123
  CLAUDE_MODEL_CATALOG,
@@ -12959,11 +14133,16 @@ init_injected_mcp();
12959
14133
  FAMOUS_SOCCER_TEAMS,
12960
14134
  GLOBAL_WORKSPACE_ID,
12961
14135
  HARNESS_ENV_KEYS,
14136
+ LEGACY_ATTACHMENTS_DIR,
14137
+ LEGACY_PLAN_FILE_REL,
14138
+ LEGACY_REVIEW_REQUEST_PATH,
12962
14139
  MAX_ANTHROPIC_CACHE_CONTROL_BLOCKS,
12963
14140
  ORCHESTRATOR_AGENT_KINDS,
12964
14141
  Orchestrator,
12965
14142
  PASTE_ATTACH_MIN_CHARS,
12966
14143
  PASTE_ATTACH_MIN_LINES,
14144
+ PLAN_FILE_NAME,
14145
+ PLAN_FILE_REL,
12967
14146
  PLAN_MODE_INSTRUCTION,
12968
14147
  REPO_REVIEW_NAME,
12969
14148
  REPO_REVIEW_PATH,
@@ -12973,6 +14152,8 @@ init_injected_mcp();
12973
14152
  SIDEBOARD_FORCE_STOP,
12974
14153
  SIDEBOARD_MCP_ALLOWED_TOOLS,
12975
14154
  THINKING_EFFORTS,
14155
+ addPrStackLayer,
14156
+ addStackLayerFromThread,
12976
14157
  addWorkspace,
12977
14158
  adoptThread,
12978
14159
  allAdapters,
@@ -12991,6 +14172,7 @@ init_injected_mcp();
12991
14172
  attachmentFromAbsolutePath,
12992
14173
  attachmentsFromBuffers,
12993
14174
  attachmentsFromWorktreePaths,
14175
+ attachmentsGitignoreBody,
12994
14176
  autoCleanupOrphansEnabled,
12995
14177
  autoRenameBranchEnabled,
12996
14178
  autoRunAfterSetupEnabled,
@@ -13013,6 +14195,7 @@ init_injected_mcp();
13013
14195
  caffeinateWhileRunningEnabled,
13014
14196
  captureLoginEnv,
13015
14197
  captureTurnBaseline,
14198
+ checkoutPrStackLayer,
13016
14199
  childEnvWithAppSettings,
13017
14200
  claudeAdapter,
13018
14201
  claudeChromeEnabled,
@@ -13032,8 +14215,10 @@ init_injected_mcp();
13032
14215
  countCacheControlBlocks,
13033
14216
  createChatTab,
13034
14217
  createEmptyThread,
14218
+ createExistingBranchWorktree,
13035
14219
  createGlobalChat,
13036
14220
  createOrUpdatePr,
14221
+ createPrStack,
13037
14222
  createThread,
13038
14223
  createThreadWorktree,
13039
14224
  currentBranch,
@@ -13043,6 +14228,7 @@ init_injected_mcp();
13043
14228
  deleteBranchOnPurgeEnabled,
13044
14229
  deleteThreadRecord,
13045
14230
  detectAgents,
14231
+ detectGhStack,
13046
14232
  detectLocalMergeConflicts,
13047
14233
  disconnectBrightsyTeam,
13048
14234
  discoverSkills,
@@ -13058,12 +14244,15 @@ init_injected_mcp();
13058
14244
  estimateThreadChars,
13059
14245
  expandComposerPrompt,
13060
14246
  extractGhErrorDetail,
14247
+ extractPendingPlanQuestions,
14248
+ extractPresentedPlan,
13061
14249
  extractiveSummary,
13062
14250
  fetchPrHead,
13063
14251
  finalizeParts,
13064
14252
  findInvalidCacheControlTtlOrder,
13065
14253
  findOrphanWorktrees,
13066
14254
  findThreadByRef,
14255
+ findThreadForStackLayer,
13067
14256
  flattenTurnInput,
13068
14257
  forkChatTab,
13069
14258
  forkMessageSlice,
@@ -13074,6 +14263,8 @@ init_injected_mcp();
13074
14263
  formatGhLandError,
13075
14264
  formatIpcInvokeError,
13076
14265
  formatMessagesAsTranscript,
14266
+ formatPlanQuestionAnswers,
14267
+ formatPlanQuestionsForChat,
13077
14268
  formatRateLimitResetHint,
13078
14269
  formatRenameBranchDirective,
13079
14270
  formatTranscriptMarkdown,
@@ -13097,6 +14288,7 @@ init_injected_mcp();
13097
14288
  getPrChecks,
13098
14289
  getPrDetails,
13099
14290
  getPrMeta,
14291
+ getPrStack,
13100
14292
  getRepoSetupInfo,
13101
14293
  getRunMode,
13102
14294
  getRunScript,
@@ -13113,9 +14305,12 @@ init_injected_mcp();
13113
14305
  healOrchestrationSoccerTitles,
13114
14306
  importConductorWorkspace,
13115
14307
  importConductorWorkspaceAsync,
14308
+ initPrStack,
14309
+ initStackFromThread,
13116
14310
  initializeGitRepository,
13117
14311
  inspectGitWorktree,
13118
14312
  installAgent,
14313
+ isAskUserToolName,
13119
14314
  isBrightsyConnected,
13120
14315
  isBrightsyNdjsonLine,
13121
14316
  isCloudCoordinatorThread,
@@ -13125,13 +14320,17 @@ init_injected_mcp();
13125
14320
  isGlobalRepoPath,
13126
14321
  isGlobalThread,
13127
14322
  isImageFilePath,
14323
+ isInPrStack,
13128
14324
  isLinearConnected,
13129
14325
  isOrchestratorCapableAgent,
13130
14326
  isOrchestratorThread,
13131
14327
  isPidAlive,
13132
14328
  isPlaceholderBranch,
14329
+ isPresentPlanToolName,
13133
14330
  isSessionQuotaLimit,
14331
+ isSideboardScratchPath,
13134
14332
  isThinkingEffort,
14333
+ isWorkspaceScratchPath,
13135
14334
  listAgentSetupInfo,
13136
14335
  listBranchCommits,
13137
14336
  listBranches,
@@ -13168,6 +14367,7 @@ init_injected_mcp();
13168
14367
  mcpAllowTools,
13169
14368
  mcpAuthWarnings,
13170
14369
  mergePr,
14370
+ mergePrStack,
13171
14371
  mergeUsage,
13172
14372
  nextPastedTextName,
13173
14373
  nextThinkingEffort,
@@ -13177,6 +14377,8 @@ init_injected_mcp();
13177
14377
  normalizeTurnInput,
13178
14378
  normalizeWorktreePath,
13179
14379
  openInSystemTerminal,
14380
+ openPrStackLayers,
14381
+ openStackLayer,
13180
14382
  opencodeAdapter,
13181
14383
  orchestrationQuotaFallbackAgent,
13182
14384
  orchestrationQuotaOnLimit,
@@ -13185,15 +14387,19 @@ init_injected_mcp();
13185
14387
  originGhRepoEnv,
13186
14388
  parseCursorRunnerLine,
13187
14389
  parseForceStopMessage,
14390
+ parseGhStackViewJson,
13188
14391
  parseGithubSlugFromRemoteUrl,
13189
14392
  parseMcpList,
14393
+ parsePlanQuestionsInput,
13190
14394
  parseSessionQuotaResetAt,
13191
14395
  partsToAssistantText,
13192
14396
  pastedTextStats,
13193
14397
  permissionMode,
14398
+ planFileAbs,
13194
14399
  previewLand,
13195
14400
  pushBranch,
13196
14401
  readExistingReviewRequestFile,
14402
+ readPlanFile,
13197
14403
  readSkillBody,
13198
14404
  readThread,
13199
14405
  readWorktreeFile,
@@ -13205,6 +14411,7 @@ init_injected_mcp();
13205
14411
  repoSlug,
13206
14412
  requestReview,
13207
14413
  requireAgent,
14414
+ resetGhStackDetectCache,
13208
14415
  resolveClaudeExecutable,
13209
14416
  resolveConductorCursorAgentId,
13210
14417
  resolveCursorModelId,
@@ -13212,7 +14419,9 @@ init_injected_mcp();
13212
14419
  resolveDiffBaseRef,
13213
14420
  resolveEffectiveIssueSource,
13214
14421
  resolveFilesToCopy,
14422
+ resolveGhAuthToken,
13215
14423
  resolveGithubRepoSlug,
14424
+ resolvePlanMarkdown,
13216
14425
  resolvePrSelector,
13217
14426
  resolveQuotaFallbackAgent,
13218
14427
  resolveRepoRoot,
@@ -13240,12 +14449,16 @@ init_injected_mcp();
13240
14449
  slugify,
13241
14450
  spawnAgentTurn,
13242
14451
  splitForCompaction,
14452
+ stackAgentDefaultsFrom,
14453
+ stackIdFrom,
14454
+ stackMergeReadiness,
13243
14455
  stageAbsolutePathsAsAttachments,
13244
14456
  stageBuffersAsAttachments,
13245
14457
  startDevServer,
13246
14458
  startMcpServer,
13247
14459
  startOrchestration,
13248
14460
  stripBrightsyNdjsonNoise,
14461
+ submitPrStack,
13249
14462
  suggestSlug,
13250
14463
  summarizeConversation,
13251
14464
  switchBrightsyAccount,
@@ -13281,6 +14494,7 @@ init_injected_mcp();
13281
14494
  worktreeNameFromPath,
13282
14495
  worktreesRoot,
13283
14496
  writeInjectedMcpConfig,
14497
+ writePlanFile,
13284
14498
  writeThread,
13285
14499
  writeWorktreeFile
13286
14500
  });