@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.
- package/README.md +82 -496
- package/dist/clean.js +2 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +40 -4
- package/dist/context-card.d.ts +1 -0
- package/dist/context-card.js +25 -0
- package/dist/doctor.js +21 -0
- package/dist/done.js +2 -0
- package/dist/gji-bundle.mjs +988 -350
- package/dist/go.d.ts +1 -0
- package/dist/go.js +8 -0
- package/dist/hooks.d.ts +1 -0
- package/dist/hooks.js +7 -1
- package/dist/hub.d.ts +31 -0
- package/dist/hub.js +287 -0
- package/dist/ls.js +9 -1
- package/dist/new.d.ts +1 -0
- package/dist/new.js +9 -0
- package/dist/pr-open.js +2 -10
- package/dist/pull-requests.d.ts +1 -0
- package/dist/pull-requests.js +32 -3
- package/dist/remove.js +2 -0
- package/dist/repo.d.ts +1 -0
- package/dist/repo.js +4 -0
- package/dist/run-hook.js +2 -0
- package/dist/shell-completion.js +11 -3
- package/dist/slots.d.ts +8 -0
- package/dist/slots.js +61 -0
- package/dist/status.d.ts +2 -0
- package/dist/status.js +9 -1
- package/dist/task-command.d.ts +9 -0
- package/dist/task-command.js +29 -0
- package/dist/task.d.ts +9 -0
- package/dist/task.js +37 -0
- package/dist/terminal-text.d.ts +4 -0
- package/dist/terminal-text.js +72 -0
- package/dist/worktree-bootstrap.js +3 -0
- package/dist/worktree-info.d.ts +6 -1
- package/dist/worktree-info.js +14 -4
- package/dist/worktree-picker.d.ts +3 -3
- package/dist/worktree-picker.js +71 -82
- package/dist/worktree-sources.d.ts +1 -0
- package/dist/worktree-sources.js +57 -11
- package/man/man1/gji-back.1 +1 -1
- package/man/man1/gji-clean.1 +1 -1
- package/man/man1/gji-completion.1 +1 -1
- package/man/man1/gji-config.1 +1 -1
- package/man/man1/gji-doctor.1 +1 -1
- package/man/man1/gji-done.1 +1 -1
- package/man/man1/gji-go.1 +4 -1
- package/man/man1/gji-history.1 +1 -1
- package/man/man1/gji-init.1 +1 -1
- package/man/man1/gji-ls.1 +1 -1
- package/man/man1/gji-new.1 +4 -1
- package/man/man1/gji-open.1 +1 -1
- package/man/man1/gji-pr.1 +1 -1
- package/man/man1/gji-remove.1 +1 -1
- package/man/man1/gji-root.1 +1 -1
- package/man/man1/gji-run-hook.1 +1 -1
- package/man/man1/gji-status.1 +1 -1
- package/man/man1/gji-sync-files.1 +1 -1
- package/man/man1/gji-sync.1 +1 -1
- package/man/man1/gji-task.1 +16 -0
- package/man/man1/gji-undo.1 +1 -1
- package/man/man1/gji-warp.1 +1 -1
- package/man/man1/gji.1 +5 -1
- package/package.json +1 -1
package/dist/worktree-picker.js
CHANGED
|
@@ -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
|
-
:
|
|
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: [
|
|
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.
|
|
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.
|
|
824
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
969
|
+
export function formatWorktreeBranchLabel(branch, pullRequests = []) {
|
|
919
970
|
const branchLabel = branch ?? "(detached)";
|
|
920
|
-
if (
|
|
971
|
+
if (pullRequests.length === 0)
|
|
921
972
|
return branchLabel;
|
|
922
|
-
return `${branchLabel} (${
|
|
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[]>;
|
package/dist/worktree-sources.js
CHANGED
|
@@ -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
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
14
|
-
if (result
|
|
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
|
+
}
|
package/man/man1/gji-back.1
CHANGED
package/man/man1/gji-clean.1
CHANGED
package/man/man1/gji-config.1
CHANGED
package/man/man1/gji-doctor.1
CHANGED
package/man/man1/gji-done.1
CHANGED
package/man/man1/gji-go.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH GJI\-GO 1 "July 2026" "gji 0.
|
|
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"
|
package/man/man1/gji-history.1
CHANGED
package/man/man1/gji-init.1
CHANGED
package/man/man1/gji-ls.1
CHANGED
package/man/man1/gji-new.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH GJI\-NEW 1 "July 2026" "gji 0.
|
|
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
|
package/man/man1/gji-open.1
CHANGED
package/man/man1/gji-pr.1
CHANGED
package/man/man1/gji-remove.1
CHANGED
package/man/man1/gji-root.1
CHANGED
package/man/man1/gji-run-hook.1
CHANGED
package/man/man1/gji-status.1
CHANGED
package/man/man1/gji-sync.1
CHANGED
|
@@ -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)
|
package/man/man1/gji-undo.1
CHANGED
package/man/man1/gji-warp.1
CHANGED
package/man/man1/gji.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH GJI 1 "July 2026" "gji 0.
|
|
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),
|