@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.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  listWorkspaces,
5
5
  removeWorkspace,
6
6
  syncWorkspacesFromThreads
7
- } from "./chunk-E4VVEKAM.js";
7
+ } from "./chunk-GNML24AW.js";
8
8
  import {
9
9
  CLOUD_COORDINATOR_BUSY_REPLY,
10
10
  CLOUD_COORDINATOR_STOPPED_REPLY,
@@ -24,7 +24,7 @@ import {
24
24
  orchestratorSessionPoisonedByBuiltins,
25
25
  parseForceStopMessage,
26
26
  takenTeamSlugsForOrchestration
27
- } from "./chunk-O5DOO7DP.js";
27
+ } from "./chunk-XX5BB7NV.js";
28
28
  import {
29
29
  BRIGHTSY_MCP_ALLOWED_TOOLS,
30
30
  CLAUDE_MODEL_CATALOG,
@@ -70,7 +70,7 @@ import {
70
70
  resolveQuotaFallbackAgent,
71
71
  sanitizeMcpServerName,
72
72
  writeInjectedMcpConfig
73
- } from "./chunk-5ZPSH7VI.js";
73
+ } from "./chunk-D3METLRW.js";
74
74
  import {
75
75
  ORCHESTRATOR_AGENT_KINDS,
76
76
  assertOrchestratorCapableAgent,
@@ -89,6 +89,17 @@ import {
89
89
  saveBrightsyConfig,
90
90
  switchBrightsyAccount
91
91
  } from "./chunk-BXJ76RHF.js";
92
+ import {
93
+ LEGACY_PLAN_FILE_REL,
94
+ PLAN_FILE_NAME,
95
+ PLAN_FILE_REL,
96
+ extractPresentedPlan,
97
+ isPresentPlanToolName,
98
+ planFileAbs,
99
+ readPlanFile,
100
+ resolvePlanMarkdown,
101
+ writePlanFile
102
+ } from "./chunk-QTUESPAW.js";
92
103
  import {
93
104
  cursorSdkMessageToEvents,
94
105
  fallbackTurnFailDetail,
@@ -144,17 +155,21 @@ import {
144
155
  enrichWorkspacesWithGithub,
145
156
  ensureGlobalCoordinatorCwd,
146
157
  formatWorkspaceInventory
147
- } from "./chunk-K3WMKGFY.js";
158
+ } from "./chunk-LRLKJM3O.js";
148
159
  import {
149
160
  FAMOUS_SOCCER_TEAMS,
161
+ addPrStackLayer,
150
162
  allocateTeamName,
151
163
  allocateTeamSlug,
152
164
  branchDisplayLabel,
165
+ checkoutPrStackLayer,
153
166
  collectTakenTeamSlugs,
154
167
  commitAll,
168
+ createExistingBranchWorktree,
155
169
  createOrUpdatePr,
156
170
  createThreadWorktree,
157
171
  currentBranch,
172
+ detectGhStack,
158
173
  detectLocalMergeConflicts,
159
174
  ensureGhPreferOrigin,
160
175
  extractGhErrorDetail,
@@ -166,10 +181,13 @@ import {
166
181
  getPrChecks,
167
182
  getPrDetails,
168
183
  getPrMeta,
184
+ getPrStack,
169
185
  ghHeadRef,
170
186
  ghRepoSelectArgs,
187
+ initPrStack,
171
188
  isDirty,
172
189
  isGhRateLimitError,
190
+ isInPrStack,
173
191
  isPlaceholderBranch,
174
192
  isSideboardScratchPath,
175
193
  listBranches,
@@ -177,11 +195,14 @@ import {
177
195
  listWorktrees,
178
196
  lookupSoccerTeam,
179
197
  mergePr,
198
+ mergePrStack,
180
199
  normalizeWorktreePath,
181
200
  originGhRepoEnv,
201
+ parseGhStackViewJson,
182
202
  parseGithubSlugFromRemoteUrl,
183
203
  pushBranch,
184
204
  removeWorktree,
205
+ resetGhStackDetectCache,
185
206
  resolveDefaultBranch,
186
207
  resolveDiffBaseRef,
187
208
  resolveGithubRepoSlug,
@@ -189,13 +210,21 @@ import {
189
210
  resolveRepoRoot,
190
211
  resolveWorktreeStartPoint,
191
212
  slugify,
213
+ stackMergeReadiness,
214
+ submitPrStack,
192
215
  suggestSlug,
193
216
  takenSlugsFromThread,
194
217
  threadDisplayLabel,
195
218
  worktreeDisplayLabel,
196
219
  worktreeDisplayLabelForGroup,
197
220
  worktreeNameFromPath
198
- } from "./chunk-ZH5QZ4CR.js";
221
+ } from "./chunk-DZFH2KLT.js";
222
+ import {
223
+ ATTACHMENTS_DIR,
224
+ LEGACY_ATTACHMENTS_DIR,
225
+ attachmentsGitignoreBody,
226
+ isWorkspaceScratchPath
227
+ } from "./chunk-FKOIHGKV.js";
199
228
  import {
200
229
  appendMessage,
201
230
  createEmptyThread,
@@ -209,7 +238,7 @@ import {
209
238
  updateThread,
210
239
  withThreadLock,
211
240
  writeThread
212
- } from "./chunk-O6W3P7V3.js";
241
+ } from "./chunk-TSRXOSVD.js";
213
242
  import {
214
243
  THINKING_EFFORTS,
215
244
  isThinkingEffort,
@@ -458,6 +487,9 @@ function toolDescription(name, input) {
458
487
  if (/present_artifact$/i.test(name)) {
459
488
  return str(input?.title) ? `Present ${str(input?.title)}` : "Present artifact";
460
489
  }
490
+ if (/present_plan$/i.test(name)) {
491
+ return str(input?.title) ? `Plan ${str(input?.title)}` : "Present plan";
492
+ }
461
493
  if (/present_schema$/i.test(name)) {
462
494
  return str(input?.title) ? `Schema ${str(input?.title)}` : "Present schema";
463
495
  }
@@ -657,9 +689,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
657
689
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
658
690
  );
659
691
  }
660
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-ECYN2MKL.js");
692
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-MSX2K27Y.js");
661
693
  if (isGlobalThread2(thread)) {
662
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-WD7FAMA2.js");
694
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-S6JZD5EF.js");
663
695
  ensureGlobalCoordinatorCwd2();
664
696
  }
665
697
  if (isOrchestratorThread(thread)) {
@@ -2181,11 +2213,6 @@ var IMAGE_MIME_BY_EXT = {
2181
2213
  bmp: "image/bmp",
2182
2214
  ico: "image/x-icon"
2183
2215
  };
2184
- var ATTACHMENTS_DIR = ".sideboard/attachments";
2185
- var ATTACHMENTS_GITIGNORE = `# Sideboard review / composer attachments (local only)
2186
- *
2187
- !.gitignore
2188
- `;
2189
2216
  var MAX_INLINE_BYTES = 4e5;
2190
2217
  var MAX_PREVIEW_BYTES = 5e6;
2191
2218
  function fileExtension(filePath) {
@@ -2203,7 +2230,7 @@ function ensureAttachmentsDir(worktreePath) {
2203
2230
  mkdirSync3(dir, { recursive: true });
2204
2231
  const gi = join6(dir, ".gitignore");
2205
2232
  if (!existsSync6(gi)) {
2206
- writeFileSync2(gi, ATTACHMENTS_GITIGNORE, "utf8");
2233
+ writeFileSync2(gi, attachmentsGitignoreBody(), "utf8");
2207
2234
  }
2208
2235
  return dir;
2209
2236
  }
@@ -2853,7 +2880,7 @@ async function confirmLand(thread, opts) {
2853
2880
 
2854
2881
  // src/threads/create.ts
2855
2882
  import { existsSync as existsSync7 } from "fs";
2856
- async function createThread(input, onSetupLine) {
2883
+ async function createThread(input, _onSetupLine) {
2857
2884
  await requireAgent(input.agent);
2858
2885
  const repoPath = await resolveRepoRoot(input.repoPath);
2859
2886
  if (!existsSync7(repoPath)) {
@@ -2911,26 +2938,10 @@ async function createThread(input, onSetupLine) {
2911
2938
  });
2912
2939
  writeThread(thread);
2913
2940
  await ensureWorkspace(repoPath);
2914
- try {
2915
- let setup = await runSetupScript(repoPath, worktreePath, onSetupLine);
2916
- if (!setup.ran) {
2917
- setup = await runCursorWorktreeSetup(repoPath, worktreePath, onSetupLine);
2918
- }
2919
- if (setup.ran && setup.exitCode !== 0 && setup.exitCode !== null) {
2920
- updateThread(thread.id, {
2921
- lastError: `Setup exited ${setup.exitCode} (thread is still usable)`
2922
- });
2923
- }
2924
- } catch (err) {
2925
- const message = err instanceof Error ? err.message : String(err);
2926
- updateThread(thread.id, {
2927
- lastError: `Setup failed: ${message}`
2928
- });
2929
- }
2930
2941
  return readThread(thread.id) ?? thread;
2931
2942
  }
2932
2943
  async function listLinearIssues(agent, repoPath) {
2933
- const { getAdapter: getAdapter2 } = await import("./agents-5ROTZNCX.js");
2944
+ const { getAdapter: getAdapter2 } = await import("./agents-YKSS6VBO.js");
2934
2945
  await requireAgent(agent, { requireLinear: true });
2935
2946
  const adapter = getAdapter2(agent);
2936
2947
  if (!adapter.listLinearIssues) {
@@ -2973,7 +2984,9 @@ function worktreeBindingFrom(from) {
2973
2984
  sourceIsFork: from.sourceIsFork,
2974
2985
  parentThreadId: from.parentThreadId,
2975
2986
  prUrl: from.prUrl,
2976
- prTitle: from.prTitle
2987
+ prTitle: from.prTitle,
2988
+ stackId: from.stackId,
2989
+ stackLayer: from.stackLayer
2977
2990
  };
2978
2991
  }
2979
2992
  function threadsSharingWorktree(worktreePath) {
@@ -3086,11 +3099,305 @@ async function forkThreadWorktree(input, onSetupLine) {
3086
3099
  return thread;
3087
3100
  }
3088
3101
 
3102
+ // src/threads/stack-layers.ts
3103
+ import { existsSync as existsSync8 } from "fs";
3104
+ function stackIdFrom(stack) {
3105
+ if (stack.stackNumber != null) return `gh-stack-${stack.stackNumber}`;
3106
+ const key = stack.layers.map((l) => l.branchName).join("|");
3107
+ if (!key) return null;
3108
+ return `gh-stack-local-${hashShort(key)}`;
3109
+ }
3110
+ function hashShort(s) {
3111
+ let h = 0;
3112
+ for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
3113
+ return Math.abs(h).toString(36);
3114
+ }
3115
+ function requireThread3(idOrRef) {
3116
+ const thread = findThreadByRef(idOrRef) ?? readThread(idOrRef);
3117
+ if (!thread) throw new Error(`Thread not found: ${idOrRef}`);
3118
+ return thread;
3119
+ }
3120
+ function sanitizeSlugPart(name) {
3121
+ return name.trim().replace(/^refs\/heads\//, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "layer";
3122
+ }
3123
+ function layerSlug(stackId, layer) {
3124
+ const stackPart = stackId.replace(/^gh-stack-/, "s");
3125
+ return sanitizeSlugPart(`${stackPart}-L${layer.position}-${layer.branchName}`);
3126
+ }
3127
+ function findThreadForStackLayer(repoPath, stackId, layer) {
3128
+ const threads = listThreads({ includeArchived: false }).filter(
3129
+ (t) => t.repoPath === repoPath && t.stackId === stackId
3130
+ );
3131
+ const byLayer = threads.find((t) => t.stackLayer === layer.position);
3132
+ if (byLayer) return byLayer;
3133
+ return threads.find((t) => t.branchName === layer.branchName) ?? listThreads({ includeArchived: false }).find(
3134
+ (t) => t.repoPath === repoPath && t.branchName === layer.branchName
3135
+ ) ?? null;
3136
+ }
3137
+ async function openStackLayer(input, _onSetupLine) {
3138
+ await requireAgent(input.agent);
3139
+ const repoPath = await resolveRepoRoot(input.repoPath);
3140
+ const stackId = stackIdFrom(input.stack);
3141
+ if (!stackId) throw new Error("Cannot open stack layer without a stack id");
3142
+ const existing = findThreadForStackLayer(repoPath, stackId, input.layer);
3143
+ if (existing) {
3144
+ const patch = {};
3145
+ if (existing.stackId !== stackId) patch.stackId = stackId;
3146
+ if (existing.stackLayer !== input.layer.position) {
3147
+ patch.stackLayer = input.layer.position;
3148
+ }
3149
+ if (input.layer.prUrl && existing.prUrl !== input.layer.prUrl) {
3150
+ patch.prUrl = input.layer.prUrl;
3151
+ }
3152
+ if (input.layer.title && existing.prTitle !== input.layer.title) {
3153
+ patch.prTitle = input.layer.title;
3154
+ }
3155
+ if (Object.keys(patch).length > 0) updateThread(existing.id, patch);
3156
+ return {
3157
+ thread: readThread(existing.id) ?? existing,
3158
+ createdWorktree: false
3159
+ };
3160
+ }
3161
+ let worktreePath = null;
3162
+ let branchName = input.layer.branchName;
3163
+ let createdWorktree = false;
3164
+ const trees = await listWorktrees(repoPath);
3165
+ const checkedOut = trees.find((w) => w.branch === branchName);
3166
+ if (checkedOut?.path && existsSync8(checkedOut.path)) {
3167
+ if (input.reuseExistingWorktree !== false) {
3168
+ worktreePath = checkedOut.path;
3169
+ } else {
3170
+ throw new Error(
3171
+ `Branch ${branchName} is already checked out at ${checkedOut.path}`
3172
+ );
3173
+ }
3174
+ }
3175
+ if (!worktreePath) {
3176
+ const slug = layerSlug(stackId, input.layer);
3177
+ const created = await createExistingBranchWorktree({
3178
+ repoPath,
3179
+ branchName,
3180
+ slug
3181
+ });
3182
+ worktreePath = created.worktreePath;
3183
+ branchName = created.branchName;
3184
+ copyConfiguredFiles(repoPath, worktreePath);
3185
+ createdWorktree = true;
3186
+ }
3187
+ const title = input.layer.title?.trim() || (input.layer.prNumber != null ? `PR #${input.layer.prNumber}` : input.layer.branchName);
3188
+ const thread = createEmptyThread({
3189
+ title,
3190
+ userSetTitle: Boolean(input.layer.title?.trim()),
3191
+ sourceType: input.layer.prNumber != null ? "pr" : "branch",
3192
+ sourceRef: input.layer.prNumber != null ? String(input.layer.prNumber) : input.layer.branchName,
3193
+ branchName,
3194
+ worktreePath,
3195
+ repoPath,
3196
+ agent: input.agent,
3197
+ autonomy: input.autonomy ?? "default",
3198
+ model: input.model ?? null,
3199
+ effort: input.effort ?? "high",
3200
+ fast: Boolean(input.fast),
3201
+ planMode: Boolean(input.planMode),
3202
+ parentThreadId: input.parentThreadId ?? null,
3203
+ status: "idle",
3204
+ prUrl: input.layer.prUrl,
3205
+ prTitle: input.layer.title ?? null,
3206
+ stackId,
3207
+ stackLayer: input.layer.position
3208
+ });
3209
+ writeThread(thread);
3210
+ await ensureWorkspace(repoPath);
3211
+ return { thread: readThread(thread.id) ?? thread, createdWorktree };
3212
+ }
3213
+ async function openPrStackLayers(input, onSetupLine) {
3214
+ const from = requireThread3(input.threadRef);
3215
+ if (!from.worktreePath?.trim() || !from.repoPath?.trim()) {
3216
+ throw new Error("Thread has no worktree");
3217
+ }
3218
+ const stack = await getPrStack(from.worktreePath);
3219
+ if (!stack) throw new Error("Current branch is not part of a GitHub PR stack");
3220
+ const layers = input.layer != null ? stack.layers.filter((l) => l.position === input.layer) : stack.layers;
3221
+ if (!layers.length) {
3222
+ throw new Error(
3223
+ input.layer != null ? `No stack layer at position ${input.layer}` : "Stack has no layers"
3224
+ );
3225
+ }
3226
+ const threads = [];
3227
+ const createdThreadIds = [];
3228
+ for (const layer of layers) {
3229
+ const { thread, createdWorktree } = await openStackLayer(
3230
+ {
3231
+ repoPath: from.repoPath,
3232
+ stack,
3233
+ layer,
3234
+ agent: from.agent,
3235
+ autonomy: from.autonomy,
3236
+ model: from.model,
3237
+ effort: from.effort,
3238
+ fast: from.fast,
3239
+ planMode: from.planMode,
3240
+ parentThreadId: from.id
3241
+ },
3242
+ onSetupLine
3243
+ );
3244
+ threads.push(thread);
3245
+ if (createdWorktree) createdThreadIds.push(thread.id);
3246
+ }
3247
+ const stackId = stackIdFrom(stack);
3248
+ const current = stack.layers[stack.currentIndex];
3249
+ if (stackId) {
3250
+ updateThread(from.id, {
3251
+ stackId,
3252
+ stackLayer: current?.position ?? from.stackLayer
3253
+ });
3254
+ }
3255
+ return { stack, threads, createdThreadIds };
3256
+ }
3257
+ async function addStackLayerFromThread(input, onSetupLine) {
3258
+ const from = requireThread3(input.threadRef);
3259
+ if (!from.worktreePath?.trim() || !from.repoPath?.trim()) {
3260
+ throw new Error("Thread has no worktree");
3261
+ }
3262
+ const status = await detectGhStack(from.worktreePath);
3263
+ if (!status.available) throw new Error(status.reason);
3264
+ await addPrStackLayer(from.worktreePath, input.branchName);
3265
+ const stack = await getPrStack(from.worktreePath);
3266
+ if (!stack) throw new Error("Stack not found after adding layer");
3267
+ const layer = stack.layers.find((l) => l.branchName === input.branchName.trim()) ?? stack.layers[stack.layers.length - 1];
3268
+ if (!layer) throw new Error("New stack layer not found");
3269
+ if (input.title?.trim()) {
3270
+ layer.title = input.title.trim();
3271
+ }
3272
+ const thread = await openStackLayer(
3273
+ {
3274
+ repoPath: from.repoPath,
3275
+ stack,
3276
+ layer,
3277
+ agent: from.agent,
3278
+ autonomy: from.autonomy,
3279
+ model: from.model,
3280
+ effort: from.effort,
3281
+ fast: from.fast,
3282
+ planMode: from.planMode,
3283
+ parentThreadId: from.id
3284
+ },
3285
+ onSetupLine
3286
+ );
3287
+ return { stack, thread: thread.thread, createdWorktree: thread.createdWorktree };
3288
+ }
3289
+ async function initStackFromThread(input, onSetupLine) {
3290
+ const from = requireThread3(input.threadRef);
3291
+ if (!from.worktreePath?.trim() || !from.branchName?.trim() || !from.repoPath?.trim()) {
3292
+ throw new Error("Thread has no worktree/branch");
3293
+ }
3294
+ const status = await detectGhStack(from.worktreePath);
3295
+ if (!status.available) throw new Error(status.reason);
3296
+ const branches = [
3297
+ from.branchName,
3298
+ ...(input.additionalBranches ?? []).map((b) => b.trim()).filter(Boolean)
3299
+ ];
3300
+ await initPrStack(from.worktreePath, branches, {
3301
+ base: input.base ?? await resolveDefaultBranch(from.repoPath)
3302
+ });
3303
+ return openPrStackLayers({ threadRef: from.id }, onSetupLine);
3304
+ }
3305
+ async function createPrStack(input, onSetupLine) {
3306
+ await requireAgent(input.agent);
3307
+ const repoPath = await resolveRepoRoot(input.repoPath);
3308
+ if (!existsSync8(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
3309
+ if (!input.branches.length) throw new Error("At least one branch name required");
3310
+ const status = await detectGhStack(repoPath);
3311
+ if (!status.available) throw new Error(status.reason);
3312
+ const team = allocateTeamSlug(repoPath);
3313
+ const base = input.base ?? await resolveDefaultBranch(repoPath);
3314
+ const bootstrap = await createThreadWorktree({
3315
+ repoPath,
3316
+ sourceRef: base,
3317
+ slug: `${team.slug}-stack-init`
3318
+ });
3319
+ copyConfiguredFiles(repoPath, bootstrap.worktreePath);
3320
+ try {
3321
+ await initPrStack(bootstrap.worktreePath, input.branches, { base });
3322
+ const bottom = input.branches[0];
3323
+ const co = await git(["checkout", bottom], bootstrap.worktreePath, {
3324
+ reject: false
3325
+ });
3326
+ if (co.exitCode !== 0) {
3327
+ throw new Error(
3328
+ co.stderr.trim() || co.stdout.trim() || `Could not check out ${bottom} after stack init`
3329
+ );
3330
+ }
3331
+ } catch (err) {
3332
+ try {
3333
+ await removeWorktree(repoPath, bootstrap.worktreePath, {
3334
+ deleteBranch: bootstrap.branchName
3335
+ });
3336
+ } catch {
3337
+ }
3338
+ throw err;
3339
+ }
3340
+ const stack = await getPrStack(bootstrap.worktreePath);
3341
+ if (!stack) {
3342
+ try {
3343
+ await removeWorktree(repoPath, bootstrap.worktreePath, {
3344
+ deleteBranch: bootstrap.branchName
3345
+ });
3346
+ } catch {
3347
+ }
3348
+ throw new Error("Stack init succeeded but gh stack view returned no stack");
3349
+ }
3350
+ const threads = [];
3351
+ const createdThreadIds = [];
3352
+ for (const layer of stack.layers) {
3353
+ const title = layer.position === 1 && input.title?.trim() ? input.title.trim() : layer.title;
3354
+ const opened = await openStackLayer(
3355
+ {
3356
+ repoPath,
3357
+ stack,
3358
+ layer: title ? { ...layer, title } : layer,
3359
+ agent: input.agent,
3360
+ autonomy: input.autonomy,
3361
+ model: input.model,
3362
+ effort: input.effort,
3363
+ fast: input.fast,
3364
+ planMode: input.planMode,
3365
+ reuseExistingWorktree: true
3366
+ },
3367
+ onSetupLine
3368
+ );
3369
+ threads.push(opened.thread);
3370
+ if (opened.createdWorktree || opened.thread.worktreePath === bootstrap.worktreePath) {
3371
+ createdThreadIds.push(opened.thread.id);
3372
+ }
3373
+ }
3374
+ const claimed = new Set(threads.map((t) => t.worktreePath));
3375
+ if (!claimed.has(bootstrap.worktreePath) && existsSync8(bootstrap.worktreePath)) {
3376
+ try {
3377
+ await removeWorktree(repoPath, bootstrap.worktreePath, {
3378
+ deleteBranch: bootstrap.branchName
3379
+ });
3380
+ } catch {
3381
+ }
3382
+ }
3383
+ return { stack, threads, createdThreadIds };
3384
+ }
3385
+ function stackAgentDefaultsFrom(input) {
3386
+ return {
3387
+ agent: input.agent,
3388
+ autonomy: input.autonomy,
3389
+ model: input.model,
3390
+ effort: input.effort,
3391
+ fast: input.fast,
3392
+ planMode: input.planMode
3393
+ };
3394
+ }
3395
+
3089
3396
  // src/threads/adopt.ts
3090
3397
  import { execFileSync } from "child_process";
3091
3398
  import {
3092
3399
  copyFileSync as copyFileSync3,
3093
- existsSync as existsSync8,
3400
+ existsSync as existsSync9,
3094
3401
  mkdtempSync,
3095
3402
  readdirSync as readdirSync3,
3096
3403
  readFileSync as readFileSync7,
@@ -3118,7 +3425,7 @@ function mapAgentType(raw) {
3118
3425
  return null;
3119
3426
  }
3120
3427
  function resolveConductorCursorAgentId(workspacePath) {
3121
- if (!workspacePath || !existsSync8(CURSOR_SDK_STORE)) return null;
3428
+ if (!workspacePath || !existsSync9(CURSOR_SDK_STORE)) return null;
3122
3429
  const normalized = workspacePath.replace(/\/$/, "");
3123
3430
  let best = null;
3124
3431
  let hashes;
@@ -3129,7 +3436,7 @@ function resolveConductorCursorAgentId(workspacePath) {
3129
3436
  }
3130
3437
  for (const hash of hashes) {
3131
3438
  const agentsFile = join7(CURSOR_SDK_STORE, hash, "agents.ndjson");
3132
- if (!existsSync8(agentsFile)) continue;
3439
+ if (!existsSync9(agentsFile)) continue;
3133
3440
  let text;
3134
3441
  try {
3135
3442
  text = readFileSync7(agentsFile, "utf8");
@@ -3156,7 +3463,7 @@ function resolveConductorCursorAgentId(workspacePath) {
3156
3463
  return best?.agentId ?? null;
3157
3464
  }
3158
3465
  async function adoptThread(input) {
3159
- if (!existsSync8(input.worktreePath)) {
3466
+ if (!existsSync9(input.worktreePath)) {
3160
3467
  throw new Error(`Worktree not found: ${input.worktreePath}`);
3161
3468
  }
3162
3469
  const repoPath = await resolveRepoRoot(input.worktreePath);
@@ -3175,7 +3482,7 @@ async function adoptThread(input) {
3175
3482
  messages: input.messages ?? []
3176
3483
  });
3177
3484
  writeThread(thread);
3178
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-ZWOOFZUV.js");
3485
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-AYTBR6KQ.js");
3179
3486
  await ensureWorkspace2(repoPath);
3180
3487
  return thread;
3181
3488
  }
@@ -3183,7 +3490,7 @@ function conductorDbPath() {
3183
3490
  return CONDUCTOR_DB;
3184
3491
  }
3185
3492
  function listConductorWorkspaces() {
3186
- if (!existsSync8(CONDUCTOR_DB)) {
3493
+ if (!existsSync9(CONDUCTOR_DB)) {
3187
3494
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
3188
3495
  }
3189
3496
  const tmp = mkdtempSync(join7(tmpdir(), "sideboard-conductor-"));
@@ -3192,7 +3499,7 @@ function listConductorWorkspaces() {
3192
3499
  copyFileSync3(CONDUCTOR_DB, snapshot);
3193
3500
  for (const suffix of ["-wal", "-shm"]) {
3194
3501
  const src = `${CONDUCTOR_DB}${suffix}`;
3195
- if (existsSync8(src)) {
3502
+ if (existsSync9(src)) {
3196
3503
  try {
3197
3504
  copyFileSync3(src, `${snapshot}${suffix}`);
3198
3505
  } catch {
@@ -3274,7 +3581,7 @@ function listConductorWorkspaces() {
3274
3581
  }
3275
3582
  }
3276
3583
  function importConductorWorkspace(workspaceId) {
3277
- if (!existsSync8(CONDUCTOR_DB)) {
3584
+ if (!existsSync9(CONDUCTOR_DB)) {
3278
3585
  throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
3279
3586
  }
3280
3587
  const tmp = mkdtempSync(join7(tmpdir(), "sideboard-conductor-"));
@@ -3283,7 +3590,7 @@ function importConductorWorkspace(workspaceId) {
3283
3590
  copyFileSync3(CONDUCTOR_DB, snapshot);
3284
3591
  for (const suffix of ["-wal", "-shm"]) {
3285
3592
  const src = `${CONDUCTOR_DB}${suffix}`;
3286
- if (existsSync8(src)) {
3593
+ if (existsSync9(src)) {
3287
3594
  try {
3288
3595
  copyFileSync3(src, `${snapshot}${suffix}`);
3289
3596
  } catch {
@@ -3303,7 +3610,7 @@ function importConductorWorkspace(workspaceId) {
3303
3610
  ).get(workspaceId);
3304
3611
  if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
3305
3612
  const worktreePath = String(row.workspacePath);
3306
- if (!existsSync8(worktreePath)) {
3613
+ if (!existsSync9(worktreePath)) {
3307
3614
  throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
3308
3615
  }
3309
3616
  let sessionId = null;
@@ -3375,10 +3682,10 @@ async function importConductorWorkspaceAsync(workspaceId) {
3375
3682
 
3376
3683
  // src/orchestrator/orchestrator.ts
3377
3684
  import { EventEmitter } from "events";
3378
- import { existsSync as existsSync12 } from "fs";
3685
+ import { existsSync as existsSync13 } from "fs";
3379
3686
 
3380
3687
  // src/git/orphan-cleanup.ts
3381
- import { existsSync as existsSync9, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
3688
+ import { existsSync as existsSync10, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
3382
3689
  import { join as join8 } from "path";
3383
3690
  function isSideboardWorktreePath(path) {
3384
3691
  return path.includes("/.sideboard/worktrees/") || path.includes("/sideboard/workspaces/");
@@ -3390,7 +3697,7 @@ async function findOrphanWorktrees(repoPaths) {
3390
3697
  repoPaths?.length ? repoPaths : threads.map((t) => t.repoPath).filter(Boolean)
3391
3698
  );
3392
3699
  const homeRoot = sideboardWorkspacesDir();
3393
- if (existsSync9(homeRoot)) {
3700
+ if (existsSync10(homeRoot)) {
3394
3701
  try {
3395
3702
  for (const entry of readdirSync4(homeRoot, { withFileTypes: true })) {
3396
3703
  if (!entry.isDirectory()) continue;
@@ -3402,7 +3709,7 @@ async function findOrphanWorktrees(repoPaths) {
3402
3709
  const orphans = [];
3403
3710
  const seen = /* @__PURE__ */ new Set();
3404
3711
  for (const repoPath of repos) {
3405
- if (!repoPath || !existsSync9(repoPath)) continue;
3712
+ if (!repoPath || !existsSync10(repoPath)) continue;
3406
3713
  try {
3407
3714
  const wts = await listWorktrees(repoPath);
3408
3715
  for (const wt of wts) {
@@ -3423,12 +3730,12 @@ async function findOrphanWorktrees(repoPaths) {
3423
3730
  }
3424
3731
  try {
3425
3732
  const root = worktreesRoot(repoPath);
3426
- if (existsSync9(root)) {
3733
+ if (existsSync10(root)) {
3427
3734
  for (const entry of readdirSync4(root, { withFileTypes: true })) {
3428
3735
  if (!entry.isDirectory()) continue;
3429
3736
  const path = join8(root, entry.name).replace(/\/$/, "");
3430
3737
  if (known.has(path) || seen.has(path)) continue;
3431
- if (!existsSync9(join8(path, ".git"))) continue;
3738
+ if (!existsSync10(join8(path, ".git"))) continue;
3432
3739
  seen.add(path);
3433
3740
  let mtimeMs = 0;
3434
3741
  try {
@@ -3577,7 +3884,7 @@ async function applyThreadIntoMain(thread, opts) {
3577
3884
  }
3578
3885
 
3579
3886
  // src/git/clone-repo.ts
3580
- import { existsSync as existsSync10 } from "fs";
3887
+ import { existsSync as existsSync11 } from "fs";
3581
3888
  import { basename as basename3, join as join9 } from "path";
3582
3889
  import { execa as execa5 } from "execa";
3583
3890
  async function cloneRepoIntoSideboard(opts) {
@@ -3590,7 +3897,7 @@ async function cloneRepoIntoSideboard(opts) {
3590
3897
  }
3591
3898
  name = name.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
3592
3899
  const dest = join9(sideboardReposDir(), name);
3593
- if (existsSync10(dest)) {
3900
+ if (existsSync11(dest)) {
3594
3901
  const repoPath2 = await resolveRepoRoot(dest);
3595
3902
  const workspace2 = await ensureWorkspace(repoPath2);
3596
3903
  return { repoPath: repoPath2, workspace: workspace2 };
@@ -3608,7 +3915,7 @@ async function cloneRepoIntoSideboard(opts) {
3608
3915
 
3609
3916
  // src/review/request-review.ts
3610
3917
  import { randomUUID as randomUUID4 } from "crypto";
3611
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
3918
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
3612
3919
  import { dirname as dirname3, join as join10 } from "path";
3613
3920
 
3614
3921
  // src/review/review-request-template.ts
@@ -3733,13 +4040,10 @@ File: src/client/frontends/desktop/core/UserData.ts
3733
4040
  // src/review/request-review.ts
3734
4041
  var REPO_REVIEW_PATH = ".sideboard/review.md";
3735
4042
  var REPO_REVIEW_NAME = "review.md";
3736
- var REVIEW_REQUEST_PATH = ".sideboard/attachments/Review request.md";
4043
+ var REVIEW_REQUEST_PATH = `${ATTACHMENTS_DIR}/Review request.md`;
4044
+ var LEGACY_REVIEW_REQUEST_PATH = `${LEGACY_ATTACHMENTS_DIR}/Review request.md`;
3737
4045
  var REVIEW_REQUEST_NAME = "Review request.md";
3738
4046
  var REVIEW_REQUEST_PREFILL = "Review.";
3739
- var ATTACHMENTS_GITIGNORE2 = `# Sideboard review / composer attachments (local only)
3740
- *
3741
- !.gitignore
3742
- `;
3743
4047
  var LEGACY_REVIEW_TEMPLATE_MARKERS = [
3744
4048
  "You are acting as a reviewer for a proposed code change made by another engineer.",
3745
4049
  "HOW MANY FINDINGS TO RETURN:"
@@ -3753,7 +4057,7 @@ function shouldRefreshReviewRequestTemplate(content) {
3753
4057
  return LEGACY_REVIEW_TEMPLATE_MARKERS.every((m) => trimmed.includes(m));
3754
4058
  }
3755
4059
  function readTextIfPresent(abs) {
3756
- if (!existsSync11(abs)) return null;
4060
+ if (!existsSync12(abs)) return null;
3757
4061
  try {
3758
4062
  const content = readFileSync8(abs, "utf8");
3759
4063
  return content.trim() ? content : null;
@@ -3762,10 +4066,10 @@ function readTextIfPresent(abs) {
3762
4066
  }
3763
4067
  }
3764
4068
  function ensureAttachmentsGitignore(worktreePath) {
3765
- const gitignoreAbs = join10(worktreePath, ".sideboard", "attachments", ".gitignore");
3766
- if (existsSync11(gitignoreAbs)) return;
4069
+ const gitignoreAbs = join10(worktreePath, ATTACHMENTS_DIR, ".gitignore");
4070
+ if (existsSync12(gitignoreAbs)) return;
3767
4071
  mkdirSync4(dirname3(gitignoreAbs), { recursive: true });
3768
- writeFileSync3(gitignoreAbs, ATTACHMENTS_GITIGNORE2, "utf8");
4072
+ writeFileSync3(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
3769
4073
  }
3770
4074
  function resolveReviewGuidelines(worktreePath) {
3771
4075
  const repoAbs = join10(worktreePath, REPO_REVIEW_PATH);
@@ -3788,6 +4092,16 @@ function resolveReviewGuidelines(worktreePath) {
3788
4092
  source: "local"
3789
4093
  };
3790
4094
  }
4095
+ const legacyAbs = join10(worktreePath, LEGACY_REVIEW_REQUEST_PATH);
4096
+ const legacyContent = readTextIfPresent(legacyAbs);
4097
+ if (legacyContent && !shouldRefreshReviewRequestTemplate(legacyContent)) {
4098
+ return {
4099
+ path: LEGACY_REVIEW_REQUEST_PATH,
4100
+ name: REVIEW_REQUEST_NAME,
4101
+ content: legacyContent,
4102
+ source: "local"
4103
+ };
4104
+ }
3791
4105
  ensureAttachmentsGitignore(worktreePath);
3792
4106
  mkdirSync4(dirname3(localAbs), { recursive: true });
3793
4107
  writeFileSync3(localAbs, REVIEW_REQUEST_TEMPLATE, "utf8");
@@ -3810,10 +4124,11 @@ function ensureReviewRequestFile(worktreePath) {
3810
4124
  };
3811
4125
  }
3812
4126
  const localAbs = join10(worktreePath, REVIEW_REQUEST_PATH);
3813
- const localContent = readTextIfPresent(localAbs);
4127
+ const localContent = readTextIfPresent(localAbs) ?? readTextIfPresent(join10(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
3814
4128
  if (localContent && !shouldRefreshReviewRequestTemplate(localContent)) {
4129
+ const path = existsSync12(localAbs) ? REVIEW_REQUEST_PATH : LEGACY_REVIEW_REQUEST_PATH;
3815
4130
  return {
3816
- path: REVIEW_REQUEST_PATH,
4131
+ path,
3817
4132
  name: REVIEW_REQUEST_NAME,
3818
4133
  content: localContent,
3819
4134
  source: "local"
@@ -3840,7 +4155,7 @@ function buildReviewRequestAttachment(content, opts) {
3840
4155
  };
3841
4156
  }
3842
4157
  function readExistingReviewRequestFile(worktreePath) {
3843
- return readTextIfPresent(join10(worktreePath, REPO_REVIEW_PATH)) ?? readTextIfPresent(join10(worktreePath, REVIEW_REQUEST_PATH));
4158
+ return readTextIfPresent(join10(worktreePath, REPO_REVIEW_PATH)) ?? readTextIfPresent(join10(worktreePath, REVIEW_REQUEST_PATH)) ?? readTextIfPresent(join10(worktreePath, LEGACY_REVIEW_REQUEST_PATH));
3844
4159
  }
3845
4160
  async function requestReview(threadRef, send) {
3846
4161
  const from = findThreadByRef(threadRef);
@@ -4084,7 +4399,7 @@ var Orchestrator = class {
4084
4399
  }
4085
4400
  continue;
4086
4401
  }
4087
- if (!existsSync12(thread.worktreePath)) {
4402
+ if (!existsSync13(thread.worktreePath)) {
4088
4403
  setStatus(thread.id, "broken", "Worktree missing on disk");
4089
4404
  this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
4090
4405
  continue;
@@ -4229,14 +4544,9 @@ var Orchestrator = class {
4229
4544
  return findThreadByRef(idOrRef) ?? readThread(idOrRef);
4230
4545
  }
4231
4546
  async createThread(input) {
4232
- let thread = await createThread(input, (line) => {
4233
- this.emit({
4234
- type: "turn_output",
4235
- threadId: "pending",
4236
- event: { type: "stdout", data: line }
4237
- });
4238
- });
4547
+ let thread = await createThread(input);
4239
4548
  this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
4549
+ await this.runSetupAfterCreate(thread.id);
4240
4550
  const { autoRunAfterSetupEnabled: autoRunAfterSetupEnabled2 } = await import("./app-settings-LZP632KI.js");
4241
4551
  if (autoRunAfterSetupEnabled2()) {
4242
4552
  try {
@@ -4250,6 +4560,19 @@ var Orchestrator = class {
4250
4560
  }
4251
4561
  return thread;
4252
4562
  }
4563
+ /** Run workspace setup after a new worktree is created (no-op if none configured). */
4564
+ async runSetupAfterCreate(threadId) {
4565
+ try {
4566
+ await this.runSetup(threadId);
4567
+ } catch (err) {
4568
+ const message = err instanceof Error ? err.message : String(err);
4569
+ if (/no setup script/i.test(message)) return;
4570
+ if (/already running/i.test(message)) return;
4571
+ updateThread(threadId, {
4572
+ lastError: `Setup failed: ${message}`
4573
+ });
4574
+ }
4575
+ }
4253
4576
  listWorkspaces() {
4254
4577
  const fromThreads = listThreads({ includeArchived: false }).map((t) => t.repoPath);
4255
4578
  return syncWorkspacesFromThreads(fromThreads);
@@ -4598,6 +4921,19 @@ var Orchestrator = class {
4598
4921
  });
4599
4922
  }
4600
4923
  const afterTurn = this.requireThread(threadId);
4924
+ if (afterTurn.planMode && afterTurn.worktreePath?.trim()) {
4925
+ const presented = extractPresentedPlan(parts);
4926
+ const exited = parts.some(
4927
+ (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
4928
+ );
4929
+ if (presented?.content) {
4930
+ writePlanFile(afterTurn.worktreePath, presented.content);
4931
+ } else if (exited || chatText && chatText.trim().length >= 400) {
4932
+ if (!readPlanFile(afterTurn.worktreePath) && chatText?.trim()) {
4933
+ writePlanFile(afterTurn.worktreePath, chatText.trim());
4934
+ }
4935
+ }
4936
+ }
4601
4937
  if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
4602
4938
  (p) => p.type === "tool" && /exitplanmode/i.test(p.name)
4603
4939
  )) {
@@ -5079,6 +5415,82 @@ var Orchestrator = class {
5079
5415
  }
5080
5416
  return meta;
5081
5417
  }
5418
+ async getPrStack(threadRef) {
5419
+ const thread = this.requireThread(threadRef);
5420
+ if (!thread.worktreePath?.trim()) return null;
5421
+ const stack = await getPrStack(thread.worktreePath);
5422
+ if (!stack) return null;
5423
+ const current = stack.currentIndex >= 0 ? stack.layers[stack.currentIndex] : null;
5424
+ const patch = {};
5425
+ if (stack.stackNumber != null) {
5426
+ const id = `gh-stack-${stack.stackNumber}`;
5427
+ if (thread.stackId !== id) patch.stackId = id;
5428
+ }
5429
+ if (current?.position != null && thread.stackLayer !== current.position) {
5430
+ patch.stackLayer = current.position;
5431
+ }
5432
+ if (current?.prUrl && current.prUrl !== thread.prUrl) patch.prUrl = current.prUrl;
5433
+ if (current?.title && current.title !== thread.prTitle) patch.prTitle = current.title;
5434
+ if (current?.branchName && current.branchName !== thread.branchName) {
5435
+ patch.branchName = current.branchName;
5436
+ }
5437
+ if (Object.keys(patch).length > 0) updateThread(thread.id, patch);
5438
+ return stack;
5439
+ }
5440
+ /** Open worktrees for all (or one) stack layers discovered from a thread. */
5441
+ async openPrStackLayers(threadRef, opts) {
5442
+ const result = await openPrStackLayers({ threadRef, layer: opts?.layer });
5443
+ for (const t of result.threads) {
5444
+ this.emit({ type: "status_changed", threadId: t.id, status: t.status });
5445
+ }
5446
+ for (const id of result.createdThreadIds) {
5447
+ await this.runSetupAfterCreate(id);
5448
+ }
5449
+ return { stack: result.stack, threads: result.threads };
5450
+ }
5451
+ /** Add a branch on top of the thread's stack and open its worktree. */
5452
+ async addStackLayer(threadRef, branchName, opts) {
5453
+ const result = await addStackLayerFromThread({
5454
+ threadRef,
5455
+ branchName,
5456
+ title: opts?.title
5457
+ });
5458
+ this.emit({
5459
+ type: "status_changed",
5460
+ threadId: result.thread.id,
5461
+ status: result.thread.status
5462
+ });
5463
+ if (result.createdWorktree) {
5464
+ await this.runSetupAfterCreate(result.thread.id);
5465
+ }
5466
+ return { stack: result.stack, thread: result.thread };
5467
+ }
5468
+ /** Initialize a stack from the current thread branch (optional extra layers). */
5469
+ async initStackFromThread(threadRef, opts) {
5470
+ const result = await initStackFromThread({
5471
+ threadRef,
5472
+ additionalBranches: opts?.additionalBranches,
5473
+ base: opts?.base
5474
+ });
5475
+ for (const t of result.threads) {
5476
+ this.emit({ type: "status_changed", threadId: t.id, status: t.status });
5477
+ }
5478
+ for (const id of result.createdThreadIds) {
5479
+ await this.runSetupAfterCreate(id);
5480
+ }
5481
+ return { stack: result.stack, threads: result.threads };
5482
+ }
5483
+ /** Create a new multi-layer stack with one worktree per layer. */
5484
+ async createPrStack(input) {
5485
+ const result = await createPrStack(input);
5486
+ for (const t of result.threads) {
5487
+ this.emit({ type: "status_changed", threadId: t.id, status: t.status });
5488
+ }
5489
+ for (const id of result.createdThreadIds) {
5490
+ await this.runSetupAfterCreate(id);
5491
+ }
5492
+ return { stack: result.stack, threads: result.threads };
5493
+ }
5082
5494
  async getPrDetails(threadRef) {
5083
5495
  const { thread, selector, cwd } = await this.withPrSelector(threadRef);
5084
5496
  if (!selector) return null;
@@ -5141,14 +5553,9 @@ var Orchestrator = class {
5141
5553
  return forkChatTab(input);
5142
5554
  }
5143
5555
  async forkThreadWorktree(input) {
5144
- const thread = await forkThreadWorktree(input, (line) => {
5145
- this.emit({
5146
- type: "turn_output",
5147
- threadId: "pending",
5148
- event: { type: "stdout", data: line }
5149
- });
5150
- });
5556
+ const thread = await forkThreadWorktree(input);
5151
5557
  this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
5558
+ await this.runSetupAfterCreate(thread.id);
5152
5559
  return thread;
5153
5560
  }
5154
5561
  renameThread(threadRef, title) {
@@ -5162,7 +5569,7 @@ var Orchestrator = class {
5162
5569
  }
5163
5570
  /**
5164
5571
  * Stage OS / worktree files into composer attachments (copies external files
5165
- * into `.sideboard/attachments/` so agents can Read images and binaries).
5572
+ * into `.context/attachments/` so agents can Read images and binaries).
5166
5573
  */
5167
5574
  attachComposerFiles(threadRef, opts) {
5168
5575
  const thread = this.requireThread(threadRef);
@@ -5236,8 +5643,8 @@ var Orchestrator = class {
5236
5643
  updateThread(thread.id, { worktreePath: globalAgentCwd2() });
5237
5644
  return setStatus(thread.id, "idle");
5238
5645
  }
5239
- if (!existsSync12(thread.worktreePath)) {
5240
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-GDV56MX4.js");
5646
+ if (!existsSync13(thread.worktreePath)) {
5647
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-RWGL7FUV.js");
5241
5648
  const { execa: execa6 } = await import("execa");
5242
5649
  const slug = thread.worktreePath.split("/").pop();
5243
5650
  const dest = thread.worktreePath;
@@ -5322,7 +5729,7 @@ async function startOrchestration(opts) {
5322
5729
  sourceRef: "default",
5323
5730
  ...createOpts
5324
5731
  }).catch(async () => {
5325
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-GDV56MX4.js");
5732
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-RWGL7FUV.js");
5326
5733
  const repo = await resolveRepoRoot2(repoPath);
5327
5734
  const def = await resolveDefaultBranch2(repo);
5328
5735
  return createThread({
@@ -5332,7 +5739,7 @@ async function startOrchestration(opts) {
5332
5739
  repoPath: repo
5333
5740
  });
5334
5741
  });
5335
- const { updateThread: upd } = await import("./thread-store-OV2X6PYO.js");
5742
+ const { updateThread: upd } = await import("./thread-store-UJIGMI5J.js");
5336
5743
  const updated = upd(thread.id, {
5337
5744
  sourceType: "orchestration",
5338
5745
  sourceRef: goal
@@ -5343,6 +5750,104 @@ async function startOrchestration(opts) {
5343
5750
  return updated;
5344
5751
  }
5345
5752
 
5753
+ // src/plan/ask-user.ts
5754
+ function asRecord2(v) {
5755
+ return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
5756
+ }
5757
+ function parseOptions(raw) {
5758
+ if (!Array.isArray(raw)) return [];
5759
+ const out = [];
5760
+ for (const item of raw) {
5761
+ if (typeof item === "string" && item.trim()) {
5762
+ out.push({ label: item.trim() });
5763
+ continue;
5764
+ }
5765
+ const o = asRecord2(item);
5766
+ if (!o) continue;
5767
+ const label = typeof o.label === "string" ? o.label.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.title === "string" ? o.title.trim() : "";
5768
+ if (!label) continue;
5769
+ const description = typeof o.description === "string" && o.description.trim() ? o.description.trim() : void 0;
5770
+ out.push({ label, description });
5771
+ }
5772
+ return out;
5773
+ }
5774
+ function parseOneQuestion(raw) {
5775
+ const o = asRecord2(raw);
5776
+ if (!o) return null;
5777
+ const question = typeof o.question === "string" ? o.question.trim() : typeof o.text === "string" ? o.text.trim() : typeof o.prompt === "string" ? o.prompt.trim() : "";
5778
+ if (!question) return null;
5779
+ const options = parseOptions(o.options);
5780
+ if (options.length < 1) return null;
5781
+ const header = typeof o.header === "string" && o.header.trim() ? o.header.trim() : void 0;
5782
+ const multiSelect = Boolean(o.multiSelect ?? o.multi_select);
5783
+ return { question, header, multiSelect, options };
5784
+ }
5785
+ function parsePlanQuestionsInput(input) {
5786
+ const root = asRecord2(input);
5787
+ if (!root) return [];
5788
+ const list = Array.isArray(root.questions) ? root.questions : Array.isArray(root.question) ? root.question : root.question || root.prompt ? [root] : [];
5789
+ const out = [];
5790
+ for (const item of list) {
5791
+ const q = parseOneQuestion(item);
5792
+ if (q) out.push(q);
5793
+ }
5794
+ return out;
5795
+ }
5796
+ var ASK_USER_TOOL_RE = /^(AskUserQuestion|ask_user|mcp__sideboard__ask_user)$/i;
5797
+ function isAskUserToolName(name) {
5798
+ if (!name) return false;
5799
+ const base = name.replace(/^mcp__sideboard__/i, "");
5800
+ return ASK_USER_TOOL_RE.test(name) || /^ask_user$/i.test(base);
5801
+ }
5802
+ function extractPendingPlanQuestions(parts) {
5803
+ if (!parts?.length) return null;
5804
+ for (let i = parts.length - 1; i >= 0; i--) {
5805
+ const p = parts[i];
5806
+ if (p.type !== "tool" || !isAskUserToolName(p.name)) continue;
5807
+ const questions = parsePlanQuestionsInput(p.input);
5808
+ if (!questions.length) continue;
5809
+ return {
5810
+ id: p.id || `ask-${i}`,
5811
+ questions,
5812
+ source: p.name || "ask_user"
5813
+ };
5814
+ }
5815
+ return null;
5816
+ }
5817
+ function formatPlanQuestionAnswers(questions, answers) {
5818
+ const lines = ["Answers to your questions:"];
5819
+ for (let i = 0; i < questions.length; i++) {
5820
+ const q = questions[i];
5821
+ const a = answers.find((x) => x.questionIndex === i);
5822
+ const header = q.header ? `**${q.header}** \u2014 ` : "";
5823
+ const parts = [];
5824
+ if (a?.selected.length) parts.push(a.selected.join(", "));
5825
+ if (a?.other?.trim()) parts.push(a.other.trim());
5826
+ const body = parts.length ? parts.join(" \xB7 ") : "(no answer)";
5827
+ lines.push(`${i + 1}. ${header}${q.question}`);
5828
+ lines.push(` \u2192 ${body}`);
5829
+ }
5830
+ return lines.join("\n");
5831
+ }
5832
+ function formatPlanQuestionsForChat(questions) {
5833
+ const lines = ["### Questions for you", ""];
5834
+ for (let i = 0; i < questions.length; i++) {
5835
+ const q = questions[i];
5836
+ const header = q.header ? `**${q.header}** \u2014 ` : "";
5837
+ lines.push(`${i + 1}. ${header}${q.question}`);
5838
+ for (let oi = 0; oi < q.options.length; oi++) {
5839
+ const opt = q.options[oi];
5840
+ const desc = opt.description?.trim();
5841
+ lines.push(
5842
+ desc ? ` - **${opt.label}** \u2014 ${desc}` : ` - **${opt.label}**`
5843
+ );
5844
+ }
5845
+ lines.push("");
5846
+ }
5847
+ lines.push("_Answer in the composer below._");
5848
+ return lines.join("\n").trimEnd();
5849
+ }
5850
+
5346
5851
  // src/mcp/server.ts
5347
5852
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5348
5853
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -5454,6 +5959,59 @@ async function startMcpServer() {
5454
5959
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
5455
5960
  }
5456
5961
  );
5962
+ server.tool(
5963
+ "ask_user",
5964
+ "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.",
5965
+ {
5966
+ questions: z.array(
5967
+ z.object({
5968
+ question: z.string().describe("Full question text ending with ?"),
5969
+ header: z.string().max(24).optional().describe("Short label shown above the question"),
5970
+ multiSelect: z.boolean().optional().describe("Allow selecting multiple options"),
5971
+ options: z.array(
5972
+ z.object({
5973
+ label: z.string(),
5974
+ description: z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
5975
+ })
5976
+ ).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
5977
+ })
5978
+ ).min(1).max(4).describe("1\u20134 questions")
5979
+ },
5980
+ async ({ questions }) => {
5981
+ const payload = {
5982
+ ok: true,
5983
+ questions,
5984
+ message: "Questions shown in Sideboard\u2019s composer. Wait for the user\u2019s next message with their answers before continuing."
5985
+ };
5986
+ return { content: [{ type: "text", text: JSON.stringify(payload) }] };
5987
+ }
5988
+ );
5989
+ server.tool(
5990
+ "present_plan",
5991
+ "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.",
5992
+ {
5993
+ title: z.string().optional().describe("Short plan title (defaults to Plan)"),
5994
+ content: z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
5995
+ thread_id: z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
5996
+ },
5997
+ async ({ title, content, thread_id }) => {
5998
+ const { writePlanFile: writePlanFile2 } = await import("./plan-file-6O7G4VPQ.js");
5999
+ let root = process.cwd();
6000
+ if (thread_id?.trim()) {
6001
+ const t = orch.getThread(thread_id.trim());
6002
+ if (t?.worktreePath?.trim()) root = t.worktreePath;
6003
+ }
6004
+ const path = writePlanFile2(root, content);
6005
+ const payload = {
6006
+ ok: true,
6007
+ path,
6008
+ title: title?.trim() || "Plan",
6009
+ content,
6010
+ message: "Plan saved to .context/attachments/plan.md and shown in Sideboard chat for approval."
6011
+ };
6012
+ return { content: [{ type: "text", text: JSON.stringify(payload) }] };
6013
+ }
6014
+ );
5457
6015
  server.tool(
5458
6016
  "present_schema",
5459
6017
  "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.",
@@ -6032,6 +6590,126 @@ async function startMcpServer() {
6032
6590
  };
6033
6591
  }
6034
6592
  );
6593
+ server.tool(
6594
+ "get_pr_stack",
6595
+ "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.",
6596
+ { ref: z.string() },
6597
+ async ({ ref }) => {
6598
+ const stack = await orch.getPrStack(ref);
6599
+ return {
6600
+ content: [{ type: "text", text: JSON.stringify(stack, null, 2) }]
6601
+ };
6602
+ }
6603
+ );
6604
+ server.tool(
6605
+ "open_pr_stack_layers",
6606
+ "Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
6607
+ {
6608
+ ref: z.string(),
6609
+ layer: z.number().int().positive().optional()
6610
+ },
6611
+ async ({ ref, layer }) => {
6612
+ const result = await orch.openPrStackLayers(ref, { layer });
6613
+ return {
6614
+ content: [
6615
+ {
6616
+ type: "text",
6617
+ text: JSON.stringify(
6618
+ {
6619
+ stackNumber: result.stack.stackNumber,
6620
+ trunk: result.stack.trunk,
6621
+ threads: result.threads.map((t) => ({
6622
+ id: t.id,
6623
+ title: t.title,
6624
+ branchName: t.branchName,
6625
+ stackLayer: t.stackLayer,
6626
+ worktreePath: t.worktreePath,
6627
+ prUrl: t.prUrl,
6628
+ link: `sideboard://thread/${t.id}`
6629
+ }))
6630
+ },
6631
+ null,
6632
+ 2
6633
+ )
6634
+ }
6635
+ ]
6636
+ };
6637
+ }
6638
+ );
6639
+ server.tool(
6640
+ "add_stack_layer",
6641
+ "Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
6642
+ {
6643
+ ref: z.string(),
6644
+ branchName: z.string(),
6645
+ title: z.string().optional()
6646
+ },
6647
+ async ({ ref, branchName, title }) => {
6648
+ const result = await orch.addStackLayer(ref, branchName, { title });
6649
+ return {
6650
+ content: [
6651
+ {
6652
+ type: "text",
6653
+ text: JSON.stringify(
6654
+ {
6655
+ id: result.thread.id,
6656
+ title: result.thread.title,
6657
+ branchName: result.thread.branchName,
6658
+ stackLayer: result.thread.stackLayer,
6659
+ worktreePath: result.thread.worktreePath,
6660
+ link: `sideboard://thread/${result.thread.id}`
6661
+ },
6662
+ null,
6663
+ 2
6664
+ )
6665
+ }
6666
+ ]
6667
+ };
6668
+ }
6669
+ );
6670
+ server.tool(
6671
+ "create_pr_stack",
6672
+ "Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
6673
+ {
6674
+ repoPath: z.string(),
6675
+ branches: z.array(z.string()).min(1),
6676
+ agent: z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
6677
+ base: z.string().optional(),
6678
+ title: z.string().optional()
6679
+ },
6680
+ async (args) => {
6681
+ const result = await orch.createPrStack({
6682
+ repoPath: args.repoPath,
6683
+ branches: args.branches,
6684
+ agent: args.agent,
6685
+ base: args.base,
6686
+ title: args.title
6687
+ });
6688
+ return {
6689
+ content: [
6690
+ {
6691
+ type: "text",
6692
+ text: JSON.stringify(
6693
+ {
6694
+ stackNumber: result.stack.stackNumber,
6695
+ trunk: result.stack.trunk,
6696
+ threads: result.threads.map((t) => ({
6697
+ id: t.id,
6698
+ title: t.title,
6699
+ branchName: t.branchName,
6700
+ stackLayer: t.stackLayer,
6701
+ worktreePath: t.worktreePath,
6702
+ link: `sideboard://thread/${t.id}`
6703
+ }))
6704
+ },
6705
+ null,
6706
+ 2
6707
+ )
6708
+ }
6709
+ ]
6710
+ };
6711
+ }
6712
+ );
6035
6713
  server.tool(
6036
6714
  "list_issues",
6037
6715
  "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.",
@@ -6385,6 +7063,7 @@ async function runCloudConnect(opts) {
6385
7063
  }
6386
7064
  }
6387
7065
  export {
7066
+ ATTACHMENTS_DIR,
6388
7067
  BRIGHTSY_MCP_ALLOWED_TOOLS,
6389
7068
  BrightsySideboardApi,
6390
7069
  CLAUDE_MODEL_CATALOG,
@@ -6400,11 +7079,16 @@ export {
6400
7079
  FAMOUS_SOCCER_TEAMS,
6401
7080
  GLOBAL_WORKSPACE_ID,
6402
7081
  HARNESS_ENV_KEYS,
7082
+ LEGACY_ATTACHMENTS_DIR,
7083
+ LEGACY_PLAN_FILE_REL,
7084
+ LEGACY_REVIEW_REQUEST_PATH,
6403
7085
  MAX_ANTHROPIC_CACHE_CONTROL_BLOCKS,
6404
7086
  ORCHESTRATOR_AGENT_KINDS,
6405
7087
  Orchestrator,
6406
7088
  PASTE_ATTACH_MIN_CHARS,
6407
7089
  PASTE_ATTACH_MIN_LINES,
7090
+ PLAN_FILE_NAME,
7091
+ PLAN_FILE_REL,
6408
7092
  PLAN_MODE_INSTRUCTION,
6409
7093
  REPO_REVIEW_NAME,
6410
7094
  REPO_REVIEW_PATH,
@@ -6414,6 +7098,8 @@ export {
6414
7098
  SIDEBOARD_FORCE_STOP,
6415
7099
  SIDEBOARD_MCP_ALLOWED_TOOLS,
6416
7100
  THINKING_EFFORTS,
7101
+ addPrStackLayer,
7102
+ addStackLayerFromThread,
6417
7103
  addWorkspace,
6418
7104
  adoptThread,
6419
7105
  allAdapters,
@@ -6432,6 +7118,7 @@ export {
6432
7118
  attachmentFromAbsolutePath,
6433
7119
  attachmentsFromBuffers,
6434
7120
  attachmentsFromWorktreePaths,
7121
+ attachmentsGitignoreBody,
6435
7122
  autoCleanupOrphansEnabled,
6436
7123
  autoRenameBranchEnabled,
6437
7124
  autoRunAfterSetupEnabled,
@@ -6454,6 +7141,7 @@ export {
6454
7141
  caffeinateWhileRunningEnabled,
6455
7142
  captureLoginEnv,
6456
7143
  captureTurnBaseline,
7144
+ checkoutPrStackLayer,
6457
7145
  childEnvWithAppSettings,
6458
7146
  claudeAdapter,
6459
7147
  claudeChromeEnabled,
@@ -6473,8 +7161,10 @@ export {
6473
7161
  countCacheControlBlocks,
6474
7162
  createChatTab,
6475
7163
  createEmptyThread,
7164
+ createExistingBranchWorktree,
6476
7165
  createGlobalChat,
6477
7166
  createOrUpdatePr,
7167
+ createPrStack,
6478
7168
  createThread,
6479
7169
  createThreadWorktree,
6480
7170
  currentBranch,
@@ -6484,6 +7174,7 @@ export {
6484
7174
  deleteBranchOnPurgeEnabled,
6485
7175
  deleteThreadRecord,
6486
7176
  detectAgents,
7177
+ detectGhStack,
6487
7178
  detectLocalMergeConflicts,
6488
7179
  disconnectBrightsyTeam,
6489
7180
  discoverSkills,
@@ -6499,12 +7190,15 @@ export {
6499
7190
  estimateThreadChars,
6500
7191
  expandComposerPrompt,
6501
7192
  extractGhErrorDetail,
7193
+ extractPendingPlanQuestions,
7194
+ extractPresentedPlan,
6502
7195
  extractiveSummary,
6503
7196
  fetchPrHead,
6504
7197
  finalizeParts,
6505
7198
  findInvalidCacheControlTtlOrder,
6506
7199
  findOrphanWorktrees,
6507
7200
  findThreadByRef,
7201
+ findThreadForStackLayer,
6508
7202
  flattenTurnInput,
6509
7203
  forkChatTab,
6510
7204
  forkMessageSlice,
@@ -6515,6 +7209,8 @@ export {
6515
7209
  formatGhLandError,
6516
7210
  formatIpcInvokeError,
6517
7211
  formatMessagesAsTranscript,
7212
+ formatPlanQuestionAnswers,
7213
+ formatPlanQuestionsForChat,
6518
7214
  formatRateLimitResetHint,
6519
7215
  formatRenameBranchDirective,
6520
7216
  formatTranscriptMarkdown,
@@ -6538,6 +7234,7 @@ export {
6538
7234
  getPrChecks,
6539
7235
  getPrDetails,
6540
7236
  getPrMeta,
7237
+ getPrStack,
6541
7238
  getRepoSetupInfo,
6542
7239
  getRunMode,
6543
7240
  getRunScript,
@@ -6554,9 +7251,12 @@ export {
6554
7251
  healOrchestrationSoccerTitles,
6555
7252
  importConductorWorkspace,
6556
7253
  importConductorWorkspaceAsync,
7254
+ initPrStack,
7255
+ initStackFromThread,
6557
7256
  initializeGitRepository,
6558
7257
  inspectGitWorktree,
6559
7258
  installAgent,
7259
+ isAskUserToolName,
6560
7260
  isBrightsyConnected,
6561
7261
  isBrightsyNdjsonLine,
6562
7262
  isCloudCoordinatorThread,
@@ -6566,14 +7266,17 @@ export {
6566
7266
  isGlobalRepoPath,
6567
7267
  isGlobalThread,
6568
7268
  isImageFilePath,
7269
+ isInPrStack,
6569
7270
  isLinearConnected,
6570
7271
  isOrchestratorCapableAgent,
6571
7272
  isOrchestratorThread,
6572
7273
  isPidAlive,
6573
7274
  isPlaceholderBranch,
7275
+ isPresentPlanToolName,
6574
7276
  isSessionQuotaLimit,
6575
7277
  isSideboardScratchPath,
6576
7278
  isThinkingEffort,
7279
+ isWorkspaceScratchPath,
6577
7280
  listAgentSetupInfo,
6578
7281
  listBranchCommits,
6579
7282
  listBranches,
@@ -6610,6 +7313,7 @@ export {
6610
7313
  mcpAllowTools,
6611
7314
  mcpAuthWarnings,
6612
7315
  mergePr,
7316
+ mergePrStack,
6613
7317
  mergeUsage,
6614
7318
  nextPastedTextName,
6615
7319
  nextThinkingEffort,
@@ -6619,6 +7323,8 @@ export {
6619
7323
  normalizeTurnInput,
6620
7324
  normalizeWorktreePath,
6621
7325
  openInSystemTerminal,
7326
+ openPrStackLayers,
7327
+ openStackLayer,
6622
7328
  opencodeAdapter,
6623
7329
  orchestrationQuotaFallbackAgent,
6624
7330
  orchestrationQuotaOnLimit,
@@ -6627,15 +7333,19 @@ export {
6627
7333
  originGhRepoEnv,
6628
7334
  parseCursorRunnerLine,
6629
7335
  parseForceStopMessage,
7336
+ parseGhStackViewJson,
6630
7337
  parseGithubSlugFromRemoteUrl,
6631
7338
  parseMcpList,
7339
+ parsePlanQuestionsInput,
6632
7340
  parseSessionQuotaResetAt,
6633
7341
  partsToAssistantText,
6634
7342
  pastedTextStats,
6635
7343
  permissionMode,
7344
+ planFileAbs,
6636
7345
  previewLand,
6637
7346
  pushBranch,
6638
7347
  readExistingReviewRequestFile,
7348
+ readPlanFile,
6639
7349
  readSkillBody,
6640
7350
  readThread,
6641
7351
  readWorktreeFile,
@@ -6647,6 +7357,7 @@ export {
6647
7357
  repoSlug,
6648
7358
  requestReview,
6649
7359
  requireAgent,
7360
+ resetGhStackDetectCache,
6650
7361
  resolveClaudeExecutable,
6651
7362
  resolveConductorCursorAgentId,
6652
7363
  resolveCursorModelId,
@@ -6656,6 +7367,7 @@ export {
6656
7367
  resolveFilesToCopy,
6657
7368
  resolveGhAuthToken,
6658
7369
  resolveGithubRepoSlug,
7370
+ resolvePlanMarkdown,
6659
7371
  resolvePrSelector,
6660
7372
  resolveQuotaFallbackAgent,
6661
7373
  resolveRepoRoot,
@@ -6683,12 +7395,16 @@ export {
6683
7395
  slugify,
6684
7396
  spawnAgentTurn,
6685
7397
  splitForCompaction,
7398
+ stackAgentDefaultsFrom,
7399
+ stackIdFrom,
7400
+ stackMergeReadiness,
6686
7401
  stageAbsolutePathsAsAttachments,
6687
7402
  stageBuffersAsAttachments,
6688
7403
  startDevServer,
6689
7404
  startMcpServer,
6690
7405
  startOrchestration,
6691
7406
  stripBrightsyNdjsonNoise,
7407
+ submitPrStack,
6692
7408
  suggestSlug,
6693
7409
  summarizeConversation,
6694
7410
  switchBrightsyAccount,
@@ -6724,6 +7440,7 @@ export {
6724
7440
  worktreeNameFromPath,
6725
7441
  worktreesRoot,
6726
7442
  writeInjectedMcpConfig,
7443
+ writePlanFile,
6727
7444
  writeThread,
6728
7445
  writeWorktreeFile
6729
7446
  };