@sideboard-ai/core 0.1.320 → 0.1.322
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.js +2 -2
- package/dist/{agents-CJZYJOWP.js → agents-PFVYC36J.js} +8 -8
- package/dist/{agents-F2E2HACB.js → agents-ROFDNE7Z.js} +9 -9
- package/dist/{chunk-QVN2WOKJ.js → chunk-2TUE6JKQ.js} +12 -0
- package/dist/{chunk-B7UL36WH.js → chunk-45UB2J47.js} +335 -65
- package/dist/{chunk-DD67JWTN.js → chunk-4YXZOXMT.js} +1 -1
- package/dist/{chunk-V5BTGQ6M.js → chunk-6U6QRUHY.js} +1 -1
- package/dist/{chunk-QGY3UNNL.js → chunk-7JV5QP5K.js} +1 -1
- package/dist/{chunk-DJZRDTZY.js → chunk-BANN5HUX.js} +6 -6
- package/dist/{chunk-CB5Z5C3J.js → chunk-BPDVHB4U.js} +1 -1
- package/dist/{chunk-RHJPP26T.js → chunk-H6GXYV5Q.js} +14 -11
- package/dist/{chunk-4GZC6DDH.js → chunk-IFWGG2M2.js} +7 -0
- package/dist/{chunk-LLIEU2ZB.js → chunk-JNEFEA2A.js} +6 -6
- package/dist/{chunk-IDPFLXRY.js → chunk-LD2DXHBP.js} +341 -67
- package/dist/{chunk-2P4XKWV2.js → chunk-QKHEVSB5.js} +1 -1
- package/dist/{chunk-P74GXA5V.js → chunk-RGUZPMTF.js} +6 -6
- package/dist/{chunk-UK2KESBN.js → chunk-RHJKZOPQ.js} +3 -3
- package/dist/{chunk-HH3VVTEC.js → chunk-S5CJUIPY.js} +5 -5
- package/dist/{chunk-5S3YP5KH.js → chunk-SPMIZSKH.js} +14 -11
- package/dist/{chunk-EQ5JY3MJ.js → chunk-TEFFR6PB.js} +1 -1
- package/dist/{chunk-EDXTP2VR.js → chunk-UWJUARFA.js} +545 -186
- package/dist/{chunk-OAFBHCSM.js → chunk-UX6EDDGR.js} +1 -1
- package/dist/{chunk-ZYZBCLJR.js → chunk-XDQSHSL6.js} +6 -6
- package/dist/{chunk-2UG25RMK.js → chunk-YBTSZ2SW.js} +12 -0
- package/dist/{chunk-PNLGWAYE.js → chunk-YWBLQBSN.js} +548 -187
- package/dist/{chunk-Y3Y7SZBZ.js → chunk-ZHGMRBSC.js} +1 -1
- package/dist/{conductor-FPFY6Z5S.js → conductor-CNT3VL3S.js} +6 -6
- package/dist/{conductor-JA5W3L7C.js → conductor-XLXJU24U.js} +6 -6
- package/dist/{connected-teams-IHK2WXUI.js → connected-teams-H4LZOJDC.js} +2 -2
- package/dist/{connected-teams-IFW3NQ6G.js → connected-teams-XYRZMQO4.js} +2 -2
- package/dist/{coordinator-prompt-NUC2ZXMJ.js → coordinator-prompt-D5EE2YTV.js} +5 -5
- package/dist/{coordinator-prompt-M75TAN43.js → coordinator-prompt-TU24TQ4I.js} +5 -5
- package/dist/{global-workspace-5A4H34AF.js → global-workspace-743TPQNF.js} +5 -5
- package/dist/{global-workspace-SCLQUNUI.js → global-workspace-UPMXDQM7.js} +5 -5
- package/dist/index.cjs +1577 -471
- package/dist/index.d.cts +182 -70
- package/dist/index.d.ts +182 -70
- package/dist/index.js +480 -62
- package/dist/mcp/run-stdio.cjs +1159 -447
- package/dist/mcp/run-stdio.js +65 -17
- package/dist/{optional-cli-MRNQKVIR.js → optional-cli-2YDQQCCD.js} +4 -4
- package/dist/{optional-cli-2V3SLME5.js → optional-cli-USD6PROE.js} +3 -3
- package/dist/{orchestrator-NPFWU3XU.js → orchestrator-B4STIW4B.js} +12 -12
- package/dist/{orchestrator-7BDZ5GWD.js → orchestrator-VQ4HRYVB.js} +11 -11
- package/dist/{run-PCX5QWR4.js → run-7QMRYP3F.js} +3 -1
- package/dist/{run-TB7RBII2.js → run-UPNCCRCN.js} +3 -1
- package/dist/{thread-store-JWHVXLDX.js → thread-store-3F7GR64A.js} +1 -1
- package/dist/{thread-store-QHYN2ZMQ.js → thread-store-JRSL4HQY.js} +1 -1
- package/dist/{workspaces-KWMBOTQD.js → workspaces-47WQL5IG.js} +5 -5
- package/dist/{workspaces-5QA3YM4L.js → workspaces-75FD4QUR.js} +5 -5
- package/dist/{worktree-O6BANO2I.js → worktree-G3E6WP66.js} +10 -8
- package/dist/{worktree-7EATHVYQ.js → worktree-PDNPL5CQ.js} +10 -8
- package/dist/{worktree-exclude-RXZPTCNH.js → worktree-exclude-INRDQFWV.js} +2 -2
- package/dist/{worktree-exclude-5N2KTYIT.js → worktree-exclude-P5NJODWP.js} +2 -2
- package/package.json +1 -1
- /package/dist/{chunk-XMHMVEIY.js → chunk-B4CDSSRN.js} +0 -0
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-N2WW62LF.js";
|
|
6
6
|
import {
|
|
7
7
|
ensureWorktreeSideboardIgnored
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-4YXZOXMT.js";
|
|
9
9
|
import {
|
|
10
10
|
gh,
|
|
11
11
|
git,
|
|
@@ -14,8 +14,9 @@ import {
|
|
|
14
14
|
githubCredentialHelperGitConfig,
|
|
15
15
|
githubGhConfigEnv,
|
|
16
16
|
materializeGithubAgentAuth,
|
|
17
|
+
originCli,
|
|
17
18
|
resolveGhAuthToken
|
|
18
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-YBTSZ2SW.js";
|
|
19
20
|
import {
|
|
20
21
|
getGithubGitAuthMode,
|
|
21
22
|
getGithubPat
|
|
@@ -26,15 +27,13 @@ import {
|
|
|
26
27
|
normalizeTakenSlug,
|
|
27
28
|
normalizeWorktreePath,
|
|
28
29
|
takenSlugsFromThread
|
|
29
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-B4CDSSRN.js";
|
|
30
31
|
import {
|
|
31
32
|
worktreesRoot
|
|
32
33
|
} from "./chunk-WOU343SI.js";
|
|
33
34
|
|
|
34
35
|
// src/git/worktree.ts
|
|
35
|
-
import {
|
|
36
|
-
import { existsSync as existsSync2, realpathSync, unlinkSync, writeFileSync } from "fs";
|
|
37
|
-
import { tmpdir } from "os";
|
|
36
|
+
import { existsSync as existsSync2, realpathSync } from "fs";
|
|
38
37
|
import { join as join2 } from "path";
|
|
39
38
|
|
|
40
39
|
// src/git/gh-errors.ts
|
|
@@ -139,9 +138,9 @@ var HTTPS_REWRITE = [
|
|
|
139
138
|
{ key: "url.https://github.com/.insteadOf", value: "git@github.com:" },
|
|
140
139
|
{ key: "url.https://github.com/.insteadOf", value: "ssh://git@github.com/" }
|
|
141
140
|
];
|
|
142
|
-
function githubHttpsInsteadOfEntries(
|
|
141
|
+
function githubHttpsInsteadOfEntries(remoteUrl2) {
|
|
143
142
|
const entries = [...HTTPS_REWRITE];
|
|
144
|
-
const url =
|
|
143
|
+
const url = remoteUrl2?.trim() ?? "";
|
|
145
144
|
const scpAlias = url.match(/^(git@github\.com-[^:]+):/i);
|
|
146
145
|
if (scpAlias?.[1]) {
|
|
147
146
|
entries.push({
|
|
@@ -334,6 +333,53 @@ function formatGitAuthModeDirective(mode) {
|
|
|
334
333
|
}
|
|
335
334
|
}
|
|
336
335
|
|
|
336
|
+
// src/git/remote-forge.ts
|
|
337
|
+
function formatRemoteTag(remote) {
|
|
338
|
+
const name = remote.forge === "other" ? remote.host : remote.forge;
|
|
339
|
+
return `${name}:${remote.slug}`;
|
|
340
|
+
}
|
|
341
|
+
function forgeForHost(host) {
|
|
342
|
+
const h = host.toLowerCase();
|
|
343
|
+
if (h === "github.com" || h.startsWith("github.com-")) return "github";
|
|
344
|
+
if (h === "origin.cursor.com") return "cursor";
|
|
345
|
+
if (h === "entire.io" || h.endsWith(".entire.io") || h === "entire.com" || h.endsWith(".entire.com")) {
|
|
346
|
+
return "entire";
|
|
347
|
+
}
|
|
348
|
+
return "other";
|
|
349
|
+
}
|
|
350
|
+
function repoFromParts(host, owner, name) {
|
|
351
|
+
if (!host || !owner || !name || owner.includes("@")) return null;
|
|
352
|
+
const normalized = host.toLowerCase();
|
|
353
|
+
return {
|
|
354
|
+
forge: forgeForHost(normalized),
|
|
355
|
+
host: normalized,
|
|
356
|
+
slug: `${owner}/${name}`
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function parseRemoteRepo(url) {
|
|
360
|
+
const trimmed = url.trim().replace(/\.git$/i, "");
|
|
361
|
+
if (!trimmed) return null;
|
|
362
|
+
if (trimmed.toLowerCase().startsWith("entire://")) {
|
|
363
|
+
const parts = trimmed.slice("entire://".length).replace(/^\/+/, "").split("/").filter(Boolean);
|
|
364
|
+
if (parts.length === 2) return { forge: "entire", host: "entire.io", slug: `${parts[0]}/${parts[1]}` };
|
|
365
|
+
if (parts.length >= 3) {
|
|
366
|
+
return repoFromParts(parts[0], parts[1], parts[2]);
|
|
367
|
+
}
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
const ssh = trimmed.match(/^git@([^:]+):([^/]+)\/([^/]+)$/i);
|
|
371
|
+
if (ssh?.[1] && ssh[2] && ssh[3]) return repoFromParts(ssh[1], ssh[2], ssh[3]);
|
|
372
|
+
try {
|
|
373
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
374
|
+
const parsed = new URL(withScheme);
|
|
375
|
+
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
376
|
+
if (parts.length < 2) return null;
|
|
377
|
+
return repoFromParts(parsed.hostname, parts[parts.length - 2], parts[parts.length - 1]);
|
|
378
|
+
} catch {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
337
383
|
// src/git/list-prs.ts
|
|
338
384
|
var PR_LIST_JSON_FIELDS = [
|
|
339
385
|
"number",
|
|
@@ -695,30 +741,6 @@ async function listGithubViewerTeamSlugs(repoPath) {
|
|
|
695
741
|
return slugs;
|
|
696
742
|
}
|
|
697
743
|
|
|
698
|
-
// src/util/enqueue-by-key.ts
|
|
699
|
-
var tails = /* @__PURE__ */ new Map();
|
|
700
|
-
function enqueueByKey(key, fn) {
|
|
701
|
-
const prev = tails.get(key) ?? Promise.resolve();
|
|
702
|
-
const next = prev.then(fn, fn);
|
|
703
|
-
tails.set(
|
|
704
|
-
key,
|
|
705
|
-
next.then(
|
|
706
|
-
() => void 0,
|
|
707
|
-
() => void 0
|
|
708
|
-
)
|
|
709
|
-
);
|
|
710
|
-
return next;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// src/git/repo-git-lock.ts
|
|
714
|
-
function repoLockKey(repoPath) {
|
|
715
|
-
const trimmed = repoPath.replace(/\/+$/, "");
|
|
716
|
-
return `git-worktree:${trimmed || "/"}`;
|
|
717
|
-
}
|
|
718
|
-
function withRepoGitLock(repoPath, fn) {
|
|
719
|
-
return enqueueByKey(repoLockKey(repoPath), fn);
|
|
720
|
-
}
|
|
721
|
-
|
|
722
744
|
// src/git/pr-gates.ts
|
|
723
745
|
function prIsInMergeQueue(gate) {
|
|
724
746
|
if (gate.isInMergeQueue) return true;
|
|
@@ -846,6 +868,359 @@ function buildMergeGateChecks(gate, opts = {}) {
|
|
|
846
868
|
return rows;
|
|
847
869
|
}
|
|
848
870
|
|
|
871
|
+
// src/git/cursor-pr.ts
|
|
872
|
+
var PLAIN_LIST_FIELDS = "number,title,headRefName,url,author,state,isDraft,reviewDecision";
|
|
873
|
+
var SEARCH_LIST_FIELDS = `${PLAIN_LIST_FIELDS},labels,reviewRequests`;
|
|
874
|
+
var VIEW_FIELDS = "number,title,body,url,state,isDraft,reviewDecision,author,createdAt,baseRefName,headRefName,additions,deletions,changedFiles,commits,comments,reviews,labels,mergeability";
|
|
875
|
+
function repoArgs(slug) {
|
|
876
|
+
return ["-R", slug];
|
|
877
|
+
}
|
|
878
|
+
function cursorHeadBranch(head, slug) {
|
|
879
|
+
const branch = head.trim().replace(/^refs\/heads\//, "");
|
|
880
|
+
const owner = slug.split("/")[0];
|
|
881
|
+
if (owner && branch.startsWith(`${owner}:`)) return branch.slice(owner.length + 1);
|
|
882
|
+
return branch;
|
|
883
|
+
}
|
|
884
|
+
function cursorMergeFields(value) {
|
|
885
|
+
if (typeof value === "string" && value.trim()) {
|
|
886
|
+
const upper = value.trim().toUpperCase();
|
|
887
|
+
return { mergeable: upper, mergeStateStatus: upper };
|
|
888
|
+
}
|
|
889
|
+
if (value && typeof value === "object") {
|
|
890
|
+
const row = value;
|
|
891
|
+
const conflictState = String(row.mergeConflictState ?? "").trim().toUpperCase();
|
|
892
|
+
if ("hasMergeConflicts" in row || conflictState) {
|
|
893
|
+
if (conflictState.includes("PENDING")) {
|
|
894
|
+
return { mergeable: "UNKNOWN", mergeStateStatus: "UNKNOWN" };
|
|
895
|
+
}
|
|
896
|
+
const conflicting = row.hasMergeConflicts === true || conflictState.includes("CONFLICT") && !conflictState.includes("CLEAN");
|
|
897
|
+
if (conflicting) return { mergeable: "CONFLICTING", mergeStateStatus: "DIRTY" };
|
|
898
|
+
if (row.hasMergeConflicts === false || conflictState.includes("CLEAN")) {
|
|
899
|
+
return { mergeable: "MERGEABLE", mergeStateStatus: "CLEAN" };
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
const verdict = String(row.verdict ?? row.verdictKind ?? "").trim().toUpperCase();
|
|
903
|
+
if (verdict === "BLOCKED") return { mergeable: null, mergeStateStatus: "BLOCKED" };
|
|
904
|
+
if (verdict === "MERGEABLE") return { mergeable: "MERGEABLE", mergeStateStatus: "CLEAN" };
|
|
905
|
+
const status = String(row.status ?? row.state ?? row.mergeable ?? "").trim().toUpperCase();
|
|
906
|
+
return { mergeable: status || null, mergeStateStatus: status || null };
|
|
907
|
+
}
|
|
908
|
+
return { mergeable: null, mergeStateStatus: null };
|
|
909
|
+
}
|
|
910
|
+
function cursorReviewDecision(value) {
|
|
911
|
+
const raw = typeof value === "string" ? value.trim().toUpperCase().replace(/[\s-]+/g, "_") : "";
|
|
912
|
+
if (!raw) return null;
|
|
913
|
+
if (raw === "REQUIRED" || raw === "REVIEW_REQUIRED") return "REVIEW_REQUIRED";
|
|
914
|
+
if (raw === "CHANGES_REQUESTED") return "CHANGES_REQUESTED";
|
|
915
|
+
if (raw === "APPROVED") return "APPROVED";
|
|
916
|
+
return raw;
|
|
917
|
+
}
|
|
918
|
+
function cursorViewToPrMeta(view) {
|
|
919
|
+
const merge = cursorMergeFields(view.mergeability ?? view.mergeable);
|
|
920
|
+
const author = view.author ?? {};
|
|
921
|
+
return {
|
|
922
|
+
number: Number(view.number),
|
|
923
|
+
title: String(view.title ?? ""),
|
|
924
|
+
url: String(view.url ?? ""),
|
|
925
|
+
state: String(view.state ?? ""),
|
|
926
|
+
isDraft: Boolean(view.isDraft),
|
|
927
|
+
reviewDecision: cursorReviewDecision(view.reviewDecision),
|
|
928
|
+
baseRefName: String(view.baseRefName ?? ""),
|
|
929
|
+
headRefName: String(view.headRefName ?? ""),
|
|
930
|
+
isInMergeQueue: false,
|
|
931
|
+
mergeable: merge.mergeable,
|
|
932
|
+
mergeStateStatus: merge.mergeStateStatus,
|
|
933
|
+
authorLogin: author.login?.trim() || null,
|
|
934
|
+
reviewerLogins: [],
|
|
935
|
+
labels: labelNames(view.labels)
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
function loginOf(author) {
|
|
939
|
+
if (!author || typeof author !== "object") return "";
|
|
940
|
+
const row = author;
|
|
941
|
+
for (const key of ["login", "handle", "email", "displayName", "name", "id"]) {
|
|
942
|
+
const value = row[key];
|
|
943
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
944
|
+
}
|
|
945
|
+
return "";
|
|
946
|
+
}
|
|
947
|
+
function upperState(value, isDraft) {
|
|
948
|
+
const raw = typeof value === "string" ? value.trim().toUpperCase() : "";
|
|
949
|
+
if (raw === "DRAFT") return "OPEN";
|
|
950
|
+
if (raw === "OPEN" || raw === "CLOSED" || raw === "MERGED") return raw;
|
|
951
|
+
return isDraft ? "OPEN" : raw;
|
|
952
|
+
}
|
|
953
|
+
function normalizeOriginPr(raw) {
|
|
954
|
+
const isDraft = raw.isDraft === true || String(raw.status ?? raw.state ?? "").toLowerCase() === "draft";
|
|
955
|
+
const login = loginOf(raw.author);
|
|
956
|
+
const author = raw.author && typeof raw.author === "object" ? { ...raw.author, login: login || "unknown" } : login ? { login } : { login: "unknown" };
|
|
957
|
+
return {
|
|
958
|
+
...raw,
|
|
959
|
+
isDraft,
|
|
960
|
+
state: upperState(raw.state ?? raw.status, isDraft),
|
|
961
|
+
author,
|
|
962
|
+
body: raw.body ?? raw.description ?? "",
|
|
963
|
+
isCrossRepository: false,
|
|
964
|
+
reviewDecision: cursorReviewDecision(raw.reviewDecision),
|
|
965
|
+
reviewRequests: originReviewRequests(raw.reviewRequests)
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
function originReviewRequests(raw) {
|
|
969
|
+
if (!Array.isArray(raw)) return raw;
|
|
970
|
+
return raw.map(originReviewer).filter((row) => row != null);
|
|
971
|
+
}
|
|
972
|
+
function originReviewer(raw) {
|
|
973
|
+
if (typeof raw === "string" && raw.trim()) return { login: raw.trim() };
|
|
974
|
+
if (!raw || typeof raw !== "object") return null;
|
|
975
|
+
const row = raw;
|
|
976
|
+
if (typeof row.login === "string" && row.login.trim()) return row;
|
|
977
|
+
const kind = String(row.kind ?? row.__typename ?? "").toLowerCase();
|
|
978
|
+
const slug = typeof row.slug === "string" ? row.slug.trim() : "";
|
|
979
|
+
const name = typeof row.name === "string" ? row.name.trim() : "";
|
|
980
|
+
const id = typeof row.id === "string" ? row.id.trim() : "";
|
|
981
|
+
if (kind.includes("team") || kind.includes("group") || slug.includes("/") && !row.login) {
|
|
982
|
+
return { __typename: "Team", slug: slug || name, name };
|
|
983
|
+
}
|
|
984
|
+
if (kind.includes("service") || kind.includes("bot")) {
|
|
985
|
+
const login2 = name || id || "cursor-bot";
|
|
986
|
+
return { __typename: "Bot", login: login2.toLowerCase().endsWith("[bot]") ? login2 : `${login2}[bot]` };
|
|
987
|
+
}
|
|
988
|
+
const login = name || id;
|
|
989
|
+
return login ? { login } : null;
|
|
990
|
+
}
|
|
991
|
+
function originChecksToRuns(stdout) {
|
|
992
|
+
let parsed;
|
|
993
|
+
try {
|
|
994
|
+
parsed = JSON.parse(stdout);
|
|
995
|
+
} catch {
|
|
996
|
+
return [];
|
|
997
|
+
}
|
|
998
|
+
const rows = Array.isArray(parsed) ? parsed : [];
|
|
999
|
+
return rows.map((row) => {
|
|
1000
|
+
const item = row ?? {};
|
|
1001
|
+
const conclusion = typeof item.conclusion === "string" ? item.conclusion.toLowerCase() : "";
|
|
1002
|
+
const status = typeof item.status === "string" ? item.status.toLowerCase() : "";
|
|
1003
|
+
const bucket = conclusion === "success" || conclusion === "neutral" ? "pass" : conclusion === "failure" || conclusion === "timed_out" || conclusion === "action_required" ? "fail" : conclusion === "cancelled" ? "cancel" : conclusion === "skipped" ? "skipping" : "pending";
|
|
1004
|
+
const state = conclusion || status || "pending";
|
|
1005
|
+
const link = typeof item.link === "string" ? item.link : typeof item.detailsUrl === "string" ? item.detailsUrl : null;
|
|
1006
|
+
const description = typeof item.summary === "string" ? item.summary : typeof item.description === "string" ? item.description : null;
|
|
1007
|
+
return {
|
|
1008
|
+
name: String(item.name ?? "check"),
|
|
1009
|
+
state,
|
|
1010
|
+
bucket,
|
|
1011
|
+
startedAt: typeof item.startedAt === "string" ? item.startedAt : null,
|
|
1012
|
+
completedAt: typeof item.completedAt === "string" ? item.completedAt : null,
|
|
1013
|
+
link,
|
|
1014
|
+
description,
|
|
1015
|
+
workflow: typeof item.group === "string" ? item.group : null,
|
|
1016
|
+
kind: "ci"
|
|
1017
|
+
};
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
function listedHasLabels(pr, labels) {
|
|
1021
|
+
if (labels.length === 0) return true;
|
|
1022
|
+
const have = new Set((pr.labels ?? []).map((name) => name.toLowerCase()));
|
|
1023
|
+
return labels.every((label) => have.has(label.toLowerCase()));
|
|
1024
|
+
}
|
|
1025
|
+
async function listCursorPrs(repoPath, slug, opts, head) {
|
|
1026
|
+
const resolved = resolveListPrsOptions(opts);
|
|
1027
|
+
const limit = Math.max(1, Math.min(250, resolved.limit ?? 200));
|
|
1028
|
+
const fetchLimit = prListFetchLimit(limit, resolved.reviewer);
|
|
1029
|
+
const useSearch = resolved.labels.length > 0 || Boolean(resolved.reviewer) || Boolean(resolved.query);
|
|
1030
|
+
const state = resolved.draft === true ? "draft" : resolved.state;
|
|
1031
|
+
const args = [
|
|
1032
|
+
"pr",
|
|
1033
|
+
"list",
|
|
1034
|
+
...repoArgs(slug),
|
|
1035
|
+
"--state",
|
|
1036
|
+
state,
|
|
1037
|
+
"--limit",
|
|
1038
|
+
String(fetchLimit),
|
|
1039
|
+
"--json",
|
|
1040
|
+
useSearch ? SEARCH_LIST_FIELDS : PLAIN_LIST_FIELDS
|
|
1041
|
+
];
|
|
1042
|
+
if (head?.trim()) args.push("--head", head.trim());
|
|
1043
|
+
if (useSearch) {
|
|
1044
|
+
for (const label of resolved.labels) args.push("--label", label);
|
|
1045
|
+
if (resolved.query) args.push("--search", resolved.query);
|
|
1046
|
+
else if (resolved.labels.length === 0) {
|
|
1047
|
+
const token = state === "all" ? "is:open OR is:closed OR is:merged" : `is:${state}`;
|
|
1048
|
+
args.push("--search", token);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
const { stdout, exitCode } = await originCli(args, repoPath, { reject: false, timeoutMs: 2e4 });
|
|
1052
|
+
if (exitCode !== 0 || !stdout.trim()) return [];
|
|
1053
|
+
let parsed;
|
|
1054
|
+
try {
|
|
1055
|
+
parsed = JSON.parse(stdout);
|
|
1056
|
+
} catch {
|
|
1057
|
+
return [];
|
|
1058
|
+
}
|
|
1059
|
+
if (!Array.isArray(parsed)) return [];
|
|
1060
|
+
const normalized = parsed.filter((row) => row && typeof row === "object").map((row) => normalizeOriginPr(row));
|
|
1061
|
+
const listed = applyPrListResponse(JSON.stringify(normalized), { ...opts, limit: void 0 });
|
|
1062
|
+
return listed.filter((pr) => listedHasLabels(pr, resolved.labels)).slice(0, limit);
|
|
1063
|
+
}
|
|
1064
|
+
async function viewCursorPr(repoPath, slug, selector, fields) {
|
|
1065
|
+
const { stdout, exitCode } = await originCli(
|
|
1066
|
+
["pr", "view", selector, ...repoArgs(slug), "--json", fields],
|
|
1067
|
+
repoPath,
|
|
1068
|
+
{ reject: false, timeoutMs: 2e4 }
|
|
1069
|
+
);
|
|
1070
|
+
if (exitCode !== 0 || !stdout.trim()) return null;
|
|
1071
|
+
try {
|
|
1072
|
+
const parsed = JSON.parse(stdout);
|
|
1073
|
+
return normalizeOriginPr(parsed);
|
|
1074
|
+
} catch {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
var CURSOR_PR_VIEW_FIELDS = VIEW_FIELDS;
|
|
1079
|
+
async function createOrUpdateCursorPr(repoPath, slug, opts) {
|
|
1080
|
+
const branch = cursorHeadBranch(opts.head, slug);
|
|
1081
|
+
const existing = await originCli(
|
|
1082
|
+
["pr", "view", branch, ...repoArgs(slug), "--json", "url"],
|
|
1083
|
+
repoPath,
|
|
1084
|
+
{ reject: false, timeoutMs: 2e4 }
|
|
1085
|
+
);
|
|
1086
|
+
if (existing.exitCode === 0 && existing.stdout.trim()) {
|
|
1087
|
+
let url2 = "";
|
|
1088
|
+
try {
|
|
1089
|
+
url2 = String(JSON.parse(existing.stdout).url ?? "");
|
|
1090
|
+
} catch {
|
|
1091
|
+
url2 = "";
|
|
1092
|
+
}
|
|
1093
|
+
if (url2) {
|
|
1094
|
+
await originCli(
|
|
1095
|
+
["pr", "edit", branch, ...repoArgs(slug), "--title", opts.title, "-F", opts.bodyFile],
|
|
1096
|
+
repoPath,
|
|
1097
|
+
{ reject: false, timeoutMs: 2e4 }
|
|
1098
|
+
);
|
|
1099
|
+
return url2;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
const args = [
|
|
1103
|
+
"pr",
|
|
1104
|
+
"create",
|
|
1105
|
+
...repoArgs(slug),
|
|
1106
|
+
"--title",
|
|
1107
|
+
opts.title,
|
|
1108
|
+
"-F",
|
|
1109
|
+
opts.bodyFile,
|
|
1110
|
+
"--base",
|
|
1111
|
+
opts.base,
|
|
1112
|
+
"--head",
|
|
1113
|
+
branch,
|
|
1114
|
+
"--push"
|
|
1115
|
+
];
|
|
1116
|
+
if (opts.draft) args.push("--draft");
|
|
1117
|
+
const created = await originCli(args, repoPath, { reject: false, timeoutMs: 6e4 });
|
|
1118
|
+
if (created.exitCode !== 0) {
|
|
1119
|
+
throw new Error(created.stderr.trim() || created.stdout.trim() || "origin pr create failed");
|
|
1120
|
+
}
|
|
1121
|
+
const printed = created.stdout.trim().split("\n").find((line) => line.startsWith("http"));
|
|
1122
|
+
if (printed) return printed;
|
|
1123
|
+
const viewed = await viewCursorPr(repoPath, slug, branch, "url");
|
|
1124
|
+
const url = typeof viewed?.url === "string" ? viewed.url.trim() : "";
|
|
1125
|
+
return url || created.stdout.trim();
|
|
1126
|
+
}
|
|
1127
|
+
async function readyCursorPr(repoPath, slug, selector) {
|
|
1128
|
+
const ready = await originCli(["pr", "ready", selector, ...repoArgs(slug)], repoPath, {
|
|
1129
|
+
reject: false,
|
|
1130
|
+
timeoutMs: 2e4
|
|
1131
|
+
});
|
|
1132
|
+
if (ready.exitCode !== 0) {
|
|
1133
|
+
throw new Error(ready.stderr.trim() || ready.stdout.trim() || "Could not mark the Cursor pull request ready");
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
async function mergeCursorPr(repoPath, slug, selector, method) {
|
|
1137
|
+
const args = ["pr", "merge", selector, ...repoArgs(slug)];
|
|
1138
|
+
if (method === "merge") args.push("--merge");
|
|
1139
|
+
else if (method === "squash") args.push("--squash");
|
|
1140
|
+
const merged = await originCli(args, repoPath, {
|
|
1141
|
+
reject: false,
|
|
1142
|
+
timeoutMs: 6e4
|
|
1143
|
+
});
|
|
1144
|
+
if (merged.exitCode !== 0) {
|
|
1145
|
+
throw new Error(merged.stderr.trim() || merged.stdout.trim() || "origin pr merge failed");
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
async function fetchCursorPrHead(repoPath, slug, number, localBranch) {
|
|
1149
|
+
const view = await viewCursorPr(repoPath, slug, String(number), "headRefName");
|
|
1150
|
+
const head = typeof view?.headRefName === "string" ? view.headRefName.trim() : "";
|
|
1151
|
+
if (!head) {
|
|
1152
|
+
throw new Error(`Could not resolve the head branch for Cursor pull request #${number}`);
|
|
1153
|
+
}
|
|
1154
|
+
const result = await git(["fetch", "origin", `${head}:${localBranch}`], repoPath, { reject: false });
|
|
1155
|
+
if (result.exitCode !== 0) {
|
|
1156
|
+
throw new Error((result.stderr || result.stdout).trim() || `git fetch origin ${head} failed`);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
async function cursorPrChecks(repoPath, slug, selector) {
|
|
1160
|
+
const { stdout, exitCode, stderr } = await originCli(
|
|
1161
|
+
["pr", "checks", selector, ...repoArgs(slug), "--json", "name,status,conclusion,detailsUrl,startedAt,completedAt,summary,group,link"],
|
|
1162
|
+
repoPath,
|
|
1163
|
+
{ reject: false, timeoutMs: 2e4 }
|
|
1164
|
+
);
|
|
1165
|
+
const err = stderr.trim();
|
|
1166
|
+
let ci;
|
|
1167
|
+
if (!stdout.trim()) {
|
|
1168
|
+
if (/no pull request|not found|no change/i.test(err)) return null;
|
|
1169
|
+
if (exitCode !== 0 && err) throw new Error(err);
|
|
1170
|
+
ci = [];
|
|
1171
|
+
} else {
|
|
1172
|
+
ci = originChecksToRuns(stdout);
|
|
1173
|
+
}
|
|
1174
|
+
const view = await viewCursorPr(
|
|
1175
|
+
repoPath,
|
|
1176
|
+
slug,
|
|
1177
|
+
selector,
|
|
1178
|
+
"url,baseRefName,reviewDecision,mergeability"
|
|
1179
|
+
);
|
|
1180
|
+
if (!view) return ci;
|
|
1181
|
+
return cursorChecksWithGate(ci, cursorViewToPrMeta(view));
|
|
1182
|
+
}
|
|
1183
|
+
function cursorChecksWithGate(ci, meta) {
|
|
1184
|
+
const gate = {
|
|
1185
|
+
mergeable: meta.mergeable,
|
|
1186
|
+
mergeStateStatus: meta.mergeStateStatus,
|
|
1187
|
+
reviewDecision: meta.reviewDecision,
|
|
1188
|
+
baseRefName: meta.baseRefName,
|
|
1189
|
+
url: meta.url,
|
|
1190
|
+
isInMergeQueue: meta.isInMergeQueue
|
|
1191
|
+
};
|
|
1192
|
+
const review = (gate.reviewDecision ?? "").toUpperCase();
|
|
1193
|
+
const hasReviewRow = review === "CHANGES_REQUESTED" || review === "REVIEW_REQUIRED";
|
|
1194
|
+
const gateRows = buildMergeGateChecks(gate, {
|
|
1195
|
+
suppressGenericBlocked: ci.some((check) => check.bucket === "fail") || hasReviewRow
|
|
1196
|
+
});
|
|
1197
|
+
return [...gateRows, ...ci];
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// src/util/enqueue-by-key.ts
|
|
1201
|
+
var tails = /* @__PURE__ */ new Map();
|
|
1202
|
+
function enqueueByKey(key, fn) {
|
|
1203
|
+
const prev = tails.get(key) ?? Promise.resolve();
|
|
1204
|
+
const next = prev.then(fn, fn);
|
|
1205
|
+
tails.set(
|
|
1206
|
+
key,
|
|
1207
|
+
next.then(
|
|
1208
|
+
() => void 0,
|
|
1209
|
+
() => void 0
|
|
1210
|
+
)
|
|
1211
|
+
);
|
|
1212
|
+
return next;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// src/git/repo-git-lock.ts
|
|
1216
|
+
function repoLockKey(repoPath) {
|
|
1217
|
+
const trimmed = repoPath.replace(/\/+$/, "");
|
|
1218
|
+
return `git-worktree:${trimmed || "/"}`;
|
|
1219
|
+
}
|
|
1220
|
+
function withRepoGitLock(repoPath, fn) {
|
|
1221
|
+
return enqueueByKey(repoLockKey(repoPath), fn);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
849
1224
|
// src/git/stack.ts
|
|
850
1225
|
var cachedStatus = null;
|
|
851
1226
|
var STATUS_TTL_MS = 6e4;
|
|
@@ -1083,31 +1458,6 @@ function allocateTeamSlug(repoPath) {
|
|
|
1083
1458
|
}
|
|
1084
1459
|
|
|
1085
1460
|
// src/git/worktree.ts
|
|
1086
|
-
async function lookupGithubGraphqlReset(cwd) {
|
|
1087
|
-
const result = await gh(["api", "rate_limit"], cwd, { reject: false });
|
|
1088
|
-
if (result.exitCode !== 0 || !result.stdout.trim()) return void 0;
|
|
1089
|
-
try {
|
|
1090
|
-
const data = JSON.parse(result.stdout);
|
|
1091
|
-
const reset = data.resources?.graphql?.reset;
|
|
1092
|
-
return typeof reset === "number" ? reset : void 0;
|
|
1093
|
-
} catch {
|
|
1094
|
-
return void 0;
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
async function formatPrCreateFailure(raw, cwd, ctx) {
|
|
1098
|
-
if (!isGhRateLimitError(raw)) {
|
|
1099
|
-
return formatGhLandError(raw, {
|
|
1100
|
-
targetedRepo: ctx?.slug,
|
|
1101
|
-
headRef: ctx?.head
|
|
1102
|
-
});
|
|
1103
|
-
}
|
|
1104
|
-
const resetAt = await lookupGithubGraphqlReset(cwd);
|
|
1105
|
-
return formatGhLandError(raw, {
|
|
1106
|
-
resetAt,
|
|
1107
|
-
targetedRepo: ctx?.slug,
|
|
1108
|
-
headRef: ctx?.head
|
|
1109
|
-
});
|
|
1110
|
-
}
|
|
1111
1461
|
function slugify(input) {
|
|
1112
1462
|
return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
|
|
1113
1463
|
}
|
|
@@ -1130,25 +1480,39 @@ function canonicalizeRepoPath(path) {
|
|
|
1130
1480
|
}
|
|
1131
1481
|
}
|
|
1132
1482
|
function parseGithubSlugFromRemoteUrl(url) {
|
|
1133
|
-
const
|
|
1134
|
-
|
|
1135
|
-
const github = trimmed.match(/github\.com[:/]([^/]+)\/([^/]+)$/i);
|
|
1136
|
-
if (github?.[1] && github[2]) return `${github[1]}/${github[2]}`;
|
|
1137
|
-
const sshAlias = trimmed.match(/^git@[^:]+:([^/]+)\/([^/]+)$/i);
|
|
1138
|
-
if (sshAlias?.[1] && sshAlias[2]) return `${sshAlias[1]}/${sshAlias[2]}`;
|
|
1139
|
-
return null;
|
|
1483
|
+
const parsed = parseRemoteRepo(url);
|
|
1484
|
+
return parsed?.forge === "github" ? parsed.slug : null;
|
|
1140
1485
|
}
|
|
1141
|
-
async function
|
|
1486
|
+
async function remoteUrl(repoPath, remote) {
|
|
1142
1487
|
const result = await git(["remote", "get-url", remote], repoPath, {
|
|
1143
1488
|
reject: false,
|
|
1144
1489
|
timeoutMs: 5e3
|
|
1145
1490
|
});
|
|
1146
1491
|
if (result.exitCode !== 0 || !result.stdout.trim()) return null;
|
|
1147
|
-
return
|
|
1492
|
+
return result.stdout.trim();
|
|
1493
|
+
}
|
|
1494
|
+
async function slugFromGitRemote(repoPath, remote) {
|
|
1495
|
+
const url = await remoteUrl(repoPath, remote);
|
|
1496
|
+
if (!url) return null;
|
|
1497
|
+
return parseGithubSlugFromRemoteUrl(url);
|
|
1498
|
+
}
|
|
1499
|
+
async function resolveOriginRemote(repoPath) {
|
|
1500
|
+
const url = await remoteUrl(repoPath, "origin");
|
|
1501
|
+
if (!url) return null;
|
|
1502
|
+
return parseRemoteRepo(url);
|
|
1503
|
+
}
|
|
1504
|
+
async function resolveProjectRemoteTag(repoPath) {
|
|
1505
|
+
const origin = await resolveOriginRemote(repoPath);
|
|
1506
|
+
if (origin) return formatRemoteTag(origin);
|
|
1507
|
+
const slug = await resolveGithubRepoSlug(repoPath);
|
|
1508
|
+
return slug ? `github:${slug}` : null;
|
|
1148
1509
|
}
|
|
1149
1510
|
async function resolveGithubRepoSlug(repoPath) {
|
|
1150
|
-
const
|
|
1151
|
-
if (
|
|
1511
|
+
const originUrl = await remoteUrl(repoPath, "origin");
|
|
1512
|
+
if (originUrl) {
|
|
1513
|
+
const parsed = parseRemoteRepo(originUrl);
|
|
1514
|
+
return parsed?.forge === "github" ? parsed.slug : null;
|
|
1515
|
+
}
|
|
1152
1516
|
const hasAlt = Boolean(await slugFromGitRemote(repoPath, "upstream")) || Boolean(await slugFromGitRemote(repoPath, "github"));
|
|
1153
1517
|
if (!hasAlt) {
|
|
1154
1518
|
const viaGh = await gh(
|
|
@@ -1194,6 +1558,8 @@ ${view.stderr}`;
|
|
|
1194
1558
|
});
|
|
1195
1559
|
}
|
|
1196
1560
|
async function originGhRepoEnv(cwd, opts) {
|
|
1561
|
+
const origin = await resolveOriginRemote(cwd);
|
|
1562
|
+
if (origin?.forge === "cursor") return { ORIGIN_REPO: origin.slug };
|
|
1197
1563
|
await ensureGhPreferOrigin(cwd);
|
|
1198
1564
|
const slug = await resolveGithubRepoSlug(cwd);
|
|
1199
1565
|
const mode = opts?.mode ?? getGithubGitAuthMode();
|
|
@@ -1308,7 +1674,16 @@ async function listBranches(repoPath, opts) {
|
|
|
1308
1674
|
}
|
|
1309
1675
|
return sorted.filter((b) => keep.has(b.name));
|
|
1310
1676
|
}
|
|
1677
|
+
function entireUsesTrails() {
|
|
1678
|
+
return new Error(
|
|
1679
|
+
"This checkout is on Entire. Trails replace pull requests \u2014 use `entire trail` (Settings \u2192 Git \u2192 Entire), not `gh`."
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1311
1682
|
async function listPrs(repoPath, opts) {
|
|
1683
|
+
const origin = await resolveOriginRemote(repoPath);
|
|
1684
|
+
if (origin?.forge === "entire") return [];
|
|
1685
|
+
const cursorSlug = origin?.forge === "cursor" ? origin.slug : null;
|
|
1686
|
+
if (cursorSlug) return listCursorPrs(repoPath, cursorSlug, opts);
|
|
1312
1687
|
const slug = await resolveGithubRepoSlug(repoPath);
|
|
1313
1688
|
const resolved = resolveListPrsOptions(opts);
|
|
1314
1689
|
const limit = resolved.limit ?? 200;
|
|
@@ -1346,6 +1721,18 @@ function prInfoFromGhView(view) {
|
|
|
1346
1721
|
};
|
|
1347
1722
|
}
|
|
1348
1723
|
async function getPr(repoPath, number) {
|
|
1724
|
+
const origin = await resolveOriginRemote(repoPath);
|
|
1725
|
+
if (origin?.forge === "entire") return null;
|
|
1726
|
+
const cursorSlug = origin?.forge === "cursor" ? origin.slug : null;
|
|
1727
|
+
if (cursorSlug) {
|
|
1728
|
+
const view = await viewCursorPr(
|
|
1729
|
+
repoPath,
|
|
1730
|
+
cursorSlug,
|
|
1731
|
+
String(number),
|
|
1732
|
+
"number,title,headRefName,url,author,isDraft,state"
|
|
1733
|
+
);
|
|
1734
|
+
return view ? prInfoFromGhView(view) : null;
|
|
1735
|
+
}
|
|
1349
1736
|
const slug = await resolveGithubRepoSlug(repoPath);
|
|
1350
1737
|
const args = [
|
|
1351
1738
|
"pr",
|
|
@@ -1407,6 +1794,13 @@ function connectedPrSelectors(thread, currentHeadPrUrl) {
|
|
|
1407
1794
|
async function getPrForHeadBranch(repoPath, branch) {
|
|
1408
1795
|
const head = normalizeGitBranchName(branch);
|
|
1409
1796
|
if (!head || isDefaultishSourceRef(head)) return null;
|
|
1797
|
+
const origin = await resolveOriginRemote(repoPath);
|
|
1798
|
+
if (origin?.forge === "entire") return null;
|
|
1799
|
+
const cursorSlug = origin?.forge === "cursor" ? origin.slug : null;
|
|
1800
|
+
if (cursorSlug) {
|
|
1801
|
+
const rows = await listCursorPrs(repoPath, cursorSlug, { state: "open", limit: 5 }, head);
|
|
1802
|
+
return rows[0] ?? null;
|
|
1803
|
+
}
|
|
1410
1804
|
const slug = await resolveGithubRepoSlug(repoPath);
|
|
1411
1805
|
const listHead = slug ? ghHeadRef(slug, head) : head;
|
|
1412
1806
|
const listArgs = [
|
|
@@ -1571,6 +1965,10 @@ async function probeLocalMergeGate(cwd, gate) {
|
|
|
1571
1965
|
return { gate, files: [] };
|
|
1572
1966
|
}
|
|
1573
1967
|
async function getPrChecks(cwd, selector) {
|
|
1968
|
+
const origin = await resolveOriginRemote(cwd);
|
|
1969
|
+
if (origin?.forge === "entire") return null;
|
|
1970
|
+
const cursorSlug = origin?.forge === "cursor" ? origin.slug : null;
|
|
1971
|
+
if (cursorSlug) return cursorPrChecks(cwd, cursorSlug, selector);
|
|
1574
1972
|
const slug = await resolveGithubRepoSlug(cwd);
|
|
1575
1973
|
const args = [
|
|
1576
1974
|
"pr",
|
|
@@ -1626,6 +2024,18 @@ async function getPrChecks(cwd, selector) {
|
|
|
1626
2024
|
return [...gateRows, ...ciChecks];
|
|
1627
2025
|
}
|
|
1628
2026
|
async function getPrMeta(cwd, selector) {
|
|
2027
|
+
const origin = await resolveOriginRemote(cwd);
|
|
2028
|
+
if (origin?.forge === "entire") return null;
|
|
2029
|
+
const cursorSlug = origin?.forge === "cursor" ? origin.slug : null;
|
|
2030
|
+
if (cursorSlug) {
|
|
2031
|
+
const view = await viewCursorPr(
|
|
2032
|
+
cwd,
|
|
2033
|
+
cursorSlug,
|
|
2034
|
+
selector,
|
|
2035
|
+
"number,title,url,state,isDraft,reviewDecision,baseRefName,headRefName,author,labels,mergeability"
|
|
2036
|
+
);
|
|
2037
|
+
return view ? cursorViewToPrMeta(view) : null;
|
|
2038
|
+
}
|
|
1629
2039
|
const slug = await resolveGithubRepoSlug(cwd);
|
|
1630
2040
|
const viewArgs = [
|
|
1631
2041
|
"pr",
|
|
@@ -1776,6 +2186,13 @@ function parsePrDetailsView(view) {
|
|
|
1776
2186
|
};
|
|
1777
2187
|
}
|
|
1778
2188
|
async function getPrDetails(cwd, selector) {
|
|
2189
|
+
const origin = await resolveOriginRemote(cwd);
|
|
2190
|
+
if (origin?.forge === "entire") return null;
|
|
2191
|
+
const cursorSlug = origin?.forge === "cursor" ? origin.slug : null;
|
|
2192
|
+
if (cursorSlug) {
|
|
2193
|
+
const view2 = await viewCursorPr(cwd, cursorSlug, selector, CURSOR_PR_VIEW_FIELDS);
|
|
2194
|
+
return view2 ? parsePrDetailsView(view2) : null;
|
|
2195
|
+
}
|
|
1779
2196
|
const slug = await resolveGithubRepoSlug(cwd);
|
|
1780
2197
|
const viewArgs = ["pr", "view", selector, "--json", PR_DETAILS_JSON_FIELDS];
|
|
1781
2198
|
if (slug) viewArgs.push("--repo", slug);
|
|
@@ -1795,6 +2212,13 @@ async function getPrDetails(cwd, selector) {
|
|
|
1795
2212
|
return parsePrDetailsView(view);
|
|
1796
2213
|
}
|
|
1797
2214
|
async function fetchPrHead(repoPath, number, localBranch) {
|
|
2215
|
+
const origin = await resolveOriginRemote(repoPath);
|
|
2216
|
+
if (origin?.forge === "entire") throw entireUsesTrails();
|
|
2217
|
+
const cursorSlug = origin?.forge === "cursor" ? origin.slug : null;
|
|
2218
|
+
if (cursorSlug) {
|
|
2219
|
+
await fetchCursorPrHead(repoPath, cursorSlug, number, localBranch);
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
1798
2222
|
const slug = await resolveGithubRepoSlug(repoPath);
|
|
1799
2223
|
const refspec = `+pull/${number}/head:${localBranch}`;
|
|
1800
2224
|
const errors = [];
|
|
@@ -1937,10 +2361,10 @@ async function originRemoteUrl(cwd) {
|
|
|
1937
2361
|
const result = await git(["remote", "get-url", "origin"], cwd, { reject: false });
|
|
1938
2362
|
return result.exitCode === 0 ? result.stdout.trim() : "";
|
|
1939
2363
|
}
|
|
1940
|
-
function githubHttpsRetryEnv(
|
|
2364
|
+
function githubHttpsRetryEnv(remoteUrl2) {
|
|
1941
2365
|
return {
|
|
1942
2366
|
GIT_TERMINAL_PROMPT: "0",
|
|
1943
|
-
...githubAgentGitEnv(void 0, { remoteUrl })
|
|
2367
|
+
...githubAgentGitEnv(void 0, { remoteUrl: remoteUrl2 })
|
|
1944
2368
|
};
|
|
1945
2369
|
}
|
|
1946
2370
|
async function fetchOriginWithAuth(repoPath, args, timeoutMs) {
|
|
@@ -1954,11 +2378,11 @@ async function fetchOriginWithAuth(repoPath, args, timeoutMs) {
|
|
|
1954
2378
|
}
|
|
1955
2379
|
const token = await resolveGithubAgentToken(mode, repoPath);
|
|
1956
2380
|
if (!token) return false;
|
|
1957
|
-
const
|
|
2381
|
+
const remoteUrl2 = await originRemoteUrl(repoPath);
|
|
1958
2382
|
const tryHttps = async (header) => git(args, repoPath, {
|
|
1959
2383
|
reject: false,
|
|
1960
2384
|
timeoutMs,
|
|
1961
|
-
env: githubHttpsRetryEnv(
|
|
2385
|
+
env: githubHttpsRetryEnv(remoteUrl2),
|
|
1962
2386
|
config: {
|
|
1963
2387
|
"http.extraHeader": header
|
|
1964
2388
|
}
|
|
@@ -2307,10 +2731,10 @@ async function pushBranch(worktreePath, branchName) {
|
|
|
2307
2731
|
sshErr || `git push origin ${branchName} failed` + (!token ? " (SSH remote and gh auth token unavailable \u2014 unlock ssh-agent or run: gh auth login)" : "")
|
|
2308
2732
|
);
|
|
2309
2733
|
}
|
|
2310
|
-
const
|
|
2734
|
+
const remoteUrl2 = await originRemoteUrl(worktreePath);
|
|
2311
2735
|
const tryHttps = async (header) => git(["push", "-u", "origin", branchName], worktreePath, {
|
|
2312
2736
|
reject: false,
|
|
2313
|
-
env: githubHttpsRetryEnv(
|
|
2737
|
+
env: githubHttpsRetryEnv(remoteUrl2),
|
|
2314
2738
|
config: {
|
|
2315
2739
|
"http.extraHeader": header
|
|
2316
2740
|
}
|
|
@@ -2323,6 +2747,40 @@ async function pushBranch(worktreePath, branchName) {
|
|
|
2323
2747
|
const httpsErr = (basicPush.stderr || bearer.stderr || bearer.stdout).trim();
|
|
2324
2748
|
throw new Error(httpsErr || sshErr || `git push origin ${branchName} failed`);
|
|
2325
2749
|
}
|
|
2750
|
+
async function markCursorPrReady(cwd, slug, selector) {
|
|
2751
|
+
const view = await viewCursorPr(cwd, slug, selector, "url,state,isDraft");
|
|
2752
|
+
if (!view) throw new Error("Could not load the Cursor pull request");
|
|
2753
|
+
const url = String(view.url ?? "");
|
|
2754
|
+
const state = String(view.state ?? "").toUpperCase();
|
|
2755
|
+
const isDraft = Boolean(view.isDraft);
|
|
2756
|
+
if (state === "MERGED" || state === "CLOSED") {
|
|
2757
|
+
return { url, state, isDraft: false };
|
|
2758
|
+
}
|
|
2759
|
+
if (isDraft) {
|
|
2760
|
+
if (await isDirty(cwd)) {
|
|
2761
|
+
throw new Error(
|
|
2762
|
+
"Commit and push local work before marking the pull request ready for review."
|
|
2763
|
+
);
|
|
2764
|
+
}
|
|
2765
|
+
const unpushed = await countUnpushedVsOrigin(cwd);
|
|
2766
|
+
if (unpushed > 0) {
|
|
2767
|
+
throw new Error(
|
|
2768
|
+
"Push this branch to origin before marking the pull request ready for review."
|
|
2769
|
+
);
|
|
2770
|
+
}
|
|
2771
|
+
await readyCursorPr(cwd, slug, selector);
|
|
2772
|
+
}
|
|
2773
|
+
return { url, state: state || "OPEN", isDraft: false };
|
|
2774
|
+
}
|
|
2775
|
+
async function mergeCursorPullRequest(cwd, slug, selector, method) {
|
|
2776
|
+
const view = await viewCursorPr(cwd, slug, selector, "url,state,isDraft");
|
|
2777
|
+
if (!view) throw new Error("Could not load the Cursor pull request");
|
|
2778
|
+
const url = String(view.url ?? "");
|
|
2779
|
+
if (String(view.state ?? "").toUpperCase() === "MERGED") return { url, state: "MERGED" };
|
|
2780
|
+
if (view.isDraft) await readyCursorPr(cwd, slug, selector);
|
|
2781
|
+
await mergeCursorPr(cwd, slug, selector, method);
|
|
2782
|
+
return { url, state: "MERGED" };
|
|
2783
|
+
}
|
|
2326
2784
|
async function runPrReady(cwd, selector, slug) {
|
|
2327
2785
|
const readyArgs = ["pr", "ready", selector];
|
|
2328
2786
|
if (slug) readyArgs.push("--repo", slug);
|
|
@@ -2334,6 +2792,10 @@ async function runPrReady(cwd, selector, slug) {
|
|
|
2334
2792
|
}
|
|
2335
2793
|
}
|
|
2336
2794
|
async function markPrReady(cwd, selector) {
|
|
2795
|
+
const origin = await resolveOriginRemote(cwd);
|
|
2796
|
+
if (origin?.forge === "entire") throw entireUsesTrails();
|
|
2797
|
+
const cursorSlug = origin?.forge === "cursor" ? origin.slug : null;
|
|
2798
|
+
if (cursorSlug) return markCursorPrReady(cwd, cursorSlug, selector);
|
|
2337
2799
|
const slug = await resolveGithubRepoSlug(cwd);
|
|
2338
2800
|
const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft"];
|
|
2339
2801
|
if (slug) viewArgs.push("--repo", slug);
|
|
@@ -2372,6 +2834,10 @@ async function markPrReady(cwd, selector) {
|
|
|
2372
2834
|
return { url, state: state || "OPEN", isDraft: false };
|
|
2373
2835
|
}
|
|
2374
2836
|
async function mergePr(cwd, selector, opts) {
|
|
2837
|
+
const origin = await resolveOriginRemote(cwd);
|
|
2838
|
+
if (origin?.forge === "entire") throw entireUsesTrails();
|
|
2839
|
+
const cursorSlug = origin?.forge === "cursor" ? origin.slug : null;
|
|
2840
|
+
if (cursorSlug) return mergeCursorPullRequest(cwd, cursorSlug, selector, opts?.method ?? "squash");
|
|
2375
2841
|
const slug = await resolveGithubRepoSlug(cwd);
|
|
2376
2842
|
const viewArgs = ["pr", "view", selector, "--json", "url,state,isDraft,number"];
|
|
2377
2843
|
if (slug) viewArgs.push("--repo", slug);
|
|
@@ -2430,116 +2896,6 @@ async function mergePr(cwd, selector, opts) {
|
|
|
2430
2896
|
}
|
|
2431
2897
|
return { url, state: "MERGED" };
|
|
2432
2898
|
}
|
|
2433
|
-
function ghPrCreateAssigneeArgs(assignee) {
|
|
2434
|
-
const raw = assignee?.trim();
|
|
2435
|
-
if (raw && /^(none|unassigned)$/i.test(raw)) return [];
|
|
2436
|
-
return ["--assignee", raw || "@me"];
|
|
2437
|
-
}
|
|
2438
|
-
async function createOrUpdatePr(worktreePath, opts) {
|
|
2439
|
-
await ensureGhPreferOrigin(worktreePath);
|
|
2440
|
-
const slug = await resolveGithubRepoSlug(worktreePath);
|
|
2441
|
-
if (!slug) {
|
|
2442
|
-
throw new Error(
|
|
2443
|
-
"Could not resolve the origin GitHub repo (owner/name) for this worktree. Check that `git remote get-url origin` points at github.com."
|
|
2444
|
-
);
|
|
2445
|
-
}
|
|
2446
|
-
const repoArgs = ghRepoSelectArgs(slug);
|
|
2447
|
-
const headRef = ghHeadRef(slug, opts.head);
|
|
2448
|
-
const branchOnly = opts.head.trim().replace(/^refs\/heads\//, "");
|
|
2449
|
-
const bodyText = clampGithubPrBody(opts.body ?? opts.title);
|
|
2450
|
-
const bodyFile = join2(
|
|
2451
|
-
tmpdir(),
|
|
2452
|
-
`sideboard-pr-body-${process.pid}-${randomBytes(6).toString("hex")}.md`
|
|
2453
|
-
);
|
|
2454
|
-
writeFileSync(bodyFile, bodyText.endsWith("\n") ? bodyText : `${bodyText}
|
|
2455
|
-
`, "utf8");
|
|
2456
|
-
const bodyArgs = ["--body-file", bodyFile];
|
|
2457
|
-
try {
|
|
2458
|
-
return await createOrUpdatePrWithBodyFile(worktreePath, {
|
|
2459
|
-
...opts,
|
|
2460
|
-
slug,
|
|
2461
|
-
repoArgs,
|
|
2462
|
-
headRef,
|
|
2463
|
-
branchOnly,
|
|
2464
|
-
bodyArgs
|
|
2465
|
-
});
|
|
2466
|
-
} finally {
|
|
2467
|
-
try {
|
|
2468
|
-
unlinkSync(bodyFile);
|
|
2469
|
-
} catch {
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
async function createOrUpdatePrWithBodyFile(worktreePath, opts) {
|
|
2474
|
-
const { slug, repoArgs, headRef, branchOnly, bodyArgs } = opts;
|
|
2475
|
-
const existing = await gh(
|
|
2476
|
-
[...repoArgs, "pr", "view", branchOnly, "--json", "url", "--jq", ".url"],
|
|
2477
|
-
worktreePath,
|
|
2478
|
-
{ reject: false }
|
|
2479
|
-
);
|
|
2480
|
-
if (existing.exitCode === 0 && existing.stdout.trim() && !opts.web) {
|
|
2481
|
-
const url2 = existing.stdout.trim();
|
|
2482
|
-
await gh(
|
|
2483
|
-
[
|
|
2484
|
-
...repoArgs,
|
|
2485
|
-
"pr",
|
|
2486
|
-
"edit",
|
|
2487
|
-
branchOnly,
|
|
2488
|
-
"--title",
|
|
2489
|
-
opts.title,
|
|
2490
|
-
...bodyArgs
|
|
2491
|
-
],
|
|
2492
|
-
worktreePath,
|
|
2493
|
-
{ reject: false }
|
|
2494
|
-
);
|
|
2495
|
-
return url2;
|
|
2496
|
-
}
|
|
2497
|
-
if (opts.web) {
|
|
2498
|
-
const args2 = [
|
|
2499
|
-
...repoArgs,
|
|
2500
|
-
"pr",
|
|
2501
|
-
"create",
|
|
2502
|
-
"--web",
|
|
2503
|
-
"--title",
|
|
2504
|
-
opts.title,
|
|
2505
|
-
...bodyArgs,
|
|
2506
|
-
"--base",
|
|
2507
|
-
opts.base,
|
|
2508
|
-
"--head",
|
|
2509
|
-
headRef
|
|
2510
|
-
];
|
|
2511
|
-
if (opts.draft) args2.push("--draft");
|
|
2512
|
-
args2.push(...ghPrCreateAssigneeArgs(opts.assignee));
|
|
2513
|
-
await gh(args2, worktreePath, { reject: false });
|
|
2514
|
-
const again = await gh(
|
|
2515
|
-
[...repoArgs, "pr", "view", branchOnly, "--json", "url", "--jq", ".url"],
|
|
2516
|
-
worktreePath,
|
|
2517
|
-
{ reject: false }
|
|
2518
|
-
);
|
|
2519
|
-
return again.stdout.trim() || "";
|
|
2520
|
-
}
|
|
2521
|
-
const args = [
|
|
2522
|
-
...repoArgs,
|
|
2523
|
-
"pr",
|
|
2524
|
-
"create",
|
|
2525
|
-
"--title",
|
|
2526
|
-
opts.title,
|
|
2527
|
-
...bodyArgs,
|
|
2528
|
-
"--base",
|
|
2529
|
-
opts.base,
|
|
2530
|
-
"--head",
|
|
2531
|
-
headRef
|
|
2532
|
-
];
|
|
2533
|
-
if (opts.draft) args.push("--draft");
|
|
2534
|
-
args.push(...ghPrCreateAssigneeArgs(opts.assignee));
|
|
2535
|
-
const created = await gh(args, worktreePath, { reject: false });
|
|
2536
|
-
if (created.exitCode !== 0) {
|
|
2537
|
-
const raw = created.stderr.trim() || created.stdout.trim() || "gh pr create failed";
|
|
2538
|
-
throw new Error(await formatPrCreateFailure(raw, worktreePath, { slug, head: headRef }));
|
|
2539
|
-
}
|
|
2540
|
-
const url = created.stdout.trim().split("\n").find((l) => l.startsWith("http")) ?? created.stdout.trim();
|
|
2541
|
-
return url;
|
|
2542
|
-
}
|
|
2543
2899
|
function suggestSlug(source) {
|
|
2544
2900
|
const base = slugify(source || "thread");
|
|
2545
2901
|
const stamp = Date.now().toString(36).slice(-4);
|
|
@@ -2547,6 +2903,8 @@ function suggestSlug(source) {
|
|
|
2547
2903
|
}
|
|
2548
2904
|
|
|
2549
2905
|
export {
|
|
2906
|
+
isGhRateLimitError,
|
|
2907
|
+
clampGithubPrBody,
|
|
2550
2908
|
formatGhLandError,
|
|
2551
2909
|
githubAgentGitEnv,
|
|
2552
2910
|
mergeAgentGitAuthEnv,
|
|
@@ -2555,10 +2913,12 @@ export {
|
|
|
2555
2913
|
resolveCodexGitWritableRoots,
|
|
2556
2914
|
codexUnattendedGitConfigArgs,
|
|
2557
2915
|
formatGitAuthModeDirective,
|
|
2916
|
+
formatRemoteTag,
|
|
2558
2917
|
resolveListPrsOptions,
|
|
2918
|
+
ciChecksFailed,
|
|
2919
|
+
createOrUpdateCursorPr,
|
|
2559
2920
|
enqueueByKey,
|
|
2560
2921
|
withRepoGitLock,
|
|
2561
|
-
ciChecksFailed,
|
|
2562
2922
|
detectGhStack,
|
|
2563
2923
|
getPrStack,
|
|
2564
2924
|
initPrStack,
|
|
@@ -2569,6 +2929,8 @@ export {
|
|
|
2569
2929
|
resolveRepoRoot,
|
|
2570
2930
|
canonicalizeRepoPath,
|
|
2571
2931
|
parseGithubSlugFromRemoteUrl,
|
|
2932
|
+
resolveOriginRemote,
|
|
2933
|
+
resolveProjectRemoteTag,
|
|
2572
2934
|
resolveGithubRepoSlug,
|
|
2573
2935
|
ghRepoSelectArgs,
|
|
2574
2936
|
ghHeadRef,
|
|
@@ -2577,6 +2939,7 @@ export {
|
|
|
2577
2939
|
resolveDefaultBranch,
|
|
2578
2940
|
resolveDiffBaseRef,
|
|
2579
2941
|
listBranches,
|
|
2942
|
+
entireUsesTrails,
|
|
2580
2943
|
listPrs,
|
|
2581
2944
|
getPr,
|
|
2582
2945
|
isDefaultishSourceRef,
|
|
@@ -2608,7 +2971,5 @@ export {
|
|
|
2608
2971
|
pushBranch,
|
|
2609
2972
|
markPrReady,
|
|
2610
2973
|
mergePr,
|
|
2611
|
-
ghPrCreateAssigneeArgs,
|
|
2612
|
-
createOrUpdatePr,
|
|
2613
2974
|
suggestSlug
|
|
2614
2975
|
};
|