@solaqua/gji 0.11.0 → 0.12.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 (67) hide show
  1. package/README.md +82 -496
  2. package/dist/clean.js +2 -0
  3. package/dist/cli.d.ts +4 -0
  4. package/dist/cli.js +40 -4
  5. package/dist/context-card.d.ts +1 -0
  6. package/dist/context-card.js +25 -0
  7. package/dist/doctor.js +21 -0
  8. package/dist/done.js +2 -0
  9. package/dist/gji-bundle.mjs +988 -350
  10. package/dist/go.d.ts +1 -0
  11. package/dist/go.js +8 -0
  12. package/dist/hooks.d.ts +1 -0
  13. package/dist/hooks.js +7 -1
  14. package/dist/hub.d.ts +31 -0
  15. package/dist/hub.js +287 -0
  16. package/dist/ls.js +9 -1
  17. package/dist/new.d.ts +1 -0
  18. package/dist/new.js +9 -0
  19. package/dist/pr-open.js +2 -10
  20. package/dist/pull-requests.d.ts +1 -0
  21. package/dist/pull-requests.js +32 -3
  22. package/dist/remove.js +2 -0
  23. package/dist/repo.d.ts +1 -0
  24. package/dist/repo.js +4 -0
  25. package/dist/run-hook.js +2 -0
  26. package/dist/shell-completion.js +11 -3
  27. package/dist/slots.d.ts +8 -0
  28. package/dist/slots.js +61 -0
  29. package/dist/status.d.ts +2 -0
  30. package/dist/status.js +9 -1
  31. package/dist/task-command.d.ts +9 -0
  32. package/dist/task-command.js +29 -0
  33. package/dist/task.d.ts +9 -0
  34. package/dist/task.js +37 -0
  35. package/dist/terminal-text.d.ts +4 -0
  36. package/dist/terminal-text.js +72 -0
  37. package/dist/worktree-bootstrap.js +3 -0
  38. package/dist/worktree-info.d.ts +6 -1
  39. package/dist/worktree-info.js +14 -4
  40. package/dist/worktree-picker.d.ts +3 -3
  41. package/dist/worktree-picker.js +71 -82
  42. package/dist/worktree-sources.d.ts +1 -0
  43. package/dist/worktree-sources.js +57 -11
  44. package/man/man1/gji-back.1 +1 -1
  45. package/man/man1/gji-clean.1 +1 -1
  46. package/man/man1/gji-completion.1 +1 -1
  47. package/man/man1/gji-config.1 +1 -1
  48. package/man/man1/gji-doctor.1 +1 -1
  49. package/man/man1/gji-done.1 +1 -1
  50. package/man/man1/gji-go.1 +4 -1
  51. package/man/man1/gji-history.1 +1 -1
  52. package/man/man1/gji-init.1 +1 -1
  53. package/man/man1/gji-ls.1 +1 -1
  54. package/man/man1/gji-new.1 +4 -1
  55. package/man/man1/gji-open.1 +1 -1
  56. package/man/man1/gji-pr.1 +1 -1
  57. package/man/man1/gji-remove.1 +1 -1
  58. package/man/man1/gji-root.1 +1 -1
  59. package/man/man1/gji-run-hook.1 +1 -1
  60. package/man/man1/gji-status.1 +1 -1
  61. package/man/man1/gji-sync-files.1 +1 -1
  62. package/man/man1/gji-sync.1 +1 -1
  63. package/man/man1/gji-task.1 +16 -0
  64. package/man/man1/gji-undo.1 +1 -1
  65. package/man/man1/gji-warp.1 +1 -1
  66. package/man/man1/gji.1 +5 -1
  67. package/package.json +1 -1
