@makerbi/remodex 1.3.10 → 1.4.1
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/bin/remodex.js +1 -0
- package/package.json +2 -2
- package/src/bridge.js +29 -0
- package/src/codex-desktop-refresher.js +1 -0
- package/src/desktop-handler.js +100 -11
- package/src/desktop-ipc-action-follower.js +648 -0
- package/src/git-handler.js +322 -9
- package/src/pet-handler.js +537 -0
- package/src/qr.js +21 -2
- package/src/workspace-handler.js +31 -4
- package/src/private-defaults.json +0 -4
package/src/git-handler.js
CHANGED
|
@@ -13,12 +13,16 @@ const { promisify } = require("util");
|
|
|
13
13
|
|
|
14
14
|
const execFileAsync = promisify(execFile);
|
|
15
15
|
const GIT_TIMEOUT_MS = 30_000;
|
|
16
|
+
/** Node defaults maxBuffer to 1 MiB; large repo diffs exceed it ("stdout maxBuffer length exceeded"). */
|
|
17
|
+
const GIT_EXEC_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
16
18
|
const GIT_DRAFT_TIMEOUT_MS = 120_000;
|
|
19
|
+
const GITHUB_CLI_TIMEOUT_MS = 120_000;
|
|
17
20
|
const GIT_DRAFT_PATCH_MAX_BYTES = 80_000;
|
|
18
21
|
const EMPTY_TREE_HASH = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
19
22
|
const DEFAULT_GIT_WRITER_MODEL = "gpt-5.4-mini";
|
|
20
23
|
|
|
21
24
|
let runStructuredCodexJsonImpl = runStructuredCodexJson;
|
|
25
|
+
let runGitHubCliImpl = runGitHubCli;
|
|
22
26
|
|
|
23
27
|
function resolveGitWriterModel(rawModel) {
|
|
24
28
|
const trimmed = typeof rawModel === "string" ? rawModel.trim() : "";
|
|
@@ -123,6 +127,10 @@ async function handleGitMethod(method, params, options = {}) {
|
|
|
123
127
|
return gitRemoteUrl(cwd);
|
|
124
128
|
case "git/generatePullRequestDraft":
|
|
125
129
|
return gitGeneratePullRequestDraft(cwd, params, options);
|
|
130
|
+
case "git/createPullRequest":
|
|
131
|
+
return gitCreatePullRequest(cwd, params, options);
|
|
132
|
+
case "git/runStackedAction":
|
|
133
|
+
return gitRunStackedAction(cwd, params, options);
|
|
126
134
|
case "git/branchesWithStatus":
|
|
127
135
|
return gitBranchesWithStatus(cwd);
|
|
128
136
|
default:
|
|
@@ -400,7 +408,7 @@ async function gitPush(cwd) {
|
|
|
400
408
|
pushErr.message?.includes("no upstream") ||
|
|
401
409
|
pushErr.message?.includes("has no upstream branch")
|
|
402
410
|
) {
|
|
403
|
-
await git(cwd, "push", "--set-upstream",
|
|
411
|
+
await git(cwd, "push", "--set-upstream", remote, branch);
|
|
404
412
|
} else {
|
|
405
413
|
throw pushErr;
|
|
406
414
|
}
|
|
@@ -963,6 +971,282 @@ async function gitRemoteUrl(cwd) {
|
|
|
963
971
|
return { url: raw, ownerRepo };
|
|
964
972
|
}
|
|
965
973
|
|
|
974
|
+
// ─── Git Stacked Actions / Pull Requests ─────────────────────
|
|
975
|
+
|
|
976
|
+
async function gitRunStackedAction(cwd, params, options = {}) {
|
|
977
|
+
const action = normalizeStackedGitAction(params.action);
|
|
978
|
+
const initialStatus = await gitStatus(cwd);
|
|
979
|
+
const wantsCommit = action === "commit" || action === "commit_push" || action === "commit_push_pr";
|
|
980
|
+
const wantsPr = action === "create_pr" || action === "commit_push_pr";
|
|
981
|
+
|
|
982
|
+
if (params.featureBranch === true) {
|
|
983
|
+
await gitCreateFeatureBranch(cwd, params);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const branch = await currentBranchName(cwd);
|
|
987
|
+
const result = {
|
|
988
|
+
action,
|
|
989
|
+
branch: {
|
|
990
|
+
status: params.featureBranch === true ? "created" : "skipped_not_requested",
|
|
991
|
+
name: params.featureBranch === true ? branch : undefined,
|
|
992
|
+
},
|
|
993
|
+
commit: { status: "skipped_not_requested" },
|
|
994
|
+
push: { status: "skipped_not_requested" },
|
|
995
|
+
pr: { status: "skipped_not_requested" },
|
|
996
|
+
status: initialStatus,
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
if (action === "push" && initialStatus.dirty) {
|
|
1000
|
+
throw gitError("dirty_worktree", "Commit or stash local changes before pushing.");
|
|
1001
|
+
}
|
|
1002
|
+
if (action === "create_pr" && initialStatus.dirty) {
|
|
1003
|
+
throw gitError("dirty_worktree", "Commit local changes before creating a PR.");
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
if (wantsCommit) {
|
|
1007
|
+
const statusBeforeCommit = await gitStatus(cwd);
|
|
1008
|
+
if (statusBeforeCommit.dirty) {
|
|
1009
|
+
const commitResult = await gitCommit(cwd, {
|
|
1010
|
+
message: params.commitMessage || params.message,
|
|
1011
|
+
});
|
|
1012
|
+
result.commit = {
|
|
1013
|
+
status: "created",
|
|
1014
|
+
...commitResult,
|
|
1015
|
+
commitSha: commitResult.hash,
|
|
1016
|
+
subject: firstCommitMessageLine(params.commitMessage || params.message),
|
|
1017
|
+
};
|
|
1018
|
+
} else if (action === "commit") {
|
|
1019
|
+
throw gitError("nothing_to_commit", "Nothing to commit.");
|
|
1020
|
+
} else {
|
|
1021
|
+
result.commit = { status: "skipped_clean" };
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
const statusBeforePush = await gitStatus(cwd);
|
|
1026
|
+
const shouldPush =
|
|
1027
|
+
action === "push" ||
|
|
1028
|
+
action === "commit_push" ||
|
|
1029
|
+
action === "commit_push_pr" ||
|
|
1030
|
+
(action === "create_pr" && (!statusBeforePush.tracking || statusBeforePush.ahead > 0));
|
|
1031
|
+
|
|
1032
|
+
if (shouldPush) {
|
|
1033
|
+
if (action === "push" && !statusBeforePush.canPush) {
|
|
1034
|
+
throw gitError("nothing_to_push", "Nothing to push.");
|
|
1035
|
+
}
|
|
1036
|
+
if (action === "commit_push" && result.commit.status === "skipped_clean" && !statusBeforePush.canPush) {
|
|
1037
|
+
throw gitError("nothing_to_commit", "Nothing to commit or push.");
|
|
1038
|
+
}
|
|
1039
|
+
if (statusBeforePush.dirty) {
|
|
1040
|
+
throw gitError("dirty_worktree", "Commit or stash local changes before pushing.");
|
|
1041
|
+
}
|
|
1042
|
+
result.push = {
|
|
1043
|
+
state: "pushed",
|
|
1044
|
+
...(await gitPush(cwd)),
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
if (wantsPr) {
|
|
1049
|
+
result.pr = await gitCreatePullRequest(cwd, {
|
|
1050
|
+
...params,
|
|
1051
|
+
pushBeforeCreate: false,
|
|
1052
|
+
}, options);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
result.status = await gitStatus(cwd);
|
|
1056
|
+
return result;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
async function gitCreatePullRequest(cwd, params, options = {}) {
|
|
1060
|
+
const status = await gitStatus(cwd);
|
|
1061
|
+
if (status.dirty) {
|
|
1062
|
+
throw gitError("dirty_worktree", "Commit local changes before creating a PR.");
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
const branch = status.branch || await currentBranchName(cwd);
|
|
1066
|
+
if (!branch || branch === "HEAD") {
|
|
1067
|
+
throw gitError("no_branch", "No current branch found.");
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
if (params.pushBeforeCreate !== false && (!status.tracking || status.ahead > 0)) {
|
|
1071
|
+
await gitPush(cwd);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
const branchResult = await gitBranches(cwd);
|
|
1075
|
+
const baseBranch = resolveBaseBranchName(params.baseBranch, branchResult.default || branchResult.defaultBranch);
|
|
1076
|
+
if (!baseBranch) {
|
|
1077
|
+
throw gitError("no_default_branch", "Could not determine the repository default branch.");
|
|
1078
|
+
}
|
|
1079
|
+
if (baseBranch === branch) {
|
|
1080
|
+
throw gitError(
|
|
1081
|
+
"pull_request_same_branch",
|
|
1082
|
+
`Cannot create a pull request from '${branch}' into itself. Create or switch to a feature branch first.`
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
await ensureGitHubCliReady(cwd);
|
|
1087
|
+
const existing = await findOpenPullRequest(cwd, branch);
|
|
1088
|
+
if (existing) {
|
|
1089
|
+
return pullRequestResult("opened_existing", existing, baseBranch, branch);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
const draft = await generatePullRequestDraftOrFallback(cwd, params, options, baseBranch, branch);
|
|
1093
|
+
const bodyFile = path.join(os.tmpdir(), `remodex-pr-body-${process.pid}-${Date.now()}-${randomBytes(4).toString("hex")}.md`);
|
|
1094
|
+
fs.writeFileSync(bodyFile, draft.body, "utf8");
|
|
1095
|
+
|
|
1096
|
+
let createOutput = null;
|
|
1097
|
+
try {
|
|
1098
|
+
createOutput = await gitHubCli(cwd, [
|
|
1099
|
+
"pr",
|
|
1100
|
+
"create",
|
|
1101
|
+
"--base",
|
|
1102
|
+
baseBranch,
|
|
1103
|
+
"--head",
|
|
1104
|
+
branch,
|
|
1105
|
+
"--title",
|
|
1106
|
+
draft.title,
|
|
1107
|
+
"--body-file",
|
|
1108
|
+
bodyFile,
|
|
1109
|
+
]);
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
const existingFromError = await findOpenPullRequest(cwd, branch).catch(() => null);
|
|
1112
|
+
if (existingFromError || isPullRequestAlreadyExistsMessage(error.message)) {
|
|
1113
|
+
return pullRequestResult("opened_existing", existingFromError, baseBranch, branch, draft.title);
|
|
1114
|
+
}
|
|
1115
|
+
throw error;
|
|
1116
|
+
} finally {
|
|
1117
|
+
fs.rmSync(bodyFile, { force: true });
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
const created = await findOpenPullRequest(cwd, branch).catch(() => null);
|
|
1121
|
+
const createdUrl = parsePullRequestUrlFromText(`${createOutput?.stdout || ""}\n${createOutput?.stderr || ""}`);
|
|
1122
|
+
return pullRequestResult("created", created, baseBranch, branch, draft.title, createdUrl);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
function normalizeStackedGitAction(rawAction) {
|
|
1126
|
+
const action = typeof rawAction === "string" ? rawAction.trim() : "";
|
|
1127
|
+
if (["commit", "push", "create_pr", "commit_push", "commit_push_pr"].includes(action)) {
|
|
1128
|
+
return action;
|
|
1129
|
+
}
|
|
1130
|
+
throw gitError("invalid_git_action", "Unknown git action.");
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
async function gitCreateFeatureBranch(cwd, params) {
|
|
1134
|
+
const requestedName = normalizeNonEmptyLine(params.featureBranchName || params.branchName);
|
|
1135
|
+
const branchName = requestedName || await defaultFeatureBranchName(cwd, params);
|
|
1136
|
+
await assertValidCreatedBranchName(cwd, branchName);
|
|
1137
|
+
if (await branchExists(cwd, branchName)) {
|
|
1138
|
+
throw gitError("branch_exists", `Branch '${branchName}' already exists.`);
|
|
1139
|
+
}
|
|
1140
|
+
await git(cwd, "checkout", "-b", branchName);
|
|
1141
|
+
return branchName;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
async function defaultFeatureBranchName(cwd, params) {
|
|
1145
|
+
const prefix = normalizeNonEmptyLine(params.featureBranchPrefix) || "remodex/mobile-pr";
|
|
1146
|
+
const timestamp = new Date().toISOString().replace(/[-:T.Z]/g, "").slice(0, 14);
|
|
1147
|
+
const base = `${prefix}-${timestamp}`;
|
|
1148
|
+
let candidate = base;
|
|
1149
|
+
for (let index = 2; await branchExists(cwd, candidate); index += 1) {
|
|
1150
|
+
candidate = `${base}-${index}`;
|
|
1151
|
+
}
|
|
1152
|
+
return candidate;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
async function branchExists(cwd, branchName) {
|
|
1156
|
+
try {
|
|
1157
|
+
await git(cwd, "show-ref", "--verify", "--quiet", `refs/heads/${branchName}`);
|
|
1158
|
+
return true;
|
|
1159
|
+
} catch {
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
async function currentBranchName(cwd) {
|
|
1165
|
+
return (await git(cwd, "rev-parse", "--abbrev-ref", "HEAD")).trim();
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
async function generatePullRequestDraftOrFallback(cwd, params, options, baseBranch, branch) {
|
|
1169
|
+
try {
|
|
1170
|
+
return await gitGeneratePullRequestDraft(cwd, { ...params, baseBranch }, options);
|
|
1171
|
+
} catch (error) {
|
|
1172
|
+
if (error?.errorCode && error.errorCode !== "pull_request_draft_generation_failed") {
|
|
1173
|
+
throw error;
|
|
1174
|
+
}
|
|
1175
|
+
return {
|
|
1176
|
+
title: `Update ${branch}`,
|
|
1177
|
+
body: [
|
|
1178
|
+
"## Summary",
|
|
1179
|
+
`- Prepare changes from \`${branch}\` for review.`,
|
|
1180
|
+
"",
|
|
1181
|
+
"## Testing",
|
|
1182
|
+
"- Not run from Remodex.",
|
|
1183
|
+
"",
|
|
1184
|
+
"## Notes",
|
|
1185
|
+
`- Base branch: \`${baseBranch}\`.`,
|
|
1186
|
+
].join("\n"),
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
function firstCommitMessageLine(message) {
|
|
1192
|
+
return normalizeNonEmptyLine(message) || "Changes from Remodex";
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
async function ensureGitHubCliReady(cwd) {
|
|
1196
|
+
try {
|
|
1197
|
+
await gitHubCli(cwd, ["auth", "status"]);
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
if (error?.errorCode) {
|
|
1200
|
+
throw error;
|
|
1201
|
+
}
|
|
1202
|
+
throw gitError("github_cli_unavailable", error.message || "GitHub CLI is unavailable.");
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
async function findOpenPullRequest(cwd, branch) {
|
|
1207
|
+
const output = await gitHubCli(cwd, [
|
|
1208
|
+
"pr",
|
|
1209
|
+
"list",
|
|
1210
|
+
"--head",
|
|
1211
|
+
branch,
|
|
1212
|
+
"--state",
|
|
1213
|
+
"open",
|
|
1214
|
+
"--limit",
|
|
1215
|
+
"1",
|
|
1216
|
+
"--json",
|
|
1217
|
+
"number,title,url,baseRefName,headRefName,state",
|
|
1218
|
+
]);
|
|
1219
|
+
const pullRequests = JSON.parse(output.stdout.trim() || "[]");
|
|
1220
|
+
return Array.isArray(pullRequests) ? pullRequests[0] || null : null;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
function pullRequestResult(status, pullRequest, baseBranch, branch, fallbackTitle = "", fallbackUrl = null) {
|
|
1224
|
+
return {
|
|
1225
|
+
status,
|
|
1226
|
+
url: pullRequest?.url || fallbackUrl || null,
|
|
1227
|
+
number: pullRequest?.number || null,
|
|
1228
|
+
baseBranch: pullRequest?.baseRefName || baseBranch,
|
|
1229
|
+
headBranch: pullRequest?.headRefName || branch,
|
|
1230
|
+
title: pullRequest?.title || fallbackTitle || "",
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
function isPullRequestAlreadyExistsMessage(message) {
|
|
1235
|
+
return typeof message === "string" && /pull request .*already exists|already exists.*pull request/i.test(message);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function parsePullRequestUrlFromText(text) {
|
|
1239
|
+
if (typeof text !== "string") {
|
|
1240
|
+
return null;
|
|
1241
|
+
}
|
|
1242
|
+
const match = text.match(/https:\/\/github\.com\/[^\s"'<>]+\/pull\/\d+/);
|
|
1243
|
+
return match ? match[0] : null;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
async function gitHubCli(cwd, args) {
|
|
1247
|
+
return runGitHubCliImpl(cwd, args);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
966
1250
|
async function buildCommitDraftContext(cwd) {
|
|
967
1251
|
const [statusResult, repoRoot] = await Promise.all([
|
|
968
1252
|
gitStatus(cwd),
|
|
@@ -1010,7 +1294,7 @@ async function buildPullRequestDraftContext(cwd, params) {
|
|
|
1010
1294
|
throw gitError("no_default_branch", "Could not determine the repository default branch.");
|
|
1011
1295
|
}
|
|
1012
1296
|
|
|
1013
|
-
const baseRef = await
|
|
1297
|
+
const baseRef = await resolvePullRequestBaseRef(cwd, baseBranch);
|
|
1014
1298
|
const mergeBase = (await git(cwd, "merge-base", "HEAD", baseRef)).trim();
|
|
1015
1299
|
const patch = truncateDraftPatch(
|
|
1016
1300
|
(await git(cwd, "diff", "--binary", "--find-renames", `${mergeBase}..HEAD`)).trim()
|
|
@@ -1041,16 +1325,17 @@ async function buildPullRequestDraftContext(cwd, params) {
|
|
|
1041
1325
|
};
|
|
1042
1326
|
}
|
|
1043
1327
|
|
|
1044
|
-
|
|
1328
|
+
// PRs compare against the remote base when possible, matching GitHub's base branch.
|
|
1329
|
+
async function resolvePullRequestBaseRef(cwd, branchName) {
|
|
1045
1330
|
const localRef = `refs/heads/${branchName}`;
|
|
1046
1331
|
const remoteRef = `refs/remotes/origin/${branchName}`;
|
|
1047
1332
|
|
|
1048
|
-
if (await refExists(cwd, localRef)) {
|
|
1049
|
-
return localRef;
|
|
1050
|
-
}
|
|
1051
1333
|
if (await refExists(cwd, remoteRef)) {
|
|
1052
1334
|
return remoteRef;
|
|
1053
1335
|
}
|
|
1336
|
+
if (await refExists(cwd, localRef)) {
|
|
1337
|
+
return localRef;
|
|
1338
|
+
}
|
|
1054
1339
|
|
|
1055
1340
|
return branchName;
|
|
1056
1341
|
}
|
|
@@ -2104,7 +2389,7 @@ async function gitDiffNoIndexNumstat(cwd, filePath) {
|
|
|
2104
2389
|
const { stdout } = await execFileAsync(
|
|
2105
2390
|
"git",
|
|
2106
2391
|
["diff", "--no-index", "--numstat", "--", "/dev/null", filePath],
|
|
2107
|
-
{ cwd, timeout: GIT_TIMEOUT_MS }
|
|
2392
|
+
{ cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: GIT_EXEC_MAX_BUFFER_BYTES }
|
|
2108
2393
|
);
|
|
2109
2394
|
return stdout;
|
|
2110
2395
|
} catch (err) {
|
|
@@ -2130,7 +2415,7 @@ async function gitDiffNoIndexPatch(cwd, filePath) {
|
|
|
2130
2415
|
const { stdout } = await execFileAsync(
|
|
2131
2416
|
"git",
|
|
2132
2417
|
["diff", "--no-index", "--binary", "--", "/dev/null", filePath],
|
|
2133
|
-
{ cwd, timeout: GIT_TIMEOUT_MS }
|
|
2418
|
+
{ cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: GIT_EXEC_MAX_BUFFER_BYTES }
|
|
2134
2419
|
);
|
|
2135
2420
|
return stdout;
|
|
2136
2421
|
} catch (err) {
|
|
@@ -2145,7 +2430,11 @@ async function gitDiffNoIndexPatch(cwd, filePath) {
|
|
|
2145
2430
|
// ─── Helpers ──────────────────────────────────────────────────
|
|
2146
2431
|
|
|
2147
2432
|
function git(cwd, ...args) {
|
|
2148
|
-
return execFileAsync("git", args, {
|
|
2433
|
+
return execFileAsync("git", args, {
|
|
2434
|
+
cwd,
|
|
2435
|
+
timeout: GIT_TIMEOUT_MS,
|
|
2436
|
+
maxBuffer: GIT_EXEC_MAX_BUFFER_BYTES,
|
|
2437
|
+
})
|
|
2149
2438
|
.then(({ stdout }) => stdout)
|
|
2150
2439
|
.catch((err) => {
|
|
2151
2440
|
const msg = (err.stderr || err.message || "").trim();
|
|
@@ -2154,6 +2443,22 @@ function git(cwd, ...args) {
|
|
|
2154
2443
|
});
|
|
2155
2444
|
}
|
|
2156
2445
|
|
|
2446
|
+
// Runs GitHub CLI in the same working tree so PR creation respects local auth and remotes.
|
|
2447
|
+
function runGitHubCli(cwd, args) {
|
|
2448
|
+
return execFileAsync("gh", args, { cwd, timeout: GITHUB_CLI_TIMEOUT_MS })
|
|
2449
|
+
.then(({ stdout, stderr }) => ({ stdout, stderr }))
|
|
2450
|
+
.catch((err) => {
|
|
2451
|
+
const detail = (err.stderr || err.message || "").trim();
|
|
2452
|
+
if (err.code === "ENOENT") {
|
|
2453
|
+
throw gitError("github_cli_unavailable", "GitHub CLI (`gh`) is required but is not available on PATH.");
|
|
2454
|
+
}
|
|
2455
|
+
if (/not logged into|not authenticated|gh auth login/i.test(detail)) {
|
|
2456
|
+
throw gitError("github_cli_unauthenticated", "GitHub CLI is not authenticated. Run `gh auth login` on this Mac and retry.");
|
|
2457
|
+
}
|
|
2458
|
+
throw gitError("github_cli_failed", detail || "GitHub CLI command failed.");
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2157
2462
|
async function revListCounts(cwd) {
|
|
2158
2463
|
const output = await git(cwd, "rev-list", "--left-right", "--count", "HEAD...@{u}");
|
|
2159
2464
|
const parts = output.trim().split(/\s+/);
|
|
@@ -2339,6 +2644,8 @@ module.exports = {
|
|
|
2339
2644
|
__test: {
|
|
2340
2645
|
gitGenerateCommitMessage,
|
|
2341
2646
|
gitGeneratePullRequestDraft,
|
|
2647
|
+
gitCreatePullRequest,
|
|
2648
|
+
gitRunStackedAction,
|
|
2342
2649
|
threadGenerateTitle,
|
|
2343
2650
|
threadNameSet,
|
|
2344
2651
|
gitBranches,
|
|
@@ -2367,5 +2674,11 @@ module.exports = {
|
|
|
2367
2674
|
resetRunStructuredCodexJsonImplementation() {
|
|
2368
2675
|
runStructuredCodexJsonImpl = runStructuredCodexJson;
|
|
2369
2676
|
},
|
|
2677
|
+
setRunGitHubCliImplementation(fn) {
|
|
2678
|
+
runGitHubCliImpl = typeof fn === "function" ? fn : runGitHubCli;
|
|
2679
|
+
},
|
|
2680
|
+
resetRunGitHubCliImplementation() {
|
|
2681
|
+
runGitHubCliImpl = runGitHubCli;
|
|
2682
|
+
},
|
|
2370
2683
|
},
|
|
2371
2684
|
};
|