@solaqua/gji 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +24 -30
  2. package/dist/browser.d.ts +7 -0
  3. package/dist/browser.js +37 -0
  4. package/dist/clean.js +34 -19
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +59 -4
  7. package/dist/doctor.d.ts +28 -0
  8. package/dist/doctor.js +454 -0
  9. package/dist/gji-bundle.mjs +2514 -378
  10. package/dist/go.d.ts +2 -1
  11. package/dist/go.js +4 -1
  12. package/dist/index.js +12 -6
  13. package/dist/init.d.ts +15 -0
  14. package/dist/init.js +232 -16
  15. package/dist/open.d.ts +2 -1
  16. package/dist/open.js +2 -1
  17. package/dist/pr-open.d.ts +19 -0
  18. package/dist/pr-open.js +290 -0
  19. package/dist/pull-requests.d.ts +33 -0
  20. package/dist/pull-requests.js +384 -0
  21. package/dist/remove.js +2 -2
  22. package/dist/repo-registry.d.ts +5 -0
  23. package/dist/repo-registry.js +87 -14
  24. package/dist/shell-completion.js +278 -10
  25. package/dist/shell-setup.d.ts +9 -0
  26. package/dist/shell-setup.js +56 -0
  27. package/dist/warp.d.ts +3 -0
  28. package/dist/warp.js +8 -3
  29. package/dist/worktree-management.d.ts +2 -1
  30. package/dist/worktree-management.js +23 -7
  31. package/dist/worktree-picker.d.ts +12 -1
  32. package/dist/worktree-picker.js +84 -6
  33. package/man/man1/gji-back.1 +1 -1
  34. package/man/man1/gji-clean.1 +1 -1
  35. package/man/man1/gji-completion.1 +1 -1
  36. package/man/man1/gji-config.1 +1 -1
  37. package/man/man1/gji-doctor.1 +19 -0
  38. package/man/man1/gji-go.1 +1 -1
  39. package/man/man1/gji-history.1 +1 -1
  40. package/man/man1/gji-init.1 +6 -3
  41. package/man/man1/gji-ls.1 +1 -1
  42. package/man/man1/gji-new.1 +1 -1
  43. package/man/man1/gji-open.1 +1 -1
  44. package/man/man1/gji-pr.1 +5 -1
  45. package/man/man1/gji-remove.1 +1 -1
  46. package/man/man1/gji-root.1 +1 -1
  47. package/man/man1/gji-run-hook.1 +1 -1
  48. package/man/man1/gji-status.1 +1 -1
  49. package/man/man1/gji-sync-files.1 +1 -1
  50. package/man/man1/gji-sync.1 +1 -1
  51. package/man/man1/gji-warp.1 +1 -1
  52. package/man/man1/gji.1 +6 -2
  53. package/package.json +1 -1
@@ -1,14 +1,27 @@
1
1
  import { env, platform, stdin, stdout } from "node:process";
2
2
  import { isCancel, Prompt } from "@clack/core";
3
3
  import { loadHistory } from "./history.js";
4
+ import { createPullRequestQuery, } from "./pull-requests.js";
4
5
  import { readWorktreeInfos, } from "./worktree-info.js";
