@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.
- package/dist/{agents-HIEJL3UV.js → agents-ON6RKKND.js} +1 -1
- package/dist/{agents-5ROTZNCX.js → agents-YKSS6VBO.js} +1 -1
- package/dist/{chunk-7EUWSBWR.js → chunk-6QTZVJ7A.js} +5 -3
- package/dist/chunk-B3SJXYIJ.js +24 -0
- package/dist/{chunk-5ZPSH7VI.js → chunk-D3METLRW.js} +5 -3
- package/dist/{chunk-ZH5QZ4CR.js → chunk-DZFH2KLT.js} +280 -7
- package/dist/chunk-FKOIHGKV.js +21 -0
- package/dist/{chunk-E4VVEKAM.js → chunk-GNML24AW.js} +2 -2
- package/dist/{chunk-EOYDCKQC.js → chunk-HLEX5AQ6.js} +4 -0
- package/dist/{chunk-K3WMKGFY.js → chunk-LRLKJM3O.js} +2 -1
- package/dist/chunk-N5PM7HGQ.js +103 -0
- package/dist/chunk-QTUESPAW.js +101 -0
- package/dist/{chunk-O6W3P7V3.js → chunk-TSRXOSVD.js} +4 -0
- package/dist/{chunk-F4Q3IM6V.js → chunk-UEAHMGHW.js} +2 -1
- package/dist/{chunk-XRSAGVRW.js → chunk-XOU6HNQJ.js} +245 -7
- package/dist/{chunk-O5DOO7DP.js → chunk-XX5BB7NV.js} +3 -3
- package/dist/{chunk-QN7XNQAT.js → chunk-YDXQ72MD.js} +2 -2
- package/dist/{chunk-YFJ4FG2P.js → chunk-YOWIYAVA.js} +3 -3
- package/dist/{coordinator-prompt-7HHJRO7B.js → coordinator-prompt-6FXVTSFN.js} +4 -3
- package/dist/{coordinator-prompt-WD7FAMA2.js → coordinator-prompt-S6JZD5EF.js} +4 -3
- package/dist/{global-workspace-OJEPGDXA.js → global-workspace-EV4G2WMQ.js} +5 -4
- package/dist/{global-workspace-ECYN2MKL.js → global-workspace-MSX2K27Y.js} +5 -4
- package/dist/index.cjs +1288 -140
- package/dist/index.d.cts +388 -15
- package/dist/index.d.ts +388 -15
- package/dist/index.js +801 -84
- package/dist/mcp/run-stdio.cjs +1059 -132
- package/dist/mcp/run-stdio.js +628 -73
- package/dist/plan-file-6O7G4VPQ.js +23 -0
- package/dist/plan-file-PHVKUAEE.js +25 -0
- package/dist/{thread-store-XICUWFNM.js → thread-store-GHOADGL2.js} +1 -1
- package/dist/{thread-store-OV2X6PYO.js → thread-store-UJIGMI5J.js} +1 -1
- package/dist/{workspaces-MUU7RGVV.js → workspaces-3RQQZQRO.js} +6 -5
- package/dist/{workspaces-ZWOOFZUV.js → workspaces-AYTBR6KQ.js} +6 -5
- package/dist/{worktree-DVNDMWZ7.js → worktree-5KEQWSAF.js} +5 -2
- package/dist/{worktree-GDV56MX4.js → worktree-RWGL7FUV.js} +5 -2
- package/package.json +1 -1
|
@@ -534,7 +534,9 @@ var SIDEBOARD_MCP_ALLOWED_TOOLS = [
|
|
|
534
534
|
var SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS = [
|
|
535
535
|
"mcp__sideboard__present_artifact",
|
|
536
536
|
"mcp__sideboard__present_schema",
|
|
537
|
-
"mcp__sideboard__present_files"
|
|
537
|
+
"mcp__sideboard__present_files",
|
|
538
|
+
"mcp__sideboard__ask_user",
|
|
539
|
+
"mcp__sideboard__present_plan"
|
|
538
540
|
];
|
|
539
541
|
var brightsyMcpCommandCache = null;
|
|
540
542
|
async function resolveBrightsyMcpCommand() {
|
|
@@ -718,7 +720,7 @@ function writeMcpServersConfig(servers) {
|
|
|
718
720
|
}
|
|
719
721
|
|
|
720
722
|
// src/agents/types.ts
|
|
721
|
-
var PLAN_MODE_INSTRUCTION = "Plan mode is active and must remain active until the user turns Plan mode off in the UI (or
|
|
723
|
+
var PLAN_MODE_INSTRUCTION = "Plan mode is active and must remain active until the user turns Plan mode off in the UI (or Approves / Hands off the plan). Analyze the codebase, search and read files as needed, and produce or refine a clear implementation plan. Do not modify, create, or delete any project files except via Sideboard MCP present_plan (writes .context/attachments/plan.md). When you need a clarifying decision (approach forks, auth choice, scope): (1) first write a short chat message that explains the decision and what each option means (tradeoffs, when to pick it) \u2014 do not leave the user staring at bare labels; (2) then call Sideboard MCP ask_user with the same options, including a description on every option. Sideboard shows questions in the composer and mirrors them in chat. After ask_user, wait for the user's next message with their answers before finalizing the plan. When the plan is ready for approval: (1) call present_plan with the full markdown plan (title + content) so Sideboard saves .context/attachments/plan.md and shows it in chat for Approve / Hand off / Copy; (2) Claude should also call ExitPlanMode after present_plan. Do not skip present_plan \u2014 the plan must be a markdown file, not only chat prose.";
|
|
722
724
|
function permissionMode(thread) {
|
|
723
725
|
if (thread.planMode) {
|
|
724
726
|
return {
|
|
@@ -899,7 +901,7 @@ var claudeAdapter = {
|
|
|
899
901
|
);
|
|
900
902
|
}
|
|
901
903
|
const mode = permissionMode(thread);
|
|
902
|
-
const { isOrchestratorThread } = await import("./global-workspace-
|
|
904
|
+
const { isOrchestratorThread } = await import("./global-workspace-EV4G2WMQ.js");
|
|
903
905
|
const isOrchestrator = isOrchestratorThread(thread);
|
|
904
906
|
const injectedServers = await buildInjectedMcpServers({
|
|
905
907
|
includeSideboard: true,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// src/paths/workspace-scratch.ts
|
|
5
|
+
var ATTACHMENTS_DIR = ".context/attachments";
|
|
6
|
+
var LEGACY_ATTACHMENTS_DIR = ".sideboard/attachments";
|
|
7
|
+
var ATTACHMENTS_GITIGNORE = `# Sideboard / workspace attachments (local only)
|
|
8
|
+
*
|
|
9
|
+
!.gitignore
|
|
10
|
+
`;
|
|
11
|
+
function attachmentsGitignoreBody() {
|
|
12
|
+
return ATTACHMENTS_GITIGNORE;
|
|
13
|
+
}
|
|
14
|
+
function isWorkspaceScratchPath(relativePath) {
|
|
15
|
+
const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
16
|
+
return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".context" || p.startsWith(".context/");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
ATTACHMENTS_DIR,
|
|
21
|
+
LEGACY_ATTACHMENTS_DIR,
|
|
22
|
+
attachmentsGitignoreBody,
|
|
23
|
+
isWorkspaceScratchPath
|
|
24
|
+
};
|
|
@@ -465,7 +465,9 @@ var SIDEBOARD_MCP_ALLOWED_TOOLS = [
|
|
|
465
465
|
var SIDEBOARD_ARTIFACT_MCP_ALLOWED_TOOLS = [
|
|
466
466
|
"mcp__sideboard__present_artifact",
|
|
467
467
|
"mcp__sideboard__present_schema",
|
|
468
|
-
"mcp__sideboard__present_files"
|
|
468
|
+
"mcp__sideboard__present_files",
|
|
469
|
+
"mcp__sideboard__ask_user",
|
|
470
|
+
"mcp__sideboard__present_plan"
|
|
469
471
|
];
|
|
470
472
|
var BRIGHTSY_MCP_ALLOWED_TOOLS = [
|
|
471
473
|
"mcp__brightsy",
|
|
@@ -656,7 +658,7 @@ async function writeInjectedMcpConfig(opts) {
|
|
|
656
658
|
}
|
|
657
659
|
|
|
658
660
|
// src/agents/types.ts
|
|
659
|
-
var PLAN_MODE_INSTRUCTION = "Plan mode is active and must remain active until the user turns Plan mode off in the UI (or
|
|
661
|
+
var PLAN_MODE_INSTRUCTION = "Plan mode is active and must remain active until the user turns Plan mode off in the UI (or Approves / Hands off the plan). Analyze the codebase, search and read files as needed, and produce or refine a clear implementation plan. Do not modify, create, or delete any project files except via Sideboard MCP present_plan (writes .context/attachments/plan.md). When you need a clarifying decision (approach forks, auth choice, scope): (1) first write a short chat message that explains the decision and what each option means (tradeoffs, when to pick it) \u2014 do not leave the user staring at bare labels; (2) then call Sideboard MCP ask_user with the same options, including a description on every option. Sideboard shows questions in the composer and mirrors them in chat. After ask_user, wait for the user's next message with their answers before finalizing the plan. When the plan is ready for approval: (1) call present_plan with the full markdown plan (title + content) so Sideboard saves .context/attachments/plan.md and shows it in chat for Approve / Hand off / Copy; (2) Claude should also call ExitPlanMode after present_plan. Do not skip present_plan \u2014 the plan must be a markdown file, not only chat prose.";
|
|
660
662
|
function permissionMode(thread) {
|
|
661
663
|
if (thread.planMode) {
|
|
662
664
|
return {
|
|
@@ -837,7 +839,7 @@ var claudeAdapter = {
|
|
|
837
839
|
);
|
|
838
840
|
}
|
|
839
841
|
const mode = permissionMode(thread);
|
|
840
|
-
const { isOrchestratorThread } = await import("./global-workspace-
|
|
842
|
+
const { isOrchestratorThread } = await import("./global-workspace-MSX2K27Y.js");
|
|
841
843
|
const isOrchestrator = isOrchestratorThread(thread);
|
|
842
844
|
const injectedServers = await buildInjectedMcpServers({
|
|
843
845
|
includeSideboard: true,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isWorkspaceScratchPath
|
|
3
|
+
} from "./chunk-FKOIHGKV.js";
|
|
1
4
|
import {
|
|
2
5
|
listThreads
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-TSRXOSVD.js";
|
|
4
7
|
import {
|
|
5
8
|
worktreesRoot
|
|
6
9
|
} from "./chunk-M37RITA6.js";
|
|
@@ -970,6 +973,206 @@ function buildMergeGateChecks(gate, opts = {}) {
|
|
|
970
973
|
return rows;
|
|
971
974
|
}
|
|
972
975
|
|
|
976
|
+
// src/git/stack.ts
|
|
977
|
+
var cachedStatus = null;
|
|
978
|
+
var STATUS_TTL_MS = 6e4;
|
|
979
|
+
async function detectGhStack(cwd) {
|
|
980
|
+
const now = Date.now();
|
|
981
|
+
if (cachedStatus && now - cachedStatus.at < STATUS_TTL_MS) {
|
|
982
|
+
return cachedStatus.status;
|
|
983
|
+
}
|
|
984
|
+
const probe = await gh(["stack", "view", "--help"], cwd, { reject: false });
|
|
985
|
+
if (probe.exitCode === 0) {
|
|
986
|
+
const status2 = { available: true };
|
|
987
|
+
cachedStatus = { at: now, status: status2 };
|
|
988
|
+
return status2;
|
|
989
|
+
}
|
|
990
|
+
const err = `${probe.stderr}
|
|
991
|
+
${probe.stdout}`;
|
|
992
|
+
const reason = /official extension|extension install|github\/gh-stack/i.test(err) ? "Install with: gh extension install github/gh-stack" : err.trim() || "gh stack is not available";
|
|
993
|
+
const status = { available: false, reason };
|
|
994
|
+
cachedStatus = { at: now, status };
|
|
995
|
+
return status;
|
|
996
|
+
}
|
|
997
|
+
function resetGhStackDetectCache() {
|
|
998
|
+
cachedStatus = null;
|
|
999
|
+
}
|
|
1000
|
+
function str(v) {
|
|
1001
|
+
return typeof v === "string" ? v : v == null ? "" : String(v);
|
|
1002
|
+
}
|
|
1003
|
+
function num(v) {
|
|
1004
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
1005
|
+
if (typeof v === "string" && v.trim() && Number.isFinite(Number(v))) {
|
|
1006
|
+
return Number(v);
|
|
1007
|
+
}
|
|
1008
|
+
return null;
|
|
1009
|
+
}
|
|
1010
|
+
function parseGhStackViewJson(raw) {
|
|
1011
|
+
let data;
|
|
1012
|
+
try {
|
|
1013
|
+
data = JSON.parse(raw);
|
|
1014
|
+
} catch {
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
if (!Array.isArray(data.branches) || data.branches.length === 0) return null;
|
|
1018
|
+
const trunk = str(data.trunk) || "main";
|
|
1019
|
+
const currentBranch2 = str(data.currentBranch);
|
|
1020
|
+
const stackNumber = num(data.stackNumber) ?? num(data.number);
|
|
1021
|
+
const layers = [];
|
|
1022
|
+
for (let i = 0; i < data.branches.length; i++) {
|
|
1023
|
+
const b = data.branches[i];
|
|
1024
|
+
if (!b || typeof b !== "object") continue;
|
|
1025
|
+
const name = str(b.name);
|
|
1026
|
+
if (!name) continue;
|
|
1027
|
+
const pr = b.pr && typeof b.pr === "object" ? b.pr : null;
|
|
1028
|
+
layers.push({
|
|
1029
|
+
position: i + 1,
|
|
1030
|
+
branchName: name,
|
|
1031
|
+
headSha: str(b.head) || void 0,
|
|
1032
|
+
baseSha: str(b.base) || void 0,
|
|
1033
|
+
isCurrent: Boolean(b.isCurrent) || name === currentBranch2,
|
|
1034
|
+
isMerged: Boolean(b.isMerged),
|
|
1035
|
+
isQueued: Boolean(b.isQueued),
|
|
1036
|
+
needsRebase: Boolean(b.needsRebase),
|
|
1037
|
+
prNumber: pr ? num(pr.number) : null,
|
|
1038
|
+
prUrl: pr && str(pr.url) ? str(pr.url) : null,
|
|
1039
|
+
prState: pr && str(pr.state) ? str(pr.state).toUpperCase() : null,
|
|
1040
|
+
title: pr && str(pr.title) ? str(pr.title) : void 0
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
if (!layers.length) return null;
|
|
1044
|
+
let currentIndex = layers.findIndex((l) => l.isCurrent);
|
|
1045
|
+
if (currentIndex < 0 && currentBranch2) {
|
|
1046
|
+
currentIndex = layers.findIndex((l) => l.branchName === currentBranch2);
|
|
1047
|
+
}
|
|
1048
|
+
const { readyToMerge, blockedReason } = stackMergeReadiness(layers, currentIndex);
|
|
1049
|
+
return {
|
|
1050
|
+
stackNumber,
|
|
1051
|
+
trunk,
|
|
1052
|
+
currentBranch: currentBranch2 || layers[currentIndex]?.branchName || layers[0].branchName,
|
|
1053
|
+
layers,
|
|
1054
|
+
currentIndex,
|
|
1055
|
+
readyToMerge,
|
|
1056
|
+
blockedReason
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
function stackMergeReadiness(layers, throughIndex) {
|
|
1060
|
+
if (throughIndex < 0 || throughIndex >= layers.length) {
|
|
1061
|
+
return { readyToMerge: false, blockedReason: "Not on a stack layer" };
|
|
1062
|
+
}
|
|
1063
|
+
for (let i = 0; i <= throughIndex; i++) {
|
|
1064
|
+
const layer = layers[i];
|
|
1065
|
+
if (layer.isMerged) continue;
|
|
1066
|
+
if (!layer.prNumber) {
|
|
1067
|
+
return {
|
|
1068
|
+
readyToMerge: false,
|
|
1069
|
+
blockedReason: `Layer ${layer.branchName} has no pull request yet`
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
if (layer.needsRebase) {
|
|
1073
|
+
return {
|
|
1074
|
+
readyToMerge: false,
|
|
1075
|
+
blockedReason: `PR #${layer.prNumber} needs rebase`
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
const state = (layer.prState ?? "").toUpperCase();
|
|
1079
|
+
if (state && state !== "OPEN" && state !== "QUEUED") {
|
|
1080
|
+
return {
|
|
1081
|
+
readyToMerge: false,
|
|
1082
|
+
blockedReason: `PR #${layer.prNumber} is ${state}`
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
return { readyToMerge: true, blockedReason: null };
|
|
1087
|
+
}
|
|
1088
|
+
async function getPrStack(cwd) {
|
|
1089
|
+
const status = await detectGhStack(cwd);
|
|
1090
|
+
if (!status.available) return null;
|
|
1091
|
+
const result = await gh(["stack", "view", "--json"], cwd, { reject: false });
|
|
1092
|
+
if (result.exitCode === 2) return null;
|
|
1093
|
+
if (result.exitCode !== 0) {
|
|
1094
|
+
if (/not in a stack|no stack/i.test(`${result.stderr}
|
|
1095
|
+
${result.stdout}`)) {
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
if (result.exitCode === 9) return null;
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
const json = result.stdout.trim();
|
|
1102
|
+
if (!json) return null;
|
|
1103
|
+
return parseGhStackViewJson(json);
|
|
1104
|
+
}
|
|
1105
|
+
async function isInPrStack(cwd) {
|
|
1106
|
+
return Boolean(await getPrStack(cwd));
|
|
1107
|
+
}
|
|
1108
|
+
async function mergePrStack(cwd, opts) {
|
|
1109
|
+
const status = await detectGhStack(cwd);
|
|
1110
|
+
if (!status.available) {
|
|
1111
|
+
throw new Error(status.reason);
|
|
1112
|
+
}
|
|
1113
|
+
const method = opts.method ?? "squash";
|
|
1114
|
+
const methodFlag = method === "rebase" ? "--rebase" : method === "merge" ? "--merge" : "--squash";
|
|
1115
|
+
const args = [
|
|
1116
|
+
"stack",
|
|
1117
|
+
"merge",
|
|
1118
|
+
String(opts.through),
|
|
1119
|
+
"--yes",
|
|
1120
|
+
methodFlag
|
|
1121
|
+
];
|
|
1122
|
+
const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
|
|
1123
|
+
if (exitCode !== 0) {
|
|
1124
|
+
throw new Error(stderr.trim() || stdout.trim() || "gh stack merge failed");
|
|
1125
|
+
}
|
|
1126
|
+
return { stdout };
|
|
1127
|
+
}
|
|
1128
|
+
async function initPrStack(cwd, branches, opts) {
|
|
1129
|
+
if (!branches.length) throw new Error("initPrStack requires at least one branch name");
|
|
1130
|
+
const status = await detectGhStack(cwd);
|
|
1131
|
+
if (!status.available) throw new Error(status.reason);
|
|
1132
|
+
const args = ["stack", "init"];
|
|
1133
|
+
if (opts?.base) args.push("--base", opts.base);
|
|
1134
|
+
args.push(...branches);
|
|
1135
|
+
const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
|
|
1136
|
+
if (exitCode !== 0) {
|
|
1137
|
+
throw new Error(stderr.trim() || stdout.trim() || "gh stack init failed");
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
async function addPrStackLayer(cwd, branchName) {
|
|
1141
|
+
if (!branchName.trim()) throw new Error("branch name required");
|
|
1142
|
+
const status = await detectGhStack(cwd);
|
|
1143
|
+
if (!status.available) throw new Error(status.reason);
|
|
1144
|
+
const { exitCode, stderr, stdout } = await gh(
|
|
1145
|
+
["stack", "add", branchName.trim()],
|
|
1146
|
+
cwd,
|
|
1147
|
+
{ reject: false }
|
|
1148
|
+
);
|
|
1149
|
+
if (exitCode !== 0) {
|
|
1150
|
+
throw new Error(stderr.trim() || stdout.trim() || "gh stack add failed");
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
async function submitPrStack(cwd, opts) {
|
|
1154
|
+
const status = await detectGhStack(cwd);
|
|
1155
|
+
if (!status.available) throw new Error(status.reason);
|
|
1156
|
+
const args = ["stack", "submit", "--auto"];
|
|
1157
|
+
if (opts?.open) args.push("--open");
|
|
1158
|
+
const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
|
|
1159
|
+
if (exitCode !== 0) {
|
|
1160
|
+
throw new Error(stderr.trim() || stdout.trim() || "gh stack submit failed");
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
async function checkoutPrStackLayer(cwd, target) {
|
|
1164
|
+
const status = await detectGhStack(cwd);
|
|
1165
|
+
if (!status.available) throw new Error(status.reason);
|
|
1166
|
+
const { exitCode, stderr, stdout } = await gh(
|
|
1167
|
+
["stack", "checkout", String(target)],
|
|
1168
|
+
cwd,
|
|
1169
|
+
{ reject: false }
|
|
1170
|
+
);
|
|
1171
|
+
if (exitCode !== 0) {
|
|
1172
|
+
throw new Error(stderr.trim() || stdout.trim() || "gh stack checkout failed");
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
973
1176
|
// src/git/worktree.ts
|
|
974
1177
|
async function lookupGithubGraphqlReset(cwd) {
|
|
975
1178
|
const result = await gh(["api", "rate_limit"], cwd, { reject: false });
|
|
@@ -1646,6 +1849,52 @@ ${add.stdout}`;
|
|
|
1646
1849
|
await ensureGhPreferOrigin(worktreePath);
|
|
1647
1850
|
return { branchName, worktreePath };
|
|
1648
1851
|
}
|
|
1852
|
+
async function createExistingBranchWorktree(opts) {
|
|
1853
|
+
const branchName = opts.branchName.trim();
|
|
1854
|
+
if (!branchName) throw new Error("branch name required");
|
|
1855
|
+
const worktreePath = join(worktreesRoot(opts.repoPath), opts.slug);
|
|
1856
|
+
if (existsSync(worktreePath)) {
|
|
1857
|
+
throw new Error(`Worktree already exists at ${worktreePath}`);
|
|
1858
|
+
}
|
|
1859
|
+
await ensureGhPreferOrigin(opts.repoPath);
|
|
1860
|
+
await git(["fetch", "origin", "--prune"], opts.repoPath, { reject: false });
|
|
1861
|
+
if (!branchName.startsWith("origin/") && !branchName.startsWith("refs/")) {
|
|
1862
|
+
await git(["fetch", "origin", branchName], opts.repoPath, { reject: false });
|
|
1863
|
+
}
|
|
1864
|
+
const existing = await listWorktrees(opts.repoPath);
|
|
1865
|
+
const already = existing.find((w) => w.branch === branchName);
|
|
1866
|
+
if (already?.path) {
|
|
1867
|
+
throw new Error(
|
|
1868
|
+
`Branch ${branchName} is already checked out at ${already.path}`
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
const startPoint = await resolveWorktreeStartPoint(opts.repoPath, branchName);
|
|
1872
|
+
const add = await git(
|
|
1873
|
+
["worktree", "add", worktreePath, startPoint],
|
|
1874
|
+
opts.repoPath,
|
|
1875
|
+
{ reject: false }
|
|
1876
|
+
);
|
|
1877
|
+
if (add.exitCode !== 0) {
|
|
1878
|
+
const retry = await git(
|
|
1879
|
+
["worktree", "add", worktreePath, branchName],
|
|
1880
|
+
opts.repoPath,
|
|
1881
|
+
{ reject: false }
|
|
1882
|
+
);
|
|
1883
|
+
if (retry.exitCode !== 0) {
|
|
1884
|
+
throw new Error(
|
|
1885
|
+
`Failed to create worktree for ${branchName}: ${retry.stderr.trim() || add.stderr.trim() || retry.stdout.trim() || add.stdout.trim() || `exit ${add.exitCode}`}`
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath, {
|
|
1890
|
+
reject: false
|
|
1891
|
+
});
|
|
1892
|
+
if (head.stdout.trim() === "HEAD" || head.stdout.trim() !== branchName) {
|
|
1893
|
+
await git(["checkout", "-B", branchName], worktreePath, { reject: false });
|
|
1894
|
+
}
|
|
1895
|
+
await ensureGhPreferOrigin(worktreePath);
|
|
1896
|
+
return { branchName, worktreePath };
|
|
1897
|
+
}
|
|
1649
1898
|
async function removeWorktree(repoPath, worktreePath, opts) {
|
|
1650
1899
|
await git(["worktree", "remove", "--force", worktreePath], repoPath, {
|
|
1651
1900
|
reject: false
|
|
@@ -1685,8 +1934,7 @@ async function isDirty(worktreePath) {
|
|
|
1685
1934
|
return false;
|
|
1686
1935
|
}
|
|
1687
1936
|
function isSideboardScratchPath(relativePath) {
|
|
1688
|
-
|
|
1689
|
-
return p === ".sideboard/attachments" || p.startsWith(".sideboard/attachments/");
|
|
1937
|
+
return isWorkspaceScratchPath(relativePath);
|
|
1690
1938
|
}
|
|
1691
1939
|
function porcelainStatusPath(line) {
|
|
1692
1940
|
const rest = line.length >= 3 ? line.slice(3) : "";
|
|
@@ -1714,7 +1962,7 @@ async function pushBranch(worktreePath, branchName) {
|
|
|
1714
1962
|
}
|
|
1715
1963
|
async function mergePr(cwd, selector, opts) {
|
|
1716
1964
|
const slug = await resolveGithubRepoSlug(cwd);
|
|
1717
|
-
const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft"];
|
|
1965
|
+
const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft,number"];
|
|
1718
1966
|
if (slug) viewArgs.push("--repo", slug);
|
|
1719
1967
|
const before = await gh(viewArgs, cwd, { reject: false });
|
|
1720
1968
|
if (before.exitCode !== 0 || !before.stdout.trim()) {
|
|
@@ -1722,16 +1970,29 @@ async function mergePr(cwd, selector, opts) {
|
|
|
1722
1970
|
}
|
|
1723
1971
|
let url = "";
|
|
1724
1972
|
let isDraft = false;
|
|
1973
|
+
let prNumber = null;
|
|
1725
1974
|
try {
|
|
1726
1975
|
const parsed = JSON.parse(before.stdout);
|
|
1727
1976
|
url = String(parsed.url ?? "");
|
|
1728
1977
|
isDraft = Boolean(parsed.isDraft);
|
|
1978
|
+
prNumber = typeof parsed.number === "number" && Number.isFinite(parsed.number) ? parsed.number : null;
|
|
1729
1979
|
if (String(parsed.state ?? "").toUpperCase() === "MERGED") {
|
|
1730
1980
|
return { url, state: "MERGED" };
|
|
1731
1981
|
}
|
|
1732
1982
|
} catch {
|
|
1733
1983
|
throw new Error("Could not parse pull request details");
|
|
1734
1984
|
}
|
|
1985
|
+
const stack = await getPrStack(cwd);
|
|
1986
|
+
const stackLayer = stack && prNumber != null ? stack.layers.find((l) => l.prNumber === prNumber) : null;
|
|
1987
|
+
if (stack && stackLayer && prNumber != null) {
|
|
1988
|
+
const throughIndex = stack.layers.findIndex((l) => l.prNumber === prNumber);
|
|
1989
|
+
const gate = stackMergeReadiness(stack.layers, throughIndex);
|
|
1990
|
+
if (!gate.readyToMerge) {
|
|
1991
|
+
throw new Error(gate.blockedReason || "Stack is not ready to merge");
|
|
1992
|
+
}
|
|
1993
|
+
await mergePrStack(cwd, { through: prNumber, method: opts?.method ?? "squash" });
|
|
1994
|
+
return { url, state: "MERGED" };
|
|
1995
|
+
}
|
|
1735
1996
|
if (isDraft) {
|
|
1736
1997
|
const readyArgs = ["pr", "ready", selector];
|
|
1737
1998
|
if (slug) readyArgs.push("--repo", slug);
|
|
@@ -1753,10 +2014,10 @@ async function mergePr(cwd, selector, opts) {
|
|
|
1753
2014
|
const after = await gh(viewArgs, cwd, { reject: false });
|
|
1754
2015
|
if (after.exitCode === 0 && after.stdout.trim()) {
|
|
1755
2016
|
try {
|
|
1756
|
-
const parsed = JSON.parse(after.stdout);
|
|
2017
|
+
const parsed = after.stdout ? JSON.parse(after.stdout) : null;
|
|
1757
2018
|
return {
|
|
1758
|
-
url: String(parsed
|
|
1759
|
-
state: String(parsed
|
|
2019
|
+
url: String(parsed?.url ?? url),
|
|
2020
|
+
state: String(parsed?.state ?? "MERGED")
|
|
1760
2021
|
};
|
|
1761
2022
|
} catch {
|
|
1762
2023
|
}
|
|
@@ -1910,6 +2171,17 @@ export {
|
|
|
1910
2171
|
extractGhErrorDetail,
|
|
1911
2172
|
formatGhLandError,
|
|
1912
2173
|
formatIpcInvokeError,
|
|
2174
|
+
detectGhStack,
|
|
2175
|
+
resetGhStackDetectCache,
|
|
2176
|
+
parseGhStackViewJson,
|
|
2177
|
+
stackMergeReadiness,
|
|
2178
|
+
getPrStack,
|
|
2179
|
+
isInPrStack,
|
|
2180
|
+
mergePrStack,
|
|
2181
|
+
initPrStack,
|
|
2182
|
+
addPrStackLayer,
|
|
2183
|
+
submitPrStack,
|
|
2184
|
+
checkoutPrStackLayer,
|
|
1913
2185
|
slugify,
|
|
1914
2186
|
resolveRepoRoot,
|
|
1915
2187
|
parseGithubSlugFromRemoteUrl,
|
|
@@ -1931,6 +2203,7 @@ export {
|
|
|
1931
2203
|
fetchPrHead,
|
|
1932
2204
|
resolveWorktreeStartPoint,
|
|
1933
2205
|
createThreadWorktree,
|
|
2206
|
+
createExistingBranchWorktree,
|
|
1934
2207
|
removeWorktree,
|
|
1935
2208
|
listWorktrees,
|
|
1936
2209
|
isDirty,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/paths/workspace-scratch.ts
|
|
2
|
+
var ATTACHMENTS_DIR = ".context/attachments";
|
|
3
|
+
var LEGACY_ATTACHMENTS_DIR = ".sideboard/attachments";
|
|
4
|
+
var ATTACHMENTS_GITIGNORE = `# Sideboard / workspace attachments (local only)
|
|
5
|
+
*
|
|
6
|
+
!.gitignore
|
|
7
|
+
`;
|
|
8
|
+
function attachmentsGitignoreBody() {
|
|
9
|
+
return ATTACHMENTS_GITIGNORE;
|
|
10
|
+
}
|
|
11
|
+
function isWorkspaceScratchPath(relativePath) {
|
|
12
|
+
const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
13
|
+
return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".context" || p.startsWith(".context/");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
ATTACHMENTS_DIR,
|
|
18
|
+
LEGACY_ATTACHMENTS_DIR,
|
|
19
|
+
attachmentsGitignoreBody,
|
|
20
|
+
isWorkspaceScratchPath
|
|
21
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isGlobalRepoPath
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-XX5BB7NV.js";
|
|
4
4
|
import {
|
|
5
5
|
ensureGhPreferOrigin,
|
|
6
6
|
resolveRepoRoot
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-DZFH2KLT.js";
|
|
8
8
|
import {
|
|
9
9
|
appDataDir
|
|
10
10
|
} from "./chunk-M37RITA6.js";
|
|
@@ -41,6 +41,8 @@ function normalizeThread(raw) {
|
|
|
41
41
|
agentPid: raw.agentPid ?? null,
|
|
42
42
|
attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
|
|
43
43
|
prTitle: raw.prTitle ?? null,
|
|
44
|
+
stackId: raw.stackId ?? null,
|
|
45
|
+
stackLayer: raw.stackLayer ?? null,
|
|
44
46
|
userSetTitle: Boolean(raw.userSetTitle),
|
|
45
47
|
activeRuns: Array.isArray(raw.activeRuns) ? raw.activeRuns : [],
|
|
46
48
|
quotaResumeAt: raw.quotaResumeAt ?? null,
|
|
@@ -65,6 +67,8 @@ function createEmptyThread(partial) {
|
|
|
65
67
|
activeRuns: partial.activeRuns ?? [],
|
|
66
68
|
prUrl: partial.prUrl ?? null,
|
|
67
69
|
prTitle: partial.prTitle ?? null,
|
|
70
|
+
stackId: partial.stackId ?? null,
|
|
71
|
+
stackLayer: partial.stackLayer ?? null,
|
|
68
72
|
userSetTitle: partial.userSetTitle ?? false,
|
|
69
73
|
messages: partial.messages ?? [],
|
|
70
74
|
attachments: partial.attachments ?? [],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveGithubRepoSlug
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DZFH2KLT.js";
|
|
4
4
|
import {
|
|
5
5
|
globalAgentCwd,
|
|
6
6
|
sideboardReposDir
|
|
@@ -42,6 +42,7 @@ var COORDINATOR_TOOL_PLAYBOOK = [
|
|
|
42
42
|
"Discover:",
|
|
43
43
|
"- list_workspaces \u2014 registered repos (path + github slug when known)",
|
|
44
44
|
"- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces (issues: Linear API or GitHub Issues)",
|
|
45
|
+
"- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
|
|
45
46
|
"- list_models \u2014 only when you need a specific model (rare); otherwise leave model unset = Auto",
|
|
46
47
|
"- list_threads / get_thread \u2014 fleet status (what is going on)",
|
|
47
48
|
"Workspaces:",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ATTACHMENTS_DIR,
|
|
5
|
+
LEGACY_ATTACHMENTS_DIR,
|
|
6
|
+
attachmentsGitignoreBody
|
|
7
|
+
} from "./chunk-B3SJXYIJ.js";
|
|
8
|
+
|
|
9
|
+
// src/plan/plan-file.ts
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
11
|
+
import { dirname, join } from "path";
|
|
12
|
+
|
|
13
|
+
// src/plan/plan-present.ts
|
|
14
|
+
var PLAN_FILE_REL = `${ATTACHMENTS_DIR}/plan.md`;
|
|
15
|
+
var PLAN_FILE_NAME = "plan.md";
|
|
16
|
+
var LEGACY_PLAN_FILE_REL = ".sideboard/plan.md";
|
|
17
|
+
function asRecord(v) {
|
|
18
|
+
return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
19
|
+
}
|
|
20
|
+
function isPresentPlanToolName(name) {
|
|
21
|
+
if (!name) return false;
|
|
22
|
+
return /present_plan$/i.test(name) || /^mcp__sideboard__present_plan$/i.test(name);
|
|
23
|
+
}
|
|
24
|
+
function extractPresentedPlan(parts) {
|
|
25
|
+
if (!parts?.length) return null;
|
|
26
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
27
|
+
const p = parts[i];
|
|
28
|
+
if (p.type !== "tool" || !isPresentPlanToolName(p.name)) continue;
|
|
29
|
+
const input = asRecord(p.input) ?? {};
|
|
30
|
+
const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
|
|
31
|
+
if (!content.trim()) continue;
|
|
32
|
+
const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
|
|
33
|
+
const path = typeof input.path === "string" && input.path.trim() ? input.path.trim() : PLAN_FILE_REL;
|
|
34
|
+
return { title, content: content.trim(), path, source: "present_plan" };
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function resolvePlanMarkdown(opts) {
|
|
39
|
+
const fromTool = extractPresentedPlan(opts.parts);
|
|
40
|
+
if (fromTool) return fromTool;
|
|
41
|
+
const file = opts.fileContent?.trim();
|
|
42
|
+
if (file) {
|
|
43
|
+
return {
|
|
44
|
+
title: "Plan",
|
|
45
|
+
content: file,
|
|
46
|
+
path: PLAN_FILE_REL,
|
|
47
|
+
source: "exit_plan"
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const text = opts.text?.trim();
|
|
51
|
+
if (text && text.length >= 80) {
|
|
52
|
+
return {
|
|
53
|
+
title: "Plan",
|
|
54
|
+
content: text,
|
|
55
|
+
path: PLAN_FILE_REL,
|
|
56
|
+
source: "text"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/plan/plan-file.ts
|
|
63
|
+
function ensureAttachmentsGitignore(worktreePath) {
|
|
64
|
+
const gitignoreAbs = join(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
65
|
+
if (existsSync(gitignoreAbs)) return;
|
|
66
|
+
mkdirSync(dirname(gitignoreAbs), { recursive: true });
|
|
67
|
+
writeFileSync(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
|
|
68
|
+
}
|
|
69
|
+
function planFileAbs(worktreePath) {
|
|
70
|
+
return join(worktreePath, PLAN_FILE_REL);
|
|
71
|
+
}
|
|
72
|
+
function readTextIfPresent(abs) {
|
|
73
|
+
if (!existsSync(abs)) return null;
|
|
74
|
+
try {
|
|
75
|
+
const content = readFileSync(abs, "utf8");
|
|
76
|
+
return content.trim() ? content : null;
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function readPlanFile(worktreePath) {
|
|
82
|
+
return readTextIfPresent(planFileAbs(worktreePath)) ?? readTextIfPresent(join(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent(join(worktreePath, LEGACY_PLAN_FILE_REL));
|
|
83
|
+
}
|
|
84
|
+
function writePlanFile(worktreePath, content) {
|
|
85
|
+
ensureAttachmentsGitignore(worktreePath);
|
|
86
|
+
const abs = planFileAbs(worktreePath);
|
|
87
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
88
|
+
const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
|
|
89
|
+
writeFileSync(abs, body, "utf8");
|
|
90
|
+
return PLAN_FILE_REL;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export {
|
|
94
|
+
PLAN_FILE_REL,
|
|
95
|
+
PLAN_FILE_NAME,
|
|
96
|
+
LEGACY_PLAN_FILE_REL,
|
|
97
|
+
isPresentPlanToolName,
|
|
98
|
+
extractPresentedPlan,
|
|
99
|
+
resolvePlanMarkdown,
|
|
100
|
+
planFileAbs,
|
|
101
|
+
readPlanFile,
|
|
102
|
+
writePlanFile
|
|
103
|
+
};
|