@solaqua/gji 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +82 -496
  2. package/dist/bootstrap-output.js +0 -1
  3. package/dist/clean.js +2 -0
  4. package/dist/cli.d.ts +4 -0
  5. package/dist/cli.js +40 -4
  6. package/dist/context-card.d.ts +1 -0
  7. package/dist/context-card.js +25 -0
  8. package/dist/dependency-bootstrap.d.ts +10 -17
  9. package/dist/dependency-bootstrap.js +343 -108
  10. package/dist/dir-clone.d.ts +1 -2
  11. package/dist/dir-clone.js +303 -167
  12. package/dist/doctor.js +21 -0
  13. package/dist/done.js +2 -0
  14. package/dist/gji-bundle.mjs +1854 -1032
  15. package/dist/go.d.ts +1 -0
  16. package/dist/go.js +8 -0
  17. package/dist/hooks.d.ts +1 -0
  18. package/dist/hooks.js +7 -1
  19. package/dist/hub.d.ts +31 -0
  20. package/dist/hub.js +299 -0
  21. package/dist/ls.js +9 -1
  22. package/dist/new.d.ts +1 -0
  23. package/dist/new.js +9 -0
  24. package/dist/pr-open.js +2 -10
  25. package/dist/pull-requests.d.ts +1 -0
  26. package/dist/pull-requests.js +32 -3
  27. package/dist/remove.js +2 -0
  28. package/dist/repo.d.ts +1 -0
  29. package/dist/repo.js +4 -0
  30. package/dist/run-hook.js +2 -0
  31. package/dist/safe-destination.js +10 -0
  32. package/dist/shell-completion.js +11 -3
  33. package/dist/slots.d.ts +8 -0
  34. package/dist/slots.js +61 -0
  35. package/dist/status.d.ts +2 -0
  36. package/dist/status.js +9 -1
  37. package/dist/sync-directories.d.ts +0 -4
  38. package/dist/sync-directories.js +6 -26
  39. package/dist/task-command.d.ts +9 -0
  40. package/dist/task-command.js +29 -0
  41. package/dist/task.d.ts +9 -0
  42. package/dist/task.js +37 -0
  43. package/dist/terminal-text.d.ts +4 -0
  44. package/dist/terminal-text.js +72 -0
  45. package/dist/worktree-bootstrap.js +3 -2
  46. package/dist/worktree-info.d.ts +6 -1
  47. package/dist/worktree-info.js +14 -4
  48. package/dist/worktree-picker.d.ts +3 -3
  49. package/dist/worktree-picker.js +84 -82
  50. package/dist/worktree-sources.d.ts +1 -0
  51. package/dist/worktree-sources.js +57 -11
  52. package/man/man1/gji-back.1 +1 -1
  53. package/man/man1/gji-clean.1 +1 -1
  54. package/man/man1/gji-completion.1 +1 -1
  55. package/man/man1/gji-config.1 +1 -1
  56. package/man/man1/gji-doctor.1 +1 -1
  57. package/man/man1/gji-done.1 +1 -1
  58. package/man/man1/gji-go.1 +4 -1
  59. package/man/man1/gji-history.1 +1 -1
  60. package/man/man1/gji-init.1 +1 -1
  61. package/man/man1/gji-ls.1 +1 -1
  62. package/man/man1/gji-new.1 +4 -1
  63. package/man/man1/gji-open.1 +1 -1
  64. package/man/man1/gji-pr.1 +1 -1
  65. package/man/man1/gji-remove.1 +1 -1
  66. package/man/man1/gji-root.1 +1 -1
  67. package/man/man1/gji-run-hook.1 +1 -1
  68. package/man/man1/gji-status.1 +1 -1
  69. package/man/man1/gji-sync-files.1 +1 -1
  70. package/man/man1/gji-sync.1 +1 -1
  71. package/man/man1/gji-task.1 +16 -0
  72. package/man/man1/gji-undo.1 +1 -1
  73. package/man/man1/gji-warp.1 +1 -1
  74. package/man/man1/gji.1 +5 -1
  75. package/package.json +1 -1
  76. package/dist/clone-failure-store.d.ts +0 -18
  77. package/dist/clone-failure-store.js +0 -230
