@sideboard-ai/core 0.1.51 → 0.1.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/{agents-HIEJL3UV.js → agents-ON6RKKND.js} +1 -1
  2. package/dist/{agents-5ROTZNCX.js → agents-YKSS6VBO.js} +1 -1
  3. package/dist/{chunk-7EUWSBWR.js → chunk-6QTZVJ7A.js} +5 -3
  4. package/dist/chunk-B3SJXYIJ.js +24 -0
  5. package/dist/{chunk-5ZPSH7VI.js → chunk-D3METLRW.js} +5 -3
  6. package/dist/{chunk-ZH5QZ4CR.js → chunk-DZFH2KLT.js} +280 -7
  7. package/dist/chunk-FKOIHGKV.js +21 -0
  8. package/dist/{chunk-E4VVEKAM.js → chunk-GNML24AW.js} +2 -2
  9. package/dist/{chunk-EOYDCKQC.js → chunk-HLEX5AQ6.js} +4 -0
  10. package/dist/{chunk-K3WMKGFY.js → chunk-LRLKJM3O.js} +2 -1
  11. package/dist/chunk-N5PM7HGQ.js +103 -0
  12. package/dist/chunk-QTUESPAW.js +101 -0
  13. package/dist/{chunk-O6W3P7V3.js → chunk-TSRXOSVD.js} +4 -0
  14. package/dist/{chunk-F4Q3IM6V.js → chunk-UEAHMGHW.js} +2 -1
  15. package/dist/{chunk-XRSAGVRW.js → chunk-XOU6HNQJ.js} +245 -7
  16. package/dist/{chunk-O5DOO7DP.js → chunk-XX5BB7NV.js} +3 -3
  17. package/dist/{chunk-QN7XNQAT.js → chunk-YDXQ72MD.js} +2 -2
  18. package/dist/{chunk-YFJ4FG2P.js → chunk-YOWIYAVA.js} +3 -3
  19. package/dist/{coordinator-prompt-7HHJRO7B.js → coordinator-prompt-6FXVTSFN.js} +4 -3
  20. package/dist/{coordinator-prompt-WD7FAMA2.js → coordinator-prompt-S6JZD5EF.js} +4 -3
  21. package/dist/{global-workspace-OJEPGDXA.js → global-workspace-EV4G2WMQ.js} +5 -4
  22. package/dist/{global-workspace-ECYN2MKL.js → global-workspace-MSX2K27Y.js} +5 -4
  23. package/dist/index.cjs +1288 -140
  24. package/dist/index.d.cts +388 -15
  25. package/dist/index.d.ts +388 -15
  26. package/dist/index.js +801 -84
  27. package/dist/mcp/run-stdio.cjs +1059 -132
  28. package/dist/mcp/run-stdio.js +628 -73
  29. package/dist/plan-file-6O7G4VPQ.js +23 -0
  30. package/dist/plan-file-PHVKUAEE.js +25 -0
  31. package/dist/{thread-store-XICUWFNM.js → thread-store-GHOADGL2.js} +1 -1
  32. package/dist/{thread-store-OV2X6PYO.js → thread-store-UJIGMI5J.js} +1 -1
  33. package/dist/{workspaces-MUU7RGVV.js → workspaces-3RQQZQRO.js} +6 -5
  34. package/dist/{workspaces-ZWOOFZUV.js → workspaces-AYTBR6KQ.js} +6 -5
  35. package/dist/{worktree-DVNDMWZ7.js → worktree-5KEQWSAF.js} +5 -2
  36. package/dist/{worktree-GDV56MX4.js → worktree-RWGL7FUV.js} +5 -2
  37. package/package.json +1 -1
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 ?? [],
@@ -2196,6 +2200,234 @@ var init_pr_gates = __esm({
2196
2200
  }
2197
2201
  });
2198
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
+
2199
2431
  // src/git/worktree.ts
2200
2432
  var worktree_exports = {};
