@sideboard-ai/core 0.1.51 → 0.1.53
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/cursor-runner.cjs +43 -4
- package/dist/agents/cursor-runner.js +45 -6
- package/dist/{agents-HIEJL3UV.js → agents-KP7UJEHJ.js} +1 -1
- package/dist/{agents-5ROTZNCX.js → agents-KYACODJ3.js} +2 -2
- package/dist/{chunk-5ZPSH7VI.js → chunk-A6HVEMIB.js} +16 -7
- package/dist/chunk-B3SJXYIJ.js +24 -0
- package/dist/{chunk-7EUWSBWR.js → chunk-BZST4HMJ.js} +21 -6
- 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-J5JTEJ5O.js → chunk-VG22SETP.js} +6 -0
- 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 +1383 -149
- package/dist/index.d.cts +389 -15
- package/dist/index.d.ts +389 -15
- package/dist/index.js +883 -91
- package/dist/mcp/run-stdio.cjs +1154 -141
- package/dist/mcp/run-stdio.js +709 -79
- 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
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ATTACHMENTS_DIR,
|
|
3
|
+
LEGACY_ATTACHMENTS_DIR,
|
|
4
|
+
attachmentsGitignoreBody
|
|
5
|
+
} from "./chunk-FKOIHGKV.js";
|
|
6
|
+
|
|
7
|
+
// src/plan/plan-file.ts
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
9
|
+
import { dirname, join } from "path";
|
|
10
|
+
|
|
11
|
+
// src/plan/plan-present.ts
|
|
12
|
+
var PLAN_FILE_REL = `${ATTACHMENTS_DIR}/plan.md`;
|
|
13
|
+
var PLAN_FILE_NAME = "plan.md";
|
|
14
|
+
var LEGACY_PLAN_FILE_REL = ".sideboard/plan.md";
|
|
15
|
+
function asRecord(v) {
|
|
16
|
+
return v != null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
17
|
+
}
|
|
18
|
+
function isPresentPlanToolName(name) {
|
|
19
|
+
if (!name) return false;
|
|
20
|
+
return /present_plan$/i.test(name) || /^mcp__sideboard__present_plan$/i.test(name);
|
|
21
|
+
}
|
|
22
|
+
function extractPresentedPlan(parts) {
|
|
23
|
+
if (!parts?.length) return null;
|
|
24
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
25
|
+
const p = parts[i];
|
|
26
|
+
if (p.type !== "tool" || !isPresentPlanToolName(p.name)) continue;
|
|
27
|
+
const input = asRecord(p.input) ?? {};
|
|
28
|
+
const content = typeof input.content === "string" ? input.content : typeof input.plan === "string" ? input.plan : typeof input.markdown === "string" ? input.markdown : "";
|
|
29
|
+
if (!content.trim()) continue;
|
|
30
|
+
const title = typeof input.title === "string" && input.title.trim() ? input.title.trim() : "Plan";
|
|
31
|
+
const path = typeof input.path === "string" && input.path.trim() ? input.path.trim() : PLAN_FILE_REL;
|
|
32
|
+
return { title, content: content.trim(), path, source: "present_plan" };
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function resolvePlanMarkdown(opts) {
|
|
37
|
+
const fromTool = extractPresentedPlan(opts.parts);
|
|
38
|
+
if (fromTool) return fromTool;
|
|
39
|
+
const file = opts.fileContent?.trim();
|
|
40
|
+
if (file) {
|
|
41
|
+
return {
|
|
42
|
+
title: "Plan",
|
|
43
|
+
content: file,
|
|
44
|
+
path: PLAN_FILE_REL,
|
|
45
|
+
source: "exit_plan"
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const text = opts.text?.trim();
|
|
49
|
+
if (text && text.length >= 80) {
|
|
50
|
+
return {
|
|
51
|
+
title: "Plan",
|
|
52
|
+
content: text,
|
|
53
|
+
path: PLAN_FILE_REL,
|
|
54
|
+
source: "text"
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/plan/plan-file.ts
|
|
61
|
+
function ensureAttachmentsGitignore(worktreePath) {
|
|
62
|
+
const gitignoreAbs = join(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
63
|
+
if (existsSync(gitignoreAbs)) return;
|
|
64
|
+
mkdirSync(dirname(gitignoreAbs), { recursive: true });
|
|
65
|
+
writeFileSync(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
|
|
66
|
+
}
|
|
67
|
+
function planFileAbs(worktreePath) {
|
|
68
|
+
return join(worktreePath, PLAN_FILE_REL);
|
|
69
|
+
}
|
|
70
|
+
function readTextIfPresent(abs) {
|
|
71
|
+
if (!existsSync(abs)) return null;
|
|
72
|
+
try {
|
|
73
|
+
const content = readFileSync(abs, "utf8");
|
|
74
|
+
return content.trim() ? content : null;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function readPlanFile(worktreePath) {
|
|
80
|
+
return readTextIfPresent(planFileAbs(worktreePath)) ?? readTextIfPresent(join(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent(join(worktreePath, LEGACY_PLAN_FILE_REL));
|
|
81
|
+
}
|
|
82
|
+
function writePlanFile(worktreePath, content) {
|
|
83
|
+
ensureAttachmentsGitignore(worktreePath);
|
|
84
|
+
const abs = planFileAbs(worktreePath);
|
|
85
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
86
|
+
const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
|
|
87
|
+
writeFileSync(abs, body, "utf8");
|
|
88
|
+
return PLAN_FILE_REL;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
PLAN_FILE_REL,
|
|
93
|
+
PLAN_FILE_NAME,
|
|
94
|
+
LEGACY_PLAN_FILE_REL,
|
|
95
|
+
isPresentPlanToolName,
|
|
96
|
+
extractPresentedPlan,
|
|
97
|
+
resolvePlanMarkdown,
|
|
98
|
+
planFileAbs,
|
|
99
|
+
readPlanFile,
|
|
100
|
+
writePlanFile
|
|
101
|
+
};
|
|
@@ -39,6 +39,8 @@ function normalizeThread(raw) {
|
|
|
39
39
|
agentPid: raw.agentPid ?? null,
|
|
40
40
|
attachments: Array.isArray(raw.attachments) ? raw.attachments : [],
|
|
41
41
|
prTitle: raw.prTitle ?? null,
|
|
42
|
+
stackId: raw.stackId ?? null,
|
|
43
|
+
stackLayer: raw.stackLayer ?? null,
|
|
42
44
|
userSetTitle: Boolean(raw.userSetTitle),
|
|
43
45
|
activeRuns: Array.isArray(raw.activeRuns) ? raw.activeRuns : [],
|
|
44
46
|
quotaResumeAt: raw.quotaResumeAt ?? null,
|
|
@@ -63,6 +65,8 @@ function createEmptyThread(partial) {
|
|
|
63
65
|
activeRuns: partial.activeRuns ?? [],
|
|
64
66
|
prUrl: partial.prUrl ?? null,
|
|
65
67
|
prTitle: partial.prTitle ?? null,
|
|
68
|
+
stackId: partial.stackId ?? null,
|
|
69
|
+
stackLayer: partial.stackLayer ?? null,
|
|
66
70
|
userSetTitle: partial.userSetTitle ?? false,
|
|
67
71
|
messages: partial.messages ?? [],
|
|
68
72
|
attachments: partial.attachments ?? [],
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
resolveGithubRepoSlug
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-XOU6HNQJ.js";
|
|
6
6
|
import {
|
|
7
7
|
globalAgentCwd,
|
|
8
8
|
sideboardReposDir
|
|
@@ -44,6 +44,7 @@ var COORDINATOR_TOOL_PLAYBOOK = [
|
|
|
44
44
|
"Discover:",
|
|
45
45
|
"- list_workspaces \u2014 registered repos (path + github slug when known)",
|
|
46
46
|
"- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces (issues: Linear API or GitHub Issues)",
|
|
47
|
+
"- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
|
|
47
48
|
"- list_models \u2014 only when you need a specific model (rare); otherwise leave model unset = Auto",
|
|
48
49
|
"- list_threads / get_thread \u2014 fleet status (what is going on)",
|
|
49
50
|
"Workspaces:",
|
|
@@ -60,6 +60,11 @@ function summarizeTurnStderr(tail, maxChars = 500) {
|
|
|
60
60
|
if (joined.length <= maxChars) return joined;
|
|
61
61
|
return joined.slice(joined.length - maxChars);
|
|
62
62
|
}
|
|
63
|
+
function looksLikeInvalidAgentSession(text) {
|
|
64
|
+
const lower = text.trim().toLowerCase();
|
|
65
|
+
if (!lower) return false;
|
|
66
|
+
return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower);
|
|
67
|
+
}
|
|
63
68
|
function looksLikeAgentFailureMessage(text) {
|
|
64
69
|
const lower = text.trim().toLowerCase();
|
|
65
70
|
if (!lower) return false;
|
|
@@ -212,6 +217,7 @@ export {
|
|
|
212
217
|
extractJsonErrorMessage,
|
|
213
218
|
pushTurnStderr,
|
|
214
219
|
summarizeTurnStderr,
|
|
220
|
+
looksLikeInvalidAgentSession,
|
|
215
221
|
looksLikeAgentFailureMessage,
|
|
216
222
|
fallbackTurnFailDetail,
|
|
217
223
|
humanizeAgentFailDetail,
|