@g-abhishek/gitx 0.1.4 → 0.1.6

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.
Files changed (48) hide show
  1. package/README.md +82 -13
  2. package/dist/ai/claudeAi.d.ts.map +1 -1
  3. package/dist/ai/claudeAi.js +4 -30
  4. package/dist/ai/claudeAi.js.map +1 -1
  5. package/dist/ai/claudeCliAi.d.ts.map +1 -1
  6. package/dist/ai/claudeCliAi.js +4 -21
  7. package/dist/ai/claudeCliAi.js.map +1 -1
  8. package/dist/ai/openAiAi.d.ts.map +1 -1
  9. package/dist/ai/openAiAi.js +4 -30
  10. package/dist/ai/openAiAi.js.map +1 -1
  11. package/dist/ai/reviewHelpers.d.ts +24 -0
  12. package/dist/ai/reviewHelpers.d.ts.map +1 -1
  13. package/dist/ai/reviewHelpers.js +123 -39
  14. package/dist/ai/reviewHelpers.js.map +1 -1
  15. package/dist/cli/commands/pr/cherryPick.d.ts +25 -0
  16. package/dist/cli/commands/pr/cherryPick.d.ts.map +1 -0
  17. package/dist/cli/commands/pr/cherryPick.js +317 -0
  18. package/dist/cli/commands/pr/cherryPick.js.map +1 -0
  19. package/dist/cli/commands/pr/fixComments.d.ts +5 -2
  20. package/dist/cli/commands/pr/fixComments.d.ts.map +1 -1
  21. package/dist/cli/commands/pr/fixComments.js +5 -82
  22. package/dist/cli/commands/pr/fixComments.js.map +1 -1
  23. package/dist/cli/commands/pr/index.d.ts.map +1 -1
  24. package/dist/cli/commands/pr/index.js +6 -2
  25. package/dist/cli/commands/pr/index.js.map +1 -1
  26. package/dist/cli/commands/pr/port.d.ts +34 -0
  27. package/dist/cli/commands/pr/port.d.ts.map +1 -0
  28. package/dist/cli/commands/pr/port.js +453 -0
  29. package/dist/cli/commands/pr/port.js.map +1 -0
  30. package/dist/cli/commands/pr/resolve.d.ts +3 -0
  31. package/dist/cli/commands/pr/resolve.d.ts.map +1 -0
  32. package/dist/cli/commands/pr/resolve.js +92 -0
  33. package/dist/cli/commands/pr/resolve.js.map +1 -0
  34. package/dist/cli/commands/pr/review.js +1 -1
  35. package/dist/cli/commands/pr/review.js.map +1 -1
  36. package/dist/cli/commands/sync.d.ts +12 -11
  37. package/dist/cli/commands/sync.d.ts.map +1 -1
  38. package/dist/cli/commands/sync.js +13 -101
  39. package/dist/cli/commands/sync.js.map +1 -1
  40. package/dist/utils/gitOps.d.ts +11 -4
  41. package/dist/utils/gitOps.d.ts.map +1 -1
  42. package/dist/utils/gitOps.js +53 -37
  43. package/dist/utils/gitOps.js.map +1 -1
  44. package/dist/workflows/pr.d.ts +1 -1
  45. package/dist/workflows/pr.d.ts.map +1 -1
  46. package/dist/workflows/pr.js +82 -19
  47. package/dist/workflows/pr.js.map +1 -1
  48. package/package.json +1 -1
