@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
@@ -2,8 +2,11 @@ import { env, platform, stdin, stdout } from "node:process";
2
2
  import { isCancel, Prompt } from "@clack/core";
3
3
  import { loadHistory } from "./history.js";
4
4
  import { createPullRequestQuery, } from "./pull-requests.js";
5
+ import { readTask } from "./task.js";
6
+ import { middleEllipsize, sanitizeTerminalText, startEllipsize, terminalWidth, } from "./terminal-text.js";
5
7
  import { readWorktreeInfos, } from "./worktree-info.js";
6
8
  const MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY = 4;
9
+ const MAX_TASK_READ_CONCURRENCY = 8;
7
10
  export async function buildWorktreePromptEntries(sources, dependencies = {}) {
8
11
  const metadataMode = dependencies.metadata ?? "full";
9
12
  const includeMetadata = metadataMode === "full";
@@ -22,7 +25,7 @@ export async function buildWorktreePromptEntries(sources, dependencies = {}) {
22
25
  loadHistory(),
23
26
  includeMetadata
24
27
  ? readWorktreeInfos(sources.map((source) => source.worktree))
25
- : Promise.resolve(sources.map((source) => createUnhydratedWorktreeInfo(source.worktree))),
28
+ : mapWithConcurrency(sources, MAX_TASK_READ_CONCURRENCY, (source) => createUnhydratedWorktreeInfo(source.worktree)),
26
29
  repositoryPullRequests,
27
30
  ]);
28
31
  const pullRequests = includeMetadata
@@ -34,11 +37,20 @@ export async function buildWorktreePromptEntries(sources, dependencies = {}) {
34
37
  .sort(comparePromptEntries)
35
38
  .map(({ lastActivityTimestamp: _lastActivityTimestamp, ...entry }) => entry);
36
39
  }
37
- function createUnhydratedWorktreeInfo(worktree) {
40
+ async function createUnhydratedWorktreeInfo(worktree) {
41
+ let task = null;
42
+ try {
43
+ task = (await readTask(worktree.path))?.task ?? null;
44
+ }
45
+ catch {
46
+ // Task metadata is optional picker decoration.
47
+ }
38
48
  return {
39
49
  ...worktree,
40
50
  lastCommitTimestamp: null,
51
+ slot: null,
41
52
  status: "unknown",
53
+ task,
42
54
  upstream: { kind: "unknown" },
43
55
  };
44
56
  }
@@ -148,17 +160,24 @@ function buildSearchableWorktreeEntry(worktree) {
148
160
  const metadata = worktree.metadata ?? null;
149
161
  const branch = worktree.branch ?? "(detached)";
150
162
  return {
151
- detail: [worktree.repoName, branch, metadata, worktree.path]
163
+ detail: [
164
+ worktree.repoName,
165
+ branch,
166
+ metadata,
167
+ worktree.task == null ? null : sanitizeTerminalText(worktree.task),
168
+ worktree.path,
169
+ ]
152
170
  .filter((part) => part !== null && part.length > 0)
153
171
  .join(" · "),
154
172
  label: worktree.label,
155
173
  searchText: buildPromptSearchText(worktree),
156
174
  value: worktree.path,
157
175
  worktree: {
158
- branch: formatWorktreeBranchLabel(worktree.branch, worktree.pullRequestNumbers),
176
+ branch: formatWorktreeBranchLabel(worktree.branch, worktree.pullRequests),
159
177
  metadata,
160
178
  path: worktree.path,
161
179
  repoName: worktree.repoName,
180
+ task: worktree.task == null ? null : sanitizeTerminalText(worktree.task),
162
181
  },
163
182
  };
164
183
  }
@@ -488,6 +507,7 @@ class SearchablePrompt {
488
507
  ellipsize: middleEllipsize,
489
508
  max: 18,
490
509
  min: 6,
510
+ removablePriority: entry.worktree.task === null ? undefined : 1,
491
511
  value: entry.worktree.repoName,
492
512
  },
493
513
  {
@@ -503,13 +523,25 @@ class SearchablePrompt {
503
523
  ellipsize: middleEllipsize,
504
524
  max: 30,
505
525
  min: 10,
526
+ removablePriority: entry.worktree.task === null ? undefined : 0,
506
527
  value: entry.worktree.metadata,
507
528
  },
508
529
  ]),
530
+ ...(entry.worktree.task === null
531
+ ? []
532
+ : [
533
+ {
534
+ ellipsize: middleEllipsize,
535
+ max: 36,
536
+ min: 10,
537
+ value: entry.worktree.task,
538
+ },
539
+ ]),
509
540
  {
510
541
  ellipsize: startEllipsize,
511
542
  max: 36,
512
543
  min: 12,
544
+ removablePriority: entry.worktree.task === null ? undefined : 2,
513
545
  value: entry.worktree.path,
514
546
  },
515
547
  ], width);
@@ -745,13 +777,25 @@ function windowPromptEntries(entries, cursor, maxItems) {
745
777
  }
746
778
  return window;
747
779
  }
