@sideboard-ai/core 0.1.192 → 0.1.193

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 (68) hide show
  1. package/dist/{abletime-JFMBCSIQ.js → abletime-QHKWGTBY.js} +4 -3
  2. package/dist/{abletime-53T7G667.js → abletime-UX53JTIH.js} +4 -3
  3. package/dist/agents/cursor-runner.js +4 -4
  4. package/dist/{agents-TXGQIK2E.js → agents-MN4GSQTL.js} +19 -17
  5. package/dist/{agents-T5VBB6X3.js → agents-OK5VD25T.js} +10 -8
  6. package/dist/{app-settings-663VQWUV.js → app-settings-LO7UFHQ6.js} +5 -2
  7. package/dist/{app-settings-74HBAAH7.js → app-settings-QYLNKRK4.js} +5 -2
  8. package/dist/branch-prefix-7F37FIPX.js +10 -0
  9. package/dist/branch-prefix-EVW2KHGQ.js +12 -0
  10. package/dist/{caffeinate-hold-SK2QISPE.js → caffeinate-hold-22VFDPSO.js} +2 -2
  11. package/dist/{caffeinate-hold-DBKMRZZO.js → caffeinate-hold-OK4JJ5PP.js} +2 -2
  12. package/dist/{chunk-FZ3G5TXM.js → chunk-3DX2T2KR.js} +174 -172
  13. package/dist/{chunk-HVIEYTUK.js → chunk-3OHR73VK.js} +1 -1
  14. package/dist/{chunk-VV7UT3E2.js → chunk-4OVCJPVI.js} +11 -9
  15. package/dist/{chunk-UBQIELFS.js → chunk-4QJRH5DL.js} +1 -1
  16. package/dist/{chunk-AO35MGOE.js → chunk-4TYXWPAJ.js} +1 -1
  17. package/dist/chunk-64QSHOKV.js +869 -0
  18. package/dist/{chunk-FY64F2ZQ.js → chunk-7AJ7OZZF.js} +4 -4
  19. package/dist/{chunk-WLPXZFRE.js → chunk-7J72QJOM.js} +1 -1
  20. package/dist/chunk-7SNJMKD6.js +20 -0
  21. package/dist/{chunk-WXKXSZN4.js → chunk-BIKCWYRP.js} +16 -873
  22. package/dist/{chunk-J7LQVLFQ.js → chunk-BWRS6GAS.js} +19 -1
  23. package/dist/{chunk-GOZ6T3AG.js → chunk-CJFBSQIB.js} +1 -1
  24. package/dist/{chunk-WHD4JNTN.js → chunk-EBPBGQ4P.js} +13 -13
  25. package/dist/{chunk-JTJHOYCU.js → chunk-EQNRWDLN.js} +10 -4
  26. package/dist/{chunk-NF6ENGHS.js → chunk-EQXXGOMD.js} +185 -182
  27. package/dist/{chunk-7NSXGL6L.js → chunk-HIX2VDRE.js} +11 -9
  28. package/dist/chunk-ILWQT2UN.js +55 -0
  29. package/dist/chunk-JTWKGOFI.js +23 -0
  30. package/dist/{chunk-TCQAFTSV.js → chunk-KJBYIIJU.js} +1 -1
  31. package/dist/{chunk-OKZFTOM2.js → chunk-KORE4WOF.js} +19 -1
  32. package/dist/{chunk-TWSCTJCR.js → chunk-MQC2VA5U.js} +14 -14
  33. package/dist/{chunk-4M2AK3O5.js → chunk-O4BMPY5Y.js} +1 -1
  34. package/dist/chunk-PBEPPXMY.js +57 -0
  35. package/dist/{chunk-IEEAVWAP.js → chunk-PE337IM2.js} +7 -7
  36. package/dist/chunk-RCWHL4TK.js +866 -0
  37. package/dist/{chunk-EFVFB2AH.js → chunk-WFUDY6PC.js} +12 -5
  38. package/dist/{chunk-XBLN52RZ.js → chunk-XPKKCFRT.js} +9 -866
  39. package/dist/{chunk-OCQPTUR7.js → chunk-YWI3IBP2.js} +1 -1
  40. package/dist/{connected-teams-WOYCH7FR.js → connected-teams-PU4AMFNA.js} +2 -2
  41. package/dist/{connected-teams-ON2KIKQX.js → connected-teams-SYQQGCXD.js} +2 -2
  42. package/dist/{coordinator-prompt-PNUDN4DW.js → coordinator-prompt-GXJIPWNA.js} +7 -5
  43. package/dist/{coordinator-prompt-S4HHFSLI.js → coordinator-prompt-WB65HIZ3.js} +9 -7
  44. package/dist/{cursor-recover-NDMLLNYX.js → cursor-recover-DTQAUDGO.js} +2 -2
  45. package/dist/{cursor-recover-L5MD6MBO.js → cursor-recover-TXVTLAMA.js} +1 -1
  46. package/dist/github-DWROJOB7.js +11 -0
  47. package/dist/github-IIF7AQNO.js +9 -0
  48. package/dist/{global-workspace-2BVCJUAL.js → global-workspace-BZ6V6TEX.js} +7 -5
  49. package/dist/{global-workspace-JTBBH2K5.js → global-workspace-MMK5UFUZ.js} +9 -7
  50. package/dist/index.cjs +274 -194
  51. package/dist/index.d.cts +47 -8
  52. package/dist/index.d.ts +47 -8
  53. package/dist/index.js +98 -127
  54. package/dist/mcp/run-stdio.cjs +260 -190
  55. package/dist/mcp/run-stdio.js +36 -76
  56. package/dist/{orchestrator-LWEZ74RK.js → orchestrator-4KI6VIFG.js} +14 -12
  57. package/dist/{orchestrator-4G76BOHH.js → orchestrator-SFOT6Q6F.js} +13 -11
  58. package/dist/{paths-RRCLNSJB.js → paths-C2VERLAE.js} +1 -1
  59. package/dist/{paths-53TLA7VQ.js → paths-LOXCISLX.js} +1 -1
  60. package/dist/{thread-store-ZL6GDIDP.js → thread-store-CO77HV2K.js} +2 -2
  61. package/dist/{thread-store-RXXNSRVO.js → thread-store-M4B52FUS.js} +2 -2
  62. package/dist/{workspaces-OY7D5RPU.js → workspaces-ACMZMKPD.js} +7 -5
  63. package/dist/{workspaces-ZYPME3YD.js → workspaces-KPJS7GLY.js} +9 -7
  64. package/dist/{worktree-7LOSSD7G.js → worktree-6IEVF3GN.js} +15 -12
  65. package/dist/{worktree-LPWTX2NA.js → worktree-UW6DNBIE.js} +20 -17
  66. package/dist/worktree-labels-3XDS6O3U.js +22 -0
  67. package/dist/worktree-labels-PEDOJBMD.js +24 -0
  68. package/package.json +1 -1