@@ -0,0 +1,453 @@
1
+ /**
2
+ * gitx pr port <number> <target1> [target2...]
3
+ *
4
+ * Ports all commits from a PR onto one or more target branches, then opens
5
+ * a new PR for each target — without touching your current working branch.
6
+ *
7
+ * Example:
8
+ * gitx pr port 12345 release/v1 release/v2
9
+ *
10
+ * → Creates port/pr-12345-to-release-v1 from origin/release/v1
11
+ * port/pr-12345-to-release-v2 from origin/release/v2
12
+ * → Cherry-picks all commits from PR #12345 onto each port branch
13
+ * → Pushes both port branches
14
+ * → Opens a PR for each: port branch → target branch
15
+ * → Prints the PR URLs
16
+ *
17
+ * Flow (per target branch):
18
+ * 1. Fetch PR metadata → source branch (head) + base branch
19
+ * 2. git fetch origin <head> — ensure source commits are local
20
+ * 3. Collect commits: origin/<base>..origin/<head> (oldest → newest)
21
+ * 4. Create port/pr-<number>-to-<target> from origin/<target>
22
+ * 5. Cherry-pick each commit with -x; AI resolves conflicts where possible
23
+ * 6. Push port branch
24
+ * 7. Create PR: port branch → target branch; print URL
25
+ *
26
+ * Options:
27
+ * --no-pr Push port branches but skip PR creation
28
+ * --draft Create PRs as drafts
29
+ * --dry-run Show what would happen without making any changes
30
+ * --no-confirm Skip the per-target confirmation prompt
31
+ */
32
+ import ora from "ora";
33
+ import { execFile } from "node:child_process";
34
+ import { promisify } from "node:util";
35
+ import { readFile, writeFile } from "node:fs/promises";
36
+ import { resolve as resolvePath } from "node:path";
37
+ import { confirm } from "@inquirer/prompts";
38
+ import { logger } from "../../../logger/logger.js";
39
+ import { isInsideGitRepo } from "../../../utils/git.js";
40
+ import { getCurrentBranch } from "../../../utils/gitOps.js";
41
+ import { GitxError } from "../../../utils/errors.js";
42
+ import { Gitx } from "../../../core/gitx.js";
43
+ import { createProvider } from "../../../providers/factory.js";
44
+ const execFileAsync = promisify(execFile);
45
+ // ─── Git helper ───────────────────────────────────────────────────────────────
46
+ async function git(args, cwd) {
47
+ try {
48
+ const result = await execFileAsync("git", args, { cwd });
49
+ return { stdout: result.stdout.trim(), stderr: "", exitCode: 0 };
50
+ }
51
+ catch (err) {
52
+ const e = err;
53
+ return {
54
+ stdout: e.stdout?.trim() ?? "",
55
+ stderr: e.stderr?.trim() ?? "",
56
+ exitCode: e.code ?? 1,
57
+ };
58
+ }
59
+ }
60
+ // ─── Conflict helpers ─────────────────────────────────────────────────────────
61
+ async function getConflictingFiles(cwd) {
62
+ const { stdout } = await git(["diff", "--name-only", "--diff-filter=U"], cwd);
63
+ return stdout.split("\n").map((l) => l.trim()).filter(Boolean);
64
+ }
65
+ // ─── AI conflict resolution ───────────────────────────────────────────────────
66
+ async function resolveConflictsWithAi(conflictFiles, cwd, gitx) {
67
+ const resolved = [];
68
+ const needsManual = [];
69
+ for (const filePath of conflictFiles) {
70
+ const absPath = resolvePath(cwd, filePath);
71
+ let content;
72
+ try {
73
+ content = await readFile(absPath, "utf8");
74
+ }
75
+ catch {
76
+ needsManual.push(filePath);
77
+ continue;
78
+ }
79
+ if (!content.includes("<<<<<<<")) {
80
+ needsManual.push(filePath);
81
+ continue;
82
+ }
83
+ const spinner = ora(` 🤖 AI resolving: ${filePath}`).start();
84
+ try {
85
+ const result = await gitx.ai.resolveConflict(filePath, content);
86
+ if (result.confidence === "high") {
87
+ await writeFile(absPath, result.resolved, "utf8");
88
+ spinner.succeed(` ✅ Auto-resolved: ${filePath} — ${result.explanation}`);
89
+ resolved.push(filePath);
90
+ }
91
+ else {
92
+ spinner.warn(` ⚠️ Low confidence: ${filePath} — ${result.explanation}`);
93
+ const preview = result.resolved.split("\n").slice(0, 30).join("\n");
94
+ logger.info(`\n${preview}\n`);
95
+ let apply = false;
96
+ try {
97
+ apply = await confirm({
98
+ message: `Apply AI resolution for ${filePath}?`,
99
+ default: true,
100
+ });
101
+ }
102
+ catch {
103
+ apply = false;
104
+ }
105
+ if (apply) {
106
+ await writeFile(absPath, result.resolved, "utf8");
107
+ logger.success(` ✅ Applied: ${filePath}`);
108
+ resolved.push(filePath);
109
+ }
110
+ else {
111
+ needsManual.push(filePath);
112
+ }
113
+ }
114
+ }
115
+ catch {
116
+ spinner.fail(` ❌ AI resolution failed: ${filePath} — resolve manually`);
117
+ needsManual.push(filePath);
118
+ }
119
+ }
120
+ return { resolved, needsManual };
121
+ }
122
+ async function cherryPickCommits(commits, cwd, gitx, aiAvailable) {
123
+ for (let i = 0; i < commits.length; i++) {
124
+ const { sha, subject } = commits[i];
125
+ const shortSha = sha.slice(0, 7);
126
+ logger.info(`\n 🍒 [${i + 1}/${commits.length}] ${shortSha} — ${subject}`);
127
+ const result = await git(["cherry-pick", "-x", sha], cwd);
128
+ if (result.exitCode === 0) {
129
+ logger.success(` ✓ Applied cleanly`);
130
+ continue;
131
+ }
132
+ const conflictFiles = await getConflictingFiles(cwd);
133
+ if (conflictFiles.length === 0) {
134
+ logger.warn(` ⚠️ Skipping (empty or already applied): ${shortSha}`);
135
+ await git(["cherry-pick", "--skip"], cwd);
136
+ continue;
137
+ }
138
+ logger.warn(`\n ⚡ Conflicts in ${conflictFiles.length} file(s):`);
139
+ conflictFiles.forEach((f) => logger.info(` • ${f}`));
140
+ if (!aiAvailable) {
141
+ logger.error(`\n ⛔ Manual conflict resolution needed. Aborting port to this target.`);
142
+ logger.info(` Run: git cherry-pick --abort (if needed) then fix manually.`);
143
+ await git(["cherry-pick", "--abort"], cwd);
144
+ return { status: "paused", pausedAt: commits[i] };
145
+ }
146
+ const { resolved, needsManual } = await resolveConflictsWithAi(conflictFiles, cwd, gitx);
147
+ if (needsManual.length > 0) {
148
+ if (resolved.length > 0) {
149
+ await git(["add", ...resolved], cwd);
150
+ logger.info(`\n ✅ Auto-resolved ${resolved.length} file(s) — staged.`);
151
+ }
152
+ logger.warn(`\n ⛔ ${needsManual.length} file(s) need manual resolution. Aborting port to this target.`);
153
+ needsManual.forEach((f) => logger.warn(` • ${f}`));
154
+ await git(["cherry-pick", "--abort"], cwd);
155
+ return { status: "paused", pausedAt: commits[i] };
156
+ }
157
+ await git(["add", ...resolved], cwd);
158
+ const continueResult = await git(["cherry-pick", "--continue", "--no-edit"], cwd);
159
+ if (continueResult.exitCode !== 0) {
160
+ logger.error(` ❌ Could not continue cherry-pick: ${continueResult.stderr}`);
161
+ await git(["cherry-pick", "--abort"], cwd);
162
+ return { status: "paused", pausedAt: commits[i] };
163
+ }
164
+ logger.success(` ✅ Conflict resolved and applied`);
165
+ }
166
+ return { status: "success" };
167
+ }
168
+ async function portPrToTarget(opts) {
169
+ const { prNumber, prTitle, prHeadBranch, prBaseBranch, commits, targetBranch, cwd, gitx, noPr, draft, skipConfirm, aiAvailable, originalBranch, } = opts;
170
+ const safePrNum = String(prNumber);
171
+ const safeTarget = targetBranch.replace(/\//g, "-");
172
+ const portBranch = `port/pr-${safePrNum}-to-${safeTarget}`;
173
+ logger.info(`\n${"─".repeat(60)}`);
174
+ logger.info(`🎯 Target: ${targetBranch}`);
175
+ logger.info(` Port branch: ${portBranch}`);
176
+ // ── 1. Verify target exists on origin ──────────────────────────────────────
177
+ const { stdout: remoteRefs } = await git(["ls-remote", "--heads", "origin", targetBranch], cwd);
178
+ if (!remoteRefs.trim()) {
179
+ logger.error(` ❌ Branch "${targetBranch}" does not exist on origin. Skipping.`);
180
+ return { target: targetBranch, portBranch, skipped: `branch "${targetBranch}" not found on origin` };
181
+ }
182
+ // ── 2. Check if port branch already exists ─────────────────────────────────
183
+ const portExistsRemote = (await git(["ls-remote", "--heads", "origin", portBranch], cwd)).stdout.trim().length > 0;
184
+ const portExistsLocal = (await git(["rev-parse", "--verify", portBranch], cwd)).exitCode === 0;
185
+ if (portExistsRemote || portExistsLocal) {
186
+ logger.warn(` ⚠️ Port branch "${portBranch}" already exists — deleting and recreating for a clean port.`);
187
+ if (portExistsLocal) {
188
+ // Make sure we're not on it before deleting
189
+ await git(["checkout", originalBranch], cwd);
190
+ await git(["branch", "-D", portBranch], cwd);
191
+ }
192
+ }
193
+ // ── 3. Confirmation ─────────────────────────────────────────────────────────
194
+ if (!skipConfirm) {
195
+ let proceed = false;
196
+ try {
197
+ proceed = await confirm({
198
+ message: `Port ${commits.length} commit(s) from PR #${prNumber} onto "${targetBranch}"?`,
199
+ default: true,
200
+ });
201
+ }
202
+ catch {
203
+ proceed = false;
204
+ }
205
+ if (!proceed) {
206
+ logger.info(" Skipped.");
207
+ return { target: targetBranch, portBranch, skipped: "user skipped" };
208
+ }
209
+ }
210
+ // ── 4. Create port branch from origin/<target> ────────────────────────────
211
+ const checkoutResult = await git(["checkout", "-b", portBranch, `origin/${targetBranch}`], cwd);
212
+ if (checkoutResult.exitCode !== 0) {
213
+ logger.error(` ❌ Could not create port branch: ${checkoutResult.stderr}`);
214
+ return { target: targetBranch, portBranch, skipped: `checkout failed: ${checkoutResult.stderr}` };
215
+ }
216
+ // ── 5. Cherry-pick commits ─────────────────────────────────────────────────
217
+ const pickResult = await cherryPickCommits(commits, cwd, gitx, aiAvailable);
218
+ // Return to original branch before reporting
219
+ await git(["checkout", originalBranch], cwd);
220
+ if (pickResult.status === "paused") {
221
+ return {
222
+ target: targetBranch,
223
+ portBranch,
224
+ conflictAt: pickResult.pausedAt?.sha,
225
+ skipped: `conflict at ${pickResult.pausedAt?.sha.slice(0, 7) ?? "?"} (port branch not pushed)`,
226
+ };
227
+ }
228
+ // ── 6. Push port branch ────────────────────────────────────────────────────
229
+ const pushSpinner = ora(` Pushing ${portBranch}…`).start();
230
+ const pushResult = await git(["push", "--force-with-lease", "--set-upstream", "origin", portBranch], cwd);
231
+ if (pushResult.exitCode !== 0) {
232
+ pushSpinner.fail(` Push failed: ${pushResult.stderr}`);
233
+ return { target: targetBranch, portBranch, skipped: `push failed: ${pushResult.stderr}` };
234
+ }
235
+ pushSpinner.succeed(` Pushed ${portBranch}`);
236
+ if (noPr) {
237
+ logger.success(` ✅ Port branch ready — create PR manually: ${portBranch} → ${targetBranch}`);
238
+ return { target: targetBranch, portBranch };
239
+ }
240
+ // ── 7. Create PR ────────────────────────────────────────────────────────────
241
+ let ctx;
242
+ try {
243
+ ctx = await gitx.getRepoContext();
244
+ }
245
+ catch {
246
+ logger.warn(` ⚠️ Could not get repo context for PR creation — create PR manually.`);
247
+ return { target: targetBranch, portBranch };
248
+ }
249
+ const provider = createProvider(ctx);
250
+ // Check if a PR already exists for this port branch
251
+ try {
252
+ const allPrs = await provider.listPRs(ctx.repoSlug);
253
+ const existing = allPrs.find((p) => p.head === portBranch && p.base === targetBranch && p.state === "open");
254
+ if (existing) {
255
+ logger.success(` ✅ PR already open — updated with new commits: ${existing.url}`);
256
+ return { target: targetBranch, portBranch, prUrl: existing.url };
257
+ }
258
+ }
259
+ catch { /* non-fatal */ }
260
+ const prSpinner = ora(` Creating PR: ${portBranch} → ${targetBranch}…`).start();
261
+ const portedPrTitle = `[Port PR #${prNumber} → ${targetBranch}] ${prTitle}`;
262
+ let prBody = `> 🍒 Port of PR #${prNumber} onto \`${targetBranch}\`\n` +
263
+ `> Original: \`${prHeadBranch}\` → \`${prBaseBranch}\`\n\n` +
264
+ `**${prTitle}**\n\n` +
265
+ `Cherry-picked ${commits.length} commit(s):\n` +
266
+ commits.map((c) => `- \`${c.sha.slice(0, 7)}\` ${c.subject}`).join("\n");
267
+ // Try AI-generated PR body
268
+ try {
269
+ const aiContent = await gitx.ai.generatePrContent(commits.map((c) => `${c.sha.slice(0, 7)} ${c.subject}`), "");
270
+ prBody =
271
+ `> 🍒 Port of PR #${prNumber} onto \`${targetBranch}\`\n` +
272
+ `> Original: \`${prHeadBranch}\` → \`${prBaseBranch}\`\n\n` +
273
+ aiContent.body;
274
+ }
275
+ catch { /* use manual body */ }
276
+ try {
277
+ const createdPr = await provider.createPR(ctx.repoSlug, {
278
+ title: portedPrTitle,
279
+ body: prBody,
280
+ head: portBranch,
281
+ base: targetBranch,
282
+ draft,
283
+ });
284
+ prSpinner.succeed(` PR created: ${createdPr.url}`);
285
+ return { target: targetBranch, portBranch, prUrl: createdPr.url };
286
+ }
287
+ catch (err) {
288
+ const msg = err instanceof Error ? err.message : String(err);
289
+ prSpinner.warn(` PR creation failed: ${msg}`);
290
+ logger.info(` Create manually: ${portBranch} → ${targetBranch}`);
291
+ return { target: targetBranch, portBranch };
292
+ }
293
+ }
294
+ // ─── Register command ─────────────────────────────────────────────────────────
295
+ export function registerPrPortCommand(pr) {
296
+ pr
297
+ .command("port")
298
+ .description("🚢 Port all commits from a PR onto one or more target branches and open PRs\n" +
299
+ " Example: gitx pr port 12345 release/v1 release/v2")
300
+ .argument("<number>", "PR number to port")
301
+ .argument("<targets...>", "Target branch(es) to port the PR commits onto")
302
+ .option("--no-pr", "Push port branches but skip PR creation")
303
+ .option("--draft", "Create PRs as drafts")
304
+ .option("--dry-run", "Show what would happen without making any changes")
305
+ .option("--no-confirm", "Skip the per-target confirmation prompt")
306
+ .action(async (prArg, targets, opts) => {
307
+ const cwd = process.cwd();
308
+ if (!(await isInsideGitRepo(cwd))) {
309
+ throw new GitxError("Not inside a git repository.", { exitCode: 2 });
310
+ }
311
+ const prNumber = parseInt(prArg, 10);
312
+ if (isNaN(prNumber) || prNumber <= 0) {
313
+ throw new GitxError(`Invalid PR number: "${prArg}"`, { exitCode: 2 });
314
+ }
315
+ if (targets.length === 0) {
316
+ throw new GitxError("Specify at least one target branch.\n Example: gitx pr port 12345 release/v1 release/v2", { exitCode: 2 });
317
+ }
318
+ const gitx = await Gitx.fromCwd(cwd);
319
+ // ── Fetch PR metadata ──────────────────────────────────────────────────
320
+ let ctx;
321
+ try {
322
+ ctx = await gitx.getRepoContext();
323
+ }
324
+ catch (err) {
325
+ throw new GitxError(`Could not determine repo context: ${err instanceof Error ? err.message : String(err)}`, { exitCode: 2 });
326
+ }
327
+ const provider = createProvider(ctx);
328
+ const prSpinner = ora(`Fetching PR #${prNumber}…`).start();
329
+ let prData;
330
+ try {
331
+ prData = await provider.getPR(ctx.repoSlug, prNumber);
332
+ prSpinner.succeed(`PR #${prNumber}: "${prData.title}" (${prData.head} → ${prData.base})`);
333
+ }
334
+ catch (err) {
335
+ prSpinner.fail(`Could not fetch PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}`);
336
+ process.exitCode = 1;
337
+ return;
338
+ }
339
+ // ── Fetch the PR's source branch ───────────────────────────────────────
340
+ const fetchSpinner = ora(`Fetching origin/${prData.head}…`).start();
341
+ const fetchResult = await git(["fetch", "origin", prData.head], cwd);
342
+ if (fetchResult.exitCode !== 0) {
343
+ // Try GitHub's PR ref (works for merged/deleted branches too)
344
+ const refResult = await git(["fetch", "origin", `refs/pull/${prNumber}/head:refs/remotes/origin/pr/${prNumber}`], cwd);
345
+ if (refResult.exitCode !== 0) {
346
+ fetchSpinner.warn(`Could not fetch "${prData.head}" — it may have been deleted. Trying with local refs.`);
347
+ }
348
+ else {
349
+ fetchSpinner.succeed(`Fetched PR #${prNumber} via refs/pull/${prNumber}/head`);
350
+ }
351
+ }
352
+ else {
353
+ fetchSpinner.succeed(`Fetched origin/${prData.head}`);
354
+ }
355
+ // ── Collect commits from the PR ────────────────────────────────────────
356
+ const headRef = (await git(["rev-parse", "--verify", `origin/${prData.head}`], cwd)).exitCode === 0
357
+ ? `origin/${prData.head}`
358
+ : `origin/pr/${prNumber}`;
359
+ const baseRef = `origin/${prData.base}`;
360
+ const logResult = await git(["log", "--format=%H %s", `${baseRef}..${headRef}`], cwd);
361
+ if (logResult.exitCode !== 0 || !logResult.stdout.trim()) {
362
+ logger.error(`❌ No commits found between ${baseRef} and ${headRef}.`);
363
+ logger.info(` Make sure "${prData.base}" exists on origin.`);
364
+ process.exitCode = 1;
365
+ return;
366
+ }
367
+ // git log is newest-first; reverse to oldest-first
368
+ const commits = logResult.stdout
369
+ .split("\n")
370
+ .map((l) => l.trim())
371
+ .filter(Boolean)
372
+ .map((line) => {
373
+ const spaceIdx = line.indexOf(" ");
374
+ return { sha: line.slice(0, spaceIdx), subject: line.slice(spaceIdx + 1) };
375
+ })
376
+ .reverse();
377
+ if (commits.length === 0) {
378
+ logger.info(`✅ PR #${prNumber} has no commits relative to "${prData.base}". Nothing to port.`);
379
+ return;
380
+ }
381
+ // ── Summary ────────────────────────────────────────────────────────────
382
+ logger.info(`\n🚢 gitx pr port`);
383
+ logger.info(` PR: #${prNumber} — ${prData.title}`);
384
+ logger.info(` Source: ${prData.head} → ${prData.base}`);
385
+ logger.info(` Commits: ${commits.length}`);
386
+ logger.info(` Targets: ${targets.join(", ")}`);
387
+ if (opts.dryRun) {
388
+ logger.info(`\n📋 Commits that would be ported:\n`);
389
+ for (const { sha, subject } of commits) {
390
+ logger.info(` + ${sha.slice(0, 7)} — ${subject}`);
391
+ }
392
+ logger.info(`\n⏸ Dry run — no changes made.`);
393
+ return;
394
+ }
395
+ const aiAvailable = await Gitx.isAiAvailable(gitx.config);
396
+ if (!aiAvailable) {
397
+ logger.warn("⚠️ No AI configured — conflicts will require manual intervention (target will be skipped).");
398
+ }
399
+ const originalBranch = await getCurrentBranch(cwd);
400
+ // ── Port to each target ────────────────────────────────────────────────
401
+ const results = [];
402
+ for (const targetBranch of targets) {
403
+ const result = await portPrToTarget({
404
+ prNumber,
405
+ prTitle: prData.title,
406
+ prHeadBranch: prData.head,
407
+ prBaseBranch: prData.base,
408
+ commits,
409
+ targetBranch,
410
+ cwd,
411
+ gitx,
412
+ noPr: opts.pr === false,
413
+ draft: opts.draft,
414
+ skipConfirm: opts.confirm === false,
415
+ aiAvailable,
416
+ originalBranch,
417
+ });
418
+ results.push(result);
419
+ // Always return to original branch between targets
420
+ const { stdout: cur } = await git(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
421
+ if (cur !== originalBranch) {
422
+ await git(["checkout", originalBranch], cwd);
423
+ }
424
+ }
425
+ // ── Final summary ──────────────────────────────────────────────────────
426
+ logger.info(`\n${"─".repeat(60)}`);
427
+ logger.info(`\n📋 gitx pr port summary — PR #${prNumber}\n`);
428
+ const succeeded = results.filter((r) => r.prUrl);
429
+ const pushed = results.filter((r) => !r.prUrl && !r.skipped);
430
+ const skipped = results.filter((r) => r.skipped);
431
+ if (succeeded.length > 0) {
432
+ logger.success(`✅ PRs created (${succeeded.length}):`);
433
+ for (const r of succeeded) {
434
+ logger.info(` ${r.target}`);
435
+ logger.info(` Branch: ${r.portBranch}`);
436
+ logger.info(` PR: ${r.prUrl}`);
437
+ }
438
+ }
439
+ if (pushed.length > 0) {
440
+ logger.info(`\n🔀 Pushed (no PR created, ${pushed.length}):`);
441
+ for (const r of pushed) {
442
+ logger.info(` ${r.portBranch} → create PR manually to ${r.target}`);
443
+ }
444
+ }
445
+ if (skipped.length > 0) {
446
+ logger.warn(`\n⚠️ Skipped (${skipped.length}):`);
447
+ for (const r of skipped) {
448
+ logger.warn(` ${r.target}: ${r.skipped}`);
449
+ }
450
+ }
451
+ });
452
+ }
453
+ //# sourceMappingURL=port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port.js","sourceRoot":"","sources":["../../../../src/cli/commands/pr/port.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gitx pr port <number> <target1> [target2...]\n *\n * Ports all commits from a PR onto one or more target branches, then opens\n * a new PR for each target — without touching your current working branch.\n *\n * Example:\n * gitx pr port 12345 release/v1 release/v2\n *\n * → Creates port/pr-12345-to-release-v1 from origin/release/v1\n * port/pr-12345-to-release-v2 from origin/release/v2\n * → Cherry-picks all commits from PR #12345 onto each port branch\n * → Pushes both port branches\n * → Opens a PR for each: port branch → target branch\n * → Prints the PR URLs\n *\n * Flow (per target branch):\n * 1. Fetch PR metadata → source branch (head) + base branch\n * 2. git fetch origin <head> — ensure source commits are local\n * 3. Collect commits: origin/<base>..origin/<head> (oldest → newest)\n * 4. Create port/pr-<number>-to-<target> from origin/<target>\n * 5. Cherry-pick each commit with -x; AI resolves conflicts where possible\n * 6. Push port branch\n * 7. Create PR: port branch → target branch; print URL\n *\n * Options:\n * --no-pr Push port branches but skip PR creation\n * --draft Create PRs as drafts\n * --dry-run Show what would happen without making any changes\n * --no-confirm Skip the per-target confirmation prompt\n */\n\nimport type { Command } from \"commander\";\nimport ora from \"ora\";\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { resolve as resolvePath } from \"node:path\";\nimport { confirm } from \"@inquirer/prompts\";\nimport { logger } from \"../../../logger/logger.js\";\nimport { isInsideGitRepo } from \"../../../utils/git.js\";\nimport { getCurrentBranch } from \"../../../utils/gitOps.js\";\nimport { GitxError } from \"../../../utils/errors.js\";\nimport { Gitx } from \"../../../core/gitx.js\";\nimport { createProvider } from \"../../../providers/factory.js\";\n\nconst execFileAsync = promisify(execFile);\n\n// ─── Git helper ───────────────────────────────────────────────────────────────\n\nasync function git(\n args: string[],\n cwd: string\n): Promise<{ stdout: string; stderr: string; exitCode: number }> {\n try {\n const result = await execFileAsync(\"git\", args, { cwd });\n return { stdout: result.stdout.trim(), stderr: \"\", exitCode: 0 };\n } catch (err: unknown) {\n const e = err as { stdout?: string; stderr?: string; code?: number };\n return {\n stdout: e.stdout?.trim() ?? \"\",\n stderr: e.stderr?.trim() ?? \"\",\n exitCode: e.code ?? 1,\n };\n }\n}\n\n// ─── Conflict helpers ─────────────────────────────────────────────────────────\n\nasync function getConflictingFiles(cwd: string): Promise<string[]> {\n const { stdout } = await git([\"diff\", \"--name-only\", \"--diff-filter=U\"], cwd);\n return stdout.split(\"\\n\").map((l) => l.trim()).filter(Boolean);\n}\n\n// ─── AI conflict resolution ───────────────────────────────────────────────────\n\nasync function resolveConflictsWithAi(\n conflictFiles: string[],\n cwd: string,\n gitx: Gitx\n): Promise<{ resolved: string[]; needsManual: string[] }> {\n const resolved: string[] = [];\n const needsManual: string[] = [];\n\n for (const filePath of conflictFiles) {\n const absPath = resolvePath(cwd, filePath);\n let content: string;\n try {\n content = await readFile(absPath, \"utf8\");\n } catch {\n needsManual.push(filePath);\n continue;\n }\n\n if (!content.includes(\"<<<<<<<\")) {\n needsManual.push(filePath);\n continue;\n }\n\n const spinner = ora(` 🤖 AI resolving: ${filePath}`).start();\n try {\n const result = await gitx.ai.resolveConflict(filePath, content);\n\n if (result.confidence === \"high\") {\n await writeFile(absPath, result.resolved, \"utf8\");\n spinner.succeed(` ✅ Auto-resolved: ${filePath} — ${result.explanation}`);\n resolved.push(filePath);\n } else {\n spinner.warn(` ⚠️ Low confidence: ${filePath} — ${result.explanation}`);\n const preview = result.resolved.split(\"\\n\").slice(0, 30).join(\"\\n\");\n logger.info(`\\n${preview}\\n`);\n\n let apply = false;\n try {\n apply = await confirm({\n message: `Apply AI resolution for ${filePath}?`,\n default: true,\n });\n } catch {\n apply = false;\n }\n\n if (apply) {\n await writeFile(absPath, result.resolved, \"utf8\");\n logger.success(` ✅ Applied: ${filePath}`);\n resolved.push(filePath);\n } else {\n needsManual.push(filePath);\n }\n }\n } catch {\n spinner.fail(` ❌ AI resolution failed: ${filePath} — resolve manually`);\n needsManual.push(filePath);\n }\n }\n\n return { resolved, needsManual };\n}\n\n// ─── Cherry-pick loop ─────────────────────────────────────────────────────────\n\ninterface Commit {\n sha: string;\n subject: string;\n}\n\ninterface CherryPickResult {\n status: \"success\" | \"paused\";\n pausedAt?: Commit;\n}\n\nasync function cherryPickCommits(\n commits: Commit[],\n cwd: string,\n gitx: Gitx,\n aiAvailable: boolean\n): Promise<CherryPickResult> {\n for (let i = 0; i < commits.length; i++) {\n const { sha, subject } = commits[i]!;\n const shortSha = sha.slice(0, 7);\n\n logger.info(`\\n 🍒 [${i + 1}/${commits.length}] ${shortSha} — ${subject}`);\n\n const result = await git([\"cherry-pick\", \"-x\", sha], cwd);\n\n if (result.exitCode === 0) {\n logger.success(` ✓ Applied cleanly`);\n continue;\n }\n\n const conflictFiles = await getConflictingFiles(cwd);\n\n if (conflictFiles.length === 0) {\n logger.warn(` ⚠️ Skipping (empty or already applied): ${shortSha}`);\n await git([\"cherry-pick\", \"--skip\"], cwd);\n continue;\n }\n\n logger.warn(`\\n ⚡ Conflicts in ${conflictFiles.length} file(s):`);\n conflictFiles.forEach((f) => logger.info(` • ${f}`));\n\n if (!aiAvailable) {\n logger.error(`\\n ⛔ Manual conflict resolution needed. Aborting port to this target.`);\n logger.info(` Run: git cherry-pick --abort (if needed) then fix manually.`);\n await git([\"cherry-pick\", \"--abort\"], cwd);\n return { status: \"paused\", pausedAt: commits[i] };\n }\n\n const { resolved, needsManual } = await resolveConflictsWithAi(conflictFiles, cwd, gitx);\n\n if (needsManual.length > 0) {\n if (resolved.length > 0) {\n await git([\"add\", ...resolved], cwd);\n logger.info(`\\n ✅ Auto-resolved ${resolved.length} file(s) — staged.`);\n }\n logger.warn(`\\n ⛔ ${needsManual.length} file(s) need manual resolution. Aborting port to this target.`);\n needsManual.forEach((f) => logger.warn(` • ${f}`));\n await git([\"cherry-pick\", \"--abort\"], cwd);\n return { status: \"paused\", pausedAt: commits[i] };\n }\n\n await git([\"add\", ...resolved], cwd);\n const continueResult = await git([\"cherry-pick\", \"--continue\", \"--no-edit\"], cwd);\n if (continueResult.exitCode !== 0) {\n logger.error(` ❌ Could not continue cherry-pick: ${continueResult.stderr}`);\n await git([\"cherry-pick\", \"--abort\"], cwd);\n return { status: \"paused\", pausedAt: commits[i] };\n }\n\n logger.success(` ✅ Conflict resolved and applied`);\n }\n\n return { status: \"success\" };\n}\n\n// ─── Port to a single target branch ──────────────────────────────────────────\n\ninterface PortResult {\n target: string;\n portBranch: string;\n prUrl?: string;\n skipped?: string; // reason why this target was skipped\n conflictAt?: string; // SHA of commit that caused an unresolvable conflict\n}\n\nasync function portPrToTarget(opts: {\n prNumber: number;\n prTitle: string;\n prHeadBranch: string;\n prBaseBranch: string;\n commits: Commit[];\n targetBranch: string;\n cwd: string;\n gitx: Gitx;\n noPr: boolean;\n draft: boolean;\n skipConfirm: boolean;\n aiAvailable: boolean;\n originalBranch: string;\n}): Promise<PortResult> {\n const {\n prNumber, prTitle, prHeadBranch, prBaseBranch,\n commits, targetBranch, cwd, gitx,\n noPr, draft, skipConfirm, aiAvailable, originalBranch,\n } = opts;\n\n const safePrNum = String(prNumber);\n const safeTarget = targetBranch.replace(/\\//g, \"-\");\n const portBranch = `port/pr-${safePrNum}-to-${safeTarget}`;\n\n logger.info(`\\n${\"─\".repeat(60)}`);\n logger.info(`🎯 Target: ${targetBranch}`);\n logger.info(` Port branch: ${portBranch}`);\n\n // ── 1. Verify target exists on origin ──────────────────────────────────────\n const { stdout: remoteRefs } = await git(\n [\"ls-remote\", \"--heads\", \"origin\", targetBranch],\n cwd\n );\n if (!remoteRefs.trim()) {\n logger.error(` ❌ Branch \"${targetBranch}\" does not exist on origin. Skipping.`);\n return { target: targetBranch, portBranch, skipped: `branch \"${targetBranch}\" not found on origin` };\n }\n\n // ── 2. Check if port branch already exists ─────────────────────────────────\n const portExistsRemote = (await git(\n [\"ls-remote\", \"--heads\", \"origin\", portBranch], cwd\n )).stdout.trim().length > 0;\n\n const portExistsLocal = (await git(\n [\"rev-parse\", \"--verify\", portBranch], cwd\n )).exitCode === 0;\n\n if (portExistsRemote || portExistsLocal) {\n logger.warn(` ⚠️ Port branch \"${portBranch}\" already exists — deleting and recreating for a clean port.`);\n if (portExistsLocal) {\n // Make sure we're not on it before deleting\n await git([\"checkout\", originalBranch], cwd);\n await git([\"branch\", \"-D\", portBranch], cwd);\n }\n }\n\n // ── 3. Confirmation ─────────────────────────────────────────────────────────\n if (!skipConfirm) {\n let proceed = false;\n try {\n proceed = await confirm({\n message: `Port ${commits.length} commit(s) from PR #${prNumber} onto \"${targetBranch}\"?`,\n default: true,\n });\n } catch {\n proceed = false;\n }\n if (!proceed) {\n logger.info(\" Skipped.\");\n return { target: targetBranch, portBranch, skipped: \"user skipped\" };\n }\n }\n\n // ── 4. Create port branch from origin/<target> ────────────────────────────\n const checkoutResult = await git(\n [\"checkout\", \"-b\", portBranch, `origin/${targetBranch}`],\n cwd\n );\n if (checkoutResult.exitCode !== 0) {\n logger.error(` ❌ Could not create port branch: ${checkoutResult.stderr}`);\n return { target: targetBranch, portBranch, skipped: `checkout failed: ${checkoutResult.stderr}` };\n }\n\n // ── 5. Cherry-pick commits ─────────────────────────────────────────────────\n const pickResult = await cherryPickCommits(commits, cwd, gitx, aiAvailable);\n\n // Return to original branch before reporting\n await git([\"checkout\", originalBranch], cwd);\n\n if (pickResult.status === \"paused\") {\n return {\n target: targetBranch,\n portBranch,\n conflictAt: pickResult.pausedAt?.sha,\n skipped: `conflict at ${pickResult.pausedAt?.sha.slice(0, 7) ?? \"?\"} (port branch not pushed)`,\n };\n }\n\n // ── 6. Push port branch ────────────────────────────────────────────────────\n const pushSpinner = ora(` Pushing ${portBranch}…`).start();\n const pushResult = await git(\n [\"push\", \"--force-with-lease\", \"--set-upstream\", \"origin\", portBranch],\n cwd\n );\n if (pushResult.exitCode !== 0) {\n pushSpinner.fail(` Push failed: ${pushResult.stderr}`);\n return { target: targetBranch, portBranch, skipped: `push failed: ${pushResult.stderr}` };\n }\n pushSpinner.succeed(` Pushed ${portBranch}`);\n\n if (noPr) {\n logger.success(` ✅ Port branch ready — create PR manually: ${portBranch} → ${targetBranch}`);\n return { target: targetBranch, portBranch };\n }\n\n // ── 7. Create PR ────────────────────────────────────────────────────────────\n let ctx;\n try {\n ctx = await gitx.getRepoContext();\n } catch {\n logger.warn(` ⚠️ Could not get repo context for PR creation — create PR manually.`);\n return { target: targetBranch, portBranch };\n }\n\n const provider = createProvider(ctx);\n\n // Check if a PR already exists for this port branch\n try {\n const allPrs = await provider.listPRs(ctx.repoSlug);\n const existing = allPrs.find(\n (p) => p.head === portBranch && p.base === targetBranch && p.state === \"open\"\n );\n if (existing) {\n logger.success(` ✅ PR already open — updated with new commits: ${existing.url}`);\n return { target: targetBranch, portBranch, prUrl: existing.url };\n }\n } catch { /* non-fatal */ }\n\n const prSpinner = ora(` Creating PR: ${portBranch} → ${targetBranch}…`).start();\n\n const portedPrTitle = `[Port PR #${prNumber} → ${targetBranch}] ${prTitle}`;\n let prBody =\n `> 🍒 Port of PR #${prNumber} onto \\`${targetBranch}\\`\\n` +\n `> Original: \\`${prHeadBranch}\\` → \\`${prBaseBranch}\\`\\n\\n` +\n `**${prTitle}**\\n\\n` +\n `Cherry-picked ${commits.length} commit(s):\\n` +\n commits.map((c) => `- \\`${c.sha.slice(0, 7)}\\` ${c.subject}`).join(\"\\n\");\n\n // Try AI-generated PR body\n try {\n const aiContent = await gitx.ai.generatePrContent(\n commits.map((c) => `${c.sha.slice(0, 7)} ${c.subject}`),\n \"\", // no diff available in this context\n );\n prBody =\n `> 🍒 Port of PR #${prNumber} onto \\`${targetBranch}\\`\\n` +\n `> Original: \\`${prHeadBranch}\\` → \\`${prBaseBranch}\\`\\n\\n` +\n aiContent.body;\n } catch { /* use manual body */ }\n\n try {\n const createdPr = await provider.createPR(ctx.repoSlug, {\n title: portedPrTitle,\n body: prBody,\n head: portBranch,\n base: targetBranch,\n draft,\n });\n prSpinner.succeed(` PR created: ${createdPr.url}`);\n return { target: targetBranch, portBranch, prUrl: createdPr.url };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n prSpinner.warn(` PR creation failed: ${msg}`);\n logger.info(` Create manually: ${portBranch} → ${targetBranch}`);\n return { target: targetBranch, portBranch };\n }\n}\n\n// ─── Register command ─────────────────────────────────────────────────────────\n\nexport function registerPrPortCommand(pr: Command): void {\n pr\n .command(\"port\")\n .description(\n \"🚢 Port all commits from a PR onto one or more target branches and open PRs\\n\" +\n \" Example: gitx pr port 12345 release/v1 release/v2\"\n )\n .argument(\"<number>\", \"PR number to port\")\n .argument(\"<targets...>\", \"Target branch(es) to port the PR commits onto\")\n .option(\"--no-pr\", \"Push port branches but skip PR creation\")\n .option(\"--draft\", \"Create PRs as drafts\")\n .option(\"--dry-run\", \"Show what would happen without making any changes\")\n .option(\"--no-confirm\", \"Skip the per-target confirmation prompt\")\n .action(async (\n prArg: string,\n targets: string[],\n opts: { pr: boolean; draft: boolean; dryRun?: boolean; confirm: boolean }\n ) => {\n const cwd = process.cwd();\n\n if (!(await isInsideGitRepo(cwd))) {\n throw new GitxError(\"Not inside a git repository.\", { exitCode: 2 });\n }\n\n const prNumber = parseInt(prArg, 10);\n if (isNaN(prNumber) || prNumber <= 0) {\n throw new GitxError(`Invalid PR number: \"${prArg}\"`, { exitCode: 2 });\n }\n\n if (targets.length === 0) {\n throw new GitxError(\n \"Specify at least one target branch.\\n Example: gitx pr port 12345 release/v1 release/v2\",\n { exitCode: 2 }\n );\n }\n\n const gitx = await Gitx.fromCwd(cwd);\n\n // ── Fetch PR metadata ──────────────────────────────────────────────────\n let ctx;\n try {\n ctx = await gitx.getRepoContext();\n } catch (err: unknown) {\n throw new GitxError(\n `Could not determine repo context: ${err instanceof Error ? err.message : String(err)}`,\n { exitCode: 2 }\n );\n }\n\n const provider = createProvider(ctx);\n\n const prSpinner = ora(`Fetching PR #${prNumber}…`).start();\n let prData;\n try {\n prData = await provider.getPR(ctx.repoSlug, prNumber);\n prSpinner.succeed(`PR #${prNumber}: \"${prData.title}\" (${prData.head} → ${prData.base})`);\n } catch (err: unknown) {\n prSpinner.fail(`Could not fetch PR #${prNumber}: ${err instanceof Error ? err.message : String(err)}`);\n process.exitCode = 1;\n return;\n }\n\n // ── Fetch the PR's source branch ───────────────────────────────────────\n const fetchSpinner = ora(`Fetching origin/${prData.head}…`).start();\n const fetchResult = await git([\"fetch\", \"origin\", prData.head], cwd);\n if (fetchResult.exitCode !== 0) {\n // Try GitHub's PR ref (works for merged/deleted branches too)\n const refResult = await git(\n [\"fetch\", \"origin\", `refs/pull/${prNumber}/head:refs/remotes/origin/pr/${prNumber}`],\n cwd\n );\n if (refResult.exitCode !== 0) {\n fetchSpinner.warn(`Could not fetch \"${prData.head}\" — it may have been deleted. Trying with local refs.`);\n } else {\n fetchSpinner.succeed(`Fetched PR #${prNumber} via refs/pull/${prNumber}/head`);\n }\n } else {\n fetchSpinner.succeed(`Fetched origin/${prData.head}`);\n }\n\n // ── Collect commits from the PR ────────────────────────────────────────\n const headRef =\n (await git([\"rev-parse\", \"--verify\", `origin/${prData.head}`], cwd)).exitCode === 0\n ? `origin/${prData.head}`\n : `origin/pr/${prNumber}`;\n\n const baseRef = `origin/${prData.base}`;\n\n const logResult = await git(\n [\"log\", \"--format=%H %s\", `${baseRef}..${headRef}`],\n cwd\n );\n\n if (logResult.exitCode !== 0 || !logResult.stdout.trim()) {\n logger.error(`❌ No commits found between ${baseRef} and ${headRef}.`);\n logger.info(` Make sure \"${prData.base}\" exists on origin.`);\n process.exitCode = 1;\n return;\n }\n\n // git log is newest-first; reverse to oldest-first\n const commits: Commit[] = logResult.stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter(Boolean)\n .map((line) => {\n const spaceIdx = line.indexOf(\" \");\n return { sha: line.slice(0, spaceIdx), subject: line.slice(spaceIdx + 1) };\n })\n .reverse();\n\n if (commits.length === 0) {\n logger.info(`✅ PR #${prNumber} has no commits relative to \"${prData.base}\". Nothing to port.`);\n return;\n }\n\n // ── Summary ────────────────────────────────────────────────────────────\n logger.info(`\\n🚢 gitx pr port`);\n logger.info(` PR: #${prNumber} — ${prData.title}`);\n logger.info(` Source: ${prData.head} → ${prData.base}`);\n logger.info(` Commits: ${commits.length}`);\n logger.info(` Targets: ${targets.join(\", \")}`);\n\n if (opts.dryRun) {\n logger.info(`\\n📋 Commits that would be ported:\\n`);\n for (const { sha, subject } of commits) {\n logger.info(` + ${sha.slice(0, 7)} — ${subject}`);\n }\n logger.info(`\\n⏸ Dry run — no changes made.`);\n return;\n }\n\n const aiAvailable = await Gitx.isAiAvailable(gitx.config);\n if (!aiAvailable) {\n logger.warn(\"⚠️ No AI configured — conflicts will require manual intervention (target will be skipped).\");\n }\n\n const originalBranch = await getCurrentBranch(cwd);\n\n // ── Port to each target ────────────────────────────────────────────────\n const results: PortResult[] = [];\n for (const targetBranch of targets) {\n const result = await portPrToTarget({\n prNumber,\n prTitle: prData.title,\n prHeadBranch: prData.head,\n prBaseBranch: prData.base,\n commits,\n targetBranch,\n cwd,\n gitx,\n noPr: opts.pr === false,\n draft: opts.draft,\n skipConfirm: opts.confirm === false,\n aiAvailable,\n originalBranch,\n });\n results.push(result);\n\n // Always return to original branch between targets\n const { stdout: cur } = await git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], cwd);\n if (cur !== originalBranch) {\n await git([\"checkout\", originalBranch], cwd);\n }\n }\n\n // ── Final summary ──────────────────────────────────────────────────────\n logger.info(`\\n${\"─\".repeat(60)}`);\n logger.info(`\\n📋 gitx pr port summary — PR #${prNumber}\\n`);\n\n const succeeded = results.filter((r) => r.prUrl);\n const pushed = results.filter((r) => !r.prUrl && !r.skipped);\n const skipped = results.filter((r) => r.skipped);\n\n if (succeeded.length > 0) {\n logger.success(`✅ PRs created (${succeeded.length}):`);\n for (const r of succeeded) {\n logger.info(` ${r.target}`);\n logger.info(` Branch: ${r.portBranch}`);\n logger.info(` PR: ${r.prUrl}`);\n }\n }\n\n if (pushed.length > 0) {\n logger.info(`\\n🔀 Pushed (no PR created, ${pushed.length}):`);\n for (const r of pushed) {\n logger.info(` ${r.portBranch} → create PR manually to ${r.target}`);\n }\n }\n\n if (skipped.length > 0) {\n logger.warn(`\\n⚠️ Skipped (${skipped.length}):`);\n for (const r of skipped) {\n logger.warn(` ${r.target}: ${r.skipped}`);\n }\n }\n });\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerPrResolveCommand(pr: Command): void;
3
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/pr/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOzC,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CA+F1D"}
@@ -0,0 +1,92 @@
1
+ import inquirer from "inquirer";
2
+ import { logger } from "../../../logger/logger.js";
3
+ import { Gitx } from "../../../core/gitx.js";
4
+ import { runFixCommentsWorkflow } from "../../../workflows/pr.js";
5
+ import { pushBranch, getCurrentBranch, isWorkingTreeDirty } from "../../../utils/gitOps.js";
6
+ export function registerPrResolveCommand(pr) {
7
+ pr.command("resolve")
8
+ .description("🔧 AI-resolve review comments: applies code fixes, commits, and pushes")
9
+ .argument("<id>", "Pull request number")
10
+ .option("--dry-run", "Preview fixes without applying or committing", false)
11
+ .option("--no-commit", "Apply fixes to working tree only — skip commit and push", false)
12
+ .option("--no-push", "Apply & commit locally but skip push", false)
13
+ .action(async (id, options) => {
14
+ const prNumber = parseInt(id, 10);
15
+ if (isNaN(prNumber) || prNumber <= 0) {
16
+ logger.error(`Invalid PR number: ${id}`);
17
+ process.exit(1);
18
+ }
19
+ // Commander sets options.commit = false when --no-commit is passed
20
+ const noCommit = options.commit === false;
21
+ const gitx = await Gitx.fromCwd();
22
+ const ctx = await gitx.getRepoContext();
23
+ logger.info(`🔧 Resolving review comments on PR #${prNumber} (${ctx.repoSlug})…\n`);
24
+ // ── AI availability check ─────────────────────────────────────────────
25
+ if (!await Gitx.isAiAvailable(gitx.config)) {
26
+ logger.warn("⚠️ No AI provider configured — cannot generate fixes.\n" +
27
+ " Run `gitx config` to set up an AI provider (Claude, OpenAI, or claude-cli).");
28
+ return;
29
+ }
30
+ // ── Confirmation prompt ───────────────────────────────────────────────
31
+ if (!options.dryRun) {
32
+ const modeDesc = noCommit
33
+ ? "apply fixes to your working tree only (no commit or push)"
34
+ : options.push === false
35
+ ? "apply fixes and commit locally (no push)"
36
+ : "apply fixes, commit, and push";
37
+ const { proceed } = await inquirer.prompt([
38
+ {
39
+ type: "confirm",
40
+ name: "proceed",
41
+ message: `This will ${modeDesc}. Continue?`,
42
+ default: false,
43
+ },
44
+ ]);
45
+ if (!proceed) {
46
+ logger.warn("Cancelled.");
47
+ return;
48
+ }
49
+ }
50
+ // ── Guard: warn about uncommitted changes ─────────────────────────────
51
+ if (!options.dryRun) {
52
+ const dirty = await isWorkingTreeDirty(gitx.cwd);
53
+ if (dirty) {
54
+ logger.warn("⚠️ You have uncommitted changes. They may conflict with applied fixes.");
55
+ const { cont } = await inquirer.prompt([
56
+ { type: "confirm", name: "cont", message: "Continue anyway?", default: false },
57
+ ]);
58
+ if (!cont) {
59
+ logger.warn("Cancelled.");
60
+ return;
61
+ }
62
+ }
63
+ }
64
+ const result = await runFixCommentsWorkflow(gitx, prNumber, options.dryRun, noCommit);
65
+ logger.info(`\n📋 PR: ${result.pr.title}`);
66
+ if (result.appliedFixes.length === 0 && result.skippedFixes.length === 0) {
67
+ logger.info("No actionable review comments found.");
68
+ return;
69
+ }
70
+ if (result.appliedFixes.length > 0) {
71
+ logger.success(`\n✅ Applied ${result.appliedFixes.length} fix(es):`);
72
+ result.appliedFixes.forEach((f) => logger.info(` • ${f.path} — ${f.rationale}`));
73
+ }
74
+ if (result.skippedFixes.length > 0) {
75
+ logger.warn(`\n⚠️ Skipped ${result.skippedFixes.length} fix(es):`);
76
+ result.skippedFixes.forEach((f) => logger.warn(` • ${f.path}: ${f.reason}`));
77
+ }
78
+ // ── Push if applicable ────────────────────────────────────────────────
79
+ if (!options.dryRun && !noCommit && options.push !== false && result.appliedFixes.length > 0) {
80
+ const branch = await getCurrentBranch(gitx.cwd);
81
+ logger.info(`\n🚀 Pushing ${branch}…`);
82
+ try {
83
+ await pushBranch(branch, gitx.cwd);
84
+ logger.success("Branch pushed.");
85
+ }
86
+ catch (err) {
87
+ logger.warn(`Push failed: ${String(err.message ?? err)}`);
88
+ }
89
+ }
90
+ });
91
+ }
92
+ //# sourceMappingURL=resolve.js.map