780
+ function findPromptPieceToRemove(pieces, fallbackIndex) {
781
+ let candidate = fallbackIndex;
782
+ let priority = Infinity;
783
+ for (let index = 0; index < pieces.length; index += 1) {
784
+ const piecePriority = pieces[index].removablePriority;
785
+ if (piecePriority !== undefined && piecePriority < priority) {
786
+ candidate = index;
787
+ priority = piecePriority;
788
+ }
789
+ }
790
+ return candidate;
791
+ }
748
792
  function fitPromptPieces(pieces, width) {
749
793
  const separator = " · ";
750
794
  let visiblePieces = pieces.filter((piece) => piece.value.length > 0);
751
795
  let available = width - terminalWidth(separator) * Math.max(0, visiblePieces.length - 1);
752
796
  while (visiblePieces.length > 1 &&
753
797
  available < minimumPieceLength(visiblePieces)) {
754
- const removableIndex = visiblePieces.length === 4 ? 2 : visiblePieces.length - 2;
798
+ const removableIndex = findPromptPieceToRemove(visiblePieces, visiblePieces.length === 4 ? 2 : visiblePieces.length - 2);
755
799
  visiblePieces = visiblePieces.filter((_, index) => index !== removableIndex);
756
800
  available =
757
801
  width - terminalWidth(separator) * Math.max(0, visiblePieces.length - 1);
@@ -819,9 +863,13 @@ function buildPromptSearchText(worktree) {
819
863
  worktree.branch ?? "detached",
820
864
  worktree.path,
821
865
  worktree.label,
866
+ worktree.task ?? "",
822
867
  `${worktree.repoName}/${worktree.branch ?? "detached"}`,
823
- ...(worktree.pullRequestNumbers ?? []).map((number) => `#${number}`),
824
- ...(worktree.pullRequestUrls ?? []),
868
+ ...(worktree.pullRequests ?? []).flatMap((pullRequest) => [
869
+ `#${pullRequest.number}`,
870
+ pullRequest.title ?? "",
871
+ pullRequest.url,
872
+ ]),
825
873
  ]
826
874
  .join(" ")
827
875
  .toLowerCase();
@@ -834,7 +882,7 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
834
882
  : lastWorkedTimestamp !== null
835
883
  ? "worked"
836
884
  : null;
837
- const branch = formatWorktreeBranchLabel(source.worktree.branch, pullRequests.map((pullRequest) => pullRequest.number));
885
+ const branch = formatWorktreeBranchLabel(source.worktree.branch, pullRequests);
838
886
  const badges = buildStatusBadges(info);
839
887
  const recency = formatPromptRecency(lastActivityTimestamp, lastActivityType, now);
840
888
  const status = badges.length > 0 ? badges.map((badge) => `[${badge}]`).join(" ") : null;
@@ -845,6 +893,9 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
845
893
  middleEllipsize(source.repoName, 22),
846
894
  middleEllipsize(branch, 34),
847
895
  metadata,
896
+ info.task === null
897
+ ? null
898
+ : middleEllipsize(sanitizeTerminalText(info.task), 36),
848
899
  path,
849
900
  ]
850
901
  .filter((part) => part !== null && part.length > 0)
@@ -855,9 +906,9 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
855
906
  label,
856
907
  lastActivityTimestamp,
857
908
  metadata,
858
- pullRequestNumbers: pullRequests.map((pullRequest) => pullRequest.number),
859
- pullRequestUrls: pullRequests.map((pullRequest) => pullRequest.url),
909
+ pullRequests,
860
910
  repoName: source.repoName,
911
+ task: info.task,
861
912
  };
862
913
  }
863
914
  async function readSourcePullRequests(source, queryPullRequests, pullRequestsByRepository) {
@@ -915,11 +966,18 @@ async function mapWithConcurrency(items, limit, mapper) {
915
966
  function sortSourcePullRequests(pullRequests) {
916
967
  return [...(pullRequests ?? [])].sort((a, b) => a.number - b.number);
917
968
  }
918
- export function formatWorktreeBranchLabel(branch, pullRequestNumbers = []) {
969
+ export function formatWorktreeBranchLabel(branch, pullRequests = []) {
919
970
  const branchLabel = branch ?? "(detached)";
920
- if (pullRequestNumbers.length === 0)
971
+ if (pullRequests.length === 0)
921
972
  return branchLabel;
922
- return `${branchLabel} (${pullRequestNumbers.map((number) => `#${number}`).join(", ")})`;
973
+ return `${branchLabel} (${pullRequests
974
+ .map((pullRequest) => {
975
+ const title = pullRequest.title
976
+ ? ` ${middleEllipsize(sanitizeTerminalText(pullRequest.title), 48)}`
977
+ : "";
978
+ return `#${pullRequest.number}${title}`;
979
+ })
980
+ .join(", ")})`;
923
981
  }
924
982
  function buildStatusBadges(info) {
925
983
  const badges = [];
@@ -1023,72 +1081,3 @@ function scoreWorktreeMatch(entry, query) {
1023
1081
  }
1024
1082
  return buildSearchText(entry.repoName, entry).includes(query) ? 1 : null;
1025
1083
  }
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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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 "July 2026" "gji 0.12.0" "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.0",
4
4
  "description": "Git worktree CLI for fast context switching.",
5
5
  "license": "MIT",
6
6
  "author": "sjquant",