@solaqua/gji 0.8.0 → 0.9.0

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 (53) hide show
  1. package/README.md +24 -30
  2. package/dist/browser.d.ts +7 -0
  3. package/dist/browser.js +37 -0
  4. package/dist/clean.js +34 -19
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +59 -4
  7. package/dist/doctor.d.ts +28 -0
  8. package/dist/doctor.js +454 -0
  9. package/dist/gji-bundle.mjs +2514 -378
  10. package/dist/go.d.ts +2 -1
  11. package/dist/go.js +4 -1
  12. package/dist/index.js +12 -6
  13. package/dist/init.d.ts +15 -0
  14. package/dist/init.js +232 -16
  15. package/dist/open.d.ts +2 -1
  16. package/dist/open.js +2 -1
  17. package/dist/pr-open.d.ts +19 -0
  18. package/dist/pr-open.js +290 -0
  19. package/dist/pull-requests.d.ts +33 -0
  20. package/dist/pull-requests.js +384 -0
  21. package/dist/remove.js +2 -2
  22. package/dist/repo-registry.d.ts +5 -0
  23. package/dist/repo-registry.js +87 -14
  24. package/dist/shell-completion.js +278 -10
  25. package/dist/shell-setup.d.ts +9 -0
  26. package/dist/shell-setup.js +56 -0
  27. package/dist/warp.d.ts +3 -0
  28. package/dist/warp.js +8 -3
  29. package/dist/worktree-management.d.ts +2 -1
  30. package/dist/worktree-management.js +23 -7
  31. package/dist/worktree-picker.d.ts +12 -1
  32. package/dist/worktree-picker.js +84 -6
  33. package/man/man1/gji-back.1 +1 -1
  34. package/man/man1/gji-clean.1 +1 -1
  35. package/man/man1/gji-completion.1 +1 -1
  36. package/man/man1/gji-config.1 +1 -1
  37. package/man/man1/gji-doctor.1 +19 -0
  38. package/man/man1/gji-go.1 +1 -1
  39. package/man/man1/gji-history.1 +1 -1
  40. package/man/man1/gji-init.1 +6 -3
  41. package/man/man1/gji-ls.1 +1 -1
  42. package/man/man1/gji-new.1 +1 -1
  43. package/man/man1/gji-open.1 +1 -1
  44. package/man/man1/gji-pr.1 +5 -1
  45. package/man/man1/gji-remove.1 +1 -1
  46. package/man/man1/gji-root.1 +1 -1
  47. package/man/man1/gji-run-hook.1 +1 -1
  48. package/man/man1/gji-status.1 +1 -1
  49. package/man/man1/gji-sync-files.1 +1 -1
  50. package/man/man1/gji-sync.1 +1 -1
  51. package/man/man1/gji-warp.1 +1 -1
  52. package/man/man1/gji.1 +6 -2
  53. package/package.json +1 -1