2201
2433
  __export(worktree_exports, {
@@ -2205,6 +2437,7 @@ __export(worktree_exports, {
2205
2437
  branchDisplayLabel: () => branchDisplayLabel,
2206
2438
  collectTakenTeamSlugs: () => collectTakenTeamSlugs,
2207
2439
  commitAll: () => commitAll,
2440
+ createExistingBranchWorktree: () => createExistingBranchWorktree,
2208
2441
  createOrUpdatePr: () => createOrUpdatePr,
2209
2442
  createThreadWorktree: () => createThreadWorktree,
2210
2443
  currentBranch: () => currentBranch,
@@ -2918,6 +3151,52 @@ ${add.stdout}`;
2918
3151
  await ensureGhPreferOrigin(worktreePath);
2919
3152
  return { branchName, worktreePath };
2920
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
+ }
2921
3200
  async function removeWorktree(repoPath, worktreePath, opts) {
2922
3201
  await git(["worktree", "remove", "--force", worktreePath], repoPath, {
2923
3202
  reject: false
@@ -2957,8 +3236,7 @@ async function isDirty(worktreePath) {
2957
3236
  return false;
2958
3237
  }
2959
3238
  function isSideboardScratchPath(relativePath) {
2960
- const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
2961
- return p === ".sideboard/attachments" || p.startsWith(".sideboard/attachments/");
3239
+ return isWorkspaceScratchPath(relativePath);
2962
3240
  }
2963
3241
  function porcelainStatusPath(line) {
2964
3242
  const rest = line.length >= 3 ? line.slice(3) : "";
@@ -2986,7 +3264,7 @@ async function pushBranch(worktreePath, branchName) {
2986
3264
  }
2987
3265
  async function mergePr(cwd, selector, opts) {
2988
3266
  const slug = await resolveGithubRepoSlug(cwd);
2989
- const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft"];
3267
+ const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft,number"];
2990
3268
  if (slug) viewArgs.push("--repo", slug);
2991
3269
  const before = await gh(viewArgs, cwd, { reject: false });
2992
3270
  if (before.exitCode !== 0 || !before.stdout.trim()) {
@@ -2994,16 +3272,29 @@ async function mergePr(cwd, selector, opts) {
2994
3272
  }
2995
3273
  let url = "";
2996
3274
  let isDraft = false;
3275
+ let prNumber = null;
2997
3276
  try {
2998
3277
  const parsed = JSON.parse(before.stdout);
2999
3278
  url = String(parsed.url ?? "");
3000
3279
  isDraft = Boolean(parsed.isDraft);
3280
+ prNumber = typeof parsed.number === "number" && Number.isFinite(parsed.number) ? parsed.number : null;
3001
3281
  if (String(parsed.state ?? "").toUpperCase() === "MERGED") {
3002
3282
  return { url, state: "MERGED" };
3003
3283
  }
3004
3284
  } catch {
3005
3285
  throw new Error("Could not parse pull request details");
3006
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
+ }
3007
3298
  if (isDraft) {
3008
3299
  const readyArgs = ["pr", "ready", selector];
3009
3300
  if (slug) readyArgs.push("--repo", slug);
@@ -3025,10 +3316,10 @@ async function mergePr(cwd, selector, opts) {
3025
3316
  const after = await gh(viewArgs, cwd, { reject: false });
3026
3317
  if (after.exitCode === 0 && after.stdout.trim()) {
3027
3318
  try {
3028
- const parsed = JSON.parse(after.stdout);
3319
+ const parsed = after.stdout ? JSON.parse(after.stdout) : null;
3029
3320
  return {
3030
- url: String(parsed.url ?? url),
3031
- state: String(parsed.state ?? "MERGED")
3321
+ url: String(parsed?.url ?? url),
3322
+ state: String(parsed?.state ?? "MERGED")
3032
3323
  };
3033
3324
  } catch {
3034
3325
  }
@@ -3176,7 +3467,9 @@ var init_worktree = __esm({
3176
3467
  init_gh_errors();
3177
3468
  init_run();
3178
3469
  init_pr_gates();
3470
+ init_stack();
3179
3471
  init_teams();
3472
+ init_workspace_scratch();
3180
3473
  init_worktree_labels();
3181
3474
  }
3182
3475
  });
@@ -3302,6 +3595,7 @@ var init_coordinator_prompt = __esm({
3302
3595
  "Discover:",
3303
3596
  "- list_workspaces \u2014 registered repos (path + github slug when known)",
3304
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",
3305
3599
  "- list_models \u2014 only when you need a specific model (rare); otherwise leave model unset = Auto",
3306
3600
  "- list_threads / get_thread \u2014 fleet status (what is going on)",
3307
3601
  "Workspaces:",
@@ -4713,7 +5007,9 @@ var init_injected_mcp = __esm({
4713
5007
  SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS = [
4714
5008
  "mcp__sideboard__present_artifact",
4715
5009
  "mcp__sideboard__present_schema",
4716
- "mcp__sideboard__present_files"
5010
+ "mcp__sideboard__present_files",
5011
+ "mcp__sideboard__ask_user",
5012
+ "mcp__sideboard__present_plan"
4717
5013
  ];
4718
5014
  BRIGHTSY_MCP_ALLOWED_TOOLS = [
4719
5015
  "mcp__brightsy",
@@ -4756,7 +5052,7 @@ var PLAN_MODE_INSTRUCTION;
4756
5052
  var init_types = __esm({
4757
5053
  "src/agents/types.ts"() {
4758
5054
  "use strict";
4759
- 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.";
4760
5056
  }
4761
5057
  });
4762
5058
 
@@ -6345,6 +6641,116 @@ var init_agents = __esm({
6345
6641
  }
6346
6642
  });
6347
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
+
6348
6754
  // src/agents/cursor-recover.ts
6349
6755
  var cursor_recover_exports = {};
6350
6756
  __export(cursor_recover_exports, {
@@ -6353,10 +6759,10 @@ __export(cursor_recover_exports, {
6353
6759
  function recoverFinishedCursorRun(opts) {
6354
6760
  const agentId = opts.agentId.trim();
6355
6761
  if (!agentId) return null;
6356
- const runsPath = (0, import_node_path24.join)(appDataDir(), "cursor-sdk-store", "runs.ndjson");
6357
- 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;
6358
6764
  try {
6359
- const lines = (0, import_node_fs25.readFileSync)(runsPath, "utf8").split("\n");
6765
+ const lines = (0, import_node_fs27.readFileSync)(runsPath, "utf8").split("\n");
6360
6766
  let best = null;
6361
6767
  for (const line of lines) {
6362
6768
  const trimmed = line.trim();
@@ -6382,12 +6788,12 @@ function recoverFinishedCursorRun(opts) {
6382
6788
  return null;
6383
6789
  }
6384
6790
  }
6385
- var import_node_fs25, import_node_path24;
6791
+ var import_node_fs27, import_node_path25;
6386
6792
  var init_cursor_recover = __esm({
6387
6793
  "src/agents/cursor-recover.ts"() {
6388
6794
  "use strict";
6389
- import_node_fs25 = require("fs");
6390
- import_node_path24 = require("path");
6795
+ import_node_fs27 = require("fs");
6796
+ import_node_path25 = require("path");
6391
6797
  init_paths();
6392
6798
  }
6393
6799
  });
@@ -6426,6 +6832,7 @@ var init_title = __esm({
6426
6832
  // src/index.ts
6427
6833
  var index_exports = {};
6428
6834
  __export(index_exports, {
6835
+ ATTACHMENTS_DIR: () => ATTACHMENTS_DIR,
6429
6836
  BRIGHTSY_MCP_ALLOWED_TOOLS: () => BRIGHTSY_MCP_ALLOWED_TOOLS,
6430
6837
  BrightsySideboardApi: () => BrightsySideboardApi,
6431
6838
  CLAUDE_MODEL_CATALOG: () => CLAUDE_MODEL_CATALOG,
@@ -6441,11 +6848,16 @@ __export(index_exports, {
6441
6848
  FAMOUS_SOCCER_TEAMS: () => FAMOUS_SOCCER_TEAMS,
6442
6849
  GLOBAL_WORKSPACE_ID: () => GLOBAL_WORKSPACE_ID,
6443
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,
6444
6854
  MAX_ANTHROPIC_CACHE_CONTROL_BLOCKS: () => MAX_ANTHROPIC_CACHE_CONTROL_BLOCKS,
6445
6855
  ORCHESTRATOR_AGENT_KINDS: () => ORCHESTRATOR_AGENT_KINDS,
6446
6856
  Orchestrator: () => Orchestrator,
6447
6857
  PASTE_ATTACH_MIN_CHARS: () => PASTE_ATTACH_MIN_CHARS,
6448
6858
  PASTE_ATTACH_MIN_LINES: () => PASTE_ATTACH_MIN_LINES,
6859
+ PLAN_FILE_NAME: () => PLAN_FILE_NAME,
6860
+ PLAN_FILE_REL: () => PLAN_FILE_REL,
6449
6861
  PLAN_MODE_INSTRUCTION: () => PLAN_MODE_INSTRUCTION,
6450
6862
  REPO_REVIEW_NAME: () => REPO_REVIEW_NAME,
6451
6863
  REPO_REVIEW_PATH: () => REPO_REVIEW_PATH,
@@ -6455,6 +6867,8 @@ __export(index_exports, {
6455
6867
  SIDEBOARD_FORCE_STOP: () => SIDEBOARD_FORCE_STOP,
6456
6868
  SIDEBOARD_MCP_ALLOWED_TOOLS: () => SIDEBOARD_MCP_ALLOWED_TOOLS,
6457
6869
  THINKING_EFFORTS: () => THINKING_EFFORTS,
6870
+ addPrStackLayer: () => addPrStackLayer,
6871
+ addStackLayerFromThread: () => addStackLayerFromThread,
6458
6872
  addWorkspace: () => addWorkspace,
6459
6873
  adoptThread: () => adoptThread,
6460
6874
  allAdapters: () => allAdapters,
@@ -6473,6 +6887,7 @@ __export(index_exports, {
6473
6887
  attachmentFromAbsolutePath: () => attachmentFromAbsolutePath,
6474
6888
  attachmentsFromBuffers: () => attachmentsFromBuffers,
6475
6889
  attachmentsFromWorktreePaths: () => attachmentsFromWorktreePaths,
6890
+ attachmentsGitignoreBody: () => attachmentsGitignoreBody,
6476
6891
  autoCleanupOrphansEnabled: () => autoCleanupOrphansEnabled,
6477
6892
  autoRenameBranchEnabled: () => autoRenameBranchEnabled,
6478
6893
  autoRunAfterSetupEnabled: () => autoRunAfterSetupEnabled,
@@ -6495,6 +6910,7 @@ __export(index_exports, {
6495
6910
  caffeinateWhileRunningEnabled: () => caffeinateWhileRunningEnabled,
6496
6911
  captureLoginEnv: () => captureLoginEnv,
6497
6912
  captureTurnBaseline: () => captureTurnBaseline,
6913
+ checkoutPrStackLayer: () => checkoutPrStackLayer,
6498
6914
  childEnvWithAppSettings: () => childEnvWithAppSettings,
6499
6915
  claudeAdapter: () => claudeAdapter,
6500
6916
  claudeChromeEnabled: () => claudeChromeEnabled,
@@ -6514,8 +6930,10 @@ __export(index_exports, {
6514
6930
  countCacheControlBlocks: () => countCacheControlBlocks,
6515
6931
  createChatTab: () => createChatTab,
6516
6932
  createEmptyThread: () => createEmptyThread,
6933
+ createExistingBranchWorktree: () => createExistingBranchWorktree,
6517
6934
  createGlobalChat: () => createGlobalChat,
6518
6935
  createOrUpdatePr: () => createOrUpdatePr,
6936
+ createPrStack: () => createPrStack,
6519
6937
  createThread: () => createThread,
6520
6938
  createThreadWorktree: () => createThreadWorktree,
6521
6939
  currentBranch: () => currentBranch,
@@ -6525,6 +6943,7 @@ __export(index_exports, {
6525
6943
  deleteBranchOnPurgeEnabled: () => deleteBranchOnPurgeEnabled,
6526
6944
  deleteThreadRecord: () => deleteThreadRecord,
6527
6945
  detectAgents: () => detectAgents,
6946
+ detectGhStack: () => detectGhStack,
6528
6947
  detectLocalMergeConflicts: () => detectLocalMergeConflicts,
6529
6948
  disconnectBrightsyTeam: () => disconnectBrightsyTeam,
6530
6949
  discoverSkills: () => discoverSkills,
@@ -6540,12 +6959,15 @@ __export(index_exports, {
6540
6959
  estimateThreadChars: () => estimateThreadChars,
6541
6960
  expandComposerPrompt: () => expandComposerPrompt,
6542
6961
  extractGhErrorDetail: () => extractGhErrorDetail,
6962
+ extractPendingPlanQuestions: () => extractPendingPlanQuestions,
6963
+ extractPresentedPlan: () => extractPresentedPlan,
6543
6964
  extractiveSummary: () => extractiveSummary,
6544
6965
  fetchPrHead: () => fetchPrHead,
6545
6966
  finalizeParts: () => finalizeParts,
6546
6967
  findInvalidCacheControlTtlOrder: () => findInvalidCacheControlTtlOrder,
6547
6968
  findOrphanWorktrees: () => findOrphanWorktrees,
6548
6969
  findThreadByRef: () => findThreadByRef,
6970
+ findThreadForStackLayer: () => findThreadForStackLayer,
6549
6971
  flattenTurnInput: () => flattenTurnInput,
6550
6972
  forkChatTab: () => forkChatTab,
6551
6973
  forkMessageSlice: () => forkMessageSlice,
@@ -6556,6 +6978,8 @@ __export(index_exports, {
6556
6978
  formatGhLandError: () => formatGhLandError,
6557
6979
  formatIpcInvokeError: () => formatIpcInvokeError,
6558
6980
  formatMessagesAsTranscript: () => formatMessagesAsTranscript,
6981
+ formatPlanQuestionAnswers: () => formatPlanQuestionAnswers,
6982
+ formatPlanQuestionsForChat: () => formatPlanQuestionsForChat,
6559
6983
  formatRateLimitResetHint: () => formatRateLimitResetHint,
6560
6984
  formatRenameBranchDirective: () => formatRenameBranchDirective,
6561
6985
  formatTranscriptMarkdown: () => formatTranscriptMarkdown,
@@ -6579,6 +7003,7 @@ __export(index_exports, {
6579
7003
  getPrChecks: () => getPrChecks,
6580
7004
  getPrDetails: () => getPrDetails,
6581
7005
  getPrMeta: () => getPrMeta,
7006
+ getPrStack: () => getPrStack,
6582
7007
  getRepoSetupInfo: () => getRepoSetupInfo,
6583
7008
  getRunMode: () => getRunMode,
6584
7009
  getRunScript: () => getRunScript,
@@ -6595,9 +7020,12 @@ __export(index_exports, {
6595
7020
  healOrchestrationSoccerTitles: () => healOrchestrationSoccerTitles,
6596
7021
  importConductorWorkspace: () => importConductorWorkspace,
6597
7022
  importConductorWorkspaceAsync: () => importConductorWorkspaceAsync,
7023
+ initPrStack: () => initPrStack,
7024
+ initStackFromThread: () => initStackFromThread,
6598
7025
  initializeGitRepository: () => initializeGitRepository,
6599
7026
  inspectGitWorktree: () => inspectGitWorktree,
6600
7027
  installAgent: () => installAgent,
7028
+ isAskUserToolName: () => isAskUserToolName,
6601
7029
  isBrightsyConnected: () => isBrightsyConnected,
6602
7030
  isBrightsyNdjsonLine: () => isBrightsyNdjsonLine,
6603
7031
  isCloudCoordinatorThread: () => isCloudCoordinatorThread,
@@ -6607,14 +7035,17 @@ __export(index_exports, {
6607
7035
  isGlobalRepoPath: () => isGlobalRepoPath,
6608
7036
  isGlobalThread: () => isGlobalThread,
6609
7037
  isImageFilePath: () => isImageFilePath,
7038
+ isInPrStack: () => isInPrStack,
6610
7039
  isLinearConnected: () => isLinearConnected,
6611
7040
  isOrchestratorCapableAgent: () => isOrchestratorCapableAgent,
6612
7041
  isOrchestratorThread: () => isOrchestratorThread,
6613
7042
  isPidAlive: () => isPidAlive,
6614
7043
  isPlaceholderBranch: () => isPlaceholderBranch,
7044
+ isPresentPlanToolName: () => isPresentPlanToolName,
6615
7045
  isSessionQuotaLimit: () => isSessionQuotaLimit,
6616
7046
  isSideboardScratchPath: () => isSideboardScratchPath,
6617
7047
  isThinkingEffort: () => isThinkingEffort,
7048
+ isWorkspaceScratchPath: () => isWorkspaceScratchPath,
6618
7049
  listAgentSetupInfo: () => listAgentSetupInfo,
6619
7050
  listBranchCommits: () => listBranchCommits,
6620
7051
  listBranches: () => listBranches,
@@ -6651,6 +7082,7 @@ __export(index_exports, {
6651
7082
  mcpAllowTools: () => mcpAllowTools,
6652
7083
  mcpAuthWarnings: () => mcpAuthWarnings,
6653
7084
  mergePr: () => mergePr,
7085
+ mergePrStack: () => mergePrStack,
6654
7086
  mergeUsage: () => mergeUsage,
6655
7087
  nextPastedTextName: () => nextPastedTextName,
6656
7088
  nextThinkingEffort: () => nextThinkingEffort,
@@ -6660,6 +7092,8 @@ __export(index_exports, {
6660
7092
  normalizeTurnInput: () => normalizeTurnInput,
6661
7093
  normalizeWorktreePath: () => normalizeWorktreePath,
6662
7094
  openInSystemTerminal: () => openInSystemTerminal,
7095
+ openPrStackLayers: () => openPrStackLayers,
7096
+ openStackLayer: () => openStackLayer,
6663
7097
  opencodeAdapter: () => opencodeAdapter,
6664
7098
  orchestrationQuotaFallbackAgent: () => orchestrationQuotaFallbackAgent,
6665
7099
  orchestrationQuotaOnLimit: () => orchestrationQuotaOnLimit,
@@ -6668,15 +7102,19 @@ __export(index_exports, {
6668
7102
  originGhRepoEnv: () => originGhRepoEnv,
6669
7103
  parseCursorRunnerLine: () => parseCursorRunnerLine,
6670
7104
  parseForceStopMessage: () => parseForceStopMessage,
7105
+ parseGhStackViewJson: () => parseGhStackViewJson,
6671
7106
  parseGithubSlugFromRemoteUrl: () => parseGithubSlugFromRemoteUrl,
6672
7107
  parseMcpList: () => parseMcpList,
7108
+ parsePlanQuestionsInput: () => parsePlanQuestionsInput,
6673
7109
  parseSessionQuotaResetAt: () => parseSessionQuotaResetAt,
6674
7110
  partsToAssistantText: () => partsToAssistantText,
6675
7111
  pastedTextStats: () => pastedTextStats,
6676
7112
  permissionMode: () => permissionMode,
7113
+ planFileAbs: () => planFileAbs,
6677
7114
  previewLand: () => previewLand,
6678
7115
  pushBranch: () => pushBranch,
6679
7116
  readExistingReviewRequestFile: () => readExistingReviewRequestFile,
7117
+ readPlanFile: () => readPlanFile,
6680
7118
  readSkillBody: () => readSkillBody,
6681
7119
  readThread: () => readThread,
6682
7120
  readWorktreeFile: () => readWorktreeFile,
@@ -6688,6 +7126,7 @@ __export(index_exports, {
6688
7126
  repoSlug: () => repoSlug,
6689
7127
  requestReview: () => requestReview,
6690
7128
  requireAgent: () => requireAgent,
7129
+ resetGhStackDetectCache: () => resetGhStackDetectCache,
6691
7130
  resolveClaudeExecutable: () => resolveClaudeExecutable,
6692
7131
  resolveConductorCursorAgentId: () => resolveConductorCursorAgentId,
6693
7132
  resolveCursorModelId: () => resolveCursorModelId,
@@ -6697,6 +7136,7 @@ __export(index_exports, {
6697
7136
  resolveFilesToCopy: () => resolveFilesToCopy,
6698
7137
  resolveGhAuthToken: () => resolveGhAuthToken,
6699
7138
  resolveGithubRepoSlug: () => resolveGithubRepoSlug,
7139
+ resolvePlanMarkdown: () => resolvePlanMarkdown,
6700
7140
  resolvePrSelector: () => resolvePrSelector,
6701
7141
  resolveQuotaFallbackAgent: () => resolveQuotaFallbackAgent,
6702
7142
  resolveRepoRoot: () => resolveRepoRoot,
@@ -6724,12 +7164,16 @@ __export(index_exports, {
6724
7164
  slugify: () => slugify,
6725
7165
  spawnAgentTurn: () => spawnAgentTurn,
6726
7166
  splitForCompaction: () => splitForCompaction,
7167
+ stackAgentDefaultsFrom: () => stackAgentDefaultsFrom,
7168
+ stackIdFrom: () => stackIdFrom,
7169
+ stackMergeReadiness: () => stackMergeReadiness,
6727
7170
  stageAbsolutePathsAsAttachments: () => stageAbsolutePathsAsAttachments,
6728
7171
  stageBuffersAsAttachments: () => stageBuffersAsAttachments,
6729
7172
  startDevServer: () => startDevServer,
6730
7173
  startMcpServer: () => startMcpServer,
6731
7174
  startOrchestration: () => startOrchestration,
6732
7175
  stripBrightsyNdjsonNoise: () => stripBrightsyNdjsonNoise,
7176
+ submitPrStack: () => submitPrStack,
6733
7177
  suggestSlug: () => suggestSlug,
6734
7178
  summarizeConversation: () => summarizeConversation,
6735
7179
  switchBrightsyAccount: () => switchBrightsyAccount,
@@ -6765,6 +7209,7 @@ __export(index_exports, {
6765
7209
  worktreeNameFromPath: () => worktreeNameFromPath,
6766
7210
  worktreesRoot: () => worktreesRoot,
6767
7211
  writeInjectedMcpConfig: () => writeInjectedMcpConfig,
7212
+ writePlanFile: () => writePlanFile,
6768
7213
  writeThread: () => writeThread,
6769
7214
  writeWorktreeFile: () => writeWorktreeFile
6770
7215
  });
@@ -6778,6 +7223,7 @@ init_global_workspace();
6778
7223
  init_run();
6779
7224
  init_gh_errors();
6780
7225
  init_worktree();
7226
+ init_stack();
6781
7227
 
6782
7228
  // src/integrations/github.ts
6783
7229
  init_run();
@@ -6974,18 +7420,18 @@ function asRecord(input) {
6974
7420
  }
6975
7421
  return void 0;
6976
7422
  }
6977
- function str(v) {
7423
+ function str2(v) {
6978
7424
  return typeof v === "string" && v.trim() ? v : void 0;
6979
7425
  }
6980
7426
  function toolDetail(name, input) {
6981
7427
  if (!input) return void 0;
6982
- const command = str(input.command) ?? str(input.cmd);
7428
+ const command = str2(input.command) ?? str2(input.cmd);
6983
7429
  if (command) return command;
6984
- 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);
6985
7431
  if (path) return path;
6986
- 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);
6987
7433
  if (pattern) return pattern;
6988
- const query = str(input.query) ?? str(input.prompt);
7434
+ const query = str2(input.query) ?? str2(input.prompt);
6989
7435
  if (query) return query.length > 80 ? `${query.slice(0, 77)}\u2026` : query;
6990
7436
  try {
6991
7437
  const raw = JSON.stringify(input);
@@ -6998,23 +7444,26 @@ function toolDescription(name, input) {
6998
7444
  const n = name.replace(/^mcp__/, "").replace(/__/g, " \xB7 ");
6999
7445
  if (/^get_record_types$|recordTypes/i.test(name)) return "List record types";
7000
7446
  if (/connectedAgentRequest/i.test(name)) {
7001
- return str(input?.agent_id) ? `Ask connected agent` : "Ask connected agent";
7447
+ return str2(input?.agent_id) ? `Ask connected agent` : "Ask connected agent";
7002
7448
  }
7003
7449
  if (/present_artifact$/i.test(name)) {
7004
- 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";
7005
7454
  }
7006
7455
  if (/present_schema$/i.test(name)) {
7007
- return str(input?.title) ? `Schema ${str(input?.title)}` : "Present schema";
7456
+ return str2(input?.title) ? `Schema ${str2(input?.title)}` : "Present schema";
7008
7457
  }
7009
7458
  if (/present_files$/i.test(name)) {
7010
- return str(input?.title) ? `Files ${str(input?.title)}` : "Present files";
7459
+ return str2(input?.title) ? `Files ${str2(input?.title)}` : "Present files";
7011
7460
  }
7012
7461
  if (/^(create|update)_artifact$/i.test(name.replace(/^mcp__[^_]+__/, ""))) {
7013
- return str(input?.title) ? `Artifact ${str(input?.title)}` : "Artifact";
7462
+ return str2(input?.title) ? `Artifact ${str2(input?.title)}` : "Artifact";
7014
7463
  }
7015
7464
  if (!input) return n;
7016
- if (/bash|shell|terminal/i.test(name) && str(input.command)) {
7017
- const cmd = str(input.command);
7465
+ if (/bash|shell|terminal/i.test(name) && str2(input.command)) {
7466
+ const cmd = str2(input.command);
7018
7467
  if (/git\s+fetch/i.test(cmd)) return "Fetch latest from origin and check status";
7019
7468
  if (/git\s+status/i.test(cmd)) return "Check git status";
7020
7469
  if (/git\s+log/i.test(cmd)) return "Inspect recent commits";
@@ -7023,11 +7472,11 @@ function toolDescription(name, input) {
7023
7472
  return "Run shell command";
7024
7473
  }
7025
7474
  if (/edit|write|apply/i.test(name)) {
7026
- const path = str(input.file_path) ?? str(input.path);
7475
+ const path = str2(input.file_path) ?? str2(input.path);
7027
7476
  return path ? `Edit ${path.split("/").pop()}` : "Edit file";
7028
7477
  }
7029
7478
  if (/read/i.test(name)) {
7030
- const path = str(input.file_path) ?? str(input.path);
7479
+ const path = str2(input.file_path) ?? str2(input.path);
7031
7480
  return path ? `Read ${path.split("/").pop()}` : "Read file";
7032
7481
  }
7033
7482
  if (/grep/i.test(name)) return "Search files";
@@ -7036,7 +7485,7 @@ function toolDescription(name, input) {
7036
7485
  }
7037
7486
  function toolFilePath(input) {
7038
7487
  if (!input) return void 0;
7039
- 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);
7040
7489
  }
7041
7490
  function countLines(text) {
7042
7491
  if (!text) return 0;
@@ -7044,8 +7493,8 @@ function countLines(text) {
7044
7493
  }
7045
7494
  function diffFromInput(input) {
7046
7495
  if (!input) return {};
7047
- const oldS = str(input.old_string) ?? str(input.oldString);
7048
- 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);
7049
7498
  if (oldS != null || newS != null) {
7050
7499
  return {
7051
7500
  additions: countLines(newS),
@@ -8212,33 +8661,33 @@ async function getDiff(worktreePath, repoPath, opts) {
8212
8661
  let combinedDiff = "";
8213
8662
  let labelBase = base;
8214
8663
  if (scope === "staged") {
8215
- const [ns, num, diff] = await Promise.all([
8664
+ const [ns, num2, diff] = await Promise.all([
8216
8665
  git(["diff", "--name-status", "--cached"], worktreePath, { reject: false }),
8217
8666
  git(["diff", "--numstat", "--cached"], worktreePath, { reject: false }),
8218
8667
  git(["diff", "--cached"], worktreePath, { reject: false })
8219
8668
  ]);
8220
8669
  nameStatus = ns.stdout;
8221
- numstat = num.stdout;
8670
+ numstat = num2.stdout;
8222
8671
  combinedDiff = diff.stdout;
8223
8672
  labelBase = "staged";
8224
8673
  } else if (scope === "unstaged") {
8225
- const [ns, num, diff] = await Promise.all([
8674
+ const [ns, num2, diff] = await Promise.all([
8226
8675
  git(["diff", "--name-status"], worktreePath, { reject: false }),
8227
8676
  git(["diff", "--numstat"], worktreePath, { reject: false }),
8228
8677
  git(["diff"], worktreePath, { reject: false })
8229
8678
  ]);
8230
8679
  nameStatus = ns.stdout;
8231
- numstat = num.stdout;
8680
+ numstat = num2.stdout;
8232
8681
  combinedDiff = diff.stdout;
8233
8682
  labelBase = "unstaged";
8234
8683
  } else if (scope === "uncommitted") {
8235
- const [ns, num, diff] = await Promise.all([
8684
+ const [ns, num2, diff] = await Promise.all([
8236
8685
  git(["diff", "--name-status", "HEAD"], worktreePath, { reject: false }),
8237
8686
  git(["diff", "--numstat", "HEAD"], worktreePath, { reject: false }),
8238
8687
  git(["diff", "HEAD"], worktreePath, { reject: false })
8239
8688
  ]);
8240
8689
  nameStatus = ns.stdout;
8241
- numstat = num.stdout;
8690
+ numstat = num2.stdout;
8242
8691
  combinedDiff = diff.stdout;
8243
8692
  labelBase = "HEAD";
8244
8693
  } else if (scope === "last_turn") {
@@ -8257,34 +8706,34 @@ async function getDiff(worktreePath, repoPath, opts) {
8257
8706
  scopeStats
8258
8707
  };
8259
8708
  }
8260
- const [ns, num, diff] = await Promise.all([
8709
+ const [ns, num2, diff] = await Promise.all([
8261
8710
  git(["diff", "--name-status", lastTurnBase], worktreePath, { reject: false }),
8262
8711
  git(["diff", "--numstat", lastTurnBase], worktreePath, { reject: false }),
8263
8712
  git(["diff", lastTurnBase], worktreePath, { reject: false })
8264
8713
  ]);
8265
8714
  nameStatus = ns.stdout;
8266
- numstat = num.stdout;
8715
+ numstat = num2.stdout;
8267
8716
  combinedDiff = diff.stdout;
8268
8717
  labelBase = "last turn";
8269
8718
  } else if (scope === "commits" && commitSha) {
8270
8719
  const range = `${commitSha}^!`;
8271
- const [ns, num, diff] = await Promise.all([
8720
+ const [ns, num2, diff] = await Promise.all([
8272
8721
  git(["diff", "--name-status", range], worktreePath, { reject: false }),
8273
8722
  git(["diff", "--numstat", range], worktreePath, { reject: false }),
8274
8723
  git(["diff", range], worktreePath, { reject: false })
8275
8724
  ]);
8276
8725
  nameStatus = ns.stdout;
8277
- numstat = num.stdout;
8726
+ numstat = num2.stdout;
8278
8727
  combinedDiff = diff.stdout;
8279
8728
  labelBase = commitSha.slice(0, 7);
8280
8729
  } else {
8281
- const [ns, num, diff] = await Promise.all([
8730
+ const [ns, num2, diff] = await Promise.all([
8282
8731
  git(["diff", "--name-status", mergeBase], worktreePath, { reject: false }),
8283
8732
  git(["diff", "--numstat", mergeBase], worktreePath, { reject: false }),
8284
8733
  git(["diff", mergeBase], worktreePath, { reject: false })
8285
8734
  ]);
8286
8735
  nameStatus = ns.stdout;
8287
- numstat = num.stdout;
8736
+ numstat = num2.stdout;
8288
8737
  combinedDiff = diff.stdout;
8289
8738
  labelBase = base;
8290
8739
  }
@@ -8717,6 +9166,7 @@ function buildDiffCommentAttachment(input) {
8717
9166
  var import_node_fs19 = require("fs");
8718
9167
  var import_node_path19 = require("path");
8719
9168
  var import_node_crypto2 = require("crypto");
9169
+ init_workspace_scratch();
8720
9170
  var IMAGE_EXTENSIONS2 = /* @__PURE__ */ new Set([
8721
9171
  "png",
8722
9172
  "jpg",
@@ -8737,11 +9187,6 @@ var IMAGE_MIME_BY_EXT = {
8737
9187
  bmp: "image/bmp",
8738
9188
  ico: "image/x-icon"
8739
9189
  };
8740
- var ATTACHMENTS_DIR = ".sideboard/attachments";
8741
- var ATTACHMENTS_GITIGNORE = `# Sideboard review / composer attachments (local only)
8742
- *
8743
- !.gitignore
8744
- `;
8745
9190
  var MAX_INLINE_BYTES = 4e5;
8746
9191
  var MAX_PREVIEW_BYTES = 5e6;
8747
9192
  function fileExtension(filePath) {
@@ -8759,7 +9204,7 @@ function ensureAttachmentsDir(worktreePath) {
8759
9204
  (0, import_node_fs19.mkdirSync)(dir, { recursive: true });
8760
9205
  const gi = (0, import_node_path19.join)(dir, ".gitignore");
8761
9206
  if (!(0, import_node_fs19.existsSync)(gi)) {
8762
- (0, import_node_fs19.writeFileSync)(gi, ATTACHMENTS_GITIGNORE, "utf8");
9207
+ (0, import_node_fs19.writeFileSync)(gi, attachmentsGitignoreBody(), "utf8");
8763
9208
  }
8764
9209
  return dir;
8765
9210
  }
@@ -9419,7 +9864,7 @@ var import_node_fs20 = require("fs");
9419
9864
  init_worktree();
9420
9865
  init_thread_store();
9421
9866
  init_workspaces();
9422
- async function createThread(input, onSetupLine) {
9867
+ async function createThread(input, _onSetupLine) {
9423
9868
  await requireAgent(input.agent);
9424
9869
  const repoPath = await resolveRepoRoot(input.repoPath);
9425
9870
  if (!(0, import_node_fs20.existsSync)(repoPath)) {
@@ -9429,10 +9874,10 @@ async function createThread(input, onSetupLine) {
9429
9874
  let sourceIsFork = false;
9430
9875
  let prUrl = null;
9431
9876
  if (input.sourceType === "pr") {
9432
- const num = Number(input.sourceRef.replace(/^#/, ""));
9433
- if (!Number.isFinite(num)) throw new Error(`Invalid PR number: ${input.sourceRef}`);
9434
- const pr = await getPr(repoPath, num);
9435
- 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`);
9436
9881
  sourceIsFork = pr.isCrossRepository;
9437
9882
  prUrl = pr.url;
9438
9883
  const localFetchBranch = `sideboard-pr-${pr.number}`;
@@ -9477,22 +9922,6 @@ async function createThread(input, onSetupLine) {
9477
9922
  });
9478
9923
  writeThread(thread);
9479
9924
  await ensureWorkspace(repoPath);
9480
- try {
9481
- let setup = await runSetupScript(repoPath, worktreePath, onSetupLine);
9482
- if (!setup.ran) {
9483
- setup = await runCursorWorktreeSetup(repoPath, worktreePath, onSetupLine);
9484
- }
9485
- if (setup.ran && setup.exitCode !== 0 && setup.exitCode !== null) {
9486
- updateThread(thread.id, {
9487
- lastError: `Setup exited ${setup.exitCode} (thread is still usable)`
9488
- });
9489
- }
9490
- } catch (err) {
9491
- const message = err instanceof Error ? err.message : String(err);
9492
- updateThread(thread.id, {
9493
- lastError: `Setup failed: ${message}`
9494
- });
9495
- }
9496
9925
  return readThread(thread.id) ?? thread;
9497
9926
  }
9498
9927
  async function listLinearIssues(agent, repoPath) {
@@ -9545,7 +9974,9 @@ function worktreeBindingFrom(from) {
9545
9974
  sourceIsFork: from.sourceIsFork,
9546
9975
  parentThreadId: from.parentThreadId,
9547
9976
  prUrl: from.prUrl,
9548
- prTitle: from.prTitle
9977
+ prTitle: from.prTitle,
9978
+ stackId: from.stackId,
9979
+ stackLayer: from.stackLayer
9549
9980
  };
9550
9981
  }
9551
9982
  function threadsSharingWorktree(worktreePath) {
@@ -9660,9 +10091,308 @@ async function forkThreadWorktree(input, onSetupLine) {
9660
10091
  return thread;
9661
10092
  }
9662
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
+
9663
10393
  // src/threads/adopt.ts
9664
10394
  var import_node_child_process = require("child_process");
9665
- var import_node_fs21 = require("fs");
10395
+ var import_node_fs22 = require("fs");
9666
10396
  var import_node_os9 = require("os");
9667
10397
  var import_node_path20 = require("path");
9668
10398
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
@@ -9687,21 +10417,21 @@ function mapAgentType(raw) {
9687
10417
  return null;
9688
10418
  }
9689
10419
  function resolveConductorCursorAgentId(workspacePath) {
9690
- 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;
9691
10421
  const normalized = workspacePath.replace(/\/$/, "");
9692
10422
  let best = null;
9693
10423
  let hashes;
9694
10424
  try {
9695
- hashes = (0, import_node_fs21.readdirSync)(CURSOR_SDK_STORE);
10425
+ hashes = (0, import_node_fs22.readdirSync)(CURSOR_SDK_STORE);
9696
10426
  } catch {
9697
10427
  return null;
9698
10428
  }
9699
10429
  for (const hash of hashes) {
9700
10430
  const agentsFile = (0, import_node_path20.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
9701
- if (!(0, import_node_fs21.existsSync)(agentsFile)) continue;
10431
+ if (!(0, import_node_fs22.existsSync)(agentsFile)) continue;
9702
10432
  let text;
9703
10433
  try {
9704
- text = (0, import_node_fs21.readFileSync)(agentsFile, "utf8");
10434
+ text = (0, import_node_fs22.readFileSync)(agentsFile, "utf8");
9705
10435
  } catch {
9706
10436
  continue;
9707
10437
  }
@@ -9725,7 +10455,7 @@ function resolveConductorCursorAgentId(workspacePath) {
9725
10455
  return best?.agentId ?? null;
9726
10456
  }
9727
10457
  async function adoptThread(input) {
9728
- if (!(0, import_node_fs21.existsSync)(input.worktreePath)) {
10458
+ if (!(0, import_node_fs22.existsSync)(input.worktreePath)) {
9729
10459
  throw new Error(`Worktree not found: ${input.worktreePath}`);
9730
10460
  }
9731
10461
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -9752,18 +10482,18 @@ function conductorDbPath() {
9752
10482
  return CONDUCTOR_DB;
9753
10483
  }
9754
10484
  function listConductorWorkspaces() {
9755
- if (!(0, import_node_fs21.existsSync)(CONDUCTOR_DB)) {
10485
+ if (!(0, import_node_fs22.existsSync)(CONDUCTOR_DB)) {
9756
10486
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
9757
10487
  }
9758
- 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-"));
9759
10489
  const snapshot = (0, import_node_path20.join)(tmp, "conductor.db");
9760
10490
  try {
9761
- (0, import_node_fs21.copyFileSync)(CONDUCTOR_DB, snapshot);
10491
+ (0, import_node_fs22.copyFileSync)(CONDUCTOR_DB, snapshot);
9762
10492
  for (const suffix of ["-wal", "-shm"]) {
9763
10493
  const src = `${CONDUCTOR_DB}${suffix}`;
9764
- if ((0, import_node_fs21.existsSync)(src)) {
10494
+ if ((0, import_node_fs22.existsSync)(src)) {
9765
10495
  try {
9766
- (0, import_node_fs21.copyFileSync)(src, `${snapshot}${suffix}`);
10496
+ (0, import_node_fs22.copyFileSync)(src, `${snapshot}${suffix}`);
9767
10497
  } catch {
9768
10498
  }
9769
10499
  }
@@ -9839,22 +10569,22 @@ function listConductorWorkspaces() {
9839
10569
  db.close();
9840
10570
  }
9841
10571
  } finally {
9842
- (0, import_node_fs21.rmSync)(tmp, { recursive: true, force: true });
10572
+ (0, import_node_fs22.rmSync)(tmp, { recursive: true, force: true });
9843
10573
  }
9844
10574
  }
9845
10575
  function importConductorWorkspace(workspaceId) {
9846
- if (!(0, import_node_fs21.existsSync)(CONDUCTOR_DB)) {
10576
+ if (!(0, import_node_fs22.existsSync)(CONDUCTOR_DB)) {
9847
10577
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
9848
10578
  }
9849
- 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-"));
9850
10580
  const snapshot = (0, import_node_path20.join)(tmp, "conductor.db");
9851
10581
  try {
9852
- (0, import_node_fs21.copyFileSync)(CONDUCTOR_DB, snapshot);
10582
+ (0, import_node_fs22.copyFileSync)(CONDUCTOR_DB, snapshot);
9853
10583
  for (const suffix of ["-wal", "-shm"]) {
9854
10584
  const src = `${CONDUCTOR_DB}${suffix}`;
9855
- if ((0, import_node_fs21.existsSync)(src)) {
10585
+ if ((0, import_node_fs22.existsSync)(src)) {
9856
10586
  try {
9857
- (0, import_node_fs21.copyFileSync)(src, `${snapshot}${suffix}`);
10587
+ (0, import_node_fs22.copyFileSync)(src, `${snapshot}${suffix}`);
9858
10588
  } catch {
9859
10589
  }
9860
10590
  }
@@ -9872,7 +10602,7 @@ function importConductorWorkspace(workspaceId) {
9872
10602
  ).get(workspaceId);
9873
10603
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
9874
10604
  const worktreePath = String(row.workspacePath);
9875
- if (!(0, import_node_fs21.existsSync)(worktreePath)) {
10605
+ if (!(0, import_node_fs22.existsSync)(worktreePath)) {
9876
10606
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
9877
10607
  }
9878
10608
  let sessionId = null;
@@ -9935,7 +10665,7 @@ function importConductorWorkspace(workspaceId) {
9935
10665
  db.close();
9936
10666
  }
9937
10667
  } finally {
9938
- (0, import_node_fs21.rmSync)(tmp, { recursive: true, force: true });
10668
+ (0, import_node_fs22.rmSync)(tmp, { recursive: true, force: true });
9939
10669
  }
9940
10670
  }
9941
10671
  async function importConductorWorkspaceAsync(workspaceId) {
@@ -9944,13 +10674,14 @@ async function importConductorWorkspaceAsync(workspaceId) {
9944
10674
 
9945
10675
  // src/orchestrator/orchestrator.ts
9946
10676
  var import_node_events = require("events");
9947
- var import_node_fs26 = require("fs");
10677
+ var import_node_fs28 = require("fs");
9948
10678
  init_error_detail();
9949
10679
  init_agents();
9950
10680
  init_worktree();
10681
+ init_stack();
9951
10682
 
9952
10683
  // src/git/orphan-cleanup.ts
9953
- var import_node_fs22 = require("fs");
10684
+ var import_node_fs23 = require("fs");
9954
10685
  var import_node_path21 = require("path");
9955
10686
  init_worktree();
9956
10687
  init_thread_store();
@@ -9966,9 +10697,9 @@ async function findOrphanWorktrees(repoPaths) {
9966
10697
  repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
9967
10698
  );
9968
10699
  const homeRoot = sideboardWorkspacesDir();
9969
- if ((0, import_node_fs22.existsSync)(homeRoot)) {
10700
+ if ((0, import_node_fs23.existsSync)(homeRoot)) {
9970
10701
  try {
9971
- 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 })) {
9972
10703
  if (!entry.isDirectory()) continue;
9973
10704
  void entry;
9974
10705
  }
@@ -9978,7 +10709,7 @@ async function findOrphanWorktrees(repoPaths) {
9978
10709
  const orphans = [];
9979
10710
  const seen = /* @__PURE__ */ new Set();
9980
10711
  for (const repoPath of repos) {
9981
- if (!repoPath || !(0, import_node_fs22.existsSync)(repoPath)) continue;
10712
+ if (!repoPath || !(0, import_node_fs23.existsSync)(repoPath)) continue;
9982
10713
  try {
9983
10714
  const wts = await listWorktrees(repoPath);
9984
10715
  for (const wt of wts) {
@@ -9989,7 +10720,7 @@ async function findOrphanWorktrees(repoPaths) {
9989
10720
  seen.add(path);
9990
10721
  let mtimeMs = 0;
9991
10722
  try {
9992
- mtimeMs = (0, import_node_fs22.statSync)(path).mtimeMs;
10723
+ mtimeMs = (0, import_node_fs23.statSync)(path).mtimeMs;
9993
10724
  } catch {
9994
10725
  mtimeMs = 0;
9995
10726
  }
@@ -9999,16 +10730,16 @@ async function findOrphanWorktrees(repoPaths) {
9999
10730
  }
10000
10731
  try {
10001
10732
  const root = worktreesRoot(repoPath);
10002
- if ((0, import_node_fs22.existsSync)(root)) {
10003
- 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 })) {
10004
10735
  if (!entry.isDirectory()) continue;
10005
10736
  const path = (0, import_node_path21.join)(root, entry.name).replace(/\/$/, "");
10006
10737
  if (known.has(path) || seen.has(path)) continue;
10007
- 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;
10008
10739
  seen.add(path);
10009
10740
  let mtimeMs = 0;
10010
10741
  try {
10011
- mtimeMs = (0, import_node_fs22.statSync)(path).mtimeMs;
10742
+ mtimeMs = (0, import_node_fs23.statSync)(path).mtimeMs;
10012
10743
  } catch {
10013
10744
  mtimeMs = Date.now();
10014
10745
  }
@@ -10154,7 +10885,7 @@ async function applyThreadIntoMain(thread, opts) {
10154
10885
  }
10155
10886
 
10156
10887
  // src/git/clone-repo.ts
10157
- var import_node_fs23 = require("fs");
10888
+ var import_node_fs24 = require("fs");
10158
10889
  var import_node_path22 = require("path");
10159
10890
  var import_execa6 = require("execa");
10160
10891
  init_paths();
@@ -10170,7 +10901,7 @@ async function cloneRepoIntoSideboard(opts) {
10170
10901
  }
10171
10902
  name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
10172
10903
  const dest = (0, import_node_path22.join)(sideboardReposDir(), name);
10173
- if ((0, import_node_fs23.existsSync)(dest)) {
10904
+ if ((0, import_node_fs24.existsSync)(dest)) {
10174
10905
  const repoPath2 = await resolveRepoRoot(dest);
10175
10906
  const workspace2 = await ensureWorkspace(repoPath2);
10176
10907
  return { repoPath: repoPath2, workspace: workspace2 };
@@ -10192,7 +10923,7 @@ init_orchestrator_capable();
10192
10923
 
10193
10924
  // src/review/request-review.ts
10194
10925
  var import_node_crypto5 = require("crypto");
10195
- var import_node_fs24 = require("fs");
10926
+ var import_node_fs25 = require("fs");
10196
10927
  var import_node_path23 = require("path");
10197
10928
  init_global_workspace();
10198
10929
  init_thread_store();
@@ -10317,15 +11048,13 @@ File: src/client/frontends/desktop/core/UserData.ts
10317
11048
  `;
10318
11049
 
10319
11050
  // src/review/request-review.ts
11051
+ init_workspace_scratch();
10320
11052
  var REPO_REVIEW_PATH = ".sideboard/review.md";
10321
11053
  var REPO_REVIEW_NAME = "review.md";
10322
- 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`;
10323
11056
  var REVIEW_REQUEST_NAME = "Review request.md";
10324
11057
  var REVIEW_REQUEST_PREFILL = "Review.";
10325
- var ATTACHMENTS_GITIGNORE2 = `# Sideboard review / composer attachments (local only)
10326
- *
10327
- !.gitignore
10328
- `;
10329
11058
  var LEGACY_REVIEW_TEMPLATE_MARKERS = [
10330
11059
  "You are acting as a reviewer for a proposed code change made by another engineer.",
10331
11060
  "HOW MANY FINDINGS TO RETURN:"
@@ -10339,19 +11068,19 @@ function shouldRefreshReviewRequestTemplate(content) {
10339
11068
  return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
10340
11069
  }
10341
11070
  function readTextIfPresent(abs) {
10342
- if (!(0, import_node_fs24.existsSync)(abs)) return null;
11071
+ if (!(0, import_node_fs25.existsSync)(abs)) return null;
10343
11072
  try {
10344
- const content = (0, import_node_fs24.readFileSync)(abs, "utf8");
11073
+ const content = (0, import_node_fs25.readFileSync)(abs, "utf8");
10345
11074
  return content.trim() ? content : null;
10346
11075
  } catch {
10347
11076
  return null;
10348
11077
  }
10349
11078
  }
10350
11079
  function ensureAttachmentsGitignore(worktreePath) {
10351
- const gitignoreAbs = (0, import_node_path23.join)(worktreePath, ".sideboard", "attachments", ".gitignore");
10352
- if ((0, import_node_fs24.existsSync)(gitignoreAbs)) return;
10353
- (0, import_node_fs24.mkdirSync)((0, import_node_path23.dirname)(gitignoreAbs), { recursive: true });
10354
- (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");
10355
11084
  }
10356
11085
  function resolveReviewGuidelines(worktreePath) {
10357
11086
  const repoAbs = (0, import_node_path23.join)(worktreePath, REPO_REVIEW_PATH);
@@ -10374,9 +11103,19 @@ function resolveReviewGuidelines(worktreePath) {
10374
11103
  source: "local"
10375
11104
  };
10376
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
+ }
10377
11116
  ensureAttachmentsGitignore(worktreePath);
10378
- (0, import_node_fs24.mkdirSync)((0, import_node_path23.dirname)(localAbs), { recursive: true });
10379
- (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");
10380
11119
  return {
10381
11120
  path: REVIEW_REQUEST_PATH,
10382
11121
  name: REVIEW_REQUEST_NAME,
@@ -10396,17 +11135,18 @@ function ensureReviewRequestFile(worktreePath) {
10396
11135
  };
10397
11136
  }
10398
11137
  const localAbs = (0, import_node_path23.join)(worktreePath, REVIEW_REQUEST_PATH);
10399
- const localContent = readTextIfPresent(localAbs);
11138
+ const localContent = readTextIfPresent(localAbs) ?? readTextIfPresent((0, import_node_path23.join)(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
10400
11139
  if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
11140
+ const path = (0, import_node_fs25.existsSync)(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
10401
11141
  return {
10402
- path: REVIEW_REQUEST_PATH,
11142
+ path,
10403
11143
  name: REVIEW_REQUEST_NAME,
10404
11144
  content: localContent,
10405
11145
  source: "local"
10406
11146
  };
10407
11147
  }
10408
- (0, import_node_fs24.mkdirSync)((0, import_node_path23.dirname)(repoAbs), { recursive: true });
10409
- (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");
10410
11150
  return {
10411
11151
  path: REPO_REVIEW_PATH,
10412
11152
  name: REPO_REVIEW_NAME,
@@ -10426,7 +11166,7 @@ function buildReviewRequestAttachment(content, opts) {
10426
11166
  };
10427
11167
  }
10428
11168
  function readExistingReviewRequestFile(worktreePath) {
10429
- 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));
10430
11170
  }
10431
11171
  async function requestReview(threadRef, send) {
10432
11172
  const from = findThreadByRef(threadRef);
@@ -10565,6 +11305,7 @@ function createQuotaFailoverChat(from, fallbackAgent, limitText) {
10565
11305
 
10566
11306
  // src/orchestrator/orchestrator.ts
10567
11307
  init_types();
11308
+ init_plan_file();
10568
11309
  init_settings();
10569
11310
 
10570
11311
  // src/threads/sync-branch.ts
@@ -10684,7 +11425,7 @@ var Orchestrator = class {
10684
11425
  }
10685
11426
  continue;
10686
11427
  }
10687
- if (!(0, import_node_fs26.existsSync)(thread.worktreePath)) {
11428
+ if (!(0, import_node_fs28.existsSync)(thread.worktreePath)) {
10688
11429
  setStatus(thread.id, "broken", "Worktree missing on disk");
10689
11430
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
10690
11431
  continue;
@@ -10829,14 +11570,9 @@ var Orchestrator = class {
10829
11570
  return findThreadByRef(idOrRef) ?? readThread(idOrRef);
10830
11571
  }
10831
11572
  async createThread(input) {
10832
- let thread = await createThread(input, (line) => {
10833
- this.emit({
10834
- type: "turn_output",
10835
- threadId: "pending",
10836
- event: { type: "stdout", data: line }
10837
- });
10838
- });
11573
+ let thread = await createThread(input);
10839
11574
  this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
11575
+ await this.runSetupAfterCreate(thread.id);
10840
11576
  const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
10841
11577
  if (autoRunAfterSetupEnabled2()) {
10842
11578
  try {
@@ -10850,6 +11586,19 @@ var Orchestrator = class {
10850
11586
  }
10851
11587
  return thread;
10852
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
+ }
10853
11602
  listWorkspaces() {
10854
11603
  const fromThreads = listThreads({ includeArchived: false }).map((t) => t.repoPath);
10855
11604
  return syncWorkspacesFromThreads(fromThreads);
@@ -11198,6 +11947,19 @@ var Orchestrator = class {
11198
11947
  });
11199
11948
  }
11200
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
+ }
11201
11963
  if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
11202
11964
  (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
11203
11965
  )) {
@@ -11679,6 +12441,82 @@ var Orchestrator = class {
11679
12441
  }
11680
12442
  return meta;
11681
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
+ }
11682
12520
  async getPrDetails(threadRef) {
11683
12521
  const { thread, selector, cwd } = await this.withPrSelector(threadRef);
11684
12522
  if (!selector) return null;
@@ -11741,14 +12579,9 @@ var Orchestrator = class {
11741
12579
  return forkChatTab(input);
11742
12580
  }
11743
12581
  async forkThreadWorktree(input) {
11744
- const thread = await forkThreadWorktree(input, (line) => {
11745
- this.emit({
11746
- type: "turn_output",
11747
- threadId: "pending",
11748
- event: { type: "stdout", data: line }
11749
- });
11750
- });
12582
+ const thread = await forkThreadWorktree(input);
11751
12583
  this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
12584
+ await this.runSetupAfterCreate(thread.id);
11752
12585
  return thread;
11753
12586
  }
11754
12587
  renameThread(threadRef, title) {
@@ -11762,7 +12595,7 @@ var Orchestrator = class {
11762
12595
  }
11763
12596
  /**
11764
12597
  * Stage OS / worktree files into composer attachments (copies external files
11765
- * into `.sideboard/attachments/` so agents can Read images and binaries).
12598
+ * into `.context/attachments/` so agents can Read images and binaries).
11766
12599
  */
11767
12600
  attachComposerFiles(threadRef, opts) {
11768
12601
  const thread = this.requireThread(threadRef);
@@ -11836,7 +12669,7 @@ var Orchestrator = class {
11836
12669
  updateThread(thread.id, { worktreePath: globalAgentCwd2() });
11837
12670
  return setStatus(thread.id, "idle");
11838
12671
  }
11839
- if (!(0, import_node_fs26.existsSync)(thread.worktreePath)) {
12672
+ if (!(0, import_node_fs28.existsSync)(thread.worktreePath)) {
11840
12673
  const { createThreadWorktree: createThreadWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
11841
12674
  const { execa: execa7 } = await import("execa");
11842
12675
  const slug = thread.worktreePath.split("/").pop();
@@ -11944,13 +12777,116 @@ async function startOrchestration(opts) {
11944
12777
  }
11945
12778
 
11946
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();
11947
12883
  init_coordinator_prompt();
11948
12884
 
11949
12885
  // src/mcp/server.ts
11950
12886
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
11951
12887
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
11952
12888
  var import_zod = require("zod");
11953
- var import_node_path25 = require("path");
12889
+ var import_node_path26 = require("path");
11954
12890
  init_worktree();
11955
12891
  init_global_workspace();
11956
12892
  init_list_models();
@@ -11999,7 +12935,7 @@ async function startMcpServer() {
11999
12935
  async () => {
12000
12936
  const threads = orch.getThreads(true);
12001
12937
  const lines = threads.map((t) => {
12002
- 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;
12003
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}` : ""}`;
12004
12940
  });
12005
12941
  return {
@@ -12061,6 +12997,59 @@ async function startMcpServer() {
12061
12997
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
12062
12998
  }
12063
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
+ );
12064
13053
  server.tool(
12065
13054
  "present_schema",
12066
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.",
@@ -12639,6 +13628,126 @@ async function startMcpServer() {
12639
13628
  };
12640
13629
  }
12641
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
+ );
12642
13751
  server.tool(
12643
13752
  "list_issues",
12644
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.",
@@ -13008,6 +14117,7 @@ init_connected_teams();
13008
14117
  init_injected_mcp();
13009
14118
  // Annotate the CommonJS export names for ESM import in node:
13010
14119
  0 && (module.exports = {
14120
+ ATTACHMENTS_DIR,
13011
14121
  BRIGHTSY_MCP_ALLOWED_TOOLS,
13012
14122
  BrightsySideboardApi,
13013
14123
  CLAUDE_MODEL_CATALOG,
@@ -13023,11 +14133,16 @@ init_injected_mcp();
13023
14133
  FAMOUS_SOCCER_TEAMS,
13024
14134
  GLOBAL_WORKSPACE_ID,
13025
14135
  HARNESS_ENV_KEYS,
14136
+ LEGACY_ATTACHMENTS_DIR,
14137
+ LEGACY_PLAN_FILE_REL,
14138
+ LEGACY_REVIEW_REQUEST_PATH,
13026
14139
  MAX_ANTHROPIC_CACHE_CONTROL_BLOCKS,
13027
14140
  ORCHESTRATOR_AGENT_KINDS,
13028
14141
  Orchestrator,
13029
14142
  PASTE_ATTACH_MIN_CHARS,
13030
14143
  PASTE_ATTACH_MIN_LINES,
14144
+ PLAN_FILE_NAME,
14145
+ PLAN_FILE_REL,
13031
14146
  PLAN_MODE_INSTRUCTION,
13032
14147
  REPO_REVIEW_NAME,
13033
14148
  REPO_REVIEW_PATH,
@@ -13037,6 +14152,8 @@ init_injected_mcp();
13037
14152
  SIDEBOARD_FORCE_STOP,
13038
14153
  SIDEBOARD_MCP_ALLOWED_TOOLS,
13039
14154
  THINKING_EFFORTS,
14155
+ addPrStackLayer,
14156
+ addStackLayerFromThread,
13040
14157
  addWorkspace,
13041
14158
  adoptThread,
13042
14159
  allAdapters,
@@ -13055,6 +14172,7 @@ init_injected_mcp();
13055
14172
  attachmentFromAbsolutePath,
13056
14173
  attachmentsFromBuffers,
13057
14174
  attachmentsFromWorktreePaths,
14175
+ attachmentsGitignoreBody,
13058
14176
  autoCleanupOrphansEnabled,
13059
14177
  autoRenameBranchEnabled,
13060
14178
  autoRunAfterSetupEnabled,
@@ -13077,6 +14195,7 @@ init_injected_mcp();
13077
14195
  caffeinateWhileRunningEnabled,
13078
14196
  captureLoginEnv,
13079
14197
  captureTurnBaseline,
14198
+ checkoutPrStackLayer,
13080
14199
  childEnvWithAppSettings,
13081
14200
  claudeAdapter,
13082
14201
  claudeChromeEnabled,
@@ -13096,8 +14215,10 @@ init_injected_mcp();
13096
14215
  countCacheControlBlocks,
13097
14216
  createChatTab,
13098
14217
  createEmptyThread,
14218
+ createExistingBranchWorktree,
13099
14219
  createGlobalChat,
13100
14220
  createOrUpdatePr,
14221
+ createPrStack,
13101
14222
  createThread,
13102
14223
  createThreadWorktree,
13103
14224
  currentBranch,
@@ -13107,6 +14228,7 @@ init_injected_mcp();
13107
14228
  deleteBranchOnPurgeEnabled,
13108
14229
  deleteThreadRecord,
13109
14230
  detectAgents,
14231
+ detectGhStack,
13110
14232
  detectLocalMergeConflicts,
13111
14233
  disconnectBrightsyTeam,
13112
14234
  discoverSkills,
@@ -13122,12 +14244,15 @@ init_injected_mcp();
13122
14244
  estimateThreadChars,
13123
14245
  expandComposerPrompt,
13124
14246
  extractGhErrorDetail,
14247
+ extractPendingPlanQuestions,
14248
+ extractPresentedPlan,
13125
14249
  extractiveSummary,
13126
14250
  fetchPrHead,
13127
14251
  finalizeParts,
13128
14252
  findInvalidCacheControlTtlOrder,
13129
14253
  findOrphanWorktrees,
13130
14254
  findThreadByRef,
14255
+ findThreadForStackLayer,
13131
14256
  flattenTurnInput,
13132
14257
  forkChatTab,
13133
14258
  forkMessageSlice,
@@ -13138,6 +14263,8 @@ init_injected_mcp();
13138
14263
  formatGhLandError,
13139
14264
  formatIpcInvokeError,
13140
14265
  formatMessagesAsTranscript,
14266
+ formatPlanQuestionAnswers,
14267
+ formatPlanQuestionsForChat,
13141
14268
  formatRateLimitResetHint,
13142
14269
  formatRenameBranchDirective,
13143
14270
  formatTranscriptMarkdown,
@@ -13161,6 +14288,7 @@ init_injected_mcp();
13161
14288
  getPrChecks,
13162
14289
  getPrDetails,
13163
14290
  getPrMeta,
14291
+ getPrStack,
13164
14292
  getRepoSetupInfo,
13165
14293
  getRunMode,
13166
14294
  getRunScript,
@@ -13177,9 +14305,12 @@ init_injected_mcp();
13177
14305
  healOrchestrationSoccerTitles,
13178
14306
  importConductorWorkspace,
13179
14307
  importConductorWorkspaceAsync,
14308
+ initPrStack,
14309
+ initStackFromThread,
13180
14310
  initializeGitRepository,
13181
14311
  inspectGitWorktree,
13182
14312
  installAgent,
14313
+ isAskUserToolName,
13183
14314
  isBrightsyConnected,
13184
14315
  isBrightsyNdjsonLine,
13185
14316
  isCloudCoordinatorThread,
@@ -13189,14 +14320,17 @@ init_injected_mcp();
13189
14320
  isGlobalRepoPath,
13190
14321
  isGlobalThread,
13191
14322
  isImageFilePath,
14323
+ isInPrStack,
13192
14324
  isLinearConnected,
13193
14325
  isOrchestratorCapableAgent,
13194
14326
  isOrchestratorThread,
13195
14327
  isPidAlive,
13196
14328
  isPlaceholderBranch,
14329
+ isPresentPlanToolName,
13197
14330
  isSessionQuotaLimit,
13198
14331
  isSideboardScratchPath,
13199
14332
  isThinkingEffort,
14333
+ isWorkspaceScratchPath,
13200
14334
  listAgentSetupInfo,
13201
14335
  listBranchCommits,
13202
14336
  listBranches,
@@ -13233,6 +14367,7 @@ init_injected_mcp();
13233
14367
  mcpAllowTools,
13234
14368
  mcpAuthWarnings,
13235
14369
  mergePr,
14370
+ mergePrStack,
13236
14371
  mergeUsage,
13237
14372
  nextPastedTextName,
13238
14373
  nextThinkingEffort,
@@ -13242,6 +14377,8 @@ init_injected_mcp();
13242
14377
  normalizeTurnInput,
13243
14378
  normalizeWorktreePath,
13244
14379
  openInSystemTerminal,
14380
+ openPrStackLayers,
14381
+ openStackLayer,
13245
14382
  opencodeAdapter,
13246
14383
  orchestrationQuotaFallbackAgent,
13247
14384
  orchestrationQuotaOnLimit,
@@ -13250,15 +14387,19 @@ init_injected_mcp();
13250
14387
  originGhRepoEnv,
13251
14388
  parseCursorRunnerLine,
13252
14389
  parseForceStopMessage,
14390
+ parseGhStackViewJson,
13253
14391
  parseGithubSlugFromRemoteUrl,
13254
14392
  parseMcpList,
14393
+ parsePlanQuestionsInput,
13255
14394
  parseSessionQuotaResetAt,
13256
14395
  partsToAssistantText,
13257
14396
  pastedTextStats,
13258
14397
  permissionMode,
14398
+ planFileAbs,
13259
14399
  previewLand,
13260
14400
  pushBranch,
13261
14401
  readExistingReviewRequestFile,
14402
+ readPlanFile,
13262
14403
  readSkillBody,
13263
14404
  readThread,
13264
14405
  readWorktreeFile,
@@ -13270,6 +14411,7 @@ init_injected_mcp();
13270
14411
  repoSlug,
13271
14412
  requestReview,
13272
14413
  requireAgent,
14414
+ resetGhStackDetectCache,
13273
14415
  resolveClaudeExecutable,
13274
14416
  resolveConductorCursorAgentId,
13275
14417
  resolveCursorModelId,
@@ -13279,6 +14421,7 @@ init_injected_mcp();
13279
14421
  resolveFilesToCopy,
13280
14422
  resolveGhAuthToken,
13281
14423
  resolveGithubRepoSlug,
14424
+ resolvePlanMarkdown,
13282
14425
  resolvePrSelector,
13283
14426
  resolveQuotaFallbackAgent,
13284
14427
  resolveRepoRoot,
@@ -13306,12 +14449,16 @@ init_injected_mcp();
13306
14449
  slugify,
13307
14450
  spawnAgentTurn,
13308
14451
  splitForCompaction,
14452
+ stackAgentDefaultsFrom,
14453
+ stackIdFrom,
14454
+ stackMergeReadiness,
13309
14455
  stageAbsolutePathsAsAttachments,
13310
14456
  stageBuffersAsAttachments,
13311
14457
  startDevServer,
13312
14458
  startMcpServer,
13313
14459
  startOrchestration,
13314
14460
  stripBrightsyNdjsonNoise,
14461
+ submitPrStack,
13315
14462
  suggestSlug,
13316
14463
  summarizeConversation,
13317
14464
  switchBrightsyAccount,
@@ -13347,6 +14494,7 @@ init_injected_mcp();
13347
14494
  worktreeNameFromPath,
13348
14495
  worktreesRoot,
13349
14496
  writeInjectedMcpConfig,
14497
+ writePlanFile,
13350
14498
  writeThread,
13351
14499
  writeWorktreeFile
13352
14500
  });