@solaqua/gji 0.12.4 → 0.13.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 (214) hide show
  1. package/README.md +12 -0
  2. package/dist/application/worktree/catalog.d.ts +31 -0
  3. package/dist/application/worktree/catalog.js +88 -0
  4. package/dist/application/worktree/context-card.d.ts +14 -0
  5. package/dist/application/worktree/context-card.js +12 -0
  6. package/dist/{go-resolver.d.ts → application/worktree/go-resolution.d.ts} +12 -4
  7. package/dist/{go-resolver.js → application/worktree/go-resolution.js} +46 -44
  8. package/dist/application/worktree/read-models.d.ts +31 -0
  9. package/dist/application/worktree/sources.d.ts +7 -0
  10. package/dist/{worktree-sources.js → application/worktree/sources.js} +12 -24
  11. package/dist/{back.d.ts → cli/commands/back.d.ts} +3 -1
  12. package/dist/{back.js → cli/commands/back.js} +8 -6
  13. package/dist/{clean.d.ts → cli/commands/clean.d.ts} +4 -2
  14. package/dist/{clean.js → cli/commands/clean.js} +32 -24
  15. package/dist/{completion.js → cli/commands/completion.js} +2 -2
  16. package/dist/{config-command.d.ts → cli/commands/config-command.d.ts} +2 -0
  17. package/dist/{config-command.js → cli/commands/config-command.js} +7 -5
  18. package/dist/{doctor.d.ts → cli/commands/doctor.d.ts} +2 -0
  19. package/dist/{doctor.js → cli/commands/doctor.js} +23 -25
  20. package/dist/{done.d.ts → cli/commands/done.d.ts} +2 -0
  21. package/dist/{done.js → cli/commands/done.js} +26 -23
  22. package/dist/{go.d.ts → cli/commands/go.d.ts} +6 -1
  23. package/dist/{go.js → cli/commands/go.js} +57 -41
  24. package/dist/{history-command.d.ts → cli/commands/history-command.d.ts} +2 -0
  25. package/dist/{history-command.js → cli/commands/history-command.js} +3 -1
  26. package/dist/{hub.d.ts → cli/commands/hub.d.ts} +7 -3
  27. package/dist/{hub.js → cli/commands/hub.js} +23 -33
  28. package/dist/{init.d.ts → cli/commands/init.d.ts} +3 -1
  29. package/dist/{init.js → cli/commands/init.js} +37 -34
  30. package/dist/{ls.d.ts → cli/commands/ls.d.ts} +4 -2
  31. package/dist/{ls.js → cli/commands/ls.js} +8 -5
  32. package/dist/{new.d.ts → cli/commands/new.d.ts} +6 -2
  33. package/dist/{new.js → cli/commands/new.js} +237 -109
  34. package/dist/{open.d.ts → cli/commands/open.d.ts} +4 -2
  35. package/dist/{open.js → cli/commands/open.js} +19 -12
  36. package/dist/{pr-open.d.ts → cli/commands/pr-open.d.ts} +4 -2
  37. package/dist/{pr-open.js → cli/commands/pr-open.js} +20 -18
  38. package/dist/{pr.d.ts → cli/commands/pr.d.ts} +5 -3
  39. package/dist/{pr.js → cli/commands/pr.js} +34 -41
  40. package/dist/{remove.d.ts → cli/commands/remove.d.ts} +4 -2
  41. package/dist/{remove.js → cli/commands/remove.js} +23 -17
  42. package/dist/{root.d.ts → cli/commands/root.d.ts} +2 -0
  43. package/dist/{root.js → cli/commands/root.js} +4 -2
  44. package/dist/{run-hook.d.ts → cli/commands/run-hook.d.ts} +2 -0
  45. package/dist/{run-hook.js → cli/commands/run-hook.js} +7 -4
  46. package/dist/{status.d.ts → cli/commands/status.d.ts} +2 -0
  47. package/dist/{status.js → cli/commands/status.js} +10 -7
  48. package/dist/{sync-files-command.d.ts → cli/commands/sync-files-command.d.ts} +2 -0
  49. package/dist/{sync-files-command.js → cli/commands/sync-files-command.js} +11 -9
  50. package/dist/{sync.d.ts → cli/commands/sync.d.ts} +2 -0
  51. package/dist/{sync.js → cli/commands/sync.js} +7 -4
  52. package/dist/{task-command.d.ts → cli/commands/task-command.d.ts} +2 -0
  53. package/dist/{task-command.js → cli/commands/task-command.js} +4 -2
  54. package/dist/{undo.d.ts → cli/commands/undo.d.ts} +8 -6
  55. package/dist/{undo.js → cli/commands/undo.js} +44 -34
  56. package/dist/{warp.d.ts → cli/commands/warp.d.ts} +5 -2
  57. package/dist/{warp.js → cli/commands/warp.js} +20 -10
  58. package/dist/cli/dependencies.d.ts +114 -0
  59. package/dist/cli/dependencies.js +118 -0
  60. package/dist/{cli.d.ts → cli/program.d.ts} +4 -2
  61. package/dist/{cli.js → cli/program.js} +108 -124
  62. package/dist/domain/repository/context.d.ts +6 -0
  63. package/dist/domain/repository/context.js +1 -0
  64. package/dist/domain/repository/registry.d.ts +5 -0
  65. package/dist/domain/repository/registry.js +1 -0
  66. package/dist/domain/shared/concurrency.d.ts +1 -0
  67. package/dist/domain/shared/concurrency.js +15 -0
  68. package/dist/domain/worktree/matching.d.ts +4 -0
  69. package/dist/domain/worktree/matching.js +79 -0
  70. package/dist/domain/worktree/policy.d.ts +2 -0
  71. package/dist/domain/worktree/policy.js +58 -0
  72. package/dist/domain/worktree/pr-reference.d.ts +1 -0
  73. package/dist/domain/worktree/pr-reference.js +9 -0
  74. package/dist/{worktree-source.d.ts → domain/worktree/source.d.ts} +1 -1
  75. package/dist/domain/worktree/source.js +1 -0
  76. package/dist/domain/worktree/types.d.ts +5 -0
  77. package/dist/domain/worktree/types.js +1 -0
  78. package/dist/gji-bundle.mjs +7100 -6265
  79. package/dist/index.js +3 -3
  80. package/dist/{dependency-bootstrap.d.ts → infrastructure/bootstrap/dependency-bootstrap.d.ts} +3 -30
  81. package/dist/{dependency-bootstrap.js → infrastructure/bootstrap/dependency-bootstrap.js} +4 -4
  82. package/dist/infrastructure/git/health.d.ts +9 -0
  83. package/dist/infrastructure/git/health.js +44 -0
  84. package/dist/infrastructure/git/refs.d.ts +6 -0
  85. package/dist/{git.js → infrastructure/git/refs.js} +1 -51
  86. package/dist/infrastructure/git/runner.d.ts +2 -0
  87. package/dist/infrastructure/git/runner.js +16 -0
  88. package/dist/{editor.d.ts → infrastructure/integrations/editor.d.ts} +2 -6
  89. package/dist/{pull-requests.d.ts → infrastructure/integrations/pull-requests.d.ts} +3 -11
  90. package/dist/{config.d.ts → infrastructure/persistence/config.d.ts} +3 -17
  91. package/dist/{config.js → infrastructure/persistence/config.js} +2 -17
  92. package/dist/{history.d.ts → infrastructure/persistence/history.d.ts} +2 -5
  93. package/dist/{task.js → infrastructure/persistence/task.js} +1 -1
  94. package/dist/infrastructure/process/command-runner.d.ts +3 -0
  95. package/dist/{hooks.d.ts → infrastructure/process/hooks.d.ts} +2 -12
  96. package/dist/infrastructure/repository/adapters.d.ts +7 -0
  97. package/dist/infrastructure/repository/adapters.js +23 -0
  98. package/dist/infrastructure/repository/context.d.ts +3 -0
  99. package/dist/infrastructure/repository/context.js +20 -0
  100. package/dist/infrastructure/repository/refs.d.ts +3 -0
  101. package/dist/infrastructure/repository/refs.js +37 -0
  102. package/dist/{repo-registry.d.ts → infrastructure/repository/registry.d.ts} +1 -5
  103. package/dist/{repo-registry.js → infrastructure/repository/registry.js} +1 -1
  104. package/dist/infrastructure/repository/worktrees.d.ts +2 -0
  105. package/dist/infrastructure/repository/worktrees.js +48 -0
  106. package/dist/{uv-validation.d.ts → infrastructure/validation/uv.d.ts} +1 -1
  107. package/dist/{uv-validation.js → infrastructure/validation/uv.js} +1 -1
  108. package/dist/infrastructure/worktree/bootstrap.d.ts +3 -0
  109. package/dist/{worktree-bootstrap.js → infrastructure/worktree/bootstrap.js} +7 -10
  110. package/dist/infrastructure/worktree/info.d.ts +6 -0
  111. package/dist/infrastructure/worktree/info.js +61 -0
  112. package/dist/{worktree-management.d.ts → infrastructure/worktree/lifecycle.d.ts} +0 -6
  113. package/dist/{worktree-management.js → infrastructure/worktree/lifecycle.js} +1 -9
  114. package/dist/ports/bootstrap.d.ts +61 -0
  115. package/dist/ports/bootstrap.js +1 -0
  116. package/dist/ports/config.d.ts +21 -0
  117. package/dist/ports/config.js +17 -0
  118. package/dist/ports/editor.d.ts +6 -0
  119. package/dist/ports/editor.js +1 -0
  120. package/dist/ports/git.d.ts +8 -0
  121. package/dist/ports/git.js +1 -0
  122. package/dist/ports/history.d.ts +5 -0
  123. package/dist/ports/history.js +1 -0
  124. package/dist/ports/hooks.d.ts +12 -0
  125. package/dist/ports/hooks.js +1 -0
  126. package/dist/{command-runner.d.ts → ports/process.d.ts} +0 -1
  127. package/dist/ports/process.js +1 -0
  128. package/dist/ports/pull-requests.d.ts +11 -0
  129. package/dist/ports/pull-requests.js +1 -0
  130. package/dist/ports/repository.d.ts +17 -0
  131. package/dist/ports/repository.js +1 -0
  132. package/dist/{bootstrap-output.d.ts → presentation/bootstrap/output.d.ts} +1 -1
  133. package/dist/presentation/bootstrap/preview.d.ts +2 -0
  134. package/dist/{bootstrap-preview.js → presentation/bootstrap/preview.js} +0 -4
  135. package/dist/{shell-completion.js → presentation/shell/completion.js} +5 -4
  136. package/dist/presentation/terminal/context-card.d.ts +2 -0
  137. package/dist/{context-card.js → presentation/terminal/context-card.js} +3 -12
  138. package/dist/presentation/worktree/format.d.ts +4 -0
  139. package/dist/presentation/worktree/format.js +41 -0
  140. package/dist/{worktree-picker.d.ts → presentation/worktree/picker.d.ts} +7 -11
  141. package/dist/{worktree-picker.js → presentation/worktree/picker.js} +36 -191
  142. package/man/man1/gji-back.1 +1 -1
  143. package/man/man1/gji-clean.1 +1 -1
  144. package/man/man1/gji-completion.1 +1 -1
  145. package/man/man1/gji-config.1 +1 -1
  146. package/man/man1/gji-doctor.1 +1 -1
  147. package/man/man1/gji-done.1 +1 -1
  148. package/man/man1/gji-go.1 +1 -1
  149. package/man/man1/gji-history.1 +1 -1
  150. package/man/man1/gji-init.1 +1 -1
  151. package/man/man1/gji-ls.1 +1 -1
  152. package/man/man1/gji-new.1 +6 -3
  153. package/man/man1/gji-open.1 +1 -1
  154. package/man/man1/gji-pr.1 +1 -1
  155. package/man/man1/gji-remove.1 +1 -1
  156. package/man/man1/gji-root.1 +1 -1
  157. package/man/man1/gji-run-hook.1 +1 -1
  158. package/man/man1/gji-status.1 +1 -1
  159. package/man/man1/gji-sync-files.1 +1 -1
  160. package/man/man1/gji-sync.1 +1 -1
  161. package/man/man1/gji-task.1 +1 -1
  162. package/man/man1/gji-undo.1 +1 -1
  163. package/man/man1/gji-warp.1 +1 -1
  164. package/man/man1/gji.1 +2 -2
  165. package/package.json +1 -1
  166. package/dist/bootstrap-preview.d.ts +0 -9
  167. package/dist/context-card.d.ts +0 -1
  168. package/dist/format-bytes.d.ts +0 -1
  169. package/dist/format-bytes.js +0 -14
  170. package/dist/git.d.ts +0 -18
  171. package/dist/repo.d.ts +0 -20
  172. package/dist/repo.js +0 -161
  173. package/dist/worktree-bootstrap.d.ts +0 -24
  174. package/dist/worktree-info.d.ts +0 -37
  175. package/dist/worktree-info.js +0 -123
  176. package/dist/worktree-sources.d.ts +0 -4
  177. /package/dist/{worktree-source.js → application/worktree/read-models.js} +0 -0
  178. /package/dist/{completion.d.ts → cli/commands/completion.d.ts} +0 -0
  179. /package/dist/{headless.d.ts → cli/runtime/headless.d.ts} +0 -0
  180. /package/dist/{headless.js → cli/runtime/headless.js} +0 -0
  181. /package/dist/{paths.d.ts → domain/shared/paths.d.ts} +0 -0
  182. /package/dist/{paths.js → domain/shared/paths.js} +0 -0
  183. /package/dist/{file-sync.d.ts → infrastructure/filesystem/file-sync.d.ts} +0 -0
  184. /package/dist/{file-sync.js → infrastructure/filesystem/file-sync.js} +0 -0
  185. /package/dist/{fs-utils.d.ts → infrastructure/filesystem/fs-utils.d.ts} +0 -0
  186. /package/dist/{fs-utils.js → infrastructure/filesystem/fs-utils.js} +0 -0
  187. /package/dist/{safe-destination.d.ts → infrastructure/filesystem/safe-destination.d.ts} +0 -0
  188. /package/dist/{safe-destination.js → infrastructure/filesystem/safe-destination.js} +0 -0
  189. /package/dist/{browser.d.ts → infrastructure/integrations/browser.d.ts} +0 -0
  190. /package/dist/{browser.js → infrastructure/integrations/browser.js} +0 -0
  191. /package/dist/{editor.js → infrastructure/integrations/editor.js} +0 -0
  192. /package/dist/{pull-requests.js → infrastructure/integrations/pull-requests.js} +0 -0
  193. /package/dist/{history.js → infrastructure/persistence/history.js} +0 -0
  194. /package/dist/{slots.d.ts → infrastructure/persistence/slots.d.ts} +0 -0
  195. /package/dist/{slots.js → infrastructure/persistence/slots.js} +0 -0
  196. /package/dist/{task.d.ts → infrastructure/persistence/task.d.ts} +0 -0
  197. /package/dist/{command-runner.js → infrastructure/process/command-runner.js} +0 -0
  198. /package/dist/{hooks.js → infrastructure/process/hooks.js} +0 -0
  199. /package/dist/{bootstrap-output.js → presentation/bootstrap/output.js} +0 -0
  200. /package/dist/{conflict.d.ts → presentation/prompts/path-conflict.d.ts} +0 -0
  201. /package/dist/{conflict.js → presentation/prompts/path-conflict.js} +0 -0
  202. /package/dist/{shell-completion.d.ts → presentation/shell/completion.d.ts} +0 -0
  203. /package/dist/{shell-handoff.d.ts → presentation/shell/handoff.d.ts} +0 -0
  204. /package/dist/{shell-handoff.js → presentation/shell/handoff.js} +0 -0
  205. /package/dist/{shell-setup.d.ts → presentation/shell/setup.d.ts} +0 -0
  206. /package/dist/{shell-setup.js → presentation/shell/setup.js} +0 -0
  207. /package/dist/{shell.d.ts → presentation/shell/shell.d.ts} +0 -0
  208. /package/dist/{shell.js → presentation/shell/shell.js} +0 -0
  209. /package/dist/{navigation-output.d.ts → presentation/terminal/navigation.d.ts} +0 -0
  210. /package/dist/{navigation-output.js → presentation/terminal/navigation.js} +0 -0
  211. /package/dist/{terminal-text.d.ts → presentation/terminal/text.d.ts} +0 -0
  212. /package/dist/{terminal-text.js → presentation/terminal/text.js} +0 -0
  213. /package/dist/{worktree-prompts.d.ts → presentation/worktree/prompts.d.ts} +0 -0
  214. /package/dist/{worktree-prompts.js → presentation/worktree/prompts.js} +0 -0
