@sideboard-ai/core 0.1.169 → 0.1.171

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 (35) hide show
  1. package/dist/{abletime-IC2V7CNB.js → abletime-7VW4EGPT.js} +2 -2
  2. package/dist/{abletime-ESVTHTFH.js → abletime-PCLL36HW.js} +2 -2
  3. package/dist/{agents-V7CF33IK.js → agents-7UTRNLXQ.js} +4 -4
  4. package/dist/{agents-LNFEVU2F.js → agents-CTWLBPCV.js} +4 -4
  5. package/dist/{app-settings-VMOTQORT.js → app-settings-663VQWUV.js} +1 -1
  6. package/dist/{app-settings-LF3TS7XS.js → app-settings-74HBAAH7.js} +1 -1
  7. package/dist/{chunk-P2VCMIZU.js → chunk-234EKD2F.js} +2 -2
  8. package/dist/{chunk-DHPZW2EA.js → chunk-2RM6BZQ2.js} +2 -2
  9. package/dist/{chunk-QHCSYWEC.js → chunk-3F7AXVIL.js} +30 -10
  10. package/dist/{chunk-BLSTB2AD.js → chunk-6X6KMUI2.js} +29 -10
  11. package/dist/{chunk-DKLGWRWC.js → chunk-AQPVBVJE.js} +1 -1
  12. package/dist/{chunk-NB7TEKCF.js → chunk-CMJ2FBBV.js} +1 -1
  13. package/dist/{chunk-7IUO67CW.js → chunk-J7LQVLFQ.js} +15 -8
  14. package/dist/{chunk-YMF5WCIA.js → chunk-OKZFTOM2.js} +15 -8
  15. package/dist/{chunk-Y7DJA3UW.js → chunk-PXNOT6NV.js} +1 -1
  16. package/dist/{chunk-ISMGVHP4.js → chunk-QYJS7LYE.js} +181 -207
  17. package/dist/{chunk-3C4B52TS.js → chunk-QYZYDO3F.js} +1 -1
  18. package/dist/{chunk-M3OIQZRS.js → chunk-ZTVBIBLB.js} +177 -138
  19. package/dist/{coordinator-prompt-JTDCEU74.js → coordinator-prompt-7CTI6HRH.js} +3 -3
  20. package/dist/{coordinator-prompt-RRUU2OYO.js → coordinator-prompt-PM6MPYYB.js} +3 -3
  21. package/dist/{global-workspace-ZF6EJAM3.js → global-workspace-CMYSFRCM.js} +3 -3
  22. package/dist/{global-workspace-4SI3CRRT.js → global-workspace-CRJ6ZVIY.js} +3 -3
  23. package/dist/index.cjs +205 -203
  24. package/dist/index.d.cts +49 -21
  25. package/dist/index.d.ts +49 -21
  26. package/dist/index.js +16 -14
  27. package/dist/mcp/run-stdio.cjs +197 -132
  28. package/dist/mcp/run-stdio.js +8 -8
  29. package/dist/{orchestrator-67BPEJZL.js → orchestrator-RW4IVITI.js} +5 -5
  30. package/dist/{orchestrator-FCH2PFPX.js → orchestrator-VPT4SXXD.js} +5 -5
  31. package/dist/{workspaces-NOC2MWQV.js → workspaces-L73MCDFA.js} +3 -3
  32. package/dist/{workspaces-A7C3WA54.js → workspaces-UYSD2STS.js} +3 -3
  33. package/dist/{worktree-GXO2VDQF.js → worktree-SQD3DBYC.js} +2 -2
  34. package/dist/{worktree-PODE23PD.js → worktree-WVEDT3FM.js} +2 -2
  35. package/package.json +1 -1
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  getGithubGitAuthMode,
6
6
  getGithubPat
7
- } from "./chunk-7IUO67CW.js";
7
+ } from "./chunk-J7LQVLFQ.js";
8
8
  import {
9
9
  listThreads
10
10
  } from "./chunk-7UURXPKD.js";
@@ -33,7 +33,7 @@ import {
33
33
  summarizeTurnStderr,
34
34
  toolDescription,
35
35
  turnFailChatText
36
- } from "./chunk-BLSTB2AD.js";
36
+ } from "./chunk-6X6KMUI2.js";
37
37
  import {
38
38
  extractPresentedPlan,
39
39
  readPlanFile,
@@ -64,7 +64,7 @@ import {
64
64
  stageAbsolutePathsAsAttachments,
65
65
  stageBuffersAsAttachments,
66
66
  syncWorkspacesFromThreads
67
- } from "./chunk-P2VCMIZU.js";
67
+ } from "./chunk-234EKD2F.js";
68
68
  import {
69
69
  addPrStackLayer,
70
70
  allocateTeamName,
@@ -114,7 +114,7 @@ import {
114
114
  worktreeDisplayLabelForGroup,
115
115
  worktreeNameFromPath,
116
116
  worktreeSlugForTicket
117
- } from "./chunk-Y7DJA3UW.js";
117
+ } from "./chunk-PXNOT6NV.js";
118
118
  import {
119
119
  ATTACHMENTS_DIR,
120
120
  DETACHED_JOBS_DIR,
@@ -167,7 +167,7 @@ import {
167
167
  stripNestedElectronEnv,
168
168
  updateAdvancedSettings,
169
169
  writeSecureJson
170
- } from "./chunk-YMF5WCIA.js";
170
+ } from "./chunk-OKZFTOM2.js";
171
171
  import {
172
172
  writePrivateFile
173
173
  } from "./chunk-NSTQ6QKD.js";
@@ -401,7 +401,7 @@ async function continueSourceThread(threadId, prompt) {
401
401
  await continueOnReply(threadId, prompt);
402
402
  return;
403
403
  }
404
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-67BPEJZL.js");
404
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-RW4IVITI.js");
405
405
  await getOrchestrator2().send(threadId, prompt);