@@ -269,9 +269,12 @@ function parseSettingsFile(path, source) {
269
269
  const prompts = {
270
270
  renameBranch: typeof promptsRaw.rename_branch === "string" ? promptsRaw.rename_branch : void 0,
271
271
  createPr: typeof promptsRaw.create_pr === "string" ? promptsRaw.create_pr : void 0,
272
- general: typeof promptsRaw.general === "string" ? promptsRaw.general : void 0
272
+ general: typeof promptsRaw.general === "string" ? promptsRaw.general : void 0,
273
+ resolveMergeConflicts: typeof promptsRaw.resolve_merge_conflicts === "string" ? promptsRaw.resolve_merge_conflicts : void 0
273
274
  };
274
- const hasPrompts = Boolean(prompts.renameBranch || prompts.createPr || prompts.general);
275
+ const hasPrompts = Boolean(
276
+ prompts.renameBranch || prompts.createPr || prompts.general || prompts.resolveMergeConflicts
277
+ );
275
278
  return {
276
279
  source,
277
280
  setup,
@@ -297,9 +300,12 @@ function mergeSettings(base, overlay) {
297
300
  const prompts = {
298
301
  renameBranch: overlay.prompts?.renameBranch ?? base.prompts?.renameBranch,
299
302
  createPr: overlay.prompts?.createPr ?? base.prompts?.createPr,
300
- general: overlay.prompts?.general ?? base.prompts?.general
303
+ general: overlay.prompts?.general ?? base.prompts?.general,
304
+ resolveMergeConflicts: overlay.prompts?.resolveMergeConflicts ?? base.prompts?.resolveMergeConflicts
301
305
  };
302
- const hasPrompts = Boolean(prompts.renameBranch || prompts.createPr || prompts.general);
306
+ const hasPrompts = Boolean(
307
+ prompts.renameBranch || prompts.createPr || prompts.general || prompts.resolveMergeConflicts
308
+ );
303
309
  return {
304
310
  source: overlay.source,
305
311
  setup: overlay.setup ?? base.setup,
@@ -2980,6 +2986,18 @@ var init_teams = __esm({
2980
2986
  });
2981
2987
 
2982
2988
  // src/git/worktree-labels.ts
2989
+ var worktree_labels_exports = {};
2990
+ __export(worktree_labels_exports, {
2991
+ branchDisplayLabel: () => branchDisplayLabel,
2992
+ isPlaceholderBranch: () => isPlaceholderBranch,
2993
+ normalizeWorktreePath: () => normalizeWorktreePath,
2994
+ threadDisplayLabel: () => threadDisplayLabel,
2995
+ ticketSlugForBranch: () => ticketSlugForBranch,
2996
+ worktreeDisplayLabel: () => worktreeDisplayLabel,
2997
+ worktreeDisplayLabelForGroup: () => worktreeDisplayLabelForGroup,
2998
+ worktreeNameFromPath: () => worktreeNameFromPath,
2999
+ worktreeSlugForTicket: () => worktreeSlugForTicket
3000
+ });
2983
3001
  function normalizeWorktreePath(worktreePath) {
2984
3002
  const trimmed = (worktreePath ?? "").replace(/\/+$/, "") || "/";
2985
3003
  const absolute = trimmed.startsWith("/");
@@ -3434,6 +3452,32 @@ var init_secret_vault = __esm({
3434
3452
  }
3435
3453
  });
3436
3454
 
3455
+ // src/git/branch-prefix.ts
3456
+ var branch_prefix_exports = {};
3457
+ __export(branch_prefix_exports, {
3458
+ examplePrefixedBranch: () => examplePrefixedBranch,
3459
+ resolveGitBranchPrefix: () => resolveGitBranchPrefix,
3460
+ sanitizeGitBranchPrefix: () => sanitizeGitBranchPrefix
3461
+ });
3462
+ function sanitizeGitBranchPrefix(raw) {
3463
+ const first = (raw ?? "").trim().replace(/^\/+|\/+$/g, "").split("/")[0] ?? "";
3464
+ const slug = first.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 39);
3465
+ return slug || null;
3466
+ }
3467
+ function resolveGitBranchPrefix(opts) {
3468
+ return sanitizeGitBranchPrefix(opts.setting) ?? sanitizeGitBranchPrefix(opts.githubLogin);
3469
+ }
3470
+ function examplePrefixedBranch(prefix, ticket) {
3471
+ const head = sanitizeGitBranchPrefix(prefix);
3472
+ const slug = ticket?.trim() ? `${ticket.trim()}-eng-fix-thing` : "bb-1234-eng-fix-thing";
3473
+ return head ? `${head}/${slug}` : slug;
3474
+ }
3475
+ var init_branch_prefix = __esm({
3476
+ "src/git/branch-prefix.ts"() {
3477
+ "use strict";
3478
+ }
3479
+ });
3480
+
3437
3481
  // src/store/issue-source-labels.ts
3438
3482
  function issueSourceLabel(source) {
3439
3483
  if (source && source in ISSUE_SOURCE_LABELS) {
@@ -3502,6 +3546,7 @@ __export(app_settings_exports, {
3502
3546
  getGithubPat: () => getGithubPat,
3503
3547
  getIssueSource: () => getIssueSource,
3504
3548
  getLinearApiKey: () => getLinearApiKey,
3549
+ gitBranchPrefixSetting: () => gitBranchPrefixSetting,
3505
3550
  harnessEnvKey: () => harnessEnvKey,
3506
3551
  isAbleTimeConnected: () => isAbleTimeConnected,
3507
3552
  isIssueSourceConnected: () => isIssueSourceConnected,
@@ -3877,6 +3922,10 @@ function normalizeAdvanced(raw) {
3877
3922
  if (typeof source.autoRenameBranch === "boolean") {
3878
3923
  out.autoRenameBranch = source.autoRenameBranch;
3879
3924
  }
3925
+ if (typeof source.branchPrefix === "string") {
3926
+ const prefix = sanitizeGitBranchPrefix(source.branchPrefix);
3927
+ if (prefix) out.branchPrefix = prefix;
3928
+ }
3880
3929
  if (typeof source.autoRunAfterSetup === "boolean") {
3881
3930
  out.autoRunAfterSetup = source.autoRunAfterSetup;
3882
3931
  }
@@ -4730,6 +4779,13 @@ function updateAdvancedSettings(patch) {
4730
4779
  if (typeof patch.autoRenameBranch === "boolean") {
4731
4780
  advanced.autoRenameBranch = patch.autoRenameBranch;
4732
4781
  }
4782
+ if (patch.branchPrefix === null || patch.branchPrefix === "") {
4783
+ delete advanced.branchPrefix;
4784
+ } else if (typeof patch.branchPrefix === "string") {
4785
+ const prefix = sanitizeGitBranchPrefix(patch.branchPrefix);
4786
+ if (prefix) advanced.branchPrefix = prefix;
4787
+ else delete advanced.branchPrefix;
4788
+ }
4733
4789
  if (typeof patch.autoRunAfterSetup === "boolean") {
4734
4790
  advanced.autoRunAfterSetup = patch.autoRunAfterSetup;
4735
4791
  }
@@ -4790,6 +4846,9 @@ function updateAdvancedSettings(patch) {
4790
4846
  function autoRenameBranchEnabled(settings = loadAppSettings()) {
4791
4847
  return settings.advanced.autoRenameBranch !== false;
4792
4848
  }
4849
+ function gitBranchPrefixSetting(settings = loadAppSettings()) {
4850
+ return sanitizeGitBranchPrefix(settings.advanced.branchPrefix);
4851
+ }
4793
4852
  function autoRunAfterSetupEnabled(settings = loadAppSettings()) {
4794
4853
  return Boolean(settings.advanced.autoRunAfterSetup);
4795
4854
  }
@@ -4935,6 +4994,7 @@ var init_app_settings = __esm({
4935
4994
  init_paths();
4936
4995
  init_private_file();
4937
4996
  init_secret_vault();
4997
+ init_branch_prefix();
4938
4998
  init_issue_source_labels();
4939
4999
  init_account_profile();
4940
5000
  HARNESS_ENV_KEYS = {
@@ -7984,7 +8044,7 @@ var init_coordinator_prompt = __esm({
7984
8044
  "- get_diff \u2014 compact diff summary",
7985
8045
  "- get_pr_checks \u2014 snapshot of a worktree thread's PR checks (null = no PR). Use this to inspect status. If the user gave a goal, the worktree agent watches with `gh pr checks --watch` \u2014 do not poll for the human.",
7986
8046
  '- request_review \u2014 open a Review chat tab on a worktree thread (attaches .claude/skills/review/SKILL.md when present, else .context/review.md copied from .sideboard/review.md / stock; sends "Review changes in this workspace."); then wait_for_turn (loop while stillRunning) / get_turn_result on the returned id. The review stays in that chat so they can read it and type next steps \u2014 do not tell the child to comment or update the PR or ticket.',
7987
- "- ask_git \u2014 commit & push, open a draft PR, mark ready for review, resolve conflicts, or merge. When the worktree is clean, Sideboard pushes / opens the PR itself. When dirty, it steers the worktree agent \u2014 then wait_for_turn (loop while stillRunning). Prefer this over paraphrasing. If ask_git / get_thread lastError says the GraphQL/PR body is too long, the branch is already pushed \u2014 send_to_thread so the worktree agent runs `gh pr create --body-file` with a short description. Do not invent SSH or auth failures from that error. If the user gave a goal (Greptile 5/5, CI green), send_to_thread that goal so the worktree agent enters the watch-fix-push loop \u2014 do not tell the human to poll.",
8047
+ "- ask_git \u2014 commit & push, open a draft PR, mark ready for review, resolve conflicts, or merge \u2014 same prompts as the desktop git buttons (Resolve, Create PR, \u2026). Always steers the worktree agent; then wait_for_turn (loop while stillRunning). Prefer this over paraphrasing. If the user gave a goal (Greptile 5/5, CI green), send_to_thread that goal so the worktree agent enters the watch-fix-push loop \u2014 do not tell the human to poll.",
7988
8048
  '- Merge (`ask_git` action=merge / send_to_thread "Merge PR.") only when the user explicitly asked to merge that PR. Do not merge because the work looks done, CI is green, or a typical flow includes it.',
7989
8049
  '- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Ready for review.", "Merge the remote branch (main) into your branch and resolve conflicts. Then, commit and push your changes.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
7990
8050
  "Process guides:",
@@ -12431,87 +12491,6 @@ var init_spawn = __esm({
12431
12491
  }
12432
12492
  });
12433
12493
 
12434
- // src/land/pr-metadata.ts
12435
- function looksGeneric(title) {
12436
- const t = title.trim();
12437
- if (!t) return true;
12438
- if (t.length < 4) return true;
12439
- if (GENERIC_TITLE_RE.test(t)) return true;
12440
- if (!/[\s:/\-_]/.test(t) && t.length <= 24) return true;
12441
- return false;
12442
- }
12443
- function truncateTitle(title, max = 72) {
12444
- const t = title.trim().replace(/\s+/g, " ");
12445
- if (t.length <= max) return t;
12446
- return `${t.slice(0, max - 1).trimEnd()}\u2026`;
12447
- }
12448
- function titleFromPaths(paths) {
12449
- if (paths.length === 0) return null;
12450
- const top = paths.slice(0, 3).map((p) => p.replace(/\/$/, ""));
12451
- if (paths.length === 1) return truncateTitle(`Update ${top[0]}`);
12452
- if (paths.length <= 3) return truncateTitle(`Update ${top.join(", ")}`);
12453
- return truncateTitle(`Update ${top[0]} and ${paths.length - 1} more files`);
12454
- }
12455
- async function suggestPrMetadata(worktreePath, opts) {
12456
- const [{ stdout: logOut }, { stdout: nameStatus }, { stdout: statOut }] = await Promise.all([
12457
- git(["log", "--format=%s", `${opts.base}..HEAD`], worktreePath, {
12458
- reject: false
12459
- }),
12460
- git(["diff", "--name-only", `${opts.base}...HEAD`], worktreePath, {
12461
- reject: false
12462
- }),
12463
- git(["diff", "--stat", `${opts.base}...HEAD`], worktreePath, {
12464
- reject: false
12465
- })
12466
- ]);
12467
- const { stdout: dirtyNames } = await git(["diff", "--name-only", "HEAD"], worktreePath, {
12468
- reject: false
12469
- });
12470
- const { stdout: untracked } = await git(
12471
- ["ls-files", "--others", "--exclude-standard"],
12472
- worktreePath,
12473
- { reject: false }
12474
- );
12475
- const subjects = logOut.split("\n").map((s) => s.trim()).filter(Boolean);
12476
- const paths = [
12477
- ...new Set(
12478
- [...nameStatus.split("\n"), ...dirtyNames.split("\n"), ...untracked.split("\n")].map((p) => p.trim()).filter(Boolean)
12479
- )
12480
- ].sort((a, b) => a.localeCompare(b));
12481
- const meaningfulSubjects = subjects.filter((s) => !looksGeneric(s));
12482
- const fallback = opts.fallbackTitle?.trim() || null;
12483
- const fallbackOk = fallback && !looksGeneric(fallback) ? fallback : null;
12484
- const title = truncateTitle(
12485
- meaningfulSubjects[0] ?? fallbackOk ?? titleFromPaths(paths) ?? "Sideboard changes"
12486
- );
12487
- const commitMessage = meaningfulSubjects[0] ?? fallbackOk ?? titleFromPaths(paths) ?? `sideboard: ${title}`;
12488
- const summaryLines = meaningfulSubjects.length > 0 ? meaningfulSubjects.slice(0, 12).map((s) => `- ${s}`) : paths.slice(0, 20).map((p) => `- \`${p}\``);
12489
- const bodyParts = [
12490
- "## Summary",
12491
- summaryLines.length > 0 ? summaryLines.join("\n") : "- Changes landed via Sideboard.",
12492
- ""
12493
- ];
12494
- if (statOut.trim()) {
12495
- bodyParts.push("## Diff", "```", statOut.trim(), "```", "");
12496
- }
12497
- if (opts.sourceLabel) {
12498
- bodyParts.push(`_Landed via Sideboard from ${opts.sourceLabel}_`);
12499
- }
12500
- return {
12501
- title,
12502
- body: bodyParts.join("\n").trim() + "\n",
12503
- commitMessage: truncateTitle(commitMessage, 72)
12504
- };
12505
- }
12506
- var GENERIC_TITLE_RE;
12507
- var init_pr_metadata = __esm({
12508
- "src/land/pr-metadata.ts"() {
12509
- "use strict";
12510
- init_run();
12511
- GENERIC_TITLE_RE = /^(sideboard:|thread\/|wip\b|tmp\b|update\b|changes?\b|misc\b|stuff\b)/i;
12512
- }
12513
- });
12514
-
12515
12494
  // src/git/agent-git-actions.ts
12516
12495
  function agentGitPrompt(action, opts) {
12517
12496
  switch (action) {
@@ -12532,6 +12511,19 @@ function agentGitPrompt(action, opts) {
12532
12511
  return "Merge PR.";
12533
12512
  }
12534
12513
  }
12514
+ function resolveSidebarGitPrompt(action, opts) {
12515
+ if (action === "create-pr" || action === "create-draft" || action === "create-web") {
12516
+ const custom = opts?.createPr?.trim();
12517
+ if (custom) return custom;
12518
+ return agentGitPrompt(action === "create-pr" ? "create-draft" : action);
12519
+ }
12520
+ if (action === "resolve-conflicts") {
12521
+ const custom = opts?.resolveMergeConflicts?.trim();
12522
+ if (custom) return custom;
12523
+ return agentGitPrompt("resolve-conflicts", opts);
12524
+ }
12525
+ return agentGitPrompt(action, opts);
12526
+ }
12535
12527
  function gitActionExpansion(action, base) {
12536
12528
  switch (action) {
12537
12529
  case "commit-push":
@@ -17785,6 +17777,87 @@ var init_diff = __esm({
17785
17777
  }
17786
17778
  });
17787
17779
 
17780
+ // src/land/pr-metadata.ts
17781
+ function looksGeneric(title) {
17782
+ const t = title.trim();
17783
+ if (!t) return true;
17784
+ if (t.length < 4) return true;
17785
+ if (GENERIC_TITLE_RE.test(t)) return true;
17786
+ if (!/[\s:/\-_]/.test(t) && t.length <= 24) return true;
17787
+ return false;
17788
+ }
17789
+ function truncateTitle(title, max = 72) {
17790
+ const t = title.trim().replace(/\s+/g, " ");
17791
+ if (t.length <= max) return t;
17792
+ return `${t.slice(0, max - 1).trimEnd()}\u2026`;
17793
+ }
17794
+ function titleFromPaths(paths) {
17795
+ if (paths.length === 0) return null;
17796
+ const top = paths.slice(0, 3).map((p) => p.replace(/\/$/, ""));
17797
+ if (paths.length === 1) return truncateTitle(`Update ${top[0]}`);
17798
+ if (paths.length <= 3) return truncateTitle(`Update ${top.join(", ")}`);
17799
+ return truncateTitle(`Update ${top[0]} and ${paths.length - 1} more files`);
17800
+ }
17801
+ async function suggestPrMetadata(worktreePath, opts) {
17802
+ const [{ stdout: logOut }, { stdout: nameStatus }, { stdout: statOut }] = await Promise.all([
17803
+ git(["log", "--format=%s", `${opts.base}..HEAD`], worktreePath, {
17804
+ reject: false
17805
+ }),
17806
+ git(["diff", "--name-only", `${opts.base}...HEAD`], worktreePath, {
17807
+ reject: false
17808
+ }),
17809
+ git(["diff", "--stat", `${opts.base}...HEAD`], worktreePath, {
17810
+ reject: false
17811
+ })
17812
+ ]);
17813
+ const { stdout: dirtyNames } = await git(["diff", "--name-only", "HEAD"], worktreePath, {
17814
+ reject: false
17815
+ });
17816
+ const { stdout: untracked } = await git(
17817
+ ["ls-files", "--others", "--exclude-standard"],
17818
+ worktreePath,
17819
+ { reject: false }
17820
+ );
17821
+ const subjects = logOut.split("\n").map((s) => s.trim()).filter(Boolean);
17822
+ const paths = [
17823
+ ...new Set(
17824
+ [...nameStatus.split("\n"), ...dirtyNames.split("\n"), ...untracked.split("\n")].map((p) => p.trim()).filter(Boolean)
17825
+ )
17826
+ ].sort((a, b) => a.localeCompare(b));
17827
+ const meaningfulSubjects = subjects.filter((s) => !looksGeneric(s));
17828
+ const fallback = opts.fallbackTitle?.trim() || null;
17829
+ const fallbackOk = fallback && !looksGeneric(fallback) ? fallback : null;
17830
+ const title = truncateTitle(
17831
+ meaningfulSubjects[0] ?? fallbackOk ?? titleFromPaths(paths) ?? "Sideboard changes"
17832
+ );
17833
+ const commitMessage = meaningfulSubjects[0] ?? fallbackOk ?? titleFromPaths(paths) ?? `sideboard: ${title}`;
17834
+ const summaryLines = meaningfulSubjects.length > 0 ? meaningfulSubjects.slice(0, 12).map((s) => `- ${s}`) : paths.slice(0, 20).map((p) => `- \`${p}\``);
17835
+ const bodyParts = [
17836
+ "## Summary",
17837
+ summaryLines.length > 0 ? summaryLines.join("\n") : "- Changes landed via Sideboard.",
17838
+ ""
17839
+ ];
17840
+ if (statOut.trim()) {
17841
+ bodyParts.push("## Diff", "```", statOut.trim(), "```", "");
17842
+ }
17843
+ if (opts.sourceLabel) {
17844
+ bodyParts.push(`_Landed via Sideboard from ${opts.sourceLabel}_`);
17845
+ }
17846
+ return {
17847
+ title,
17848
+ body: bodyParts.join("\n").trim() + "\n",
17849
+ commitMessage: truncateTitle(commitMessage, 72)
17850
+ };
17851
+ }
17852
+ var GENERIC_TITLE_RE;
17853
+ var init_pr_metadata = __esm({
17854
+ "src/land/pr-metadata.ts"() {
17855
+ "use strict";
17856
+ init_run();
17857
+ GENERIC_TITLE_RE = /^(sideboard:|thread\/|wip\b|tmp\b|update\b|changes?\b|misc\b|stuff\b)/i;
17858
+ }
17859
+ });
17860
+
17788
17861
  // src/land/land.ts
17789
17862
  async function previewLand(thread) {
17790
17863
  if (thread.sourceIsFork) {
@@ -18315,13 +18388,14 @@ function formatRenameBranchDirective(thread, opts) {
18315
18388
  if (!isPlaceholderBranch(thread.branchName, thread.worktreePath)) return null;
18316
18389
  const dir = worktreeNameFromPath(thread.worktreePath);
18317
18390
  const ticket = thread.sourceType === "ticket" ? ticketSlugForBranch(thread.sourceRef ?? "") : null;
18318
- const example = ticket ? `\`feat/${ticket}-dark-mode\` or \`fix/${ticket}-panel-width\`` : "`fix/panel-width` or `feat/dark-mode`";
18391
+ const prefix = sanitizeGitBranchPrefix(opts?.branchPrefix);
18392
+ const example = examplePrefixedBranch(prefix, ticket);
18393
+ const shape = prefix ? `\`${prefix}/<ticket-or-task>-<short-kebab-description>\`` : "`<ticket-or-task>-<short-kebab-description>`";
18319
18394
  const lines = [
18320
18395
  "Branch naming (do this early in the turn):",
18321
18396
  `- Current branch \`${thread.branchName}\` is a temporary placeholder. The worktree folder \`${dir}\` is a stable nickname \u2014 do not rename or leave that directory.`,
18322
- `- Rename the git branch to a short kebab-case name that describes this task (what you are changing), e.g. ${example}:`,
18397
+ `- Rename the git branch to ${shape}, e.g. \`${example}\`:`,
18323
18398
  " `git branch -m <new-name>`",
18324
- "- Prefer Conventional Commits style prefixes when they fit (`fix/`, `feat/`, `chore/`, `docs/`).",
18325
18399
  ...ticket ? [
18326
18400
  `- Keep ticket \`${ticket}\` in the new branch name so the issue stays findable.`
18327
18401
  ] : [],
@@ -18505,6 +18579,7 @@ var init_instructions = __esm({
18505
18579
  import_node_fs48 = require("fs");
18506
18580
  import_node_path45 = require("path");
18507
18581
  init_git_auth_mode();
18582
+ init_branch_prefix();
18508
18583
  init_worktree_labels();
18509
18584
  init_detached_job_path();
18510
18585
  PR_GOAL_RE = /greptile|\bci\b.*\b(green|pass)|checks?\s+(pass|green)|until\s+.*\b(pass|green|merge|clean)|\bfix ci\b|watch[- ]fix[- ]push/i;
@@ -19337,6 +19412,64 @@ var init_schedule_runner = __esm({
19337
19412
  }
19338
19413
  });
19339
19414
 
19415
+ // src/integrations/github.ts
19416
+ var github_exports = {};
19417
+ __export(github_exports, {
19418
+ getGitHubStatus: () => getGitHubStatus,
19419
+ refreshGitHubAuth: () => refreshGitHubAuth
19420
+ });
19421
+ async function getGitHubStatus() {
19422
+ const which = await run("which", ["gh"], { reject: false });
19423
+ if (which.exitCode !== 0 || !which.stdout.trim()) {
19424
+ return {
19425
+ connected: false,
19426
+ login: null,
19427
+ hosts: [],
19428
+ reason: "gh CLI not found on PATH"
19429
+ };
19430
+ }
19431
+ const loginResult = await run(
19432
+ "gh",
19433
+ ["api", "user", "--jq", ".login"],
19434
+ { reject: false }
19435
+ );
19436
+ if (loginResult.exitCode === 0 && loginResult.stdout.trim()) {
19437
+ return {
19438
+ connected: true,
19439
+ login: loginResult.stdout.trim(),
19440
+ hosts: ["github.com"],
19441
+ reason: null
19442
+ };
19443
+ }
19444
+ const status = await run("gh", ["auth", "status"], { reject: false });
19445
+ const text7 = `${status.stdout}
19446
+ ${status.stderr}`;
19447
+ const loginMatch = text7.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text7.match(/Logged in to ([^\s]+) as (\S+)/i);
19448
+ if (loginMatch) {
19449
+ return {
19450
+ connected: true,
19451
+ login: loginMatch[2].replace(/['"]/g, ""),
19452
+ hosts: [loginMatch[1]],
19453
+ reason: null
19454
+ };
19455
+ }
19456
+ return {
19457
+ connected: false,
19458
+ login: null,
19459
+ hosts: [],
19460
+ reason: status.stderr.trim() || status.stdout.trim() || "Not logged in to GitHub (run: gh auth login)"
19461
+ };
19462
+ }
19463
+ async function refreshGitHubAuth() {
19464
+ return getGitHubStatus();
19465
+ }
19466
+ var init_github = __esm({
19467
+ "src/integrations/github.ts"() {
19468
+ "use strict";
19469
+ init_run();
19470
+ }
19471
+ });
19472
+
19340
19473
  // src/agents/cursor-store.ts
19341
19474
  function cursorSdkStoreDir(threadId) {
19342
19475
  const root = (0, import_node_path49.join)(appDataDir(), CURSOR_SDK_STORE_DIR);
@@ -19576,7 +19709,6 @@ var init_orchestrator = __esm({
19576
19709
  init_spawn();
19577
19710
  init_agents();
19578
19711
  init_worktree();
19579
- init_pr_metadata();
19580
19712
  init_stack();
19581
19713
  init_agent_git_actions();
19582
19714
  init_pr_merge_archive();
@@ -20364,7 +20496,7 @@ var init_orchestrator = __esm({
20364
20496
  }
20365
20497
  const isBrightsy = fresh.agent === "brightsy";
20366
20498
  const isOrchestration = isOrchestratorThread(fresh);
20367
- const { autoRenameBranchEnabled: autoRenameBranchEnabled2, getGithubGitAuthMode: getGithubGitAuthMode2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
20499
+ const { autoRenameBranchEnabled: autoRenameBranchEnabled2, getGithubGitAuthMode: getGithubGitAuthMode2, gitBranchPrefixSetting: gitBranchPrefixSetting2 } = await Promise.resolve().then(() => (init_app_settings(), app_settings_exports));
20368
20500
  const gitAuthMode = getGithubGitAuthMode2();
20369
20501
  const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
20370
20502
  githubSlug: await resolveGithubRepoSlug(fresh.worktreePath).catch(
@@ -20400,9 +20532,26 @@ var init_orchestrator = __esm({
20400
20532
  }
20401
20533
  }
20402
20534
  const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
20403
- const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled2() ? formatRenameBranchDirective(fresh, {
20404
- customPrompt: settings?.prompts?.renameBranch
20405
- }) : null;
20535
+ let renameBranchDirective = null;
20536
+ if (!isBrightsy && !isOrchestration && autoRenameBranchEnabled2()) {
20537
+ const { isPlaceholderBranch: isPlaceholderBranch2 } = await Promise.resolve().then(() => (init_worktree_labels(), worktree_labels_exports));
20538
+ let githubLogin = null;
20539
+ if (isPlaceholderBranch2(fresh.branchName, fresh.worktreePath) && !gitBranchPrefixSetting2()) {
20540
+ try {
20541
+ const { getGitHubStatus: getGitHubStatus2 } = await Promise.resolve().then(() => (init_github(), github_exports));
20542
+ githubLogin = (await getGitHubStatus2()).login;
20543
+ } catch {
20544
+ }
20545
+ }
20546
+ const { resolveGitBranchPrefix: resolveGitBranchPrefix2 } = await Promise.resolve().then(() => (init_branch_prefix(), branch_prefix_exports));
20547
+ renameBranchDirective = formatRenameBranchDirective(fresh, {
20548
+ customPrompt: settings?.prompts?.renameBranch,
20549
+ branchPrefix: resolveGitBranchPrefix2({
20550
+ setting: gitBranchPrefixSetting2(),
20551
+ githubLogin
20552
+ })
20553
+ });
20554
+ }
20406
20555
  let seed = null;
20407
20556
  if (!fresh.sessionId) {
20408
20557
  const prior = fresh.messages.slice(0, -1);
@@ -21432,9 +21581,9 @@ var init_orchestrator = __esm({
21432
21581
  return tab;
21433
21582
  }
21434
21583
  /**
21435
- * Desktop git buttons + MCP `ask_git`.
21436
- * When the worktree is clean, push / open the PR here (HTTPS via `gh` if SSH
21437
- * is missing). When dirty, steer the worktree agent to commit first.
21584
+ * Desktop git buttons + MCP `ask_git`. Always queues the worktree agent
21585
+ * with the action prompt (same path as Resolve). Repository `[prompts]`
21586
+ * overrides apply when set.
21438
21587
  */
21439
21588
  async askGit(threadRef, action) {
21440
21589
  if (!AGENT_GIT_ACTIONS.includes(action)) {
@@ -21452,17 +21601,6 @@ var init_orchestrator = __esm({
21452
21601
  "No pull request linked. Ask the worktree agent to open a draft PR first (ask_git create-draft)."
21453
21602
  );
21454
21603
  }
21455
- if ((action === "commit-push" || action === "create-draft" || action === "create-web") && thread.worktreePath?.trim() && !await isDirty(thread.worktreePath)) {
21456
- try {
21457
- await this.pushAndMaybeOpenPr(thread, action);
21458
- } catch (err) {
21459
- const message = err instanceof Error ? err.message : String(err);
21460
- updateThread(thread.id, { lastError: message });
21461
- this.emit({ type: "error", threadId: thread.id, message });
21462
- throw err;
21463
- }
21464
- return this.requireThread(threadRef);
21465
- }
21466
21604
  let prBase;
21467
21605
  if (action === "resolve-conflicts") {
21468
21606
  try {
@@ -21471,42 +21609,18 @@ var init_orchestrator = __esm({
21471
21609
  } catch {
21472
21610
  }
21473
21611
  }
21474
- return this.send(threadRef, agentGitPrompt(action, { prBase }), {
21475
- followUp: resolveOrchChildFollowUp()
21476
- });
21477
- }
21478
- /** Push origin (gh HTTPS fallback) and create/update the PR when requested. */
21479
- async pushAndMaybeOpenPr(thread, action) {
21480
- const cwd = thread.worktreePath;
21481
- const branch = await currentBranch(cwd);
21482
- await pushBranch(cwd, branch);
21483
- if (action === "commit-push") return;
21484
- const base = await resolveDefaultBranch(thread.repoPath);
21485
- const meta = await suggestPrMetadata(cwd, {
21486
- base,
21487
- fallbackTitle: thread.prTitle ?? thread.title
21488
- });
21489
- const url = await createOrUpdatePr(cwd, {
21490
- title: meta.title,
21491
- body: meta.body,
21492
- base,
21493
- head: branch,
21494
- draft: action === "create-draft",
21495
- web: action === "create-web"
21496
- });
21497
- if (!url) return;
21498
- const patch = {
21499
- prUrl: url,
21500
- prTitle: meta.title,
21501
- prIsDraft: action === "create-draft"
21502
- };
21503
- try {
21504
- const fetched = await getPrMeta(cwd, url);
21505
- if (fetched?.title) patch.prTitle = fetched.title;
21506
- if (fetched) patch.prIsDraft = Boolean(fetched.isDraft);
21507
- } catch {
21508
- }
21509
- updateThread(thread.id, patch);
21612
+ const settings = loadWorkspaceSettings(thread.worktreePath, thread.repoPath);
21613
+ return this.send(
21614
+ threadRef,
21615
+ resolveSidebarGitPrompt(action, {
21616
+ prBase,
21617
+ createPr: settings?.prompts?.createPr,
21618
+ resolveMergeConflicts: settings?.prompts?.resolveMergeConflicts
21619
+ }),
21620
+ {
21621
+ followUp: resolveOrchChildFollowUp()
21622
+ }
21623
+ );
21510
21624
  }
21511
21625
  setThreadOptions(threadRef, patch) {
21512
21626
  const thread = this.requireThread(threadRef);
@@ -25373,51 +25487,7 @@ async function getHomeBoardInputs(workspaces, opts) {
25373
25487
 
25374
25488
  // src/mcp/server.ts
25375
25489
  init_home_board();
25376
-
25377
- // src/integrations/github.ts
25378
- init_run();
25379
- async function getGitHubStatus() {
25380
- const which = await run("which", ["gh"], { reject: false });
25381
- if (which.exitCode !== 0 || !which.stdout.trim()) {
25382
- return {
25383
- connected: false,
25384
- login: null,
25385
- hosts: [],
25386
- reason: "gh CLI not found on PATH"
25387
- };
25388
- }
25389
- const loginResult = await run(
25390
- "gh",
25391
- ["api", "user", "--jq", ".login"],
25392
- { reject: false }
25393
- );
25394
- if (loginResult.exitCode === 0 && loginResult.stdout.trim()) {
25395
- return {
25396
- connected: true,
25397
- login: loginResult.stdout.trim(),
25398
- hosts: ["github.com"],
25399
- reason: null
25400
- };
25401
- }
25402
- const status = await run("gh", ["auth", "status"], { reject: false });
25403
- const text7 = `${status.stdout}
25404
- ${status.stderr}`;
25405
- const loginMatch = text7.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text7.match(/Logged in to ([^\s]+) as (\S+)/i);
25406
- if (loginMatch) {
25407
- return {
25408
- connected: true,
25409
- login: loginMatch[2].replace(/['"]/g, ""),
25410
- hosts: [loginMatch[1]],
25411
- reason: null
25412
- };
25413
- }
25414
- return {
25415
- connected: false,
25416
- login: null,
25417
- hosts: [],
25418
- reason: status.stderr.trim() || status.stdout.trim() || "Not logged in to GitHub (run: gh auth login)"
25419
- };
25420
- }
25490
+ init_github();
25421
25491
 
25422
25492
  // src/mcp/create-thread-agent.ts
25423
25493
  init_cursor();
@@ -26235,7 +26305,7 @@ async function startMcpServer() {
26235
26305
  );
26236
26306
  server.tool(
26237
26307
  "ask_git",
26238
- "Commit & push, open a draft PR, mark ready for review, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` even when origin is SSH / Settings \u2192 Git is SSH). When dirty, steers the worktree agent to commit (Settings \u2192 Follow-up, default interrupt now); then wait_for_turn (loop while stillRunning). Do not start a checks loop on a plain push \u2014 only if the user gave a goal (Greptile 5/5, CI green). Pass a worktree thread ref (not the orchestrator). action=ready-for-review and action=merge only when the user explicitly asked (or clicked the desktop button). Do not run git or gh from the orchestration cwd. If this tool errors that the GraphQL/PR body is too long, the branch is already pushed \u2014 have the worktree agent retry `gh pr create --body-file` with a short description (GitHub limit 65,536 characters). Do not invent SSH/auth failures from that error.",
26308
+ "Commit & push, open a draft PR, mark ready for review, resolve conflicts, or merge \u2014 same prompts as the desktop git buttons (including Resolve). Always steers the worktree agent; does not push or open the PR locally. Then wait_for_turn (loop while stillRunning). Do not start a checks loop on a plain push \u2014 only if the user gave a goal (Greptile 5/5, CI green). Pass a worktree thread ref (not the orchestrator). action=ready-for-review and action=merge only when the user explicitly asked (or clicked the desktop button). Do not run git or gh from the orchestration cwd.",
26239
26309
  {
26240
26310
  ref: import_zod9.z.string().describe("Worktree thread id/ref"),
26241
26311
  action: import_zod9.z.enum(AGENT_GIT_ACTIONS).describe(