@sideboard-ai/core 0.1.10 → 0.1.19
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-OAX7XPKX.js → agents-2XMZO3CY.js} +1 -1
- package/dist/{chunk-2M4OHXYX.js → chunk-5263JXQY.js} +2 -2
- package/dist/{chunk-WMCPLDW3.js → chunk-JQJZTL2Q.js} +1 -1
- package/dist/{chunk-TLJH3L2C.js → chunk-LIUV5ONW.js} +44 -4
- package/dist/{chunk-LL7DTZ5B.js → chunk-LXHSRNJJ.js} +225 -48
- package/dist/{chunk-2R5VV4BA.js → chunk-SNHWAARD.js} +6 -5
- package/dist/{chunk-E4PWXO2C.js → chunk-WWBC56EL.js} +78 -29
- package/dist/{coordinator-prompt-6R2TX4WQ.js → coordinator-prompt-QPTX6YCW.js} +2 -2
- package/dist/{global-workspace-R44HGBU6.js → global-workspace-IV6LIDTO.js} +3 -3
- package/dist/index.cjs +360 -72
- package/dist/index.d.cts +93 -5
- package/dist/index.d.ts +93 -5
- package/dist/index.js +26 -6
- package/dist/mcp/run-stdio.cjs +2761 -2501
- package/dist/mcp/run-stdio.js +6 -6
- package/dist/{workspaces-TCJFYI35.js → workspaces-JBWIRU55.js} +4 -4
- package/dist/{worktree-NGFDN3J4.js → worktree-TYI2SANE.js} +11 -1
- package/package.json +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ensureGlobalCoordinatorCwd
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-SNHWAARD.js";
|
|
4
4
|
import {
|
|
5
5
|
allocateTeamName,
|
|
6
6
|
takenSlugsFromThread,
|
|
7
7
|
teamSlugFromName
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-LXHSRNJJ.js";
|
|
9
9
|
import {
|
|
10
10
|
createEmptyThread,
|
|
11
11
|
listThreads,
|
|
@@ -719,7 +719,7 @@ var claudeAdapter = {
|
|
|
719
719
|
);
|
|
720
720
|
}
|
|
721
721
|
const mode = permissionMode(thread);
|
|
722
|
-
const { isOrchestratorThread } = await import("./global-workspace-
|
|
722
|
+
const { isOrchestratorThread } = await import("./global-workspace-IV6LIDTO.js");
|
|
723
723
|
const isOrchestrator = isOrchestratorThread(thread);
|
|
724
724
|
const injectedServers = await buildInjectedMcpServers({
|
|
725
725
|
includeSideboard: isOrchestrator,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isGlobalRepoPath
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5263JXQY.js";
|
|
4
4
|
import {
|
|
5
|
+
ensureGhPreferOrigin,
|
|
5
6
|
resolveRepoRoot
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-LXHSRNJJ.js";
|
|
7
8
|
import {
|
|
8
9
|
appDataDir
|
|
9
10
|
} from "./chunk-M37RITA6.js";
|
|
@@ -14,6 +15,9 @@ import { basename, join } from "path";
|
|
|
14
15
|
function workspacesFile() {
|
|
15
16
|
return join(appDataDir(), "workspaces.json");
|
|
16
17
|
}
|
|
18
|
+
function removedWorkspacesFile() {
|
|
19
|
+
return join(appDataDir(), "removed-workspaces.json");
|
|
20
|
+
}
|
|
17
21
|
function readAll() {
|
|
18
22
|
const path = workspacesFile();
|
|
19
23
|
if (!existsSync(path)) return [];
|
|
@@ -28,12 +32,44 @@ function writeAll(list) {
|
|
|
28
32
|
mkdirSync(appDataDir(), { recursive: true });
|
|
29
33
|
writeFileSync(workspacesFile(), JSON.stringify(list, null, 2), "utf8");
|
|
30
34
|
}
|
|
35
|
+
function readRemoved() {
|
|
36
|
+
const path = removedWorkspacesFile();
|
|
37
|
+
if (!existsSync(path)) return /* @__PURE__ */ new Set();
|
|
38
|
+
try {
|
|
39
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
40
|
+
return new Set(Array.isArray(raw) ? raw.filter((p) => typeof p === "string") : []);
|
|
41
|
+
} catch {
|
|
42
|
+
return /* @__PURE__ */ new Set();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function writeRemoved(paths) {
|
|
46
|
+
mkdirSync(appDataDir(), { recursive: true });
|
|
47
|
+
writeFileSync(removedWorkspacesFile(), JSON.stringify([...paths].sort(), null, 2), "utf8");
|
|
48
|
+
}
|
|
49
|
+
function rememberRemoved(repoPath) {
|
|
50
|
+
const next = readRemoved();
|
|
51
|
+
next.add(repoPath);
|
|
52
|
+
writeRemoved(next);
|
|
53
|
+
}
|
|
54
|
+
function forgetRemoved(repoPath) {
|
|
55
|
+
const next = readRemoved();
|
|
56
|
+
if (!next.delete(repoPath)) return;
|
|
57
|
+
writeRemoved(next);
|
|
58
|
+
}
|
|
31
59
|
function listWorkspaces() {
|
|
32
|
-
|
|
60
|
+
const all = readAll();
|
|
61
|
+
const valid = all.filter(
|
|
62
|
+
(w) => Boolean(w.path) && w.path !== "/" && w.path !== "." && !isGlobalRepoPath(w.path)
|
|
63
|
+
);
|
|
64
|
+
if (valid.length !== all.length) writeAll(valid);
|
|
65
|
+
return valid.sort((a, b) => a.name.localeCompare(b.name));
|
|
33
66
|
}
|
|
34
67
|
async function addWorkspace(repoPath) {
|
|
35
68
|
const root = await resolveRepoRoot(repoPath);
|
|
69
|
+
if (!root || root === "/") throw new Error(`Invalid repo path: ${repoPath}`);
|
|
36
70
|
if (!existsSync(root)) throw new Error(`Repo not found: ${root}`);
|
|
71
|
+
forgetRemoved(root);
|
|
72
|
+
await ensureGhPreferOrigin(root);
|
|
37
73
|
const current = readAll();
|
|
38
74
|
const existing = current.find((w) => w.path === root);
|
|
39
75
|
if (existing) return existing;
|
|
@@ -47,16 +83,20 @@ async function addWorkspace(repoPath) {
|
|
|
47
83
|
}
|
|
48
84
|
function removeWorkspace(repoPath) {
|
|
49
85
|
writeAll(readAll().filter((w) => w.path !== repoPath));
|
|
86
|
+
rememberRemoved(repoPath);
|
|
50
87
|
}
|
|
51
88
|
async function ensureWorkspace(repoPath) {
|
|
52
89
|
return addWorkspace(repoPath);
|
|
53
90
|
}
|
|
54
91
|
function syncWorkspacesFromThreads(repoPaths) {
|
|
55
92
|
const current = readAll();
|
|
93
|
+
const removed = readRemoved();
|
|
56
94
|
const byPath = new Map(current.map((w) => [w.path, w]));
|
|
57
95
|
let dirty = false;
|
|
58
96
|
for (const path of repoPaths) {
|
|
59
|
-
if (!path || isGlobalRepoPath(path) || byPath.has(path))
|
|
97
|
+
if (!path || path === "/" || isGlobalRepoPath(path) || byPath.has(path) || removed.has(path)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
60
100
|
if (!existsSync(path)) continue;
|
|
61
101
|
const ws = {
|
|
62
102
|
path,
|
|
@@ -459,6 +459,64 @@ function worktreeDisplayLabelForGroup(threads) {
|
|
|
459
459
|
return threadDisplayLabel(canonical);
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
+
// src/git/gh-errors.ts
|
|
463
|
+
function isGhRateLimitError(text) {
|
|
464
|
+
return /API rate limit (already )?exceeded/i.test(text) || /rate limit exceeded/i.test(text);
|
|
465
|
+
}
|
|
466
|
+
function formatRateLimitResetHint(resetEpochSec, nowMs = Date.now()) {
|
|
467
|
+
const ms = resetEpochSec * 1e3 - nowMs;
|
|
468
|
+
if (ms <= 0) return "soon";
|
|
469
|
+
const mins = Math.max(1, Math.ceil(ms / 6e4));
|
|
470
|
+
if (mins < 60) {
|
|
471
|
+
return `in about ${mins} minute${mins === 1 ? "" : "s"}`;
|
|
472
|
+
}
|
|
473
|
+
const hours = Math.ceil(mins / 60);
|
|
474
|
+
return `in about ${hours} hour${hours === 1 ? "" : "s"}`;
|
|
475
|
+
}
|
|
476
|
+
function extractGhErrorDetail(text) {
|
|
477
|
+
const trimmed = text.trim();
|
|
478
|
+
if (!trimmed) return "";
|
|
479
|
+
const graphql = trimmed.match(/\bGraphQL:\s*(.+)$/im);
|
|
480
|
+
if (graphql?.[1]) return `GraphQL: ${graphql[1].trim()}`;
|
|
481
|
+
const http = trimmed.match(/\bHTTP\s+\d{3}:\s*(.+)$/im);
|
|
482
|
+
if (http?.[1]) return `HTTP: ${http[1].trim()}`;
|
|
483
|
+
const lines = trimmed.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
484
|
+
if (lines.length > 1 && /^Command failed with exit code/i.test(lines[0])) {
|
|
485
|
+
return lines.slice(1).join(" ").trim() || lines[0];
|
|
486
|
+
}
|
|
487
|
+
return trimmed;
|
|
488
|
+
}
|
|
489
|
+
function formatGhLandError(raw, opts) {
|
|
490
|
+
const trimmed = raw.trim();
|
|
491
|
+
if (trimmed.startsWith("GitHub API rate limit exceeded.")) {
|
|
492
|
+
return trimmed;
|
|
493
|
+
}
|
|
494
|
+
const detail = extractGhErrorDetail(raw);
|
|
495
|
+
if (/Head ref must be a branch|No commits between|Head sha can't be blank/i.test(
|
|
496
|
+
raw
|
|
497
|
+
) || /Head ref must be a branch|No commits between|Head sha can't be blank/i.test(
|
|
498
|
+
detail
|
|
499
|
+
)) {
|
|
500
|
+
const target = opts?.targetedRepo ? ` Targeted ${opts.targetedRepo}` : "";
|
|
501
|
+
const head = opts?.headRef ? ` with head ${opts.headRef}` : "";
|
|
502
|
+
const hint = opts?.targetedRepo ? ` Branch was pushed to origin \u2014 confirm it exists on GitHub and differs from the base branch.${target}${head}.` : " Often `gh` targeted upstream instead of origin. Branch was pushed \u2014 retry in the latest Sideboard, or run: gh pr create -R <owner/name> --base main --head <branch>.";
|
|
503
|
+
return `Could not create the pull request.${hint}`;
|
|
504
|
+
}
|
|
505
|
+
if (isGhRateLimitError(raw) || isGhRateLimitError(detail)) {
|
|
506
|
+
const when = opts?.resetAt ? ` Try again ${formatRateLimitResetHint(opts.resetAt, opts.nowMs)}.` : " Wait a few minutes and try again.";
|
|
507
|
+
const pushNote = opts?.pushed === false ? "" : " Your branch was already pushed.";
|
|
508
|
+
return `GitHub API rate limit exceeded.${pushNote}${when} Or create the pull request in the browser (Push & open on GitHub).`;
|
|
509
|
+
}
|
|
510
|
+
return detail || "Failed to create or update pull request";
|
|
511
|
+
}
|
|
512
|
+
function formatIpcInvokeError(err) {
|
|
513
|
+
let msg = err instanceof Error ? err.message : String(err);
|
|
514
|
+
msg = msg.replace(/^Error invoking remote method '[^']+':\s*/i, "");
|
|
515
|
+
msg = msg.replace(/^ExecaError:\s*/i, "");
|
|
516
|
+
msg = msg.replace(/^Error:\s*/i, "");
|
|
517
|
+
return formatGhLandError(msg);
|
|
518
|
+
}
|
|
519
|
+
|
|
462
520
|
// src/git/pr-gates.ts
|
|
463
521
|
function buildMergeGateChecks(gate, opts = {}) {
|
|
464
522
|
const rows = [];
|
|
@@ -547,6 +605,31 @@ function buildMergeGateChecks(gate, opts = {}) {
|
|
|
547
605
|
}
|
|
548
606
|
|
|
549
607
|
// src/git/worktree.ts
|
|
608
|
+
async function lookupGithubGraphqlReset(cwd) {
|
|
609
|
+
const result = await gh(["api", "rate_limit"], cwd, { reject: false });
|
|
610
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) return void 0;
|
|
611
|
+
try {
|
|
612
|
+
const data = JSON.parse(result.stdout);
|
|
613
|
+
const reset = data.resources?.graphql?.reset;
|
|
614
|
+
return typeof reset === "number" ? reset : void 0;
|
|
615
|
+
} catch {
|
|
616
|
+
return void 0;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
async function formatPrCreateFailure(raw, cwd, ctx) {
|
|
620
|
+
if (!isGhRateLimitError(raw)) {
|
|
621
|
+
return formatGhLandError(raw, {
|
|
622
|
+
targetedRepo: ctx?.slug,
|
|
623
|
+
headRef: ctx?.head
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
const resetAt = await lookupGithubGraphqlReset(cwd);
|
|
627
|
+
return formatGhLandError(raw, {
|
|
628
|
+
resetAt,
|
|
629
|
+
targetedRepo: ctx?.slug,
|
|
630
|
+
headRef: ctx?.head
|
|
631
|
+
});
|
|
632
|
+
}
|
|
550
633
|
function slugify(input) {
|
|
551
634
|
return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
|
|
552
635
|
}
|
|
@@ -555,10 +638,13 @@ async function resolveRepoRoot(cwd) {
|
|
|
555
638
|
return stdout.trim();
|
|
556
639
|
}
|
|
557
640
|
function parseGithubSlugFromRemoteUrl(url) {
|
|
558
|
-
const trimmed = url.trim();
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
return `${
|
|
641
|
+
const trimmed = url.trim().replace(/\.git$/i, "");
|
|
642
|
+
if (!trimmed) return null;
|
|
643
|
+
const github = trimmed.match(/github\.com[:/]([^/]+)\/([^/]+)$/i);
|
|
644
|
+
if (github?.[1] && github[2]) return `${github[1]}/${github[2]}`;
|
|
645
|
+
const sshAlias = trimmed.match(/^git@[^:]+:([^/]+)\/([^/]+)$/i);
|
|
646
|
+
if (sshAlias?.[1] && sshAlias[2]) return `${sshAlias[1]}/${sshAlias[2]}`;
|
|
647
|
+
return null;
|
|
562
648
|
}
|
|
563
649
|
async function slugFromGitRemote(repoPath, remote) {
|
|
564
650
|
const result = await git(["remote", "get-url", remote], repoPath, {
|
|
@@ -570,13 +656,16 @@ async function slugFromGitRemote(repoPath, remote) {
|
|
|
570
656
|
async function resolveGithubRepoSlug(repoPath) {
|
|
571
657
|
const fromOrigin = await slugFromGitRemote(repoPath, "origin");
|
|
572
658
|
if (fromOrigin) return fromOrigin;
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
659
|
+
const hasAlt = Boolean(await slugFromGitRemote(repoPath, "upstream")) || Boolean(await slugFromGitRemote(repoPath, "github"));
|
|
660
|
+
if (!hasAlt) {
|
|
661
|
+
const viaGh = await gh(
|
|
662
|
+
["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"],
|
|
663
|
+
repoPath,
|
|
664
|
+
{ reject: false }
|
|
665
|
+
);
|
|
666
|
+
if (viaGh.exitCode === 0 && viaGh.stdout.trim()) {
|
|
667
|
+
return viaGh.stdout.trim();
|
|
668
|
+
}
|
|
580
669
|
}
|
|
581
670
|
for (const remote of ["upstream", "github"]) {
|
|
582
671
|
const slug = await slugFromGitRemote(repoPath, remote);
|
|
@@ -584,15 +673,35 @@ async function resolveGithubRepoSlug(repoPath) {
|
|
|
584
673
|
}
|
|
585
674
|
return null;
|
|
586
675
|
}
|
|
676
|
+
function ghRepoSelectArgs(slug) {
|
|
677
|
+
return ["-R", slug];
|
|
678
|
+
}
|
|
679
|
+
function ghHeadRef(slug, branch) {
|
|
680
|
+
const owner = slug.split("/")[0];
|
|
681
|
+
const head = branch.trim().replace(/^refs\/heads\//, "");
|
|
682
|
+
if (!owner || !head) return head || branch;
|
|
683
|
+
if (head.includes(":")) return head;
|
|
684
|
+
return `${owner}:${head}`;
|
|
685
|
+
}
|
|
686
|
+
async function ensureGhPreferOrigin(cwd) {
|
|
687
|
+
const originSlug = await slugFromGitRemote(cwd, "origin");
|
|
688
|
+
if (!originSlug) return;
|
|
689
|
+
const hasAlt = Boolean(await slugFromGitRemote(cwd, "upstream")) || Boolean(await slugFromGitRemote(cwd, "github"));
|
|
690
|
+
if (!hasAlt) return;
|
|
691
|
+
const view = await gh(["repo", "set-default", "--view"], cwd, {
|
|
692
|
+
reject: false
|
|
693
|
+
});
|
|
694
|
+
const current = `${view.stdout}
|
|
695
|
+
${view.stderr}`;
|
|
696
|
+
if (view.exitCode === 0 && current.includes(originSlug)) return;
|
|
697
|
+
await gh(["repo", "set-default", "origin"], cwd, { reject: false });
|
|
698
|
+
}
|
|
699
|
+
async function originGhRepoEnv(cwd) {
|
|
700
|
+
await ensureGhPreferOrigin(cwd);
|
|
701
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
702
|
+
return slug ? { GH_REPO: slug } : {};
|
|
703
|
+
}
|
|
587
704
|
async function resolveDefaultBranch(repoPath) {
|
|
588
|
-
const viaGh = await gh(
|
|
589
|
-
["repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"],
|
|
590
|
-
repoPath,
|
|
591
|
-
{ reject: false }
|
|
592
|
-
);
|
|
593
|
-
if (viaGh.exitCode === 0 && viaGh.stdout.trim()) {
|
|
594
|
-
return viaGh.stdout.trim();
|
|
595
|
-
}
|
|
596
705
|
const viaOrigin = await git(
|
|
597
706
|
["symbolic-ref", "refs/remotes/origin/HEAD"],
|
|
598
707
|
repoPath,
|
|
@@ -601,6 +710,31 @@ async function resolveDefaultBranch(repoPath) {
|
|
|
601
710
|
if (viaOrigin.exitCode === 0 && viaOrigin.stdout.trim()) {
|
|
602
711
|
return viaOrigin.stdout.trim().replace(/^refs\/remotes\/origin\//, "");
|
|
603
712
|
}
|
|
713
|
+
const slug = await resolveGithubRepoSlug(repoPath);
|
|
714
|
+
const viaGh = await gh(
|
|
715
|
+
[
|
|
716
|
+
"repo",
|
|
717
|
+
"view",
|
|
718
|
+
...slug ? ["--repo", slug] : [],
|
|
719
|
+
"--json",
|
|
720
|
+
"defaultBranchRef",
|
|
721
|
+
"--jq",
|
|
722
|
+
".defaultBranchRef.name"
|
|
723
|
+
],
|
|
724
|
+
repoPath,
|
|
725
|
+
{ reject: false }
|
|
726
|
+
);
|
|
727
|
+
if (viaGh.exitCode === 0 && viaGh.stdout.trim()) {
|
|
728
|
+
return viaGh.stdout.trim();
|
|
729
|
+
}
|
|
730
|
+
for (const candidate of ["main", "master"]) {
|
|
731
|
+
const check = await git(
|
|
732
|
+
["show-ref", "--verify", "--quiet", `refs/remotes/origin/${candidate}`],
|
|
733
|
+
repoPath,
|
|
734
|
+
{ reject: false }
|
|
735
|
+
);
|
|
736
|
+
if (check.exitCode === 0) return candidate;
|
|
737
|
+
}
|
|
604
738
|
for (const candidate of ["main", "master"]) {
|
|
605
739
|
const check = await git(
|
|
606
740
|
["show-ref", "--verify", "--quiet", `refs/heads/${candidate}`],
|
|
@@ -861,6 +995,37 @@ async function getPrChecks(cwd, selector) {
|
|
|
861
995
|
});
|
|
862
996
|
return [...gateRows, ...ciChecks];
|
|
863
997
|
}
|
|
998
|
+
async function getPrMeta(cwd, selector) {
|
|
999
|
+
const slug = await resolveGithubRepoSlug(cwd);
|
|
1000
|
+
const viewArgs = [
|
|
1001
|
+
"pr",
|
|
1002
|
+
"view",
|
|
1003
|
+
selector,
|
|
1004
|
+
"--json",
|
|
1005
|
+
"number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName"
|
|
1006
|
+
];
|
|
1007
|
+
if (slug) viewArgs.push("--repo", slug);
|
|
1008
|
+
const { stdout, exitCode, stderr } = await gh(viewArgs, cwd, { reject: false });
|
|
1009
|
+
if (exitCode !== 0 || !stdout.trim()) {
|
|
1010
|
+
if (/no pull requests found/i.test(stderr)) return null;
|
|
1011
|
+
return null;
|
|
1012
|
+
}
|
|
1013
|
+
try {
|
|
1014
|
+
const view = JSON.parse(stdout);
|
|
1015
|
+
return {
|
|
1016
|
+
number: Number(view.number),
|
|
1017
|
+
title: String(view.title ?? ""),
|
|
1018
|
+
url: String(view.url ?? ""),
|
|
1019
|
+
state: String(view.state ?? ""),
|
|
1020
|
+
isDraft: Boolean(view.isDraft),
|
|
1021
|
+
reviewDecision: typeof view.reviewDecision === "string" && view.reviewDecision ? view.reviewDecision : null,
|
|
1022
|
+
baseRefName: String(view.baseRefName ?? ""),
|
|
1023
|
+
headRefName: String(view.headRefName ?? "")
|
|
1024
|
+
};
|
|
1025
|
+
} catch {
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
864
1029
|
async function getPrDetails(cwd, selector) {
|
|
865
1030
|
const slug = await resolveGithubRepoSlug(cwd);
|
|
866
1031
|
const viewArgs = [
|
|
@@ -882,7 +1047,6 @@ async function getPrDetails(cwd, selector) {
|
|
|
882
1047
|
"additions",
|
|
883
1048
|
"deletions",
|
|
884
1049
|
"changedFiles",
|
|
885
|
-
"commits",
|
|
886
1050
|
"comments",
|
|
887
1051
|
"reviews"
|
|
888
1052
|
].join(",")
|
|
@@ -901,14 +1065,7 @@ async function getPrDetails(cwd, selector) {
|
|
|
901
1065
|
} catch {
|
|
902
1066
|
throw new Error(stderr.trim() || "gh pr view returned invalid JSON");
|
|
903
1067
|
}
|
|
904
|
-
let checks = [];
|
|
905
|
-
try {
|
|
906
|
-
checks = await getPrChecks(cwd, selector) ?? [];
|
|
907
|
-
} catch {
|
|
908
|
-
checks = [];
|
|
909
|
-
}
|
|
910
1068
|
const author = view.author ?? {};
|
|
911
|
-
const commits = Array.isArray(view.commits) ? view.commits : [];
|
|
912
1069
|
const comments = Array.isArray(view.comments) ? view.comments : [];
|
|
913
1070
|
const reviews = Array.isArray(view.reviews) ? view.reviews : [];
|
|
914
1071
|
return {
|
|
@@ -925,19 +1082,8 @@ async function getPrDetails(cwd, selector) {
|
|
|
925
1082
|
additions: Number(view.additions ?? 0),
|
|
926
1083
|
deletions: Number(view.deletions ?? 0),
|
|
927
1084
|
changedFiles: Number(view.changedFiles ?? 0),
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
const authors = Array.isArray(row.authors) ? row.authors.map((a) => {
|
|
931
|
-
const actor = a;
|
|
932
|
-
return { login: actor.login ?? "unknown", name: actor.name ?? null };
|
|
933
|
-
}) : [];
|
|
934
|
-
return {
|
|
935
|
-
oid: String(row.oid ?? ""),
|
|
936
|
-
messageHeadline: String(row.messageHeadline ?? ""),
|
|
937
|
-
committedDate: String(row.committedDate ?? ""),
|
|
938
|
-
authors
|
|
939
|
-
};
|
|
940
|
-
}),
|
|
1085
|
+
// Commits live in Changes; omit from GraphQL to save rate-limit points.
|
|
1086
|
+
commits: [],
|
|
941
1087
|
comments: comments.map((c) => {
|
|
942
1088
|
const row = c;
|
|
943
1089
|
const a = row.author ?? {};
|
|
@@ -957,7 +1103,8 @@ async function getPrDetails(cwd, selector) {
|
|
|
957
1103
|
submittedAt: normalizeGhTime(row.submittedAt)
|
|
958
1104
|
};
|
|
959
1105
|
}),
|
|
960
|
-
|
|
1106
|
+
// CI lives in Checks tab via getPrChecks — nesting burned GraphQL points.
|
|
1107
|
+
checks: []
|
|
961
1108
|
};
|
|
962
1109
|
}
|
|
963
1110
|
async function fetchPrHead(repoPath, number, localBranch) {
|
|
@@ -993,6 +1140,7 @@ async function createThreadWorktree(opts) {
|
|
|
993
1140
|
if (existsSync(worktreePath)) {
|
|
994
1141
|
throw new Error(`Worktree already exists at ${worktreePath}`);
|
|
995
1142
|
}
|
|
1143
|
+
await ensureGhPreferOrigin(opts.repoPath);
|
|
996
1144
|
await git(["fetch", "origin", "--prune"], opts.repoPath, { reject: false });
|
|
997
1145
|
if (!opts.sourceRef.startsWith("origin/") && !opts.sourceRef.startsWith("refs/")) {
|
|
998
1146
|
await git(["fetch", "origin", opts.sourceRef], opts.repoPath, {
|
|
@@ -1021,6 +1169,7 @@ ${add.stdout}`;
|
|
|
1021
1169
|
);
|
|
1022
1170
|
if (retry.exitCode === 0) {
|
|
1023
1171
|
branchName = alt;
|
|
1172
|
+
await ensureGhPreferOrigin(worktreePath);
|
|
1024
1173
|
return { branchName, worktreePath };
|
|
1025
1174
|
}
|
|
1026
1175
|
}
|
|
@@ -1029,6 +1178,7 @@ ${add.stdout}`;
|
|
|
1029
1178
|
`Failed to create worktree: ${add.stderr.trim() || add.stdout.trim() || `exit ${add.exitCode}`}`
|
|
1030
1179
|
);
|
|
1031
1180
|
}
|
|
1181
|
+
await ensureGhPreferOrigin(worktreePath);
|
|
1032
1182
|
return { branchName, worktreePath };
|
|
1033
1183
|
}
|
|
1034
1184
|
async function removeWorktree(repoPath, worktreePath, opts) {
|
|
@@ -1129,8 +1279,18 @@ async function mergePr(cwd, selector, opts) {
|
|
|
1129
1279
|
return { url, state: "MERGED" };
|
|
1130
1280
|
}
|
|
1131
1281
|
async function createOrUpdatePr(worktreePath, opts) {
|
|
1282
|
+
await ensureGhPreferOrigin(worktreePath);
|
|
1283
|
+
const slug = await resolveGithubRepoSlug(worktreePath);
|
|
1284
|
+
if (!slug) {
|
|
1285
|
+
throw new Error(
|
|
1286
|
+
"Could not resolve the origin GitHub repo (owner/name) for this worktree. Check that `git remote get-url origin` points at github.com."
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
const repoArgs = ghRepoSelectArgs(slug);
|
|
1290
|
+
const headRef = ghHeadRef(slug, opts.head);
|
|
1291
|
+
const branchOnly = opts.head.trim().replace(/^refs\/heads\//, "");
|
|
1132
1292
|
const existing = await gh(
|
|
1133
|
-
["pr", "view",
|
|
1293
|
+
[...repoArgs, "pr", "view", branchOnly, "--json", "url", "--jq", ".url"],
|
|
1134
1294
|
worktreePath,
|
|
1135
1295
|
{ reject: false }
|
|
1136
1296
|
);
|
|
@@ -1138,9 +1298,10 @@ async function createOrUpdatePr(worktreePath, opts) {
|
|
|
1138
1298
|
const url2 = existing.stdout.trim();
|
|
1139
1299
|
await gh(
|
|
1140
1300
|
[
|
|
1301
|
+
...repoArgs,
|
|
1141
1302
|
"pr",
|
|
1142
1303
|
"edit",
|
|
1143
|
-
|
|
1304
|
+
branchOnly,
|
|
1144
1305
|
"--title",
|
|
1145
1306
|
opts.title,
|
|
1146
1307
|
"--body",
|
|
@@ -1153,6 +1314,7 @@ async function createOrUpdatePr(worktreePath, opts) {
|
|
|
1153
1314
|
}
|
|
1154
1315
|
if (opts.web) {
|
|
1155
1316
|
const args2 = [
|
|
1317
|
+
...repoArgs,
|
|
1156
1318
|
"pr",
|
|
1157
1319
|
"create",
|
|
1158
1320
|
"--web",
|
|
@@ -1163,18 +1325,19 @@ async function createOrUpdatePr(worktreePath, opts) {
|
|
|
1163
1325
|
"--base",
|
|
1164
1326
|
opts.base,
|
|
1165
1327
|
"--head",
|
|
1166
|
-
|
|
1328
|
+
headRef
|
|
1167
1329
|
];
|
|
1168
1330
|
if (opts.draft) args2.push("--draft");
|
|
1169
1331
|
await gh(args2, worktreePath, { reject: false });
|
|
1170
1332
|
const again = await gh(
|
|
1171
|
-
["pr", "view",
|
|
1333
|
+
[...repoArgs, "pr", "view", branchOnly, "--json", "url", "--jq", ".url"],
|
|
1172
1334
|
worktreePath,
|
|
1173
1335
|
{ reject: false }
|
|
1174
1336
|
);
|
|
1175
1337
|
return again.stdout.trim() || "";
|
|
1176
1338
|
}
|
|
1177
1339
|
const args = [
|
|
1340
|
+
...repoArgs,
|
|
1178
1341
|
"pr",
|
|
1179
1342
|
"create",
|
|
1180
1343
|
"--title",
|
|
@@ -1184,11 +1347,15 @@ async function createOrUpdatePr(worktreePath, opts) {
|
|
|
1184
1347
|
"--base",
|
|
1185
1348
|
opts.base,
|
|
1186
1349
|
"--head",
|
|
1187
|
-
|
|
1350
|
+
headRef
|
|
1188
1351
|
];
|
|
1189
1352
|
if (opts.draft) args.push("--draft");
|
|
1190
|
-
const
|
|
1191
|
-
|
|
1353
|
+
const created = await gh(args, worktreePath, { reject: false });
|
|
1354
|
+
if (created.exitCode !== 0) {
|
|
1355
|
+
const raw = created.stderr.trim() || created.stdout.trim() || "gh pr create failed";
|
|
1356
|
+
throw new Error(await formatPrCreateFailure(raw, worktreePath, { slug, head: headRef }));
|
|
1357
|
+
}
|
|
1358
|
+
const url = created.stdout.trim().split("\n").find((l) => l.startsWith("http")) ?? created.stdout.trim();
|
|
1192
1359
|
return url;
|
|
1193
1360
|
}
|
|
1194
1361
|
function suggestSlug(source) {
|
|
@@ -1252,10 +1419,19 @@ export {
|
|
|
1252
1419
|
threadDisplayLabel,
|
|
1253
1420
|
worktreeDisplayLabel,
|
|
1254
1421
|
worktreeDisplayLabelForGroup,
|
|
1422
|
+
isGhRateLimitError,
|
|
1423
|
+
formatRateLimitResetHint,
|
|
1424
|
+
extractGhErrorDetail,
|
|
1425
|
+
formatGhLandError,
|
|
1426
|
+
formatIpcInvokeError,
|
|
1255
1427
|
slugify,
|
|
1256
1428
|
resolveRepoRoot,
|
|
1257
1429
|
parseGithubSlugFromRemoteUrl,
|
|
1258
1430
|
resolveGithubRepoSlug,
|
|
1431
|
+
ghRepoSelectArgs,
|
|
1432
|
+
ghHeadRef,
|
|
1433
|
+
ensureGhPreferOrigin,
|
|
1434
|
+
originGhRepoEnv,
|
|
1259
1435
|
resolveDefaultBranch,
|
|
1260
1436
|
resolveDiffBaseRef,
|
|
1261
1437
|
listBranches,
|
|
@@ -1264,6 +1440,7 @@ export {
|
|
|
1264
1440
|
resolvePrSelector,
|
|
1265
1441
|
detectLocalMergeConflicts,
|
|
1266
1442
|
getPrChecks,
|
|
1443
|
+
getPrMeta,
|
|
1267
1444
|
getPrDetails,
|
|
1268
1445
|
fetchPrHead,
|
|
1269
1446
|
resolveWorktreeStartPoint,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveGithubRepoSlug
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LXHSRNJJ.js";
|
|
4
4
|
import {
|
|
5
5
|
globalAgentCwd,
|
|
6
6
|
sideboardReposDir
|
|
@@ -31,7 +31,8 @@ function coordinatorGreenfieldPlaybook(reposDir) {
|
|
|
31
31
|
"- Examples:",
|
|
32
32
|
` - Clone: \`git clone <url> ${reposDir}/<name>\``,
|
|
33
33
|
` - New GitHub repo: \`gh repo create <owner>/<name> --private --clone -- ${reposDir}/<name>\` (or mkdir + git init + gh repo create + remote add + push)`,
|
|
34
|
-
"- Then: add_workspace with that absolute path \u2192 create_thread (repoPath + parentThreadId) \u2192 send_to_thread (build) \u2192 wait_for_turn \u2192 send_to_thread (`gh pr create --draft
|
|
34
|
+
"- Then: add_workspace with that absolute path \u2192 create_thread (repoPath + parentThreadId) \u2192 send_to_thread (build) \u2192 wait_for_turn \u2192 send_to_thread (`gh pr create --draft -R <origin-owner/name>`) or create_draft_pr.",
|
|
35
|
+
"- Always target the child worktree's **origin** (`github:` slug from list_workspaces / `git remote get-url origin` in that worktree). Never open PRs against `upstream`.",
|
|
35
36
|
"- Do coding work in the child worktree thread, not by editing files in this home cwd."
|
|
36
37
|
].join("\n");
|
|
37
38
|
}
|
|
@@ -56,8 +57,8 @@ var COORDINATOR_TOOL_PLAYBOOK = [
|
|
|
56
57
|
"Inspect / PRs:",
|
|
57
58
|
"- get_diff \u2014 compact diff summary",
|
|
58
59
|
"- preview_land \u2014 preview push+PR (does not push)",
|
|
59
|
-
"- Prefer asking the worktree agent via send_to_thread to open a draft PR
|
|
60
|
-
"- create_draft_pr \u2014 fallback: commit (if dirty), push, open/update a DRAFT PR from the orchestrator",
|
|
60
|
+
"- Prefer asking the worktree agent via send_to_thread to open a draft PR with `gh pr create --draft -R <origin-owner/name>` (use the workspace `github:` slug / that worktree's origin \u2014 never upstream) so it owns title/body from the diff.",
|
|
61
|
+
"- create_draft_pr \u2014 fallback: commit (if dirty), push to origin, open/update a DRAFT PR from the orchestrator (always against origin)",
|
|
61
62
|
"Human-only (do not attempt): ready-for-review confirm_land, purge_thread.",
|
|
62
63
|
"Thread links in replies: when mentioning a chat/thread for the user, include a markdown link `[Title](sideboard://thread/<id>)` using the full id (or the link field from create_thread / list_threads). Sideboard renders these as clickable opens.",
|
|
63
64
|
"Bash / Read / etc: allowed for (1) inspecting target worktrees / registered repo paths from MCP, and (2) greenfield setup under ~/sideboard/repos (git clone, gh repo create, git init+remote). Never git init/clone *inside* this synthetic home cwd \u2014 emptiness here is expected, not a bug."
|
|
@@ -123,7 +124,7 @@ function coordinatorSystemPrompt(opts) {
|
|
|
123
124
|
COORDINATOR_TOOL_PLAYBOOK,
|
|
124
125
|
coordinatorGreenfieldPlaybook(reposDir),
|
|
125
126
|
"When creating threads, pass the correct repoPath for the target workspace and parentThreadId for children.",
|
|
126
|
-
"Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 send_to_thread (ask for `gh pr create --draft
|
|
127
|
+
"Typical flow (existing): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn \u2192 send_to_thread (ask for `gh pr create --draft -R <origin-owner/name>` using the workspace github slug) \u2192 wait_for_turn. Use create_draft_pr only if the child cannot open the PR. Never target upstream.",
|
|
127
128
|
"Typical flow (new app): Bash under repos dir (clone or gh repo create) \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn \u2192 draft PR.",
|
|
128
129
|
`Goal: ${opts.goal}`,
|
|
129
130
|
`Parent thread id (pass as parentThreadId when creating children): ${opts.parentId}`,
|