406
406
  } catch {
407
407
  }
@@ -744,9 +744,9 @@ async function spawnAgentTurn(thread, input, onEvent) {
744
744
  `Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
745
745
  );
746
746
  }
747
- const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-4SI3CRRT.js");
747
+ const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-CRJ6ZVIY.js");
748
748
  if (isGlobalThread2(thread)) {
749
- const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-RRUU2OYO.js");
749
+ const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-PM6MPYYB.js");
750
750
  ensureGlobalCoordinatorCwd2(
751
751
  isOrchestratorThread(thread) ? { orchestratorThreadId: thread.id } : void 0
752
752
  );
@@ -957,6 +957,51 @@ function agentGitPrompt(action, opts) {
957
957
  return "Merge PR.";
958
958
  }
959
959
  }
960
+ var RESOLVE_CONFLICTS_RE = /^Merge the remote branch(?: \(([^)]+)\))? into your branch and resolve conflicts\. Then, commit and push your changes\.$/;
961
+ function gitActionExpansion(action, base) {
962
+ switch (action) {
963
+ case "commit-push":
964
+ return "Commit any uncommitted work with a purpose-stating message, then push to origin (`git push -u origin HEAD`; this updates an existing PR when one is linked). Do not start a checks loop unless a goal was given.";
965
+ case "create-draft":
966
+ return "Commit, push, then create a draft PR with `gh pr create --draft -R <origin-owner/name>` (title/body from the change purpose). Update the existing PR instead if one is linked.";
967
+ case "create-web":
968
+ return "Commit, push, then `gh pr create --web -R <origin-owner/name>`.";
969
+ case "resolve-conflicts":
970
+ return `Fetch the PR base${base ? ` (\`${base}\`)` : ""}, merge it into this branch, resolve conflicts carefully, then commit and push until the PR is mergeable.`;
971
+ case "merge":
972
+ return "This phrase is the explicit ask to merge this thread's open pull request on GitHub. If `gh stack view` shows a stack, use `gh stack merge`; otherwise `gh pr merge` (respect repo defaults / squash vs merge). Do not force-push main/master or merge locally into the main checkout.";
973
+ }
974
+ }
975
+ function expandCanonicalGitRequest(prompt) {
976
+ const trimmed = prompt.trim();
977
+ let action = null;
978
+ let base = null;
979
+ for (const candidate of AGENT_GIT_ACTIONS) {
980
+ if (candidate === "resolve-conflicts") continue;
981
+ if (agentGitPrompt(candidate) === trimmed) {
982
+ action = candidate;
983
+ break;
984
+ }
985
+ }
986
+ if (!action) {
987
+ const m = RESOLVE_CONFLICTS_RE.exec(trimmed);
988
+ if (m) {
989
+ action = "resolve-conflicts";
990
+ base = m[1] ?? null;
991
+ }
992
+ }
993
+ let expansion = action ? gitActionExpansion(action, base) : null;
994
+ if (!expansion && trimmed === "Address review comments.") {
995
+ expansion = "Read the PR review feedback (`gh pr view --comments`, `gh api` review comments), make the requested changes, commit, and push.";
996
+ } else if (!expansion && FIX_CI_RE.test(trimmed)) {
997
+ expansion = "Investigate that failing check (`gh pr checks`, `gh run view --log-failed`), fix it, commit, and push. Loop only if a goal to keep going until it passes was given.";
998
+ }
999
+ if (!expansion) return prompt;
1000
+ return `${trimmed}
1001
+
1002
+ (Sideboard git request \u2014 do not ask for clarification: ${expansion})`;
1003
+ }
1004
+ var FIX_CI_RE = /^Fix CI: .+\.$/;
960
1005
 
961
1006
  // src/git/pr-merge-archive.ts
962
1007
  function normalizePrState(state) {
@@ -1198,47 +1243,84 @@ function formatToolPart(t, detail) {
1198
1243
  }
1199
1244
  function formatMessagesAsTranscript(messages, opts) {
1200
1245
  const tools = opts?.tools ?? "full";
1201
- const blocks = [];
1202
- for (const m of messages) {
1203
- if (m.role === "summary") {
1204
- blocks.push(`## Prior summary
1205
- ${m.text}`);
1206
- continue;
1207
- }
1208
- if (m.role === "user") {
1209
- blocks.push(`### User
1246
+ const thinking = opts?.thinking ?? true;
1247
+ return messages.map((m) => formatMessageBlock(m, { tools, thinking })).join("\n\n");
1248
+ }
1249
+ function formatMessageBlock(m, opts) {
1250
+ if (m.role === "summary") return `## Prior summary
1251
+ ${m.text}`;
1252
+ if (m.role === "user") return `### User
1253
+ ${m.text}`;
1254
+ const bits = [];
1255
+ if (m.text?.trim()) {
1256
+ bits.push(`### Agent
1210
1257
  ${m.text}`);
1211
- continue;
1212
- }
1213
- const bits = [];
1214
- if (m.text?.trim()) {
1215
- bits.push(`### Agent
1216
- ${m.text}`);
1217
- } else {
1218
- bits.push("### Agent");
1219
- }
1220
- const thinking = (m.parts ?? []).filter((p) => p.type === "thinking");
1221
- for (const th of thinking) {
1258
+ } else {
1259
+ bits.push("### Agent");
1260
+ }
1261
+ if (opts.thinking) {
1262
+ for (const th of m.parts ?? []) {
1222
1263
  if (th.type !== "thinking" || !th.text.trim()) continue;
1223
1264
  bits.push("Thinking:");
1224
1265
  bits.push(th.text);
1225
1266
  }
1226
- const toolParts = (m.parts ?? []).filter(
1227
- (p) => p.type === "tool"
1228
- );
1229
- if (tools !== "none" && toolParts.length > 0) {
1230
- if (tools === "summary") bits.push("Tools:");
1231
- for (const t of toolParts) {
1232
- bits.push(formatToolPart(t, tools));
1233
- }
1267
+ }
1268
+ const toolParts = (m.parts ?? []).filter(
1269
+ (p) => p.type === "tool"
1270
+ );
1271
+ if (opts.tools !== "none" && toolParts.length > 0) {
1272
+ if (opts.tools === "summary") bits.push("Tools:");
1273
+ for (const t of toolParts) {
1274
+ bits.push(formatToolPart(t, opts.tools));
1234
1275
  }
1235
- blocks.push(bits.join("\n"));
1236
1276
  }
1237
- return blocks.join("\n\n");
1277
+ return bits.join("\n");
1238
1278
  }
1279
+ var SEED_MAX_CHARS = 6e4;
1280
+ var SEED_FULL_TOOL_MESSAGES = 6;
1239
1281
  function buildSessionSeed(messages, opts) {
1240
1282
  if (messages.length === 0) return null;
1241
- const body = formatMessagesAsTranscript(messages, { tools: opts?.tools ?? "full" });
1283
+ const tools = opts?.tools ?? "full";
1284
+ const fullCount = opts?.fullToolMessages ?? SEED_FULL_TOOL_MESSAGES;
1285
+ const maxChars = opts?.maxChars ?? SEED_MAX_CHARS;
1286
+ const olderTools = tools === "none" ? "none" : "summary";
1287
+ const fullFrom = Math.max(0, messages.length - fullCount);
1288
+ const blocks = messages.map(
1289
+ (m, i) => formatMessageBlock(m, {
1290
+ tools: i >= fullFrom ? tools : olderTools,
1291
+ thinking: false
1292
+ })
1293
+ );
1294
+ const keep = new Array(blocks.length).fill(false);
1295
+ let used = 0;
1296
+ for (let i = 0; i < blocks.length; i++) {
1297
+ if (messages[i].role === "summary") {
1298
+ keep[i] = true;
1299
+ used += blocks[i].length + 2;
1300
+ }
1301
+ }
1302
+ for (let i = blocks.length - 1; i >= 0; i--) {
1303
+ if (keep[i]) continue;
1304
+ const next = used + blocks[i].length + 2;
1305
+ if (next > maxChars && used > 0) break;
1306
+ keep[i] = true;
1307
+ used = next;
1308
+ }
1309
+ const dropped = keep.filter((k) => !k).length;
1310
+ const kept = [];
1311
+ let markerPlaced = false;
1312
+ for (let i = 0; i < blocks.length; i++) {
1313
+ if (!keep[i]) continue;
1314
+ if (dropped > 0 && !markerPlaced && messages[i].role !== "summary") {
1315
+ kept.push(`_(${dropped} older message${dropped === 1 ? "" : "s"} omitted for length)_`);
1316
+ markerPlaced = true;
1317
+ }
1318
+ kept.push(blocks[i]);
1319
+ }
1320
+ if (dropped > 0 && !markerPlaced) {
1321
+ kept.push(`_(${dropped} older message${dropped === 1 ? "" : "s"} omitted for length)_`);
1322
+ }
1323
+ const body = kept.join("\n\n");
1242
1324
  if (!body.trim()) return null;
1243
1325
  return [
1244
1326
  "Sideboard conversation context (restored after compaction or a new session):",
@@ -1292,7 +1374,7 @@ function findLastBrightsyContextSummary(messages) {
1292
1374
  function buildBrightsySessionSeed(messages) {
1293
1375
  const match = findLastBrightsyContextSummary(messages);
1294
1376
  const tail = match ? messages.slice(match.index + 1) : messages;
1295
- const body = formatMessagesAsTranscript(tail, { tools: "none" });
1377
+ const body = formatMessagesAsTranscript(tail, { tools: "none", thinking: false });
1296
1378
  if (!match && !body.trim()) return null;
1297
1379
  const blocks = [
1298
1380
  "Sideboard conversation context (restored after compaction or a new session):",
@@ -3361,7 +3443,7 @@ async function createThread(input, _onSetupLine) {
3361
3443
  });
3362
3444
  await requireAgent(resolved.agent);
3363
3445
  if (input.cowboy) {
3364
- const { cowboyModeEnabled } = await import("./app-settings-LF3TS7XS.js");
3446
+ const { cowboyModeEnabled } = await import("./app-settings-74HBAAH7.js");
3365
3447
  if (!cowboyModeEnabled()) {
3366
3448
  throw new Error(
3367
3449
  "Cowboy mode is off. Enable it in Settings \u2192 Advanced."
@@ -3419,7 +3501,7 @@ async function createThread(input, _onSetupLine) {
3419
3501
  let sourceIsFork = false;
3420
3502
  if (!input.cowboy && sourceType === "branch" && (!sourceRef || sourceRef === "HEAD" || sourceRef === "default")) {
3421
3503
  if (isAbleTimeConnected() && getIssueSource() === "abletime") {
3422
- const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-ESVTHTFH.js");
3504
+ const { ensureAbleTimeTask, issueAttachmentForAbleTimeTask } = await import("./abletime-PCLL36HW.js");
3423
3505
  const task = await ensureAbleTimeTask({
3424
3506
  title: title?.trim() || "Untitled work",
3425
3507
  description: input.prompt?.trim() || void 0
@@ -3927,7 +4009,7 @@ async function adoptThread(input) {
3927
4009
  messages: input.messages ?? []
3928
4010
  });
3929
4011
  writeThread(thread);
3930
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-NOC2MWQV.js");
4012
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-L73MCDFA.js");
3931
4013
  await ensureWorkspace2(repoPath);
3932
4014
  const { ensureWorktreeSideboardIgnored: ensureWorktreeSideboardIgnored2 } = await import("./worktree-exclude-N5X5I422.js");
3933
4015
  await ensureWorktreeSideboardIgnored2(input.worktreePath);
@@ -5599,7 +5681,7 @@ function expandComposerPrompt(worktreePath, prompt, opts) {
5599
5681
  }
5600
5682
 
5601
5683
  // src/agents/instructions.ts
5602
- import { existsSync as existsSync16, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
5684
+ import { existsSync as existsSync16 } from "fs";
5603
5685
  import { join as join15 } from "path";
5604
5686
 
5605
5687
  // src/skills/detached-job-path.ts
@@ -5689,67 +5771,38 @@ function formatWorktreeDirective(thread, opts) {
5689
5771
  lines.push(
5690
5772
  "- Always use this worktree's `origin` remote (`git remote get-url origin` from this cwd). Never push to or open PRs against `upstream` (template remotes)."
5691
5773
  );
5692
- lines.push(
5693
- "- Push with `git push -u origin HEAD` (or the current branch name). Do not `git push upstream`."
5694
- );
5695
- lines.push("");
5696
5774
  lines.push(formatGitAuthModeDirective(opts?.gitAuthMode ?? "auto"));
5697
5775
  lines.push(
5698
- "- Derive the PR title and body from what the changes actually do and why \u2014 inspect the diff/commits and the user request. Do not use the soccer-team worktree nickname or placeholder branch as the PR title."
5699
- );
5700
- lines.push(
5701
- "- Prefer a concise imperative title (Conventional Commits style when it fits: feat:/fix:/chore:/docs:). Body should summarize intent, key changes, and test notes."
5776
+ "- PR title/body and commit messages state what the change does and why (inspect the diff/commits and the user request) \u2014 never the worktree nickname or placeholder branch. Concise imperative title (Conventional Commits when it fits: feat:/fix:/chore:/docs:); body = intent, key changes, test notes."
5702
5777
  );
5703
5778
  lines.push(
5704
- "- Commit with messages that state the purpose of the change (same standard as the PR). Stay on this thread branch. Never push directly to main/master or merge locally into the main checkout. Do not merge the PR unless this turn is a Merge PR request or the user explicitly asked. When merging, use GitHub from this worktree (`gh pr merge` / `gh stack merge`)."
5779
+ "- Stay on this thread branch. Never push to main/master or merge locally into the main checkout. Merge the PR only when this turn is a Merge PR request or the user explicitly asked \u2014 via GitHub from this worktree (`gh pr merge` / `gh stack merge`)."
5705
5780
  );
5706
5781
  if (thread.prUrl) {
5707
5782
  lines.push(
5708
5783
  `- A PR already exists (${thread.prUrl}). Update it (push + edit title/body if the purpose drifted) instead of opening a duplicate.`
5709
5784
  );
5710
- } else if (opts?.githubSlug) {
5711
- lines.push(
5712
- `- Prefer a draft PR first: \`gh pr create --draft -R ${opts.githubSlug}\` (or update via \`gh pr edit -R ${opts.githubSlug}\`) once the change set is coherent. Always pass \`-R ${opts.githubSlug}\` (this worktree's origin). Bare \`gh pr create\` may target upstream instead of origin on dual-remote checkouts. Mark ready for review only when asked. Title/body must reflect the change purpose, not the worktree name. If GitHub rejects the GraphQL body as too long, retry with a short \`--body-file\` (limit 65,536 characters) \u2014 do not paste a changelog or diff into \`--body\`.`
5713
- );
5714
5785
  } else {
5786
+ const slug = opts?.githubSlug ?? "<origin-owner/name>";
5787
+ const resolve = opts?.githubSlug ? "" : " Resolve `<origin-owner/name>` with `git remote get-url origin` in this worktree.";
5715
5788
  lines.push(
5716
- "- Prefer a draft PR first: `gh pr create --draft -R <origin-owner/name>` (or update via `gh pr edit -R \u2026`) once the change set is coherent. Resolve `<origin-owner/name>` with `git remote get-url origin` in this worktree \u2014 never from `upstream`. Mark ready for review only when asked. Title/body must reflect the change purpose, not the worktree name. If GitHub rejects the GraphQL body as too long, retry with a short `--body-file` (limit 65,536 characters) \u2014 do not paste a changelog or diff into `--body`."
5789
+ `- Prefer a draft PR first: \`gh pr create --draft -R ${slug}\` (update via \`gh pr edit -R ${slug}\`). Always pass \`-R\` \u2014 bare \`gh pr create\` may target upstream on dual-remote checkouts.${resolve} Mark ready for review only when asked. If GitHub rejects the body as too long, retry with a short \`--body-file\` \u2014 do not paste a changelog or diff into \`--body\`.`
5717
5790
  );
5718
5791
  }
5719
- lines.push("");
5720
- lines.push(
5721
- "Short git requests from the Sideboard UI are complete instructions \u2014 expand them using the rules above without asking for clarification:"
5722
- );
5723
- lines.push(
5724
- '- "Commit and push." \u2192 commit any uncommitted work with a purpose-stating message, then push to origin (updates an existing PR if one is linked). Do not start a checks loop unless they gave a goal.'
5725
- );
5726
- lines.push(
5727
- '- "Commit, push, and open a draft PR." \u2192 commit, push, then create a draft PR with `gh pr create --draft -R \u2026` (title/body from the change purpose).'
5728
- );
5729
5792
  lines.push(
5730
- '- "Commit, push, and open a PR in the browser." \u2192 commit, push, then `gh pr create --web -R \u2026`.'
5793
+ '- Sideboard git buttons send short phrases ("Commit and push.", "Merge PR.", \u2026) with their meaning attached \u2014 act on them without asking for clarification. If the user gives a goal (Greptile 5/5, CI green, until checks pass), you get the watch-fix-push playbook with that request.'
5731
5794
  );
5732
- lines.push(
5733
- '- "Fix CI: <name>." \u2192 investigate that failing check, fix it, commit, and push. Loop only if they gave a goal to keep going until it passes.'
5734
- );
5735
- lines.push(
5736
- '- "Merge the remote branch (<base>) into your branch and resolve conflicts. Then, commit and push your changes." \u2192 fetch the PR base, merge it into this branch, resolve conflicts carefully, commit, and push until the PR is mergeable.'
5737
- );
5738
- lines.push(
5739
- '- "Address review comments." \u2192 read PR review feedback, make the requested changes, commit, and push.'
5740
- );
5741
- lines.push(
5742
- '- "Merge PR." \u2192 merge this thread\'s open pull request on GitHub (this phrase is the explicit ask). If `gh stack view` shows a stack, use `gh stack merge`; otherwise `gh pr merge` (respect repo defaults / squash vs merge). Do not force-push main/master or merge locally into the main checkout.'
5743
- );
5744
- lines.push("");
5745
- lines.push(formatPrGateDirective());
5746
5795
  lines.push("");
5747
- lines.push(formatProcessGuideDirective());
5796
+ lines.push(formatProcessGuideDirective({ worktreePath: thread.worktreePath }));
5748
5797
  return lines.join("\n");
5749
5798
  }
5750
5799
  function formatWorktreeReminder() {
5751
5800
  return "Sideboard worktree: stay in this cwd for all file and git work. Push and open PRs against origin, never upstream. Do not edit the main repo checkout. If a goal is given (Greptile 5/5, CI green), watch-fix-push until it lands \u2014 do not watch after every push.";
5752
5801
  }
5802
+ var 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;
5803
+ function mentionsPrGoal(prompt) {
5804
+ return PR_GOAL_RE.test(prompt);
5805
+ }
5753
5806
  var GITHUB_TICKET_REF = /^(?:#?\d+|gh-\d+)$/i;
5754
5807
  var KEYED_TICKET_REF = /^(?:[A-Z][A-Z0-9]{0,9}-\d+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
5755
5808
  function issueTicketFromThread(thread, preferredSource = "github") {
@@ -5818,29 +5871,27 @@ function formatPrGateDirective() {
5818
5871
  "- Greptile 5/5: wait for the greptile check; read the latest `N/5` from the PR body or Greptile\u2019s most recently *updated* issue comment; also fetch unresolved review comments / the \u201CPrompt to fix all with AI\u201D block. Done = **5/5 and zero unresolved Greptile comments**. If Greptile is idle, comment `@greptile review`. Cap about 5 iterations, then report leftovers. Do not merge unless asked."
5819
5872
  ].join("\n");
5820
5873
  }
5821
- function formatProcessGuideDirective() {
5874
+ function formatProcessGuideDirective(opts) {
5875
+ const worktree = opts?.worktreePath?.trim();
5876
+ const hasGraphSkill = !!worktree && existsSync16(join15(worktree, ".claude/skills/graph-engineering/SKILL.md"));
5877
+ const hasReviewSkill = !!worktree && existsSync16(join15(worktree, ".claude/skills/review/SKILL.md"));
5822
5878
  return [
5823
5879
  "Process guides (recurring work only):",
5824
- "- Long jobs (pack, test, deploy, anything that may run more than ~30s): Sideboard always provides `/long-running`. Detach and wait; `stop_job` if hanging or wrong. Do not ask the human to poll.",
5825
- "- If `.claude/skills/graph-engineering/SKILL.md` exists, follow it (`/graph-engineering`) for migrations, ports, batch fixes, and other fan-out. Judge first; state on disk; grow the rulebook; do not patch around it.",
5826
- "- If this same shape of work will happen again, write `.claude/skills/<kebab-name>/SKILL.md` in this worktree (Claude Code project skill). Sideboard `/name`, Claude Code, and `attach` all load that path. Do not leave the method only in chat.",
5827
- "- Merge-readiness notes: if `.claude/skills/review/SKILL.md` already exists, edit it. Otherwise write them to `.context/review.md` (copied from `.sideboard/review.md` when that file exists). Do not create a review skill.",
5828
- "- Do not write new skills under `.sideboard/skills/` (that folder only \u2014 other `.sideboard/` files such as settings.toml are fine). Point Codex/OpenCode at the Claude skill from `AGENTS.md`. Optional: symlink `.cursor/skills/<name>` to the Claude skill.",
5880
+ hasGraphSkill ? "- Migrations, ports, batch fixes, and other fan-out: follow `.claude/skills/graph-engineering/SKILL.md` (`/graph-engineering`). Judge first; state on disk; grow the rulebook; do not patch around it." : null,
5881
+ "- If this same shape of work will happen again, write `.claude/skills/<kebab-name>/SKILL.md` in this worktree (Claude Code project skill; Sideboard `/name`, Claude Code, and `attach` all load it). Not under `.sideboard/skills/`. Point Codex/OpenCode at it from `AGENTS.md`.",
5882
+ hasReviewSkill ? "- Merge-readiness notes go in `.claude/skills/review/SKILL.md` (edit it; do not create another review skill)." : "- Merge-readiness notes go in `.context/review.md` (copied from `.sideboard/review.md` when that file exists). Do not create a review skill.",
5829
5883
  "- Skip a guide for a one-off. If a matching skill exists, follow it. Same miss twice \u2192 edit the skill, do not patch around it."
5830
- ].join("\n");
5884
+ ].filter(Boolean).join("\n");
5831
5885
  }
5832
5886
  function formatLongRunningDirective(opts) {
5833
5887
  const invoke = formatDetachedJobInvoke(opts?.scriptPath);
5834
5888
  return [
5835
- "Long-running jobs (mandatory when a command may run more than ~30s):",
5836
- "A Sideboard worktree turn SIGTERMs the agent shell (and its process group) when the user sends another message or the turn is interrupted. `block_until_ms: 0` is not enough. Do not ask the human to poll.",
5837
- `Helper (same tool as \`scripts/detached-job.cjs\` when that file exists in the worktree): \`${invoke}\``,
5838
- `- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
5839
- "- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
5840
- `- Loop Sideboard MCP \`wait_for_job\` with the same id (returns in ~45s). stillRunning \u2192 present the same id with \`content=delta\` only \u2192 wait_for_job again. Shell fallback: \`${invoke} wait <id>\`. Do not resend the full log or HTML.`,
5841
- `- If it is hanging, producing no useful output, or doing the wrong thing: MCP \`stop_job\` (same id, optional reason) or \`${invoke} stop <id>\`. Do not loop forever. Do not stop a pack/test/deploy that is clearly making progress.`,
5842
- "- Do not end the turn with \u201CI\u2019ll let you know when it\u2019s done.\u201D Stay in the loop until stillRunning is false (or you stopped it).",
5843
- "- ok \u2192 finish the task. failed / stopped \u2192 read the log, fix or narrow the command, start once.",
5889
+ "Long-running jobs (mandatory when a command may run more than ~30s \u2014 pack, test, deploy, `gh pr checks --watch`):",
5890
+ "A Sideboard worktree turn SIGTERMs the agent shell (and its process group) when the user sends another message or the turn is interrupted; `block_until_ms: 0` is not enough. Detach instead, and never ask the human to poll.",
5891
+ `- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree; same tool as \`scripts/detached-job.cjs\` when the worktree has it). If JSON says already-running, do not start again.`,
5892
+ "- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running`, then loop Sideboard MCP `wait_for_job` (returns in ~45s). stillRunning \u2192 present the same id with `content=delta` only \u2192 wait again. Shell fallback: the same helper with `wait <id>`.",
5893
+ "- Hanging, no useful output, or the wrong thing \u2192 `stop_job` (or the helper with `stop <id>`). Do not stop a pack/test/deploy that is clearly making progress.",
5894
+ "- Stay in the loop until stillRunning is false (or you stopped it). ok \u2192 finish the task. failed / stopped \u2192 read the log, fix or narrow the command, start once.",
5844
5895
  "State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
5845
5896
  ].join("\n");
5846
5897
  }
@@ -5850,27 +5901,11 @@ function formatLongRunningReminder(opts) {
5850
5901
  }
5851
5902
  function formatArtifactDirective() {
5852
5903
  return [
5853
- "Sideboard side column (desktop UI):",
5854
- "claude.ai\u2019s \u201CArtifact\u201D tool does NOT exist in Claude Code. That is expected.",
5855
- "Do not unprompted-duplicate a payload. Chat markdown (including tables) already renders in the transcript \u2014 do not also call present_schema with those same rows just to display them. If the user asks for an editable / interactive table, call present_schema even if markdown already showed the data. Do not also call present_artifact for a document you already fenced in chat.",
5856
- "Documents (HTML/SVG/markdown):",
5857
- "1) Emit a fenced code block tagged `html` (preferred), `svg`, or `markdown` with the FULL document \u2014 Sideboard opens a side column. Example:",
5858
- "```html",
5859
- "<!DOCTYPE html><html><head><title>Demo</title></head><body><h1>Hi</h1></body></html>",
5860
- "```",
5861
- "2) Or call Sideboard MCP `present_artifact` with title, type (html|svg|markdown|react|log), and content \u2014 not both a fence and this tool for the same body.",
5862
- " type=log is append-only: same `artifact_id`, `content` = new lines only (plus optional status/phase). Do not resend the full log or wrap it in HTML.",
5863
- "CMS / JSON Schema forms & tables (Brightsy or any schema+schemaUi source):",
5864
- "3) Call Sideboard MCP `present_schema` when the user needs to filter, edit, publish, or persist rows \u2014 including after you already showed a markdown table, if they then ask for an editable table. If they only need to read the data, a markdown table is enough. When you do call it, pass title, mode (table|form), and either:",
5865
- " - datasource=brightsy + resource_id (record type UUID) after fetching types via Brightsy MCP, or",
5866
- " - datasource=inline + resource: { id, title, schema, schemaUi } and optional records/record.",
5867
- "Files / media browser (CMS file manager column):",
5868
- "4) Call Sideboard MCP `present_files` with optional title, path, and datasource (brightsy|memory).",
5869
- " Opens the Files column for browse/upload/pick. Do NOT say a file manager UI is missing.",
5870
- "Multiple-choice questions:",
5871
- "5) Call Sideboard MCP `ask_user` only when work is blocked on choosing among a few concrete options (approach forks, which API, auth vs cookies). First write a short chat message that explains the decision and what each option means (tradeoffs, when to pick it). Include a description on every option. After calling, stop and wait for their next message with answers. If you are asking a real multiple-choice, use ask_user rather than chat bullets so Sideboard shows the composer picker.",
5872
- "Do not call ask_user for greetings, check-ins, \u201Chello\u201D, open-ended how-can-I-help, or to invent a menu of possible next tasks \u2014 reply in chat. If one option is the obvious default, proceed without asking.",
5873
- "Never say artifacts, CMS UI, or the Files column are unavailable. present_schema is for interactive list/edit/publish (use it when they ask to edit, even if chat already had a markdown table); present_files for storage UI; html fences for standalone pages; ask_user only for those blocked predefined-option questions."
5904
+ "Sideboard side column (desktop UI) \u2014 Sideboard MCP tools present_artifact, present_schema, present_files, ask_user (see their descriptions). claude.ai\u2019s \u201CArtifact\u201D tool does not exist here; never say artifacts, the CMS UI, or the Files column are unavailable.",
5905
+ "- Standalone documents: a fenced block tagged `html` (preferred), `svg`, or `markdown` containing the FULL document opens the side column by itself. Use that or present_artifact \u2014 never both for the same body.",
5906
+ "- present_artifact type=log appends: same artifact_id, content = new lines only (plus status/phase). Do not resend the full log or wrap it in HTML.",
5907
+ "- Data: a markdown table is enough to read. Call present_schema only when the user needs to filter/edit/publish/persist rows \u2014 including when they ask for an editable table after you already showed markdown. Never re-present rows you already wrote just to display them.",
5908
+ "- ask_user only when work is blocked on a few concrete options (approach fork, which API, auth vs cookies): first a short chat message explaining the decision and each option, then the call (description on every option), then stop and wait. Not for greetings, check-ins, or an invented menu of next tasks \u2014 reply in chat. If one option is the obvious default, proceed."
5874
5909
  ].join("\n");
5875
5910
  }
5876
5911
  function formatUiReminder() {
@@ -6012,7 +6047,7 @@ async function syncThreadBranchFromGit(threadId) {
6012
6047
 
6013
6048
  // src/store/schedules.ts
6014
6049
  import { randomUUID as randomUUID4 } from "crypto";
6015
- import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
6050
+ import { existsSync as existsSync17, readFileSync as readFileSync12 } from "fs";
6016
6051
  import { join as join16 } from "path";
6017
6052
  import { Cron } from "croner";
6018
6053
  var DURATION_RE = /^(\d+)(s|m|h|d)$/i;
@@ -6125,7 +6160,7 @@ function readAll() {
6125
6160
  const path = schedulesPath();
6126
6161
  if (!existsSync17(path)) return [];
6127
6162
  try {
6128
- const parsed = JSON.parse(readFileSync13(path, "utf8"));
6163
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
6129
6164
  if (!Array.isArray(parsed)) return [];
6130
6165
  return parsed.map((row) => {
6131
6166
  try {
@@ -6248,7 +6283,7 @@ function formatScheduledPrompt(name, prompt) {
6248
6283
  ${prompt}`;
6249
6284
  }
6250
6285
  async function defaultDeps() {
6251
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-67BPEJZL.js");
6286
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-RW4IVITI.js");
6252
6287
  const orch = getOrchestrator2();
6253
6288
  return {
6254
6289
  findThread: (id) => findThreadByRef(id),
@@ -6510,7 +6545,7 @@ var Orchestrator = class {
6510
6545
  orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
6511
6546
  });
6512
6547
  }
6513
- const { autoCleanupOrphansEnabled } = await import("./app-settings-LF3TS7XS.js");
6548
+ const { autoCleanupOrphansEnabled } = await import("./app-settings-74HBAAH7.js");
6514
6549
  if (autoCleanupOrphansEnabled() && shouldRunWorktreeCleanup() && orphans.length > 0) {
6515
6550
  await cleanupOrphanWorktrees({ repoPaths });
6516
6551
  }
@@ -6789,7 +6824,7 @@ var Orchestrator = class {
6789
6824
  }
6790
6825
  await setup;
6791
6826
  if (skipSetup) return;
6792
- const { autoRunAfterSetupEnabled } = await import("./app-settings-LF3TS7XS.js");
6827
+ const { autoRunAfterSetupEnabled } = await import("./app-settings-74HBAAH7.js");
6793
6828
  if (autoRunAfterSetupEnabled()) {
6794
6829
  try {
6795
6830
  await this.startDev(threadId);
@@ -7072,17 +7107,21 @@ var Orchestrator = class {
7072
7107
  const slackReplyContext = formatSlackRepliesForTurn(
7073
7108
  pendingSlackExternalReplies(thread.messages)
7074
7109
  );
7075
- const agentPrompt = [
7076
- thread.planMode ? PLAN_MODE_INSTRUCTION : null,
7077
- orchestrationReminder,
7110
+ const turnReminders = [
7078
7111
  worktreeReminder,
7079
7112
  optionalServicesReminder,
7080
7113
  issueToolsReminder,
7081
7114
  viewerContextReminder,
7082
7115
  artifactReminder,
7083
- longRunningReminder,
7116
+ longRunningReminder
7117
+ ].filter(Boolean).join("\n\n") || void 0;
7118
+ const prGateDirective = thread.agent !== "brightsy" && !isOrchestratorThread(thread) && mentionsPrGoal(expandedPrompt) ? formatPrGateDirective() : null;
7119
+ const agentPrompt = [
7120
+ thread.planMode ? PLAN_MODE_INSTRUCTION : null,
7121
+ orchestrationReminder,
7122
+ prGateDirective,
7084
7123
  slackReplyContext,
7085
- expandedPrompt
7124
+ expandCanonicalGitRequest(expandedPrompt)
7086
7125
  ].filter(Boolean).join("\n\n");
7087
7126
  if (thread.attachments.length > 0) {
7088
7127
  updateThread(threadId, { attachments: [] });
@@ -7100,7 +7139,7 @@ var Orchestrator = class {
7100
7139
  }
7101
7140
  const isBrightsy = fresh.agent === "brightsy";
7102
7141
  const isOrchestration = isOrchestratorThread(fresh);
7103
- const { autoRenameBranchEnabled, getGithubGitAuthMode } = await import("./app-settings-LF3TS7XS.js");
7142
+ const { autoRenameBranchEnabled, getGithubGitAuthMode } = await import("./app-settings-74HBAAH7.js");
7104
7143
  const gitAuthMode = getGithubGitAuthMode();
7105
7144
  const worktreeDirective = isBrightsy || isOrchestration ? null : formatWorktreeDirective(fresh, {
7106
7145
  githubSlug: await resolveGithubRepoSlug(fresh.worktreePath).catch(
@@ -7172,7 +7211,7 @@ var Orchestrator = class {
7172
7211
  const stderrTail = [];
7173
7212
  const handle = await spawnAgentTurn(
7174
7213
  fresh,
7175
- { cachedPrefix, prompt: agentPrompt },
7214
+ { cachedPrefix, prompt: agentPrompt, systemPrompt: turnReminders },
7176
7215
  (event) => {
7177
7216
  this.emit({ type: "turn_output", threadId, event });
7178
7217
  noteTurnLiveEvent(threadId, event);
@@ -7281,7 +7320,7 @@ var Orchestrator = class {
7281
7320
  ].filter(Boolean).join("\n\n---\n\n");
7282
7321
  const retryHandle = await spawnAgentTurn(
7283
7322
  retryThread,
7284
- { cachedPrefix: retryPrefix, prompt: agentPrompt },
7323
+ { cachedPrefix: retryPrefix, prompt: agentPrompt, systemPrompt: turnReminders },
7285
7324
  (event) => {
7286
7325
  this.emit({ type: "turn_output", threadId, event });
7287
7326
  if (event.type === "session_id") {
@@ -8012,7 +8051,7 @@ var Orchestrator = class {
8012
8051
  updateThread(thread.id, { title: meta.title });
8013
8052
  }
8014
8053
  }
8015
- const { autoArchiveOnMergeEnabled } = await import("./app-settings-LF3TS7XS.js");
8054
+ const { autoArchiveOnMergeEnabled } = await import("./app-settings-74HBAAH7.js");
8016
8055
  const latest = this.requireThread(thread.id);
8017
8056
  if (!shouldAutoArchiveOnPrMerge({
8018
8057
  previousPrState: prevState || null,
@@ -8329,7 +8368,7 @@ var Orchestrator = class {
8329
8368
  this.emit({ type: "status_changed", threadId: archived.id, status: "archived" });
8330
8369
  if (thread.repoPath && !isGlobalRepoPath(thread.repoPath)) {
8331
8370
  try {
8332
- const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-NOC2MWQV.js");
8371
+ const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-L73MCDFA.js");
8333
8372
  await ensureWorkspace2(thread.repoPath);
8334
8373
  } catch {
8335
8374
  }
@@ -8356,7 +8395,7 @@ var Orchestrator = class {
8356
8395
  await runArchiveScript(thread.repoPath, thread.worktreePath);
8357
8396
  } catch {
8358
8397
  }
8359
- const { deleteBranchOnPurgeEnabled } = await import("./app-settings-LF3TS7XS.js");
8398
+ const { deleteBranchOnPurgeEnabled } = await import("./app-settings-74HBAAH7.js");
8360
8399
  const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled();
8361
8400
  await removeWorktree(thread.repoPath, thread.worktreePath, {
8362
8401
  deleteBranch: deleteBranch ? thread.branchName : void 0
@@ -8383,7 +8422,7 @@ var Orchestrator = class {
8383
8422
  `Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
8384
8423
  );
8385
8424
  }
8386
- const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-GXO2VDQF.js");
8425
+ const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-SQD3DBYC.js");
8387
8426
  const slug = thread.worktreePath.split("/").pop();
8388
8427
  const dest = thread.worktreePath;
8389
8428
  await withRepoGitLock(thread.repoPath, async () => {
@@ -8493,7 +8532,7 @@ async function startOrchestration(opts) {
8493
8532
  sourceRef: "default",
8494
8533
  ...createOpts
8495
8534
  }).catch(async () => {
8496
- const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-GXO2VDQF.js");
8535
+ const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-SQD3DBYC.js");
8497
8536
  const repo = await resolveRepoRoot2(repoPath);
8498
8537
  const def = await resolveDefaultBranch2(repo);
8499
8538
  return orch.createThread({
@@ -7,10 +7,10 @@ import {
7
7
  enrichWorkspacesWithGithub,
8
8
  ensureGlobalCoordinatorCwd,
9
9
  formatWorkspaceInventory
10
- } from "./chunk-DHPZW2EA.js";
11
- import "./chunk-3C4B52TS.js";
10
+ } from "./chunk-2RM6BZQ2.js";
11
+ import "./chunk-QYZYDO3F.js";
12
12
  import "./chunk-6K64FJDD.js";
13
- import "./chunk-7IUO67CW.js";
13
+ import "./chunk-J7LQVLFQ.js";
14
14
  import "./chunk-4TR3HZFT.js";
15
15
  import "./chunk-JT7R45JB.js";
16
16
  import "./chunk-7UURXPKD.js";
@@ -9,13 +9,13 @@ import {
9
9
  enrichWorkspacesWithGithub,
10
10
  ensureGlobalCoordinatorCwd,
11
11
  formatWorkspaceInventory
12
- } from "./chunk-P2VCMIZU.js";
13
- import "./chunk-Y7DJA3UW.js";
12
+ } from "./chunk-234EKD2F.js";
13
+ import "./chunk-PXNOT6NV.js";
14
14
  import "./chunk-D6JWYJ4P.js";
15
15
  import "./chunk-LZQULFIT.js";
16
16
  import "./chunk-SYFVUF2C.js";
17
17
  import "./chunk-FNQNJOHY.js";
18
- import "./chunk-YMF5WCIA.js";
18
+ import "./chunk-OKZFTOM2.js";
19
19
  import "./chunk-NSTQ6QKD.js";
20
20
  import "./chunk-KGZBYWZ3.js";
21
21
  import "./chunk-JTJHOYCU.js";
@@ -15,10 +15,10 @@ import {
15
15
  orchestratorSessionPoisonedByBuiltins,
16
16
  slackCoordinatorSourceRef,
17
17
  takenTeamSlugsForOrchestration
18
- } from "./chunk-DHPZW2EA.js";
19
- import "./chunk-3C4B52TS.js";
18
+ } from "./chunk-2RM6BZQ2.js";
19
+ import "./chunk-QYZYDO3F.js";
20
20
  import "./chunk-6K64FJDD.js";
21
- import "./chunk-7IUO67CW.js";
21
+ import "./chunk-J7LQVLFQ.js";
22
22
  import "./chunk-4TR3HZFT.js";
23
23
  import "./chunk-JT7R45JB.js";
24
24
  import "./chunk-7UURXPKD.js";