@@ -1,10 +1,26 @@
1
1
  import { env, platform, stdin, stdout } from "node:process";
2
2
  import { isCancel, Prompt } from "@clack/core";
3
+ import { spinner } from "@clack/prompts";
3
4
  import { loadHistory } from "./history.js";
4
5
  import { createPullRequestQuery, } from "./pull-requests.js";
6
+ import { readTask } from "./task.js";
7
+ import { middleEllipsize, sanitizeTerminalText, startEllipsize, terminalWidth, } from "./terminal-text.js";
5
8
  import { readWorktreeInfos, } from "./worktree-info.js";
6
9
  const MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY = 4;
10
+ const MAX_TASK_READ_CONCURRENCY = 8;
7
11
  export async function buildWorktreePromptEntries(sources, dependencies = {}) {
12
+ const loading = stdin.isTTY === true && stdout.isTTY === true
13
+ ? spinner({ indicator: "timer" })
14
+ : null;
15
+ loading?.start("Loading worktrees");
16
+ try {
17
+ return await buildWorktreePromptEntriesWithoutLoading(sources, dependencies);
18
+ }
19
+ finally {
20
+ loading?.stop();
21
+ }
22
+ }
23
+ async function buildWorktreePromptEntriesWithoutLoading(sources, dependencies) {
8
24
  const metadataMode = dependencies.metadata ?? "full";
9
25
  const includeMetadata = metadataMode === "full";
10
26
  const pullRequestQuery = createPullRequestQuery();
@@ -22,7 +38,7 @@ export async function buildWorktreePromptEntries(sources, dependencies = {}) {
22
38
  loadHistory(),
23
39
  includeMetadata
24
40
  ? readWorktreeInfos(sources.map((source) => source.worktree))
25
- : Promise.resolve(sources.map((source) => createUnhydratedWorktreeInfo(source.worktree))),
41
+ : mapWithConcurrency(sources, MAX_TASK_READ_CONCURRENCY, (source) => createUnhydratedWorktreeInfo(source.worktree)),
26
42
  repositoryPullRequests,
27
43
  ]);
28
44
  const pullRequests = includeMetadata
@@ -34,11 +50,20 @@ export async function buildWorktreePromptEntries(sources, dependencies = {}) {
34
50
  .sort(comparePromptEntries)
35
51
  .map(({ lastActivityTimestamp: _lastActivityTimestamp, ...entry }) => entry);
36
52
  }
37
- function createUnhydratedWorktreeInfo(worktree) {
53
+ async function createUnhydratedWorktreeInfo(worktree) {
54
+ let task = null;
55
+ try {
56
+ task = (await readTask(worktree.path))?.task ?? null;
57
+ }
58
+ catch {
59
+ // Task metadata is optional picker decoration.
60
+ }
38
61
  return {
39
62
  ...worktree,
40
63
  lastCommitTimestamp: null,
64
+ slot: null,
41
65
  status: "unknown",
66
+ task,
42
67
  upstream: { kind: "unknown" },
43
68
  };
44
69
  }
@@ -148,17 +173,24 @@ function buildSearchableWorktreeEntry(worktree) {
148
173
  const metadata = worktree.metadata ?? null;
149
174
  const branch = worktree.branch ?? "(detached)";
150
175
  return {
151
- detail: [worktree.repoName, branch, metadata, worktree.path]
176
+ detail: [
177
+ worktree.repoName,
178
+ branch,
179
+ metadata,
180
+ worktree.task == null ? null : sanitizeTerminalText(worktree.task),
181
+ worktree.path,
182
+ ]
152
183
  .filter((part) => part !== null && part.length > 0)
153
184
  .join(" · "),
154
185
  label: worktree.label,
155
186
  searchText: buildPromptSearchText(worktree),
156
187
  value: worktree.path,
157
188
  worktree: {
158
- branch: formatWorktreeBranchLabel(worktree.branch, worktree.pullRequestNumbers),
189
+ branch: formatWorktreeBranchLabel(worktree.branch, worktree.pullRequests),
159
190
  metadata,
160
191
  path: worktree.path,
161
192
  repoName: worktree.repoName,
193
+ task: worktree.task == null ? null : sanitizeTerminalText(worktree.task),
162
194
  },
163
195
  };
164
196
  }
@@ -488,6 +520,7 @@ class SearchablePrompt {
488
520
  ellipsize: middleEllipsize,
489
521
  max: 18,
490
522
  min: 6,
523
+ removablePriority: entry.worktree.task === null ? undefined : 1,
491
524
  value: entry.worktree.repoName,
492
525
  },
493
526
  {
@@ -503,13 +536,25 @@ class SearchablePrompt {
503
536
  ellipsize: middleEllipsize,
504
537
  max: 30,
505
538
  min: 10,
539
+ removablePriority: entry.worktree.task === null ? undefined : 0,
506
540
  value: entry.worktree.metadata,
507
541
  },
508
542
  ]),