@@ -1,15 +1,10 @@
1
1
  import { env, platform, stdin, stdout } from "node:process";
2
2
  import { isCancel, Prompt } from "@clack/core";
3
3
  import { spinner } from "@clack/prompts";
4
- import { loadHistory } from "./history.js";
5
- import { createPullRequestQuery, } from "./pull-requests.js";
6
- import { readTask } from "./task.js";
7
- import { middleEllipsize, sanitizeTerminalText, startEllipsize, terminalWidth, } from "./terminal-text.js";
8
- import { readWorktreeInfos, } from "./worktree-info.js";
9
- const MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY = 4;
10
- const MAX_TASK_READ_CONCURRENCY = 8;
4
+ import { loadWorktreeCatalog, } from "../../application/worktree/catalog.js";
5
+ import { middleEllipsize, sanitizeTerminalText, startEllipsize, terminalWidth, } from "../terminal/text.js";
11
6
  export async function buildWorktreePromptEntries(sources, dependencies = {}) {
12
- const loading = stdin.isTTY === true && stdout.isTTY === true
7
+ const loading = stdin.isTTY === true && stdout.isTTY === true && stdin.isRaw !== true
13
8
  ? spinner({ indicator: "timer" })
14
9
  : null;
15
10
  loading?.start("Loading worktrees");
@@ -22,91 +17,14 @@ export async function buildWorktreePromptEntries(sources, dependencies = {}) {
22
17
  }
23
18
  async function buildWorktreePromptEntriesWithoutLoading(sources, dependencies) {
24
19
  const metadataMode = dependencies.metadata ?? "full";
25
- const includeMetadata = metadataMode === "full";
26
- const pullRequestQuery = createPullRequestQuery();
27
- const queryPullRequests = dependencies.queryPullRequests ?? pullRequestQuery.listOpenPullRequests;
28
- const queryRepositoryPullRequests = includeMetadata
29
- ? (dependencies.queryRepositoryPullRequests ??
30
- (dependencies.queryPullRequests === undefined
31
- ? pullRequestQuery.listOpenPullRequestsForRepository
32
- : undefined))
33
- : undefined;
34
- const repositoryPullRequests = queryRepositoryPullRequests === undefined
35
- ? Promise.resolve(null)
36
- : readRepositoryPullRequests(sources, queryRepositoryPullRequests);
37
- const [history, infos, pullRequestsByRepository] = await Promise.all([
38
- loadHistory(),
39
- includeMetadata
40
- ? readWorktreeInfos(sources.map((source) => source.worktree))
41
- : mapWithConcurrency(sources, MAX_TASK_READ_CONCURRENCY, (source) => createUnhydratedWorktreeInfo(source.worktree)),
42
- repositoryPullRequests,
43
- ]);
44
- const pullRequests = includeMetadata
45
- ? await Promise.all(sources.map((source) => readSourcePullRequests(source, queryPullRequests, pullRequestsByRepository)))
46
- : sources.map(() => []);
47
- const historyByPath = new Map(history.map((entry) => [entry.path, entry]));
48
- const entries = sources.map((source, index) => buildWorktreePromptEntry(source, infos[index], pullRequests[index], historyByPath.get(source.worktree.path)?.timestamp ?? null, Date.now()));
20
+ const hydrated = await loadWorktreeCatalog(sources, metadataMode, dependencies.catalog ?? missingCatalogDependencies());
21
+ const entries = hydrated.map(({ info, lastUsedTimestamp, pullRequests, source }) => buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp, Date.now()));
49
22
  return entries
50
23
  .sort(comparePromptEntries)
51
24
  .map(({ lastActivityTimestamp: _lastActivityTimestamp, ...entry }) => entry);
52
25
  }
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
- }
61
- return {
62
- ...worktree,
63
- lastCommitTimestamp: null,
64
- slot: null,
65
- status: "unknown",
66
- task,
67
- upstream: { kind: "unknown" },
68
- };
69
- }
70
- export function resolveWorktreeQuery(sources, query) {
71
- return resolveWorktreeQueryMatches(sources, query)[0] ?? null;
72
- }
73
- export function resolveWorktreeQueryMatches(sources, query) {
74
- const normalizedQuery = normalizeQuery(query);
75
- if (normalizedQuery === null)
76
- return [];
77
- const matches = findWorktreePromptSourceMatches(sources, normalizedQuery);
78
- if (isAmbiguousRepoOnlyQuery(matches, normalizedQuery))
79
- return [];
80
- const bestScore = matches[0]?.matchScore;
81
- return matches
82
- .filter((match) => match.matchScore === bestScore)
83
- .map((match) => match.source);
84
- }
85
- export function resolveExactWorktreeQueryMatches(sources, query) {
86
- const normalizedQuery = normalizeQuery(query);
87
- if (normalizedQuery === null)
88
- return [];
89
- return sources.filter((source) => scoreWorktreeMatch({
90
- ...source.worktree,
91
- repoName: source.repoName,
92
- }, normalizedQuery) === 1000);
93
- }
94
- function findWorktreePromptSourceMatches(sources, normalizedQuery) {
95
- return sources
96
- .flatMap((source) => {
97
- const matchScore = scoreWorktreeMatch({
98
- ...source.worktree,
99
- repoName: source.repoName,
100
- }, normalizedQuery);
101
- return matchScore === null ? [] : [{ matchScore, source }];
102
- })
103
- .sort(compareQueryMatches);
104
- }
105
- function isAmbiguousRepoOnlyQuery(matches, query) {
106
- if (matches[0]?.matchScore === 1000)
107
- return false;
108
- return (matches.filter((match) => match.source.repoName.toLowerCase() === query)
109
- .length > 1);
26
+ function missingCatalogDependencies() {
27
+ throw new Error("Worktree picker hydration must be supplied by the application composition root");
110
28
  }
111
29
  export async function promptForSingleWorktree(message, worktrees, io = {}) {
112
30
  const choice = await runSearchablePrompt({
@@ -129,18 +47,6 @@ export async function promptForMultipleWorktrees(message, worktrees, io = {}) {
129
47
  });
130
48
  return Array.isArray(choice) ? choice : null;
131
49
  }
132
- function compareQueryMatches(a, b) {
133
- if (a.matchScore !== b.matchScore) {
134
- return b.matchScore - a.matchScore;
135
- }
136
- if (a.source.worktree.isCurrent && !b.source.worktree.isCurrent)
137
- return -1;
138
- if (!a.source.worktree.isCurrent && b.source.worktree.isCurrent)
139
- return 1;
140
- return (a.source.repoName.localeCompare(b.source.repoName) ||
141
- (a.source.worktree.branch ?? "").localeCompare(b.source.worktree.branch ?? "") ||
142
- a.source.worktree.path.localeCompare(b.source.worktree.path));
143
- }
144
50
  function groupPromptEntries(worktrees) {
145
51
  const groups = new Map();
146
52
  for (const worktree of worktrees) {
@@ -214,6 +120,7 @@ class SearchablePrompt {
214
120
  selected = new Set();
215
121
  scope;
216
122
  scopeTogglePending = false;
123
+ promptClosed = false;
217
124
  prompt;
218
125
  constructor(options, input, output) {
219
126
  this.options = options;
@@ -244,6 +151,8 @@ class SearchablePrompt {
244
151
  this.cancel(prompt);
245
152
  return;
246
153
  }
154
+ if (this.scopeTogglePending)
155
+ return;
247
156
  if (action === "escape") {
248
157
  this.handleEscapeKey(prompt);
249
158
  return;
@@ -256,12 +165,12 @@ class SearchablePrompt {
256
165
  void this.toggleScope(prompt);
257
166
  return;
258
167
  }
259
- if (this.searchActive && this.handleSearchKey(character, key?.name)) {
168
+ if (this.searchActive && this.handleSearchKey(character, key)) {
260
169
  this.syncValue();
261
170
  renderPrompt(prompt);
262
171
  return;
263
172
  }
264
- if (character === "/" && !this.searchActive) {
173
+ if (isSearchToggleKey(character, key) && !this.searchActive) {
265
174
  this.searchActive = true;
266
175
  this.query = "";
267
176
  this.cursor = this.firstSelectableIndex();
@@ -281,6 +190,8 @@ class SearchablePrompt {
281
190
  renderPrompt(prompt);
282
191
  try {
283
192
  const nextScope = await currentScope.toggle();
193
+ if (this.promptClosed)
194
+ return;
284
195
  this.entries = nextScope.entries.map(buildSearchableWorktreeEntry);
285
196
  this.scope = {
286
197
  label: nextScope.label,
@@ -292,13 +203,16 @@ class SearchablePrompt {
292
203
  this.syncValue();
293
204
  }
294
205
  catch (error) {
206
+ if (this.promptClosed)
207
+ return;
295
208
  prompt.error =
296
209
  error instanceof Error ? error.message : "Could not load worktrees";
297
210
  prompt.state = "error";
298
211
  }
299
212
  finally {
300
213
  this.scopeTogglePending = false;
301
- renderPrompt(prompt);
214
+ if (!this.promptClosed)
215
+ renderPrompt(prompt);
302
216
  }
303
217
  }
304
218
  handleEscapeKey(prompt) {
@@ -313,12 +227,15 @@ class SearchablePrompt {
313
227
  this.cancel(prompt);
314
228
  }
315
229
  cancel(prompt) {
230
+ this.promptClosed = true;
316
231
  prompt.state = "cancel";
317
232
  renderPrompt(prompt);
318
233
  closePrompt(prompt);
319
234
  }
320
- handleSearchKey(character, keyName) {
321
- if (keyName === "space" || character === " ") {
235
+ handleSearchKey(character, key) {
236
+ const keyName = key?.name;
237
+ const input = character ?? key?.sequence;
238
+ if (keyName === "space" || input === " ") {
322
239
  return false;
323
240
  }
324
241
  if (keyName === "backspace" || keyName === "delete") {
@@ -326,8 +243,8 @@ class SearchablePrompt {
326
243
  this.cursor = this.firstSelectableIndex();
327
244
  return true;
328
245
  }
329
- if (isPrintableSearchCharacter(character)) {
330
- this.query += character;
246
+ if (isPrintableSearchCharacter(input)) {
247
+ this.query += input;
331
248
  this.cursor = this.firstSelectableIndex();
332
249
  return true;
333
250
  }
@@ -373,6 +290,7 @@ class SearchablePrompt {
373
290
  submit(prompt) {
374
291
  const entry = this.visibleEntries()[this.cursor];
375
292
  if (!this.options.multiple) {
293
+ this.promptClosed = true;
376
294
  prompt.value = isSelectableEntry(entry) ? entry.value : null;
377
295
  prompt.state = "submit";
378
296
  renderPrompt(prompt);
@@ -386,6 +304,7 @@ class SearchablePrompt {
386
304
  return;
387
305
  }
388
306
  prompt.value = [...this.selected];
307
+ this.promptClosed = true;
389
308
  prompt.state = "submit";
390
309
  renderPrompt(prompt);
391
310
  closePrompt(prompt);
@@ -592,7 +511,7 @@ class SearchablePrompt {
592
511
  return index === -1 ? 0 : index;
593
512
  }
594
513
  visibleEntries() {
595
- const query = normalizeQuery(this.query);
514
+ const query = normalizePromptQuery(this.query);
596
515
  if (query === null) {
597
516
  return this.entries;
598
517
  }
@@ -640,7 +559,10 @@ function onWorktreeKeypress(character, key) {
640
559
  this.worktreePrompt.handleKeypress(this, character, key);
641
560
  }
642
561
  function isScopeToggleKey(character, key) {
643
- return key?.name === "tab" || character === "\t";
562
+ return key?.name === "tab" || character === "\t" || key?.sequence === "\t";
563
+ }
564
+ function isSearchToggleKey(character, key) {
565
+ return character === "/" || key?.sequence === "/";
644
566
  }
645
567
  function resolvePromptAction(character, key) {
646
568
  if ((key?.ctrl && key.name === "c") || character === "\u0003") {
@@ -924,61 +846,6 @@ function buildWorktreePromptEntry(source, info, pullRequests, lastUsedTimestamp,
924
846
  task: info.task,
925
847
  };
926
848
  }
927
- async function readSourcePullRequests(source, queryPullRequests, pullRequestsByRepository) {
928
- if (source.repoRoot === undefined || source.worktree.branch === null) {
929
- return [];
930
- }
931
- if (pullRequestsByRepository !== null) {
932
- return sortSourcePullRequests(pullRequestsByRepository
933
- .get(source.repoRoot)
934
- ?.filter((pullRequest) => pullRequest.sourceBranch === source.worktree.branch));
935
- }
936
- try {
937
- const pullRequests = await queryPullRequests(source.repoRoot, source.worktree.branch);
938
- return sortSourcePullRequests(pullRequests);
939
- }
940
- catch {
941
- // PR metadata is optional selector decoration; preserve the worktree entry on lookup failures.
942
- return [];
943
- }
944
- }
945
- async function readRepositoryPullRequests(sources, queryPullRequests) {
946
- const repoRoots = [
947
- ...new Set(sources.flatMap((source) => source.repoRoot === undefined || source.worktree.branch === null
948
- ? []
949
- : [source.repoRoot])),
950
- ];
951
- const results = await mapWithConcurrency(repoRoots, MAX_PULL_REQUEST_REPOSITORY_QUERY_CONCURRENCY, async (repoRoot) => {
952
- try {
953
- return [
954
- repoRoot,
955
- sortSourcePullRequests(await queryPullRequests(repoRoot)),
956
- ];
957
- }
958
- catch {
959
- return [repoRoot, []];
960
- }
961
- });
962
- return new Map(results);
963
- }
964
- async function mapWithConcurrency(items, limit, mapper) {
965
- const results = new Array(items.length);
966
- let nextIndex = 0;
967
- async function readNext() {
968
- for (;;) {
969
- const index = nextIndex;
970
- nextIndex += 1;
971
- if (index >= items.length)
972
- return;
973
- results[index] = await mapper(items[index]);
974
- }
975
- }
976
- await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => readNext()));
977
- return results;
978
- }
979
- function sortSourcePullRequests(pullRequests) {
980
- return [...(pullRequests ?? [])].sort((a, b) => a.number - b.number);
981
- }
982
849
  export function formatWorktreeBranchLabel(branch, pullRequests = []) {
983
850
  const branchLabel = branch ?? "(detached)";
984
851
  if (pullRequests.length === 0)
@@ -1048,20 +915,6 @@ function isYesterday(timestamp, now) {
1048
915
  date.getMonth() === yesterday.getMonth() &&
1049
916
  date.getDate() === yesterday.getDate());
1050
917
  }
1051
- function buildSearchText(repoName, worktree) {
1052
- return [
1053
- repoName,
1054
- worktree.branch ?? "detached",
1055
- worktree.path,
1056
- `${repoName}/${worktree.branch ?? "detached"}`,
1057
- ]
1058
- .join(" ")
1059
- .toLowerCase();
1060
- }
1061
- function normalizeQuery(query) {
1062
- const normalized = query?.trim().toLowerCase();
1063
- return normalized && normalized.length > 0 ? normalized : null;
1064
- }
1065
918
  function comparePromptEntries(a, b) {
1066
919
  if (a.isCurrent && !b.isCurrent)
1067
920
  return -1;
@@ -1079,18 +932,10 @@ function comparePromptEntries(a, b) {
1079
932
  (a.branch ?? "").localeCompare(b.branch ?? "") ||
1080
933
  a.path.localeCompare(b.path));
1081
934
  }
935
+ function normalizePromptQuery(query) {
936
+ const normalized = query?.trim().toLowerCase();
937
+ return normalized && normalized.length > 0 ? normalized : null;
938
+ }
1082
939
  function groupRank(group) {
1083
940
  return group === "recent" ? 0 : 1;
1084
941
  }
1085
- function scoreWorktreeMatch(entry, query) {
1086
- const branch = entry.branch ?? "detached";
1087
- const exactCandidates = [
1088
- branch,
1089
- entry.path,
1090
- `${entry.repoName}/${branch}`,
1091
- ].map((candidate) => candidate.toLowerCase());
1092
- if (exactCandidates.includes(query)) {
1093
- return 1000;
1094
- }
1095
- return buildSearchText(entry.repoName, entry).includes(query) ? 1 : null;
1096
- }
@@ -1,4 +1,4 @@
1
- .TH GJI\-BACK 1 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-BACK 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-CLEAN 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-COMPLETION 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-CONFIG 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-DOCTOR 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-DONE 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-GO 1 "August 2026" "gji 0.13.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-go \- resolve and jump to a worktree path
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-HISTORY 1 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-HISTORY 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-INIT 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-LS 1 "August 2026" "gji 0.13.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-ls \- list active worktrees
4
4
  .SH SYNOPSIS
@@ -1,10 +1,10 @@
1
- .TH GJI\-NEW 1 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-NEW 1 "August 2026" "gji 0.13.0" "User Commands"
2
2
  .SH NAME
3
- gji\-new \- create a new branch or detached linked worktree
3
+ gji\-new \- create a branch with an optional linked worktree
4
4
  .SH SYNOPSIS
5
5
  .B gji new [\fIoptions\fR] [options] [branch]
6
6
  .SH DESCRIPTION
7
- create a new branch or detached linked worktree
7
+ create a branch with an optional linked worktree
8
8
  .SH BOOTSTRAP_POLICY
9
9
  dependencyBuildCommand overrides Cargo's default cargo check. Supported lockfiles are installed automatically in new worktrees; set dependencyBootstrap to off or pass \-\-no\-install to skip setup. Sync\-file failures stop dependency installation and after\-create hooks. JSON bootstrap events include structured reasons, and failed setup reports the created worktree path.
10
10
 
@@ -13,6 +13,9 @@ Dependency bootstrap never copies dependency trees or build caches. pnpm, Yarn,
13
13
 
14
14
  .SH OPTIONS
15
15
  .TP
16
+ .B \-\-branch\-only
17
+ update the default branch and create the new branch in the current worktree
18
+ .TP
16
19
  .B \-f, \-\-force
17
20
  remove and recreate the worktree if the target path already exists
18
21
  .TP
@@ -1,4 +1,4 @@
1
- .TH GJI\-OPEN 1 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-OPEN 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-PR 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-REMOVE 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-ROOT 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-RUN\-HOOK 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-STATUS 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-SYNC\-FILES 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-SYNC 1 "August 2026" "gji 0.13.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\-TASK 1 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-TASK 1 "August 2026" "gji 0.13.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-task \- show or update the current worktree task
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-UNDO 1 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-UNDO 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI\-WARP 1 "August 2026" "gji 0.13.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 "August 2026" "gji 0.12.4" "User Commands"
1
+ .TH GJI 1 "August 2026" "gji 0.13.0" "User Commands"
2
2
  .SH NAME
3
3
  gji \- Context switching without the mess.
4
4
  .SH SYNOPSIS
@@ -12,7 +12,7 @@ eliminating the need for \fBgit stash\fR when switching contexts.
12
12
  .SH COMMANDS
13
13
  .TP
14
14
  .B new [options] [branch]
15
- create a new branch or detached linked worktree
15
+ create a branch with an optional linked worktree
16
16
  .TP
17
17
  .B done [options] [branch]
18
18
  finish the current linked worktree and return safely
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solaqua/gji",
3
- "version": "0.12.4",
3
+ "version": "0.13.0",
4
4
  "description": "Git worktree CLI for fast context switching.",
5
5
  "license": "MIT",
6
6
  "author": "sjquant",
@@ -1,9 +0,0 @@
1
- import type { DependencyBootstrapMode } from "./dependency-bootstrap.js";
2
- import { type DependencyBootstrapPreview } from "./dependency-bootstrap.js";
3
- export declare function createDependencyBootstrapPreview(mode: DependencyBootstrapMode, context: {
4
- repoRoot: string;
5
- currentRoot?: string;
6
- worktreePath: string;
7
- cargoBuildCommand?: string;
8
- }): Promise<DependencyBootstrapPreview>;
9
- export declare function formatDependencyBootstrapPreview(preview: DependencyBootstrapPreview | undefined): string;
@@ -1 +0,0 @@
1
- export declare function renderContextCard(worktreePath: string): Promise<string | null>;
@@ -1 +0,0 @@
1
- export declare function formatBytes(bytes: number | undefined): string;
@@ -1,14 +0,0 @@
1
- export function formatBytes(bytes) {
2
- if (bytes === undefined)
3
- return "size unavailable";
4
- if (bytes < 1024)
5
- return `${bytes} B`;
6
- const units = ["KB", "MB", "GB", "TB"];
7
- let value = bytes;
8
- let unit = -1;
9
- while (value >= 1024 && unit < units.length - 1) {
10
- value /= 1024;
11
- unit += 1;
12
- }
13
- return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unit]}`;
14
- }
package/dist/git.d.ts DELETED
@@ -1,18 +0,0 @@
1
- export interface WorktreeHealth {
2
- ahead: number;
3
- behind: number;
4
- hasUpstream: boolean;
5
- upstreamGone: boolean;
6
- status: "clean" | "dirty";
7
- }
8
- export interface RemoteBase {
9
- branch: string;
10
- ref: string;
11
- }
12
- export declare function runGit(cwd: string, args: string[]): Promise<string>;
13
- export declare function readWorktreeHealth(cwd: string): Promise<WorktreeHealth>;
14
- export declare function isDirtyWorktree(cwd: string): Promise<boolean>;
15
- export declare function isBranchMergedInto(cwd: string, branch: string, base?: string): Promise<boolean>;
16
- export declare function resolveRemoteDefaultBranch(cwd: string, remote: string): Promise<string | null>;
17
- export declare function resolveRemoteBase(cwd: string, remote: string, configuredBranch?: string): Promise<RemoteBase | null>;
18
- export declare function readBranchLastCommitTimestamp(cwd: string, branch: string): Promise<number | null>;
package/dist/repo.d.ts DELETED
@@ -1,20 +0,0 @@
1
- export interface RepositoryContext {
2
- currentRoot: string;
3
- gitCommonDir: string;
4
- isWorktree: boolean;
5
- repoName: string;
6
- repoRoot: string;
7
- }
8
- export interface WorktreeEntry {
9
- branch: string | null;
10
- isCurrent: boolean;
11
- path: string;
12
- }
13
- export declare function detectRepository(cwd: string): Promise<RepositoryContext>;
14
- export declare function worktreeGitDir(worktreePath: string): Promise<string>;
15
- export declare function resolveWorktreePath(repoRoot: string, branch: string, basePath?: string): string;
16
- export declare function validateBranchName(name: string): string | null;
17
- export declare function hasLocalBranch(repoRoot: string, branch: string): Promise<boolean>;
18
- export declare function hasRemoteBranch(repoRoot: string, remote: string, branch: string): Promise<boolean>;
19
- export declare function getRepositoryRemoteUrl(repoRoot: string, remote: string): Promise<string | null>;
20
- export declare function listWorktrees(cwd: string): Promise<WorktreeEntry[]>;