5
- export async function buildWorktreePromptEntries(sources) {
6
- const [history, infos] = await Promise.all([
6
+ const MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY = 4;
7
+ export async function buildWorktreePromptEntries(sources, dependencies = {}) {
8
+ const pullRequestQuery = createPullRequestQuery();
9
+ const queryPullRequests = dependencies.queryPullRequests ?? pullRequestQuery.listOpenPullRequests;
10
+ const queryRepositoryPullRequests = dependencies.queryRepositoryPullRequests ??
11
+ (dependencies.queryPullRequests === undefined
12
+ ? pullRequestQuery.listOpenPullRequestsForRepository
13
+ : undefined);
14
+ const repositoryPullRequests = queryRepositoryPullRequests === undefined
15
+ ? Promise.resolve(null)
16
+ : readRepositoryPullRequests(sources, queryRepositoryPullRequests);
17
+ const [history, infos, pullRequestsByRepository] = await Promise.all([
7
18
  loadHistory(),
8
19
  readWorktreeInfos(sources.map((source) => source.worktree)),
20
+ repositoryPullRequests,
9
21
  ]);
22
+ const pullRequests = await Promise.all(sources.map((source) => readSourcePullRequests(source, queryPullRequests, pullRequestsByRepository)));
10
23
  const historyByPath = new Map(history.map((entry) => [entry.path, entry]));
11
- const entries = sources.map((source, index) => buildWorktreePromptEntry(source, infos[index], historyByPath.get(source.worktree.path)?.timestamp ?? null, Date.now()));
24
+ const entries = sources.map((source, index) => buildWorktreePromptEntry(source, infos[index], pullRequests[index], historyByPath.get(source.worktree.path)?.timestamp ?? null, Date.now()));
12
25
  return entries
13
26
  .sort(comparePromptEntries)
14
27
  .map(({ lastActivityTimestamp: _lastActivityTimestamp, ...entry }) => entry);
@@ -110,7 +123,7 @@ function buildSearchableWorktreeEntry(worktree) {
110
123
  searchText: buildPromptSearchText(worktree),
111
124
  value: worktree.path,
112
125
  worktree: {
113
- branch,
126
+ branch: formatWorktreeBranchLabel(worktree.branch, worktree.pullRequestNumbers),
114
127
  metadata,
115
128
  path: worktree.path,
116
129
  repoName: worktree.repoName,
@@ -715,11 +728,13 @@ function buildPromptSearchText(worktree) {
715
728
  worktree.path,
716
729
  worktree.label,
717
730
  `${worktree.repoName}/${worktree.branch ?? "detached"}`,
731
+ ...(worktree.pullRequestNumbers ?? []).map((number) => `#${number}`),
732
+ ...(worktree.pullRequestUrls ?? []),
718
733
  ]
719
734
  .join(" ")
720
735
  .toLowerCase();
721
736
  }
722
- function buildWorktreePromptEntry(source, info, lastUsedTimestamp, now) {
737
+ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp, now) {
723
738
  const lastWorkedTimestamp = info.lastCommitTimestamp === null ? null : info.lastCommitTimestamp * 1000;
724
739
  const lastActivityTimestamp = lastUsedTimestamp ?? lastWorkedTimestamp;
725
740
  const lastActivityType = lastUsedTimestamp !== null
@@ -727,7 +742,7 @@ function buildWorktreePromptEntry(source, info, lastUsedTimestamp, now) {
727
742
  : lastWorkedTimestamp !== null
728
743
  ? "worked"
729
744
  : null;
730
- const branch = source.worktree.branch ?? "(detached)";
745
+ const branch = formatWorktreeBranchLabel(source.worktree.branch, pullRequests.map((pullRequest) => pullRequest.number));
731
746
  const badges = buildStatusBadges(info);
732
747
  const recency = formatPromptRecency(lastActivityTimestamp, lastActivityType, now);
733
748
  const status = badges.length > 0 ? badges.map((badge) => `[${badge}]`).join(" ") : null;
@@ -748,9 +763,72 @@ function buildWorktreePromptEntry(source, info, lastUsedTimestamp, now) {
748
763
  label,
749
764
  lastActivityTimestamp,
750
765
  metadata,
766
+ pullRequestNumbers: pullRequests.map((pullRequest) => pullRequest.number),
767
+ pullRequestUrls: pullRequests.map((pullRequest) => pullRequest.url),
751
768
  repoName: source.repoName,
752
769
  };
753
770
  }
771
+ async function readSourcePullRequests(source, queryPullRequests, pullRequestsByRepository) {
772
+ if (source.repoRoot === undefined || source.worktree.branch === null) {
773
+ return [];
774
+ }
775
+ if (pullRequestsByRepository !== null) {
776
+ return sortSourcePullRequests(pullRequestsByRepository
777
+ .get(source.repoRoot)
778
+ ?.filter((pullRequest) => pullRequest.sourceBranch === source.worktree.branch));
779
+ }
780
+ try {
781
+ const pullRequests = await queryPullRequests(source.repoRoot, source.worktree.branch);
782
+ return sortSourcePullRequests(pullRequests);
783
+ }
784
+ catch {
785
+ // PR metadata is optional selector decoration; preserve the worktree entry on lookup failures.
786
+ return [];
787
+ }
788
+ }
789
+ async function readRepositoryPullRequests(sources, queryPullRequests) {
790
+ const repoRoots = [
791
+ ...new Set(sources.flatMap((source) => source.repoRoot === undefined || source.worktree.branch === null
792
+ ? []
793
+ : [source.repoRoot])),
794
+ ];
795
+ const results = await mapWithConcurrency(repoRoots, MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY, async (repoRoot) => {
796
+ try {
797
+ return [
798
+ repoRoot,
799
+ sortSourcePullRequests(await queryPullRequests(repoRoot)),
800
+ ];
801
+ }
802
+ catch {
803
+ return [repoRoot, []];
804
+ }
805
+ });
806
+ return new Map(results);
807
+ }
808
+ async function mapWithConcurrency(items, limit, mapper) {
809
+ const results = new Array(items.length);
810
+ let nextIndex = 0;
811
+ async function readNext() {
812
+ for (;;) {
813
+ const index = nextIndex;
814
+ nextIndex += 1;
815
+ if (index >= items.length)
816
+ return;
817
+ results[index] = await mapper(items[index]);
818
+ }
819
+ }
820
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => readNext()));
821
+ return results;
822
+ }
823
+ function sortSourcePullRequests(pullRequests) {
824
+ return [...(pullRequests ?? [])].sort((a, b) => a.number - b.number);
825
+ }
826
+ export function formatWorktreeBranchLabel(branch, pullRequestNumbers = []) {
827
+ const branchLabel = branch ?? "(detached)";
828
+ if (pullRequestNumbers.length === 0)
829
+ return branchLabel;
830
+ return `${branchLabel} (${pullRequestNumbers.map((number) => `#${number}`).join(", ")})`;
831
+ }
754
832
  function buildStatusBadges(info) {
755
833
  const badges = [];
756
834
  if (info.isCurrent) {
@@ -1,4 +1,4 @@
1
- .TH GJI\-BACK 1 "July 2026" "gji 0.8.0" "User Commands"
1
+ .TH GJI\-BACK 1 "July 2026" "gji 0.9.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.8.0" "User Commands"
1
+ .TH GJI\-CLEAN 1 "July 2026" "gji 0.9.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.8.0" "User Commands"
1
+ .TH GJI\-COMPLETION 1 "July 2026" "gji 0.9.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.8.0" "User Commands"
1
+ .TH GJI\-CONFIG 1 "July 2026" "gji 0.9.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-config \- manage global config defaults
4
4
  .SH SYNOPSIS
@@ -0,0 +1,19 @@
1
+ .TH GJI\-DOCTOR 1 "July 2026" "gji 0.9.0" "User Commands"
2
+ .SH NAME
3
+ gji\-doctor \- check gji installation and configuration health
4
+ .SH SYNOPSIS
5
+ .B gji doctor [\fIoptions\fR] [options]
6
+ .SH DESCRIPTION
7
+ check gji installation and configuration health
8
+ .SH OPTIONS
9
+ .TP
10
+ .B \-\-fix
11
+ apply safe automatic fixes after showing the plan
12
+ .TP
13
+ .B \-\-yes
14
+ apply \-\-fix without prompting
15
+ .TP
16
+ .B \-\-json
17
+ emit diagnostic checks as JSON
18
+ .SH "SEE ALSO"
19
+ .BR gji (1)
package/man/man1/gji-go.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH GJI\-GO 1 "July 2026" "gji 0.8.0" "User Commands"
1
+ .TH GJI\-GO 1 "July 2026" "gji 0.9.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-go \- print or select a worktree path
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-HISTORY 1 "July 2026" "gji 0.8.0" "User Commands"
1
+ .TH GJI\-HISTORY 1 "July 2026" "gji 0.9.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-history \- show navigation history
4
4
  .SH SYNOPSIS
@@ -1,13 +1,16 @@
1
- .TH GJI\-INIT 1 "July 2026" "gji 0.8.0" "User Commands"
1
+ .TH GJI\-INIT 1 "July 2026" "gji 0.9.0" "User Commands"
2
2
  .SH NAME
3
- gji\-init \- print or install shell integration
3
+ gji\-init \- set up shell integration interactively or print a shell wrapper
4
4
  .SH SYNOPSIS
5
5
  .B gji init [\fIoptions\fR] [options] [shell]
6
6
  .SH DESCRIPTION
7
- print or install shell integration
7
+ set up shell integration interactively or print a shell wrapper
8
8
  .SH OPTIONS
9
9
  .TP
10
10
  .B \-\-write
11
11
  write the integration to the shell config file
12
+ .TP
13
+ .B \-\-json
14
+ emit a JSON error in non\-interactive mode
12
15
  .SH "SEE ALSO"
13
16
  .BR gji (1)
package/man/man1/gji-ls.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH GJI\-LS 1 "July 2026" "gji 0.8.0" "User Commands"
1
+ .TH GJI\-LS 1 "July 2026" "gji 0.9.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.8.0" "User Commands"
1
+ .TH GJI\-NEW 1 "July 2026" "gji 0.9.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-new \- create a new branch or detached linked worktree
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-OPEN 1 "July 2026" "gji 0.8.0" "User Commands"
1
+ .TH GJI\-OPEN 1 "July 2026" "gji 0.9.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.8.0" "User Commands"
1
+ .TH GJI\-PR 1 "July 2026" "gji 0.9.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
@@ -12,5 +12,9 @@ show what would be created without executing any git commands or writing files
12
12
  .TP
13
13
  .B \-\-json
14
14
  emit JSON on success or error instead of human\-readable output
15
+ .SH SUBCOMMANDS
16
+ .TP
17
+ .B open
18
+ open an existing pull request in the default browser
15
19
  .SH "SEE ALSO"
16
20
  .BR gji (1)
@@ -1,4 +1,4 @@
1
- .TH GJI\-REMOVE 1 "July 2026" "gji 0.8.0" "User Commands"
1
+ .TH GJI\-REMOVE 1 "July 2026" "gji 0.9.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-remove \- remove a linked worktree and delete its branch when present
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-ROOT 1 "July 2026" "gji 0.8.0" "User Commands"
1
+ .TH GJI\-ROOT 1 "July 2026" "gji 0.9.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.8.0" "User Commands"
1
+ .TH GJI\-RUN\-HOOK 1 "July 2026" "gji 0.9.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.8.0" "User Commands"
1
+ .TH GJI\-STATUS 1 "July 2026" "gji 0.9.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.8.0" "User Commands"
1
+ .TH GJI\-SYNC\-FILES 1 "July 2026" "gji 0.9.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.8.0" "User Commands"
1
+ .TH GJI\-SYNC 1 "July 2026" "gji 0.9.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-sync \- fetch and update one or all worktrees
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-WARP 1 "July 2026" "gji 0.8.0" "User Commands"
1
+ .TH GJI\-WARP 1 "July 2026" "gji 0.9.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-warp \- jump to any worktree across 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.8.0" "User Commands"
1
+ .TH GJI 1 "July 2026" "gji 0.9.0" "User Commands"
2
2
  .SH NAME
3
3
  gji \- Context switching without the mess.
4
4
  .SH SYNOPSIS
@@ -15,7 +15,10 @@ eliminating the need for \fBgit stash\fR when switching contexts.
15
15
  create a new branch or detached linked worktree
16
16
  .TP
17
17
  .B init [options] [shell]
18
- print or install shell integration
18
+ set up shell integration interactively or print a shell wrapper
19
+ .TP
20
+ .B doctor [options]
21
+ check gji installation and configuration health
19
22
  .TP
20
23
  .B completion [options] [shell]
21
24
  print shell completion definitions
@@ -77,6 +80,7 @@ output the version number
77
80
  .SH "SEE ALSO"
78
81
  .BR gji\-new (1),
79
82
  .BR gji\-init (1),
83
+ .BR gji\-doctor (1),
80
84
  .BR gji\-completion (1),
81
85
  .BR gji\-pr (1),
82
86
  .BR gji\-back (1),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solaqua/gji",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Git worktree CLI for fast context switching.",
5
5
  "license": "MIT",
6
6
  "author": "sjquant",