543
+ ...(entry.worktree.task === null
544
+ ? []
545
+ : [
546
+ {
547
+ ellipsize: middleEllipsize,
548
+ max: 36,
549
+ min: 10,
550
+ value: entry.worktree.task,
551
+ },
552
+ ]),
509
553
  {
510
554
  ellipsize: startEllipsize,
511
555
  max: 36,
512
556
  min: 12,
557
+ removablePriority: entry.worktree.task === null ? undefined : 2,
513
558
  value: entry.worktree.path,
514
559
  },
515
560
  ], width);
@@ -745,13 +790,25 @@ function windowPromptEntries(entries, cursor, maxItems) {
745
790
  }
746
791
  return window;
747
792
  }
793
+ function findPromptPieceToRemove(pieces, fallbackIndex) {
794
+ let candidate = fallbackIndex;
795
+ let priority = Infinity;
796
+ for (let index = 0; index < pieces.length; index += 1) {
797
+ const piecePriority = pieces[index].removablePriority;
798
+ if (piecePriority !== undefined && piecePriority < priority) {
799
+ candidate = index;
800
+ priority = piecePriority;
801
+ }
802
+ }
803
+ return candidate;
804
+ }
748
805
  function fitPromptPieces(pieces, width) {
749
806
  const separator = " · ";
750
807
  let visiblePieces = pieces.filter((piece) => piece.value.length > 0);
751
808
  let available = width - terminalWidth(separator) * Math.max(0, visiblePieces.length - 1);
752
809
  while (visiblePieces.length > 1 &&
753
810
  available < minimumPieceLength(visiblePieces)) {
754
- const removableIndex = visiblePieces.length === 4 ? 2 : visiblePieces.length - 2;
811
+ const removableIndex = findPromptPieceToRemove(visiblePieces, visiblePieces.length === 4 ? 2 : visiblePieces.length - 2);
755
812
  visiblePieces = visiblePieces.filter((_, index) => index !== removableIndex);
756
813
  available =
757
814
  width - terminalWidth(separator) * Math.max(0, visiblePieces.length - 1);
@@ -819,9 +876,13 @@ function buildPromptSearchText(worktree) {
819
876
  worktree.branch ?? "detached",
820
877
  worktree.path,
821
878
  worktree.label,
879
+ worktree.task ?? "",
822
880
  `${worktree.repoName}/${worktree.branch ?? "detached"}`,
823
- ...(worktree.pullRequestNumbers ?? []).map((number) => `#${number}`),
824
- ...(worktree.pullRequestUrls ?? []),
881
+ ...(worktree.pullRequests ?? []).flatMap((pullRequest) => [
882
+ `#${pullRequest.number}`,
883
+ pullRequest.title ?? "",
884
+ pullRequest.url,
885
+ ]),
825
886
  ]
826
887
  .join(" ")
827
888
  .toLowerCase();
@@ -834,7 +895,7 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
834
895
  : lastWorkedTimestamp !== null
835
896
  ? "worked"
836
897
  : null;
837
- const branch = formatWorktreeBranchLabel(source.worktree.branch, pullRequests.map((pullRequest) => pullRequest.number));
898
+ const branch = formatWorktreeBranchLabel(source.worktree.branch, pullRequests);
838
899
  const badges = buildStatusBadges(info);
839
900
  const recency = formatPromptRecency(lastActivityTimestamp, lastActivityType, now);
840
901
  const status = badges.length > 0 ? badges.map((badge) => `[${badge}]`).join(" ") : null;
@@ -845,6 +906,9 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
845
906
  middleEllipsize(source.repoName, 22),
846
907
  middleEllipsize(branch, 34),
847
908
  metadata,
909
+ info.task === null
910
+ ? null
911
+ : middleEllipsize(sanitizeTerminalText(info.task), 36),
848
912
  path,
849
913
  ]
850
914
  .filter((part) => part !== null && part.length > 0)
@@ -855,9 +919,9 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
855
919
  label,
856
920
  lastActivityTimestamp,
857
921
  metadata,
858
- pullRequestNumbers: pullRequests.map((pullRequest) => pullRequest.number),
859
- pullRequestUrls: pullRequests.map((pullRequest) => pullRequest.url),
922
+ pullRequests,
860
923
  repoName: source.repoName,
924
+ task: info.task,
861
925
  };
