@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
|
@@ -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:",
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
isWorkspaceScratchPath
|
|
5
|
+
} from "./chunk-B3SJXYIJ.js";
|
|
3
6
|
import {
|
|
4
7
|
listThreads
|
|
5
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-HLEX5AQ6.js";
|
|
6
9
|
import {
|
|
7
10
|
gh,
|
|
8
|
-
git
|
|
9
|
-
|
|
11
|
+
git,
|
|
12
|
+
resolveGhAuthToken
|
|
13
|
+
} from "./chunk-ZNM4MAN4.js";
|
|
10
14
|
import {
|
|
11
15
|
worktreesRoot
|
|
12
16
|
} from "./chunk-7MV3RXSC.js";
|
|
@@ -964,6 +968,178 @@ function buildMergeGateChecks(gate, opts = {}) {
|
|
|
964
968
|
return rows;
|
|
965
969
|
}
|
|
966
970
|
|
|
971
|
+
// src/git/stack.ts
|
|
972
|
+
var cachedStatus = null;
|
|
973
|
+
var STATUS_TTL_MS = 6e4;
|
|
974
|
+
async function detectGhStack(cwd) {
|
|
975
|
+
const now = Date.now();
|
|
976
|
+
if (cachedStatus && now - cachedStatus.at < STATUS_TTL_MS) {
|
|
977
|
+
return cachedStatus.status;
|
|
978
|
+
}
|
|
979
|
+
const probe = await gh(["stack", "view", "--help"], cwd, { reject: false });
|
|
980
|
+
if (probe.exitCode === 0) {
|
|
981
|
+
const status2 = { available: true };
|
|
982
|
+
cachedStatus = { at: now, status: status2 };
|
|
983
|
+
return status2;
|
|
984
|
+
}
|
|
985
|
+
const err = `${probe.stderr}
|
|
986
|
+
${probe.stdout}`;
|
|
987
|
+
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";
|
|
988
|
+
const status = { available: false, reason };
|
|
989
|
+
cachedStatus = { at: now, status };
|
|
990
|
+
return status;
|
|
991
|
+
}
|
|
992
|
+
function str(v) {
|
|
993
|
+
return typeof v === "string" ? v : v == null ? "" : String(v);
|
|
994
|
+
}
|
|
995
|
+
function num(v) {
|
|
996
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
997
|
+
if (typeof v === "string" && v.trim() && Number.isFinite(Number(v))) {
|
|
998
|
+
return Number(v);
|
|
999
|
+
}
|
|
1000
|
+
return null;
|
|
1001
|
+
}
|
|
1002
|
+
function parseGhStackViewJson(raw) {
|
|
1003
|
+
let data;
|
|
1004
|
+
try {
|
|
1005
|
+
data = JSON.parse(raw);
|
|
1006
|
+
} catch {
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
if (!Array.isArray(data.branches) || data.branches.length === 0) return null;
|
|
1010
|
+
const trunk = str(data.trunk) || "main";
|
|
1011
|
+
const currentBranch2 = str(data.currentBranch);
|
|
1012
|
+
const stackNumber = num(data.stackNumber) ?? num(data.number);
|
|
1013
|
+
const layers = [];
|
|
1014
|
+
for (let i = 0; i < data.branches.length; i++) {
|
|
1015
|
+
const b = data.branches[i];
|
|
1016
|
+
if (!b || typeof b !== "object") continue;
|
|
1017
|
+
const name = str(b.name);
|
|
1018
|
+
if (!name) continue;
|
|
1019
|
+
const pr = b.pr && typeof b.pr === "object" ? b.pr : null;
|
|
1020
|
+
layers.push({
|
|
1021
|
+
position: i + 1,
|
|
1022
|
+
branchName: name,
|
|
1023
|
+
headSha: str(b.head) || void 0,
|
|
1024
|
+
baseSha: str(b.base) || void 0,
|
|
1025
|
+
isCurrent: Boolean(b.isCurrent) || name === currentBranch2,
|
|
1026
|
+
isMerged: Boolean(b.isMerged),
|
|
1027
|
+
isQueued: Boolean(b.isQueued),
|
|
1028
|
+
needsRebase: Boolean(b.needsRebase),
|
|
1029
|
+
prNumber: pr ? num(pr.number) : null,
|
|
1030
|
+
prUrl: pr && str(pr.url) ? str(pr.url) : null,
|
|
1031
|
+
prState: pr && str(pr.state) ? str(pr.state).toUpperCase() : null,
|
|
1032
|
+
title: pr && str(pr.title) ? str(pr.title) : void 0
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
if (!layers.length) return null;
|
|
1036
|
+
let currentIndex = layers.findIndex((l) => l.isCurrent);
|
|
1037
|
+
if (currentIndex < 0 && currentBranch2) {
|
|
1038
|
+
currentIndex = layers.findIndex((l) => l.branchName === currentBranch2);
|
|
1039
|
+
}
|
|
1040
|
+
const { readyToMerge, blockedReason } = stackMergeReadiness(layers, currentIndex);
|
|
1041
|
+
return {
|
|
1042
|
+
stackNumber,
|
|
1043
|
+
trunk,
|
|
1044
|
+
currentBranch: currentBranch2 || layers[currentIndex]?.branchName || layers[0].branchName,
|
|
1045
|
+
layers,
|
|
1046
|
+
currentIndex,
|
|
1047
|
+
readyToMerge,
|
|
1048
|
+
blockedReason
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
function stackMergeReadiness(layers, throughIndex) {
|
|
1052
|
+
if (throughIndex < 0 || throughIndex >= layers.length) {
|
|
1053
|
+
return { readyToMerge: false, blockedReason: "Not on a stack layer" };
|
|
1054
|
+
}
|
|
1055
|
+
for (let i = 0; i <= throughIndex; i++) {
|
|
1056
|
+
const layer = layers[i];
|
|
1057
|
+
if (layer.isMerged) continue;
|
|
1058
|
+
if (!layer.prNumber) {
|
|
1059
|
+
return {
|
|
1060
|
+
readyToMerge: false,
|
|
1061
|
+
blockedReason: `Layer ${layer.branchName} has no pull request yet`
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
if (layer.needsRebase) {
|
|
1065
|
+
return {
|
|
1066
|
+
readyToMerge: false,
|
|
1067
|
+
blockedReason: `PR #${layer.prNumber} needs rebase`
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
const state = (layer.prState ?? "").toUpperCase();
|
|
1071
|
+
if (state && state !== "OPEN" && state !== "QUEUED") {
|
|
1072
|
+
return {
|
|
1073
|
+
readyToMerge: false,
|
|
1074
|
+
blockedReason: `PR #${layer.prNumber} is ${state}`
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return { readyToMerge: true, blockedReason: null };
|
|
1079
|
+
}
|
|
1080
|
+
async function getPrStack(cwd) {
|
|
1081
|
+
const status = await detectGhStack(cwd);
|
|
1082
|
+
if (!status.available) return null;
|
|
1083
|
+
const result = await gh(["stack", "view", "--json"], cwd, { reject: false });
|
|
1084
|
+
if (result.exitCode === 2) return null;
|
|
1085
|
+
if (result.exitCode !== 0) {
|
|
1086
|
+
if (/not in a stack|no stack/i.test(`${result.stderr}
|
|
1087
|
+
${result.stdout}`)) {
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
if (result.exitCode === 9) return null;
|
|
1091
|
+
return null;
|
|
1092
|
+
}
|
|
1093
|
+
const json = result.stdout.trim();
|
|
1094
|
+
if (!json) return null;
|
|
1095
|
+
return parseGhStackViewJson(json);
|
|
1096
|
+
}
|
|
1097
|
+
async function mergePrStack(cwd, opts) {
|
|
1098
|
+
const status = await detectGhStack(cwd);
|
|
1099
|
+
if (!status.available) {
|
|
1100
|
+
throw new Error(status.reason);
|
|
1101
|
+
}
|
|
1102
|
+
const method = opts.method ?? "squash";
|
|
1103
|
+
const methodFlag = method === "rebase" ? "--rebase" : method === "merge" ? "--merge" : "--squash";
|
|
1104
|
+
const args = [
|
|
1105
|
+
"stack",
|
|
1106
|
+
"merge",
|
|
1107
|
+
String(opts.through),
|
|
1108
|
+
"--yes",
|
|
1109
|
+
methodFlag
|
|
1110
|
+
];
|
|
1111
|
+
const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
|
|
1112
|
+
if (exitCode !== 0) {
|
|
1113
|
+
throw new Error(stderr.trim() || stdout.trim() || "gh stack merge failed");
|
|
1114
|
+
}
|
|
1115
|
+
return { stdout };
|
|
1116
|
+
}
|
|
1117
|
+
async function initPrStack(cwd, branches, opts) {
|
|
1118
|
+
if (!branches.length) throw new Error("initPrStack requires at least one branch name");
|
|
1119
|
+
const status = await detectGhStack(cwd);
|
|
1120
|
+
if (!status.available) throw new Error(status.reason);
|
|
1121
|
+
const args = ["stack", "init"];
|
|
1122
|
+
if (opts?.base) args.push("--base", opts.base);
|
|
1123
|
+
args.push(...branches);
|
|
1124
|
+
const { exitCode, stderr, stdout } = await gh(args, cwd, { reject: false });
|
|
1125
|
+
if (exitCode !== 0) {
|
|
1126
|
+
throw new Error(stderr.trim() || stdout.trim() || "gh stack init failed");
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
async function addPrStackLayer(cwd, branchName) {
|
|
1130
|
+
if (!branchName.trim()) throw new Error("branch name required");
|
|
1131
|
+
const status = await detectGhStack(cwd);
|
|
1132
|
+
if (!status.available) throw new Error(status.reason);
|
|
1133
|
+
const { exitCode, stderr, stdout } = await gh(
|
|
1134
|
+
["stack", "add", branchName.trim()],
|
|
1135
|
+
cwd,
|
|
1136
|
+
{ reject: false }
|
|
1137
|
+
);
|
|
1138
|
+
if (exitCode !== 0) {
|
|
1139
|
+
throw new Error(stderr.trim() || stdout.trim() || "gh stack add failed");
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
967
1143
|
// src/git/worktree.ts
|
|
968
1144
|
async function lookupGithubGraphqlReset(cwd) {
|
|
969
1145
|
const result = await gh(["api", "rate_limit"], cwd, { reject: false });
|
|
@@ -1471,18 +1647,31 @@ async function fetchPrHead(repoPath, number, localBranch) {
|
|
|
1471
1647
|
const slug = await resolveGithubRepoSlug(repoPath);
|
|
1472
1648
|
const refspec = `+pull/${number}/head:${localBranch}`;
|
|
1473
1649
|
const errors = [];
|
|
1474
|
-
const
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
}
|
|
1650
|
+
const httpsRemote = slug ? `https://github.com/${slug}.git` : null;
|
|
1651
|
+
const tryFetch = async (remote, opts) => {
|
|
1652
|
+
const label = opts?.ghAuth ? `${remote} (gh auth)` : remote;
|
|
1653
|
+
const gitOpts = { reject: false };
|
|
1654
|
+
if (opts?.ghAuth) {
|
|
1655
|
+
const token = await resolveGhAuthToken(repoPath);
|
|
1656
|
+
if (!token) {
|
|
1657
|
+
errors.push(`${label}: gh auth token unavailable`);
|
|
1658
|
+
return false;
|
|
1659
|
+
}
|
|
1660
|
+
gitOpts.env = { GIT_TERMINAL_PROMPT: "0" };
|
|
1661
|
+
gitOpts.config = {
|
|
1662
|
+
"http.extraHeader": `AUTHORIZATION: bearer ${token}`
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
const result = await git(["fetch", remote, refspec], repoPath, gitOpts);
|
|
1478
1666
|
if (result.exitCode === 0) return true;
|
|
1479
1667
|
const detail = (result.stderr || result.stdout).trim();
|
|
1480
|
-
if (detail) errors.push(`${
|
|
1668
|
+
if (detail) errors.push(`${label}: ${detail}`);
|
|
1481
1669
|
return false;
|
|
1482
1670
|
};
|
|
1483
1671
|
let fetched = await tryFetch("origin");
|
|
1484
|
-
if (!fetched &&
|
|
1485
|
-
fetched = await tryFetch(
|
|
1672
|
+
if (!fetched && httpsRemote) {
|
|
1673
|
+
fetched = await tryFetch(httpsRemote);
|
|
1674
|
+
if (!fetched) fetched = await tryFetch(httpsRemote, { ghAuth: true });
|
|
1486
1675
|
}
|
|
1487
1676
|
const localOk = async () => {
|
|
1488
1677
|
const verify = await git(["rev-parse", "--verify", localBranch], repoPath, {
|
|
@@ -1511,18 +1700,28 @@ async function fetchPrHead(repoPath, number, localBranch) {
|
|
|
1511
1700
|
}
|
|
1512
1701
|
}
|
|
1513
1702
|
if (oid) {
|
|
1514
|
-
const ensureOid = async (remote) => {
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
|
|
1703
|
+
const ensureOid = async (remote, opts) => {
|
|
1704
|
+
const gitOpts = { reject: false };
|
|
1705
|
+
if (opts?.ghAuth) {
|
|
1706
|
+
const token = await resolveGhAuthToken(repoPath);
|
|
1707
|
+
if (!token) return false;
|
|
1708
|
+
gitOpts.env = { GIT_TERMINAL_PROMPT: "0" };
|
|
1709
|
+
gitOpts.config = {
|
|
1710
|
+
"http.extraHeader": `AUTHORIZATION: bearer ${token}`
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
const got = await git(["fetch", remote, oid], repoPath, gitOpts);
|
|
1518
1714
|
return got.exitCode === 0;
|
|
1519
1715
|
};
|
|
1520
1716
|
let haveObject = (await git(["cat-file", "-e", `${oid}^{commit}`], repoPath, {
|
|
1521
1717
|
reject: false
|
|
1522
1718
|
})).exitCode === 0;
|
|
1523
1719
|
if (!haveObject) haveObject = await ensureOid("origin");
|
|
1524
|
-
if (!haveObject &&
|
|
1525
|
-
haveObject = await ensureOid(
|
|
1720
|
+
if (!haveObject && httpsRemote) {
|
|
1721
|
+
haveObject = await ensureOid(httpsRemote);
|
|
1722
|
+
if (!haveObject) {
|
|
1723
|
+
haveObject = await ensureOid(httpsRemote, { ghAuth: true });
|
|
1724
|
+
}
|
|
1526
1725
|
}
|
|
1527
1726
|
if (haveObject) {
|
|
1528
1727
|
const branched = await git(["branch", "-f", localBranch, oid], repoPath, {
|
|
@@ -1617,6 +1816,52 @@ ${add.stdout}`;
|
|
|
1617
1816
|
await ensureGhPreferOrigin(worktreePath);
|
|
1618
1817
|
return { branchName, worktreePath };
|
|
1619
1818
|
}
|
|
1819
|
+
async function createExistingBranchWorktree(opts) {
|
|
1820
|
+
const branchName = opts.branchName.trim();
|
|
1821
|
+
if (!branchName) throw new Error("branch name required");
|
|
1822
|
+
const worktreePath = join(worktreesRoot(opts.repoPath), opts.slug);
|
|
1823
|
+
if (existsSync(worktreePath)) {
|
|
1824
|
+
throw new Error(`Worktree already exists at ${worktreePath}`);
|
|
1825
|
+
}
|
|
1826
|
+
await ensureGhPreferOrigin(opts.repoPath);
|
|
1827
|
+
await git(["fetch", "origin", "--prune"], opts.repoPath, { reject: false });
|
|
1828
|
+
if (!branchName.startsWith("origin/") && !branchName.startsWith("refs/")) {
|
|
1829
|
+
await git(["fetch", "origin", branchName], opts.repoPath, { reject: false });
|
|
1830
|
+
}
|
|
1831
|
+
const existing = await listWorktrees(opts.repoPath);
|
|
1832
|
+
const already = existing.find((w) => w.branch === branchName);
|
|
1833
|
+
if (already?.path) {
|
|
1834
|
+
throw new Error(
|
|
1835
|
+
`Branch ${branchName} is already checked out at ${already.path}`
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
const startPoint = await resolveWorktreeStartPoint(opts.repoPath, branchName);
|
|
1839
|
+
const add = await git(
|
|
1840
|
+
["worktree", "add", worktreePath, startPoint],
|
|
1841
|
+
opts.repoPath,
|
|
1842
|
+
{ reject: false }
|
|
1843
|
+
);
|
|
1844
|
+
if (add.exitCode !== 0) {
|
|
1845
|
+
const retry = await git(
|
|
1846
|
+
["worktree", "add", worktreePath, branchName],
|
|
1847
|
+
opts.repoPath,
|
|
1848
|
+
{ reject: false }
|
|
1849
|
+
);
|
|
1850
|
+
if (retry.exitCode !== 0) {
|
|
1851
|
+
throw new Error(
|
|
1852
|
+
`Failed to create worktree for ${branchName}: ${retry.stderr.trim() || add.stderr.trim() || retry.stdout.trim() || add.stdout.trim() || `exit ${add.exitCode}`}`
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath, {
|
|
1857
|
+
reject: false
|
|
1858
|
+
});
|
|
1859
|
+
if (head.stdout.trim() === "HEAD" || head.stdout.trim() !== branchName) {
|
|
1860
|
+
await git(["checkout", "-B", branchName], worktreePath, { reject: false });
|
|
1861
|
+
}
|
|
1862
|
+
await ensureGhPreferOrigin(worktreePath);
|
|
1863
|
+
return { branchName, worktreePath };
|
|
1864
|
+
}
|
|
1620
1865
|
async function removeWorktree(repoPath, worktreePath, opts) {
|
|
1621
1866
|
await git(["worktree", "remove", "--force", worktreePath], repoPath, {
|
|
1622
1867
|
reject: false
|
|
@@ -1646,8 +1891,27 @@ async function listWorktrees(repoPath) {
|
|
|
1646
1891
|
return entries;
|
|
1647
1892
|
}
|
|
1648
1893
|
async function isDirty(worktreePath) {
|
|
1649
|
-
const { stdout } = await git(["status", "--porcelain"], worktreePath);
|
|
1650
|
-
|
|
1894
|
+
const { stdout } = await git(["status", "--porcelain", "-uall"], worktreePath);
|
|
1895
|
+
for (const line of stdout.split("\n")) {
|
|
1896
|
+
if (!line.trim()) continue;
|
|
1897
|
+
const rel = porcelainStatusPath(line);
|
|
1898
|
+
if (!rel || isSideboardScratchPath(rel)) continue;
|
|
1899
|
+
return true;
|
|
1900
|
+
}
|
|
1901
|
+
return false;
|
|
1902
|
+
}
|
|
1903
|
+
function isSideboardScratchPath(relativePath) {
|
|
1904
|
+
return isWorkspaceScratchPath(relativePath);
|
|
1905
|
+
}
|
|
1906
|
+
function porcelainStatusPath(line) {
|
|
1907
|
+
const rest = line.length >= 3 ? line.slice(3) : "";
|
|
1908
|
+
if (!rest) return "";
|
|
1909
|
+
const arrow = rest.lastIndexOf(" -> ");
|
|
1910
|
+
const raw = arrow >= 0 ? rest.slice(arrow + 4) : rest;
|
|
1911
|
+
if (raw.startsWith('"') && raw.endsWith('"')) {
|
|
1912
|
+
return raw.slice(1, -1).replace(/\\([\\"])/g, "$1");
|
|
1913
|
+
}
|
|
1914
|
+
return raw;
|
|
1651
1915
|
}
|
|
1652
1916
|
async function currentBranch(worktreePath) {
|
|
1653
1917
|
const { stdout } = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath);
|
|
@@ -1665,7 +1929,7 @@ async function pushBranch(worktreePath, branchName) {
|
|
|
1665
1929
|
}
|
|
1666
1930
|
async function mergePr(cwd, selector, opts) {
|
|
1667
1931
|
const slug = await resolveGithubRepoSlug(cwd);
|
|
1668
|
-
const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft"];
|
|
1932
|
+
const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft,number"];
|
|
1669
1933
|
if (slug) viewArgs.push("--repo", slug);
|
|
1670
1934
|
const before = await gh(viewArgs, cwd, { reject: false });
|
|
1671
1935
|
if (before.exitCode !== 0 || !before.stdout.trim()) {
|
|
@@ -1673,16 +1937,29 @@ async function mergePr(cwd, selector, opts) {
|
|
|
1673
1937
|
}
|
|
1674
1938
|
let url = "";
|
|
1675
1939
|
let isDraft = false;
|
|
1940
|
+
let prNumber = null;
|
|
1676
1941
|
try {
|
|
1677
1942
|
const parsed = JSON.parse(before.stdout);
|
|
1678
1943
|
url = String(parsed.url ?? "");
|
|
1679
1944
|
isDraft = Boolean(parsed.isDraft);
|
|
1945
|
+
prNumber = typeof parsed.number === "number" && Number.isFinite(parsed.number) ? parsed.number : null;
|
|
1680
1946
|
if (String(parsed.state ?? "").toUpperCase() === "MERGED") {
|
|
1681
1947
|
return { url, state: "MERGED" };
|
|
1682
1948
|
}
|
|
1683
1949
|
} catch {
|
|
1684
1950
|
throw new Error("Could not parse pull request details");
|
|
1685
1951
|
}
|
|
1952
|
+
const stack = await getPrStack(cwd);
|
|
1953
|
+
const stackLayer = stack && prNumber != null ? stack.layers.find((l) => l.prNumber === prNumber) : null;
|
|
1954
|
+
if (stack && stackLayer && prNumber != null) {
|
|
1955
|
+
const throughIndex = stack.layers.findIndex((l) => l.prNumber === prNumber);
|
|
1956
|
+
const gate = stackMergeReadiness(stack.layers, throughIndex);
|
|
1957
|
+
if (!gate.readyToMerge) {
|
|
1958
|
+
throw new Error(gate.blockedReason || "Stack is not ready to merge");
|
|
1959
|
+
}
|
|
1960
|
+
await mergePrStack(cwd, { through: prNumber, method: opts?.method ?? "squash" });
|
|
1961
|
+
return { url, state: "MERGED" };
|
|
1962
|
+
}
|
|
1686
1963
|
if (isDraft) {
|
|
1687
1964
|
const readyArgs = ["pr", "ready", selector];
|
|
1688
1965
|
if (slug) readyArgs.push("--repo", slug);
|
|
@@ -1704,10 +1981,10 @@ async function mergePr(cwd, selector, opts) {
|
|
|
1704
1981
|
const after = await gh(viewArgs, cwd, { reject: false });
|
|
1705
1982
|
if (after.exitCode === 0 && after.stdout.trim()) {
|
|
1706
1983
|
try {
|
|
1707
|
-
const parsed = JSON.parse(after.stdout);
|
|
1984
|
+
const parsed = after.stdout ? JSON.parse(after.stdout) : null;
|
|
1708
1985
|
return {
|
|
1709
|
-
url: String(parsed
|
|
1710
|
-
state: String(parsed
|
|
1986
|
+
url: String(parsed?.url ?? url),
|
|
1987
|
+
state: String(parsed?.state ?? "MERGED")
|
|
1711
1988
|
};
|
|
1712
1989
|
} catch {
|
|
1713
1990
|
}
|
|
@@ -1857,6 +2134,10 @@ export {
|
|
|
1857
2134
|
worktreeDisplayLabel,
|
|
1858
2135
|
worktreeDisplayLabelForGroup,
|
|
1859
2136
|
formatGhLandError,
|
|
2137
|
+
detectGhStack,
|
|
2138
|
+
getPrStack,
|
|
2139
|
+
initPrStack,
|
|
2140
|
+
addPrStackLayer,
|
|
1860
2141
|
slugify,
|
|
1861
2142
|
resolveRepoRoot,
|
|
1862
2143
|
parseGithubSlugFromRemoteUrl,
|
|
@@ -1878,9 +2159,11 @@ export {
|
|
|
1878
2159
|
fetchPrHead,
|
|
1879
2160
|
resolveWorktreeStartPoint,
|
|
1880
2161
|
createThreadWorktree,
|
|
2162
|
+
createExistingBranchWorktree,
|
|
1881
2163
|
removeWorktree,
|
|
1882
2164
|
listWorktrees,
|
|
1883
2165
|
isDirty,
|
|
2166
|
+
isSideboardScratchPath,
|
|
1884
2167
|
currentBranch,
|
|
1885
2168
|
commitAll,
|
|
1886
2169
|
pushBranch,
|
|
@@ -3,18 +3,18 @@ import {
|
|
|
3
3
|
} from "./chunk-GI7E5733.js";
|
|
4
4
|
import {
|
|
5
5
|
ensureGlobalCoordinatorCwd
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-LRLKJM3O.js";
|
|
7
7
|
import {
|
|
8
8
|
allocateTeamName,
|
|
9
9
|
takenSlugsFromThread,
|
|
10
10
|
teamSlugFromName
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-DZFH2KLT.js";
|
|
12
12
|
import {
|
|
13
13
|
createEmptyThread,
|
|
14
14
|
listThreads,
|
|
15
15
|
updateThread,
|
|
16
16
|
writeThread
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-TSRXOSVD.js";
|
|
18
18
|
import {
|
|
19
19
|
globalAgentCwd
|
|
20
20
|
} from "./chunk-M37RITA6.js";
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
isGlobalRepoPath
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-YOWIYAVA.js";
|
|
6
6
|
import {
|
|
7
7
|
ensureGhPreferOrigin,
|
|
8
8
|
resolveRepoRoot
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-XOU6HNQJ.js";
|
|
10
10
|
import {
|
|
11
11
|
appDataDir
|
|
12
12
|
} from "./chunk-7MV3RXSC.js";
|
|
@@ -5,18 +5,18 @@ import {
|
|
|
5
5
|
} from "./chunk-S2LL5HA4.js";
|
|
6
6
|
import {
|
|
7
7
|
ensureGlobalCoordinatorCwd
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-UEAHMGHW.js";
|
|
9
9
|
import {
|
|
10
10
|
allocateTeamName,
|
|
11
11
|
takenSlugsFromThread,
|
|
12
12
|
teamSlugFromName
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-XOU6HNQJ.js";
|
|
14
14
|
import {
|
|
15
15
|
createEmptyThread,
|
|
16
16
|
listThreads,
|
|
17
17
|
updateThread,
|
|
18
18
|
writeThread
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-HLEX5AQ6.js";
|
|
20
20
|
import {
|
|
21
21
|
globalAgentCwd
|
|
22
22
|
} from "./chunk-7MV3RXSC.js";
|
|
@@ -63,15 +63,33 @@ async function run(file, args, opts) {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
async function git(args, cwd, opts) {
|
|
66
|
-
|
|
66
|
+
const prefix = [];
|
|
67
|
+
if (opts?.config) {
|
|
68
|
+
for (const [key, value] of Object.entries(opts.config)) {
|
|
69
|
+
if (!key) continue;
|
|
70
|
+
prefix.push("-c", `${key}=${value}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return run("git", [...prefix, ...args], {
|
|
74
|
+
cwd,
|
|
75
|
+
reject: opts?.reject,
|
|
76
|
+
env: opts?.env
|
|
77
|
+
});
|
|
67
78
|
}
|
|
68
79
|
async function gh(args, cwd, opts) {
|
|
69
80
|
return run("gh", args, { cwd, reject: opts?.reject });
|
|
70
81
|
}
|
|
82
|
+
async function resolveGhAuthToken(cwd) {
|
|
83
|
+
const result = await gh(["auth", "token"], cwd, { reject: false });
|
|
84
|
+
if (result.exitCode !== 0) return null;
|
|
85
|
+
const token = result.stdout.trim();
|
|
86
|
+
return token || null;
|
|
87
|
+
}
|
|
71
88
|
|
|
72
89
|
export {
|
|
73
90
|
ensureAgentPath,
|
|
74
91
|
run,
|
|
75
92
|
git,
|
|
76
|
-
gh
|
|
93
|
+
gh,
|
|
94
|
+
resolveGhAuthToken
|
|
77
95
|
};
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
ensureConnectedBrightsyTeamTokens,
|
|
10
10
|
getConnectedBrightsyTeamsRaw,
|
|
11
11
|
listConnectedBrightsyTeams
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-H6GGDLYS.js";
|
|
13
|
+
import "./chunk-ZNM4MAN4.js";
|
|
14
14
|
import "./chunk-7MV3RXSC.js";
|
|
15
15
|
export {
|
|
16
16
|
applyConnectedTeamToCli,
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
ensureConnectedBrightsyTeamTokens,
|
|
8
8
|
getConnectedBrightsyTeamsRaw,
|
|
9
9
|
listConnectedBrightsyTeams
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BXJ76RHF.js";
|
|
11
11
|
import "./chunk-M37RITA6.js";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-7LNGIP3X.js";
|
|
13
13
|
export {
|
|
14
14
|
applyConnectedTeamToCli,
|
|
15
15
|
brightsyMcpServerName,
|