@@ -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 = readFile10;
3955
- function readFile10(path9, options, cb) {
3954
+ fs6.readFile = readFile12;
3955
+ function readFile12(path9, options, cb) {
3956
3956
  if (typeof options === "function")
3957
3957
  cb = options, options = null;
3958
3958
  return go$readFile(path9, options, cb);
@@ -3968,8 +3968,8 @@ var require_graceful_fs = __commonJS({
3968
3968
  }
3969
3969
  }
3970
3970
  var fs$writeFile = fs6.writeFile;
3971
- fs6.writeFile = writeFile10;
3972
- function writeFile10(path9, data, options, cb) {
3971
+ fs6.writeFile = writeFile12;
3972
+ function writeFile12(path9, data, options, cb) {
3973
3973
  if (typeof options === "function")
3974
3974
  cb = options, options = null;
3975
3975
  return go$writeFile(path9, data, 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 join17 = __require("path").join;
5389
+ var join19 = __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
- join17(etc, name, "config"),
5415
- join17(etc, name + "rc")
5414
+ join19(etc, name, "config"),
5415
+ join19(etc, name + "rc")
5416
5416
  ].forEach(addConfigFile);
5417
5417
  if (home)
5418
5418
  [
5419
- join17(home, ".config", name, "config"),
5420
- join17(home, ".config", name),
5421
- join17(home, "." + name, "config"),
5422
- join17(home, "." + name + "rc")
5419
+ join19(home, ".config", name, "config"),
5420
+ join19(home, ".config", name),
5421
+ join19(home, "." + name, "config"),
5422
+ join19(home, "." + name + "rc")
5423
5423
  ].forEach(addConfigFile);
5424
5424
  addConfigFile(cc.find("." + name + "rc"));
5425
5425
  if (env5.config) addConfigFile(env5.config);
@@ -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 = readFile10;
5929
- function readFile10(path9, options, cb) {
5928
+ fs6.readFile = readFile12;
5929
+ function readFile12(path9, options, cb) {
5930
5930
  if (typeof options === "function")
5931
5931
  cb = options, options = null;
5932
5932
  return go$readFile(path9, options, cb);
@@ -5942,8 +5942,8 @@ var require_graceful_fs2 = __commonJS({
5942
5942
  }
5943
5943
  }
5944
5944
  var fs$writeFile = fs6.writeFile;
5945
- fs6.writeFile = writeFile10;
5946
- function writeFile10(path9, data, options, cb) {
5945
+ fs6.writeFile = writeFile12;
5946
+ function writeFile12(path9, data, options, cb) {
5947
5947
  if (typeof options === "function")
5948
5948
  cb = options, options = null;
5949
5949
  return go$writeFile(path9, data, 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 dirname13 = path9.dirname(name2);
6761
- if (dirname13 === name2) {
6760
+ const dirname14 = path9.dirname(name2);
6761
+ if (dirname14 === name2) {
6762
6762
  return original;
6763
6763
  }
6764
- return find(dirname13, original);
6764
+ return find(dirname14, original);
6765
6765
  } catch (error) {
6766
6766
  if (name2 === original) {
6767
6767
  if (error.code === "ENOENT") {
@@ -9422,7 +9422,7 @@ var require_picocolors = __commonJS({
9422
9422
  });
9423
9423
 
9424
9424
  // src/index.ts
9425
- import { homedir as homedir10 } from "node:os";
9425
+ import { homedir as homedir11 } from "node:os";
9426
9426
 
9427
9427
  // src/cli.ts
9428
9428
  import { createRequire } from "node:module";
@@ -9522,7 +9522,7 @@ var retryifyAsync = (fn, options) => {
9522
9522
  throw error;
9523
9523
  const delay2 = Math.round(interval * Math.random());
9524
9524
  if (delay2 > 0) {
9525
- const delayPromise = new Promise((resolve12) => setTimeout(resolve12, delay2));
9525
+ const delayPromise = new Promise((resolve13) => setTimeout(resolve13, delay2));
9526
9526
  return delayPromise.then(() => attempt.apply(void 0, args));
9527
9527
  } else {
9528
9528
  return attempt.apply(void 0, args);
@@ -9781,14 +9781,14 @@ var Temp = {
9781
9781
  }
9782
9782
  },
9783
9783
  truncate: (filePath) => {
9784
- const basename11 = path2.basename(filePath);
9785
- if (basename11.length <= LIMIT_BASENAME_LENGTH)
9784
+ const basename12 = path2.basename(filePath);
9785
+ if (basename12.length <= LIMIT_BASENAME_LENGTH)
9786
9786
  return filePath;
9787
- const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename11);
9787
+ const truncable = /^(\.?)(.*?)((?:\.[^.]+)?(?:\.tmp-\d{10}[a-f0-9]{6})?)$/.exec(basename12);
9788
9788
  if (!truncable)
9789
9789
  return filePath;
9790
- const truncationLength = basename11.length - LIMIT_BASENAME_LENGTH;
9791
- return `${filePath.slice(0, -basename11.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
9790
+ const truncationLength = basename12.length - LIMIT_BASENAME_LENGTH;
9791
+ return `${filePath.slice(0, -basename12.length)}${truncable[1]}${truncable[2].slice(0, -truncationLength)}${truncable[3]}`;
9792
9792
  }
9793
9793
  };
9794
9794
  node_default(Temp.purgeSyncAll);
@@ -11088,14 +11088,14 @@ var TimeoutError = class extends Error {
11088
11088
 
11089
11089
  // node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/timeout.js
11090
11090
  async function timeout(request, init, abortController, options) {
11091
- return new Promise((resolve12, reject) => {
11091
+ return new Promise((resolve13, reject) => {
11092
11092
  const timeoutId = setTimeout(() => {
11093
11093
  if (abortController) {
11094
11094
  abortController.abort();
11095
11095
  }
11096
11096
  reject(new TimeoutError(request));
11097
11097
  }, options.timeout);
11098
- void options.fetch(request, init).then(resolve12).catch(reject).then(() => {
11098
+ void options.fetch(request, init).then(resolve13).catch(reject).then(() => {
11099
11099
  clearTimeout(timeoutId);
11100
11100
  });
11101
11101
  });
@@ -11103,7 +11103,7 @@ async function timeout(request, init, abortController, options) {
11103
11103
 
11104
11104
  // node_modules/.pnpm/ky@1.14.3/node_modules/ky/distribution/utils/delay.js
11105
11105
  async function delay(ms, { signal }) {
11106
- return new Promise((resolve12, reject) => {
11106
+ return new Promise((resolve13, reject) => {
11107
11107
  if (signal) {
11108
11108
  signal.throwIfAborted();
11109
11109
  signal.addEventListener("abort", abortHandler, { once: true });
@@ -11114,7 +11114,7 @@ async function delay(ms, { signal }) {
11114
11114
  }
11115
11115
  const timeoutId = setTimeout(() => {
11116
11116
  signal?.removeEventListener("abort", abortHandler);
11117
- resolve12();
11117
+ resolve13();
11118
11118
  }, ms);
11119
11119
  });
11120
11120
  }
@@ -13219,7 +13219,7 @@ async function runArgvHook(hookCmd, cwd, context, stderr, stdout2) {
13219
13219
  stderr("gji: hook argv command must include a non-empty command\n");
13220
13220
  return;
13221
13221
  }
13222
- await new Promise((resolve12) => {
13222
+ await new Promise((resolve13) => {
13223
13223
  const child = spawn2(command, args, {
13224
13224
  cwd,
13225
13225
  shell: false,
@@ -13239,18 +13239,18 @@ async function runArgvHook(hookCmd, cwd, context, stderr, stdout2) {
13239
13239
  `
13240
13240
  );
13241
13241
  }
13242
- resolve12();
13242
+ resolve13();
13243
13243
  });
13244
13244
  child.on("error", (err) => {
13245
13245
  stderr(`gji: hook failed to start: ${err.message}
13246
13246
  `);
13247
- resolve12();
13247
+ resolve13();
13248
13248
  });
13249
13249
  });
13250
13250
  }
13251
13251
  async function runShellHook(hookCmd, cwd, context, stderr, stdout2) {
13252
13252
  const interpolated = interpolate(hookCmd, context);
13253
- await new Promise((resolve12) => {
13253
+ await new Promise((resolve13) => {
13254
13254
  const child = spawn2(interpolated, {
13255
13255
  cwd,
13256
13256
  shell: true,
@@ -13268,12 +13268,12 @@ async function runShellHook(hookCmd, cwd, context, stderr, stdout2) {
13268
13268
  stderr(`gji: hook exited with code ${code}: ${interpolated}
13269
13269
  `);
13270
13270
  }
13271
- resolve12();
13271
+ resolve13();
13272
13272
  });
13273
13273
  child.on("error", (err) => {
13274
13274
  stderr(`gji: hook failed to start: ${err.message}
13275
13275
  `);
13276
- resolve12();
13276
+ resolve13();
13277
13277
  });
13278
13278
  });
13279
13279
  }
@@ -13282,14 +13282,18 @@ function hookEnvironment(context) {
13282
13282
  ...process.env,
13283
13283
  GJI_BRANCH: context.branch ?? "",
13284
13284
  GJI_PATH: context.path,
13285
- GJI_REPO: context.repo
13285
+ GJI_REPO: context.repo,
13286
+ ...context.slot === null || context.slot === void 0 ? {} : { GJI_SLOT: String(context.slot) }
13286
13287
  };
13287
13288
  }
13288
13289
  function formatArgvHook(command, args) {
13289
13290
  return JSON.stringify([command, ...args]);
13290
13291
  }
13291
13292
  function interpolate(template, context) {
13292
- return template.replace(/\{\{branch\}\}/g, context.branch ?? "").replace(/\{\{path\}\}/g, context.path).replace(/\{\{repo\}\}/g, context.repo);
13293
+ return template.replace(/\{\{branch\}\}/g, context.branch ?? "").replace(/\{\{path\}\}/g, context.path).replace(/\{\{repo\}\}/g, context.repo).replace(
13294
+ /\{\{slot\}\}/g,
13295
+ context.slot === null || context.slot === void 0 ? "" : String(context.slot)
13296
+ );
13293
13297
  }
13294
13298
  function extractHooks(config) {
13295
13299
  const raw = config.hooks;
@@ -13486,6 +13490,10 @@ async function detectRepository(cwd) {
13486
13490
  repoRoot
13487
13491
  };
13488
13492
  }
13493
+ async function worktreeGitDir(worktreePath) {
13494
+ const rawGitDir = await runGit(worktreePath, ["rev-parse", "--git-dir"]);
13495
+ return isAbsolute2(rawGitDir) ? rawGitDir : resolve3(worktreePath, rawGitDir);
13496
+ }
13489
13497
  function resolveWorktreePath(repoRoot, branch, basePath) {
13490
13498
  const segments = branch.split("/").filter(Boolean);
13491
13499
  if (segments.length === 0) {
@@ -14277,42 +14285,98 @@ function isHeadless() {
14277
14285
  return process.env.GJI_NO_TUI === "1";
14278
14286
  }
14279
14287
 
14288
+ // src/slots.ts
14289
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile4 } from "node:fs/promises";
14290
+ import { homedir as homedir4 } from "node:os";
14291
+ import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
14292
+ function SLOTS_FILE_PATH(home = homedir4()) {
14293
+ const configDir = process.env.GJI_CONFIG_DIR;
14294
+ return configDir ? join4(resolve4(configDir), "slots.json") : join4(home, GLOBAL_CONFIG_DIRECTORY, "slots.json");
14295
+ }
14296
+ async function loadSlots(home = homedir4()) {
14297
+ try {
14298
+ const parsed = JSON.parse(
14299
+ await readFile3(SLOTS_FILE_PATH(home), "utf8")
14300
+ );
14301
+ if (!isSlotStore(parsed)) return {};
14302
+ return parsed;
14303
+ } catch {
14304
+ return {};
14305
+ }
14306
+ }
14307
+ async function assignWorktreeSlot(repoRoot, worktreePath, home = homedir4()) {
14308
+ const slots = await loadSlots(home);
14309
+ const existing = slots[worktreePath];
14310
+ if (existing !== void 0) return existing;
14311
+ if (worktreePath !== repoRoot && slots[repoRoot] === void 0)
14312
+ slots[repoRoot] = 0;
14313
+ const used = new Set(Object.values(slots));
14314
+ const slot = worktreePath === repoRoot ? 0 : nextAvailableSlot(used);
14315
+ slots[worktreePath] = slot;
14316
+ await saveSlots(slots, home);
14317
+ return slot;
14318
+ }
14319
+ async function getWorktreeSlot(worktreePath, home = homedir4()) {
14320
+ const slot = (await loadSlots(home))[worktreePath];
14321
+ return slot === void 0 ? null : slot;
14322
+ }
14323
+ async function releaseWorktreeSlot(worktreePath, home = homedir4()) {
14324
+ const slots = await loadSlots(home);
14325
+ if (!(worktreePath in slots)) return;
14326
+ delete slots[worktreePath];
14327
+ await saveSlots(slots, home);
14328
+ }
14329
+ async function saveSlots(slots, home) {
14330
+ const path9 = SLOTS_FILE_PATH(home);
14331
+ await mkdir3(dirname4(path9), { recursive: true });
14332
+ await writeFile4(path9, `${JSON.stringify(slots, null, 2)}
14333
+ `, "utf8");
14334
+ }
14335
+ function nextAvailableSlot(used) {
14336
+ let slot = 1;
14337
+ while (used.has(slot)) slot += 1;
14338
+ return slot;
14339
+ }
14340
+ function isSlotStore(value) {
14341
+ return typeof value === "object" && value !== null && Object.values(value).every((slot) => Number.isInteger(slot) && slot >= 0);
14342
+ }
14343
+
14280
14344
  // src/undo.ts
14281
14345
  import {
14282
14346
  access as access3,
14283
- mkdir as mkdir3,
14347
+ mkdir as mkdir4,
14284
14348
  open,
14285
- readFile as readFile3,
14349
+ readFile as readFile4,
14286
14350
  rename,
14287
14351
  stat,
14288
14352
  unlink,
14289
- writeFile as writeFile4
14353
+ writeFile as writeFile5
14290
14354
  } from "node:fs/promises";
14291
- import { homedir as homedir4 } from "node:os";
14292
- import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
14355
+ import { homedir as homedir5 } from "node:os";
14356
+ import { dirname as dirname5, join as join5, resolve as resolve5 } from "node:path";
14293
14357
  var MAX_UNDO_RECORDS = 20;
14294
14358
  var UNDO_LOCK_STALE_AFTER_MS = 6e4;
14295
14359
  var UNDO_LOCK_RETRY_DELAY_MS = 5;
14296
14360
  var UNDO_LOCK_WAIT_TIMEOUT_MS = UNDO_LOCK_STALE_AFTER_MS + UNDO_LOCK_RETRY_DELAY_MS;
14297
14361
  var undoLogQueues = /* @__PURE__ */ new Map();
14298
- function undoLogPath(home = homedir4()) {
14362
+ function undoLogPath(home = homedir5()) {
14299
14363
  const configured = process.env.GJI_CONFIG_DIR;
14300
- return join4(
14301
- configured ? resolve4(configured) : join4(home, GLOBAL_CONFIG_DIRECTORY),
14364
+ return join5(
14365
+ configured ? resolve5(configured) : join5(home, GLOBAL_CONFIG_DIRECTORY),
14302
14366
  "undo-log.json"
14303
14367
  );
14304
14368
  }
14305
- async function loadUndoRecords(home = homedir4()) {
14369
+ async function loadUndoRecords(home = homedir5()) {
14306
14370
  try {
14307
14371
  const parsed = JSON.parse(
14308
- await readFile3(undoLogPath(home), "utf8")
14372
+ await readFile4(undoLogPath(home), "utf8")
14309
14373
  );
14310
14374
  return Array.isArray(parsed) ? parsed.filter(isUndoRecord) : [];
14311
14375
  } catch {
14312
14376
  return [];
14313
14377
  }
14314
14378
  }
14315
- async function recordUndoOperation(op, repoRoot, worktrees, home = homedir4()) {
14379
+ async function recordUndoOperation(op, repoRoot, worktrees, home = homedir5()) {
14316
14380
  const entries = [];
14317
14381
  for (const worktree of worktrees) {
14318
14382
  try {
@@ -14355,7 +14419,7 @@ async function recordUndoOperation(op, repoRoot, worktrees, home = homedir4()) {
14355
14419
  return record;
14356
14420
  });
14357
14421
  }
14358
- async function finalizeUndoOperation(record, removedWorktrees, home = homedir4()) {
14422
+ async function finalizeUndoOperation(record, removedWorktrees, home = homedir5()) {
14359
14423
  const removedPaths = new Set(
14360
14424
  removedWorktrees.map((worktree) => worktree.path)
14361
14425
  );
@@ -14371,7 +14435,7 @@ async function finalizeUndoOperation(record, removedWorktrees, home = homedir4()
14371
14435
  await writeUndoRecords(home, remaining);
14372
14436
  });
14373
14437
  }
14374
- async function restoreUndoRecord(record, home = homedir4()) {
14438
+ async function restoreUndoRecord(record, home = homedir5()) {
14375
14439
  const restored = [];
14376
14440
  const failed = [];
14377
14441
  const unresolved = [];
@@ -14478,7 +14542,7 @@ async function runUndoCommand(options) {
14478
14542
  }
14479
14543
  async function hasMalformedUndoLog() {
14480
14544
  try {
14481
- const parsed = JSON.parse(await readFile3(undoLogPath(), "utf8"));
14545
+ const parsed = JSON.parse(await readFile4(undoLogPath(), "utf8"));
14482
14546
  return !Array.isArray(parsed) || parsed.some((entry) => !isUndoRecord(entry));
14483
14547
  } catch (error) {
14484
14548
  return error instanceof Error && "code" in error && error.code !== "ENOENT";
@@ -14569,7 +14633,7 @@ async function assertCommitExists(repoRoot, headSha) {
14569
14633
  }
14570
14634
  async function enqueueUndoLogWrite(home, operation) {
14571
14635
  const path9 = undoLogPath(home);
14572
- await mkdir3(dirname4(path9), { recursive: true });
14636
+ await mkdir4(dirname5(path9), { recursive: true });
14573
14637
  const previous = undoLogQueues.get(path9) ?? Promise.resolve();
14574
14638
  const runLocked = async () => {
14575
14639
  const release = await acquireUndoLogLock(path9);
@@ -14610,7 +14674,7 @@ async function acquireUndoLogLock(path9) {
14610
14674
  const remainingMs = deadline - Date.now();
14611
14675
  if (remainingMs > 0)
14612
14676
  await new Promise(
14613
- (resolve12) => setTimeout(resolve12, Math.min(UNDO_LOCK_RETRY_DELAY_MS, remainingMs))
14677
+ (resolve13) => setTimeout(resolve13, Math.min(UNDO_LOCK_RETRY_DELAY_MS, remainingMs))
14614
14678
  );
14615
14679
  }
14616
14680
  }
@@ -14618,10 +14682,10 @@ async function acquireUndoLogLock(path9) {
14618
14682
  }
14619
14683
  async function writeUndoRecords(home, records) {
14620
14684
  const path9 = undoLogPath(home);
14621
- await mkdir3(dirname4(path9), { recursive: true });
14685
+ await mkdir4(dirname5(path9), { recursive: true });
14622
14686
  const temporaryPath = `${path9}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
14623
14687
  try {
14624
- await writeFile4(temporaryPath, `${JSON.stringify(records, null, 2)}
14688
+ await writeFile5(temporaryPath, `${JSON.stringify(records, null, 2)}
14625
14689
  `, {
14626
14690
  encoding: "utf8",
14627
14691
  flag: "wx"
@@ -14660,6 +14724,41 @@ function emitUndoError(options, message) {
14660
14724
  return 1;
14661
14725
  }
14662
14726
 
14727
+ // src/task.ts
14728
+ import { readFile as readFile5, unlink as unlink2, writeFile as writeFile6 } from "node:fs/promises";
14729
+ import { join as join6 } from "node:path";
14730
+ async function readTask(worktreePath) {
14731
+ try {
14732
+ const parsed = JSON.parse(
14733
+ await readFile5(await taskPath(worktreePath), "utf8")
14734
+ );
14735
+ return isTaskMetadata(parsed) ? parsed : null;
14736
+ } catch {
14737
+ return null;
14738
+ }
14739
+ }
14740
+ async function writeTask(worktreePath, task, source = "manual") {
14741
+ const metadata = { source, task, updatedAt: Date.now() };
14742
+ const path9 = await taskPath(worktreePath);
14743
+ await writeFile6(path9, `${JSON.stringify(metadata, null, 2)}
14744
+ `, "utf8");
14745
+ return metadata;
14746
+ }
14747
+ async function clearTask(worktreePath) {
14748
+ try {
14749
+ await unlink2(await taskPath(worktreePath));
14750
+ } catch {
14751
+ }
14752
+ }
14753
+ async function taskPath(worktreePath) {
14754
+ return join6(await worktreeGitDir(worktreePath), "gji-task.json");
14755
+ }
14756
+ function isTaskMetadata(value) {
14757
+ if (typeof value !== "object" || value === null) return false;
14758
+ const candidate = value;
14759
+ return typeof candidate.task === "string" && (candidate.source === "manual" || candidate.source === "pr") && typeof candidate.updatedAt === "number";
14760
+ }
14761
+
14663
14762
  // src/worktree-info.ts
14664
14763
  var MAX_WORKTREE_INFO_READ_CONCURRENCY = 8;
14665
14764
  async function readWorktreeInfos(worktrees) {
@@ -14686,16 +14785,22 @@ async function mapWithConcurrency(items, limit, mapper) {
14686
14785
  return results;
14687
14786
  }
14688
14787
  async function readWorktreeInfo(worktree) {
14689
- const [healthResult, lastCommitResult] = await Promise.allSettled([
14788
+ const [healthResult, lastCommitResult, slotResult, taskResult] = await Promise.allSettled([
14690
14789
  readWorktreeHealth(worktree.path),
14691
- worktree.branch === null ? null : readBranchLastCommitTimestamp(worktree.path, worktree.branch)
14790
+ worktree.branch === null ? null : readBranchLastCommitTimestamp(worktree.path, worktree.branch),
14791
+ getWorktreeSlot(worktree.path),
14792
+ readTask(worktree.path)
14692
14793
  ]);
14693
14794
  const health = healthResult.status === "fulfilled" ? healthResult.value : null;
14694
14795
  const lastCommitTimestamp = lastCommitResult.status === "fulfilled" ? lastCommitResult.value : null;
14796
+ const slot = slotResult.status === "fulfilled" ? slotResult.value : null;
14797
+ const task = taskResult.status === "fulfilled" ? taskResult.value?.task ?? null : null;
14695
14798
  return {
14696
14799
  ...worktree,
14697
14800
  lastCommitTimestamp,
14801
+ slot,
14698
14802
  status: health?.status ?? "unknown",
14803
+ task,
14699
14804
  upstream: buildUpstreamState(worktree.branch, health)
14700
14805
  };
14701
14806
  }
@@ -14723,7 +14828,9 @@ function serializeWorktreeInfo(info) {
14723
14828
  branch: info.branch,
14724
14829
  lastCommitTimestamp: info.lastCommitTimestamp,
14725
14830
  path: info.path,
14831
+ slot: info.slot,
14726
14832
  status: info.status,
14833
+ task: info.task,
14727
14834
  upstream: info.upstream
14728
14835
  };
14729
14836
  }
@@ -14757,11 +14864,8 @@ function formatAheadBehind(ahead, behind) {
14757
14864
  function formatLastCommit(timestampSeconds) {
14758
14865
  return timestampSeconds === null ? "n/a" : formatRelativeAge(timestampSeconds);
14759
14866
  }
14760
- function formatRelativeAge(timestampSeconds) {
14761
- const ageSeconds = Math.max(
14762
- 0,
14763
- Math.floor(Date.now() / 1e3) - timestampSeconds
14764
- );
14867
+ function formatRelativeAge(timestampSeconds, nowSeconds = Math.floor(Date.now() / 1e3)) {
14868
+ const ageSeconds = Math.max(0, nowSeconds - timestampSeconds);
14765
14869
  const units = [
14766
14870
  { label: "y", seconds: 365 * 24 * 60 * 60 },
14767
14871
  { label: "mo", seconds: 30 * 24 * 60 * 60 },
@@ -14871,6 +14975,7 @@ import { execFile as execFile3 } from "node:child_process";
14871
14975
  import { promisify as promisify4 } from "node:util";
14872
14976
  var execFileAsync3 = promisify4(execFile3);
14873
14977
  var PULL_REQUEST_QUERY_TIMEOUT_MS = 2500;
14978
+ var PULL_REQUEST_CACHE_TTL_MS = 3e4;
14874
14979
  function parsePullRequestRemote(remoteUrl) {
14875
14980
  const parsed = parseRemoteUrl(remoteUrl.trim());
14876
14981
  if (parsed === null) return null;
@@ -14905,10 +15010,37 @@ function parsePullRequestRemote(remoteUrl) {
14905
15010
  function createPullRequestQuery(dependencies = {}) {
14906
15011
  const runCommand2 = dependencies.runCommand ?? defaultRunCommand;
14907
15012
  const fetcher = dependencies.fetch ?? fetch;
15013
+ const repositoryCache = /* @__PURE__ */ new Map();
15014
+ const repositoryInFlight = /* @__PURE__ */ new Map();
15015
+ const listRepositoryPullRequests = (repoRoot) => {
15016
+ const cached = repositoryCache.get(repoRoot);
15017
+ if (cached !== void 0 && cached.expiresAt > Date.now()) {
15018
+ return Promise.resolve(cached.value);
15019
+ }
15020
+ const inFlight = repositoryInFlight.get(repoRoot);
15021
+ if (inFlight !== void 0) return inFlight;
15022
+ const request = listOpenPullRequestsForRepository(
15023
+ repoRoot,
15024
+ runCommand2,
15025
+ fetcher
15026
+ ).then((value) => {
15027
+ repositoryCache.set(repoRoot, {
15028
+ expiresAt: Date.now() + PULL_REQUEST_CACHE_TTL_MS,
15029
+ value
15030
+ });
15031
+ repositoryInFlight.delete(repoRoot);
15032
+ return value;
15033
+ }).catch((error) => {
15034
+ repositoryInFlight.delete(repoRoot);
15035
+ throw error;
15036
+ });
15037
+ repositoryInFlight.set(repoRoot, request);
15038
+ return request;
15039
+ };
14908
15040
  return {
14909
15041
  findOpenPullRequest: (repoRoot, number) => findOpenPullRequest(repoRoot, number, runCommand2, fetcher),
14910
15042
  listOpenPullRequests: (repoRoot, sourceBranch) => listOpenPullRequests(repoRoot, sourceBranch, runCommand2, fetcher),
14911
- listOpenPullRequestsForRepository: (repoRoot) => listOpenPullRequestsForRepository(repoRoot, runCommand2, fetcher)
15043
+ listOpenPullRequestsForRepository: listRepositoryPullRequests
14912
15044
  };
14913
15045
  }
14914
15046
  async function listOpenPullRequests(repoRoot, sourceBranch, runCommand2, fetcher) {
@@ -15039,7 +15171,7 @@ function providerCliCommand(remote, query) {
15039
15171
  "view",
15040
15172
  String(query.number),
15041
15173
  "--json",
15042
- "number,url,headRefName,state",
15174
+ "number,url,headRefName,title,state",
15043
15175
  "--repo",
15044
15176
  coordinate
15045
15177
  ],
@@ -15054,7 +15186,7 @@ function providerCliCommand(remote, query) {
15054
15186
  "open",
15055
15187
  ...query.kind === "branch" ? ["--head", sourceBranch] : [],
15056
15188
  "--json",
15057
- "number,url,headRefName,state",
15189
+ "number,url,headRefName,title,state",
15058
15190
  "--limit",
15059
15191
  "100",
15060
15192
  "--repo",
@@ -15155,6 +15287,7 @@ function normalizePullRequest(value, query) {
15155
15287
  const url = stringValue(
15156
15288
  value.html_url ?? value.web_url ?? value.url ?? (isRecord(value.links) && isRecord(value.links.html) ? value.links.html.href : void 0)
15157
15289
  );
15290
+ const title = stringValue(value.title);
15158
15291
  const state = stringValue(value.state)?.toLowerCase();
15159
15292
  if (number === null || url === null) return null;
15160
15293
  if (query.kind !== "number" && sourceBranch === null) return null;
@@ -15163,6 +15296,7 @@ function normalizePullRequest(value, query) {
15163
15296
  return {
15164
15297
  number,
15165
15298
  sourceBranch: sourceBranch ?? "",
15299
+ ...title === null ? {} : { title },
15166
15300
  url
15167
15301
  };
15168
15302
  }
@@ -15226,8 +15360,61 @@ function numberValue(value) {
15226
15360
  return null;
15227
15361
  }
15228
15362
 
15363
+ // src/terminal-text.ts
15364
+ function sanitizeTerminalText(value) {
15365
+ return Array.from(value, (character) => {
15366
+ const codePoint = character.codePointAt(0) ?? 0;
15367
+ return codePoint <= 31 || codePoint >= 127 && codePoint <= 159 ? " " : character;
15368
+ }).join("");
15369
+ }
15370
+ function middleEllipsize(value, maxLength) {
15371
+ if (terminalWidth(value) <= maxLength) return value;
15372
+ if (maxLength <= 1) return "\u2026";
15373
+ const keep = maxLength - 1;
15374
+ const start = takeTerminalColumns(value, Math.ceil(keep / 2), "start");
15375
+ const end = takeTerminalColumns(value, Math.floor(keep / 2), "end");
15376
+ return `${start}\u2026${end}`;
15377
+ }
15378
+ function startEllipsize(value, maxLength) {
15379
+ if (terminalWidth(value) <= maxLength) return value;
15380
+ if (maxLength <= 1) return "\u2026";
15381
+ return `\u2026${takeTerminalColumns(value, maxLength - 1, "end")}`;
15382
+ }
15383
+ function terminalWidth(value) {
15384
+ let width = 0;
15385
+ for (const character of Array.from(value)) {
15386
+ width += characterTerminalWidth(character);
15387
+ }
15388
+ return width;
15389
+ }
15390
+ function takeTerminalColumns(value, maxWidth, direction) {
15391
+ const characters = direction === "start" ? Array.from(value) : Array.from(value).reverse();
15392
+ const kept = [];
15393
+ let width = 0;
15394
+ for (const character of characters) {
15395
+ const characterWidth = characterTerminalWidth(character);
15396
+ if (width + characterWidth > maxWidth) break;
15397
+ kept.push(character);
15398
+ width += characterWidth;
15399
+ }
15400
+ return direction === "start" ? kept.join("") : kept.reverse().join("");
15401
+ }
15402
+ function characterTerminalWidth(character) {
15403
+ const codePoint = character.codePointAt(0);
15404
+ if (codePoint === void 0) return 0;
15405
+ if (isZeroWidthCodePoint(codePoint) || new RegExp("\\p{Mark}", "u").test(character)) return 0;
15406
+ return isWideCodePoint(codePoint) ? 2 : 1;
15407
+ }
15408
+ function isZeroWidthCodePoint(codePoint) {
15409
+ return codePoint === 0 || codePoint === 8205 || codePoint >= 65024 && codePoint <= 65039;
15410
+ }
15411
+ function isWideCodePoint(codePoint) {
15412
+ return codePoint >= 4352 && codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 42191 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65135 || codePoint >= 65280 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127744 && codePoint <= 129791 || codePoint >= 131072 && codePoint <= 262141;
15413
+ }
15414
+
15229
15415
  // src/worktree-picker.ts
15230
15416
  var MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY = 4;
15417
+ var MAX_TASK_READ_CONCURRENCY = 8;
15231
15418
  async function buildWorktreePromptEntries(sources, dependencies = {}) {
15232
15419
  const metadataMode = dependencies.metadata ?? "full";
15233
15420
  const includeMetadata = metadataMode === "full";
@@ -15237,10 +15424,10 @@ async function buildWorktreePromptEntries(sources, dependencies = {}) {
15237
15424
  const repositoryPullRequests = queryRepositoryPullRequests === void 0 ? Promise.resolve(null) : readRepositoryPullRequests(sources, queryRepositoryPullRequests);
15238
15425
  const [history, infos, pullRequestsByRepository] = await Promise.all([
15239
15426
  loadHistory(),
15240
- includeMetadata ? readWorktreeInfos(sources.map((source) => source.worktree)) : Promise.resolve(
15241
- sources.map(
15242
- (source) => createUnhydratedWorktreeInfo(source.worktree)
15243
- )
15427
+ includeMetadata ? readWorktreeInfos(sources.map((source) => source.worktree)) : mapWithConcurrency2(
15428
+ sources,
15429
+ MAX_TASK_READ_CONCURRENCY,
15430
+ (source) => createUnhydratedWorktreeInfo(source.worktree)
15244
15431
  ),
15245
15432
  repositoryPullRequests
15246
15433
  ]);
@@ -15267,11 +15454,18 @@ async function buildWorktreePromptEntries(sources, dependencies = {}) {
15267
15454
  ({ lastActivityTimestamp: _lastActivityTimestamp, ...entry }) => entry
15268
15455
  );
15269
15456
  }
15270
- function createUnhydratedWorktreeInfo(worktree) {
15457
+ async function createUnhydratedWorktreeInfo(worktree) {
15458
+ let task = null;
15459
+ try {
15460
+ task = (await readTask(worktree.path))?.task ?? null;
15461
+ } catch {
15462
+ }
15271
15463
  return {
15272
15464
  ...worktree,
15273
15465
  lastCommitTimestamp: null,
15466
+ slot: null,
15274
15467
  status: "unknown",
15468
+ task,
15275
15469
  upstream: { kind: "unknown" }
15276
15470
  };
15277
15471
  }
@@ -15377,18 +15571,22 @@ function buildSearchableWorktreeEntry(worktree) {
15377
15571
  const metadata = worktree.metadata ?? null;
15378
15572
  const branch = worktree.branch ?? "(detached)";
15379
15573
  return {
15380
- detail: [worktree.repoName, branch, metadata, worktree.path].filter((part) => part !== null && part.length > 0).join(" \xB7 "),
15574
+ detail: [
15575
+ worktree.repoName,
15576
+ branch,
15577
+ metadata,
15578
+ worktree.task == null ? null : sanitizeTerminalText(worktree.task),
15579
+ worktree.path
15580
+ ].filter((part) => part !== null && part.length > 0).join(" \xB7 "),
15381
15581
  label: worktree.label,
15382
15582
  searchText: buildPromptSearchText(worktree),
15383
15583
  value: worktree.path,
15384
15584
  worktree: {
15385
- branch: formatWorktreeBranchLabel(
15386
- worktree.branch,
15387
- worktree.pullRequestNumbers
15388
- ),
15585
+ branch: formatWorktreeBranchLabel(worktree.branch, worktree.pullRequests),
15389
15586
  metadata,
15390
15587
  path: worktree.path,
15391
- repoName: worktree.repoName
15588
+ repoName: worktree.repoName,
15589
+ task: worktree.task == null ? null : sanitizeTerminalText(worktree.task)
15392
15590
  }
15393
15591
  };
15394
15592
  }
@@ -15706,6 +15904,7 @@ var SearchablePrompt = class {
15706
15904
  ellipsize: middleEllipsize,
15707
15905
  max: 18,
15708
15906
  min: 6,
15907
+ removablePriority: entry.worktree.task === null ? void 0 : 1,
15709
15908
  value: entry.worktree.repoName
15710
15909
  },
15711
15910
  {
@@ -15719,13 +15918,23 @@ var SearchablePrompt = class {
15719
15918
  ellipsize: middleEllipsize,
15720
15919
  max: 30,
15721
15920
  min: 10,
15921
+ removablePriority: entry.worktree.task === null ? void 0 : 0,
15722
15922
  value: entry.worktree.metadata
15723
15923
  }
15724
15924
  ],
15925
+ ...entry.worktree.task === null ? [] : [
15926
+ {
15927
+ ellipsize: middleEllipsize,
15928
+ max: 36,
15929
+ min: 10,
15930
+ value: entry.worktree.task
15931
+ }
15932
+ ],
15725
15933
  {
15726
15934
  ellipsize: startEllipsize,
15727
15935
  max: 36,
15728
15936
  min: 12,
15937
+ removablePriority: entry.worktree.task === null ? void 0 : 2,
15729
15938
  value: entry.worktree.path
15730
15939
  }
15731
15940
  ],
@@ -15940,12 +16149,27 @@ function windowPromptEntries(entries, cursor, maxItems) {
15940
16149
  }
15941
16150
  return window;
15942
16151
  }
16152
+ function findPromptPieceToRemove(pieces, fallbackIndex) {
16153
+ let candidate = fallbackIndex;
16154
+ let priority = Infinity;
16155
+ for (let index = 0; index < pieces.length; index += 1) {
16156
+ const piecePriority = pieces[index].removablePriority;
16157
+ if (piecePriority !== void 0 && piecePriority < priority) {
16158
+ candidate = index;
16159
+ priority = piecePriority;
16160
+ }
16161
+ }
16162
+ return candidate;
16163
+ }
15943
16164
  function fitPromptPieces(pieces, width) {
15944
16165
  const separator = " \xB7 ";
15945
16166
  let visiblePieces = pieces.filter((piece) => piece.value.length > 0);
15946
16167
  let available = width - terminalWidth(separator) * Math.max(0, visiblePieces.length - 1);
15947
16168
  while (visiblePieces.length > 1 && available < minimumPieceLength(visiblePieces)) {
15948
- const removableIndex = visiblePieces.length === 4 ? 2 : visiblePieces.length - 2;
16169
+ const removableIndex = findPromptPieceToRemove(
16170
+ visiblePieces,
16171
+ visiblePieces.length === 4 ? 2 : visiblePieces.length - 2
16172
+ );
15949
16173
  visiblePieces = visiblePieces.filter(
15950
16174
  (_3, index) => index !== removableIndex
15951
16175
  );
@@ -16015,9 +16239,13 @@ function buildPromptSearchText(worktree) {
16015
16239
  worktree.branch ?? "detached",
16016
16240
  worktree.path,
16017
16241
  worktree.label,
16242
+ worktree.task ?? "",
16018
16243
  `${worktree.repoName}/${worktree.branch ?? "detached"}`,
16019
- ...(worktree.pullRequestNumbers ?? []).map((number) => `#${number}`),
16020
- ...worktree.pullRequestUrls ?? []
16244
+ ...(worktree.pullRequests ?? []).flatMap((pullRequest) => [
16245
+ `#${pullRequest.number}`,
16246
+ pullRequest.title ?? "",
16247
+ pullRequest.url
16248
+ ])
16021
16249
  ].join(" ").toLowerCase();
16022
16250
  }
16023
16251
  function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp, now) {
@@ -16026,7 +16254,7 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
16026
16254
  const lastActivityType = lastUsedTimestamp !== null ? "used" : lastWorkedTimestamp !== null ? "worked" : null;
16027
16255
  const branch = formatWorktreeBranchLabel(
16028
16256
  source.worktree.branch,
16029
- pullRequests.map((pullRequest) => pullRequest.number)
16257
+ pullRequests
16030
16258
  );
16031
16259
  const badges = buildStatusBadges(info);
16032
16260
  const recency = formatPromptRecency(
@@ -16044,6 +16272,7 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
16044
16272
  middleEllipsize(source.repoName, 22),
16045
16273
  middleEllipsize(branch, 34),
16046
16274
  metadata,
16275
+ info.task === null ? null : middleEllipsize(sanitizeTerminalText(info.task), 36),
16047
16276
  path9
16048
16277
  ].filter((part) => part !== null && part.length > 0).join(" \xB7 ");
16049
16278
  return {
@@ -16052,9 +16281,9 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
16052
16281
  label,
16053
16282
  lastActivityTimestamp,
16054
16283
  metadata,
16055
- pullRequestNumbers: pullRequests.map((pullRequest) => pullRequest.number),
16056
- pullRequestUrls: pullRequests.map((pullRequest) => pullRequest.url),
16057
- repoName: source.repoName
16284
+ pullRequests,
16285
+ repoName: source.repoName,
16286
+ task: info.task
16058
16287
  };
16059
16288
  }
16060
16289
  async function readSourcePullRequests(source, queryPullRequests, pullRequestsByRepository) {
@@ -16121,10 +16350,13 @@ async function mapWithConcurrency2(items, limit, mapper) {
16121
16350
  function sortSourcePullRequests(pullRequests) {
16122
16351
  return [...pullRequests ?? []].sort((a, b3) => a.number - b3.number);
16123
16352
  }
16124
- function formatWorktreeBranchLabel(branch, pullRequestNumbers = []) {
16353
+ function formatWorktreeBranchLabel(branch, pullRequests = []) {
16125
16354
  const branchLabel = branch ?? "(detached)";
16126
- if (pullRequestNumbers.length === 0) return branchLabel;
16127
- return `${branchLabel} (${pullRequestNumbers.map((number) => `#${number}`).join(", ")})`;
16355
+ if (pullRequests.length === 0) return branchLabel;
16356
+ return `${branchLabel} (${pullRequests.map((pullRequest) => {
16357
+ const title = pullRequest.title ? ` ${middleEllipsize(sanitizeTerminalText(pullRequest.title), 48)}` : "";
16358
+ return `#${pullRequest.number}${title}`;
16359
+ }).join(", ")})`;
16128
16360
  }
16129
16361
  function buildStatusBadges(info) {
16130
16362
  const badges = [];
@@ -16220,60 +16452,6 @@ function scoreWorktreeMatch(entry, query) {
16220
16452
  }
16221
16453
  return buildSearchText(entry.repoName, entry).includes(query) ? 1 : null;
16222
16454
  }
16223
- function middleEllipsize(value, maxLength) {
16224
- if (terminalWidth(value) <= maxLength) {
16225
- return value;
16226
- }
16227
- if (maxLength <= 1) {
16228
- return "\u2026";
16229
- }
16230
- const keep = maxLength - 1;
16231
- const start = takeTerminalColumns(value, Math.ceil(keep / 2), "start");
16232
- const end = takeTerminalColumns(value, Math.floor(keep / 2), "end");
16233
- return `${start}\u2026${end}`;
16234
- }
16235
- function startEllipsize(value, maxLength) {
16236
- if (terminalWidth(value) <= maxLength) {
16237
- return value;
16238
- }
16239
- if (maxLength <= 1) {
16240
- return "\u2026";
16241
- }
16242
- return `\u2026${takeTerminalColumns(value, maxLength - 1, "end")}`;
16243
- }
16244
- function takeTerminalColumns(value, maxWidth, direction) {
16245
- const characters = direction === "start" ? Array.from(value) : Array.from(value).reverse();
16246
- const kept = [];
16247
- let width = 0;
16248
- for (const character of characters) {
16249
- const characterWidth = terminalWidth(character);
16250
- if (width + characterWidth > maxWidth) break;
16251
- kept.push(character);
16252
- width += characterWidth;
16253
- }
16254
- return direction === "start" ? kept.join("") : kept.reverse().join("");
16255
- }
16256
- function terminalWidth(value) {
16257
- let width = 0;
16258
- for (const character of Array.from(value)) {
16259
- width += characterTerminalWidth(character);
16260
- }
16261
- return width;
16262
- }
16263
- function characterTerminalWidth(character) {
16264
- const codePoint = character.codePointAt(0);
16265
- if (codePoint === void 0) return 0;
16266
- if (isZeroWidthCodePoint(codePoint) || new RegExp("\\p{Mark}", "u").test(character)) {
16267
- return 0;
16268
- }
16269
- return isWideCodePoint(codePoint) ? 2 : 1;
16270
- }
16271
- function isZeroWidthCodePoint(codePoint) {
16272
- return codePoint === 0 || codePoint === 8205 || codePoint >= 65024 && codePoint <= 65039;
16273
- }
16274
- function isWideCodePoint(codePoint) {
16275
- return codePoint >= 4352 && codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 42191 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65135 || codePoint >= 65280 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510 || codePoint >= 127744 && codePoint <= 129791 || codePoint >= 131072 && codePoint <= 262141;
16276
- }
16277
16455
 
16278
16456
  // src/worktree-prompts.ts
16279
16457
  async function defaultConfirmForceRemoveWorktree(worktreePath) {
@@ -16486,6 +16664,9 @@ function createCleanCommand(dependencies = {}) {
16486
16664
  }
16487
16665
  }
16488
16666
  await finalizeUndoOperation(journal, removedWorktrees);
16667
+ await Promise.all(
16668
+ removedWorktrees.map((worktree) => releaseWorktreeSlot(worktree.path))
16669
+ );
16489
16670
  if (options.json) {
16490
16671
  const removed = removedWorktrees.map((worktree) => {
16491
16672
  const info = selectedInfoByPath.get(worktree.path);
@@ -16700,6 +16881,7 @@ var TOP_LEVEL_COMMANDS = [
16700
16881
  { name: "jump", description: "alias of go" },
16701
16882
  { name: "root", description: "print the main repository root path" },
16702
16883
  { name: "status", description: "summarize repository and worktree health" },
16884
+ { name: "task", description: "show or update the current worktree task" },
16703
16885
  { name: "sync", description: "fetch and update one or all worktrees" },
16704
16886
  {
16705
16887
  name: "sync-files",
@@ -16816,7 +16998,7 @@ _gji_completion() {
16816
16998
  cur="\${COMP_WORDS[COMP_CWORD]:-}"
16817
16999
 
16818
17000
  if [ "$COMP_CWORD" -eq 1 ]; then
16819
- COMPREPLY=( $(compgen -W "${topLevelCommands}" -- "$cur") )
17001
+ COMPREPLY=( $(compgen -W "${topLevelCommands} --json --help" -- "$cur") )
16820
17002
  return 0
16821
17003
  fi
16822
17004
 
@@ -16824,7 +17006,7 @@ _gji_completion() {
16824
17006
 
16825
17007
  case "$command_name" in
16826
17008
  new)
16827
- COMPREPLY=( $(compgen -W "--detached --from-current --no-fetch --take --copy --force --open --editor --dry-run --json --help" -- "$cur") )
17009
+ COMPREPLY=( $(compgen -W "--detached --from-current --no-fetch --take --copy --task --force --open --editor --dry-run --json --help" -- "$cur") )
16828
17010
  ;;
16829
17011
  done)
16830
17012
  COMPREPLY=( $(compgen -W "--force --keep-branch --json --help" -- "$cur") )
@@ -17075,6 +17257,7 @@ complete -c gji -n '__fish_seen_subcommand_from new' -l detached -d 'create a de
17075
17257
  complete -c gji -n '__fish_seen_subcommand_from new' -l from-current -d 'base the new branch on the current worktree instead of the main worktree'
17076
17258
  complete -c gji -n '__fish_seen_subcommand_from new' -l take -d 'move current uncommitted changes into the new worktree'
17077
17259
  complete -c gji -n '__fish_seen_subcommand_from new' -l copy -d 'copy current uncommitted changes instead of moving them (requires --take)'
17260
+ complete -c gji -n '__fish_seen_subcommand_from new' -l task -r -d 'record the purpose of the new worktree'
17078
17261
  complete -c gji -n '__fish_seen_subcommand_from new' -l force -d 'remove and recreate the worktree if the target path already exists'
17079
17262
  complete -c gji -n '__fish_seen_subcommand_from new' -l open -d 'open the new worktree in an editor after creation'
17080
17263
  complete -c gji -n '__fish_seen_subcommand_from new' -l editor -r -d 'editor CLI to use with --open (code, cursor, zed, \u2026)'
@@ -17112,11 +17295,14 @@ complete -c gji -n '__fish_seen_subcommand_from open' -a '(__gji_worktree_branch
17112
17295
  complete -c gji -n '__fish_seen_subcommand_from go jump' -l print -d 'print the resolved worktree path explicitly'
17113
17296
  complete -c gji -n '__fish_seen_subcommand_from go jump' -l root -d 'navigate to the main repository root'
17114
17297
  complete -c gji -n '__fish_seen_subcommand_from go jump' -l json -d 'emit JSON for an existing worktree destination'
17298
+ complete -c gji -n '__fish_seen_subcommand_from go jump' -l quiet -d 'suppress the context card'
17115
17299
  complete -c gji -n '__fish_seen_subcommand_from go jump' -a '(__gji_worktree_branches)' -d 'worktree branch'
17116
17300
 
17117
17301
  complete -c gji -n '__fish_seen_subcommand_from root' -l print -d 'print the resolved repository root path explicitly'
17118
17302
 
17119
17303
  complete -c gji -n '__fish_seen_subcommand_from status' -l json -d 'print repository and worktree health as JSON'
17304
+ complete -c gji -n '__fish_seen_subcommand_from task' -l clear -d 'clear the current worktree task'
17305
+ complete -c gji -n '__fish_seen_subcommand_from task' -l json -d 'emit task metadata as JSON'
17120
17306
 
17121
17307
  complete -c gji -n '__fish_seen_subcommand_from sync' -l all -d 'sync every worktree in the repository'
17122
17308
  complete -c gji -n '__fish_seen_subcommand_from sync' -l json -d 'emit JSON on success or error instead of human-readable output'
@@ -17142,6 +17328,8 @@ complete -c gji -n '__fish_seen_subcommand_from remove rm' -a '(__gji_worktree_b
17142
17328
 
17143
17329
  ${hookLines}
17144
17330
 
17331
+ complete -c gji -n '__fish_use_subcommand' -l json -d 'emit the repository hub as JSON'
17332
+
17145
17333
  complete -c gji -n '__fish_seen_subcommand_from warp' -l print -d 'print the resolved worktree path without changing directory'
17146
17334
  complete -c gji -n '__fish_seen_subcommand_from warp' -l json -d 'emit JSON on success or error instead of human-readable output'
17147
17335
 
@@ -17222,6 +17410,7 @@ local -a command_entries worktree_branches
17222
17410
 
17223
17411
  command_entries=(
17224
17412
  ${commandLines}
17413
+ '--json:emit the repository hub as JSON'
17225
17414
  )
17226
17415
 
17227
17416
  if (( CURRENT == 2 )); then
@@ -17231,7 +17420,7 @@ fi
17231
17420
 
17232
17421
  case "\${words[2]}" in
17233
17422
  new)
17234
- _arguments '--detached[create a detached worktree without a branch]' '--from-current[base the new branch on the current worktree instead of the main worktree]' '--no-fetch[skip refreshing the remote default branch before creating the new branch]' '--take[move current uncommitted changes into the new worktree]' '--copy[copy current uncommitted changes instead of moving them (requires --take)]' '--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: '
17423
+ _arguments '--detached[create a detached worktree without a branch]' '--from-current[base the new branch on the current worktree instead of the main worktree]' '--no-fetch[skip refreshing the remote default branch before creating the new branch]' '--take[move current uncommitted changes into the new worktree]' '--copy[copy current uncommitted changes instead of moving them (requires --take)]' '--task[record the purpose of the new worktree]:description:' '--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: '
17235
17424
  ;;
17236
17425
  done)
17237
17426
  _arguments '--force[remove dirty or unmerged worktrees without prompting]' '--keep-branch[remove the worktree but preserve its branch]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch: '
@@ -17400,9 +17589,9 @@ function writeJson(stdout2, value) {
17400
17589
  // src/doctor.ts
17401
17590
  import { execFile as execFile4 } from "node:child_process";
17402
17591
  import { constants as constants3 } from "node:fs";
17403
- import { access as access6, readFile as readFile5 } from "node:fs/promises";
17404
- import { homedir as homedir6 } from "node:os";
17405
- import { basename as basename4, dirname as dirname6, join as join7 } from "node:path";
17592
+ import { access as access6, readFile as readFile7 } from "node:fs/promises";
17593
+ import { homedir as homedir7 } from "node:os";
17594
+ import { basename as basename4, dirname as dirname7, join as join9 } from "node:path";
17406
17595
  import { promisify as promisify5 } from "node:util";
17407
17596
 
17408
17597
  // src/editor.ts
@@ -17436,9 +17625,9 @@ var EDITORS = [
17436
17625
  ];
17437
17626
  async function defaultSpawnEditor(cli, args) {
17438
17627
  const child = spawn3(cli, args, { detached: true, stdio: "ignore" });
17439
- await new Promise((resolve12, reject) => {
17628
+ await new Promise((resolve13, reject) => {
17440
17629
  child.once("error", reject);
17441
- child.once("spawn", resolve12);
17630
+ child.once("spawn", resolve13);
17442
17631
  });
17443
17632
  child.unref();
17444
17633
  }
@@ -17448,29 +17637,29 @@ import { randomUUID } from "node:crypto";
17448
17637
  import { constants } from "node:fs";
17449
17638
  import {
17450
17639
  access as access4,
17451
- mkdir as mkdir4,
17452
- readFile as readFile4,
17640
+ mkdir as mkdir5,
17641
+ readFile as readFile6,
17453
17642
  realpath,
17454
17643
  rename as rename2,
17455
17644
  rm as rm2,
17456
- writeFile as writeFile5
17645
+ writeFile as writeFile7
17457
17646
  } from "node:fs/promises";
17458
- import { homedir as homedir5 } from "node:os";
17459
- import { basename as basename3, dirname as dirname5, join as join5, resolve as resolve5 } from "node:path";
17647
+ import { homedir as homedir6 } from "node:os";
17648
+ import { basename as basename3, dirname as dirname6, join as join7, resolve as resolve6 } from "node:path";
17460
17649
  var REGISTRY_FILE_NAME = "repos.json";
17461
17650
  var MAX_REGISTRY_ENTRIES = 100;
17462
17651
  var registryLocks = /* @__PURE__ */ new Map();
17463
- function REGISTRY_FILE_PATH(home = homedir5()) {
17652
+ function REGISTRY_FILE_PATH(home = homedir6()) {
17464
17653
  const configDir = process.env.GJI_CONFIG_DIR;
17465
17654
  if (configDir) {
17466
- return join5(resolve5(configDir), REGISTRY_FILE_NAME);
17655
+ return join7(resolve6(configDir), REGISTRY_FILE_NAME);
17467
17656
  }
17468
- return join5(home, GLOBAL_CONFIG_DIRECTORY, REGISTRY_FILE_NAME);
17657
+ return join7(home, GLOBAL_CONFIG_DIRECTORY, REGISTRY_FILE_NAME);
17469
17658
  }
17470
- async function loadRegistry(home = homedir5()) {
17659
+ async function loadRegistry(home = homedir6()) {
17471
17660
  const path9 = REGISTRY_FILE_PATH(home);
17472
17661
  try {
17473
- const raw = await readFile4(path9, "utf8");
17662
+ const raw = await readFile6(path9, "utf8");
17474
17663
  const parsed = JSON.parse(raw);
17475
17664
  if (!Array.isArray(parsed)) return [];
17476
17665
  return parsed.filter(isRegistryEntry);
@@ -17482,10 +17671,10 @@ async function canonicalizeRepoPath(repoPath) {
17482
17671
  try {
17483
17672
  return await realpath(repoPath);
17484
17673
  } catch {
17485
- return resolve5(repoPath);
17674
+ return resolve6(repoPath);
17486
17675
  }
17487
17676
  }
17488
- async function registerRepo(repoPath, home = homedir5()) {
17677
+ async function registerRepo(repoPath, home = homedir6()) {
17489
17678
  const registryPath = REGISTRY_FILE_PATH(home);
17490
17679
  await withRegistryLock(registryPath, async () => {
17491
17680
  const existing = await normalizeRegistryForWrite(await loadRegistry(home));
@@ -17501,7 +17690,7 @@ async function registerRepo(repoPath, home = homedir5()) {
17501
17690
  await writeRegistry(next, registryPath);
17502
17691
  });
17503
17692
  }
17504
- async function removeMissingRegistryEntries(paths, home = homedir5()) {
17693
+ async function removeMissingRegistryEntries(paths, home = homedir6()) {
17505
17694
  if (paths.size === 0) return { removedPaths: [], skippedPaths: [] };
17506
17695
  const registryPath = REGISTRY_FILE_PATH(home);
17507
17696
  return withRegistryLock(registryPath, async () => {
@@ -17527,8 +17716,8 @@ async function removeMissingRegistryEntries(paths, home = homedir5()) {
17527
17716
  async function withRegistryLock(registryPath, operation) {
17528
17717
  const previous = registryLocks.get(registryPath);
17529
17718
  let release;
17530
- const current = new Promise((resolve12) => {
17531
- release = resolve12;
17719
+ const current = new Promise((resolve13) => {
17720
+ release = resolve13;
17532
17721
  });
17533
17722
  registryLocks.set(registryPath, current);
17534
17723
  if (previous) await previous;
@@ -17555,10 +17744,10 @@ function isMissingPathError(error) {
17555
17744
  return code === "ENOENT" || code === "ENOTDIR";
17556
17745
  }
17557
17746
  async function writeRegistry(entries, registryPath) {
17558
- await mkdir4(dirname5(registryPath), { recursive: true });
17747
+ await mkdir5(dirname6(registryPath), { recursive: true });
17559
17748
  const temporaryPath = `${registryPath}.tmp-${process.pid}-${randomUUID()}`;
17560
17749
  try {
17561
- await writeFile5(
17750
+ await writeFile7(
17562
17751
  temporaryPath,
17563
17752
  `${JSON.stringify(entries, null, 2)}
17564
17753
  `,
@@ -17594,13 +17783,13 @@ function isRegistryEntry(value) {
17594
17783
  // src/shell-setup.ts
17595
17784
  import { constants as constants2 } from "node:fs";
17596
17785
  import { access as access5 } from "node:fs/promises";
17597
- import { delimiter, join as join6 } from "node:path";
17786
+ import { delimiter, join as join8 } from "node:path";
17598
17787
  var SHELL_INTEGRATION_START_MARKER = "# >>> gji init >>>";
17599
17788
  var SHELL_INTEGRATION_END_MARKER = "# <<< gji init <<<";
17600
17789
  var ONBOARDING_SHELL_INTEGRATION_START_MARKER = "# >>> gji shell integration >>>";
17601
17790
  var ONBOARDING_SHELL_INTEGRATION_END_MARKER = "# <<< gji shell integration <<<";
17602
17791
  async function executableExists(command) {
17603
- const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => join6(directory, command));
17792
+ const candidates = command.includes("/") ? [command] : (process.env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => join8(directory, command));
17604
17793
  for (const path9 of candidates) {
17605
17794
  try {
17606
17795
  await access5(path9, constants2.X_OK);
@@ -17613,17 +17802,17 @@ async function executableExists(command) {
17613
17802
  function resolveShellConfigPath(shell, home) {
17614
17803
  switch (shell) {
17615
17804
  case "bash":
17616
- return join6(home, ".bashrc");
17805
+ return join8(home, ".bashrc");
17617
17806
  case "fish":
17618
- return join6(home, ".config", "fish", "config.fish");
17807
+ return join8(home, ".config", "fish", "config.fish");
17619
17808
  case "zsh":
17620
- return join6(home, ".zshrc");
17809
+ return join8(home, ".zshrc");
17621
17810
  }
17622
17811
  }
17623
17812
  function resolveCompletionPath(shell, home) {
17624
17813
  switch (shell) {
17625
17814
  case "bash":
17626
- return join6(
17815
+ return join8(
17627
17816
  home,
17628
17817
  ".local",
17629
17818
  "share",
@@ -17632,9 +17821,9 @@ function resolveCompletionPath(shell, home) {
17632
17821
  "gji"
17633
17822
  );
17634
17823
  case "fish":
17635
- return join6(home, ".config", "fish", "completions", "gji.fish");
17824
+ return join8(home, ".config", "fish", "completions", "gji.fish");
17636
17825
  case "zsh":
17637
- return join6(home, ".zsh", "completions", "_gji");
17826
+ return join8(home, ".zsh", "completions", "_gji");
17638
17827
  }
17639
17828
  }
17640
17829
  function hasShellIntegration(config, shell) {
@@ -17665,7 +17854,7 @@ async function runDoctorCommand(options) {
17665
17854
  }
17666
17855
  return 1;
17667
17856
  }
17668
- const home = options.home ?? homedir6();
17857
+ const home = options.home ?? homedir7();
17669
17858
  const shell = resolveSupportedShell(
17670
17859
  void 0,
17671
17860
  options.shell ?? process.env.SHELL
@@ -17711,7 +17900,7 @@ async function collectDoctorInspection(cwd, home, shell) {
17711
17900
  KNOWN_GLOBAL_CONFIG_KEYS
17712
17901
  );
17713
17902
  const localConfig = repository ? await inspectConfig(
17714
- join7(repository.repoRoot, CONFIG_FILE_NAME),
17903
+ join9(repository.repoRoot, CONFIG_FILE_NAME),
17715
17904
  "local",
17716
17905
  KNOWN_CONFIG_KEYS
17717
17906
  ) : null;
@@ -17734,11 +17923,30 @@ async function collectDoctorInspection(cwd, home, shell) {
17734
17923
  ),
17735
17924
  await checkWorktreeBase(repository, effectiveConfig, home),
17736
17925
  registry.check,
17737
- await checkEditor(effectiveConfig)
17926
+ await checkEditor(effectiveConfig),
17927
+ await checkOrphanSlots(home)
17738
17928
  ],
17739
17929
  missingRegistryPaths: registry.missingPaths
17740
17930
  };
17741
17931
  }
17932
+ async function checkOrphanSlots(home) {
17933
+ const slots = await loadSlots(home);
17934
+ const missing = (await Promise.all(
17935
+ Object.keys(slots).map(async (path9) => {
17936
+ try {
17937
+ await access6(path9);
17938
+ return false;
17939
+ } catch {
17940
+ return true;
17941
+ }
17942
+ })
17943
+ )).filter(Boolean).length;
17944
+ return {
17945
+ id: "slots",
17946
+ message: missing === 0 ? "worktree slots healthy" : `${missing} orphaned worktree slot${missing === 1 ? "" : "s"}`,
17947
+ status: "ok"
17948
+ };
17949
+ }
17742
17950
  function buildDoctorFixes(missingRegistryPaths) {
17743
17951
  if (missingRegistryPaths.length === 0) return [];
17744
17952
  const count = missingRegistryPaths.length;
@@ -17801,7 +18009,7 @@ async function detectRepositoryOrSkip(cwd) {
17801
18009
  }
17802
18010
  async function inspectConfig(path9, label, knownKeys) {
17803
18011
  try {
17804
- const contents = await readFile5(path9, "utf8");
18012
+ const contents = await readFile7(path9, "utf8");
17805
18013
  const value = JSON.parse(contents);
17806
18014
  if (!isConfigObject(value)) {
17807
18015
  return {
@@ -17871,7 +18079,7 @@ function resolvePerRepoConfig(repos, repoRoot, home) {
17871
18079
  }
17872
18080
  function expandTilde2(path9, home) {
17873
18081
  if (path9 === "~") return home;
17874
- if (path9.startsWith("~/")) return join7(home, path9.slice(2));
18082
+ if (path9.startsWith("~/")) return join9(home, path9.slice(2));
17875
18083
  return path9;
17876
18084
  }
17877
18085
  async function checkGitVersion() {
@@ -17922,7 +18130,7 @@ async function checkShellIntegration(shell, home) {
17922
18130
  }
17923
18131
  const rcPath = resolveShellConfigPath(shell, home);
17924
18132
  try {
17925
- const contents = await readFile5(rcPath, "utf8");
18133
+ const contents = await readFile7(rcPath, "utf8");
17926
18134
  if (hasShellIntegration(contents, shell)) {
17927
18135
  return okCheck(
17928
18136
  "shell-integration",
@@ -17998,7 +18206,7 @@ function resolveWorktreeBase(repoRoot, config, home) {
17998
18206
  if (typeof configuredPath === "string" && (configuredPath.startsWith("/") || configuredPath.startsWith("~"))) {
17999
18207
  return expandTilde2(configuredPath, home);
18000
18208
  }
18001
- return join7(dirname6(repoRoot), "worktrees", basename4(repoRoot));
18209
+ return join9(dirname7(repoRoot), "worktrees", basename4(repoRoot));
18002
18210
  }
18003
18211
  async function findNearestExistingPath(path9) {
18004
18212
  let candidate = path9;
@@ -18007,7 +18215,7 @@ async function findNearestExistingPath(path9) {
18007
18215
  await access6(candidate, constants3.F_OK);
18008
18216
  return candidate;
18009
18217
  } catch {
18010
- const parent = dirname6(candidate);
18218
+ const parent = dirname7(candidate);
18011
18219
  if (parent === candidate) return null;
18012
18220
  candidate = parent;
18013
18221
  }
@@ -18308,6 +18516,7 @@ async function runDoneCommand(options) {
18308
18516
  }
18309
18517
  }
18310
18518
  await finalizeUndoOperation(journal, [target]);
18519
+ await releaseWorktreeSlot(target.path);
18311
18520
  await runGit(repository.repoRoot, ["worktree", "prune"]).catch(
18312
18521
  () => void 0
18313
18522
  );
@@ -18397,10 +18606,34 @@ function toMessage3(error) {
18397
18606
  // src/go.ts
18398
18607
  import { basename as basename9 } from "node:path";
18399
18608
 
18609
+ // src/context-card.ts
18610
+ async function renderContextCard(worktreePath) {
18611
+ const worktree = (await listWorktrees(worktreePath)).find(
18612
+ (entry) => entry.path === worktreePath
18613
+ );
18614
+ if (!worktree) return null;
18615
+ const info = await readWorktreeInfo(worktree);
18616
+ const task = await readTask(worktreePath);
18617
+ if (!task) return null;
18618
+ const rows = [`\u250C ${info.branch ?? "(detached)"}`];
18619
+ rows.push(`\u2502 task ${task.task}`);
18620
+ rows.push(`\u2502 state ${formatState(info)}`);
18621
+ if (info.lastCommitTimestamp !== null)
18622
+ rows.push(`\u2502 last ${formatLastCommit2(info)}`);
18623
+ rows.push("\u2514");
18624
+ return rows.join("\n");
18625
+ }
18626
+ function formatState(info) {
18627
+ return `${info.status}${info.upstream.kind === "tracked" ? ` \xB7 \u2191${info.upstream.ahead} \u2193${info.upstream.behind}` : ""}`;
18628
+ }
18629
+ function formatLastCommit2(info) {
18630
+ return new Date(info.lastCommitTimestamp).toISOString();
18631
+ }
18632
+
18400
18633
  // src/pr.ts
18401
18634
  import { execFile as execFile7 } from "node:child_process";
18402
- import { mkdir as mkdir8 } from "node:fs/promises";
18403
- import { dirname as dirname10 } from "node:path";
18635
+ import { mkdir as mkdir9 } from "node:fs/promises";
18636
+ import { dirname as dirname11 } from "node:path";
18404
18637
  import { promisify as promisify8 } from "node:util";
18405
18638
 
18406
18639
  // src/format-bytes.ts
@@ -18449,27 +18682,27 @@ function formatDuration(ms) {
18449
18682
 
18450
18683
  // src/dependency-bootstrap.ts
18451
18684
  import { execFile as execFile6 } from "node:child_process";
18452
- import { lstat as lstat4, readFile as readFile8, realpath as realpath3, rm as rm5 } from "node:fs/promises";
18453
- import { join as join11, relative as relative2, resolve as resolve8, sep as sep2 } from "node:path";
18685
+ import { lstat as lstat4, readFile as readFile10, realpath as realpath3, rm as rm5 } from "node:fs/promises";
18686
+ import { join as join13, relative as relative2, resolve as resolve9, sep as sep2 } from "node:path";
18454
18687
  import { promisify as promisify7 } from "node:util";
18455
18688
 
18456
18689
  // src/clone-failure-store.ts
18457
18690
  import { randomUUID as randomUUID2 } from "node:crypto";
18458
18691
  import {
18459
- mkdir as mkdir5,
18692
+ mkdir as mkdir6,
18460
18693
  mkdtemp,
18461
18694
  readdir,
18462
- readFile as readFile6,
18695
+ readFile as readFile8,
18463
18696
  rename as rename3,
18464
18697
  rm as rm3,
18465
18698
  rmdir,
18466
18699
  stat as stat2,
18467
- unlink as unlink2,
18700
+ unlink as unlink3,
18468
18701
  utimes,
18469
- writeFile as writeFile6
18702
+ writeFile as writeFile8
18470
18703
  } from "node:fs/promises";
18471
- import { homedir as homedir7 } from "node:os";
18472
- import { dirname as dirname7, join as join8, resolve as resolve6 } from "node:path";
18704
+ import { homedir as homedir8 } from "node:os";
18705
+ import { dirname as dirname8, join as join10, resolve as resolve7 } from "node:path";
18473
18706
  var STATE_FILE_NAME = "state.json";
18474
18707
  var STATE_LOCK_SUFFIX = ".lock";
18475
18708
  var CLONE_FAILURE_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -18536,7 +18769,7 @@ var FileCloneFailureStore = class {
18536
18769
  }
18537
18770
  async readState() {
18538
18771
  try {
18539
- const raw = await readFile6(this.stateFilePath(), "utf8");
18772
+ const raw = await readFile8(this.stateFilePath(), "utf8");
18540
18773
  const parsed = JSON.parse(raw);
18541
18774
  if (!isPlainObject2(parsed)) return {};
18542
18775
  return isPlainObject2(parsed.syncDirs) ? parsed : { ...parsed, syncDirs: {} };
@@ -18547,14 +18780,14 @@ var FileCloneFailureStore = class {
18547
18780
  async writeState(state) {
18548
18781
  try {
18549
18782
  const path9 = this.stateFilePath();
18550
- const directory = dirname7(path9);
18551
- await mkdir5(directory, { recursive: true });
18783
+ const directory = dirname8(path9);
18784
+ await mkdir6(directory, { recursive: true });
18552
18785
  const temporaryDirectory = await mkdtemp(
18553
- join8(directory, `.gji-state-${randomUUID2()}-`)
18786
+ join10(directory, `.gji-state-${randomUUID2()}-`)
18554
18787
  );
18555
- const temporaryPath = join8(temporaryDirectory, STATE_FILE_NAME);
18788
+ const temporaryPath = join10(temporaryDirectory, STATE_FILE_NAME);
18556
18789
  try {
18557
- await writeFile6(
18790
+ await writeFile8(
18558
18791
  temporaryPath,
18559
18792
  `${JSON.stringify(state, null, 2)}
18560
18793
  `,
@@ -18567,18 +18800,18 @@ var FileCloneFailureStore = class {
18567
18800
  } catch {
18568
18801
  }
18569
18802
  }
18570
- stateFilePath(home = homedir7()) {
18803
+ stateFilePath(home = homedir8()) {
18571
18804
  const configuredDirectory = process.env.GJI_CONFIG_DIR;
18572
- const directory = configuredDirectory ? resolve6(configuredDirectory) : join8(home, GLOBAL_CONFIG_DIRECTORY);
18573
- return join8(directory, STATE_FILE_NAME);
18805
+ const directory = configuredDirectory ? resolve7(configuredDirectory) : join10(home, GLOBAL_CONFIG_DIRECTORY);
18806
+ return join10(directory, STATE_FILE_NAME);
18574
18807
  }
18575
18808
  };
18576
18809
  async function acquireStateLock(lockPath) {
18577
18810
  const lockToken = randomUUID2();
18578
- await mkdir5(dirname7(lockPath), { recursive: true }).catch(() => void 0);
18811
+ await mkdir6(dirname8(lockPath), { recursive: true }).catch(() => void 0);
18579
18812
  for (let attempt = 0; attempt < 200; attempt += 1) {
18580
18813
  try {
18581
- await mkdir5(lockPath);
18814
+ await mkdir6(lockPath);
18582
18815
  } catch (error) {
18583
18816
  if (!isErrorCode(error, "EEXIST")) return void 0;
18584
18817
  try {
@@ -18598,12 +18831,12 @@ async function acquireStateLock(lockPath) {
18598
18831
  } catch (lockError) {
18599
18832
  if (!isErrorCode(lockError, "ENOENT")) return void 0;
18600
18833
  }
18601
- await new Promise((resolve12) => setTimeout(resolve12, 25));
18834
+ await new Promise((resolve13) => setTimeout(resolve13, 25));
18602
18835
  continue;
18603
18836
  }
18604
18837
  try {
18605
- await writeFile6(
18606
- join8(lockPath, ownerFileName(lockToken)),
18838
+ await writeFile8(
18839
+ join10(lockPath, ownerFileName(lockToken)),
18607
18840
  `${lockToken}
18608
18841
  `,
18609
18842
  {
@@ -18625,7 +18858,7 @@ async function lockFreshnessPath(lockPath) {
18625
18858
  const owner = (await readdir(lockPath)).find(
18626
18859
  (entry) => entry.startsWith("owner-")
18627
18860
  );
18628
- return owner ? join8(lockPath, owner) : lockPath;
18861
+ return owner ? join10(lockPath, owner) : lockPath;
18629
18862
  } catch (error) {
18630
18863
  if (isErrorCode(error, "ENOENT")) throw error;
18631
18864
  return lockPath;
@@ -18640,8 +18873,8 @@ function startStateLockHeartbeat(lockPath, lockToken) {
18640
18873
  }
18641
18874
  async function refreshStateLock(lockPath, lockToken) {
18642
18875
  try {
18643
- const ownerPath = join8(lockPath, ownerFileName(lockToken));
18644
- await readFile6(ownerPath, "utf8");
18876
+ const ownerPath = join10(lockPath, ownerFileName(lockToken));
18877
+ await readFile8(ownerPath, "utf8");
18645
18878
  const now = /* @__PURE__ */ new Date();
18646
18879
  await utimes(ownerPath, now, now);
18647
18880
  } catch {
@@ -18649,8 +18882,8 @@ async function refreshStateLock(lockPath, lockToken) {
18649
18882
  }
18650
18883
  async function releaseStateLock(lockPath, lockToken) {
18651
18884
  try {
18652
- const ownerPath = join8(lockPath, ownerFileName(lockToken));
18653
- await unlink2(ownerPath);
18885
+ const ownerPath = join10(lockPath, ownerFileName(lockToken));
18886
+ await unlink3(ownerPath);
18654
18887
  await rmdir(lockPath);
18655
18888
  } catch (error) {
18656
18889
  if (!isErrorCode(error, "ENOENT") && !isErrorCode(error, "ENOTEMPTY")) {
@@ -18663,8 +18896,8 @@ function ownerFileName(lockToken) {
18663
18896
  }
18664
18897
  var defaultCloneFailureStore = new FileCloneFailureStore();
18665
18898
  async function cloneFailureScope(source, destination) {
18666
- const sourcePath = resolve6(source);
18667
- const destinationParent = resolve6(dirname7(destination));
18899
+ const sourcePath = resolve7(source);
18900
+ const destinationParent = resolve7(dirname8(destination));
18668
18901
  const [sourceDevice, destinationDevice] = await Promise.all([
18669
18902
  readDevice(sourcePath),
18670
18903
  readDevice(destinationParent)
@@ -18691,7 +18924,7 @@ function isErrorCode(error, code) {
18691
18924
  // src/command-runner.ts
18692
18925
  import { spawn as spawn4 } from "node:child_process";
18693
18926
  var runCommand = async (command, cwd, stderr, stdout2 = (chunk) => process.stdout.write(chunk), options) => {
18694
- await new Promise((resolve12, reject) => {
18927
+ await new Promise((resolve13, reject) => {
18695
18928
  const shell = options?.shell ?? true;
18696
18929
  const [executable, args] = shell ? [command, []] : splitCommand(command);
18697
18930
  const child = spawn4(executable, args, {
@@ -18710,7 +18943,7 @@ var runCommand = async (command, cwd, stderr, stdout2 = (chunk) => process.stdou
18710
18943
  if (code !== 0) {
18711
18944
  reject(new Error(`exited with code ${code}`));
18712
18945
  } else {
18713
- resolve12();
18946
+ resolve13();
18714
18947
  }
18715
18948
  });
18716
18949
  child.on("error", reject);
@@ -18734,22 +18967,22 @@ import {
18734
18967
  chmod,
18735
18968
  cp,
18736
18969
  lstat as lstat3,
18737
- mkdir as mkdir7,
18970
+ mkdir as mkdir8,
18738
18971
  mkdtemp as mkdtemp2,
18739
18972
  opendir,
18740
18973
  readdir as readdir2,
18741
- readFile as readFile7,
18974
+ readFile as readFile9,
18742
18975
  readlink,
18743
18976
  realpath as realpath2,
18744
18977
  rename as rename4,
18745
18978
  rm as rm4,
18746
18979
  rmdir as rmdir2,
18747
18980
  symlink,
18748
- unlink as unlink3,
18981
+ unlink as unlink4,
18749
18982
  utimes as utimes2,
18750
- writeFile as writeFile7
18983
+ writeFile as writeFile9
18751
18984
  } from "node:fs/promises";
18752
- import { basename as basename6, dirname as dirname8, join as join10 } from "node:path";
18985
+ import { basename as basename6, dirname as dirname9, join as join12 } from "node:path";
18753
18986
  import { promisify as promisify6 } from "node:util";
18754
18987
 
18755
18988
  // src/fs-utils.ts
@@ -18778,20 +19011,20 @@ function hasErrorCode(error, code) {
18778
19011
 
18779
19012
  // src/safe-destination.ts
18780
19013
  import { constants as constants4 } from "node:fs";
18781
- import { lstat as lstat2, mkdir as mkdir6, open as open2 } from "node:fs/promises";
18782
- import { isAbsolute as isAbsolute3, join as join9, relative, resolve as resolve7, sep } from "node:path";
19014
+ import { lstat as lstat2, mkdir as mkdir7, open as open2 } from "node:fs/promises";
19015
+ import { isAbsolute as isAbsolute3, join as join11, relative, resolve as resolve8, sep } from "node:path";
18783
19016
  async function openDestinationDirectory(root, path9) {
18784
19017
  const segments = destinationSegments(root, path9);
18785
19018
  if (process.platform !== "linux") {
18786
19019
  await ensureDestinationDirectory(root, path9);
18787
- return { path: resolve7(path9), close: async () => void 0 };
19020
+ return { path: resolve8(path9), close: async () => void 0 };
18788
19021
  }
18789
19022
  let handle = await open2(root, destinationDirectoryFlags());
18790
19023
  try {
18791
19024
  for (const segment of segments) {
18792
- const childPath = join9(fileDescriptorPath(handle.fd), segment);
19025
+ const childPath = join11(fileDescriptorPath(handle.fd), segment);
18793
19026
  try {
18794
- await mkdir6(childPath);
19027
+ await mkdir7(childPath);
18795
19028
  } catch (error) {
18796
19029
  if (!isAlreadyExistsError(error)) throw error;
18797
19030
  }
@@ -18809,8 +19042,8 @@ async function openDestinationDirectory(root, path9) {
18809
19042
  }
18810
19043
  }
18811
19044
  async function inspectDestination(root, path9) {
18812
- const resolvedPath = resolve7(path9);
18813
- const resolvedRoot = resolve7(root);
19045
+ const resolvedPath = resolve8(path9);
19046
+ const resolvedRoot = resolve8(root);
18814
19047
  const distance = relative(resolvedRoot, resolvedPath);
18815
19048
  if (isAbsolute3(distance) || distance === ".." || distance.startsWith(`..${sep}`)) {
18816
19049
  return { kind: "unsafe", reason: "destination escapes the worktree" };
@@ -18827,7 +19060,7 @@ async function inspectDestination(root, path9) {
18827
19060
  }
18828
19061
  const segments = distance.split(sep).filter(Boolean);
18829
19062
  for (const [index, segment] of segments.entries()) {
18830
- current = resolve7(current, segment);
19063
+ current = resolve8(current, segment);
18831
19064
  try {
18832
19065
  const stats = await lstat2(current);
18833
19066
  if (stats.isSymbolicLink()) {
@@ -18868,8 +19101,8 @@ async function inspectDestination(root, path9) {
18868
19101
  }
18869
19102
  }
18870
19103
  function destinationSegments(root, path9) {
18871
- const resolvedRoot = resolve7(root);
18872
- const resolvedPath = resolve7(path9);
19104
+ const resolvedRoot = resolve8(root);
19105
+ const resolvedPath = resolve8(path9);
18873
19106
  const distance = relative(resolvedRoot, resolvedPath);
18874
19107
  if (isAbsolute3(distance) || distance === ".." || distance.startsWith(`..${sep}`)) {
18875
19108
  throw new Error("destination escapes the worktree");
@@ -18887,8 +19120,8 @@ function fileDescriptorPath(fd) {
18887
19120
  );
18888
19121
  }
18889
19122
  async function ensureDestinationDirectory(root, path9) {
18890
- const resolvedRoot = resolve7(root);
18891
- const resolvedPath = resolve7(path9);
19123
+ const resolvedRoot = resolve8(root);
19124
+ const resolvedPath = resolve8(path9);
18892
19125
  const distance = relative(resolvedRoot, resolvedPath);
18893
19126
  if (isAbsolute3(distance) || distance === ".." || distance.startsWith(`..${sep}`)) {
18894
19127
  throw new Error("destination escapes the worktree");
@@ -18899,7 +19132,7 @@ async function ensureDestinationDirectory(root, path9) {
18899
19132
  throw new Error("worktree root is not a directory");
18900
19133
  }
18901
19134
  for (const segment of distance.split(sep).filter(Boolean)) {
18902
- current = resolve7(current, segment);
19135
+ current = resolve8(current, segment);
18903
19136
  try {
18904
19137
  const stats = await lstat2(current);
18905
19138
  if (stats.isSymbolicLink() || !stats.isDirectory()) {
@@ -18908,7 +19141,7 @@ async function ensureDestinationDirectory(root, path9) {
18908
19141
  } catch (error) {
18909
19142
  if (!isNotFoundError(error)) throw error;
18910
19143
  try {
18911
- await mkdir6(current);
19144
+ await mkdir7(current);
18912
19145
  } catch (mkdirError) {
18913
19146
  if (!isAlreadyExistsError(mkdirError)) throw mkdirError;
18914
19147
  }
@@ -18941,7 +19174,7 @@ async function cloneDir(source, destination, options = {}) {
18941
19174
  throw new CloneDestinationExistsError(destination);
18942
19175
  }
18943
19176
  const startedAt = Date.now();
18944
- const parent = dirname8(destination);
19177
+ const parent = dirname9(destination);
18945
19178
  let safeParent;
18946
19179
  try {
18947
19180
  if (options.destinationRoot) {
@@ -18957,10 +19190,10 @@ async function cloneDir(source, destination, options = {}) {
18957
19190
  parent
18958
19191
  );
18959
19192
  } else {
18960
- await mkdir7(parent, { recursive: true });
19193
+ await mkdir8(parent, { recursive: true });
18961
19194
  }
18962
19195
  const operationParent = safeParent?.path ?? parent;
18963
- const operationDestination = join10(operationParent, basename6(destination));
19196
+ const operationDestination = join12(operationParent, basename6(destination));
18964
19197
  if (await destinationExists(operationDestination)) {
18965
19198
  throw new CloneDestinationExistsError(destination);
18966
19199
  }
@@ -18973,9 +19206,9 @@ async function cloneDir(source, destination, options = {}) {
18973
19206
  try {
18974
19207
  reservationPath = await reserveDestination(operationDestination);
18975
19208
  temporaryRoot = await mkdtemp2(
18976
- join10(operationParent, `.${basename6(destination)}.gji-clone-`)
19209
+ join12(operationParent, `.${basename6(destination)}.gji-clone-`)
18977
19210
  );
18978
- const temporaryDestination = join10(temporaryRoot, basename6(destination));
19211
+ const temporaryDestination = join12(temporaryRoot, basename6(destination));
18979
19212
  const copyDirectory = options.copyDirectory ?? (platformIsDarwin(platform3) ? runNativeCloneDirectory : async (source2, target) => {
18980
19213
  if (!strategy) {
18981
19214
  throw new CloneUnsupportedError(
@@ -19083,7 +19316,7 @@ async function directorySize(path9) {
19083
19316
  if (entryCount > SIZE_ESTIMATE_MAX_ENTRIES || Date.now() - startedAt > SIZE_ESTIMATE_MAX_MS) {
19084
19317
  throw new Error("directory size estimate exceeded its safety limit");
19085
19318
  }
19086
- const entryPath = join10(current, entry.name);
19319
+ const entryPath = join12(current, entry.name);
19087
19320
  if (entry.isDirectory()) pending.push(entryPath);
19088
19321
  else total += (await lstat3(entryPath)).size;
19089
19322
  }
@@ -19128,10 +19361,10 @@ async function acquireCloneLock(lockPath, destination) {
19128
19361
  const lockToken = randomUUID3();
19129
19362
  for (let attempt = 0; attempt < 3; attempt += 1) {
19130
19363
  try {
19131
- await mkdir7(lockPath);
19364
+ await mkdir8(lockPath);
19132
19365
  try {
19133
- await writeFile7(
19134
- join10(lockPath, ownerFileName2(lockToken)),
19366
+ await writeFile9(
19367
+ join12(lockPath, ownerFileName2(lockToken)),
19135
19368
  `${lockToken}
19136
19369
  `,
19137
19370
  {
@@ -19164,10 +19397,10 @@ async function acquireCloneLock(lockPath, destination) {
19164
19397
  throw error;
19165
19398
  }
19166
19399
  try {
19167
- await mkdir7(lockPath);
19400
+ await mkdir8(lockPath);
19168
19401
  try {
19169
- await writeFile7(
19170
- join10(lockPath, ownerFileName2(lockToken)),
19402
+ await writeFile9(
19403
+ join12(lockPath, ownerFileName2(lockToken)),
19171
19404
  `${lockToken}
19172
19405
  `,
19173
19406
  {
@@ -19199,7 +19432,7 @@ async function lockFreshnessPath2(lockPath) {
19199
19432
  try {
19200
19433
  const entries = await readdir2(lockPath);
19201
19434
  const owner = entries.find((entry) => entry.startsWith("owner-"));
19202
- return owner ? join10(lockPath, owner) : lockPath;
19435
+ return owner ? join12(lockPath, owner) : lockPath;
19203
19436
  } catch (error) {
19204
19437
  if (isNotFoundError(error)) throw error;
19205
19438
  return lockPath;
@@ -19214,19 +19447,19 @@ async function publishCloneContents(temporaryDestination, destination, reservati
19214
19447
  const temporaryEntries = await readdir2(temporaryDestination);
19215
19448
  for (const entry of temporaryEntries) {
19216
19449
  await publishCloneEntry(
19217
- join10(temporaryDestination, entry),
19218
- join10(destination, entry),
19450
+ join12(temporaryDestination, entry),
19451
+ join12(destination, entry),
19219
19452
  () => onEntryPublished(entry),
19220
19453
  copyFileEntry
19221
19454
  );
19222
19455
  }
19223
- await unlink3(reservationPath);
19456
+ await unlink4(reservationPath);
19224
19457
  }
19225
19458
  async function publishCloneEntry(source, destination, onCreated, copyFileEntry) {
19226
19459
  const sourceStats = await lstat3(source);
19227
19460
  if (sourceStats.isDirectory()) {
19228
19461
  try {
19229
- await mkdir7(destination);
19462
+ await mkdir8(destination);
19230
19463
  } catch (error) {
19231
19464
  if (isAlreadyExistsError(error)) {
19232
19465
  throw new CloneDestinationExistsError(destination);
@@ -19236,8 +19469,8 @@ async function publishCloneEntry(source, destination, onCreated, copyFileEntry)
19236
19469
  onCreated();
19237
19470
  for (const entry of await readdir2(source)) {
19238
19471
  await publishCloneEntry(
19239
- join10(source, entry),
19240
- join10(destination, entry),
19472
+ join12(source, entry),
19473
+ join12(destination, entry),
19241
19474
  () => void 0,
19242
19475
  copyFileEntry
19243
19476
  );
@@ -19288,19 +19521,19 @@ async function copyCloneMetadata(sourceStats, destination) {
19288
19521
  }
19289
19522
  async function reserveDestination(destination) {
19290
19523
  try {
19291
- await mkdir7(destination);
19524
+ await mkdir8(destination);
19292
19525
  } catch (error) {
19293
19526
  if (isAlreadyExistsError(error)) {
19294
19527
  throw new CloneDestinationExistsError(destination);
19295
19528
  }
19296
19529
  throw error;
19297
19530
  }
19298
- const reservationPath = join10(
19531
+ const reservationPath = join12(
19299
19532
  destination,
19300
19533
  `.gji-clone-reservation-${randomUUID3()}`
19301
19534
  );
19302
19535
  try {
19303
- await writeFile7(reservationPath, "gji clone reservation\n", {
19536
+ await writeFile9(reservationPath, "gji clone reservation\n", {
19304
19537
  flag: "wx"
19305
19538
  });
19306
19539
  return reservationPath;
@@ -19340,8 +19573,8 @@ function startLockHeartbeat(lockPath, lockToken) {
19340
19573
  }
19341
19574
  async function refreshCloneLock(lockPath, lockToken) {
19342
19575
  try {
19343
- const ownerPath = join10(lockPath, ownerFileName2(lockToken));
19344
- await readFile7(ownerPath, "utf8");
19576
+ const ownerPath = join12(lockPath, ownerFileName2(lockToken));
19577
+ await readFile9(ownerPath, "utf8");
19345
19578
  const now = /* @__PURE__ */ new Date();
19346
19579
  await utimes2(ownerPath, now, now);
19347
19580
  } catch {
@@ -19349,8 +19582,8 @@ async function refreshCloneLock(lockPath, lockToken) {
19349
19582
  }
19350
19583
  async function releaseCloneLock(lockPath, lockToken) {
19351
19584
  try {
19352
- const ownerPath = join10(lockPath, ownerFileName2(lockToken));
19353
- await unlink3(ownerPath);
19585
+ const ownerPath = join12(lockPath, ownerFileName2(lockToken));
19586
+ await unlink4(ownerPath);
19354
19587
  await rmdir2(lockPath);
19355
19588
  } catch (error) {
19356
19589
  if (!isNotFoundError(error) && !isDirectoryNotEmptyError(error))
@@ -19807,7 +20040,7 @@ function createBootstrapAdapters(checkUvRuntime, cargoBuildCommand) {
19807
20040
  shell: false,
19808
20041
  beforeRepair: async (target) => {
19809
20042
  if (!target.existingBeforeBootstrap) {
19810
- await rm5(join11(target.targetPath, ".modules.yaml"), {
20043
+ await rm5(join13(target.targetPath, ".modules.yaml"), {
19811
20044
  force: true
19812
20045
  });
19813
20046
  }
@@ -19891,9 +20124,9 @@ var LockfileBootstrapAdapter = class {
19891
20124
  }
19892
20125
  async detect(context) {
19893
20126
  const detectionRoot = context.detectionRoot ?? context.sourceRoot;
19894
- if (!await pathExists2(join11(detectionRoot, this.lockfile))) return null;
19895
- const sourcePath = join11(context.sourceRoot, this.relativePath);
19896
- const targetPath = join11(context.worktreePath, this.relativePath);
20127
+ if (!await pathExists2(join13(detectionRoot, this.lockfile))) return null;
20128
+ const sourcePath = join13(context.sourceRoot, this.relativePath);
20129
+ const targetPath = join13(context.worktreePath, this.relativePath);
19897
20130
  const destinationInspection = await inspectDestination(
19898
20131
  context.worktreePath,
19899
20132
  targetPath
@@ -19974,13 +20207,13 @@ async function safeSourceDirectory(target) {
19974
20207
  }
19975
20208
  async function defaultCheckUvRuntime(target) {
19976
20209
  try {
19977
- const config = await readFile8(
19978
- join11(target.sourcePath, "pyvenv.cfg"),
20210
+ const config = await readFile10(
20211
+ join13(target.sourcePath, "pyvenv.cfg"),
19979
20212
  "utf8"
19980
20213
  );
19981
20214
  const expected = config.match(/^version\s*=\s*(\d+\.\d+)/mu)?.[1];
19982
20215
  if (!expected) return false;
19983
- const sourceInterpreter = join11(
20216
+ const sourceInterpreter = join13(
19984
20217
  target.sourcePath,
19985
20218
  process.platform === "win32" ? "Scripts/python.exe" : "bin/python"
19986
20219
  );
@@ -20006,12 +20239,12 @@ async function isAllowedSourceRoot(repoRoot, sourceRoot) {
20006
20239
  realpath3(sourceRoot)
20007
20240
  ]);
20008
20241
  if (resolvedRepoRoot === resolvedSourceRoot) return true;
20009
- const gitFile = await readFile8(join11(resolvedSourceRoot, ".git"), "utf8");
20242
+ const gitFile = await readFile10(join13(resolvedSourceRoot, ".git"), "utf8");
20010
20243
  const gitDirectory = gitFile.match(/^gitdir:\s*(.+)$/mu)?.[1];
20011
20244
  if (!gitDirectory) return false;
20012
20245
  return isWithin(
20013
- join11(resolvedRepoRoot, ".git", "worktrees"),
20014
- resolve8(resolvedSourceRoot, gitDirectory)
20246
+ join13(resolvedRepoRoot, ".git", "worktrees"),
20247
+ resolve9(resolvedSourceRoot, gitDirectory)
20015
20248
  );
20016
20249
  } catch {
20017
20250
  return false;
@@ -20186,12 +20419,12 @@ function toErrorMessage3(error) {
20186
20419
 
20187
20420
  // src/sync-plan.ts
20188
20421
  import { lstat as lstat5, realpath as realpath4 } from "node:fs/promises";
20189
- import { isAbsolute as isAbsolute4, join as join12, relative as relative3, resolve as resolve9, sep as sep3 } from "node:path";
20422
+ import { isAbsolute as isAbsolute4, join as join14, relative as relative3, resolve as resolve10, sep as sep3 } from "node:path";
20190
20423
  async function prepareSyncDirectoryPlan(repoRoot, worktreePath, directories) {
20191
20424
  const normalizedDirectories = directories.map(validateSyncDirPattern).sort(directoryDepthAscending);
20192
20425
  const plan = [];
20193
20426
  for (const directory of normalizedDirectories) {
20194
- const destination = join12(worktreePath, directory);
20427
+ const destination = join14(worktreePath, directory);
20195
20428
  const destinationState = await inspectDestination(
20196
20429
  worktreePath,
20197
20430
  destination
@@ -20265,7 +20498,7 @@ async function estimateSyncDirectories(repoRoot, worktreePath, directories) {
20265
20498
  return estimateSyncDirectoryPlan(plan);
20266
20499
  }
20267
20500
  async function resolveSyncDirectorySource(repoRoot, directory) {
20268
- const source = join12(repoRoot, directory);
20501
+ const source = join14(repoRoot, directory);
20269
20502
  let resolvedSource;
20270
20503
  try {
20271
20504
  resolvedSource = await realpath4(source);
@@ -20291,7 +20524,7 @@ function directoryDepthAscending(left, right) {
20291
20524
  return left.split(/[\\/]+/u).length - right.split(/[\\/]+/u).length;
20292
20525
  }
20293
20526
  function isPathInside2(parent, child) {
20294
- const relativePath = relative3(resolve9(parent), resolve9(child));
20527
+ const relativePath = relative3(resolve10(parent), resolve10(child));
20295
20528
  return relativePath !== "" && !isAbsolute4(relativePath) && relativePath !== ".." && !relativePath.startsWith(`..${sep3}`);
20296
20529
  }
20297
20530
  function toErrorMessage4(error) {
@@ -20306,12 +20539,12 @@ import { constants as constants7 } from "node:fs";
20306
20539
  import { copyFile, lstat as lstat6, realpath as realpath5 } from "node:fs/promises";
20307
20540
  import {
20308
20541
  basename as basename7,
20309
- dirname as dirname9,
20542
+ dirname as dirname10,
20310
20543
  isAbsolute as isAbsolute5,
20311
- join as join13,
20544
+ join as join15,
20312
20545
  normalize as normalize2,
20313
20546
  relative as relative4,
20314
- resolve as resolve10,
20547
+ resolve as resolve11,
20315
20548
  sep as sep4
20316
20549
  } from "node:path";
20317
20550
  async function syncFiles(mainRoot, targetPath, patterns) {
@@ -20319,7 +20552,7 @@ async function syncFiles(mainRoot, targetPath, patterns) {
20319
20552
  const normalized = validateSyncFilePattern(pattern);
20320
20553
  const sourcePath = await resolveSyncFileSource(mainRoot, normalized);
20321
20554
  if (!sourcePath) continue;
20322
- const destPath = join13(targetPath, normalized);
20555
+ const destPath = join15(targetPath, normalized);
20323
20556
  const existingDestination = await readDestinationEntry(destPath);
20324
20557
  if (existingDestination?.isSymbolicLink()) {
20325
20558
  throw new Error(`destination is a symbolic link: ${destPath}`);
@@ -20327,7 +20560,7 @@ async function syncFiles(mainRoot, targetPath, patterns) {
20327
20560
  if (existingDestination) {
20328
20561
  continue;
20329
20562
  }
20330
- const destinationParent = dirname9(destPath);
20563
+ const destinationParent = dirname10(destPath);
20331
20564
  const beforeCreate = await inspectDestination(
20332
20565
  targetPath,
20333
20566
  destinationParent
@@ -20340,7 +20573,7 @@ async function syncFiles(mainRoot, targetPath, patterns) {
20340
20573
  destinationParent
20341
20574
  );
20342
20575
  try {
20343
- const safeDestination = join13(safeParent.path, basename7(destPath));
20576
+ const safeDestination = join15(safeParent.path, basename7(destPath));
20344
20577
  const safeExistingDestination = await readDestinationEntry(safeDestination);
20345
20578
  if (safeExistingDestination?.isSymbolicLink()) {
20346
20579
  throw new Error(`destination is a symbolic link: ${destPath}`);
@@ -20376,7 +20609,7 @@ async function resolveSyncFileSource(mainRoot, pattern) {
20376
20609
  try {
20377
20610
  [resolvedRoot, resolvedSource] = await Promise.all([
20378
20611
  realpath5(mainRoot),
20379
- realpath5(join13(mainRoot, pattern))
20612
+ realpath5(join15(mainRoot, pattern))
20380
20613
  ]);
20381
20614
  } catch (error) {
20382
20615
  if (isNotFoundError(error)) return void 0;
@@ -20390,7 +20623,7 @@ async function resolveSyncFileSource(mainRoot, pattern) {
20390
20623
  const sourceStats = await lstat6(resolvedSource);
20391
20624
  if (!sourceStats.isFile()) {
20392
20625
  throw new Error(
20393
- `syncFiles: source is not a file: ${join13(mainRoot, pattern)}`
20626
+ `syncFiles: source is not a file: ${join15(mainRoot, pattern)}`
20394
20627
  );
20395
20628
  }
20396
20629
  return resolvedSource;
@@ -20404,13 +20637,13 @@ async function readDestinationEntry(path9) {
20404
20637
  }
20405
20638
  }
20406
20639
  function isPathInside3(root, candidate) {
20407
- const distance = relative4(resolve10(root), resolve10(candidate));
20640
+ const distance = relative4(resolve11(root), resolve11(candidate));
20408
20641
  return distance === "" || !isAbsolute5(distance) && distance !== ".." && !distance.startsWith(`..${sep4}`);
20409
20642
  }
20410
20643
 
20411
20644
  // src/package-manager.ts
20412
20645
  import { access as access8, readdir as readdir3 } from "node:fs/promises";
20413
- import { join as join14 } from "node:path";
20646
+ import { join as join16 } from "node:path";
20414
20647
  var ENTRIES = [
20415
20648
  // JavaScript / TypeScript
20416
20649
  { name: "pnpm", signals: ["pnpm-lock.yaml"], command: "pnpm install" },
@@ -20540,7 +20773,7 @@ async function detectPackageManager(repoRoot) {
20540
20773
  async function matchesExact(repoRoot, signals) {
20541
20774
  for (const signal of signals) {
20542
20775
  try {
20543
- await access8(join14(repoRoot, signal));
20776
+ await access8(join16(repoRoot, signal));
20544
20777
  return true;
20545
20778
  } catch {
20546
20779
  }
@@ -20911,13 +21144,15 @@ async function bootstrapWorktree(options) {
20911
21144
  );
20912
21145
  }
20913
21146
  const hooks = extractHooks(options.config);
21147
+ const slot = await assignWorktreeSlot(options.repoRoot, options.worktreePath);
20914
21148
  await runHook(
20915
21149
  hooks["after-create"],
20916
21150
  options.worktreePath,
20917
21151
  {
20918
21152
  branch: options.branch,
20919
21153
  path: options.worktreePath,
20920
- repo: basename8(options.repoRoot)
21154
+ repo: basename8(options.repoRoot),
21155
+ slot
20921
21156
  },
20922
21157
  options.reporter.write,
20923
21158
  options.json ? () => void 0 : options.commandStdout ?? ((chunk) => process.stdout.write(chunk))
@@ -21113,7 +21348,7 @@ ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(byte
21113
21348
  }
21114
21349
  return 1;
21115
21350
  }
21116
- await mkdir8(dirname10(worktreePath), { recursive: true });
21351
+ await mkdir9(dirname11(worktreePath), { recursive: true });
21117
21352
  const branchAlreadyExists = await localBranchExists(
21118
21353
  repository.repoRoot,
21119
21354
  branchName
@@ -21277,26 +21512,31 @@ async function writeOutput(worktreePath, stdout2, outputEnv) {
21277
21512
  }
21278
21513
 
21279
21514
  // src/worktree-sources.ts
21515
+ var MAX_REPOSITORY_DISCOVERY_CONCURRENCY = 4;
21280
21516
  async function listRegisteredWorktreeSources(cwd, onSkipped) {
21281
21517
  const registry = await loadRegistry();
21282
21518
  const currentRoot = await detectRepository(cwd).then((repository) => repository.currentRoot).catch(() => null);
21283
- const results = await Promise.allSettled(
21284
- registry.map(async (entry) => {
21285
- const worktrees = await listWorktrees(entry.path);
21286
- return { repoName: entry.name, repoRoot: entry.path, worktrees };
21287
- })
21519
+ const results = await mapWithConcurrency3(
21520
+ registry,
21521
+ MAX_REPOSITORY_DISCOVERY_CONCURRENCY,
21522
+ async (entry) => {
21523
+ try {
21524
+ const worktrees = await listWorktrees(entry.path);
21525
+ return { entry, worktrees };
21526
+ } catch {
21527
+ onSkipped?.(entry);
21528
+ return null;
21529
+ }
21530
+ }
21288
21531
  );
21289
21532
  const allItems = [];
21290
- for (const [index, result] of results.entries()) {
21291
- if (result.status === "rejected") {
21292
- onSkipped?.(registry[index]);
21293
- continue;
21294
- }
21295
- const { repoName, repoRoot, worktrees } = result.value;
21533
+ for (const result of results) {
21534
+ if (result === null) continue;
21535
+ const { entry, worktrees } = result;
21296
21536
  for (const worktree of worktrees) {
21297
21537
  allItems.push({
21298
- repoRoot,
21299
- repoName,
21538
+ repoRoot: entry.path,
21539
+ repoName: entry.name,
21300
21540
  worktree: {
21301
21541
  ...worktree,
21302
21542
  isCurrent: currentRoot !== null && worktree.path === currentRoot
@@ -21306,6 +21546,44 @@ async function listRegisteredWorktreeSources(cwd, onSkipped) {
21306
21546
  }
21307
21547
  return allItems;
21308
21548
  }
21549
+ async function listDiscoverableWorktreeSources(cwd, onSkipped) {
21550
+ const currentRepository = await detectRepository(cwd).catch(() => null);
21551
+ const registeredSources = await listRegisteredWorktreeSources(cwd, onSkipped);
21552
+ if (currentRepository === null) return dedupeSources(registeredSources);
21553
+ let currentSources = [];
21554
+ try {
21555
+ currentSources = (await listWorktrees(cwd)).map((worktree) => ({
21556
+ repoName: currentRepository.repoName,
21557
+ repoRoot: currentRepository.repoRoot,
21558
+ worktree
21559
+ }));
21560
+ } catch {
21561
+ }
21562
+ return dedupeSources([...currentSources, ...registeredSources]);
21563
+ }
21564
+ async function mapWithConcurrency3(items, limit, mapper) {
21565
+ const results = new Array(items.length);
21566
+ let nextIndex = 0;
21567
+ async function readNext() {
21568
+ for (; ; ) {
21569
+ const index = nextIndex++;
21570
+ if (index >= items.length) return;
21571
+ results[index] = await mapper(items[index]);
21572
+ }
21573
+ }
21574
+ await Promise.all(
21575
+ Array.from({ length: Math.min(limit, items.length) }, () => readNext())
21576
+ );
21577
+ return results;
21578
+ }
21579
+ function dedupeSources(sources) {
21580
+ const seen = /* @__PURE__ */ new Set();
21581
+ return sources.filter((source) => {
21582
+ if (seen.has(source.worktree.path)) return false;
21583
+ seen.add(source.worktree.path);
21584
+ return true;
21585
+ });
21586
+ }
21309
21587
 
21310
21588
  // src/go-resolver.ts
21311
21589
  async function resolveGoBranch(options) {
@@ -21487,8 +21765,8 @@ function normalizeRemoteUrl(value) {
21487
21765
 
21488
21766
  // src/new.ts
21489
21767
  import { execFile as execFile8 } from "node:child_process";
21490
- import { access as access9, mkdir as mkdir9 } from "node:fs/promises";
21491
- import { dirname as dirname11, resolve as resolve11 } from "node:path";
21768
+ import { access as access9, mkdir as mkdir10 } from "node:fs/promises";
21769
+ import { dirname as dirname12, resolve as resolve12 } from "node:path";
21492
21770
  import { promisify as promisify9 } from "node:util";
21493
21771
  var execFileAsync8 = promisify9(execFile8);
21494
21772
  var NEW_OUTPUT_FILE_ENV = "GJI_NEW_OUTPUT_FILE";
@@ -21742,7 +22020,7 @@ ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(byte
21742
22020
  }
21743
22021
  return 0;
21744
22022
  }
21745
- await mkdir9(dirname11(worktreePath), { recursive: true });
22023
+ await mkdir10(dirname12(worktreePath), { recursive: true });
21746
22024
  const startPoint = options.fromCurrent && !options.detached ? await resolveCurrentWorktreeHead(options.cwd) : void 0;
21747
22025
  const takeStartPoint = options.take ? await resolveCurrentWorktreeHead(options.cwd) : void 0;
21748
22026
  const changedTakeFiles = options.take ? await listTakeFiles(options.cwd) : [];
@@ -21842,6 +22120,16 @@ ${dryRunSyncDirs.map(({ dir, bytes }) => `Would clone ${dir} (${formatBytes(byte
21842
22120
  `could not apply taken changes; your changes are safe in stash: ${stashSha}`
21843
22121
  );
21844
22122
  }
22123
+ if (options.task !== void 0) {
22124
+ try {
22125
+ await writeTask(worktreePath, options.task);
22126
+ } catch (error) {
22127
+ options.stderr(
22128
+ `Warning: could not save task metadata: ${error instanceof Error ? error.message : String(error)}
22129
+ `
22130
+ );
22131
+ }
22132
+ }
21845
22133
  const bootstrap = await bootstrapWorktree({
21846
22134
  branch: worktreeName,
21847
22135
  cloneDirectory,
@@ -22165,7 +22453,7 @@ async function detectInProgressGitState(cwd) {
22165
22453
  ["rev-parse", "--git-path", marker],
22166
22454
  { cwd }
22167
22455
  );
22168
- await access9(resolve11(cwd, stdout2.trim()));
22456
+ await access9(resolve12(cwd, stdout2.trim()));
22169
22457
  return name;
22170
22458
  } catch {
22171
22459
  }
@@ -22579,10 +22867,16 @@ async function navigateToExistingWorktree(options, path9, worktree) {
22579
22867
  {
22580
22868
  branch: worktree?.branch ?? void 0,
22581
22869
  path: path9,
22582
- repo: basename9(repository.repoRoot)
22870
+ repo: basename9(repository.repoRoot),
22871
+ slot: await getWorktreeSlot(path9)
22583
22872
  },
22584
22873
  options.stderr
22585
22874
  );
22875
+ if (!options.quiet) {
22876
+ const card = await renderContextCard(path9);
22877
+ if (card) options.stderr(`${card}
22878
+ `);
22879
+ }
22586
22880
  await recordWorktreeUsage(path9, worktree?.branch ?? null);
22587
22881
  await writeShellOutput(GO_OUTPUT_FILE_ENV, path9, options.stdout);
22588
22882
  return 0;
@@ -22710,11 +23004,288 @@ async function runHistoryCommand(options) {
22710
23004
  return 0;
22711
23005
  }
22712
23006
 
23007
+ // src/hub.ts
23008
+ import { basename as basename10 } from "node:path";
23009
+ var MAX_HUB_REPOSITORY_CONCURRENCY = 4;
23010
+ var defaultDependencies = {
23011
+ queryRepositoryPullRequests: createPullRequestQuery().listOpenPullRequestsForRepository
23012
+ };
23013
+ async function buildHubData(cwd, dependencies = {}) {
23014
+ const queryRepositoryPullRequests = dependencies.queryRepositoryPullRequests ?? defaultDependencies.queryRepositoryPullRequests;
23015
+ const currentRepository = await detectRepository(cwd).catch(() => null);
23016
+ const history = await loadHistory();
23017
+ const lastUsedByPath = new Map(
23018
+ history.map((entry) => [entry.path, entry.timestamp])
23019
+ );
23020
+ const sources = await listDiscoverableWorktreeSources(cwd);
23021
+ const groups = /* @__PURE__ */ new Map();
23022
+ for (const source of sources) {
23023
+ if (!source.repoRoot) continue;
23024
+ const repoRoot = source.repoRoot;
23025
+ const existing = groups.get(repoRoot);
23026
+ if (existing) {
23027
+ existing.sources.push(source);
23028
+ } else {
23029
+ groups.set(repoRoot, {
23030
+ name: source.repoName || basename10(repoRoot),
23031
+ sources: [source]
23032
+ });
23033
+ }
23034
+ }
23035
+ const repositories = await mapWithConcurrency4(
23036
+ [...groups.entries()],
23037
+ MAX_HUB_REPOSITORY_CONCURRENCY,
23038
+ async ([root, group]) => {
23039
+ const infos = await readWorktreeInfos(
23040
+ group.sources.map((source) => source.worktree)
23041
+ );
23042
+ let pullRequests = [];
23043
+ try {
23044
+ pullRequests = await queryRepositoryPullRequests(root);
23045
+ } catch {
23046
+ }
23047
+ const worktrees = infos.map((info) => ({
23048
+ ...info,
23049
+ lastUsedTimestamp: lastUsedByPath.get(info.path) ?? null,
23050
+ pullRequests: pullRequests.filter(
23051
+ (pullRequest) => pullRequest.sourceBranch === info.branch
23052
+ )
23053
+ }));
23054
+ return {
23055
+ current: currentRepository?.repoRoot === root,
23056
+ name: group.name,
23057
+ pullRequests,
23058
+ root,
23059
+ worktrees
23060
+ };
23061
+ }
23062
+ );
23063
+ repositories.sort((left, right) => {
23064
+ if (left.current !== right.current) return left.current ? -1 : 1;
23065
+ return left.name.localeCompare(right.name);
23066
+ });
23067
+ return {
23068
+ currentRepository: currentRepository?.repoRoot ?? null,
23069
+ repositories
23070
+ };
23071
+ }
23072
+ async function runHubCommand(options, dependencies = {}) {
23073
+ const data = await buildHubData(options.cwd, dependencies);
23074
+ if (options.json) {
23075
+ options.stdout(`${JSON.stringify(data, null, 2)}
23076
+ `);
23077
+ return 0;
23078
+ }
23079
+ options.stdout(
23080
+ `${formatHubOutput(
23081
+ data,
23082
+ options.columns ?? process.stdout.columns ?? 80,
23083
+ options.now ?? Date.now()
23084
+ )}
23085
+ `
23086
+ );
23087
+ return 0;
23088
+ }
23089
+ function formatHubOutput(data, columns = process.stdout.columns ?? 80, now = Date.now()) {
23090
+ if (data.repositories.length === 0) {
23091
+ return "No registered repositories. Run gji from a repository to add one.";
23092
+ }
23093
+ const lineWidth = Math.max(1, columns);
23094
+ const allWorktrees = data.repositories.flatMap(
23095
+ (repository) => repository.worktrees.map((worktree) => ({ repository, worktree }))
23096
+ );
23097
+ const attention = allWorktrees.filter(
23098
+ ({ worktree }) => isActionable(worktree)
23099
+ );
23100
+ const current = allWorktrees.find(({ worktree }) => worktree.isCurrent) ?? null;
23101
+ const next = chooseNextWorktree(allWorktrees);
23102
+ const remainingAttention = attention.filter(
23103
+ (entry) => entry !== current && entry !== next
23104
+ );
23105
+ const quiet = allWorktrees.filter(
23106
+ (entry) => entry !== current && entry !== next && !attention.includes(entry)
23107
+ );
23108
+ const recentOther = [...quiet].sort(
23109
+ (left, right) => (right.worktree.lastCommitTimestamp ?? 0) - (left.worktree.lastCommitTimestamp ?? 0)
23110
+ ).slice(0, 5);
23111
+ const hiddenCount = quiet.length - recentOther.length;
23112
+ const lines = [
23113
+ `GJI ${data.repositories.length} repositories \xB7 ${allWorktrees.length} worktrees${attention.length > 0 ? ` \xB7 ${attention.length} need attention` : ""}`,
23114
+ ""
23115
+ ];
23116
+ if (data.repositories.length > 1) {
23117
+ lines.push("REPOSITORIES");
23118
+ for (const repository of data.repositories) {
23119
+ lines.push(formatRepositorySummary(repository, lineWidth));
23120
+ }
23121
+ lines.push("");
23122
+ }
23123
+ if (current !== null) {
23124
+ lines.push("CURRENT");
23125
+ lines.push(...formatHubWorktree(current, lineWidth, "@", now));
23126
+ lines.push("");
23127
+ }
23128
+ if (next !== null && next !== current) {
23129
+ lines.push("NEXT");
23130
+ lines.push(...formatHubWorktree(next, lineWidth, "\u203A", now));
23131
+ lines.push("");
23132
+ }
23133
+ if (remainingAttention.length > 0) {
23134
+ lines.push("ATTENTION");
23135
+ for (const entry of remainingAttention) {
23136
+ lines.push(...formatHubWorktree(entry, lineWidth, "!", now));
23137
+ }
23138
+ lines.push("");
23139
+ }
23140
+ if (recentOther.length > 0) {
23141
+ lines.push("OTHER");
23142
+ for (const entry of recentOther) {
23143
+ lines.push(...formatHubWorktree(entry, lineWidth, " ", now));
23144
+ }
23145
+ if (hiddenCount > 0) {
23146
+ lines.push(
23147
+ ` ${hiddenCount} quiet worktree${hiddenCount === 1 ? "" : "s"} hidden`
23148
+ );
23149
+ }
23150
+ lines.push("");
23151
+ }
23152
+ if (next === null && remainingAttention.length === 0 && recentOther.length === 0) {
23153
+ lines.push("No active worktrees.");
23154
+ }
23155
+ lines.push("Run `gji go <repo>/<branch>` to switch worktrees.");
23156
+ return lines.map(
23157
+ (line) => terminalWidth(line) > lineWidth ? middleEllipsize(line, lineWidth) : line
23158
+ ).join("\n").trimEnd();
23159
+ }
23160
+ function formatRepositorySummary(repository, lineWidth) {
23161
+ const attentionCount = repository.worktrees.filter(isActionable).length;
23162
+ const attention = attentionCount === 0 ? "healthy" : `${attentionCount} attention`;
23163
+ const marker = repository.current ? "*" : " ";
23164
+ return middleEllipsize(
23165
+ `${marker} ${repository.name} \xB7 ${repository.worktrees.length} worktree${repository.worktrees.length === 1 ? "" : "s"} \xB7 ${attention}`,
23166
+ lineWidth
23167
+ );
23168
+ }
23169
+ function chooseNextWorktree(entries) {
23170
+ return [...entries].sort((left, right) => {
23171
+ const scoreDifference = nextWorktreeScore(right) - nextWorktreeScore(left);
23172
+ if (scoreDifference !== 0) return scoreDifference;
23173
+ const lastUsedDifference = (right.worktree.lastUsedTimestamp ?? 0) - (left.worktree.lastUsedTimestamp ?? 0);
23174
+ if (lastUsedDifference !== 0) return lastUsedDifference;
23175
+ return (right.worktree.lastCommitTimestamp ?? 0) - (left.worktree.lastCommitTimestamp ?? 0);
23176
+ })[0] ?? null;
23177
+ }
23178
+ function nextWorktreeScore(entry) {
23179
+ const { worktree } = entry;
23180
+ let score = worktree.isCurrent && (worktree.task !== null || isActionable(worktree)) ? 100 : 0;
23181
+ if (worktree.task !== null) score += 40;
23182
+ if (worktree.status === "dirty") score += 30;
23183
+ if (worktree.upstream.kind === "stale") score += 25;
23184
+ if (worktree.upstream.kind === "tracked" && worktree.upstream.behind > 0) {
23185
+ score += 25;
23186
+ }
23187
+ if (worktree.pullRequests.length > 0) score += 20;
23188
+ if (worktree.lastUsedTimestamp !== null && worktree.lastUsedTimestamp > 0) {
23189
+ score += 15;
23190
+ }
23191
+ return score;
23192
+ }
23193
+ function isActionable(worktree) {
23194
+ return worktree.status === "dirty" || worktree.upstream.kind === "stale" || worktree.upstream.kind === "tracked" && worktree.upstream.behind > 0 || worktree.pullRequests.length > 0;
23195
+ }
23196
+ function formatHubWorktree(entry, lineWidth, marker, now) {
23197
+ const { repository, worktree } = entry;
23198
+ const branch = worktree.branch ?? "(detached)";
23199
+ const status = [
23200
+ formatWorktreeHealth(worktree),
23201
+ formatWorktreeUpstream(worktree),
23202
+ worktree.pullRequests.length > 0 ? worktree.pullRequests.map(
23203
+ (pullRequest) => pullRequest.title ? `#${pullRequest.number} ${formatHumanText(pullRequest.title, 60)}` : `#${pullRequest.number}`
23204
+ ).join(", ") : null,
23205
+ formatWorktreeActivity(worktree, now)
23206
+ ].filter((value) => value !== null).join(" \xB7 ");
23207
+ const task = worktree.task === null ? null : `task: ${formatHumanText(worktree.task, 80)}`;
23208
+ const target = `${repository.name}/${branch}`;
23209
+ const lines = [`${marker} ${middleEllipsize(target, lineWidth - 2)}`];
23210
+ if (status.length > 0) {
23211
+ lines.push(` ${middleEllipsize(status, lineWidth - 2)}`);
23212
+ }
23213
+ if (task !== null) {
23214
+ lines.push(` ${middleEllipsize(task, lineWidth - 2)}`);
23215
+ }
23216
+ if (marker === "@" || marker === "\u203A" || marker === "!") {
23217
+ lines.push(` ${middleEllipsize(nextAction(worktree), lineWidth - 2)}`);
23218
+ }
23219
+ return lines;
23220
+ }
23221
+ function formatWorktreeHealth(worktree) {
23222
+ if (worktree.status === "dirty") return "! dirty";
23223
+ if (worktree.status === "unknown") return "? health unknown";
23224
+ return null;
23225
+ }
23226
+ function formatWorktreeUpstream(worktree) {
23227
+ if (worktree.upstream.kind === "stale") return "! stale upstream";
23228
+ if (worktree.upstream.kind === "tracked" && (worktree.upstream.behind > 0 || worktree.upstream.ahead > 0)) {
23229
+ return formatUpstreamState(worktree.upstream);
23230
+ }
23231
+ return null;
23232
+ }
23233
+ function formatWorktreeActivity(worktree, now) {
23234
+ if (worktree.isCurrent) return null;
23235
+ const timestamps = [
23236
+ worktree.lastUsedTimestamp ?? 0,
23237
+ (worktree.lastCommitTimestamp ?? 0) * 1e3
23238
+ ].filter((timestamp) => timestamp > 0);
23239
+ if (timestamps.length === 0) return null;
23240
+ const latest = Math.max(...timestamps);
23241
+ const ageSeconds = Math.floor((now - latest) / 1e3);
23242
+ if (ageSeconds < 24 * 60 * 60) return null;
23243
+ return `inactive ${formatRelativeAge(
23244
+ Math.floor(latest / 1e3),
23245
+ Math.floor(now / 1e3)
23246
+ )}`;
23247
+ }
23248
+ function nextAction(worktree) {
23249
+ if (worktree.isCurrent && worktree.task !== null) {
23250
+ return "next: continue task";
23251
+ }
23252
+ if (worktree.status === "dirty") {
23253
+ return "next: switch \u2192 inspect changes";
23254
+ }
23255
+ if (worktree.upstream.kind === "stale" || worktree.upstream.kind === "tracked" && worktree.upstream.behind > 0) {
23256
+ return "next: switch \u2192 sync";
23257
+ }
23258
+ const pullRequest = worktree.pullRequests[0];
23259
+ if (pullRequest !== void 0) {
23260
+ return `next: switch \u2192 open PR '#${pullRequest.number}'`;
23261
+ }
23262
+ return "next: switch worktree";
23263
+ }
23264
+ function formatHumanText(value, maxLength) {
23265
+ const sanitized = sanitizeTerminalText(value);
23266
+ return sanitized.length <= maxLength ? sanitized : `${sanitized.slice(0, Math.max(0, maxLength - 1))}\u2026`;
23267
+ }
23268
+ async function mapWithConcurrency4(items, limit, mapper) {
23269
+ const results = new Array(items.length);
23270
+ let nextIndex = 0;
23271
+ async function readNext() {
23272
+ for (; ; ) {
23273
+ const index = nextIndex++;
23274
+ if (index >= items.length) return;
23275
+ results[index] = await mapper(items[index]);
23276
+ }
23277
+ }
23278
+ await Promise.all(
23279
+ Array.from({ length: Math.min(limit, items.length) }, () => readNext())
23280
+ );
23281
+ return results;
23282
+ }
23283
+
22713
23284
  // src/init.ts
22714
23285
  import { constants as constants8 } from "node:fs";
22715
- import { access as access10, mkdir as mkdir10, readFile as readFile9, writeFile as writeFile8 } from "node:fs/promises";
22716
- import { homedir as homedir8 } from "node:os";
22717
- import { dirname as dirname12 } from "node:path";
23286
+ import { access as access10, mkdir as mkdir11, readFile as readFile11, writeFile as writeFile10 } from "node:fs/promises";
23287
+ import { homedir as homedir9 } from "node:os";
23288
+ import { dirname as dirname13 } from "node:path";
22718
23289
  var ZSH_COMPLETION_PATH_LINE = "fpath=(~/.zsh/completions $fpath)";
22719
23290
  var SHELL_WRAPPED_COMMANDS = [
22720
23291
  {
@@ -22784,7 +23355,7 @@ async function runOnboardingInitCommand(options) {
22784
23355
  if (options.json || isHeadless() || !canRunOnboarding(options)) {
22785
23356
  return writeNonInteractiveInitError(options);
22786
23357
  }
22787
- const home = options.home ?? homedir8();
23358
+ const home = options.home ?? homedir9();
22788
23359
  const context = {
22789
23360
  detectedShell: resolveSupportedShell(void 0, process.env.SHELL),
22790
23361
  home
@@ -22838,8 +23409,8 @@ async function runDefaultOnboarding(context) {
22838
23409
  const installCompletion = await promptForCompletion(shell, completionPath);
22839
23410
  if (installCompletion === null) return abortDefaultOnboarding();
22840
23411
  if (installCompletion) {
22841
- await mkdir10(dirname12(completionPath), { recursive: true });
22842
- await writeFile8(completionPath, renderShellCompletion(shell), "utf8");
23412
+ await mkdir11(dirname13(completionPath), { recursive: true });
23413
+ await writeFile10(completionPath, renderShellCompletion(shell), "utf8");
22843
23414
  }
22844
23415
  if (shell === "zsh" && (completionAlreadyInstalled || installCompletion)) {
22845
23416
  await ensureZshCompletionPath(rcPath);
@@ -22931,8 +23502,8 @@ async function applyOnboardingResult(result, home) {
22931
23502
  const completionAlreadyInstalled = await fileExists(completionPath);
22932
23503
  await applyShellIntegration(result.shell, result.shellIntegration, home);
22933
23504
  if (result.installCompletion) {
22934
- await mkdir10(dirname12(completionPath), { recursive: true });
22935
- await writeFile8(
23505
+ await mkdir11(dirname13(completionPath), { recursive: true });
23506
+ await writeFile10(
22936
23507
  completionPath,
22937
23508
  renderShellCompletion(result.shell),
22938
23509
  "utf8"
@@ -22947,17 +23518,17 @@ async function applyOnboardingResult(result, home) {
22947
23518
  }
22948
23519
  async function installOnboardingShellIntegration(shell, home) {
22949
23520
  const rcPath = resolveShellConfigPath(shell, home);
22950
- await mkdir10(dirname12(rcPath), { recursive: true });
23521
+ await mkdir11(dirname13(rcPath), { recursive: true });
22951
23522
  const current = await readExistingConfig(rcPath);
22952
23523
  if (shell === "fish") {
22953
- await writeFile8(
23524
+ await writeFile10(
22954
23525
  rcPath,
22955
23526
  upsertShellIntegration(current, renderShellIntegration(shell)),
22956
23527
  "utf8"
22957
23528
  );
22958
23529
  return;
22959
23530
  }
22960
- await writeFile8(
23531
+ await writeFile10(
22961
23532
  rcPath,
22962
23533
  upsertOnboardingShellIntegration(current, shell),
22963
23534
  "utf8"
@@ -22991,7 +23562,7 @@ async function ensureZshCompletionPath(rcPath) {
22991
23562
  const current = await readExistingConfig(rcPath);
22992
23563
  const next = upsertZshCompletionPath(current);
22993
23564
  if (next !== current) {
22994
- await writeFile8(rcPath, next, "utf8");
23565
+ await writeFile10(rcPath, next, "utf8");
22995
23566
  }
22996
23567
  }
22997
23568
  function upsertZshCompletionPath(existingConfig) {
@@ -23022,7 +23593,7 @@ async function fileExists(path9) {
23022
23593
  }
23023
23594
  async function runLegacyInitCommand(options) {
23024
23595
  const shell = resolveSupportedShell(options.shell, process.env.SHELL);
23025
- const home = options.home ?? homedir8();
23596
+ const home = options.home ?? homedir9();
23026
23597
  if (!shell) {
23027
23598
  options.stderr?.(
23028
23599
  "Unable to detect a supported shell. Specify one explicitly: bash, fish, or zsh.\n"
@@ -23035,10 +23606,10 @@ async function runLegacyInitCommand(options) {
23035
23606
  return 0;
23036
23607
  }
23037
23608
  const rcPath = resolveShellConfigPath(shell, home);
23038
- await mkdir10(dirname12(rcPath), { recursive: true });
23609
+ await mkdir11(dirname13(rcPath), { recursive: true });
23039
23610
  const current = await readExistingConfig(rcPath);
23040
23611
  const next = upsertShellIntegration(current, script);
23041
- await writeFile8(rcPath, next, "utf8");
23612
+ await writeFile10(rcPath, next, "utf8");
23042
23613
  options.stdout(`${rcPath}
23043
23614
  `);
23044
23615
  const { config: globalConfig } = await loadGlobalConfig(home);
@@ -23129,7 +23700,7 @@ async function saveWizardConfig(result, cwd, home) {
23129
23700
  }
23130
23701
  async function readExistingConfig(path9) {
23131
23702
  try {
23132
- return await readFile9(path9, "utf8");
23703
+ return await readFile11(path9, "utf8");
23133
23704
  } catch (error) {
23134
23705
  if (isMissingFileError3(error)) {
23135
23706
  return "";
@@ -23317,6 +23888,7 @@ function formatDetailedWorktreeTable(worktrees) {
23317
23888
  lastCommit: formatLastCommit(worktree.lastCommitTimestamp),
23318
23889
  path: worktree.path,
23319
23890
  status: worktree.status,
23891
+ task: worktree.task,
23320
23892
  upstream: formatUpstreamState(worktree.upstream)
23321
23893
  }));
23322
23894
  const branchWidth = Math.max(
@@ -23335,12 +23907,17 @@ function formatDetailedWorktreeTable(worktrees) {
23335
23907
  "LAST".length,
23336
23908
  ...rows.map((row) => row.lastCommit.length)
23337
23909
  );
23910
+ const hasTasks = rows.some((row) => row.task !== null);
23911
+ const taskWidth = Math.max(
23912
+ "TASK".length,
23913
+ ...rows.map((row) => row.task?.slice(0, 40).length ?? 0)
23914
+ );
23338
23915
  const lines = [
23339
- " " + "BRANCH".padEnd(branchWidth, " ") + " " + "STATUS".padEnd(statusWidth, " ") + " " + "UPSTREAM".padEnd(upstreamWidth, " ") + " " + "LAST".padEnd(lastCommitWidth, " ") + " PATH"
23916
+ " " + "BRANCH".padEnd(branchWidth, " ") + " " + "STATUS".padEnd(statusWidth, " ") + " " + "UPSTREAM".padEnd(upstreamWidth, " ") + " " + "LAST".padEnd(lastCommitWidth, " ") + (hasTasks ? " " + "TASK".padEnd(taskWidth, " ") : "") + " PATH"
23340
23917
  ];
23341
23918
  for (const row of rows) {
23342
23919
  lines.push(
23343
- `${row.isCurrent ? "*" : " "} ${row.branch.padEnd(branchWidth, " ")} ${row.status.padEnd(statusWidth, " ")} ${row.upstream.padEnd(upstreamWidth, " ")} ${row.lastCommit.padEnd(lastCommitWidth, " ")} ` + row.path
23920
+ `${row.isCurrent ? "*" : " "} ${row.branch.padEnd(branchWidth, " ")} ${row.status.padEnd(statusWidth, " ")} ${row.upstream.padEnd(upstreamWidth, " ")} ${row.lastCommit.padEnd(lastCommitWidth, " ")}` + (hasTasks ? ` ${(row.task?.slice(0, 40) ?? "").padEnd(taskWidth, " ")}` : "") + " " + row.path
23344
23921
  );
23345
23922
  }
23346
23923
  return lines.join("\n");
@@ -23373,8 +23950,8 @@ function sortWorktrees(worktrees) {
23373
23950
 
23374
23951
  // src/open.ts
23375
23952
  import { execFile as execFile9 } from "node:child_process";
23376
- import { access as access11, writeFile as writeFile9 } from "node:fs/promises";
23377
- import { join as join15 } from "node:path";
23953
+ import { access as access11, writeFile as writeFile11 } from "node:fs/promises";
23954
+ import { join as join17 } from "node:path";
23378
23955
  import { promisify as promisify10 } from "node:util";
23379
23956
  var execFileAsync9 = promisify10(execFile9);
23380
23957
  function createOpenCommand(dependencies = {}) {
@@ -23541,14 +24118,14 @@ async function defaultPromptForEditor(editors) {
23541
24118
  return choice;
23542
24119
  }
23543
24120
  async function ensureWorkspaceFile(worktreePath, repoName) {
23544
- const workspacePath = join15(worktreePath, `${repoName}.code-workspace`);
24121
+ const workspacePath = join17(worktreePath, `${repoName}.code-workspace`);
23545
24122
  try {
23546
24123
  await access11(workspacePath);
23547
24124
  return workspacePath;
23548
24125
  } catch {
23549
24126
  }
23550
24127
  const workspace = { folders: [{ path: "." }], settings: {} };
23551
- await writeFile9(
24128
+ await writeFile11(
23552
24129
  workspacePath,
23553
24130
  `${JSON.stringify(workspace, null, 2)}
23554
24131
  `,
@@ -23773,7 +24350,7 @@ async function openFromWorktreeSelector(repoRoot, repoName, options, promptForWo
23773
24350
  queryPullRequests: async (_root, branch) => pullRequestsByBranch.get(branch) ?? []
23774
24351
  });
23775
24352
  const connectedEntries = entries.filter(
23776
- (entry) => (entry.pullRequestNumbers?.length ?? 0) > 0
24353
+ (entry) => (entry.pullRequests?.length ?? 0) > 0
23777
24354
  );
23778
24355
  if (connectedEntries.length === 0) {
23779
24356
  if (lookupError !== null) {
@@ -23889,13 +24466,7 @@ async function openForBranch(repoRoot, branch, options, queryPullRequests, promp
23889
24466
  );
23890
24467
  }
23891
24468
  async function openSelectedEntry(entry, options, promptForPullRequest, openInBrowser) {
23892
- const numbers = entry.pullRequestNumbers ?? [];
23893
- const urls = entry.pullRequestUrls ?? [];
23894
- const pullRequests = numbers.map((number, index) => ({
23895
- number,
23896
- sourceBranch: entry.branch ?? "",
23897
- url: urls[index] ?? ""
23898
- })).filter((pullRequest) => pullRequest.url.length > 0);
24469
+ const pullRequests = entry.pullRequests ?? [];
23899
24470
  return openPullRequests(
23900
24471
  pullRequests,
23901
24472
  options,
@@ -23962,7 +24533,7 @@ function formatError(error) {
23962
24533
  }
23963
24534
 
23964
24535
  // src/remove.ts
23965
- import { basename as basename10 } from "node:path";
24536
+ import { basename as basename11 } from "node:path";
23966
24537
  var REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
23967
24538
  function createRemoveCommand(dependencies = {}) {
23968
24539
  const promptForWorktree2 = dependencies.promptForWorktree ?? defaultPromptForWorktree3;
@@ -24068,7 +24639,7 @@ function createRemoveCommand(dependencies = {}) {
24068
24639
  {
24069
24640
  branch: worktree.branch ?? void 0,
24070
24641
  path: worktree.path,
24071
- repo: basename10(repository.repoRoot)
24642
+ repo: basename11(repository.repoRoot)
24072
24643
  },
24073
24644
  options.stderr
24074
24645
  );
@@ -24096,6 +24667,7 @@ function createRemoveCommand(dependencies = {}) {
24096
24667
  }
24097
24668
  }
24098
24669
  await finalizeUndoOperation(journal, [worktree]);
24670
+ await releaseWorktreeSlot(worktree.path);
24099
24671
  if (worktree.branch) {
24100
24672
  try {
24101
24673
  await deleteBranch(repository.repoRoot, worktree.branch);
@@ -24219,7 +24791,8 @@ async function runHookCommand(options) {
24219
24791
  {
24220
24792
  branch: currentWorktree?.branch ?? void 0,
24221
24793
  path: repository.currentRoot,
24222
- repo: repository.repoName
24794
+ repo: repository.repoName,
24795
+ slot: await getWorktreeSlot(repository.currentRoot)
24223
24796
  },
24224
24797
  options.stderr
24225
24798
  );
@@ -24284,12 +24857,18 @@ function formatStatusJson(repoRoot, currentRoot, rows) {
24284
24857
  };
24285
24858
  }
24286
24859
  async function buildStatusRow(worktree) {
24287
- const health = await readWorktreeHealth(worktree.path);
24860
+ const [health, slot, task] = await Promise.all([
24861
+ readWorktreeHealth(worktree.path),
24862
+ getWorktreeSlot(worktree.path),
24863
+ readTask(worktree.path)
24864
+ ]);
24288
24865
  return {
24289
24866
  branch: worktree.branch,
24290
24867
  current: worktree.isCurrent,
24291
24868
  path: worktree.path,
24869
+ slot,
24292
24870
  status: health.status,
24871
+ task: task?.task ?? null,
24293
24872
  upstream: buildUpstreamState2(worktree.branch, health)
24294
24873
  };
24295
24874
  }
@@ -24458,11 +25037,11 @@ function resolveConfiguredString2(value) {
24458
25037
  }
24459
25038
 
24460
25039
  // src/sync-files-command.ts
24461
- import { homedir as homedir9 } from "node:os";
24462
- import { join as join16 } from "node:path";
25040
+ import { homedir as homedir10 } from "node:os";
25041
+ import { join as join18 } from "node:path";
24463
25042
  async function runSyncFilesCommand(options) {
24464
25043
  const repository = await detectRepository(options.cwd);
24465
- const home = options.home ?? homedir9();
25044
+ const home = options.home ?? homedir10();
24466
25045
  const loaded = await loadGlobalConfig(home);
24467
25046
  const repoEntry = findRepoConfigEntry(
24468
25047
  loaded.config,
@@ -24594,10 +25173,40 @@ function writeError(options, message) {
24594
25173
  }
24595
25174
  function expandTilde3(value, home) {
24596
25175
  if (value === "~") return home;
24597
- if (value.startsWith("~/")) return join16(home, value.slice(2));
25176
+ if (value.startsWith("~/")) return join18(home, value.slice(2));
24598
25177
  return value;
24599
25178
  }
24600
25179
 
25180
+ // src/task-command.ts
25181
+ async function runTaskCommand(options) {
25182
+ const repository = await detectRepository(options.cwd);
25183
+ if (options.clear && options.task !== void 0)
25184
+ return emitTaskError(options, "--clear cannot be combined with a task");
25185
+ if (options.clear) await clearTask(repository.currentRoot);
25186
+ else if (options.task !== void 0)
25187
+ await writeTask(repository.currentRoot, options.task);
25188
+ const task = await readTask(repository.currentRoot);
25189
+ if (options.json) {
25190
+ options.stdout(
25191
+ `${JSON.stringify({ task: task?.task ?? null }, null, 2)}
25192
+ `
25193
+ );
25194
+ } else if (task) {
25195
+ options.stdout(`${task.task}
25196
+ `);
25197
+ } else {
25198
+ options.stdout('no task set \u2014 set one: gji task "..."\n');
25199
+ }
25200
+ return 0;
25201
+ }
25202
+ function emitTaskError(options, message) {
25203
+ if (options.json) options.stderr(`${JSON.stringify({ error: message })}
25204
+ `);
25205
+ else options.stderr(`gji task: ${message}
25206
+ `);
25207
+ return 1;
25208
+ }
25209
+
24601
25210
  // src/warp.ts
24602
25211
  var WARP_OUTPUT_FILE_ENV = "GJI_WARP_OUTPUT_FILE";
24603
25212
  async function runWarpCommand(options) {
@@ -24746,7 +25355,7 @@ async function runCli(argv, options = {}) {
24746
25355
  const cwd = options.cwd ?? process.cwd();
24747
25356
  const stdout2 = options.stdout ?? (() => void 0);
24748
25357
  const stderr = options.stderr ?? (() => void 0);
24749
- if (shouldRegisterCurrentRepo(argv)) {
25358
+ if (shouldRegisterCurrentRepo(argv) || isHubInvocation(argv)) {
24750
25359
  await maybeRegisterCurrentRepo(cwd);
24751
25360
  }
24752
25361
  const program2 = createProgram();
@@ -24755,9 +25364,19 @@ async function runCli(argv, options = {}) {
24755
25364
  writeOut: stdout2
24756
25365
  });
24757
25366
  program2.exitOverride();
24758
- if (argv.length === 0) {
24759
- program2.outputHelp();
24760
- return { exitCode: 0 };
25367
+ if (isHubInvocation(argv)) {
25368
+ const exitCode = await runHubCommand(
25369
+ {
25370
+ columns: options.columns,
25371
+ cwd,
25372
+ json: argv.includes("--json"),
25373
+ now: options.now,
25374
+ stderr,
25375
+ stdout: stdout2
25376
+ },
25377
+ options.hubDependencies
25378
+ );
25379
+ return { exitCode };
24761
25380
  }
24762
25381
  try {
24763
25382
  attachCommandActions(program2, {
@@ -24808,6 +25427,9 @@ function shouldRegisterCurrentRepo(argv) {
24808
25427
  "init"
24809
25428
  ].includes(command ?? "");
24810
25429
  }
25430
+ function isHubInvocation(argv) {
25431
+ return argv.length === 0 || argv.length === 1 && argv[0] === "--json";
25432
+ }
24811
25433
  async function maybeRegisterCurrentRepo(cwd) {
24812
25434
  try {
24813
25435
  const { repoRoot } = await detectRepository(cwd);
@@ -24827,7 +25449,7 @@ function registerCommands(program2) {
24827
25449
  ).option(
24828
25450
  "--no-fetch",
24829
25451
  "skip refreshing the remote default branch before creating the new branch"
24830
- ).option("--take", "move current uncommitted changes into the new worktree").option(
25452
+ ).option("--take", "move current uncommitted changes into the new worktree").option("--task <description>", "record the purpose of the new worktree").option(
24831
25453
  "--copy",
24832
25454
  "copy current uncommitted changes instead of moving them (requires --take)"
24833
25455
  ).option("--open", "open the new worktree in an editor after creation").option(
@@ -24874,9 +25496,10 @@ function registerCommands(program2) {
24874
25496
  "--workspace",
24875
25497
  "generate a .code-workspace file before opening (VS Code / Cursor / Windsurf)"
24876
25498
  ).action(notImplemented("open"));
24877
- program2.command("go [branch]").alias("jump").description("resolve and jump to a worktree path").option("--root", "navigate to the main repository root").option("--print", "print the resolved worktree path explicitly").option("--json", "emit JSON for an existing worktree destination").action(notImplemented("go"));
25499
+ program2.command("go [branch]").alias("jump").description("resolve and jump to a worktree path").option("--root", "navigate to the main repository root").option("--print", "print the resolved worktree path explicitly").option("--quiet", "suppress the context card").option("--json", "emit JSON for an existing worktree destination").action(notImplemented("go"));
24878
25500
  program2.command("root").description("print the main repository root path").option("--print", "print the resolved repository root path explicitly").action(notImplemented("root"));
24879
25501
  program2.command("status").description("summarize repository and worktree health").option("--json", "print repository and worktree health as JSON").action(notImplemented("status"));
25502
+ program2.command("task [task]").description("show or update the current worktree task").option("--clear", "clear the current worktree task").option("--json", "emit task metadata as JSON").action(notImplemented("task"));
24880
25503
  program2.command("sync").description("fetch and update one or all worktrees").option("--all", "sync every worktree in the repository").option(
24881
25504
  "--json",
24882
25505
  "emit JSON on success or error instead of human-readable output"
@@ -24935,7 +25558,8 @@ function attachCommandActions(program2, options) {
24935
25558
  noFetch: commandOptions2.fetch === false,
24936
25559
  json: commandOptions2.json,
24937
25560
  open: commandOptions2.open,
24938
- take: commandOptions2.take
25561
+ take: commandOptions2.take,
25562
+ task: commandOptions2.task
24939
25563
  });
24940
25564
  if (exitCode !== 0) {
24941
25565
  throw commanderExit(exitCode);
@@ -25094,6 +25718,7 @@ function attachCommandActions(program2, options) {
25094
25718
  cwd: options.cwd,
25095
25719
  json: commandOptions2.json,
25096
25720
  print: commandOptions2.print,
25721
+ quiet: commandOptions2.quiet,
25097
25722
  root: commandOptions2.root,
25098
25723
  stderr: options.stderr,
25099
25724
  stdout: options.stdout
@@ -25123,6 +25748,19 @@ function attachCommandActions(program2, options) {
25123
25748
  throw commanderExit(exitCode);
25124
25749
  }
25125
25750
  });
25751
+ program2.commands.find((command) => command.name() === "task")?.action(
25752
+ async (task, commandOptions2) => {
25753
+ const exitCode = await runTaskCommand({
25754
+ clear: commandOptions2.clear,
25755
+ cwd: options.cwd,
25756
+ json: commandOptions2.json,
25757
+ stderr: options.stderr,
25758
+ task,
25759
+ stdout: options.stdout
25760
+ });
25761
+ if (exitCode !== 0) throw commanderExit(exitCode);
25762
+ }
25763
+ );
25126
25764
  program2.commands.find((command) => command.name() === "sync")?.action(async (commandOptions2) => {
25127
25765
  const exitCode = await runSyncCommand({
25128
25766
  all: commandOptions2.all,
@@ -25354,7 +25992,7 @@ async function main() {
25354
25992
  }
25355
25993
  async function warnIfMissingShellIntegration() {
25356
25994
  try {
25357
- const { config } = await loadGlobalConfig(homedir10());
25995
+ const { config } = await loadGlobalConfig(homedir11());
25358
25996
  if (!config.shellIntegration) {
25359
25997
  const setupCommand = isHeadless() ? legacyShellSetupCommand() : "gji init";
25360
25998
  process.stderr.write(