862
926
  }
863
927
  async function readSourcePullRequests(source, queryPullRequests, pullRequestsByRepository) {
@@ -915,11 +979,18 @@ async function mapWithConcurrency(items, limit, mapper) {
915
979
  function sortSourcePullRequests(pullRequests) {
916
980
  return [...(pullRequests ?? [])].sort((a, b) => a.number - b.number);
917
981
  }
918
- export function formatWorktreeBranchLabel(branch, pullRequestNumbers = []) {
982
+ export function formatWorktreeBranchLabel(branch, pullRequests = []) {
919
983
  const branchLabel = branch ?? "(detached)";
920
- if (pullRequestNumbers.length === 0)
984
+ if (pullRequests.length === 0)
921
985
  return branchLabel;
922
- return `${branchLabel} (${pullRequestNumbers.map((number) => `#${number}`).join(", ")})`;
986
+ return `${branchLabel} (${pullRequests
987
+ .map((pullRequest) => {
988
+ const title = pullRequest.title
989
+ ? ` ${middleEllipsize(sanitizeTerminalText(pullRequest.title), 48)}`
990
+ : "";
991
+ return `#${pullRequest.number}${title}`;
992
+ })
993
+ .join(", ")})`;
923
994
  }
924
995
  function buildStatusBadges(info) {
925
996
  const badges = [];
@@ -1023,72 +1094,3 @@ function scoreWorktreeMatch(entry, query) {
1023
1094
  }
1024
1095
  return buildSearchText(entry.repoName, entry).includes(query) ? 1 : null;
1025
1096
  }
1026
- function middleEllipsize(value, maxLength) {
1027
- if (terminalWidth(value) <= maxLength) {
1028
- return value;
1029
- }
1030
- if (maxLength <= 1) {
1031
- return "…";
1032
- }
1033
- const keep = maxLength - 1;
1034
- const start = takeTerminalColumns(value, Math.ceil(keep / 2), "start");
1035
- const end = takeTerminalColumns(value, Math.floor(keep / 2), "end");
1036
- return `${start}…${end}`;
1037
- }
1038
- function startEllipsize(value, maxLength) {
1039
- if (terminalWidth(value) <= maxLength) {
1040
- return value;
1041
- }
1042
- if (maxLength <= 1) {
1043
- return "…";
1044
- }
1045
- return `…${takeTerminalColumns(value, maxLength - 1, "end")}`;
1046
- }
1047
- function takeTerminalColumns(value, maxWidth, direction) {
1048
- const characters = direction === "start" ? Array.from(value) : Array.from(value).reverse();
1049
- const kept = [];
1050
- let width = 0;
1051
- for (const character of characters) {
1052
- const characterWidth = terminalWidth(character);
1053
- if (width + characterWidth > maxWidth)
1054
- break;
1055
- kept.push(character);
1056
- width += characterWidth;
1057
- }
1058
- return direction === "start" ? kept.join("") : kept.reverse().join("");
1059
- }
1060
- function terminalWidth(value) {
1061
- let width = 0;
1062
- for (const character of Array.from(value)) {
1063
- width += characterTerminalWidth(character);
1064
- }
1065
- return width;
1066
- }
1067
- function characterTerminalWidth(character) {
1068
- const codePoint = character.codePointAt(0);
1069
- if (codePoint === undefined)
1070
- return 0;
1071
- if (isZeroWidthCodePoint(codePoint) || /\p{Mark}/u.test(character)) {
1072
- return 0;
1073
- }
1074
- return isWideCodePoint(codePoint) ? 2 : 1;
1075
- }
1076
- function isZeroWidthCodePoint(codePoint) {
1077
- return (codePoint === 0 ||
1078
- codePoint === 0x200d ||
1079
- (codePoint >= 0xfe00 && codePoint <= 0xfe0f));
1080
- }
1081
- function isWideCodePoint(codePoint) {
1082
- return ((codePoint >= 0x1100 && codePoint <= 0x115f) ||
1083
- codePoint === 0x2329 ||
1084
- codePoint === 0x232a ||
1085
- (codePoint >= 0x2e80 && codePoint <= 0xa4cf) ||
1086
- (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
1087
- (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
1088
- (codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
1089
- (codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
1090
- (codePoint >= 0xff00 && codePoint <= 0xff60) ||
1091
- (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
1092
- (codePoint >= 0x1f300 && codePoint <= 0x1faff) ||
1093
- (codePoint >= 0x20000 && codePoint <= 0x3fffd));
1094
- }
@@ -1,3 +1,4 @@
1
1
  import { type RepoRegistryEntry } from "./repo-registry.js";
2
2
  import type { WorktreeSource } from "./worktree-source.js";
3
3
  export declare function listRegisteredWorktreeSources(cwd: string, onSkipped?: (entry: RepoRegistryEntry) => void): Promise<WorktreeSource[]>;
4
+ export declare function listDiscoverableWorktreeSources(cwd: string, onSkipped?: (entry: RepoRegistryEntry) => void): Promise<WorktreeSource[]>;
@@ -1,25 +1,30 @@
1
1
  import { detectRepository, listWorktrees } from "./repo.js";
2
2
  import { loadRegistry } from "./repo-registry.js";
3
+ const MAX_REPOSITORY_DISCOVERY_CONCURRENCY = 4;
3
4
  export async function listRegisteredWorktreeSources(cwd, onSkipped) {
4
5
  const registry = await loadRegistry();
5
6
  const currentRoot = await detectRepository(cwd)
6
7
  .then((repository) => repository.currentRoot)
7
8
  .catch(() => null);
8
- const results = await Promise.allSettled(registry.map(async (entry) => {
9
- const worktrees = await listWorktrees(entry.path);
10
- return { repoName: entry.name, repoRoot: entry.path, worktrees };
11
- }));
9
+ const results = await mapWithConcurrency(registry, MAX_REPOSITORY_DISCOVERY_CONCURRENCY, async (entry) => {
10
+ try {
11
+ const worktrees = await listWorktrees(entry.path);
12
+ return { entry, worktrees };
13
+ }
14
+ catch {
15
+ onSkipped?.(entry);
16
+ return null;
17
+ }
18
+ });
12
19
  const allItems = [];
13
- for (const [index, result] of results.entries()) {
14
- if (result.status === "rejected") {
15
- onSkipped?.(registry[index]);
20
+ for (const result of results) {
21
+ if (result === null)
16
22
  continue;
17
- }
18
- const { repoName, repoRoot, worktrees } = result.value;
23
+ const { entry, worktrees } = result;
19
24
  for (const worktree of worktrees) {
20
25
  allItems.push({
21
- repoRoot,
22
- repoName,
26
+ repoRoot: entry.path,
27
+ repoName: entry.name,
23
28
  worktree: {
24
29
  ...worktree,
25
30
  isCurrent: currentRoot !== null && worktree.path === currentRoot,
@@ -29,3 +34,44 @@ export async function listRegisteredWorktreeSources(cwd, onSkipped) {
29
34
  }
30
35
  return allItems;
31
36
  }
37
+ export async function listDiscoverableWorktreeSources(cwd, onSkipped) {
38
+ const currentRepository = await detectRepository(cwd).catch(() => null);
39
+ const registeredSources = await listRegisteredWorktreeSources(cwd, onSkipped);
40
+ if (currentRepository === null)
41
+ return dedupeSources(registeredSources);
42
+ let currentSources = [];
43
+ try {
44
+ currentSources = (await listWorktrees(cwd)).map((worktree) => ({
45
+ repoName: currentRepository.repoName,
46
+ repoRoot: currentRepository.repoRoot,
47
+ worktree,
48
+ }));
49
+ }
50
+ catch {
51
+ // Registered repositories remain discoverable when the current checkout is transiently unavailable.
52
+ }
53
+ return dedupeSources([...currentSources, ...registeredSources]);
54
+ }
55
+ async function mapWithConcurrency(items, limit, mapper) {
56
+ const results = new Array(items.length);
57
+ let nextIndex = 0;
58
+ async function readNext() {
59
+ for (;;) {
60
+ const index = nextIndex++;
61
+ if (index >= items.length)
62
+ return;
63
+ results[index] = await mapper(items[index]);
64
+ }
65
+ }
66
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => readNext()));
67
+ return results;
68
+ }
69
+ function dedupeSources(sources) {
70
+ const seen = new Set();
71
+ return sources.filter((source) => {
72
+ if (seen.has(source.worktree.path))
73
+ return false;
74
+ seen.add(source.worktree.path);
75
+ return true;
76
+ });
77
+ }
@@ -1,4 +1,4 @@
1
- .TH GJI\-BACK 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-BACK 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-back \- navigate to the previously visited worktree, optionally N steps back
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-CLEAN 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-CLEAN 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-clean \- interactively prune linked worktrees
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-COMPLETION 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-COMPLETION 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-completion \- print shell completion definitions
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-CONFIG 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-CONFIG 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-config \- manage global config defaults
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-DOCTOR 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-DOCTOR 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-doctor \- check gji installation and configuration health
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-DONE 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-DONE 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-done \- finish the current linked worktree and return safely
4
4
  .SH SYNOPSIS
package/man/man1/gji-go.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH GJI\-GO 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-GO 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-go \- resolve and jump to a worktree path
4
4
  .SH SYNOPSIS
@@ -13,6 +13,9 @@ navigate to the main repository root
13
13
  .B \-\-print
14
14
  print the resolved worktree path explicitly
15
15
  .TP
16
+ .B \-\-quiet
17
+ suppress the context card
18
+ .TP
16
19
  .B \-\-json
17
20
  emit JSON for an existing worktree destination
18
21
  .SH "SEE ALSO"
@@ -1,4 +1,4 @@
1
- .TH GJI\-HISTORY 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-HISTORY 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-history \- show navigation history
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-INIT 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-INIT 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-init \- set up shell integration interactively or print a shell wrapper
4
4
  .SH SYNOPSIS
package/man/man1/gji-ls.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH GJI\-LS 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-LS 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-ls \- list active worktrees
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-NEW 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-NEW 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-new \- create a new branch or detached linked worktree and CoW\-bootstrap configured directories
4
4
  .SH SYNOPSIS
@@ -28,6 +28,9 @@ skip refreshing the remote default branch before creating the new branch
28
28
  .B \-\-take
29
29
  move current uncommitted changes into the new worktree
30
30
  .TP
31
+ .B \-\-task <description>
32
+ record the purpose of the new worktree
33
+ .TP
31
34
  .B \-\-copy
32
35
  copy current uncommitted changes instead of moving them (requires \-\-take)
33
36
  .TP
@@ -1,4 +1,4 @@
1
- .TH GJI\-OPEN 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-OPEN 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-open \- open the worktree in an editor
4
4
  .SH SYNOPSIS
package/man/man1/gji-pr.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH GJI\-PR 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-PR 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-pr \- fetch a pull request by number, #number, or URL into a linked worktree
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-REMOVE 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-REMOVE 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-remove \- deprecated: use gji done for one worktree or gji clean for bulk cleanup
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-ROOT 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-ROOT 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-root \- print the main repository root path
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-RUN\-HOOK 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-RUN\-HOOK 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-run\-hook \- run a named hook (after\-create, after\-enter, before\-remove) in the current worktree
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-STATUS 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-STATUS 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-status \- summarize repository and worktree health
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-SYNC\-FILES 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-SYNC\-FILES 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-sync\-files \- manage local files copied into new worktrees
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-SYNC 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-SYNC 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-sync \- fetch and update one or all worktrees
4
4
  .SH SYNOPSIS
@@ -0,0 +1,16 @@
1
+ .TH GJI\-TASK 1 "August 2026" "gji 0.12.1" "User Commands"
2
+ .SH NAME
3
+ gji\-task \- show or update the current worktree task
4
+ .SH SYNOPSIS
5
+ .B gji task [\fIoptions\fR] [options] [task]
6
+ .SH DESCRIPTION
7
+ show or update the current worktree task
8
+ .SH OPTIONS
9
+ .TP
10
+ .B \-\-clear
11
+ clear the current worktree task
12
+ .TP
13
+ .B \-\-json
14
+ emit task metadata as JSON
15
+ .SH "SEE ALSO"
16
+ .BR gji (1)
@@ -1,4 +1,4 @@
1
- .TH GJI\-UNDO 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-UNDO 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-undo \- restore the most recent destructive worktree operation
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-WARP 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI\-WARP 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-warp \- deprecated: use gji go (press Tab for all known repos)
4
4
  .SH SYNOPSIS
package/man/man1/gji.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH GJI 1 "July 2026" "gji 0.11.0" "User Commands"
1
+ .TH GJI 1 "August 2026" "gji 0.12.1" "User Commands"
2
2
  .SH NAME
3
3
  gji \- Context switching without the mess.
4
4
  .SH SYNOPSIS
@@ -52,6 +52,9 @@ print the main repository root path
52
52
  .B status [options]
53
53
  summarize repository and worktree health
54
54
  .TP
55
+ .B task [options] [task]
56
+ show or update the current worktree task
57
+ .TP
55
58
  .B sync [options]
56
59
  fetch and update one or all worktrees
57
60
  .TP
@@ -97,6 +100,7 @@ output the version number
97
100
  .BR gji\-go (1),
98
101
  .BR gji\-root (1),
99
102
  .BR gji\-status (1),
103
+ .BR gji\-task (1),
100
104
  .BR gji\-sync (1),
101
105
  .BR gji\-sync\-files (1),
102
106
  .BR gji\-ls (1),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solaqua/gji",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "Git worktree CLI for fast context switching.",
5
5
  "license": "MIT",
6
6
  "author": "sjquant",
@@ -1,18 +0,0 @@
1
- export interface CloneFailureStore {
2
- isCached(repoRoot: string, directory: string, scope?: string): Promise<boolean>;
3
- cache(repoRoot: string, directory: string, reason: string, scope?: string): Promise<void>;
4
- clear(repoRoot: string, directory: string, scope?: string): Promise<void>;
5
- }
6
- export declare class FileCloneFailureStore implements CloneFailureStore {
7
- private updateQueue;
8
- isCached(repoRoot: string, directory: string, scope?: string): Promise<boolean>;
9
- cache(repoRoot: string, directory: string, reason: string, scope?: string): Promise<void>;
10
- clear(repoRoot: string, directory: string, scope?: string): Promise<void>;
11
- private update;
12
- private withStateLock;
13
- private readState;
14
- private writeState;
15
- private stateFilePath;
16
- }
17
- export declare const defaultCloneFailureStore: CloneFailureStore;
18
- export declare function cloneFailureScope(source: string, destination: string): Promise<string>;