@sideboard-ai/core 0.1.50 → 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-LUB3Q773.js → agents-ON6RKKND.js} +3 -3
- package/dist/{agents-3P4N6KHC.js → agents-YKSS6VBO.js} +3 -3
- package/dist/{chunk-RQI4TOXK.js → chunk-6QTZVJ7A.js} +7 -5
- package/dist/{chunk-AJ6ROGD7.js → chunk-7LNGIP3X.js} +20 -2
- package/dist/chunk-B3SJXYIJ.js +24 -0
- package/dist/{chunk-ILQK4P5R.js → chunk-BXJ76RHF.js} +4 -4
- package/dist/{chunk-SS34TVSY.js → chunk-D3METLRW.js} +7 -5
- package/dist/{chunk-FV6FN6V5.js → chunk-DZFH2KLT.js} +340 -22
- package/dist/chunk-FKOIHGKV.js +21 -0
- package/dist/{chunk-3NAS4MWH.js → chunk-GNML24AW.js} +2 -2
- package/dist/{chunk-E35APBHV.js → chunk-H6GGDLYS.js} +1 -1
- package/dist/{chunk-EOYDCKQC.js → chunk-HLEX5AQ6.js} +4 -0
- package/dist/{chunk-ZQCQIWIP.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-GZXBYHJC.js → chunk-UEAHMGHW.js} +2 -1
- package/dist/{chunk-ZVV5EEQN.js → chunk-XOU6HNQJ.js} +305 -22
- package/dist/{chunk-5WYEJ3F3.js → chunk-XX5BB7NV.js} +3 -3
- package/dist/{chunk-V4JRXYYU.js → chunk-YDXQ72MD.js} +2 -2
- package/dist/{chunk-DF5VQQKA.js → chunk-YOWIYAVA.js} +3 -3
- package/dist/{chunk-ISY4EGF6.js → chunk-ZNM4MAN4.js} +20 -2
- package/dist/{connected-teams-UBXHZGO4.js → connected-teams-2ANNZCP5.js} +2 -2
- package/dist/{connected-teams-GF52Q7LB.js → connected-teams-ONC4666A.js} +2 -2
- package/dist/{coordinator-prompt-VG4BZ5JL.js → coordinator-prompt-6FXVTSFN.js} +5 -4
- package/dist/{coordinator-prompt-SPGDT7J5.js → coordinator-prompt-S6JZD5EF.js} +5 -4
- package/dist/{global-workspace-OJF4ENH4.js → global-workspace-EV4G2WMQ.js} +6 -5
- package/dist/{global-workspace-KSR3E63K.js → global-workspace-MSX2K27Y.js} +6 -5
- package/dist/index.cjs +1369 -155
- package/dist/index.d.cts +397 -14
- package/dist/index.d.ts +397 -14
- package/dist/index.js +811 -90
- package/dist/mcp/run-stdio.cjs +1136 -147
- package/dist/mcp/run-stdio.js +635 -79
- package/dist/plan-file-6O7G4VPQ.js +23 -0
- package/dist/plan-file-PHVKUAEE.js +25 -0
- package/dist/{run-HMRSRG3U.js → run-H3IMNOYN.js} +3 -1
- package/dist/run-ZJKYHXGN.js +12 -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-OZE7LRDO.js → workspaces-3RQQZQRO.js} +7 -6
- package/dist/{workspaces-UJX7JIGH.js → workspaces-AYTBR6KQ.js} +7 -6
- package/dist/{worktree-XG5PCLZY.js → worktree-5KEQWSAF.js} +8 -3
- package/dist/{worktree-TEDAJ57S.js → worktree-RWGL7FUV.js} +8 -3
- package/package.json +1 -1
- package/dist/run-LF6E5IKL.js +0 -10
|
@@ -1,13 +1,17 @@
|
|
|
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";
|
|
7
10
|
import {
|
|
8
11
|
gh,
|
|
9
|
-
git
|
|
10
|
-
|
|
12
|
+
git,
|
|
13
|
+
resolveGhAuthToken
|
|
14
|
+
} from "./chunk-7LNGIP3X.js";
|
|
11
15
|
|
|
12
16
|
// src/git/worktree.ts
|
|
13
17
|
import { existsSync, readdirSync } from "fs";
|
|
@@ -969,6 +973,206 @@ function buildMergeGateChecks(gate, opts = {}) {
|
|
|
969
973
|
return rows;
|
|
970
974
|
}
|
|
971
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
|
+
|
|
972
1176
|
// src/git/worktree.ts
|
|
973
1177
|
async function lookupGithubGraphqlReset(cwd) {
|
|
974
1178
|
const result = await gh(["api", "rate_limit"], cwd, { reject: false });
|
|
@@ -1476,18 +1680,31 @@ async function fetchPrHead(repoPath, number, localBranch) {
|
|
|
1476
1680
|
const slug = await resolveGithubRepoSlug(repoPath);
|
|
1477
1681
|
const refspec = `+pull/${number}/head:${localBranch}`;
|
|
1478
1682
|
const errors = [];
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
}
|
|
1683
|
+
const httpsRemote = slug ? `https://github.com/${slug}.git` : null;
|
|
1684
|
+
const tryFetch = async (remote, opts) => {
|
|
1685
|
+
const label = opts?.ghAuth ? `${remote} (gh auth)` : remote;
|
|
1686
|
+
const gitOpts = { reject: false };
|
|
1687
|
+
if (opts?.ghAuth) {
|
|
1688
|
+
const token = await resolveGhAuthToken(repoPath);
|
|
1689
|
+
if (!token) {
|
|
1690
|
+
errors.push(`${label}: gh auth token unavailable`);
|
|
1691
|
+
return false;
|
|
1692
|
+
}
|
|
1693
|
+
gitOpts.env = { GIT_TERMINAL_PROMPT: "0" };
|
|
1694
|
+
gitOpts.config = {
|
|
1695
|
+
"http.extraHeader": `AUTHORIZATION: bearer ${token}`
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
const result = await git(["fetch", remote, refspec], repoPath, gitOpts);
|
|
1483
1699
|
if (result.exitCode === 0) return true;
|
|
1484
1700
|
const detail = (result.stderr || result.stdout).trim();
|
|
1485
|
-
if (detail) errors.push(`${
|
|
1701
|
+
if (detail) errors.push(`${label}: ${detail}`);
|
|
1486
1702
|
return false;
|
|
1487
1703
|
};
|
|
1488
1704
|
let fetched = await tryFetch("origin");
|
|
1489
|
-
if (!fetched &&
|
|
1490
|
-
fetched = await tryFetch(
|
|
1705
|
+
if (!fetched && httpsRemote) {
|
|
1706
|
+
fetched = await tryFetch(httpsRemote);
|
|
1707
|
+
if (!fetched) fetched = await tryFetch(httpsRemote, { ghAuth: true });
|
|
1491
1708
|
}
|
|
1492
1709
|
const localOk = async () => {
|
|
1493
1710
|
const verify = await git(["rev-parse", "--verify", localBranch], repoPath, {
|
|
@@ -1516,18 +1733,28 @@ async function fetchPrHead(repoPath, number, localBranch) {
|
|
|
1516
1733
|
}
|
|
1517
1734
|
}
|
|
1518
1735
|
if (oid) {
|
|
1519
|
-
const ensureOid = async (remote) => {
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1522
|
-
|
|
1736
|
+
const ensureOid = async (remote, opts) => {
|
|
1737
|
+
const gitOpts = { reject: false };
|
|
1738
|
+
if (opts?.ghAuth) {
|
|
1739
|
+
const token = await resolveGhAuthToken(repoPath);
|
|
1740
|
+
if (!token) return false;
|
|
1741
|
+
gitOpts.env = { GIT_TERMINAL_PROMPT: "0" };
|
|
1742
|
+
gitOpts.config = {
|
|
1743
|
+
"http.extraHeader": `AUTHORIZATION: bearer ${token}`
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
const got = await git(["fetch", remote, oid], repoPath, gitOpts);
|
|
1523
1747
|
return got.exitCode === 0;
|
|
1524
1748
|
};
|
|
1525
1749
|
let haveObject = (await git(["cat-file", "-e", `${oid}^{commit}`], repoPath, {
|
|
1526
1750
|
reject: false
|
|
1527
1751
|
})).exitCode === 0;
|
|
1528
1752
|
if (!haveObject) haveObject = await ensureOid("origin");
|
|
1529
|
-
if (!haveObject &&
|
|
1530
|
-
haveObject = await ensureOid(
|
|
1753
|
+
if (!haveObject && httpsRemote) {
|
|
1754
|
+
haveObject = await ensureOid(httpsRemote);
|
|
1755
|
+
if (!haveObject) {
|
|
1756
|
+
haveObject = await ensureOid(httpsRemote, { ghAuth: true });
|
|
1757
|
+
}
|
|
1531
1758
|
}
|
|
1532
1759
|
if (haveObject) {
|
|
1533
1760
|
const branched = await git(["branch", "-f", localBranch, oid], repoPath, {
|
|
@@ -1622,6 +1849,52 @@ ${add.stdout}`;
|
|
|
1622
1849
|
await ensureGhPreferOrigin(worktreePath);
|
|
1623
1850
|
return { branchName, worktreePath };
|
|
1624
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
|
+
}
|
|
1625
1898
|
async function removeWorktree(repoPath, worktreePath, opts) {
|
|
1626
1899
|
await git(["worktree", "remove", "--force", worktreePath], repoPath, {
|
|
1627
1900
|
reject: false
|
|
@@ -1651,8 +1924,27 @@ async function listWorktrees(repoPath) {
|
|
|
1651
1924
|
return entries;
|
|
1652
1925
|
}
|
|
1653
1926
|
async function isDirty(worktreePath) {
|
|
1654
|
-
const { stdout } = await git(["status", "--porcelain"], worktreePath);
|
|
1655
|
-
|
|
1927
|
+
const { stdout } = await git(["status", "--porcelain", "-uall"], worktreePath);
|
|
1928
|
+
for (const line of stdout.split("\n")) {
|
|
1929
|
+
if (!line.trim()) continue;
|
|
1930
|
+
const rel = porcelainStatusPath(line);
|
|
1931
|
+
if (!rel || isSideboardScratchPath(rel)) continue;
|
|
1932
|
+
return true;
|
|
1933
|
+
}
|
|
1934
|
+
return false;
|
|
1935
|
+
}
|
|
1936
|
+
function isSideboardScratchPath(relativePath) {
|
|
1937
|
+
return isWorkspaceScratchPath(relativePath);
|
|
1938
|
+
}
|
|
1939
|
+
function porcelainStatusPath(line) {
|
|
1940
|
+
const rest = line.length >= 3 ? line.slice(3) : "";
|
|
1941
|
+
if (!rest) return "";
|
|
1942
|
+
const arrow = rest.lastIndexOf(" -> ");
|
|
1943
|
+
const raw = arrow >= 0 ? rest.slice(arrow + 4) : rest;
|
|
1944
|
+
if (raw.startsWith('"') && raw.endsWith('"')) {
|
|
1945
|
+
return raw.slice(1, -1).replace(/\\([\\"])/g, "$1");
|
|
1946
|
+
}
|
|
1947
|
+
return raw;
|
|
1656
1948
|
}
|
|
1657
1949
|
async function currentBranch(worktreePath) {
|
|
1658
1950
|
const { stdout } = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath);
|
|
@@ -1670,7 +1962,7 @@ async function pushBranch(worktreePath, branchName) {
|
|
|
1670
1962
|
}
|
|
1671
1963
|
async function mergePr(cwd, selector, opts) {
|
|
1672
1964
|
const slug = await resolveGithubRepoSlug(cwd);
|
|
1673
|
-
const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft"];
|
|
1965
|
+
const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft,number"];
|
|
1674
1966
|
if (slug) viewArgs.push("--repo", slug);
|
|
1675
1967
|
const before = await gh(viewArgs, cwd, { reject: false });
|
|
1676
1968
|
if (before.exitCode !== 0 || !before.stdout.trim()) {
|
|
@@ -1678,16 +1970,29 @@ async function mergePr(cwd, selector, opts) {
|
|
|
1678
1970
|
}
|
|
1679
1971
|
let url = "";
|
|
1680
1972
|
let isDraft = false;
|
|
1973
|
+
let prNumber = null;
|
|
1681
1974
|
try {
|
|
1682
1975
|
const parsed = JSON.parse(before.stdout);
|
|
1683
1976
|
url = String(parsed.url ?? "");
|
|
1684
1977
|
isDraft = Boolean(parsed.isDraft);
|
|
1978
|
+
prNumber = typeof parsed.number === "number" && Number.isFinite(parsed.number) ? parsed.number : null;
|
|
1685
1979
|
if (String(parsed.state ?? "").toUpperCase() === "MERGED") {
|
|
1686
1980
|
return { url, state: "MERGED" };
|
|
1687
1981
|
}
|
|
1688
1982
|
} catch {
|
|
1689
1983
|
throw new Error("Could not parse pull request details");
|
|
1690
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
|
+
}
|
|
1691
1996
|
if (isDraft) {
|
|
1692
1997
|
const readyArgs = ["pr", "ready", selector];
|
|
1693
1998
|
if (slug) readyArgs.push("--repo", slug);
|
|
@@ -1709,10 +2014,10 @@ async function mergePr(cwd, selector, opts) {
|
|
|
1709
2014
|
const after = await gh(viewArgs, cwd, { reject: false });
|
|
1710
2015
|
if (after.exitCode === 0 && after.stdout.trim()) {
|
|
1711
2016
|
try {
|
|
1712
|
-
const parsed = JSON.parse(after.stdout);
|
|
2017
|
+
const parsed = after.stdout ? JSON.parse(after.stdout) : null;
|
|
1713
2018
|
return {
|
|
1714
|
-
url: String(parsed
|
|
1715
|
-
state: String(parsed
|
|
2019
|
+
url: String(parsed?.url ?? url),
|
|
2020
|
+
state: String(parsed?.state ?? "MERGED")
|
|
1716
2021
|
};
|
|
1717
2022
|
} catch {
|
|
1718
2023
|
}
|
|
@@ -1866,6 +2171,17 @@ export {
|
|
|
1866
2171
|
extractGhErrorDetail,
|
|
1867
2172
|
formatGhLandError,
|
|
1868
2173
|
formatIpcInvokeError,
|
|
2174
|
+
detectGhStack,
|
|
2175
|
+
resetGhStackDetectCache,
|
|
2176
|
+
parseGhStackViewJson,
|
|
2177
|
+
stackMergeReadiness,
|
|
2178
|
+
getPrStack,
|
|
2179
|
+
isInPrStack,
|
|
2180
|
+
mergePrStack,
|
|
2181
|
+
initPrStack,
|
|
2182
|
+
addPrStackLayer,
|
|
2183
|
+
submitPrStack,
|
|
2184
|
+
checkoutPrStackLayer,
|
|
1869
2185
|
slugify,
|
|
1870
2186
|
resolveRepoRoot,
|
|
1871
2187
|
parseGithubSlugFromRemoteUrl,
|
|
@@ -1887,9 +2203,11 @@ export {
|
|
|
1887
2203
|
fetchPrHead,
|
|
1888
2204
|
resolveWorktreeStartPoint,
|
|
1889
2205
|
createThreadWorktree,
|
|
2206
|
+
createExistingBranchWorktree,
|
|
1890
2207
|
removeWorktree,
|
|
1891
2208
|
listWorktrees,
|
|
1892
2209
|
isDirty,
|
|
2210
|
+
isSideboardScratchPath,
|
|
1893
2211
|
currentBranch,
|
|
1894
2212
|
commitAll,
|
|
1895
2213
|
pushBranch,
|
|
@@ -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
|
+
};
|