@@ -3462,10 +3462,10 @@ var require_commander = __commonJS({
3462
3462
  // node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/polyfills.js
3463
3463
  var require_polyfills = __commonJS({
3464
3464
  "node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/polyfills.js"(exports, module) {
3465
- var constants2 = __require("constants");
3465
+ var constants6 = __require("constants");
3466
3466
  var origCwd = process.cwd;
3467
3467
  var cwd = null;
3468
- var platform2 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
3468
+ var platform3 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
3469
3469
  process.cwd = function() {
3470
3470
  if (!cwd)
3471
3471
  cwd = origCwd.call(process);
@@ -3486,7 +3486,7 @@ var require_polyfills = __commonJS({
3486
3486
  var chdir;
3487
3487
  module.exports = patch;
3488
3488
  function patch(fs5) {
3489
- if (constants2.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
3489
+ if (constants6.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
3490
3490
  patchLchmod(fs5);
3491
3491
  }
3492
3492
  if (!fs5.lutimes) {
@@ -3524,9 +3524,9 @@ var require_polyfills = __commonJS({
3524
3524
  fs5.lchownSync = function() {
3525
3525
  };
3526
3526
  }
3527
- if (platform2 === "win32") {
3527
+ if (platform3 === "win32") {
3528
3528
  fs5.rename = typeof fs5.rename !== "function" ? fs5.rename : (function(fs$rename) {
3529
- function rename(from, to, cb) {
3529
+ function rename2(from, to, cb) {
3530
3530
  var start = Date.now();
3531
3531
  var backoff = 0;
3532
3532
  fs$rename(from, to, function CB(er) {
@@ -3546,8 +3546,8 @@ var require_polyfills = __commonJS({
3546
3546
  if (cb) cb(er);
3547
3547
  });
3548
3548
  }
3549
- if (Object.setPrototypeOf) Object.setPrototypeOf(rename, fs$rename);
3550
- return rename;
3549
+ if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
3550
+ return rename2;
3551
3551
  })(fs5.rename);
3552
3552
  }
3553
3553
  fs5.read = typeof fs5.read !== "function" ? fs5.read : (function(fs$read) {
@@ -3588,7 +3588,7 @@ var require_polyfills = __commonJS({
3588
3588
  fs6.lchmod = function(path9, mode, callback) {
3589
3589
  fs6.open(
3590
3590
  path9,
3591
- constants2.O_WRONLY | constants2.O_SYMLINK,
3591
+ constants6.O_WRONLY | constants6.O_SYMLINK,
3592
3592
  mode,
3593
3593
  function(err, fd) {
3594
3594
  if (err) {
@@ -3604,7 +3604,7 @@ var require_polyfills = __commonJS({
3604
3604
  );
3605
3605
  };
3606
3606
  fs6.lchmodSync = function(path9, mode) {
3607
- var fd = fs6.openSync(path9, constants2.O_WRONLY | constants2.O_SYMLINK, mode);
3607
+ var fd = fs6.openSync(path9, constants6.O_WRONLY | constants6.O_SYMLINK, mode);
3608
3608
  var threw = true;
3609
3609
  var ret;
3610
3610
  try {
@@ -3624,9 +3624,9 @@ var require_polyfills = __commonJS({
3624
3624
  };
3625
3625
  }
3626
3626
  function patchLutimes(fs6) {
3627
- if (constants2.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
3627
+ if (constants6.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
3628
3628
  fs6.lutimes = function(path9, at, mt, cb) {
3629
- fs6.open(path9, constants2.O_SYMLINK, function(er, fd) {
3629
+ fs6.open(path9, constants6.O_SYMLINK, function(er, fd) {
3630
3630
  if (er) {
3631
3631
  if (cb) cb(er);
3632
3632
  return;
@@ -3639,7 +3639,7 @@ var require_polyfills = __commonJS({
3639
3639
  });
3640
3640
  };
3641
3641
  fs6.lutimesSync = function(path9, at, mt) {
3642
- var fd = fs6.openSync(path9, constants2.O_SYMLINK);
3642
+ var fd = fs6.openSync(path9, constants6.O_SYMLINK);
3643
3643
  var ret;
3644
3644
  var threw = true;
3645
3645
  try {
@@ -3951,8 +3951,8 @@ var require_graceful_fs = __commonJS({
3951
3951
  fs6.createReadStream = createReadStream;
3952
3952
  fs6.createWriteStream = createWriteStream;
3953
3953
  var fs$readFile = fs6.readFile;
3954
- fs6.readFile = readFile5;
3955
- function readFile5(path9, options, cb) {
3954
+ fs6.readFile = readFile6;
3955
+ function readFile6(path9, options, cb) {
3956
3956
  if (typeof options === "function")
3957
3957
  cb = options, options = null;
3958
3958
  return go$readFile(path9, options, cb);
@@ -5386,7 +5386,7 @@ var require_minimist = __commonJS({
5386
5386
  var require_rc = __commonJS({
5387
5387
  "node_modules/.pnpm/rc@1.2.8/node_modules/rc/index.js"(exports, module) {
5388
5388
  var cc = require_utils();
5389
- var join10 = __require("path").join;
5389
+ var join11 = __require("path").join;
5390
5390
  var deepExtend = require_deep_extend();
5391
5391
  var etc = "/etc";
5392
5392
  var win = process.platform === "win32";
@@ -5411,15 +5411,15 @@ var require_rc = __commonJS({
5411
5411
  }
5412
5412
  if (!win)
5413
5413
  [
5414
- join10(etc, name, "config"),
5415
- join10(etc, name + "rc")
5414
+ join11(etc, name, "config"),
5415
+ join11(etc, name + "rc")
5416
5416
  ].forEach(addConfigFile);
5417
5417
  if (home)
5418
5418
  [
5419
- join10(home, ".config", name, "config"),
5420
- join10(home, ".config", name),
5421
- join10(home, "." + name, "config"),
5422
- join10(home, "." + name + "rc")
5419
+ join11(home, ".config", name, "config"),
5420
+ join11(home, ".config", name),
5421
+ join11(home, "." + name, "config"),
5422
+ join11(home, "." + name + "rc")
5423
5423
  ].forEach(addConfigFile);
5424
5424
  addConfigFile(cc.find("." + name + "rc"));
5425
5425
  if (env5.config) addConfigFile(env5.config);
@@ -5436,10 +5436,10 @@ var require_rc = __commonJS({
5436
5436
  // node_modules/.pnpm/graceful-fs@4.2.10/node_modules/graceful-fs/polyfills.js
5437
5437
  var require_polyfills2 = __commonJS({
5438
5438
  "node_modules/.pnpm/graceful-fs@4.2.10/node_modules/graceful-fs/polyfills.js"(exports, module) {
5439
- var constants2 = __require("constants");
5439
+ var constants6 = __require("constants");
5440
5440
  var origCwd = process.cwd;
5441
5441
  var cwd = null;
5442
- var platform2 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
5442
+ var platform3 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
5443
5443
  process.cwd = function() {
5444
5444
  if (!cwd)
5445
5445
  cwd = origCwd.call(process);
@@ -5460,7 +5460,7 @@ var require_polyfills2 = __commonJS({
5460
5460
  var chdir;
5461
5461
  module.exports = patch;
5462
5462
  function patch(fs5) {
5463
- if (constants2.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
5463
+ if (constants6.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
5464
5464
  patchLchmod(fs5);
5465
5465
  }
5466
5466
  if (!fs5.lutimes) {
@@ -5498,9 +5498,9 @@ var require_polyfills2 = __commonJS({
5498
5498
  fs5.lchownSync = function() {
5499
5499
  };
5500
5500
  }
5501
- if (platform2 === "win32") {
5501
+ if (platform3 === "win32") {
5502
5502
  fs5.rename = typeof fs5.rename !== "function" ? fs5.rename : (function(fs$rename) {
5503
- function rename(from, to, cb) {
5503
+ function rename2(from, to, cb) {
5504
5504
  var start = Date.now();
5505
5505
  var backoff = 0;
5506
5506
  fs$rename(from, to, function CB(er) {
@@ -5520,8 +5520,8 @@ var require_polyfills2 = __commonJS({
5520
5520
  if (cb) cb(er);
5521
5521
  });
5522
5522
  }
5523
- if (Object.setPrototypeOf) Object.setPrototypeOf(rename, fs$rename);
5524
- return rename;
5523
+ if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
5524
+ return rename2;
5525
5525
  })(fs5.rename);
5526
5526
  }
5527
5527
  fs5.read = typeof fs5.read !== "function" ? fs5.read : (function(fs$read) {
@@ -5562,7 +5562,7 @@ var require_polyfills2 = __commonJS({
5562
5562
  fs6.lchmod = function(path9, mode, callback) {
5563
5563
  fs6.open(
5564
5564
  path9,
5565
- constants2.O_WRONLY | constants2.O_SYMLINK,
5565
+ constants6.O_WRONLY | constants6.O_SYMLINK,
5566
5566
  mode,
5567
5567
  function(err, fd) {
5568
5568
  if (err) {
@@ -5578,7 +5578,7 @@ var require_polyfills2 = __commonJS({
5578
5578
  );
5579
5579
  };
5580
5580
  fs6.lchmodSync = function(path9, mode) {
5581
- var fd = fs6.openSync(path9, constants2.O_WRONLY | constants2.O_SYMLINK, mode);
5581
+ var fd = fs6.openSync(path9, constants6.O_WRONLY | constants6.O_SYMLINK, mode);
5582
5582
  var threw = true;
5583
5583
  var ret;
5584
5584
  try {
@@ -5598,9 +5598,9 @@ var require_polyfills2 = __commonJS({
5598
5598
  };
5599
5599
  }
5600
5600
  function patchLutimes(fs6) {
5601
- if (constants2.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
5601
+ if (constants6.hasOwnProperty("O_SYMLINK") && fs6.futimes) {
5602
5602
  fs6.lutimes = function(path9, at, mt, cb) {
5603
- fs6.open(path9, constants2.O_SYMLINK, function(er, fd) {
5603
+ fs6.open(path9, constants6.O_SYMLINK, function(er, fd) {
5604
5604
  if (er) {
5605
5605
  if (cb) cb(er);
5606
5606
  return;
@@ -5613,7 +5613,7 @@ var require_polyfills2 = __commonJS({
5613
5613
  });
5614
5614
  };
5615
5615
  fs6.lutimesSync = function(path9, at, mt) {
5616
- var fd = fs6.openSync(path9, constants2.O_SYMLINK);
5616
+ var fd = fs6.openSync(path9, constants6.O_SYMLINK);
5617
5617
  var ret;
5618
5618
  var threw = true;
5619
5619
  try {
@@ -5925,8 +5925,8 @@ var require_graceful_fs2 = __commonJS({
5925
5925
  fs6.createReadStream = createReadStream;
5926
5926
  fs6.createWriteStream = createWriteStream;
5927
5927
  var fs$readFile = fs6.readFile;
5928
- fs6.readFile = readFile5;
5929
- function readFile5(path9, options, cb) {
5928
+ fs6.readFile = readFile6;
5929
+ function readFile6(path9, options, cb) {
5930
5930
  if (typeof options === "function")
5931
5931
  cb = options, options = null;
5932
5932
  return go$readFile(path9, options, cb);
@@ -6757,11 +6757,11 @@ var require_util = __commonJS({
6757
6757
  if (files.includes("node_modules") || files.includes("package.json") || files.includes("package.json5") || files.includes("package.yaml") || files.includes("pnpm-workspace.yaml")) {
6758
6758
  return name2;
6759
6759
  }
6760
- const dirname9 = path9.dirname(name2);
6761
- if (dirname9 === name2) {
6760
+ const dirname10 = path9.dirname(name2);
6761
+ if (dirname10 === name2) {
6762
6762
  return original;
6763
6763
  }
6764
- return find(dirname9, original);
6764
+ return find(dirname10, original);
6765
6765
  } catch (error) {
6766
6766
  if (name2 === original) {
6767
6767
  if (error.code === "ENOENT") {
@@ -7950,20 +7950,20 @@ var require_range = __commonJS({
7950
7950
  };
7951
7951
  var replaceTilde = (comp, options) => {
7952
7952
  const r2 = options.loose ? re[t.TILDELOOSE] : re[t.TILDE];
7953
- return comp.replace(r2, (_3, M2, m2, p2, pr) => {
7954
- debug("tilde", comp, _3, M2, m2, p2, pr);
7953
+ return comp.replace(r2, (_3, M3, m2, p2, pr) => {
7954
+ debug("tilde", comp, _3, M3, m2, p2, pr);
7955
7955
  let ret;
7956
- if (isX(M2)) {
7956
+ if (isX(M3)) {
7957
7957
  ret = "";
7958
7958
  } else if (isX(m2)) {
7959
- ret = `>=${M2}.0.0 <${+M2 + 1}.0.0-0`;
7959
+ ret = `>=${M3}.0.0 <${+M3 + 1}.0.0-0`;
7960
7960
  } else if (isX(p2)) {
7961
- ret = `>=${M2}.${m2}.0 <${M2}.${+m2 + 1}.0-0`;
7961
+ ret = `>=${M3}.${m2}.0 <${M3}.${+m2 + 1}.0-0`;
7962
7962
  } else if (pr) {
7963
7963
  debug("replaceTilde pr", pr);
7964
- ret = `>=${M2}.${m2}.${p2}-${pr} <${M2}.${+m2 + 1}.0-0`;
7964
+ ret = `>=${M3}.${m2}.${p2}-${pr} <${M3}.${+m2 + 1}.0-0`;
7965
7965
  } else {
7966
- ret = `>=${M2}.${m2}.${p2} <${M2}.${+m2 + 1}.0-0`;
7966
+ ret = `>=${M3}.${m2}.${p2} <${M3}.${+m2 + 1}.0-0`;
7967
7967
  }
7968
7968
  debug("tilde return", ret);
7969
7969
  return ret;
@@ -7976,40 +7976,40 @@ var require_range = __commonJS({
7976
7976
  debug("caret", comp, options);
7977
7977
  const r2 = options.loose ? re[t.CARETLOOSE] : re[t.CARET];
7978
7978
  const z2 = options.includePrerelease ? "-0" : "";
7979
- return comp.replace(r2, (_3, M2, m2, p2, pr) => {
7980
- debug("caret", comp, _3, M2, m2, p2, pr);
7979
+ return comp.replace(r2, (_3, M3, m2, p2, pr) => {
7980
+ debug("caret", comp, _3, M3, m2, p2, pr);
7981
7981
  let ret;
7982
- if (isX(M2)) {
7982
+ if (isX(M3)) {
7983
7983
  ret = "";
7984
7984
  } else if (isX(m2)) {
7985
- ret = `>=${M2}.0.0${z2} <${+M2 + 1}.0.0-0`;
7985
+ ret = `>=${M3}.0.0${z2} <${+M3 + 1}.0.0-0`;
7986
7986
  } else if (isX(p2)) {
7987
- if (M2 === "0") {
7988
- ret = `>=${M2}.${m2}.0${z2} <${M2}.${+m2 + 1}.0-0`;
7987
+ if (M3 === "0") {
7988
+ ret = `>=${M3}.${m2}.0${z2} <${M3}.${+m2 + 1}.0-0`;
7989
7989
  } else {
7990
- ret = `>=${M2}.${m2}.0${z2} <${+M2 + 1}.0.0-0`;
7990
+ ret = `>=${M3}.${m2}.0${z2} <${+M3 + 1}.0.0-0`;
7991
7991
  }
7992
7992
  } else if (pr) {
7993
7993
  debug("replaceCaret pr", pr);
7994
- if (M2 === "0") {
7994
+ if (M3 === "0") {
7995
7995
  if (m2 === "0") {
7996
- ret = `>=${M2}.${m2}.${p2}-${pr} <${M2}.${m2}.${+p2 + 1}-0`;
7996
+ ret = `>=${M3}.${m2}.${p2}-${pr} <${M3}.${m2}.${+p2 + 1}-0`;
7997
7997
  } else {
7998
- ret = `>=${M2}.${m2}.${p2}-${pr} <${M2}.${+m2 + 1}.0-0`;
7998
+ ret = `>=${M3}.${m2}.${p2}-${pr} <${M3}.${+m2 + 1}.0-0`;
7999
7999
  }
8000
8000
  } else {
8001
- ret = `>=${M2}.${m2}.${p2}-${pr} <${+M2 + 1}.0.0-0`;
8001
+ ret = `>=${M3}.${m2}.${p2}-${pr} <${+M3 + 1}.0.0-0`;
8002
8002
  }
8003
8003
  } else {
8004
8004
  debug("no pr");
8005
- if (M2 === "0") {
8005
+ if (M3 === "0") {
8006
8006
  if (m2 === "0") {
8007
- ret = `>=${M2}.${m2}.${p2}${z2} <${M2}.${m2}.${+p2 + 1}-0`;
8007
+ ret = `>=${M3}.${m2}.${p2}${z2} <${M3}.${m2}.${+p2 + 1}-0`;
8008
8008
  } else {
8009
- ret = `>=${M2}.${m2}.${p2}${z2} <${M2}.${+m2 + 1}.0-0`;
8009
+ ret = `>=${M3}.${m2}.${p2}${z2} <${M3}.${+m2 + 1}.0-0`;
8010
8010
  }
8011
8011
  } else {
8012
- ret = `>=${M2}.${m2}.${p2} <${+M2 + 1}.0.0-0`;
8012
+ ret = `>=${M3}.${m2}.${p2} <${+M3 + 1}.0.0-0`;
8013
8013
  }
8014
8014
  }
8015
8015
  debug("caret return", ret);
@@ -8023,9 +8023,9 @@ var require_range = __commonJS({
8023
8023
  var replaceXRange = (comp, options) => {
8024
8024
  comp = comp.trim();
8025
8025
  const r2 = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE];
8026
- return comp.replace(r2, (ret, gtlt, M2, m2, p2, pr) => {
8027
- debug("xRange", comp, ret, gtlt, M2, m2, p2, pr);
8028
- const xM = isX(M2);
8026
+ return comp.replace(r2, (ret, gtlt, M3, m2, p2, pr) => {
8027
+ debug("xRange", comp, ret, gtlt, M3, m2, p2, pr);
8028
+ const xM = isX(M3);
8029
8029
  const xm = xM || isX(m2);
8030
8030
  const xp = xm || isX(p2);
8031
8031
  const anyX = xp;
@@ -8047,7 +8047,7 @@ var require_range = __commonJS({
8047
8047
  if (gtlt === ">") {
8048
8048
  gtlt = ">=";
8049
8049
  if (xm) {
8050
- M2 = +M2 + 1;
8050
+ M3 = +M3 + 1;
8051
8051
  m2 = 0;
8052
8052
  p2 = 0;
8053
8053
  } else {
@@ -8057,7 +8057,7 @@ var require_range = __commonJS({
8057
8057
  } else if (gtlt === "<=") {
8058
8058
  gtlt = "<";
8059
8059
  if (xm) {
8060
- M2 = +M2 + 1;
8060
+ M3 = +M3 + 1;
8061
8061
  } else {
8062
8062
  m2 = +m2 + 1;
8063
8063
  }
@@ -8065,11 +8065,11 @@ var require_range = __commonJS({
8065
8065
  if (gtlt === "<") {
8066
8066
  pr = "-0";
8067
8067
  }
8068
- ret = `${gtlt + M2}.${m2}.${p2}${pr}`;
8068
+ ret = `${gtlt + M3}.${m2}.${p2}${pr}`;
8069
8069
  } else if (xm) {
8070
- ret = `>=${M2}.0.0${pr} <${+M2 + 1}.0.0-0`;
8070
+ ret = `>=${M3}.0.0${pr} <${+M3 + 1}.0.0-0`;
8071
8071
  } else if (xp) {
8072
- ret = `>=${M2}.${m2}.0${pr} <${M2}.${+m2 + 1}.0-0`;
8072
+ ret = `>=${M3}.${m2}.0${pr} <${M3}.${+m2 + 1}.0-0`;
8073
8073
  }
8074
8074
  debug("xRange return", ret);
8075
8075
  return ret;
@@ -8729,7 +8729,7 @@ var require_semver2 = __commonJS({
8729
8729
  "node_modules/.pnpm/semver@7.7.4/node_modules/semver/index.js"(exports, module) {
8730
8730
  "use strict";
8731
8731
  var internalRe = require_re();
8732
- var constants2 = require_constants();
8732
+ var constants6 = require_constants();
8733
8733
  var SemVer = require_semver();
8734
8734
  var identifiers = require_identifiers();
8735
8735
  var parse = require_parse();
@@ -8811,8 +8811,8 @@ var require_semver2 = __commonJS({
8811
8811
  re: internalRe.re,
8812
8812
  src: internalRe.src,
8813
8813
  tokens: internalRe.t,
8814
- SEMVER_SPEC_VERSION: constants2.SEMVER_SPEC_VERSION,
8815
- RELEASE_TYPES: constants2.RELEASE_TYPES,
8814
+ SEMVER_SPEC_VERSION: constants6.SEMVER_SPEC_VERSION,
8815
+ RELEASE_TYPES: constants6.RELEASE_TYPES,
8816
8816
  compareIdentifiers: identifiers.compareIdentifiers,
8817
8817
  rcompareIdentifiers: identifiers.rcompareIdentifiers
8818
8818
  };
@@ -9422,7 +9422,7 @@ var require_picocolors = __commonJS({
9422
9422
  });
9423
9423
 
9424
9424
  // src/index.ts
9425
- import { homedir as homedir7 } from "node:os";
9425
+ import { homedir as homedir8 } from "node:os";
9426
9426
 
9427
9427
  // src/cli.ts
9428
9428
  import { createRequire } from "node:module";
@@ -9781,14 +9781,14 @@ var Temp = {
9781
9781
  }
9782
9782
  },
9783
9783
  truncate: (filePath) => {
9784
- const basename9 = path2.basename(filePath);
9785
- if (basename9.length <= LIMIT_BASENAME_LENGTH)
9784
+ const basename10 = path2.basename(filePath);
9785
+ if (basename10.length <= LIMIT_BASENAME_LENGTH)
9786
9786
  return filePath;
9787
- const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename9);
9787
+ const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename10);
9788
9788
  if (!truncable)
9789
9789
  return filePath;
9790
- const truncationLength = basename9.length - LIMIT_BASENAME_LENGTH;
9791
- return `${filePath.slice(0, -basename9.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
9790
+ const truncationLength = basename10.length - LIMIT_BASENAME_LENGTH;
9791
+ return `${filePath.slice(0, -basename10.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
9792
9792
  }
9793
9793
  };
9794
9794
  node_default(Temp.purgeSyncAll);
@@ -14000,6 +14000,29 @@ ${import_picocolors2.default.gray(d2)} ${t}
14000
14000
 
14001
14001
  `);
14002
14002
  };
14003
+ var M2 = { message: (t = "", { symbol: n = import_picocolors2.default.gray(o) } = {}) => {
14004
+ const r2 = [`${import_picocolors2.default.gray(o)}`];
14005
+ if (t) {
14006
+ const [i, ...s] = t.split(`
14007
+ `);
14008
+ r2.push(`${n} ${i}`, ...s.map((c) => `${import_picocolors2.default.gray(o)} ${c}`));
14009
+ }
14010
+ process.stdout.write(`${r2.join(`
14011
+ `)}
14012
+ `);
14013
+ }, info: (t) => {
14014
+ M2.message(t, { symbol: import_picocolors2.default.blue(q) });
14015
+ }, success: (t) => {
14016
+ M2.message(t, { symbol: import_picocolors2.default.green(D) });
14017
+ }, step: (t) => {
14018
+ M2.message(t, { symbol: import_picocolors2.default.green(C) });
14019
+ }, warn: (t) => {
14020
+ M2.message(t, { symbol: import_picocolors2.default.yellow(U) });
14021
+ }, warning: (t) => {
14022
+ M2.warn(t);
14023
+ }, error: (t) => {
14024
+ M2.message(t, { symbol: import_picocolors2.default.red(K2) });
14025
+ } };
14003
14026
  var J2 = `${import_picocolors2.default.gray(o)} `;
14004
14027
 
14005
14028
  // src/headless.ts
@@ -14127,6 +14150,7 @@ function formatRelativeAge(timestampSeconds) {
14127
14150
 
14128
14151
  // src/worktree-management.ts
14129
14152
  import { execFile as execFile2 } from "node:child_process";
14153
+ import { rm } from "node:fs/promises";
14130
14154
  import { promisify as promisify3 } from "node:util";
14131
14155
  var execFileAsync2 = promisify3(execFile2);
14132
14156
  var GIT_ENV = { ...process.env, LC_ALL: "C" };
@@ -14147,10 +14171,24 @@ async function removeWorktree(repoRoot, worktreePath) {
14147
14171
  });
14148
14172
  }
14149
14173
  async function forceRemoveWorktree(repoRoot, worktreePath) {
14150
- await execFileAsync2("git", ["worktree", "remove", "--force", worktreePath], {
14151
- cwd: repoRoot,
14152
- env: GIT_ENV
14153
- });
14174
+ try {
14175
+ await execFileAsync2(
14176
+ "git",
14177
+ ["worktree", "remove", "--force", worktreePath],
14178
+ {
14179
+ cwd: repoRoot,
14180
+ env: GIT_ENV
14181
+ }
14182
+ );
14183
+ } catch (error) {
14184
+ const worktreeIsStillRegistered = (await listWorktrees(repoRoot)).some(
14185
+ (worktree) => worktree.path === worktreePath
14186
+ );
14187
+ if (worktreeIsStillRegistered) {
14188
+ throw error;
14189
+ }
14190
+ await rm(worktreePath, { force: true, recursive: true });
14191
+ }
14154
14192
  }
14155
14193
  async function deleteBranch(repoRoot, branch) {
14156
14194
  await execFileAsync2("git", ["branch", "-d", branch], {
@@ -14164,8 +14202,14 @@ async function forceDeleteBranch(repoRoot, branch) {
14164
14202
  env: GIT_ENV
14165
14203
  });
14166
14204
  }
14167
- function isWorktreeDirtyError(error) {
14168
- return hasStderr(error) && error.stderr.includes("contains modified or untracked files");
14205
+ function isWorktreeForceRemovalError(error) {
14206
+ if (!hasStderr(error)) {
14207
+ return false;
14208
+ }
14209
+ return error.stderr.includes("contains modified or untracked files") || isWorktreeDeletionError(error);
14210
+ }
14211
+ function isWorktreeDeletionError(error) {
14212
+ return hasStderr(error) && error.stderr.includes("failed to delete");
14169
14213
  }
14170
14214
  function isBranchUnmergedError(error) {
14171
14215
  return hasStderr(error) && error.stderr.includes("is not fully merged");
@@ -14176,16 +14220,394 @@ function hasStderr(error) {
14176
14220
 
14177
14221
  // src/worktree-picker.ts
14178
14222
  import { env as env4, platform, stdin, stdout } from "node:process";
14179
- async function buildWorktreePromptEntries(sources) {
14180
- const [history, infos] = await Promise.all([
14223
+
14224
+ // src/pull-requests.ts
14225
+ import { execFile as execFile3 } from "node:child_process";
14226
+ import { promisify as promisify4 } from "node:util";
14227
+ var execFileAsync3 = promisify4(execFile3);
14228
+ var PULL_REQUEST_QUERY_TIMEOUT_MS = 2500;
14229
+ function parsePullRequestRemote(remoteUrl) {
14230
+ const parsed = parseRemoteUrl(remoteUrl.trim());
14231
+ if (parsed === null) return null;
14232
+ const forge = detectForge(parsed.host);
14233
+ if (forge === null) return null;
14234
+ if (forge === "bitbucket" && parsed.host !== "bitbucket.org") return null;
14235
+ const path9 = parsed.path.replace(/^\/+/, "").replace(/\.git$/, "").split("/").filter(Boolean);
14236
+ if (path9.length < 2) return null;
14237
+ if (forge === "gitlab") {
14238
+ const repository2 = path9.at(-1);
14239
+ const namespace2 = path9.slice(0, -1).join("/");
14240
+ if (repository2 === void 0 || namespace2.length === 0) return null;
14241
+ return {
14242
+ forge,
14243
+ host: parsed.host,
14244
+ namespace: namespace2,
14245
+ repository: repository2,
14246
+ webBaseUrl: `https://${parsed.host}`
14247
+ };
14248
+ }
14249
+ const namespace = path9.at(-2);
14250
+ const repository = path9.at(-1);
14251
+ if (namespace === void 0 || repository === void 0) return null;
14252
+ return {
14253
+ forge,
14254
+ host: parsed.host,
14255
+ namespace,
14256
+ repository,
14257
+ webBaseUrl: `https://${parsed.host}`
14258
+ };
14259
+ }
14260
+ function createPullRequestQuery(dependencies = {}) {
14261
+ const runCommand = dependencies.runCommand ?? defaultRunCommand;
14262
+ const fetcher = dependencies.fetch ?? fetch;
14263
+ return {
14264
+ findOpenPullRequest: (repoRoot, number) => findOpenPullRequest(repoRoot, number, runCommand, fetcher),
14265
+ listOpenPullRequests: (repoRoot, sourceBranch) => listOpenPullRequests(repoRoot, sourceBranch, runCommand, fetcher),
14266
+ listOpenPullRequestsForRepository: (repoRoot) => listOpenPullRequestsForRepository(repoRoot, runCommand, fetcher)
14267
+ };
14268
+ }
14269
+ async function listOpenPullRequests(repoRoot, sourceBranch, runCommand, fetcher) {
14270
+ const remote = await readPullRequestRemote(repoRoot, runCommand);
14271
+ const cliResult = await queryProviderCli(
14272
+ remote,
14273
+ { kind: "branch", sourceBranch },
14274
+ repoRoot,
14275
+ runCommand
14276
+ );
14277
+ if (cliResult !== null) return sortPullRequests(cliResult);
14278
+ const apiResult = await queryPublicApi(
14279
+ remote,
14280
+ { kind: "branch", sourceBranch },
14281
+ fetcher
14282
+ );
14283
+ return sortPullRequests(apiResult);
14284
+ }
14285
+ async function listOpenPullRequestsForRepository(repoRoot, runCommand, fetcher) {
14286
+ const remote = await readPullRequestRemote(repoRoot, runCommand);
14287
+ const cliResult = await queryProviderCli(
14288
+ remote,
14289
+ { kind: "repository" },
14290
+ repoRoot,
14291
+ runCommand
14292
+ );
14293
+ if (cliResult !== null) return sortPullRequests(cliResult);
14294
+ const apiResult = await queryPublicApi(
14295
+ remote,
14296
+ { kind: "repository" },
14297
+ fetcher
14298
+ );
14299
+ return sortPullRequests(apiResult);
14300
+ }
14301
+ async function findOpenPullRequest(repoRoot, number, runCommand, fetcher) {
14302
+ const remote = await readPullRequestRemote(repoRoot, runCommand);
14303
+ const cliResult = await queryProviderCli(
14304
+ remote,
14305
+ { kind: "number", number },
14306
+ repoRoot,
14307
+ runCommand
14308
+ );
14309
+ if (cliResult !== null) return cliResult[0] ?? null;
14310
+ const apiResult = await queryPublicApi(
14311
+ remote,
14312
+ { kind: "number", number },
14313
+ fetcher
14314
+ );
14315
+ return apiResult[0] ?? null;
14316
+ }
14317
+ async function readPullRequestRemote(repoRoot, runCommand) {
14318
+ const result = await runCommand(
14319
+ "git",
14320
+ ["remote", "get-url", "origin"],
14321
+ commandOptions(repoRoot)
14322
+ );
14323
+ const remote = parsePullRequestRemote(result.stdout);
14324
+ if (remote === null) {
14325
+ throw new Error("origin remote is unsupported or missing");
14326
+ }
14327
+ return remote;
14328
+ }
14329
+ async function queryProviderCli(remote, query, repoRoot, runCommand) {
14330
+ const providerCommand = providerCliCommand(remote, query);
14331
+ try {
14332
+ const result = await runCommand(
14333
+ providerCommand.command,
14334
+ providerCommand.args,
14335
+ commandOptions(repoRoot)
14336
+ );
14337
+ return parseProviderOutput(remote, result.stdout, query);
14338
+ } catch {
14339
+ return null;
14340
+ }
14341
+ }
14342
+ async function queryPublicApi(remote, query, fetcher) {
14343
+ const url = publicApiUrl(remote, query);
14344
+ const controller = new AbortController();
14345
+ const timer = setTimeout(
14346
+ () => controller.abort(),
14347
+ PULL_REQUEST_QUERY_TIMEOUT_MS
14348
+ );
14349
+ try {
14350
+ const response = await fetcher(url, {
14351
+ headers: { Accept: "application/json" },
14352
+ signal: controller.signal
14353
+ });
14354
+ if (response.status === 404 && query.kind === "number") return [];
14355
+ if (!response.ok) {
14356
+ throw new Error(`public API returned HTTP ${response.status}`);
14357
+ }
14358
+ const payload = await response.json();
14359
+ return parseApiOutput(remote, payload, query);
14360
+ } finally {
14361
+ clearTimeout(timer);
14362
+ }
14363
+ }
14364
+ function parseRemoteUrl(remoteUrl) {
14365
+ const scpMatch = remoteUrl.match(/^[^@]+@([^:]+):(.+)$/);
14366
+ if (scpMatch) {
14367
+ return { host: scpMatch[1].toLowerCase(), path: scpMatch[2] };
14368
+ }
14369
+ try {
14370
+ const url = new URL(remoteUrl);
14371
+ if (!url.hostname) return null;
14372
+ return { host: url.hostname.toLowerCase(), path: url.pathname };
14373
+ } catch {
14374
+ return null;
14375
+ }
14376
+ }
14377
+ function detectForge(host) {
14378
+ if (host === "github.com" || host.includes("github")) return "github";
14379
+ if (host === "gitlab.com" || host.includes("gitlab")) return "gitlab";
14380
+ if (host === "bitbucket.org" || host.includes("bitbucket")) {
14381
+ return "bitbucket";
14382
+ }
14383
+ return null;
14384
+ }
14385
+ function providerCliCommand(remote, query) {
14386
+ const coordinate = providerRepositoryCoordinate(remote);
14387
+ const sourceBranch = query.sourceBranch ?? "";
14388
+ switch (remote.forge) {
14389
+ case "github": {
14390
+ if (query.kind === "number") {
14391
+ return {
14392
+ args: [
14393
+ "pr",
14394
+ "view",
14395
+ String(query.number),
14396
+ "--json",
14397
+ "number,url,headRefName,state",
14398
+ "--repo",
14399
+ coordinate
14400
+ ],
14401
+ command: "gh"
14402
+ };
14403
+ }
14404
+ return {
14405
+ args: [
14406
+ "pr",
14407
+ "list",
14408
+ "--state",
14409
+ "open",
14410
+ ...query.kind === "branch" ? ["--head", sourceBranch] : [],
14411
+ "--json",
14412
+ "number,url,headRefName,state",
14413
+ "--limit",
14414
+ "100",
14415
+ "--repo",
14416
+ coordinate
14417
+ ],
14418
+ command: "gh"
14419
+ };
14420
+ }
14421
+ case "gitlab": {
14422
+ if (query.kind === "number") {
14423
+ return {
14424
+ args: [
14425
+ "mr",
14426
+ "view",
14427
+ String(query.number),
14428
+ "--output",
14429
+ "json",
14430
+ "--repo",
14431
+ coordinate
14432
+ ],
14433
+ command: "glab"
14434
+ };
14435
+ }
14436
+ return {
14437
+ args: [
14438
+ "mr",
14439
+ "list",
14440
+ "--state",
14441
+ "opened",
14442
+ ...query.kind === "branch" ? ["--source-branch", sourceBranch] : [],
14443
+ "--output",
14444
+ "json",
14445
+ "--per-page",
14446
+ "100",
14447
+ "--repo",
14448
+ coordinate
14449
+ ],
14450
+ command: "glab"
14451
+ };
14452
+ }
14453
+ case "bitbucket": {
14454
+ if (query.kind === "number") {
14455
+ return {
14456
+ args: [
14457
+ "pr",
14458
+ "view",
14459
+ String(query.number),
14460
+ "--format",
14461
+ "json",
14462
+ "--repo",
14463
+ coordinate
14464
+ ],
14465
+ command: "bb"
14466
+ };
14467
+ }
14468
+ return {
14469
+ args: [
14470
+ "pr",
14471
+ "list",
14472
+ "--state",
14473
+ "OPEN",
14474
+ ...query.kind === "branch" ? ["--source", sourceBranch] : [],
14475
+ "--format",
14476
+ "json",
14477
+ "--repo",
14478
+ coordinate
14479
+ ],
14480
+ command: "bb"
14481
+ };
14482
+ }
14483
+ }
14484
+ }
14485
+ function providerRepositoryCoordinate(remote) {
14486
+ const coordinate = `${remote.namespace}/${remote.repository}`;
14487
+ return remote.host === "github.com" || remote.host === "bitbucket.org" ? coordinate : `${remote.host}/${coordinate}`;
14488
+ }
14489
+ function parseProviderOutput(remote, stdout2, query) {
14490
+ const payload = JSON.parse(stdout2);
14491
+ const values = remote.forge === "bitbucket" && isRecord(payload) ? Array.isArray(payload.values) ? payload.values : [] : Array.isArray(payload) ? payload : [payload];
14492
+ return values.map((value) => normalizePullRequest(value, query)).filter((value) => value !== null).filter(
14493
+ (value) => query.kind === "branch" ? value.sourceBranch === query.sourceBranch : query.kind === "number" ? value.number === query.number : true
14494
+ );
14495
+ }
14496
+ function parseApiOutput(remote, payload, query) {
14497
+ const values = remote.forge === "bitbucket" && isRecord(payload) ? Array.isArray(payload.values) ? payload.values : [] : query.kind === "number" ? [payload] : Array.isArray(payload) ? payload : [];
14498
+ return values.map((value) => normalizePullRequest(value, query)).filter((value) => value !== null).filter(
14499
+ (value) => query.kind === "branch" ? value.sourceBranch === query.sourceBranch : query.kind === "number" ? value.number === query.number : true
14500
+ );
14501
+ }
14502
+ function normalizePullRequest(value, query) {
14503
+ if (!isRecord(value)) return null;
14504
+ const number = numberValue(value.number ?? value.iid ?? value.id);
14505
+ const head = isRecord(value.head) ? value.head : null;
14506
+ const source = isRecord(value.source) ? value.source : null;
14507
+ const sourceBranch = stringValue(
14508
+ value.headRefName ?? (head !== null ? head.ref : void 0) ?? value.source_branch ?? (source !== null && isRecord(source.branch) ? source.branch.name : void 0)
14509
+ );
14510
+ const url = stringValue(
14511
+ value.html_url ?? value.web_url ?? value.url ?? (isRecord(value.links) && isRecord(value.links.html) ? value.links.html.href : void 0)
14512
+ );
14513
+ const state = stringValue(value.state)?.toLowerCase();
14514
+ if (number === null || url === null) return null;
14515
+ if (query.kind !== "number" && sourceBranch === null) return null;
14516
+ if (query.kind === "number" && number !== query.number) return null;
14517
+ if (state !== void 0 && !["open", "opened"].includes(state)) return null;
14518
+ return {
14519
+ number,
14520
+ sourceBranch: sourceBranch ?? "",
14521
+ url
14522
+ };
14523
+ }
14524
+ function publicApiUrl(remote, query) {
14525
+ const branch = encodeURIComponent(query.sourceBranch ?? "");
14526
+ const number = query.number === void 0 ? "" : String(query.number);
14527
+ switch (remote.forge) {
14528
+ case "github": {
14529
+ const coordinate = `${encodeURIComponent(remote.namespace)}/${encodeURIComponent(remote.repository)}`;
14530
+ const apiBase = remote.host === "github.com" ? "https://api.github.com" : `https://${remote.host}/api/v3`;
14531
+ if (query.kind === "number") {
14532
+ return `${apiBase}/repos/${coordinate}/pulls/${number}`;
14533
+ }
14534
+ return `${apiBase}/repos/${coordinate}/pulls?state=open&per_page=100`;
14535
+ }
14536
+ case "gitlab": {
14537
+ const project = encodeURIComponent(
14538
+ `${remote.namespace}/${remote.repository}`
14539
+ );
14540
+ if (query.kind === "number") {
14541
+ return `${remote.webBaseUrl}/api/v4/projects/${project}/merge_requests/${number}`;
14542
+ }
14543
+ return query.kind === "branch" ? `${remote.webBaseUrl}/api/v4/projects/${project}/merge_requests?state=opened&source_branch=${branch}&per_page=100` : `${remote.webBaseUrl}/api/v4/projects/${project}/merge_requests?state=opened&per_page=100`;
14544
+ }
14545
+ case "bitbucket": {
14546
+ const coordinate = `${encodeURIComponent(remote.namespace)}/${encodeURIComponent(remote.repository)}`;
14547
+ if (query.kind === "number") {
14548
+ return `https://api.bitbucket.org/2.0/repositories/${coordinate}/pullrequests/${number}`;
14549
+ }
14550
+ return query.kind === "branch" ? `https://api.bitbucket.org/2.0/repositories/${coordinate}/pullrequests?state=OPEN&source.branch.name=${branch}&pagelen=100` : `https://api.bitbucket.org/2.0/repositories/${coordinate}/pullrequests?state=OPEN&pagelen=100`;
14551
+ }
14552
+ }
14553
+ }
14554
+ function sortPullRequests(pullRequests) {
14555
+ return [...pullRequests].sort((a, b3) => a.number - b3.number);
14556
+ }
14557
+ function commandOptions(cwd) {
14558
+ return {
14559
+ cwd,
14560
+ env: {
14561
+ ...process.env,
14562
+ GIT_TERMINAL_PROMPT: "0",
14563
+ GH_PROMPT_DISABLED: "1",
14564
+ GLAB_NON_INTERACTIVE: "1"
14565
+ },
14566
+ timeout: PULL_REQUEST_QUERY_TIMEOUT_MS
14567
+ };
14568
+ }
14569
+ async function defaultRunCommand(command, args, options) {
14570
+ return execFileAsync3(command, args, options);
14571
+ }
14572
+ function isRecord(value) {
14573
+ return typeof value === "object" && value !== null;
14574
+ }
14575
+ function stringValue(value) {
14576
+ return typeof value === "string" && value.length > 0 ? value : null;
14577
+ }
14578
+ function numberValue(value) {
14579
+ if (typeof value === "number" && Number.isInteger(value)) return value;
14580
+ if (typeof value === "string" && /^\d+$/.test(value)) return Number(value);
14581
+ return null;
14582
+ }
14583
+
14584
+ // src/worktree-picker.ts
14585
+ var MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY = 4;
14586
+ async function buildWorktreePromptEntries(sources, dependencies = {}) {
14587
+ const pullRequestQuery = createPullRequestQuery();
14588
+ const queryPullRequests = dependencies.queryPullRequests ?? pullRequestQuery.listOpenPullRequests;
14589
+ const queryRepositoryPullRequests = dependencies.queryRepositoryPullRequests ?? (dependencies.queryPullRequests === void 0 ? pullRequestQuery.listOpenPullRequestsForRepository : void 0);
14590
+ const repositoryPullRequests = queryRepositoryPullRequests === void 0 ? Promise.resolve(null) : readRepositoryPullRequests(sources, queryRepositoryPullRequests);
14591
+ const [history, infos, pullRequestsByRepository] = await Promise.all([
14181
14592
  loadHistory(),
14182
- readWorktreeInfos(sources.map((source) => source.worktree))
14593
+ readWorktreeInfos(sources.map((source) => source.worktree)),
14594
+ repositoryPullRequests
14183
14595
  ]);
14596
+ const pullRequests = await Promise.all(
14597
+ sources.map(
14598
+ (source) => readSourcePullRequests(
14599
+ source,
14600
+ queryPullRequests,
14601
+ pullRequestsByRepository
14602
+ )
14603
+ )
14604
+ );
14184
14605
  const historyByPath = new Map(history.map((entry) => [entry.path, entry]));
14185
14606
  const entries = sources.map(
14186
14607
  (source, index) => buildWorktreePromptEntry(
14187
14608
  source,
14188
14609
  infos[index],
14610
+ pullRequests[index],
14189
14611
  historyByPath.get(source.worktree.path)?.timestamp ?? null,
14190
14612
  Date.now()
14191
14613
  )
@@ -14283,7 +14705,10 @@ function buildSearchableWorktreeEntry(worktree) {
14283
14705
  searchText: buildPromptSearchText(worktree),
14284
14706
  value: worktree.path,
14285
14707
  worktree: {
14286
- branch,
14708
+ branch: formatWorktreeBranchLabel(
14709
+ worktree.branch,
14710
+ worktree.pullRequestNumbers
14711
+ ),
14287
14712
  metadata,
14288
14713
  path: worktree.path,
14289
14714
  repoName: worktree.repoName
@@ -14865,14 +15290,19 @@ function buildPromptSearchText(worktree) {
14865
15290
  worktree.branch ?? "detached",
14866
15291
  worktree.path,
14867
15292
  worktree.label,
14868
- `${worktree.repoName}/${worktree.branch ?? "detached"}`
15293
+ `${worktree.repoName}/${worktree.branch ?? "detached"}`,
15294
+ ...(worktree.pullRequestNumbers ?? []).map((number) => `#${number}`),
15295
+ ...worktree.pullRequestUrls ?? []
14869
15296
  ].join(" ").toLowerCase();
14870
15297
  }
14871
- function buildWorktreePromptEntry(source, info, lastUsedTimestamp, now) {
15298
+ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp, now) {
14872
15299
  const lastWorkedTimestamp = info.lastCommitTimestamp === null ? null : info.lastCommitTimestamp * 1e3;
14873
15300
  const lastActivityTimestamp = lastUsedTimestamp ?? lastWorkedTimestamp;
14874
15301
  const lastActivityType = lastUsedTimestamp !== null ? "used" : lastWorkedTimestamp !== null ? "worked" : null;
14875
- const branch = source.worktree.branch ?? "(detached)";
15302
+ const branch = formatWorktreeBranchLabel(
15303
+ source.worktree.branch,
15304
+ pullRequests.map((pullRequest) => pullRequest.number)
15305
+ );
14876
15306
  const badges = buildStatusBadges(info);
14877
15307
  const recency = formatPromptRecency(
14878
15308
  lastActivityTimestamp,
@@ -14897,9 +15327,80 @@ function buildWorktreePromptEntry(source, info, lastUsedTimestamp, now) {
14897
15327
  label,
14898
15328
  lastActivityTimestamp,
14899
15329
  metadata,
15330
+ pullRequestNumbers: pullRequests.map((pullRequest) => pullRequest.number),
15331
+ pullRequestUrls: pullRequests.map((pullRequest) => pullRequest.url),
14900
15332
  repoName: source.repoName
14901
15333
  };
14902
15334
  }
15335
+ async function readSourcePullRequests(source, queryPullRequests, pullRequestsByRepository) {
15336
+ if (source.repoRoot === void 0 || source.worktree.branch === null) {
15337
+ return [];
15338
+ }
15339
+ if (pullRequestsByRepository !== null) {
15340
+ return sortSourcePullRequests(
15341
+ pullRequestsByRepository.get(source.repoRoot)?.filter(
15342
+ (pullRequest) => pullRequest.sourceBranch === source.worktree.branch
15343
+ )
15344
+ );
15345
+ }
15346
+ try {
15347
+ const pullRequests = await queryPullRequests(
15348
+ source.repoRoot,
15349
+ source.worktree.branch
15350
+ );
15351
+ return sortSourcePullRequests(pullRequests);
15352
+ } catch {
15353
+ return [];
15354
+ }
15355
+ }
15356
+ async function readRepositoryPullRequests(sources, queryPullRequests) {
15357
+ const repoRoots = [
15358
+ ...new Set(
15359
+ sources.flatMap(
15360
+ (source) => source.repoRoot === void 0 || source.worktree.branch === null ? [] : [source.repoRoot]
15361
+ )
15362
+ )
15363
+ ];
15364
+ const results = await mapWithConcurrency2(
15365
+ repoRoots,
15366
+ MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY,
15367
+ async (repoRoot) => {
15368
+ try {
15369
+ return [
15370
+ repoRoot,
15371
+ sortSourcePullRequests(await queryPullRequests(repoRoot))
15372
+ ];
15373
+ } catch {
15374
+ return [repoRoot, []];
15375
+ }
15376
+ }
15377
+ );
15378
+ return new Map(results);
15379
+ }
15380
+ async function mapWithConcurrency2(items, limit, mapper) {
15381
+ const results = new Array(items.length);
15382
+ let nextIndex = 0;
15383
+ async function readNext() {
15384
+ for (; ; ) {
15385
+ const index = nextIndex;
15386
+ nextIndex += 1;
15387
+ if (index >= items.length) return;
15388
+ results[index] = await mapper(items[index]);
15389
+ }
15390
+ }
15391
+ await Promise.all(
15392
+ Array.from({ length: Math.min(limit, items.length) }, () => readNext())
15393
+ );
15394
+ return results;
15395
+ }
15396
+ function sortSourcePullRequests(pullRequests) {
15397
+ return [...pullRequests ?? []].sort((a, b3) => a.number - b3.number);
15398
+ }
15399
+ function formatWorktreeBranchLabel(branch, pullRequestNumbers = []) {
15400
+ const branchLabel = branch ?? "(detached)";
15401
+ if (pullRequestNumbers.length === 0) return branchLabel;
15402
+ return `${branchLabel} (${pullRequestNumbers.map((number) => `#${number}`).join(", ")})`;
15403
+ }
14903
15404
  function buildStatusBadges(info) {
14904
15405
  const badges = [];
14905
15406
  if (info.isCurrent) {
@@ -15156,8 +15657,8 @@ function createCleanCommand(dependencies = {}) {
15156
15657
  }
15157
15658
  return 0;
15158
15659
  }
15159
- const removedPaths = [];
15160
15660
  const removedWorktrees = [];
15661
+ const failures = [];
15161
15662
  for (const worktree of selectedWorktrees) {
15162
15663
  if (options.stale && !await isStaleCleanupCandidate(
15163
15664
  repository.repoRoot,
@@ -15173,10 +15674,15 @@ function createCleanCommand(dependencies = {}) {
15173
15674
  try {
15174
15675
  await removeWorktree(repository.repoRoot, worktree.path);
15175
15676
  } catch (error) {
15176
- if (!isWorktreeDirtyError(error)) {
15177
- throw error;
15677
+ if (!isWorktreeForceRemovalError(error)) {
15678
+ failures.push({
15679
+ branch: worktree.branch,
15680
+ message: toMessage(error),
15681
+ path: worktree.path
15682
+ });
15683
+ continue;
15178
15684
  }
15179
- if (options.stale) {
15685
+ if (options.stale && !isWorktreeDeletionError(error)) {
15180
15686
  options.stderr(
15181
15687
  `Skipped ${worktree.path}: no longer a safe stale cleanup candidate
15182
15688
  `
@@ -15184,24 +15690,24 @@ function createCleanCommand(dependencies = {}) {
15184
15690
  continue;
15185
15691
  }
15186
15692
  if (!options.force && !await confirmForceRemoveWorktree(worktree.path)) {
15187
- reportRemovedPaths(removedPaths, options.stderr);
15188
- options.stderr("Aborted\n");
15189
- return 1;
15693
+ failures.push({
15694
+ branch: worktree.branch,
15695
+ message: "force removal declined",
15696
+ path: worktree.path
15697
+ });
15698
+ continue;
15190
15699
  }
15191
15700
  try {
15192
15701
  await forceRemoveWorktree(repository.repoRoot, worktree.path);
15193
15702
  } catch (forceError) {
15194
- if (!options.json) {
15195
- reportRemovedPaths(removedPaths, options.stderr);
15196
- }
15197
- emitError(
15198
- options,
15199
- `Failed to remove worktree at ${worktree.path}: ${toMessage(forceError)}`
15200
- );
15201
- return 1;
15703
+ failures.push({
15704
+ branch: worktree.branch,
15705
+ message: toMessage(forceError),
15706
+ path: worktree.path
15707
+ });
15708
+ continue;
15202
15709
  }
15203
15710
  }
15204
- removedPaths.push(worktree.path);
15205
15711
  removedWorktrees.push(worktree);
15206
15712
  if (worktree.branch) {
15207
15713
  try {
@@ -15233,13 +15739,16 @@ function createCleanCommand(dependencies = {}) {
15233
15739
  const info = selectedInfoByPath.get(worktree.path);
15234
15740
  return info === void 0 ? { branch: worktree.branch, path: worktree.path } : serializeWorktreeInfo(info);
15235
15741
  });
15236
- options.stdout(`${JSON.stringify({ removed }, null, 2)}
15742
+ const payload = failures.length === 0 ? { removed } : { removed, failed: failures };
15743
+ options.stdout(`${JSON.stringify(payload, null, 2)}
15237
15744
  `);
15745
+ } else if (failures.length > 0) {
15746
+ reportCleanFailures(failures, options.stderr);
15238
15747
  } else {
15239
15748
  options.stdout(`${repository.repoRoot}
15240
15749
  `);
15241
15750
  }
15242
- return 0;
15751
+ return failures.length === 0 ? 0 : 1;
15243
15752
  };
15244
15753
  }
15245
15754
  var runCleanCommand = createCleanCommand();
@@ -15316,9 +15825,13 @@ function resolveSelectedWorktrees(worktrees, selections) {
15316
15825
  }
15317
15826
  return selectedWorktrees;
15318
15827
  }
15319
- function reportRemovedPaths(paths, stderr) {
15320
- if (paths.length > 0) {
15321
- stderr(`Already removed: ${paths.join(", ")}
15828
+ function reportCleanFailures(failures, stderr) {
15829
+ const noun = failures.length === 1 ? "worktree" : "worktrees";
15830
+ stderr(`Failed to clean ${failures.length} ${noun}:
15831
+ `);
15832
+ for (const failure of failures) {
15833
+ const branch = failure.branch === null ? "detached" : failure.branch;
15834
+ stderr(`- ${failure.path} (${branch}): ${failure.message}
15322
15835
  `);
15323
15836
  }
15324
15837
  }
@@ -15409,7 +15922,14 @@ var TOP_LEVEL_COMMANDS = [
15409
15922
  name: "new",
15410
15923
  description: "create a new branch or detached linked worktree"
15411
15924
  },
15412
- { name: "init", description: "print or install shell integration" },
15925
+ {
15926
+ name: "init",
15927
+ description: "set up shell integration interactively or print a shell wrapper"
15928
+ },
15929
+ {
15930
+ name: "doctor",
15931
+ description: "check gji installation and configuration health"
15932
+ },
15413
15933
  { name: "completion", description: "print shell completion definitions" },
15414
15934
  { name: "pr", description: "fetch a pull request into a linked worktree" },
15415
15935
  { name: "back", description: "navigate to the previously visited worktree" },
@@ -15462,6 +15982,70 @@ function renderBashCompletion() {
15462
15982
  command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
15463
15983
  }
15464
15984
 
15985
+ __gji_pr_targets_cache=""
15986
+ __gji_pr_targets_cache_at=0
15987
+ __gji_pr_targets_cache_loaded=0
15988
+ __gji_pr_targets_cache_repo=""
15989
+
15990
+ __gji_run_bounded() {
15991
+ local output_file pid status attempt
15992
+ output_file=$(mktemp "\${TMPDIR:-/tmp}/gji-completion.XXXXXX") || return 124
15993
+ "$@" >"$output_file" 2>/dev/null &
15994
+ pid=$!
15995
+ for ((attempt = 0; attempt < 20; attempt++)); do
15996
+ if ! kill -0 "$pid" 2>/dev/null; then
15997
+ wait "$pid"
15998
+ status=$?
15999
+ cat "$output_file"
16000
+ rm -f "$output_file"
16001
+ return "$status"
16002
+ fi
16003
+ sleep 0.1
16004
+ done
16005
+ kill "$pid" 2>/dev/null
16006
+ wait "$pid" 2>/dev/null
16007
+ rm -f "$output_file"
16008
+ return 124
16009
+ }
16010
+
16011
+ __gji_pr_targets() {
16012
+ local now repo_key branches targets
16013
+ now=$(date +%s)
16014
+ repo_key=$(command git rev-parse --show-toplevel 2>/dev/null)
16015
+ if [ "$__gji_pr_targets_cache_loaded" -eq 1 ] && [ "$repo_key" = "$__gji_pr_targets_cache_repo" ] && [ "$((now - __gji_pr_targets_cache_at))" -lt 30 ]; then
16016
+ printf '%s
16017
+ ' "$__gji_pr_targets_cache"
16018
+ return
16019
+ fi
16020
+
16021
+ branches="$(__gji_worktree_branches)"
16022
+ targets=""
16023
+ if [ -n "$repo_key" ] && command -v gh >/dev/null 2>&1; then
16024
+ targets=$(__gji_run_bounded env GH_PROMPT_DISABLED=1 gh pr list --state open --json number --limit 100 |
16025
+ tr '{},' '\\n' | sed -n 's/.*"number":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')
16026
+ elif [ -n "$repo_key" ] && command -v glab >/dev/null 2>&1; then
16027
+ targets=$(__gji_run_bounded env GLAB_NON_INTERACTIVE=1 glab mr list --state opened --output json --per-page 100 |
16028
+ tr '{},' '\\n' | sed -n 's/.*"iid":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')
16029
+ elif [ -n "$repo_key" ] && command -v bb >/dev/null 2>&1; then
16030
+ targets=$(__gji_run_bounded bb pr list --state OPEN --format json |
16031
+ tr '{},' '\\n' | sed -n 's/.*"id":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')
16032
+ fi
16033
+
16034
+ __gji_pr_targets_cache="$branches"
16035
+ if [ -n "$targets" ]; then
16036
+ if [ -n "$__gji_pr_targets_cache" ]; then
16037
+ __gji_pr_targets_cache+=$'
16038
+ '
16039
+ fi
16040
+ __gji_pr_targets_cache+="$targets"
16041
+ fi
16042
+ __gji_pr_targets_cache_at="$now"
16043
+ __gji_pr_targets_cache_loaded=1
16044
+ __gji_pr_targets_cache_repo="$repo_key"
16045
+ printf '%s
16046
+ ' "$__gji_pr_targets_cache"
16047
+ }
16048
+
15465
16049
  _gji_completion() {
15466
16050
  local cur command_name
15467
16051
  COMPREPLY=()
@@ -15479,13 +16063,37 @@ _gji_completion() {
15479
16063
  COMPREPLY=( $(compgen -W "--detached --force --open --editor --dry-run --json --help" -- "$cur") )
15480
16064
  ;;
15481
16065
  init)
15482
- COMPREPLY=( $(compgen -W "${shells} --write --help" -- "$cur") )
16066
+ COMPREPLY=( $(compgen -W "${shells} --write --json --help" -- "$cur") )
16067
+ ;;
16068
+ doctor)
16069
+ COMPREPLY=( $(compgen -W "--fix --yes --json --help" -- "$cur") )
15483
16070
  ;;
15484
16071
  completion)
15485
16072
  COMPREPLY=( $(compgen -W "${shells} --help" -- "$cur") )
15486
16073
  ;;
15487
16074
  pr)
15488
- COMPREPLY=( $(compgen -W "--dry-run --json --help" -- "$cur") )
16075
+ if [ "$COMP_CWORD" -eq 2 ]; then
16076
+ COMPREPLY=( $(compgen -W "open --dry-run --json --help" -- "$cur") )
16077
+ elif [ "\${COMP_WORDS[2]}" = "open" ]; then
16078
+ local has_select=0 has_target=0 word
16079
+ for word in "\${COMP_WORDS[@]:3:$((COMP_CWORD - 3))}"; do
16080
+ if [ "$word" = "--select" ]; then has_select=1; fi
16081
+ if [[ "$word" != -* && "$word" != "" ]]; then has_target=1; fi
16082
+ done
16083
+ if [[ "$cur" == -* ]]; then
16084
+ if [[ "$has_select" -eq 1 || "$has_target" -eq 1 ]]; then
16085
+ COMPREPLY=( $(compgen -W "--help" -- "$cur") )
16086
+ else
16087
+ COMPREPLY=( $(compgen -W "--select --help" -- "$cur") )
16088
+ fi
16089
+ elif [[ "$has_select" -eq 1 || "$has_target" -eq 1 ]]; then
16090
+ COMPREPLY=()
16091
+ else
16092
+ COMPREPLY=( $(compgen -W "$(__gji_pr_targets) --select --help" -- "$cur") )
16093
+ fi
16094
+ else
16095
+ COMPREPLY=( $(compgen -W "--dry-run --json --help" -- "$cur") )
16096
+ fi
15489
16097
  ;;
15490
16098
  back)
15491
16099
  COMPREPLY=( $(compgen -W "--print --help" -- "$cur") )
@@ -15570,6 +16178,103 @@ function renderFishCompletion() {
15570
16178
  command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
15571
16179
  end
15572
16180
 
16181
+ set -g __gji_pr_targets_cache
16182
+ set -g __gji_pr_targets_cache_at 0
16183
+ set -g __gji_pr_targets_cache_loaded 0
16184
+ set -g __gji_pr_targets_cache_repo
16185
+
16186
+ function __gji_run_bounded
16187
+ set -l output_file (mktemp "$TMPDIR/gji-completion.XXXXXX" 2>/dev/null)
16188
+ if test -z "$output_file"
16189
+ set output_file (mktemp "/tmp/gji-completion.XXXXXX" 2>/dev/null)
16190
+ end
16191
+ if test -z "$output_file"
16192
+ return 124
16193
+ end
16194
+
16195
+ command $argv >$output_file 2>/dev/null &
16196
+ set -l pid $last_pid
16197
+ for attempt in (seq 1 20)
16198
+ if not kill -0 $pid 2>/dev/null
16199
+ wait $pid
16200
+ set -l exit_status $status
16201
+ command cat $output_file
16202
+ command rm -f $output_file
16203
+ return $exit_status
16204
+ end
16205
+ sleep 0.1
16206
+ end
16207
+ kill $pid 2>/dev/null
16208
+ wait $pid 2>/dev/null
16209
+ command rm -f $output_file
16210
+ return 124
16211
+ end
16212
+
16213
+ function __gji_pr_targets
16214
+ set -l now (date +%s)
16215
+ set -l repo_key (command git rev-parse --show-toplevel 2>/dev/null)
16216
+ if test $__gji_pr_targets_cache_loaded -eq 1; and test "$repo_key" = "$__gji_pr_targets_cache_repo"; and test (math $now - $__gji_pr_targets_cache_at) -lt 30
16217
+ printf '%s
16218
+ ' $__gji_pr_targets_cache
16219
+ return
16220
+ end
16221
+
16222
+ set -l branches (__gji_worktree_branches)
16223
+ set -l targets
16224
+ if test -n "$repo_key"; and command -q gh
16225
+ set targets (__gji_run_bounded env GH_PROMPT_DISABLED=1 gh pr list --state open --json number --limit 100 |
16226
+ string replace -a -r '[{},]' '\\n' |
16227
+ string match -r '"number"[[:space:]]*:[[:space:]]*[0-9]+' | string replace -r '.*:[[:space:]]*' '')
16228
+ else if test -n "$repo_key"; and command -q glab
16229
+ set targets (__gji_run_bounded env GLAB_NON_INTERACTIVE=1 glab mr list --state opened --output json --per-page 100 |
16230
+ string replace -a -r '[{},]' '\\n' |
16231
+ string match -r '"iid"[[:space:]]*:[[:space:]]*[0-9]+' | string replace -r '.*:[[:space:]]*' '')
16232
+ else if test -n "$repo_key"; and command -q bb
16233
+ set targets (__gji_run_bounded bb pr list --state OPEN --format json |
16234
+ string replace -a -r '[{},]' '\\n' |
16235
+ string match -r '"id"[[:space:]]*:[[:space:]]*[0-9]+' | string replace -r '.*:[[:space:]]*' '')
16236
+ end
16237
+
16238
+ set -g __gji_pr_targets_cache $branches $targets
16239
+ set -g __gji_pr_targets_cache_at $now
16240
+ set -g __gji_pr_targets_cache_loaded 1
16241
+ set -g __gji_pr_targets_cache_repo $repo_key
16242
+ printf '%s
16243
+ ' $__gji_pr_targets_cache
16244
+ end
16245
+
16246
+ function __gji_should_complete_pr_select
16247
+ set -l tokens (commandline -opc)
16248
+ if test (count $tokens) -lt 3; or test $tokens[2] != pr; or test $tokens[3] != open
16249
+ return 1
16250
+ end
16251
+ for token in $tokens[4..-1]
16252
+ if test $token = --select; or not string match -q -- '-*' $token
16253
+ return 1
16254
+ end
16255
+ end
16256
+ return 0
16257
+ end
16258
+
16259
+ function __gji_should_complete_pr_target
16260
+ set -l tokens (commandline -opc)
16261
+ if test (count $tokens) -lt 3; or test $tokens[2] != pr; or test $tokens[3] != open
16262
+ return 1
16263
+ end
16264
+ if string match -q -- '-*' (commandline -ct)
16265
+ return 1
16266
+ end
16267
+ for token in $tokens[4..-1]
16268
+ if test $token = --select
16269
+ return 1
16270
+ end
16271
+ if not string match -q -- '-*' $token
16272
+ return 1
16273
+ end
16274
+ end
16275
+ return 0
16276
+ end
16277
+
15573
16278
  function __gji_should_complete_config_action
15574
16279
  set -l tokens (commandline -opc)
15575
16280
  test (count $tokens) -eq 2
@@ -15599,14 +16304,22 @@ complete -c gji -n '__fish_seen_subcommand_from new' -l dry-run -d 'show what wo
15599
16304
  complete -c gji -n '__fish_seen_subcommand_from new' -l json -d 'emit JSON on success or error instead of human-readable output'
15600
16305
 
15601
16306
  complete -c gji -n '__fish_seen_subcommand_from init' -l write -d 'write the integration to the shell config file'
16307
+ complete -c gji -n '__fish_seen_subcommand_from init' -l json -d 'emit a JSON error in non-interactive mode'
15602
16308
  ${shellLines}
15603
16309
 
16310
+ complete -c gji -n '__fish_seen_subcommand_from doctor' -l fix -d 'apply safe automatic fixes after showing the plan'
16311
+ complete -c gji -n '__fish_seen_subcommand_from doctor' -l yes -d 'apply --fix without prompting'
16312
+ complete -c gji -n '__fish_seen_subcommand_from doctor' -l json -d 'emit diagnostic checks as JSON'
16313
+
15604
16314
  complete -c gji -n '__fish_seen_subcommand_from completion' -a 'bash' -d 'shell'
15605
16315
  complete -c gji -n '__fish_seen_subcommand_from completion' -a 'fish' -d 'shell'
15606
16316
  complete -c gji -n '__fish_seen_subcommand_from completion' -a 'zsh' -d 'shell'
15607
16317
 
15608
- complete -c gji -n '__fish_seen_subcommand_from pr' -l dry-run -d 'show what would be created without executing any git commands or writing files'
15609
- complete -c gji -n '__fish_seen_subcommand_from pr' -l json -d 'emit JSON on success or error instead of human-readable output'
16318
+ complete -c gji -n '__fish_seen_subcommand_from pr; and test (commandline -opc)[3] != open' -l dry-run -d 'show what would be created without executing any git commands or writing files'
16319
+ complete -c gji -n '__fish_seen_subcommand_from pr; and test (commandline -opc)[3] != open' -l json -d 'emit JSON on success or error instead of human-readable output'
16320
+ complete -c gji -n '__fish_seen_subcommand_from pr' -a 'open' -d 'open a pull request in the default browser'
16321
+ complete -c gji -n '__gji_should_complete_pr_select' -l select -d 'choose a pull request from any linked worktree'
16322
+ complete -c gji -n '__gji_should_complete_pr_target' -a '(__gji_pr_targets)' -d 'branch or PR number'
15610
16323
 
15611
16324
  complete -c gji -n '__fish_seen_subcommand_from back' -l print -d 'print the resolved worktree path explicitly'
15612
16325
 
@@ -15668,15 +16381,71 @@ __gji_worktree_branches() {
15668
16381
  command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
15669
16382
  }
15670
16383
 
16384
+ typeset -g __gji_pr_targets_cache=()
16385
+ typeset -g __gji_pr_targets_cache_at=0
16386
+ typeset -g __gji_pr_targets_cache_loaded=0
16387
+ typeset -g __gji_pr_targets_cache_repo=""
16388
+
16389
+ __gji_run_bounded() {
16390
+ local output_file pid status attempt
16391
+ output_file=$(mktemp "\${TMPDIR:-/tmp}/gji-completion.XXXXXX") || return 124
16392
+ "$@" >"$output_file" 2>/dev/null &
16393
+ pid=$!
16394
+ for ((attempt = 0; attempt < 20; attempt++)); do
16395
+ if ! kill -0 "$pid" 2>/dev/null; then
16396
+ wait "$pid"
16397
+ status=$?
16398
+ cat "$output_file"
16399
+ rm -f "$output_file"
16400
+ return "$status"
16401
+ fi
16402
+ sleep 0.1
16403
+ done
16404
+ kill "$pid" 2>/dev/null
16405
+ wait "$pid" 2>/dev/null
16406
+ rm -f "$output_file"
16407
+ return 124
16408
+ }
16409
+
16410
+ __gji_pr_targets() {
16411
+ local now repo_key
16412
+ local -a branches targets
16413
+ now=$(date +%s)
16414
+ repo_key=$(command git rev-parse --show-toplevel 2>/dev/null)
16415
+ if (( __gji_pr_targets_cache_loaded == 1 && "\${repo_key}" == "\${__gji_pr_targets_cache_repo}" && now - __gji_pr_targets_cache_at < 30 )); then
16416
+ print -rl -- "\${__gji_pr_targets_cache[@]}"
16417
+ return
16418
+ fi
16419
+
16420
+ branches=("\${(@f)$(__gji_worktree_branches)}")
16421
+ targets=()
16422
+ if [[ -n "\${repo_key}" && $+commands[gh] -eq 1 ]]; then
16423
+ targets=("\${(@f)$(__gji_run_bounded env GH_PROMPT_DISABLED=1 gh pr list --state open --json number --limit 100 |
16424
+ tr '{},' '\\n' | sed -n 's/.*"number":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
16425
+ elif [[ -n "\${repo_key}" && $+commands[glab] -eq 1 ]]; then
16426
+ targets=("\${(@f)$(__gji_run_bounded env GLAB_NON_INTERACTIVE=1 glab mr list --state opened --output json --per-page 100 |
16427
+ tr '{},' '\\n' | sed -n 's/.*"iid":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
16428
+ elif [[ -n "\${repo_key}" && $+commands[bb] -eq 1 ]]; then
16429
+ targets=("\${(@f)$(__gji_run_bounded bb pr list --state OPEN --format json |
16430
+ tr '{},' '\\n' | sed -n 's/.*"id":[[:space:]]*\\([0-9][0-9]*\\).*/\\1/p')}")
16431
+ fi
16432
+
16433
+ __gji_pr_targets_cache=("\${branches[@]}" "\${targets[@]}")
16434
+ __gji_pr_targets_cache_at="$now"
16435
+ __gji_pr_targets_cache_loaded=1
16436
+ __gji_pr_targets_cache_repo="$repo_key"
16437
+ print -rl -- "\${__gji_pr_targets_cache[@]}"
16438
+ }
16439
+
15671
16440
  local context state line
15672
- local -a commands worktree_branches
16441
+ local -a command_entries worktree_branches
15673
16442
 
15674
- commands=(
16443
+ command_entries=(
15675
16444
  ${commandLines}
15676
16445
  )
15677
16446
 
15678
16447
  if (( CURRENT == 2 )); then
15679
- _describe 'command' commands
16448
+ _describe 'command' command_entries
15680
16449
  return
15681
16450
  fi
15682
16451
 
@@ -15685,13 +16454,26 @@ case "\${words[2]}" in
15685
16454
  _arguments '--detached[create a detached worktree without a branch]' '--force[remove and recreate the worktree if the target path already exists]' '--open[open the new worktree in an editor after creation]' '--editor[editor CLI to use with --open (code, cursor, zed, \u2026)]:editor:' '--dry-run[show what would be created without executing any git commands or writing files]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch: '
15686
16455
  ;;
15687
16456
  init)
15688
- _arguments '--write[write the integration to the shell config file]' '2:shell:(${shells})'
16457
+ _arguments '--write[write the integration to the shell config file]' '--json[emit a JSON error in non-interactive mode]' '2:shell:(${shells})'
16458
+ ;;
16459
+ doctor)
16460
+ _arguments '--fix[apply safe automatic fixes after showing the plan]' '--yes[apply --fix without prompting]' '--json[emit diagnostic checks as JSON]'
15689
16461
  ;;
15690
16462
  completion)
15691
16463
  _arguments '2:shell:(${shells})'
15692
16464
  ;;
15693
16465
  pr)
15694
- _arguments '--dry-run[show what would be created without executing any git commands or writing files]' '--json[emit JSON on success or error instead of human-readable output]' '2:ref: '
16466
+ if [[ "\${words[3]}" == "open" ]]; then
16467
+ if (( \${words[(I)--select]} )); then
16468
+ _arguments
16469
+ elif [[ -n "\${words[4]}" && "\${words[4]}" != -* ]]; then
16470
+ _arguments
16471
+ else
16472
+ _arguments '--select[choose a pull request from any linked worktree]' '4:branch or PR number:->pr_targets'
16473
+ fi
16474
+ else
16475
+ _arguments '--dry-run[show what would be created without executing any git commands or writing files]' '--json[emit JSON on success or error instead of human-readable output]' '2:ref:(open)'
16476
+ fi
15695
16477
  ;;
15696
16478
  back)
15697
16479
  _arguments '--print[print the resolved worktree path explicitly]' '2:steps: '
@@ -15754,6 +16536,10 @@ case "$state" in
15754
16536
  worktree_branches=(\${(@f)$(__gji_worktree_branches)})
15755
16537
  _describe 'worktree branch' worktree_branches
15756
16538
  ;;
16539
+ pr_targets)
16540
+ worktree_branches=(\${(@f)$(__gji_pr_targets)})
16541
+ _describe 'branch or PR number' worktree_branches
16542
+ ;;
15757
16543
  config_keys)
15758
16544
  _values 'config key' ${configKeys}
15759
16545
  ;;
@@ -15817,51 +16603,13 @@ function writeJson(stdout2, value) {
15817
16603
  `);
15818
16604
  }
15819
16605
 
15820
- // src/go.ts
15821
- import { basename as basename6 } from "node:path";
15822
-
15823
- // src/warp.ts
15824
- import { realpath as realpath2 } from "node:fs/promises";
15825
- import { basename as basename5, resolve as resolve5 } from "node:path";
15826
-
15827
- // src/new.ts
15828
- import { execFile as execFile3 } from "node:child_process";
15829
- import { mkdir as mkdir4 } from "node:fs/promises";
15830
- import { basename as basename3, dirname as dirname5 } from "node:path";
15831
- import { promisify as promisify4 } from "node:util";
15832
-
15833
- // src/conflict.ts
15834
- import { constants } from "node:fs";
15835
- import { access as access2 } from "node:fs/promises";
15836
- async function pathExists(path9) {
15837
- try {
15838
- await access2(path9, constants.F_OK);
15839
- return true;
15840
- } catch {
15841
- return false;
15842
- }
15843
- }
15844
- async function promptForPathConflict(path9) {
15845
- const choice = await ve({
15846
- message: `Target path already exists: ${path9}`,
15847
- options: [
15848
- {
15849
- value: "abort",
15850
- label: "Abort",
15851
- hint: "Keep the existing directory untouched"
15852
- },
15853
- {
15854
- value: "reuse",
15855
- label: "Reuse path",
15856
- hint: "Print the existing path and stop"
15857
- }
15858
- ]
15859
- });
15860
- if (pD(choice)) {
15861
- return "abort";
15862
- }
15863
- return choice;
15864
- }
16606
+ // src/doctor.ts
16607
+ import { execFile as execFile4 } from "node:child_process";
16608
+ import { constants as constants3 } from "node:fs";
16609
+ import { access as access4, readFile as readFile4 } from "node:fs/promises";
16610
+ import { homedir as homedir5 } from "node:os";
16611
+ import { basename as basename4, dirname as dirname5, join as join6 } from "node:path";
16612
+ import { promisify as promisify5 } from "node:util";
15865
16613
 
15866
16614
  // src/editor.ts
15867
16615
  import { spawn as spawn3 } from "node:child_process";
@@ -15901,14 +16649,774 @@ async function defaultSpawnEditor(cli, args) {
15901
16649
  child.unref();
15902
16650
  }
15903
16651
 
16652
+ // src/repo-registry.ts
16653
+ import { randomUUID } from "node:crypto";
16654
+ import { constants } from "node:fs";
16655
+ import {
16656
+ access as access2,
16657
+ mkdir as mkdir3,
16658
+ readFile as readFile3,
16659
+ realpath,
16660
+ rename,
16661
+ rm as rm2,
16662
+ writeFile as writeFile4
16663
+ } from "node:fs/promises";
16664
+ import { homedir as homedir4 } from "node:os";
16665
+ import { basename as basename3, dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
16666
+ var REGISTRY_FILE_NAME = "repos.json";
16667
+ var MAX_REGISTRY_ENTRIES = 100;
16668
+ var registryLocks = /* @__PURE__ */ new Map();
16669
+ function REGISTRY_FILE_PATH(home = homedir4()) {
16670
+ const configDir = process.env.GJI_CONFIG_DIR;
16671
+ if (configDir) {
16672
+ return join4(resolve4(configDir), REGISTRY_FILE_NAME);
16673
+ }
16674
+ return join4(home, GLOBAL_CONFIG_DIRECTORY, REGISTRY_FILE_NAME);
16675
+ }
16676
+ async function loadRegistry(home = homedir4()) {
16677
+ const path9 = REGISTRY_FILE_PATH(home);
16678
+ try {
16679
+ const raw = await readFile3(path9, "utf8");
16680
+ const parsed = JSON.parse(raw);
16681
+ if (!Array.isArray(parsed)) return [];
16682
+ return parsed.filter(isRegistryEntry);
16683
+ } catch {
16684
+ return [];
16685
+ }
16686
+ }
16687
+ async function canonicalizeRepoPath(repoPath) {
16688
+ try {
16689
+ return await realpath(repoPath);
16690
+ } catch {
16691
+ return resolve4(repoPath);
16692
+ }
16693
+ }
16694
+ async function registerRepo(repoPath, home = homedir4()) {
16695
+ const registryPath = REGISTRY_FILE_PATH(home);
16696
+ await withRegistryLock(registryPath, async () => {
16697
+ const existing = await normalizeRegistryForWrite(await loadRegistry(home));
16698
+ const canonicalRepoPath = await canonicalizeRepoPath(repoPath);
16699
+ if (existing.length > 0 && existing[0].path === canonicalRepoPath) return;
16700
+ const entry = {
16701
+ lastUsed: Date.now(),
16702
+ name: basename3(canonicalRepoPath),
16703
+ path: canonicalRepoPath
16704
+ };
16705
+ const filtered = existing.filter((e2) => e2.path !== canonicalRepoPath);
16706
+ const next = [entry, ...filtered].slice(0, MAX_REGISTRY_ENTRIES);
16707
+ await writeRegistry(next, registryPath);
16708
+ });
16709
+ }
16710
+ async function removeMissingRegistryEntries(paths, home = homedir4()) {
16711
+ if (paths.size === 0) return { removedPaths: [], skippedPaths: [] };
16712
+ const registryPath = REGISTRY_FILE_PATH(home);
16713
+ return withRegistryLock(registryPath, async () => {
16714
+ const entries = await loadRegistry(home);
16715
+ const candidates = entries.filter((entry) => paths.has(entry.path));
16716
+ const status = await Promise.all(
16717
+ candidates.map(async (entry) => ({
16718
+ entry,
16719
+ missing: await isMissingPath(entry.path)
16720
+ }))
16721
+ );
16722
+ const removedPaths = status.filter(({ missing }) => missing).map(({ entry }) => entry.path);
16723
+ const skippedPaths = status.filter(({ missing }) => !missing).map(({ entry }) => entry.path);
16724
+ if (removedPaths.length > 0) {
16725
+ await writeRegistry(
16726
+ entries.filter((entry) => !removedPaths.includes(entry.path)),
16727
+ registryPath
16728
+ );
16729
+ }
16730
+ return { removedPaths, skippedPaths };
16731
+ });
16732
+ }
16733
+ async function withRegistryLock(registryPath, operation) {
16734
+ const previous = registryLocks.get(registryPath);
16735
+ let release;
16736
+ const current = new Promise((resolve6) => {
16737
+ release = resolve6;
16738
+ });
16739
+ registryLocks.set(registryPath, current);
16740
+ if (previous) await previous;
16741
+ try {
16742
+ return await operation();
16743
+ } finally {
16744
+ release();
16745
+ if (registryLocks.get(registryPath) === current) {
16746
+ registryLocks.delete(registryPath);
16747
+ }
16748
+ }
16749
+ }
16750
+ async function isMissingPath(path9) {
16751
+ try {
16752
+ await access2(path9, constants.F_OK);
16753
+ return false;
16754
+ } catch (error) {
16755
+ return isMissingPathError(error);
16756
+ }
16757
+ }
16758
+ function isMissingPathError(error) {
16759
+ if (!(error instanceof Error) || !("code" in error)) return false;
16760
+ const code = error.code;
16761
+ return code === "ENOENT" || code === "ENOTDIR";
16762
+ }
16763
+ async function writeRegistry(entries, registryPath) {
16764
+ await mkdir3(dirname4(registryPath), { recursive: true });
16765
+ const temporaryPath = `${registryPath}.tmp-${process.pid}-${randomUUID()}`;
16766
+ try {
16767
+ await writeFile4(
16768
+ temporaryPath,
16769
+ `${JSON.stringify(entries, null, 2)}
16770
+ `,
16771
+ "utf8"
16772
+ );
16773
+ await rename(temporaryPath, registryPath);
16774
+ } catch (error) {
16775
+ await rm2(temporaryPath, { force: true }).catch(() => void 0);
16776
+ throw error;
16777
+ }
16778
+ }
16779
+ async function normalizeRegistryForWrite(entries) {
16780
+ const normalized = [];
16781
+ const seenPaths = /* @__PURE__ */ new Set();
16782
+ for (const entry of entries) {
16783
+ const canonicalPath = await canonicalizeRepoPath(entry.path);
16784
+ if (seenPaths.has(canonicalPath)) {
16785
+ continue;
16786
+ }
16787
+ seenPaths.add(canonicalPath);
16788
+ normalized.push({
16789
+ ...entry,
16790
+ name: basename3(canonicalPath),
16791
+ path: canonicalPath
16792
+ });
16793
+ }
16794
+ return normalized;
16795
+ }
16796
+ function isRegistryEntry(value) {
16797
+ return typeof value === "object" && value !== null && "path" in value && typeof value.path === "string" && "name" in value && typeof value.name === "string" && "lastUsed" in value && typeof value.lastUsed === "number";
16798
+ }
16799
+
16800
+ // src/shell-setup.ts
16801
+ import { constants as constants2 } from "node:fs";
16802
+ import { access as access3 } from "node:fs/promises";
16803
+ import { delimiter, join as join5 } from "node:path";
16804
+ var SHELL_INTEGRATION_START_MARKER = "# >>> gji init >>>";
16805
+ var SHELL_INTEGRATION_END_MARKER = "# <<< gji init <<<";
16806
+ var ONBOARDING_SHELL_INTEGRATION_START_MARKER = "# >>> gji shell integration >>>";
16807
+ var ONBOARDING_SHELL_INTEGRATION_END_MARKER = "# <<< gji shell integration <<<";
16808
+ async function executableExists(command) {
16809
+ const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => join5(directory, command));
16810
+ for (const path9 of candidates) {
16811
+ try {
16812
+ await access3(path9, constants2.X_OK);
16813
+ return true;
16814
+ } catch {
16815
+ }
16816
+ }
16817
+ return false;
16818
+ }
16819
+ function resolveShellConfigPath(shell, home) {
16820
+ switch (shell) {
16821
+ case "bash":
16822
+ return join5(home, ".bashrc");
16823
+ case "fish":
16824
+ return join5(home, ".config", "fish", "config.fish");
16825
+ case "zsh":
16826
+ return join5(home, ".zshrc");
16827
+ }
16828
+ }
16829
+ function resolveCompletionPath(shell, home) {
16830
+ switch (shell) {
16831
+ case "bash":
16832
+ return join5(
16833
+ home,
16834
+ ".local",
16835
+ "share",
16836
+ "bash-completion",
16837
+ "completions",
16838
+ "gji"
16839
+ );
16840
+ case "fish":
16841
+ return join5(home, ".config", "fish", "completions", "gji.fish");
16842
+ case "zsh":
16843
+ return join5(home, ".zsh", "completions", "_gji");
16844
+ }
16845
+ }
16846
+ function hasShellIntegration(config, shell) {
16847
+ if (config.includes(SHELL_INTEGRATION_START_MARKER) && config.includes(SHELL_INTEGRATION_END_MARKER) || config.includes(ONBOARDING_SHELL_INTEGRATION_START_MARKER) && config.includes(ONBOARDING_SHELL_INTEGRATION_END_MARKER)) {
16848
+ return true;
16849
+ }
16850
+ if (shell === "fish") return false;
16851
+ return new RegExp(
16852
+ `^[\\t ]*eval\\s+["']?\\$\\(\\s*gji\\s+init\\s+${shell}\\s*\\)["']?\\s*(?:#.*)?$`,
16853
+ "m"
16854
+ ).test(config);
16855
+ }
16856
+
16857
+ // src/doctor.ts
16858
+ var execFileAsync4 = promisify5(execFile4);
16859
+ var MINIMUM_GIT_VERSION = { major: 2, minor: 17 };
16860
+ async function runDoctorCommand(options) {
16861
+ if (options.yes && !options.fix) {
16862
+ const message = "--yes requires --fix";
16863
+ if (options.json) {
16864
+ (options.stderr ?? options.stdout)(
16865
+ `${JSON.stringify({ error: message })}
16866
+ `
16867
+ );
16868
+ } else {
16869
+ (options.stderr ?? options.stdout)(`gji doctor: ${message}
16870
+ `);
16871
+ }
16872
+ return 1;
16873
+ }
16874
+ const home = options.home ?? homedir5();
16875
+ const shell = resolveSupportedShell(
16876
+ void 0,
16877
+ options.shell ?? process.env.SHELL
16878
+ );
16879
+ let inspection = await collectDoctorInspection(options.cwd, home, shell);
16880
+ let fixes = [];
16881
+ if (options.fix) {
16882
+ fixes = buildDoctorFixes(inspection.missingRegistryPaths);
16883
+ if (fixes.length > 0) {
16884
+ const approval = await requestFixApproval(fixes, options);
16885
+ if (approval === "apply") {
16886
+ fixes = await applyDoctorFixes(fixes, home);
16887
+ } else {
16888
+ fixes = fixes.map((fix) => ({
16889
+ ...fix,
16890
+ status: approval,
16891
+ hint: approval === "pending" ? "re-run with --yes to apply this fix without a prompt" : "run gji doctor --fix again to review this fix"
16892
+ }));
16893
+ }
16894
+ }
16895
+ inspection = await collectDoctorInspection(options.cwd, home, shell);
16896
+ }
16897
+ const problems = inspection.checks.filter(
16898
+ (check) => check.status === "fail"
16899
+ ).length;
16900
+ if (options.json) {
16901
+ const output = options.fix ? { checks: inspection.checks, problems, fixes } : { checks: inspection.checks, problems };
16902
+ options.stdout(`${JSON.stringify(output)}
16903
+ `);
16904
+ } else {
16905
+ options.stdout(renderDoctorChecks(inspection.checks, problems));
16906
+ if (options.fix) {
16907
+ options.stdout(renderDoctorFixes(fixes));
16908
+ }
16909
+ }
16910
+ return problems > 0 ? 1 : 0;
16911
+ }
16912
+ async function collectDoctorInspection(cwd, home, shell) {
16913
+ const repository = await detectRepositoryOrSkip(cwd);
16914
+ const globalConfig = await inspectConfig(
16915
+ GLOBAL_CONFIG_FILE_PATH(home),
16916
+ "global",
16917
+ KNOWN_GLOBAL_CONFIG_KEYS
16918
+ );
16919
+ const localConfig = repository ? await inspectConfig(
16920
+ join6(repository.repoRoot, CONFIG_FILE_NAME),
16921
+ "local",
16922
+ KNOWN_CONFIG_KEYS
16923
+ ) : null;
16924
+ const effectiveConfig = resolveEffectiveConfig(
16925
+ globalConfig.config,
16926
+ localConfig?.config ?? {},
16927
+ repository?.repoRoot,
16928
+ home
16929
+ );
16930
+ const registry = await inspectRegistry(home);
16931
+ return {
16932
+ checks: [
16933
+ await checkGitVersion(),
16934
+ await checkShellIntegration(shell, home),
16935
+ await checkCompletion(shell, home),
16936
+ globalConfig.check,
16937
+ localConfig?.check ?? skippedCheck(
16938
+ "local-config",
16939
+ "local config not checked outside a Git repository"
16940
+ ),
16941
+ await checkWorktreeBase(repository, effectiveConfig, home),
16942
+ registry.check,
16943
+ await checkEditor(effectiveConfig)
16944
+ ],
16945
+ missingRegistryPaths: registry.missingPaths
16946
+ };
16947
+ }
16948
+ function buildDoctorFixes(missingRegistryPaths) {
16949
+ if (missingRegistryPaths.length === 0) return [];
16950
+ const count = missingRegistryPaths.length;
16951
+ return [
16952
+ {
16953
+ id: "repo-registry",
16954
+ message: `remove ${count} stale ${count === 1 ? "repository entry" : "repository entries"} from the registry`,
16955
+ paths: missingRegistryPaths,
16956
+ status: "pending"
16957
+ }
16958
+ ];
16959
+ }
16960
+ async function requestFixApproval(fixes, options) {
16961
+ if (options.yes) return "apply";
16962
+ if (!isDoctorInteractive(options)) return "pending";
16963
+ const confirmed = options.confirmFixes ? await options.confirmFixes(fixes) : await ye({
16964
+ initialValue: true,
16965
+ message: `Apply ${fixes.length} automatic ${fixes.length === 1 ? "fix" : "fixes"} (${fixes.flatMap((fix) => fix.paths ?? []).join(", ")})?`
16966
+ });
16967
+ if (pD(confirmed) || !confirmed) return "declined";
16968
+ return "apply";
16969
+ }
16970
+ function isDoctorInteractive(options) {
16971
+ if (options.json || isHeadless()) return false;
16972
+ if (options.interactive !== void 0) return options.interactive;
16973
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
16974
+ }
16975
+ async function applyDoctorFixes(fixes, home) {
16976
+ return Promise.all(
16977
+ fixes.map(async (fix) => {
16978
+ try {
16979
+ const result = await removeMissingRegistryEntries(
16980
+ new Set(fix.paths ?? []),
16981
+ home
16982
+ );
16983
+ const removedCount = result.removedPaths.length;
16984
+ const skippedCount = result.skippedPaths.length;
16985
+ return {
16986
+ ...fix,
16987
+ message: removedCount === 0 ? "no stale repository entries were removed" : `removed ${removedCount} stale ${removedCount === 1 ? "repository entry" : "repository entries"} from the registry`,
16988
+ status: removedCount === 0 ? "skipped" : "applied",
16989
+ hint: skippedCount > 0 ? `${skippedCount} path(s) were no longer confirmed missing` : void 0
16990
+ };
16991
+ } catch (error) {
16992
+ return {
16993
+ ...fix,
16994
+ status: "failed",
16995
+ hint: error instanceof Error ? error.message : String(error)
16996
+ };
16997
+ }
16998
+ })
16999
+ );
17000
+ }
17001
+ async function detectRepositoryOrSkip(cwd) {
17002
+ try {
17003
+ return await detectRepository(cwd);
17004
+ } catch {
17005
+ return null;
17006
+ }
17007
+ }
17008
+ async function inspectConfig(path9, label, knownKeys) {
17009
+ try {
17010
+ const contents = await readFile4(path9, "utf8");
17011
+ const value = JSON.parse(contents);
17012
+ if (!isConfigObject(value)) {
17013
+ return {
17014
+ check: failedCheck(
17015
+ `${label}-config`,
17016
+ `${label} config must contain a JSON object (${path9})`,
17017
+ "replace the file contents with a JSON object"
17018
+ ),
17019
+ config: {}
17020
+ };
17021
+ }
17022
+ const unknownKeys = Object.keys(value).filter((key) => !knownKeys.has(key));
17023
+ const warning = unknownKeys.length > 0 ? `; warning: unknown ${unknownKeys.length === 1 ? "key" : "keys"} ${unknownKeys.map((key) => `"${key}"`).join(", ")}` : "";
17024
+ return {
17025
+ check: okCheck(
17026
+ `${label}-config`,
17027
+ `${label} config valid (${path9}${warning})`
17028
+ ),
17029
+ config: value
17030
+ };
17031
+ } catch (error) {
17032
+ if (isMissingFileError2(error)) {
17033
+ return {
17034
+ check: okCheck(
17035
+ `${label}-config`,
17036
+ `${label} config not found (optional)`
17037
+ ),
17038
+ config: {}
17039
+ };
17040
+ }
17041
+ return {
17042
+ check: failedCheck(
17043
+ `${label}-config`,
17044
+ `${label} config is invalid (${path9})`,
17045
+ "fix the JSON syntax and run gji doctor again"
17046
+ ),
17047
+ config: {}
17048
+ };
17049
+ }
17050
+ }
17051
+ function isConfigObject(value) {
17052
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17053
+ }
17054
+ function isMissingFileError2(error) {
17055
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
17056
+ }
17057
+ function resolveEffectiveConfig(globalConfig, localConfig, repoRoot, home) {
17058
+ const globalBase = { ...globalConfig };
17059
+ const repos = globalBase.repos;
17060
+ delete globalBase.repos;
17061
+ return {
17062
+ ...globalBase,
17063
+ ...resolvePerRepoConfig(repos, repoRoot, home),
17064
+ ...localConfig
17065
+ };
17066
+ }
17067
+ function resolvePerRepoConfig(repos, repoRoot, home) {
17068
+ if (!repoRoot || !isConfigObject(repos)) {
17069
+ return {};
17070
+ }
17071
+ for (const [path9, config] of Object.entries(repos)) {
17072
+ if (expandTilde2(path9, home) === repoRoot && isConfigObject(config)) {
17073
+ return config;
17074
+ }
17075
+ }
17076
+ return {};
17077
+ }
17078
+ function expandTilde2(path9, home) {
17079
+ if (path9 === "~") return home;
17080
+ if (path9.startsWith("~/")) return join6(home, path9.slice(2));
17081
+ return path9;
17082
+ }
17083
+ async function checkGitVersion() {
17084
+ try {
17085
+ const { stdout: stdout2 } = await execFileAsync4("git", ["--version"]);
17086
+ const version = parseGitVersion(stdout2);
17087
+ if (!version) {
17088
+ return failedCheck(
17089
+ "git-version",
17090
+ "could not parse the installed Git version",
17091
+ "install Git 2.17 or newer"
17092
+ );
17093
+ }
17094
+ if (isGitVersionSupported(version)) {
17095
+ return okCheck("git-version", `git ${version.raw}`);
17096
+ }
17097
+ return failedCheck(
17098
+ "git-version",
17099
+ `git ${version.raw} is too old (requires Git 2.17 or newer)`,
17100
+ "upgrade Git and run gji doctor again"
17101
+ );
17102
+ } catch {
17103
+ return failedCheck(
17104
+ "git-version",
17105
+ "git is not available on PATH",
17106
+ "install Git 2.17 or newer"
17107
+ );
17108
+ }
17109
+ }
17110
+ function parseGitVersion(output) {
17111
+ const match = /(?:^|\s)(\d+)\.(\d+)(?:\.(\d+))?/.exec(output);
17112
+ if (!match) return null;
17113
+ return {
17114
+ major: Number(match[1]),
17115
+ minor: Number(match[2]),
17116
+ raw: match.slice(1).filter(Boolean).join(".")
17117
+ };
17118
+ }
17119
+ function isGitVersionSupported(version) {
17120
+ return version.major > MINIMUM_GIT_VERSION.major || version.major === MINIMUM_GIT_VERSION.major && version.minor >= MINIMUM_GIT_VERSION.minor;
17121
+ }
17122
+ async function checkShellIntegration(shell, home) {
17123
+ if (!shell) {
17124
+ return skippedCheck(
17125
+ "shell-integration",
17126
+ "shell integration not checked (unable to detect a supported shell)"
17127
+ );
17128
+ }
17129
+ const rcPath = resolveShellConfigPath(shell, home);
17130
+ try {
17131
+ const contents = await readFile4(rcPath, "utf8");
17132
+ if (hasShellIntegration(contents, shell)) {
17133
+ return okCheck(
17134
+ "shell-integration",
17135
+ `${shell} integration found in ${rcPath}`
17136
+ );
17137
+ }
17138
+ } catch (error) {
17139
+ if (isMissingFileError2(error)) {
17140
+ return failedCheck(
17141
+ "shell-integration",
17142
+ `shell integration not found because ${rcPath} does not exist`,
17143
+ `create ${rcPath}, then add: eval "$(gji init ${shell})"`
17144
+ );
17145
+ }
17146
+ return failedCheck(
17147
+ "shell-integration",
17148
+ `could not read ${rcPath}`,
17149
+ `add: eval "$(gji init ${shell})"`
17150
+ );
17151
+ }
17152
+ return failedCheck(
17153
+ "shell-integration",
17154
+ `shell integration not found in ${rcPath}`,
17155
+ `add: eval "$(gji init ${shell})"`
17156
+ );
17157
+ }
17158
+ async function checkCompletion(shell, home) {
17159
+ if (!shell) {
17160
+ return skippedCheck(
17161
+ "completion",
17162
+ "shell completion not checked (unable to detect a supported shell)"
17163
+ );
17164
+ }
17165
+ const path9 = resolveCompletionPath(shell, home);
17166
+ if (await pathExists(path9)) {
17167
+ return okCheck("completion", `${shell} completion installed (${path9})`);
17168
+ }
17169
+ return skippedCheck(
17170
+ "completion",
17171
+ `${shell} completion not installed (optional)`,
17172
+ `run: gji completion ${shell} > ${path9}`
17173
+ );
17174
+ }
17175
+ async function checkWorktreeBase(repository, config, home) {
17176
+ if (!repository) {
17177
+ return skippedCheck(
17178
+ "worktree-base",
17179
+ "worktree base not checked outside a Git repository"
17180
+ );
17181
+ }
17182
+ const basePath = resolveWorktreeBase(repository.repoRoot, config, home);
17183
+ const writablePath = await findNearestExistingPath(basePath);
17184
+ if (!writablePath) {
17185
+ return failedCheck(
17186
+ "worktree-base",
17187
+ `worktree base cannot be created (${basePath})`,
17188
+ "create a writable parent directory or update worktreePath"
17189
+ );
17190
+ }
17191
+ try {
17192
+ await access4(writablePath, constants3.W_OK | constants3.X_OK);
17193
+ return okCheck("worktree-base", `worktree base writable (${basePath})`);
17194
+ } catch {
17195
+ return failedCheck(
17196
+ "worktree-base",
17197
+ `worktree base is not writable (${writablePath})`,
17198
+ "update worktreePath to use a writable directory"
17199
+ );
17200
+ }
17201
+ }
17202
+ function resolveWorktreeBase(repoRoot, config, home) {
17203
+ const configuredPath = config.worktreePath;
17204
+ if (typeof configuredPath === "string" && (configuredPath.startsWith("/") || configuredPath.startsWith("~"))) {
17205
+ return expandTilde2(configuredPath, home);
17206
+ }
17207
+ return join6(dirname5(repoRoot), "worktrees", basename4(repoRoot));
17208
+ }
17209
+ async function findNearestExistingPath(path9) {
17210
+ let candidate = path9;
17211
+ while (true) {
17212
+ try {
17213
+ await access4(candidate, constants3.F_OK);
17214
+ return candidate;
17215
+ } catch {
17216
+ const parent = dirname5(candidate);
17217
+ if (parent === candidate) return null;
17218
+ candidate = parent;
17219
+ }
17220
+ }
17221
+ }
17222
+ async function inspectRegistry(home) {
17223
+ const entries = await loadRegistry(home);
17224
+ const missingEntries = await Promise.all(
17225
+ entries.map(async (entry) => ({
17226
+ entry,
17227
+ status: await inspectRegistryPath(entry.path)
17228
+ }))
17229
+ );
17230
+ const missingCount = missingEntries.filter(
17231
+ ({ status }) => status === "missing"
17232
+ ).length;
17233
+ const unreadableCount = missingEntries.filter(
17234
+ ({ status }) => status === "unreadable"
17235
+ ).length;
17236
+ const missingPaths = missingEntries.filter(({ status }) => status === "missing").map(({ entry }) => entry.path);
17237
+ if (missingCount === 0 && unreadableCount === 0) {
17238
+ return {
17239
+ check: okCheck(
17240
+ "repo-registry",
17241
+ `${entries.length} repos registered, all reachable`
17242
+ ),
17243
+ missingPaths
17244
+ };
17245
+ }
17246
+ const messageParts = [];
17247
+ if (missingCount > 0) {
17248
+ messageParts.push(
17249
+ `${missingCount} ${missingCount === 1 ? "path is" : "paths are"} missing`
17250
+ );
17251
+ }
17252
+ if (unreadableCount > 0) {
17253
+ messageParts.push(
17254
+ `${unreadableCount} ${unreadableCount === 1 ? "path is" : "paths are"} not accessible`
17255
+ );
17256
+ }
17257
+ return {
17258
+ check: failedCheck(
17259
+ "repo-registry",
17260
+ `${entries.length} repos registered, ${messageParts.join(", ")}`,
17261
+ missingCount > 0 ? `remove confirmed stale entries from ${REGISTRY_FILE_PATH(home)}; check permissions for inaccessible paths` : "check permissions for inaccessible paths before removing registry entries"
17262
+ ),
17263
+ missingPaths
17264
+ };
17265
+ }
17266
+ async function inspectRegistryPath(path9) {
17267
+ try {
17268
+ await access4(path9, constants3.F_OK);
17269
+ return "exists";
17270
+ } catch (error) {
17271
+ if (isMissingPathError2(error)) return "missing";
17272
+ return "unreadable";
17273
+ }
17274
+ }
17275
+ function isMissingPathError2(error) {
17276
+ if (!(error instanceof Error) || !("code" in error)) return false;
17277
+ const code = error.code;
17278
+ return code === "ENOENT" || code === "ENOTDIR";
17279
+ }
17280
+ async function pathExists(path9) {
17281
+ try {
17282
+ await access4(path9, constants3.F_OK);
17283
+ return true;
17284
+ } catch {
17285
+ return false;
17286
+ }
17287
+ }
17288
+ async function checkEditor(config) {
17289
+ const editor = config.editor;
17290
+ if (typeof editor !== "string" || editor.length === 0) {
17291
+ return skippedCheck("editor", "editor not configured (optional)");
17292
+ }
17293
+ if (await executableExists(editor)) {
17294
+ return okCheck("editor", `editor "${editor}" found on PATH`);
17295
+ }
17296
+ const knownEditor = EDITORS.some(({ cli }) => cli === editor);
17297
+ return failedCheck(
17298
+ "editor",
17299
+ `editor "${editor}" was not found on PATH`,
17300
+ knownEditor ? `install ${editor} or choose another editor with: gji open --save` : "choose another editor with: gji open --save"
17301
+ );
17302
+ }
17303
+ function okCheck(id, message) {
17304
+ return { id, message, status: "ok" };
17305
+ }
17306
+ function failedCheck(id, message, hint) {
17307
+ return { hint, id, message, status: "fail" };
17308
+ }
17309
+ function skippedCheck(id, message, hint) {
17310
+ return { hint, id, message, status: "skip" };
17311
+ }
17312
+ function renderDoctorChecks(checks, problems) {
17313
+ const lines = ["gji doctor", ""];
17314
+ for (const check of checks) {
17315
+ lines.push(` ${statusSymbol(check.status)} ${check.message}`);
17316
+ if (check.hint) lines.push(` ${check.hint}`);
17317
+ }
17318
+ lines.push(
17319
+ "",
17320
+ `${problems} ${problems === 1 ? "problem" : "problems"} found.`
17321
+ );
17322
+ return `${lines.join("\n")}
17323
+ `;
17324
+ }
17325
+ function renderDoctorFixes(fixes) {
17326
+ const lines = ["", "Automatic fixes:"];
17327
+ if (fixes.length === 0) {
17328
+ lines.push(" No automatic fixes available.");
17329
+ } else {
17330
+ for (const fix of fixes) {
17331
+ lines.push(` ${fixStatusSymbol(fix.status)} ${fix.message}`);
17332
+ if (fix.paths && fix.paths.length > 0) {
17333
+ for (const path9 of fix.paths) lines.push(` ${path9}`);
17334
+ }
17335
+ if (fix.hint) lines.push(` ${fix.hint}`);
17336
+ }
17337
+ }
17338
+ return `${lines.join("\n")}
17339
+ `;
17340
+ }
17341
+ function fixStatusSymbol(status) {
17342
+ switch (status) {
17343
+ case "applied":
17344
+ return "\u2713";
17345
+ case "declined":
17346
+ return "-";
17347
+ case "failed":
17348
+ return "\u2717";
17349
+ case "pending":
17350
+ return "!";
17351
+ case "skipped":
17352
+ return "-";
17353
+ }
17354
+ }
17355
+ function statusSymbol(status) {
17356
+ switch (status) {
17357
+ case "fail":
17358
+ return "\u2717";
17359
+ case "ok":
17360
+ return "\u2713";
17361
+ case "skip":
17362
+ return "-";
17363
+ }
17364
+ }
17365
+
17366
+ // src/go.ts
17367
+ import { basename as basename7 } from "node:path";
17368
+
17369
+ // src/warp.ts
17370
+ import { realpath as realpath2 } from "node:fs/promises";
17371
+ import { basename as basename6, resolve as resolve5 } from "node:path";
17372
+
17373
+ // src/new.ts
17374
+ import { execFile as execFile5 } from "node:child_process";
17375
+ import { mkdir as mkdir5 } from "node:fs/promises";
17376
+ import { basename as basename5, dirname as dirname7 } from "node:path";
17377
+ import { promisify as promisify6 } from "node:util";
17378
+
17379
+ // src/conflict.ts
17380
+ import { constants as constants4 } from "node:fs";
17381
+ import { access as access5 } from "node:fs/promises";
17382
+ async function pathExists2(path9) {
17383
+ try {
17384
+ await access5(path9, constants4.F_OK);
17385
+ return true;
17386
+ } catch {
17387
+ return false;
17388
+ }
17389
+ }
17390
+ async function promptForPathConflict(path9) {
17391
+ const choice = await ve({
17392
+ message: `Target path already exists: ${path9}`,
17393
+ options: [
17394
+ {
17395
+ value: "abort",
17396
+ label: "Abort",
17397
+ hint: "Keep the existing directory untouched"
17398
+ },
17399
+ {
17400
+ value: "reuse",
17401
+ label: "Reuse path",
17402
+ hint: "Print the existing path and stop"
17403
+ }
17404
+ ]
17405
+ });
17406
+ if (pD(choice)) {
17407
+ return "abort";
17408
+ }
17409
+ return choice;
17410
+ }
17411
+
15904
17412
  // src/file-sync.ts
15905
- import { copyFile, mkdir as mkdir3, stat } from "node:fs/promises";
15906
- import { dirname as dirname4, isAbsolute as isAbsolute2, join as join4, normalize } from "node:path";
17413
+ import { copyFile, mkdir as mkdir4, stat } from "node:fs/promises";
17414
+ import { dirname as dirname6, isAbsolute as isAbsolute2, join as join7, normalize } from "node:path";
15907
17415
  async function syncFiles(mainRoot, targetPath, patterns) {
15908
17416
  for (const pattern of patterns) {
15909
17417
  const normalized = validateSyncFilePattern(pattern);
15910
- const sourcePath = join4(mainRoot, normalized);
15911
- const destPath = join4(targetPath, normalized);
17418
+ const sourcePath = join7(mainRoot, normalized);
17419
+ const destPath = join7(targetPath, normalized);
15912
17420
  const sourceExists = await fileExists(sourcePath);
15913
17421
  if (!sourceExists) {
15914
17422
  continue;
@@ -15917,7 +17425,7 @@ async function syncFiles(mainRoot, targetPath, patterns) {
15917
17425
  if (destExists) {
15918
17426
  continue;
15919
17427
  }
15920
- await mkdir3(dirname4(destPath), { recursive: true });
17428
+ await mkdir4(dirname6(destPath), { recursive: true });
15921
17429
  await copyFile(sourcePath, destPath);
15922
17430
  }
15923
17431
  }
@@ -15954,8 +17462,8 @@ function isNotFoundError(error) {
15954
17462
  import { spawn as spawn4 } from "node:child_process";
15955
17463
 
15956
17464
  // src/package-manager.ts
15957
- import { access as access3, readdir } from "node:fs/promises";
15958
- import { join as join5 } from "node:path";
17465
+ import { access as access6, readdir } from "node:fs/promises";
17466
+ import { join as join8 } from "node:path";
15959
17467
  var ENTRIES = [
15960
17468
  // JavaScript / TypeScript
15961
17469
  { name: "pnpm", signals: ["pnpm-lock.yaml"], command: "pnpm install" },
@@ -16085,7 +17593,7 @@ async function detectPackageManager(repoRoot) {
16085
17593
  async function matchesExact(repoRoot, signals) {
16086
17594
  for (const signal of signals) {
16087
17595
  try {
16088
- await access3(join5(repoRoot, signal));
17596
+ await access6(join8(repoRoot, signal));
16089
17597
  return true;
16090
17598
  } catch {
16091
17599
  }
@@ -16249,7 +17757,7 @@ function isPlainObject2(value) {
16249
17757
  }
16250
17758
 
16251
17759
  // src/new.ts
16252
- var execFileAsync3 = promisify4(execFile3);
17760
+ var execFileAsync5 = promisify6(execFile5);
16253
17761
  var NEW_OUTPUT_FILE_ENV = "GJI_NEW_OUTPUT_FILE";
16254
17762
  function createNewCommand(dependencies = {}) {
16255
17763
  const createBranchPlaceholder = dependencies.createBranchPlaceholder ?? generateBranchPlaceholder;
@@ -16317,11 +17825,11 @@ function createNewCommand(dependencies = {}) {
16317
17825
  worktreeName,
16318
17826
  configuredBasePath
16319
17827
  );
16320
- if (!usesGeneratedDetachedName && await pathExists(worktreePath)) {
17828
+ if (!usesGeneratedDetachedName && await pathExists2(worktreePath)) {
16321
17829
  if (options.force) {
16322
17830
  if (!options.dryRun) {
16323
17831
  try {
16324
- await execFileAsync3(
17832
+ await execFileAsync5(
16325
17833
  "git",
16326
17834
  ["worktree", "remove", "--force", worktreePath],
16327
17835
  { cwd: repository.repoRoot }
@@ -16342,7 +17850,7 @@ function createNewCommand(dependencies = {}) {
16342
17850
  }
16343
17851
  if (!options.detached) {
16344
17852
  try {
16345
- await execFileAsync3("git", ["branch", "-D", worktreeName], {
17853
+ await execFileAsync5("git", ["branch", "-D", worktreeName], {
16346
17854
  cwd: repository.repoRoot
16347
17855
  });
16348
17856
  } catch {
@@ -16399,9 +17907,9 @@ function createNewCommand(dependencies = {}) {
16399
17907
  }
16400
17908
  return 0;
16401
17909
  }
16402
- await mkdir4(dirname5(worktreePath), { recursive: true });
17910
+ await mkdir5(dirname7(worktreePath), { recursive: true });
16403
17911
  const gitArgs = options.detached ? ["worktree", "add", "--detach", worktreePath] : await localBranchExists(repository.repoRoot, worktreeName) ? ["worktree", "add", worktreePath, worktreeName] : ["worktree", "add", "-b", worktreeName, worktreePath];
16404
- await execFileAsync3("git", gitArgs, { cwd: repository.repoRoot });
17912
+ await execFileAsync5("git", gitArgs, { cwd: repository.repoRoot });
16405
17913
  const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter(
16406
17914
  (p2) => typeof p2 === "string"
16407
17915
  ) : [];
@@ -16430,7 +17938,7 @@ function createNewCommand(dependencies = {}) {
16430
17938
  {
16431
17939
  branch: worktreeName,
16432
17940
  path: worktreePath,
16433
- repo: basename3(repository.repoRoot)
17941
+ repo: basename5(repository.repoRoot)
16434
17942
  },
16435
17943
  options.stderr
16436
17944
  );
@@ -16582,7 +18090,7 @@ async function resolveUniqueDetachedWorktreePath(repoRoot, baseName, basePath) {
16582
18090
  candidateName,
16583
18091
  basePath
16584
18092
  );
16585
- if (!await pathExists(candidatePath)) {
18093
+ if (!await pathExists2(candidatePath)) {
16586
18094
  return candidatePath;
16587
18095
  }
16588
18096
  attempt += 1;
@@ -16605,7 +18113,7 @@ async function defaultPromptForBranch(placeholder) {
16605
18113
  }
16606
18114
  async function localBranchExists(repoRoot, branchName) {
16607
18115
  try {
16608
- await execFileAsync3(
18116
+ await execFileAsync5(
16609
18117
  "git",
16610
18118
  ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
16611
18119
  { cwd: repoRoot }
@@ -16650,74 +18158,6 @@ async function openWorktree(worktreePath, editorCli, spawnFn, stderr) {
16650
18158
  }
16651
18159
  }
16652
18160
 
16653
- // src/repo-registry.ts
16654
- import { mkdir as mkdir5, readFile as readFile3, realpath, writeFile as writeFile4 } from "node:fs/promises";
16655
- import { homedir as homedir4 } from "node:os";
16656
- import { basename as basename4, dirname as dirname6, join as join6, resolve as resolve4 } from "node:path";
16657
- var REGISTRY_FILE_NAME = "repos.json";
16658
- var MAX_REGISTRY_ENTRIES = 100;
16659
- function REGISTRY_FILE_PATH(home = homedir4()) {
16660
- const configDir = process.env.GJI_CONFIG_DIR;
16661
- if (configDir) {
16662
- return join6(resolve4(configDir), REGISTRY_FILE_NAME);
16663
- }
16664
- return join6(home, GLOBAL_CONFIG_DIRECTORY, REGISTRY_FILE_NAME);
16665
- }
16666
- async function loadRegistry(home = homedir4()) {
16667
- const path9 = REGISTRY_FILE_PATH(home);
16668
- try {
16669
- const raw = await readFile3(path9, "utf8");
16670
- const parsed = JSON.parse(raw);
16671
- if (!Array.isArray(parsed)) return [];
16672
- return parsed.filter(isRegistryEntry);
16673
- } catch {
16674
- return [];
16675
- }
16676
- }
16677
- async function canonicalizeRepoPath(repoPath) {
16678
- try {
16679
- return await realpath(repoPath);
16680
- } catch {
16681
- return resolve4(repoPath);
16682
- }
16683
- }
16684
- async function registerRepo(repoPath, home = homedir4()) {
16685
- const registryPath = REGISTRY_FILE_PATH(home);
16686
- const existing = await normalizeRegistryForWrite(await loadRegistry(home));
16687
- const canonicalRepoPath = await canonicalizeRepoPath(repoPath);
16688
- if (existing.length > 0 && existing[0].path === canonicalRepoPath) return;
16689
- const entry = {
16690
- lastUsed: Date.now(),
16691
- name: basename4(canonicalRepoPath),
16692
- path: canonicalRepoPath
16693
- };
16694
- const filtered = existing.filter((e2) => e2.path !== canonicalRepoPath);
16695
- const next = [entry, ...filtered].slice(0, MAX_REGISTRY_ENTRIES);
16696
- await mkdir5(dirname6(registryPath), { recursive: true });
16697
- await writeFile4(registryPath, `${JSON.stringify(next, null, 2)}
16698
- `, "utf8");
16699
- }
16700
- async function normalizeRegistryForWrite(entries) {
16701
- const normalized = [];
16702
- const seenPaths = /* @__PURE__ */ new Set();
16703
- for (const entry of entries) {
16704
- const canonicalPath = await canonicalizeRepoPath(entry.path);
16705
- if (seenPaths.has(canonicalPath)) {
16706
- continue;
16707
- }
16708
- seenPaths.add(canonicalPath);
16709
- normalized.push({
16710
- ...entry,
16711
- name: basename4(canonicalPath),
16712
- path: canonicalPath
16713
- });
16714
- }
16715
- return normalized;
16716
- }
16717
- function isRegistryEntry(value) {
16718
- return typeof value === "object" && value !== null && "path" in value && typeof value.path === "string" && "name" in value && typeof value.name === "string" && "lastUsed" in value && typeof value.lastUsed === "number";
16719
- }
16720
-
16721
18161
  // src/warp.ts
16722
18162
  var WARP_OUTPUT_FILE_ENV = "GJI_WARP_OUTPUT_FILE";
16723
18163
  async function runWarpCommand(options) {
@@ -16749,7 +18189,8 @@ async function runWarpNavigate(options) {
16749
18189
  const target = await resolveWarpTarget({
16750
18190
  ...options,
16751
18191
  commandName: "gji warp",
16752
- json: options.json
18192
+ json: options.json,
18193
+ queryPullRequests: options.queryPullRequests
16753
18194
  });
16754
18195
  if (!target) return 1;
16755
18196
  if (options.json) {
@@ -16829,7 +18270,7 @@ async function deduplicateRegistryForNew(registry) {
16829
18270
  seenPaths.add(canonicalPath);
16830
18271
  deduplicated.push({
16831
18272
  ...entry,
16832
- name: basename5(canonicalPath),
18273
+ name: basename6(canonicalPath),
16833
18274
  path: canonicalPath
16834
18275
  });
16835
18276
  }
@@ -16866,15 +18307,16 @@ async function resolveWarpTarget(options) {
16866
18307
  const results = await Promise.allSettled(
16867
18308
  registry.map(async (entry) => {
16868
18309
  const worktrees = await listWorktrees(entry.path);
16869
- return { repoName: entry.name, worktrees };
18310
+ return { repoName: entry.name, repoRoot: entry.path, worktrees };
16870
18311
  })
16871
18312
  );
16872
18313
  const allItems = [];
16873
18314
  for (const result of results) {
16874
18315
  if (result.status === "rejected") continue;
16875
- const { repoName, worktrees } = result.value;
18316
+ const { repoName, repoRoot, worktrees } = result.value;
16876
18317
  for (const worktree of worktrees) {
16877
18318
  allItems.push({
18319
+ repoRoot,
16878
18320
  repoName,
16879
18321
  worktree: {
16880
18322
  ...worktree,
@@ -16888,6 +18330,7 @@ async function resolveWarpTarget(options) {
16888
18330
  return null;
16889
18331
  }
16890
18332
  const promptSources = allItems.map((item) => ({
18333
+ repoRoot: item.repoRoot,
16891
18334
  repoName: item.repoName,
16892
18335
  worktree: item.worktree
16893
18336
  }));
@@ -16899,7 +18342,9 @@ async function resolveWarpTarget(options) {
16899
18342
  }
16900
18343
  return { branch: match.worktree.branch, path: match.worktree.path };
16901
18344
  }
16902
- const promptEntries = await buildWorktreePromptEntries(promptSources);
18345
+ const promptEntries = await buildWorktreePromptEntries(promptSources, {
18346
+ queryPullRequests: options.queryPullRequests
18347
+ });
16903
18348
  const path9 = await promptForWarpTarget(promptEntries);
16904
18349
  if (!path9) {
16905
18350
  options.stderr("Aborted\n");
@@ -16947,10 +18392,13 @@ function createGoCommand(dependencies = {}) {
16947
18392
  return 1;
16948
18393
  }
16949
18394
  const promptSources = worktrees.map((worktree) => ({
18395
+ repoRoot: repository.repoRoot,
16950
18396
  repoName: repository.repoName,
16951
18397
  worktree
16952
18398
  }));
16953
- const promptEntries = options.branch ? [] : await buildWorktreePromptEntries(promptSources);
18399
+ const promptEntries = options.branch ? [] : await buildWorktreePromptEntries(promptSources, {
18400
+ queryPullRequests: dependencies.queryPullRequests
18401
+ });
16954
18402
  const queried = options.branch ? resolveWorktreeQuery(promptSources, options.branch) : null;
16955
18403
  const prompted = options.branch ? null : await prompt(promptEntries);
16956
18404
  const resolvedPath = options.branch ? queried?.worktree.path : prompted ?? void 0;
@@ -16978,7 +18426,7 @@ function createGoCommand(dependencies = {}) {
16978
18426
  {
16979
18427
  branch: chosenWorktree?.branch ?? void 0,
16980
18428
  path: resolvedPath,
16981
- repo: basename6(repository.repoRoot)
18429
+ repo: basename7(repository.repoRoot)
16982
18430
  },
16983
18431
  options.stderr
16984
18432
  );
@@ -17009,11 +18457,11 @@ async function runHistoryCommand(options) {
17009
18457
  }
17010
18458
 
17011
18459
  // src/init.ts
17012
- import { mkdir as mkdir6, readFile as readFile4, writeFile as writeFile5 } from "node:fs/promises";
17013
- import { homedir as homedir5 } from "node:os";
17014
- import { dirname as dirname7, join as join7 } from "node:path";
17015
- var START_MARKER = "# >>> gji init >>>";
17016
- var END_MARKER = "# <<< gji init <<<";
18460
+ import { constants as constants5 } from "node:fs";
18461
+ import { access as access7, mkdir as mkdir6, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
18462
+ import { homedir as homedir6 } from "node:os";
18463
+ import { dirname as dirname8 } from "node:path";
18464
+ var ZSH_COMPLETION_PATH_LINE = "fpath=(~/.zsh/completions $fpath)";
17017
18465
  var SHELL_WRAPPED_COMMANDS = [
17018
18466
  {
17019
18467
  bypassOptions: ["--help"],
@@ -17064,10 +18512,256 @@ var SHELL_WRAPPED_COMMANDS = [
17064
18512
  names: ["warp"],
17065
18513
  tempPrefix: "gji-warp"
17066
18514
  }
17067
- ];
17068
- async function runInitCommand(options) {
18515
+ ];
18516
+ async function runInitCommand(options) {
18517
+ if (options.shell === void 0) {
18518
+ return runOnboardingInitCommand(options);
18519
+ }
18520
+ return runLegacyInitCommand(options);
18521
+ }
18522
+ async function runOnboardingInitCommand(options) {
18523
+ if (options.json || isHeadless() || !canRunOnboarding(options)) {
18524
+ return writeNonInteractiveInitError(options);
18525
+ }
18526
+ const home = options.home ?? homedir6();
18527
+ const context = {
18528
+ detectedShell: resolveSupportedShell(void 0, process.env.SHELL),
18529
+ home
18530
+ };
18531
+ if (options.promptForOnboarding === void 0) {
18532
+ return runDefaultOnboarding(context);
18533
+ }
18534
+ const result = await options.promptForOnboarding(context);
18535
+ if (!result) {
18536
+ options.stderr?.("Aborted.\n");
18537
+ return 1;
18538
+ }
18539
+ await applyOnboardingResult(result, home);
18540
+ return 0;
18541
+ }
18542
+ function canRunOnboarding(options) {
18543
+ return options.interactive ?? (options.promptForOnboarding !== void 0 || process.stdin.isTTY === true && process.stdout.isTTY === true);
18544
+ }
18545
+ function writeNonInteractiveInitError(options) {
18546
+ const error = "run `gji init <shell> --write` in non-interactive mode";
18547
+ options.stderr?.(
18548
+ options.json ? `${JSON.stringify({ error })}
18549
+ ` : `${error}
18550
+ `
18551
+ );
18552
+ return 1;
18553
+ }
18554
+ async function runDefaultOnboarding(context) {
18555
+ Ie("gji init");
18556
+ const shell = await ve({
18557
+ message: "Which shell should gji configure?",
18558
+ initialValue: context.detectedShell ?? "zsh",
18559
+ options: [
18560
+ { value: "zsh", label: "zsh" },
18561
+ { value: "bash", label: "bash" },
18562
+ { value: "fish", label: "fish" }
18563
+ ]
18564
+ });
18565
+ if (pD(shell)) return abortDefaultOnboarding();
18566
+ const rcPath = resolveShellConfigPath(shell, context.home);
18567
+ const currentConfig = await readExistingConfig(rcPath);
18568
+ const shellIntegration = await promptForShellIntegration(
18569
+ currentConfig,
18570
+ rcPath,
18571
+ shell
18572
+ );
18573
+ if (shellIntegration === null) return abortDefaultOnboarding();
18574
+ const completionPath = resolveCompletionPath(shell, context.home);
18575
+ const completionAlreadyInstalled = await fileExists2(completionPath);
18576
+ await applyShellIntegration(shell, shellIntegration, context.home);
18577
+ const installCompletion = await promptForCompletion(shell, completionPath);
18578
+ if (installCompletion === null) return abortDefaultOnboarding();
18579
+ if (installCompletion) {
18580
+ await mkdir6(dirname8(completionPath), { recursive: true });
18581
+ await writeFile5(completionPath, renderShellCompletion(shell), "utf8");
18582
+ }
18583
+ if (shell === "zsh" && (completionAlreadyInstalled || installCompletion)) {
18584
+ await ensureZshCompletionPath(rcPath);
18585
+ }
18586
+ const editor = await promptForEditor();
18587
+ if (editor === null) return abortDefaultOnboarding();
18588
+ if (editor) {
18589
+ await updateGlobalConfigKey("editor", editor, context.home);
18590
+ }
18591
+ Se(
18592
+ `Setup complete. Restart your shell or run: source ${rcPath}
18593
+ Verify with: gji doctor`
18594
+ );
18595
+ return 0;
18596
+ }
18597
+ async function applyShellIntegration(shell, choice, home) {
18598
+ if (choice === "install") {
18599
+ await installOnboardingShellIntegration(shell, home);
18600
+ }
18601
+ if (choice !== "skip") {
18602
+ await updateGlobalConfigKey("shellIntegration", true, home);
18603
+ }
18604
+ }
18605
+ function abortDefaultOnboarding() {
18606
+ Se("Aborted.");
18607
+ return 1;
18608
+ }
18609
+ async function promptForShellIntegration(currentConfig, rcPath, shell) {
18610
+ if (currentConfig.includes(ONBOARDING_SHELL_INTEGRATION_START_MARKER) && currentConfig.includes(ONBOARDING_SHELL_INTEGRATION_END_MARKER)) {
18611
+ M2.success(`Shell integration marker found in ${rcPath}; refreshing it.`);
18612
+ return "install";
18613
+ }
18614
+ if (hasShellIntegration(currentConfig, shell)) {
18615
+ M2.success(`Shell integration already installed in ${rcPath} \u2713`);
18616
+ return "existing";
18617
+ }
18618
+ const confirmed = await ye({
18619
+ message: `Install shell integration in ${rcPath}?`,
18620
+ initialValue: true
18621
+ });
18622
+ if (pD(confirmed)) return null;
18623
+ return confirmed ? "install" : "skip";
18624
+ }
18625
+ async function promptForCompletion(shell, completionPath) {
18626
+ if (await fileExists2(completionPath)) {
18627
+ M2.success(`${shell} completion already installed at ${completionPath} \u2713`);
18628
+ return false;
18629
+ }
18630
+ const confirmed = await ye({
18631
+ message: `Install ${shell} completion at ${completionPath}?`,
18632
+ initialValue: true
18633
+ });
18634
+ if (pD(confirmed)) return null;
18635
+ return confirmed;
18636
+ }
18637
+ async function promptForEditor() {
18638
+ const availableEditors = await findAvailableEditors();
18639
+ if (availableEditors.length === 0) {
18640
+ M2.info("No supported editor CLIs found on PATH; skipping editor setup.");
18641
+ return void 0;
18642
+ }
18643
+ const skipValue = "__gji_skip_editor__";
18644
+ const selected = await ve({
18645
+ message: "Which editor should gji use?",
18646
+ options: [
18647
+ ...availableEditors.map(({ cli, name }) => ({
18648
+ value: cli,
18649
+ label: name,
18650
+ hint: cli
18651
+ })),
18652
+ { value: skipValue, label: "Skip" }
18653
+ ]
18654
+ });
18655
+ if (pD(selected)) return null;
18656
+ return selected === skipValue ? void 0 : selected;
18657
+ }
18658
+ async function findAvailableEditors() {
18659
+ const availableEditors = [];
18660
+ for (const editor of EDITORS) {
18661
+ if (await executableExists(editor.cli)) {
18662
+ availableEditors.push(editor);
18663
+ }
18664
+ }
18665
+ return availableEditors;
18666
+ }
18667
+ async function applyOnboardingResult(result, home) {
18668
+ const rcPath = resolveShellConfigPath(result.shell, home);
18669
+ const completionPath = resolveCompletionPath(result.shell, home);
18670
+ const completionAlreadyInstalled = await fileExists2(completionPath);
18671
+ await applyShellIntegration(result.shell, result.shellIntegration, home);
18672
+ if (result.installCompletion) {
18673
+ await mkdir6(dirname8(completionPath), { recursive: true });
18674
+ await writeFile5(
18675
+ completionPath,
18676
+ renderShellCompletion(result.shell),
18677
+ "utf8"
18678
+ );
18679
+ }
18680
+ if (result.shell === "zsh" && (completionAlreadyInstalled || result.installCompletion)) {
18681
+ await ensureZshCompletionPath(rcPath);
18682
+ }
18683
+ if (result.editor) {
18684
+ await updateGlobalConfigKey("editor", result.editor, home);
18685
+ }
18686
+ }
18687
+ async function installOnboardingShellIntegration(shell, home) {
18688
+ const rcPath = resolveShellConfigPath(shell, home);
18689
+ await mkdir6(dirname8(rcPath), { recursive: true });
18690
+ const current = await readExistingConfig(rcPath);
18691
+ if (shell === "fish") {
18692
+ await writeFile5(
18693
+ rcPath,
18694
+ upsertShellIntegration(current, renderShellIntegration(shell)),
18695
+ "utf8"
18696
+ );
18697
+ return;
18698
+ }
18699
+ await writeFile5(
18700
+ rcPath,
18701
+ upsertOnboardingShellIntegration(current, shell),
18702
+ "utf8"
18703
+ );
18704
+ }
18705
+ function upsertOnboardingShellIntegration(existingConfig, shell) {
18706
+ const script = renderOnboardingShellIntegration(shell).trimEnd();
18707
+ const blockPattern = new RegExp(
18708
+ `${escapeForRegExp(ONBOARDING_SHELL_INTEGRATION_START_MARKER)}[\\s\\S]*?${escapeForRegExp(ONBOARDING_SHELL_INTEGRATION_END_MARKER)}\\n?`,
18709
+ "m"
18710
+ );
18711
+ if (blockPattern.test(existingConfig)) {
18712
+ return ensureTrailingNewline(
18713
+ existingConfig.replace(blockPattern, `${script}
18714
+ `)
18715
+ );
18716
+ }
18717
+ const prefix = existingConfig.trimEnd();
18718
+ if (prefix.length === 0) return ensureTrailingNewline(script);
18719
+ return ensureTrailingNewline(`${prefix}
18720
+
18721
+ ${script}`);
18722
+ }
18723
+ function renderOnboardingShellIntegration(shell) {
18724
+ return `${ONBOARDING_SHELL_INTEGRATION_START_MARKER}
18725
+ eval "$(gji init ${shell})"
18726
+ ${ONBOARDING_SHELL_INTEGRATION_END_MARKER}
18727
+ `;
18728
+ }
18729
+ async function ensureZshCompletionPath(rcPath) {
18730
+ const current = await readExistingConfig(rcPath);
18731
+ const next = upsertZshCompletionPath(current);
18732
+ if (next !== current) {
18733
+ await writeFile5(rcPath, next, "utf8");
18734
+ }
18735
+ }
18736
+ function upsertZshCompletionPath(existingConfig) {
18737
+ const configWithoutCompletionPath = existingConfig.replace(
18738
+ /# >>> gji zsh completion path >>>[\s\S]*?# <<< gji zsh completion path <<<\n?/m,
18739
+ ""
18740
+ ).replace(
18741
+ new RegExp(
18742
+ `^[\\t ]*${escapeForRegExp(ZSH_COMPLETION_PATH_LINE)}[\\t ]*\\n?`,
18743
+ "gm"
18744
+ ),
18745
+ ""
18746
+ );
18747
+ const suffix = configWithoutCompletionPath.replace(/^(?:\r?\n)+/, "").trimEnd();
18748
+ return ensureTrailingNewline(
18749
+ suffix.length === 0 ? ZSH_COMPLETION_PATH_LINE : `${ZSH_COMPLETION_PATH_LINE}
18750
+
18751
+ ${suffix}`
18752
+ );
18753
+ }
18754
+ async function fileExists2(path9) {
18755
+ try {
18756
+ await access7(path9, constants5.F_OK);
18757
+ return true;
18758
+ } catch {
18759
+ return false;
18760
+ }
18761
+ }
18762
+ async function runLegacyInitCommand(options) {
17069
18763
  const shell = resolveSupportedShell(options.shell, process.env.SHELL);
17070
- const home = options.home ?? homedir5();
18764
+ const home = options.home ?? homedir6();
17071
18765
  if (!shell) {
17072
18766
  options.stderr?.(
17073
18767
  "Unable to detect a supported shell. Specify one explicitly: bash, fish, or zsh.\n"
@@ -17080,7 +18774,7 @@ async function runInitCommand(options) {
17080
18774
  return 0;
17081
18775
  }
17082
18776
  const rcPath = resolveShellConfigPath(shell, home);
17083
- await mkdir6(dirname7(rcPath), { recursive: true });
18777
+ await mkdir6(dirname8(rcPath), { recursive: true });
17084
18778
  const current = await readExistingConfig(rcPath);
17085
18779
  const next = upsertShellIntegration(current, script);
17086
18780
  await writeFile5(rcPath, next, "utf8");
@@ -17089,7 +18783,7 @@ async function runInitCommand(options) {
17089
18783
  const { config: globalConfig } = await loadGlobalConfig(home);
17090
18784
  const alreadyConfigured = "shellIntegration" in globalConfig || "installSaveTarget" in globalConfig;
17091
18785
  const hasCustomPrompt = options.promptForSetup !== void 0;
17092
- const canPrompt = hasCustomPrompt || process.stdout.isTTY === true;
18786
+ const canPrompt = !isHeadless() && (hasCustomPrompt || process.stdout.isTTY === true);
17093
18787
  if (!alreadyConfigured && canPrompt) {
17094
18788
  const prompt = options.promptForSetup ?? defaultPromptForSetup;
17095
18789
  const result = await prompt();
@@ -17111,30 +18805,30 @@ function renderShellIntegration(shell) {
17111
18805
  ).join("\n\n");
17112
18806
  switch (shell) {
17113
18807
  case "fish":
17114
- return `${START_MARKER}
18808
+ return `${SHELL_INTEGRATION_START_MARKER}
17115
18809
  function gji --wraps gji --description 'gji shell integration'
17116
18810
  ${indentBlock(commandBlocks, 4)}
17117
18811
 
17118
18812
  command gji $argv
17119
18813
  end
17120
- ${END_MARKER}
18814
+ ${SHELL_INTEGRATION_END_MARKER}
17121
18815
  `;
17122
18816
  case "bash":
17123
18817
  case "zsh":
17124
- return `${START_MARKER}
18818
+ return `${SHELL_INTEGRATION_START_MARKER}
17125
18819
  gji() {
17126
18820
  ${indentBlock(commandBlocks, 2)}
17127
18821
 
17128
18822
  command gji "$@"
17129
18823
  }
17130
- ${END_MARKER}
18824
+ ${SHELL_INTEGRATION_END_MARKER}
17131
18825
  `;
17132
18826
  }
17133
18827
  }
17134
18828
  function upsertShellIntegration(existingConfig, script) {
17135
18829
  const trimmedScript = script.trimEnd();
17136
18830
  const blockPattern = new RegExp(
17137
- `${escapeForRegExp(START_MARKER)}[\\s\\S]*?${escapeForRegExp(END_MARKER)}\\n?`,
18831
+ `${escapeForRegExp(SHELL_INTEGRATION_START_MARKER)}[\\s\\S]*?${escapeForRegExp(SHELL_INTEGRATION_END_MARKER)}\\n?`,
17138
18832
  "m"
17139
18833
  );
17140
18834
  if (blockPattern.test(existingConfig)) {
@@ -17172,21 +18866,11 @@ async function saveWizardConfig(result, cwd, home) {
17172
18866
  await saveGlobalConfig({ ...existing, ...values }, home);
17173
18867
  }
17174
18868
  }
17175
- function resolveShellConfigPath(shell, home) {
17176
- switch (shell) {
17177
- case "bash":
17178
- return join7(home, ".bashrc");
17179
- case "fish":
17180
- return join7(home, ".config", "fish", "config.fish");
17181
- case "zsh":
17182
- return join7(home, ".zshrc");
17183
- }
17184
- }
17185
18869
  async function readExistingConfig(path9) {
17186
18870
  try {
17187
- return await readFile4(path9, "utf8");
18871
+ return await readFile5(path9, "utf8");
17188
18872
  } catch (error) {
17189
- if (isMissingFileError2(error)) {
18873
+ if (isMissingFileError3(error)) {
17190
18874
  return "";
17191
18875
  }
17192
18876
  throw error;
@@ -17199,7 +18883,7 @@ function ensureTrailingNewline(value) {
17199
18883
  function escapeForRegExp(value) {
17200
18884
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17201
18885
  }
17202
- function isMissingFileError2(error) {
18886
+ function isMissingFileError3(error) {
17203
18887
  return error instanceof Error && "code" in error && error.code === "ENOENT";
17204
18888
  }
17205
18889
  function renderFishWrapper(command) {
@@ -17424,14 +19108,14 @@ function sortWorktrees(worktrees) {
17424
19108
  }
17425
19109
 
17426
19110
  // src/open.ts
17427
- import { execFile as execFile4 } from "node:child_process";
17428
- import { access as access4, writeFile as writeFile6 } from "node:fs/promises";
17429
- import { join as join8 } from "node:path";
17430
- import { promisify as promisify5 } from "node:util";
17431
- var execFileAsync4 = promisify5(execFile4);
19111
+ import { execFile as execFile6 } from "node:child_process";
19112
+ import { access as access8, writeFile as writeFile6 } from "node:fs/promises";
19113
+ import { join as join9 } from "node:path";
19114
+ import { promisify as promisify7 } from "node:util";
19115
+ var execFileAsync6 = promisify7(execFile6);
17432
19116
  function createOpenCommand(dependencies = {}) {
17433
19117
  const detectEditors = dependencies.detectEditors ?? detectInstalledEditors;
17434
- const promptForEditor = dependencies.promptForEditor ?? defaultPromptForEditor;
19118
+ const promptForEditor2 = dependencies.promptForEditor ?? defaultPromptForEditor;
17435
19119
  const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree;
17436
19120
  const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
17437
19121
  return async function runOpenCommand2(options) {
@@ -17466,9 +19150,11 @@ function createOpenCommand(dependencies = {}) {
17466
19150
  } else {
17467
19151
  const entries = await buildWorktreePromptEntries(
17468
19152
  worktrees.map((worktree) => ({
19153
+ repoRoot: repository.repoRoot,
17469
19154
  repoName: repository.repoName,
17470
19155
  worktree
17471
- }))
19156
+ })),
19157
+ { queryPullRequests: dependencies.queryPullRequests }
17472
19158
  );
17473
19159
  const chosen = await promptForWorktree2(entries);
17474
19160
  if (!chosen) {
@@ -17500,7 +19186,7 @@ function createOpenCommand(dependencies = {}) {
17500
19186
  if (installed.length === 1 || isHeadless()) {
17501
19187
  editorCli = installed[0].cli;
17502
19188
  } else {
17503
- const chosen = await promptForEditor(installed);
19189
+ const chosen = await promptForEditor2(installed);
17504
19190
  if (!chosen) {
17505
19191
  options.stderr("Aborted\n");
17506
19192
  return 1;
@@ -17559,7 +19245,7 @@ async function detectInstalledEditors() {
17559
19245
  }
17560
19246
  async function isCommandAvailable(command) {
17561
19247
  try {
17562
- await execFileAsync4("which", [command]);
19248
+ await execFileAsync6("which", [command]);
17563
19249
  return true;
17564
19250
  } catch {
17565
19251
  return false;
@@ -17577,9 +19263,9 @@ async function defaultPromptForEditor(editors) {
17577
19263
  return choice;
17578
19264
  }
17579
19265
  async function ensureWorkspaceFile(worktreePath, repoName) {
17580
- const workspacePath = join8(worktreePath, `${repoName}.code-workspace`);
19266
+ const workspacePath = join9(worktreePath, `${repoName}.code-workspace`);
17581
19267
  try {
17582
- await access4(workspacePath);
19268
+ await access8(workspacePath);
17583
19269
  return workspacePath;
17584
19270
  } catch {
17585
19271
  }
@@ -17594,11 +19280,11 @@ async function ensureWorkspaceFile(worktreePath, repoName) {
17594
19280
  }
17595
19281
 
17596
19282
  // src/pr.ts
17597
- import { execFile as execFile5 } from "node:child_process";
19283
+ import { execFile as execFile7 } from "node:child_process";
17598
19284
  import { mkdir as mkdir7 } from "node:fs/promises";
17599
- import { basename as basename7, dirname as dirname8 } from "node:path";
17600
- import { promisify as promisify6 } from "node:util";
17601
- var execFileAsync5 = promisify6(execFile5);
19285
+ import { basename as basename8, dirname as dirname9 } from "node:path";
19286
+ import { promisify as promisify8 } from "node:util";
19287
+ var execFileAsync7 = promisify8(execFile7);
17602
19288
  var PR_OUTPUT_FILE_ENV = "GJI_PR_OUTPUT_FILE";
17603
19289
  function parsePrInput(input) {
17604
19290
  if (/^\d+$/.test(input)) return input;
@@ -17640,7 +19326,7 @@ function createPrCommand(dependencies = {}) {
17640
19326
  branchName,
17641
19327
  configuredBasePath
17642
19328
  );
17643
- if (await pathExists(worktreePath)) {
19329
+ if (await pathExists2(worktreePath)) {
17644
19330
  if (options.json || isHeadless()) {
17645
19331
  const message = `target worktree path already exists: ${worktreePath}`;
17646
19332
  if (options.json) {
@@ -17706,13 +19392,13 @@ function createPrCommand(dependencies = {}) {
17706
19392
  }
17707
19393
  return 1;
17708
19394
  }
17709
- await mkdir7(dirname8(worktreePath), { recursive: true });
19395
+ await mkdir7(dirname9(worktreePath), { recursive: true });
17710
19396
  const branchAlreadyExists = await localBranchExists2(
17711
19397
  repository.repoRoot,
17712
19398
  branchName
17713
19399
  );
17714
19400
  const worktreeArgs = branchAlreadyExists ? ["worktree", "add", worktreePath, branchName] : ["worktree", "add", "-b", branchName, worktreePath, remoteRef];
17715
- await execFileAsync5("git", worktreeArgs, { cwd: repository.repoRoot });
19401
+ await execFileAsync7("git", worktreeArgs, { cwd: repository.repoRoot });
17716
19402
  const syncPatterns = Array.isArray(config.syncFiles) ? config.syncFiles.filter(
17717
19403
  (p2) => typeof p2 === "string"
17718
19404
  ) : [];
@@ -17741,7 +19427,7 @@ function createPrCommand(dependencies = {}) {
17741
19427
  {
17742
19428
  branch: branchName,
17743
19429
  path: worktreePath,
17744
- repo: basename7(repository.repoRoot)
19430
+ repo: basename8(repository.repoRoot)
17745
19431
  },
17746
19432
  options.stderr
17747
19433
  );
@@ -17759,7 +19445,7 @@ function createPrCommand(dependencies = {}) {
17759
19445
  }
17760
19446
  async function localBranchExists2(repoRoot, branchName) {
17761
19447
  try {
17762
- await execFileAsync5(
19448
+ await execFileAsync7(
17763
19449
  "git",
17764
19450
  ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
17765
19451
  { cwd: repoRoot }
@@ -17773,7 +19459,7 @@ var runPrCommand = createPrCommand();
17773
19459
  async function fetchPullRequestRef(repoRoot, input, prNumber, remoteRef) {
17774
19460
  for (const sourceRef of listPullRequestSourceRefs(input, prNumber)) {
17775
19461
  try {
17776
- await execFileAsync5(
19462
+ await execFileAsync7(
17777
19463
  "git",
17778
19464
  ["fetch", "origin", `${sourceRef}:${remoteRef}`],
17779
19465
  { cwd: repoRoot }
@@ -17823,11 +19509,415 @@ async function writeOutput2(worktreePath, stdout2) {
17823
19509
  await writeShellOutput(PR_OUTPUT_FILE_ENV, worktreePath, stdout2);
17824
19510
  }
17825
19511
 
19512
+ // src/browser.ts
19513
+ import { execFile as execFile8 } from "node:child_process";
19514
+ import { platform as platform2 } from "node:process";
19515
+ import { promisify as promisify9 } from "node:util";
19516
+ var execFileAsync8 = promisify9(execFile8);
19517
+ function createBrowserOpener(dependencies = {}) {
19518
+ const currentPlatform = dependencies.platform ?? platform2;
19519
+ const runCommand = dependencies.runCommand ?? defaultRunCommand2;
19520
+ return async function openBrowser2(url) {
19521
+ validateBrowserUrl(url);
19522
+ const command = browserCommand(currentPlatform, url);
19523
+ await runCommand(command.command, command.args);
19524
+ };
19525
+ }
19526
+ var openBrowser = createBrowserOpener();
19527
+ function browserCommand(currentPlatform, url) {
19528
+ if (currentPlatform === "darwin") return { args: [url], command: "open" };
19529
+ if (currentPlatform === "win32") {
19530
+ return { args: [url], command: "explorer.exe" };
19531
+ }
19532
+ return { args: [url], command: "xdg-open" };
19533
+ }
19534
+ function validateBrowserUrl(url) {
19535
+ let parsed;
19536
+ try {
19537
+ parsed = new URL(url);
19538
+ } catch {
19539
+ throw new Error("browser URL is invalid");
19540
+ }
19541
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
19542
+ throw new Error("browser URL must use http or https");
19543
+ }
19544
+ }
19545
+ async function defaultRunCommand2(command, args) {
19546
+ await execFileAsync8(command, args, { timeout: 1e4 });
19547
+ }
19548
+
19549
+ // src/pr-open.ts
19550
+ function createPrOpenCommand(dependencies = {}) {
19551
+ const query = createPullRequestQuery();
19552
+ const findOpenPullRequest2 = dependencies.findOpenPullRequest ?? query.findOpenPullRequest;
19553
+ const openInBrowser = dependencies.openBrowser ?? openBrowser;
19554
+ const promptForPullRequest = dependencies.promptForPullRequest ?? defaultPromptForPullRequest;
19555
+ const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree2;
19556
+ const queryPullRequests = dependencies.queryPullRequests ?? query.listOpenPullRequests;
19557
+ const queryRepositoryPullRequests = dependencies.queryRepositoryPullRequests ?? (dependencies.queryPullRequests === void 0 ? query.listOpenPullRequestsForRepository : void 0);
19558
+ return async function runPrOpenCommand2(options) {
19559
+ if (options.select && options.target !== void 0) {
19560
+ options.stderr("gji pr open: --select cannot be used with a target\n");
19561
+ return 1;
19562
+ }
19563
+ if (options.select && isHeadless()) {
19564
+ options.stderr(
19565
+ "gji pr open --select: selector is unavailable in non-interactive mode (GJI_NO_TUI=1)\n"
19566
+ );
19567
+ return 1;
19568
+ }
19569
+ const repository = await detectRepository(options.cwd).catch((error) => {
19570
+ options.stderr(
19571
+ `gji pr open: unable to detect the current repository: ${formatError(error)}
19572
+ `
19573
+ );
19574
+ return null;
19575
+ });
19576
+ if (repository === null) return 1;
19577
+ if (options.target === void 0) {
19578
+ if (!options.select) {
19579
+ return openCurrentWorktree(
19580
+ repository.repoRoot,
19581
+ options,
19582
+ queryPullRequests,
19583
+ promptForPullRequest,
19584
+ openInBrowser
19585
+ );
19586
+ }
19587
+ return openFromWorktreeSelector(
19588
+ repository.repoRoot,
19589
+ repository.repoName,
19590
+ options,
19591
+ promptForWorktree2,
19592
+ promptForPullRequest,
19593
+ queryPullRequests,
19594
+ queryRepositoryPullRequests,
19595
+ openInBrowser
19596
+ );
19597
+ }
19598
+ const number = parsePrNumberTarget(options.target);
19599
+ if (number !== null) {
19600
+ return openByNumber(
19601
+ repository.repoRoot,
19602
+ number,
19603
+ options,
19604
+ findOpenPullRequest2,
19605
+ openInBrowser
19606
+ );
19607
+ }
19608
+ const worktrees = await listWorktrees(options.cwd).catch((error) => {
19609
+ options.stderr(
19610
+ `gji pr open: unable to list worktrees: ${formatError(error)}
19611
+ `
19612
+ );
19613
+ return null;
19614
+ });
19615
+ if (worktrees === null) return 1;
19616
+ const match = resolveWorktreeQuery(
19617
+ worktrees.map((worktree) => ({
19618
+ repoRoot: repository.repoRoot,
19619
+ repoName: repository.repoName,
19620
+ worktree
19621
+ })),
19622
+ options.target
19623
+ );
19624
+ if (match === null) {
19625
+ options.stderr(
19626
+ `gji pr open: no worktree found matching: ${options.target}
19627
+ `
19628
+ );
19629
+ return 1;
19630
+ }
19631
+ return openForBranch(
19632
+ repository.repoRoot,
19633
+ match.worktree.branch,
19634
+ options,
19635
+ queryPullRequests,
19636
+ promptForPullRequest,
19637
+ openInBrowser
19638
+ );
19639
+ };
19640
+ }
19641
+ var runPrOpenCommand = createPrOpenCommand();
19642
+ async function openCurrentWorktree(repoRoot, options, queryPullRequests, promptForPullRequest, openInBrowser) {
19643
+ const worktrees = await listWorktrees(options.cwd).catch((error) => {
19644
+ options.stderr(
19645
+ `gji pr open: unable to list worktrees: ${formatError(error)}
19646
+ `
19647
+ );
19648
+ return null;
19649
+ });
19650
+ if (worktrees === null) return 1;
19651
+ const currentWorktree = worktrees.find((worktree) => worktree.isCurrent);
19652
+ if (currentWorktree === void 0) {
19653
+ options.stderr("gji pr open: unable to identify the current worktree\n");
19654
+ return 1;
19655
+ }
19656
+ if (currentWorktree.branch === null) {
19657
+ options.stderr(
19658
+ "gji pr open: current worktree is detached and has no PR branch\n"
19659
+ );
19660
+ return 1;
19661
+ }
19662
+ let pullRequests;
19663
+ try {
19664
+ pullRequests = await queryPullRequests(repoRoot, currentWorktree.branch);
19665
+ } catch (error) {
19666
+ options.stderr(
19667
+ `gji pr open: failed to look up PRs for current branch ${currentWorktree.branch}: ${formatError(error)}
19668
+ `
19669
+ );
19670
+ return 1;
19671
+ }
19672
+ if (pullRequests.length === 0) {
19673
+ options.stderr(
19674
+ isHeadless() ? "gji pr open: no open PR found for the current worktree; pass an explicit branch or PR number (for example, gji pr open '#123')\n" : "gji pr open: no open PR found for the current worktree; use gji pr open --select to choose another\n"
19675
+ );
19676
+ return 1;
19677
+ }
19678
+ return openPullRequests(
19679
+ pullRequests,
19680
+ options,
19681
+ promptForPullRequest,
19682
+ openInBrowser
19683
+ );
19684
+ }
19685
+ async function openFromWorktreeSelector(repoRoot, repoName, options, promptForWorktree2, promptForPullRequest, queryPullRequests, queryRepositoryPullRequests, openInBrowser) {
19686
+ const worktrees = await listWorktrees(options.cwd).catch((error) => {
19687
+ options.stderr(
19688
+ `gji pr open: unable to list worktrees: ${formatError(error)}
19689
+ `
19690
+ );
19691
+ return null;
19692
+ });
19693
+ if (worktrees === null) return 1;
19694
+ let lookupError = null;
19695
+ const queryForSelector = async (root, branch) => {
19696
+ try {
19697
+ return await queryPullRequests(root, branch);
19698
+ } catch (error) {
19699
+ lookupError ??= error;
19700
+ throw error;
19701
+ }
19702
+ };
19703
+ const queryRepositoryForSelector = queryRepositoryPullRequests === void 0 ? void 0 : async (root) => {
19704
+ try {
19705
+ return await queryRepositoryPullRequests(root);
19706
+ } catch (error) {
19707
+ lookupError ??= error;
19708
+ throw error;
19709
+ }
19710
+ };
19711
+ let connectedWorktrees;
19712
+ try {
19713
+ connectedWorktrees = await readConnectedWorktreeSources(
19714
+ repoRoot,
19715
+ repoName,
19716
+ worktrees,
19717
+ queryForSelector,
19718
+ queryRepositoryForSelector
19719
+ );
19720
+ } catch {
19721
+ connectedWorktrees = { pullRequestsByBranch: /* @__PURE__ */ new Map(), sources: [] };
19722
+ }
19723
+ const { pullRequestsByBranch, sources } = connectedWorktrees;
19724
+ const entries = await buildWorktreePromptEntries(sources, {
19725
+ queryPullRequests: async (_root, branch) => pullRequestsByBranch.get(branch) ?? []
19726
+ });
19727
+ const connectedEntries = entries.filter(
19728
+ (entry) => (entry.pullRequestNumbers?.length ?? 0) > 0
19729
+ );
19730
+ if (connectedEntries.length === 0) {
19731
+ if (lookupError !== null) {
19732
+ options.stderr(
19733
+ `gji pr open: failed to look up open PRs: ${formatError(lookupError)}
19734
+ `
19735
+ );
19736
+ return 1;
19737
+ }
19738
+ options.stderr("gji pr open: no open PR worktrees found\n");
19739
+ return 1;
19740
+ }
19741
+ const selectedPath = await promptForWorktree2(connectedEntries);
19742
+ if (selectedPath === null) {
19743
+ options.stderr("Aborted\n");
19744
+ return 1;
19745
+ }
19746
+ const selectedEntry = connectedEntries.find(
19747
+ (entry) => entry.path === selectedPath
19748
+ );
19749
+ if (selectedEntry === void 0) {
19750
+ options.stderr("gji pr open: selected worktree is no longer available\n");
19751
+ return 1;
19752
+ }
19753
+ return openSelectedEntry(
19754
+ selectedEntry,
19755
+ options,
19756
+ promptForPullRequest,
19757
+ openInBrowser
19758
+ );
19759
+ }
19760
+ async function readConnectedWorktreeSources(repoRoot, repoName, worktrees, queryPullRequests, queryRepositoryPullRequests) {
19761
+ const pullRequestsByBranch = /* @__PURE__ */ new Map();
19762
+ if (queryRepositoryPullRequests !== void 0) {
19763
+ const pullRequests = await queryRepositoryPullRequests(repoRoot);
19764
+ for (const worktree of worktrees) {
19765
+ if (worktree.branch === null) continue;
19766
+ const branchPullRequests = pullRequests.filter(
19767
+ (pullRequest) => pullRequest.sourceBranch === worktree.branch
19768
+ );
19769
+ if (branchPullRequests.length > 0) {
19770
+ pullRequestsByBranch.set(worktree.branch, branchPullRequests);
19771
+ }
19772
+ }
19773
+ } else {
19774
+ const branchResults = await Promise.all(
19775
+ worktrees.map(async (worktree) => {
19776
+ if (worktree.branch === null) {
19777
+ return { pullRequests: [], worktree };
19778
+ }
19779
+ try {
19780
+ return {
19781
+ pullRequests: await queryPullRequests(repoRoot, worktree.branch),
19782
+ worktree
19783
+ };
19784
+ } catch {
19785
+ return { pullRequests: [], worktree };
19786
+ }
19787
+ })
19788
+ );
19789
+ for (const { pullRequests, worktree } of branchResults) {
19790
+ if (worktree.branch !== null && pullRequests.length > 0) {
19791
+ pullRequestsByBranch.set(worktree.branch, pullRequests);
19792
+ }
19793
+ }
19794
+ }
19795
+ const sources = worktrees.flatMap((worktree) => {
19796
+ if (worktree.branch === null || !pullRequestsByBranch.has(worktree.branch)) {
19797
+ return [];
19798
+ }
19799
+ return [{ repoRoot, repoName, worktree }];
19800
+ });
19801
+ return { pullRequestsByBranch, sources };
19802
+ }
19803
+ async function openByNumber(repoRoot, number, options, findOpenPullRequest2, openInBrowser) {
19804
+ let pullRequest;
19805
+ try {
19806
+ pullRequest = await findOpenPullRequest2(repoRoot, number);
19807
+ } catch (error) {
19808
+ options.stderr(
19809
+ `gji pr open: failed to look up PR #${number}: ${formatError(error)}
19810
+ `
19811
+ );
19812
+ return 1;
19813
+ }
19814
+ if (pullRequest === null) {
19815
+ options.stderr(`gji pr open: no open PR found for #${number}
19816
+ `);
19817
+ return 1;
19818
+ }
19819
+ return launchBrowser(pullRequest, options, openInBrowser);
19820
+ }
19821
+ async function openForBranch(repoRoot, branch, options, queryPullRequests, promptForPullRequest, openInBrowser) {
19822
+ if (branch === null) {
19823
+ options.stderr("gji pr open: detached worktrees have no PR branch\n");
19824
+ return 1;
19825
+ }
19826
+ let pullRequests;
19827
+ try {
19828
+ pullRequests = await queryPullRequests(repoRoot, branch);
19829
+ } catch (error) {
19830
+ options.stderr(
19831
+ `gji pr open: failed to look up PRs for branch ${branch}: ${formatError(error)}
19832
+ `
19833
+ );
19834
+ return 1;
19835
+ }
19836
+ return openPullRequests(
19837
+ pullRequests,
19838
+ options,
19839
+ promptForPullRequest,
19840
+ openInBrowser
19841
+ );
19842
+ }
19843
+ async function openSelectedEntry(entry, options, promptForPullRequest, openInBrowser) {
19844
+ const numbers = entry.pullRequestNumbers ?? [];
19845
+ const urls = entry.pullRequestUrls ?? [];
19846
+ const pullRequests = numbers.map((number, index) => ({
19847
+ number,
19848
+ sourceBranch: entry.branch ?? "",
19849
+ url: urls[index] ?? ""
19850
+ })).filter((pullRequest) => pullRequest.url.length > 0);
19851
+ return openPullRequests(
19852
+ pullRequests,
19853
+ options,
19854
+ promptForPullRequest,
19855
+ openInBrowser
19856
+ );
19857
+ }
19858
+ async function openPullRequests(pullRequests, options, promptForPullRequest, openInBrowser) {
19859
+ if (pullRequests.length === 0) {
19860
+ options.stderr("gji pr open: no open PR found for the selected branch\n");
19861
+ return 1;
19862
+ }
19863
+ if (pullRequests.length > 1 && isHeadless()) {
19864
+ options.stderr(
19865
+ "gji pr open: multiple open PRs found; pass an explicit PR number in non-interactive mode (GJI_NO_TUI=1)\n"
19866
+ );
19867
+ return 1;
19868
+ }
19869
+ const selected = pullRequests.length === 1 ? pullRequests[0] : await promptForPullRequest(pullRequests);
19870
+ if (selected === null || selected === void 0) {
19871
+ options.stderr("Aborted\n");
19872
+ return 1;
19873
+ }
19874
+ return launchBrowser(selected, options, openInBrowser);
19875
+ }
19876
+ async function launchBrowser(pullRequest, options, openInBrowser) {
19877
+ try {
19878
+ await openInBrowser(pullRequest.url);
19879
+ } catch (error) {
19880
+ options.stderr(
19881
+ `gji pr open: failed to open PR #${pullRequest.number} in the browser: ${formatError(error)}
19882
+ `
19883
+ );
19884
+ return 1;
19885
+ }
19886
+ options.stdout(`Opened PR #${pullRequest.number}: ${pullRequest.url}
19887
+ `);
19888
+ return 0;
19889
+ }
19890
+ function parsePrNumberTarget(target) {
19891
+ const match = target.match(/^#?(\d+)$/);
19892
+ return match === null ? null : Number(match[1]);
19893
+ }
19894
+ async function defaultPromptForWorktree2(worktrees) {
19895
+ return promptForSingleWorktree(
19896
+ "Choose a worktree with an open PR",
19897
+ worktrees
19898
+ );
19899
+ }
19900
+ async function defaultPromptForPullRequest(pullRequests) {
19901
+ const choice = await ve({
19902
+ message: "Choose a pull request",
19903
+ options: pullRequests.map((pullRequest) => ({
19904
+ value: pullRequest.number,
19905
+ label: `#${pullRequest.number}`,
19906
+ hint: pullRequest.url
19907
+ }))
19908
+ });
19909
+ if (pD(choice)) return null;
19910
+ return pullRequests.find((pullRequest) => pullRequest.number === choice) ?? null;
19911
+ }
19912
+ function formatError(error) {
19913
+ return error instanceof Error ? error.message : String(error);
19914
+ }
19915
+
17826
19916
  // src/remove.ts
17827
- import { basename as basename8 } from "node:path";
19917
+ import { basename as basename9 } from "node:path";
17828
19918
  var REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
17829
19919
  function createRemoveCommand(dependencies = {}) {
17830
- const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree2;
19920
+ const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree3;
17831
19921
  const confirmRemoval = dependencies.confirmRemoval ?? defaultConfirmRemoval2;
17832
19922
  const confirmForceRemoveWorktree = dependencies.confirmForceRemoveWorktree ?? defaultConfirmForceRemoveWorktree;
17833
19923
  const confirmForceDeleteBranch = dependencies.confirmForceDeleteBranch ?? defaultConfirmForceDeleteBranch;
@@ -17911,14 +20001,14 @@ function createRemoveCommand(dependencies = {}) {
17911
20001
  {
17912
20002
  branch: worktree.branch ?? void 0,
17913
20003
  path: worktree.path,
17914
- repo: basename8(repository.repoRoot)
20004
+ repo: basename9(repository.repoRoot)
17915
20005
  },
17916
20006
  options.stderr
17917
20007
  );
17918
20008
  try {
17919
20009
  await removeWorktree(repository.repoRoot, worktree.path);
17920
20010
  } catch (error) {
17921
- if (!isWorktreeDirtyError(error)) {
20011
+ if (!isWorktreeForceRemovalError(error)) {
17922
20012
  throw error;
17923
20013
  }
17924
20014
  if (!options.force && !await confirmForceRemoveWorktree(worktree.path)) {
@@ -17971,7 +20061,7 @@ function createRemoveCommand(dependencies = {}) {
17971
20061
  };
17972
20062
  }
17973
20063
  var runRemoveCommand = createRemoveCommand();
17974
- async function defaultPromptForWorktree2(worktrees) {
20064
+ async function defaultPromptForWorktree3(worktrees) {
17975
20065
  return promptForSingleWorktree("Choose a worktree to finish", worktrees);
17976
20066
  }
17977
20067
  async function defaultConfirmRemoval2(worktree) {
@@ -18296,11 +20386,11 @@ function resolveConfiguredString2(value) {
18296
20386
  }
18297
20387
 
18298
20388
  // src/sync-files-command.ts
18299
- import { homedir as homedir6 } from "node:os";
18300
- import { join as join9 } from "node:path";
20389
+ import { homedir as homedir7 } from "node:os";
20390
+ import { join as join10 } from "node:path";
18301
20391
  async function runSyncFilesCommand(options) {
18302
20392
  const repository = await detectRepository(options.cwd);
18303
- const home = options.home ?? homedir6();
20393
+ const home = options.home ?? homedir7();
18304
20394
  const loaded = await loadGlobalConfig(home);
18305
20395
  const repoEntry = findRepoConfigEntry(
18306
20396
  loaded.config,
@@ -18346,7 +20436,7 @@ function findRepoConfigEntry(config, repoRoot, home) {
18346
20436
  const repos = config.repos;
18347
20437
  if (!isPlainObject3(repos)) return null;
18348
20438
  for (const [key, value] of Object.entries(repos)) {
18349
- if (expandTilde2(key, home) === repoRoot && isPlainObject3(value)) {
20439
+ if (expandTilde3(key, home) === repoRoot && isPlainObject3(value)) {
18350
20440
  return { config: value, key };
18351
20441
  }
18352
20442
  }
@@ -18430,9 +20520,9 @@ function writeError(options, message) {
18430
20520
  options.stderr(`gji sync-files: ${message}
18431
20521
  `);
18432
20522
  }
18433
- function expandTilde2(value, home) {
20523
+ function expandTilde3(value, home) {
18434
20524
  if (value === "~") return home;
18435
- if (value.startsWith("~/")) return join9(home, value.slice(2));
20525
+ if (value.startsWith("~/")) return join10(home, value.slice(2));
18436
20526
  return value;
18437
20527
  }
18438
20528
 
@@ -18454,7 +20544,9 @@ function readPackageMetadata() {
18454
20544
  }
18455
20545
  async function runCli(argv, options = {}) {
18456
20546
  await maybeNotifyForUpdates(argv);
18457
- maybeRegisterCurrentRepo(options.cwd ?? process.cwd());
20547
+ if (argv[0] !== "doctor") {
20548
+ maybeRegisterCurrentRepo(options.cwd ?? process.cwd());
20549
+ }
18458
20550
  const program2 = createProgram();
18459
20551
  const cwd = options.cwd ?? process.cwd();
18460
20552
  const stdout2 = options.stdout ?? (() => void 0);
@@ -18469,7 +20561,12 @@ async function runCli(argv, options = {}) {
18469
20561
  return { exitCode: 0 };
18470
20562
  }
18471
20563
  try {
18472
- attachCommandActions(program2, { cwd, stderr, stdout: stdout2 });
20564
+ attachCommandActions(program2, {
20565
+ cwd,
20566
+ prOpenDependencies: options.prOpenDependencies,
20567
+ stderr,
20568
+ stdout: stdout2
20569
+ });
18473
20570
  await program2.parseAsync(["node", "gji", ...argv], { from: "node" });
18474
20571
  return { exitCode: 0 };
18475
20572
  } catch (error) {
@@ -18515,9 +20612,12 @@ function registerCommands(program2) {
18515
20612
  "--json",
18516
20613
  "emit JSON on success or error instead of human-readable output"
18517
20614
  ).action(notImplemented("new"));
18518
- program2.command("init [shell]").description("print or install shell integration").option("--write", "write the integration to the shell config file").action(notImplemented("init"));
20615
+ program2.command("init [shell]").description(
20616
+ "set up shell integration interactively or print a shell wrapper"
20617
+ ).option("--write", "write the integration to the shell config file").option("--json", "emit a JSON error in non-interactive mode").action(notImplemented("init"));
20618
+ program2.command("doctor").description("check gji installation and configuration health").option("--fix", "apply safe automatic fixes after showing the plan").option("--yes", "apply --fix without prompting").option("--json", "emit diagnostic checks as JSON").action(notImplemented("doctor"));
18519
20619
  program2.command("completion [shell]").description("print shell completion definitions").action(notImplemented("completion"));
18520
- program2.command("pr <ref>").description(
20620
+ const prCommand = program2.command("pr <ref>").usage("[options] <ref>").description(
18521
20621
  "fetch a pull request by number, #number, or URL into a linked worktree"
18522
20622
  ).option(
18523
20623
  "--dry-run",
@@ -18526,6 +20626,7 @@ function registerCommands(program2) {
18526
20626
  "--json",
18527
20627
  "emit JSON on success or error instead of human-readable output"
18528
20628
  ).action(notImplemented("pr"));
20629
+ prCommand.command("open [target]").description("open an existing pull request in the default browser").option("--select", "choose a pull request from any linked worktree").action(notImplemented("pr open"));
18529
20630
  program2.command("back [n]").description(
18530
20631
  "navigate to the previously visited worktree, optionally N steps back"
18531
20632
  ).option("--print", "print the resolved worktree path explicitly").action(notImplemented("back"));
@@ -18583,16 +20684,16 @@ function registerCommands(program2) {
18583
20684
  }
18584
20685
  function attachCommandActions(program2, options) {
18585
20686
  program2.commands.find((command) => command.name() === "new")?.action(
18586
- async (branch, commandOptions) => {
20687
+ async (branch, commandOptions2) => {
18587
20688
  const exitCode = await runNewCommand({
18588
20689
  ...options,
18589
20690
  branch,
18590
- detached: commandOptions.detached,
18591
- dryRun: commandOptions.dryRun,
18592
- editor: commandOptions.editor,
18593
- force: commandOptions.force,
18594
- json: commandOptions.json,
18595
- open: commandOptions.open
20691
+ detached: commandOptions2.detached,
20692
+ dryRun: commandOptions2.dryRun,
20693
+ editor: commandOptions2.editor,
20694
+ force: commandOptions2.force,
20695
+ json: commandOptions2.json,
20696
+ open: commandOptions2.open
18596
20697
  });
18597
20698
  if (exitCode !== 0) {
18598
20699
  throw commanderExit(exitCode);
@@ -18600,13 +20701,29 @@ function attachCommandActions(program2, options) {
18600
20701
  }
18601
20702
  );
18602
20703
  program2.commands.find((command) => command.name() === "init")?.action(
18603
- async (shell, commandOptions) => {
20704
+ async (shell, commandOptions2) => {
18604
20705
  const exitCode = await runInitCommand({
18605
20706
  cwd: options.cwd,
20707
+ json: commandOptions2.json,
18606
20708
  shell,
18607
20709
  stderr: options.stderr,
18608
20710
  stdout: options.stdout,
18609
- write: commandOptions.write
20711
+ write: commandOptions2.write
20712
+ });
20713
+ if (exitCode !== 0) {
20714
+ throw commanderExit(exitCode);
20715
+ }
20716
+ }
20717
+ );
20718
+ program2.commands.find((command) => command.name() === "doctor")?.action(
20719
+ async (commandOptions2) => {
20720
+ const exitCode = await runDoctorCommand({
20721
+ cwd: options.cwd,
20722
+ fix: commandOptions2.fix,
20723
+ json: commandOptions2.json,
20724
+ stderr: options.stderr,
20725
+ stdout: options.stdout,
20726
+ yes: commandOptions2.yes
18610
20727
  });
18611
20728
  if (exitCode !== 0) {
18612
20729
  throw commanderExit(exitCode);
@@ -18624,11 +20741,11 @@ function attachCommandActions(program2, options) {
18624
20741
  }
18625
20742
  });
18626
20743
  program2.commands.find((command) => command.name() === "pr")?.action(
18627
- async (number, commandOptions) => {
20744
+ async (number, commandOptions2) => {
18628
20745
  const exitCode = await runPrCommand({
18629
20746
  cwd: options.cwd,
18630
- dryRun: commandOptions.dryRun,
18631
- json: commandOptions.json,
20747
+ dryRun: commandOptions2.dryRun,
20748
+ json: commandOptions2.json,
18632
20749
  number,
18633
20750
  stderr: options.stderr,
18634
20751
  stdout: options.stdout
@@ -18638,8 +20755,24 @@ function attachCommandActions(program2, options) {
18638
20755
  }
18639
20756
  }
18640
20757
  );
20758
+ const prOpenCommand = program2.commands.find((command) => command.name() === "pr")?.commands.find((command) => command.name() === "open");
20759
+ const runPrOpenCommand2 = createPrOpenCommand(options.prOpenDependencies);
20760
+ prOpenCommand?.action(
20761
+ async (target, commandOptions2) => {
20762
+ const exitCode = await runPrOpenCommand2({
20763
+ cwd: options.cwd,
20764
+ stderr: options.stderr,
20765
+ stdout: options.stdout,
20766
+ select: commandOptions2.select,
20767
+ target
20768
+ });
20769
+ if (exitCode !== 0) {
20770
+ throw commanderExit(exitCode);
20771
+ }
20772
+ }
20773
+ );
18641
20774
  program2.commands.find((command) => command.name() === "back")?.action(
18642
- async (n, commandOptions) => {
20775
+ async (n, commandOptions2) => {
18643
20776
  if (n !== void 0 && !/^\d+$/.test(n)) {
18644
20777
  options.stderr(`gji back: invalid step count: ${n}
18645
20778
  `);
@@ -18649,7 +20782,7 @@ function attachCommandActions(program2, options) {
18649
20782
  const exitCode = await runBackCommand({
18650
20783
  cwd: options.cwd,
18651
20784
  n: steps,
18652
- print: commandOptions.print,
20785
+ print: commandOptions2.print,
18653
20786
  stderr: options.stderr,
18654
20787
  stdout: options.stdout
18655
20788
  });
@@ -18658,10 +20791,10 @@ function attachCommandActions(program2, options) {
18658
20791
  }
18659
20792
  }
18660
20793
  );
18661
- program2.commands.find((command) => command.name() === "history")?.action(async (commandOptions) => {
20794
+ program2.commands.find((command) => command.name() === "history")?.action(async (commandOptions2) => {
18662
20795
  const exitCode = await runHistoryCommand({
18663
20796
  cwd: options.cwd,
18664
- json: commandOptions.json,
20797
+ json: commandOptions2.json,
18665
20798
  stdout: options.stdout
18666
20799
  });
18667
20800
  if (exitCode !== 0) {
@@ -18669,15 +20802,15 @@ function attachCommandActions(program2, options) {
18669
20802
  }
18670
20803
  });
18671
20804
  program2.commands.find((command) => command.name() === "open")?.action(
18672
- async (branch, commandOptions) => {
20805
+ async (branch, commandOptions2) => {
18673
20806
  const exitCode = await runOpenCommand({
18674
20807
  branch,
18675
20808
  cwd: options.cwd,
18676
- editor: commandOptions.editor,
18677
- save: commandOptions.save,
20809
+ editor: commandOptions2.editor,
20810
+ save: commandOptions2.save,
18678
20811
  stderr: options.stderr,
18679
20812
  stdout: options.stdout,
18680
- workspace: commandOptions.workspace
20813
+ workspace: commandOptions2.workspace
18681
20814
  });
18682
20815
  if (exitCode !== 0) {
18683
20816
  throw commanderExit(exitCode);
@@ -18685,11 +20818,11 @@ function attachCommandActions(program2, options) {
18685
20818
  }
18686
20819
  );
18687
20820
  program2.commands.find((command) => command.name() === "go")?.action(
18688
- async (branch, commandOptions) => {
20821
+ async (branch, commandOptions2) => {
18689
20822
  const exitCode = await runGoCommand({
18690
20823
  branch,
18691
20824
  cwd: options.cwd,
18692
- print: commandOptions.print,
20825
+ print: commandOptions2.print,
18693
20826
  stderr: options.stderr,
18694
20827
  stdout: options.stdout
18695
20828
  });
@@ -18698,31 +20831,31 @@ function attachCommandActions(program2, options) {
18698
20831
  }
18699
20832
  }
18700
20833
  );
18701
- program2.commands.find((command) => command.name() === "root")?.action(async (commandOptions) => {
20834
+ program2.commands.find((command) => command.name() === "root")?.action(async (commandOptions2) => {
18702
20835
  const exitCode = await runRootCommand({
18703
20836
  cwd: options.cwd,
18704
- print: commandOptions.print,
20837
+ print: commandOptions2.print,
18705
20838
  stdout: options.stdout
18706
20839
  });
18707
20840
  if (exitCode !== 0) {
18708
20841
  throw commanderExit(exitCode);
18709
20842
  }
18710
20843
  });
18711
- program2.commands.find((command) => command.name() === "status")?.action(async (commandOptions) => {
20844
+ program2.commands.find((command) => command.name() === "status")?.action(async (commandOptions2) => {
18712
20845
  const exitCode = await runStatusCommand({
18713
20846
  cwd: options.cwd,
18714
- json: commandOptions.json,
20847
+ json: commandOptions2.json,
18715
20848
  stdout: options.stdout
18716
20849
  });
18717
20850
  if (exitCode !== 0) {
18718
20851
  throw commanderExit(exitCode);
18719
20852
  }
18720
20853
  });
18721
- program2.commands.find((command) => command.name() === "sync")?.action(async (commandOptions) => {
20854
+ program2.commands.find((command) => command.name() === "sync")?.action(async (commandOptions2) => {
18722
20855
  const exitCode = await runSyncCommand({
18723
- all: commandOptions.all,
20856
+ all: commandOptions2.all,
18724
20857
  cwd: options.cwd,
18725
- json: commandOptions.json,
20858
+ json: commandOptions2.json,
18726
20859
  stderr: options.stderr,
18727
20860
  stdout: options.stdout
18728
20861
  });
@@ -18733,11 +20866,11 @@ function attachCommandActions(program2, options) {
18733
20866
  const syncFilesCommand = program2.commands.find(
18734
20867
  (command) => command.name() === "sync-files"
18735
20868
  );
18736
- syncFilesCommand?.action(async (commandOptions) => {
20869
+ syncFilesCommand?.action(async (commandOptions2) => {
18737
20870
  const exitCode = await runSyncFilesCommand({
18738
20871
  action: "list",
18739
20872
  cwd: options.cwd,
18740
- json: commandOptions.json,
20873
+ json: commandOptions2.json,
18741
20874
  stderr: options.stderr,
18742
20875
  stdout: options.stdout
18743
20876
  });
@@ -18745,11 +20878,11 @@ function attachCommandActions(program2, options) {
18745
20878
  throw commanderExit(exitCode);
18746
20879
  }
18747
20880
  });
18748
- syncFilesCommand?.commands.find((command) => command.name() === "list")?.action(async (commandOptions) => {
20881
+ syncFilesCommand?.commands.find((command) => command.name() === "list")?.action(async (commandOptions2) => {
18749
20882
  const exitCode = await runSyncFilesCommand({
18750
20883
  action: "list",
18751
20884
  cwd: options.cwd,
18752
- json: commandOptions.json || syncFilesCommand?.opts().json,
20885
+ json: commandOptions2.json || syncFilesCommand?.opts().json,
18753
20886
  stderr: options.stderr,
18754
20887
  stdout: options.stdout
18755
20888
  });
@@ -18757,11 +20890,11 @@ function attachCommandActions(program2, options) {
18757
20890
  throw commanderExit(exitCode);
18758
20891
  }
18759
20892
  });
18760
- syncFilesCommand?.commands.find((command) => command.name() === "add")?.action(async (paths, commandOptions) => {
20893
+ syncFilesCommand?.commands.find((command) => command.name() === "add")?.action(async (paths, commandOptions2) => {
18761
20894
  const exitCode = await runSyncFilesCommand({
18762
20895
  action: "add",
18763
20896
  cwd: options.cwd,
18764
- json: commandOptions.json || syncFilesCommand?.opts().json,
20897
+ json: commandOptions2.json || syncFilesCommand?.opts().json,
18765
20898
  paths,
18766
20899
  stderr: options.stderr,
18767
20900
  stdout: options.stdout
@@ -18770,11 +20903,11 @@ function attachCommandActions(program2, options) {
18770
20903
  throw commanderExit(exitCode);
18771
20904
  }
18772
20905
  });
18773
- const runSyncFilesRemoveCommand = async (paths, commandOptions) => {
20906
+ const runSyncFilesRemoveCommand = async (paths, commandOptions2) => {
18774
20907
  const exitCode = await runSyncFilesCommand({
18775
20908
  action: "remove",
18776
20909
  cwd: options.cwd,
18777
- json: commandOptions.json || syncFilesCommand?.opts().json,
20910
+ json: commandOptions2.json || syncFilesCommand?.opts().json,
18778
20911
  paths,
18779
20912
  stderr: options.stderr,
18780
20913
  stdout: options.stdout
@@ -18784,11 +20917,11 @@ function attachCommandActions(program2, options) {
18784
20917
  }
18785
20918
  };
18786
20919
  syncFilesCommand?.commands.find((command) => command.name() === "remove")?.action(runSyncFilesRemoveCommand);
18787
- program2.commands.find((command) => command.name() === "ls")?.action(async (commandOptions) => {
20920
+ program2.commands.find((command) => command.name() === "ls")?.action(async (commandOptions2) => {
18788
20921
  const exitCode = await runLsCommand({
18789
- compact: commandOptions.compact,
20922
+ compact: commandOptions2.compact,
18790
20923
  cwd: options.cwd,
18791
- json: commandOptions.json,
20924
+ json: commandOptions2.json,
18792
20925
  stdout: options.stdout
18793
20926
  });
18794
20927
  if (exitCode !== 0) {
@@ -18796,13 +20929,13 @@ function attachCommandActions(program2, options) {
18796
20929
  }
18797
20930
  });
18798
20931
  program2.commands.find((command) => command.name() === "clean")?.action(
18799
- async (commandOptions) => {
20932
+ async (commandOptions2) => {
18800
20933
  const exitCode = await runCleanCommand({
18801
20934
  cwd: options.cwd,
18802
- dryRun: commandOptions.dryRun,
18803
- force: commandOptions.force,
18804
- json: commandOptions.json,
18805
- stale: commandOptions.stale,
20935
+ dryRun: commandOptions2.dryRun,
20936
+ force: commandOptions2.force,
20937
+ json: commandOptions2.json,
20938
+ stale: commandOptions2.stale,
18806
20939
  stderr: options.stderr,
18807
20940
  stdout: options.stdout
18808
20941
  });
@@ -18811,13 +20944,13 @@ function attachCommandActions(program2, options) {
18811
20944
  }
18812
20945
  }
18813
20946
  );
18814
- const runRemovalCommand = async (branch, commandOptions = {}) => {
20947
+ const runRemovalCommand = async (branch, commandOptions2 = {}) => {
18815
20948
  const exitCode = await runRemoveCommand({
18816
20949
  branch,
18817
20950
  cwd: options.cwd,
18818
- dryRun: commandOptions.dryRun,
18819
- force: commandOptions.force,
18820
- json: commandOptions.json,
20951
+ dryRun: commandOptions2.dryRun,
20952
+ force: commandOptions2.force,
20953
+ json: commandOptions2.json,
18821
20954
  stderr: options.stderr,
18822
20955
  stdout: options.stdout
18823
20956
  });
@@ -18837,14 +20970,14 @@ function attachCommandActions(program2, options) {
18837
20970
  }
18838
20971
  });
18839
20972
  program2.commands.find((command) => command.name() === "warp")?.action(
18840
- async (branch, commandOptions) => {
18841
- const newFlag = commandOptions.new;
20973
+ async (branch, commandOptions2) => {
20974
+ const newFlag = commandOptions2.new;
18842
20975
  const newWorktree = newFlag !== void 0 && newFlag !== false;
18843
20976
  const newBranch = typeof newFlag === "string" ? newFlag : void 0;
18844
20977
  const exitCode = await runWarpCommand({
18845
20978
  branch: newWorktree ? newBranch ?? branch : branch,
18846
20979
  cwd: options.cwd,
18847
- json: commandOptions.json,
20980
+ json: commandOptions2.json,
18848
20981
  newWorktree,
18849
20982
  stderr: options.stderr,
18850
20983
  stdout: options.stdout
@@ -18938,18 +21071,21 @@ async function main() {
18938
21071
  }
18939
21072
  async function warnIfMissingShellIntegration() {
18940
21073
  try {
18941
- const { config } = await loadGlobalConfig(homedir7());
21074
+ const { config } = await loadGlobalConfig(homedir8());
18942
21075
  if (!config.shellIntegration) {
18943
- const shellBin = (process.env.SHELL ?? "").split("/").at(-1);
18944
- const shellArg = shellBin && ["bash", "zsh", "fish"].includes(shellBin) ? ` ${shellBin}` : "";
21076
+ const setupCommand = isHeadless() ? legacyShellSetupCommand() : "gji init";
18945
21077
  process.stderr.write(
18946
- `gji: shell integration not set up \u2014 run \`gji init${shellArg} --write\` to enable automatic cd.
21078
+ `gji: shell integration not set up \u2014 run \`${setupCommand}\` to enable automatic cd.
18947
21079
  `
18948
21080
  );
18949
21081
  }
18950
21082
  } catch {
18951
21083
  }
18952
21084
  }
21085
+ function legacyShellSetupCommand() {
21086
+ const shell = (process.env.SHELL ?? "").split("/").at(-1);
21087
+ return shell && ["bash", "fish", "zsh"].includes(shell) ? `gji init ${shell} --write` : "gji init <shell> --write";
21088
+ }
18953
21089
  void main();
18954
21090
  /*! Bundled license information:
18955
21091