@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,30 +1,29 @@
1
1
  import { basename } from "node:path";
2
2
  import { confirm, isCancel } from "@clack/prompts";
3
+ import { loadContextCardModel } from "../../application/worktree/context-card.js";
4
+ import { resolveGoBranch, } from "../../application/worktree/go-resolution.js";
5
+ import { listRegisteredWorktreeSources } from "../../application/worktree/sources.js";
6
+ import { writeShellOutput } from "../../presentation/shell/handoff.js";
7
+ import { renderContextCard } from "../../presentation/terminal/context-card.js";
8
+ import { createNavigationRepository, createNavigationTarget, } from "../../presentation/terminal/navigation.js";
9
+ import { buildWorktreePromptEntries, promptForSingleWorktree, } from "../../presentation/worktree/picker.js";
10
+ import { defaultCliDependencies, withPullRequestQueries, } from "../dependencies.js";
11
+ import { isHeadless } from "../runtime/headless.js";
3
12
  import { runBackCommand } from "./back.js";
4
- import { loadEffectiveConfig } from "./config.js";
5
- import { renderContextCard } from "./context-card.js";
6
- import { resolveGoBranch } from "./go-resolver.js";
7
- import { isHeadless } from "./headless.js";
8
- import { recordWorktreeUsage } from "./history.js";
9
- import { extractHooks, runHook } from "./hooks.js";
10
- import { createNavigationRepository, createNavigationTarget, } from "./navigation-output.js";
11
13
  import { runNewCommand } from "./new.js";
12
14
  import { runPrCommand } from "./pr.js";
13
- import { detectRepository, listWorktrees, } from "./repo.js";
14
- import { writeShellOutput } from "./shell-handoff.js";
15
- import { getWorktreeSlot } from "./slots.js";
16
- import { buildWorktreePromptEntries, promptForSingleWorktree, } from "./worktree-picker.js";
17
- import { listRegisteredWorktreeSources } from "./worktree-sources.js";
18
15
  const GO_OUTPUT_FILE_ENV = "GJI_GO_OUTPUT_FILE";
19
16
  export function createGoCommand(dependencies = {}) {
20
17
  const confirmBranchCreation = dependencies.confirmBranchCreation ?? defaultConfirmBranchCreation;
21
18
  const prompt = dependencies.promptForWorktree ?? promptForWorktree;
19
+ const configuredRuntime = dependencies.runtime;
22
20
  return async function runGoCommand(options) {
21
+ const runtime = configuredRuntime ?? options.runtime ?? defaultCliDependencies;
23
22
  if (options.root) {
24
23
  if (options.branch !== undefined) {
25
24
  return emitError(options, "--root cannot be combined with a branch");
26
25
  }
27
- return navigateToRepositoryRoot(options);
26
+ return navigateToRepositoryRoot(options, runtime);
28
27
  }
29
28
  if (options.branch === "-") {
30
29
  return runBackCommand({
@@ -34,9 +33,10 @@ export function createGoCommand(dependencies = {}) {
34
33
  outputEnv: GO_OUTPUT_FILE_ENV,
35
34
  stderr: options.stderr,
36
35
  stdout: options.stdout,
36
+ runtime,
37
37
  });
38
38
  }
39
- const [repository, currentWorktrees] = await readCurrentRepository(options.cwd);
39
+ const [repository, currentWorktrees] = await readCurrentRepository(options.cwd, runtime);
40
40
  const currentSources = repository
41
41
  ? toPromptSources(repository.repoRoot, repository.repoName, currentWorktrees)
42
42
  : [];
@@ -49,7 +49,11 @@ export function createGoCommand(dependencies = {}) {
49
49
  let promptSources = currentSources;
50
50
  const loadAllSources = async () => {
51
51
  if (registeredSources === null) {
52
- registeredSources = await listRegisteredWorktreeSources(options.cwd, () => {
52
+ registeredSources = await listRegisteredWorktreeSources(options.cwd, {
53
+ ...runtime.repositoryContext,
54
+ ...runtime.repositoryRegistry,
55
+ ...runtime.worktrees,
56
+ }, () => {
53
57
  skippedRegisteredRepos++;
54
58
  });
55
59
  }
@@ -77,7 +81,7 @@ export function createGoCommand(dependencies = {}) {
77
81
  return {
78
82
  entries: await buildWorktreePromptEntries(promptSources, {
79
83
  metadata: currentRepositoryScope ? "full" : "fast",
80
- queryPullRequests: dependencies.queryPullRequests,
84
+ catalog: withPullRequestQueries(runtime, dependencies.queryPullRequests),
81
85
  }),
82
86
  label: currentRepositoryScope
83
87
  ? "current repository"
@@ -91,7 +95,7 @@ export function createGoCommand(dependencies = {}) {
91
95
  }
92
96
  const promptEntries = await buildWorktreePromptEntries(promptSources, {
93
97
  metadata: repository ? "full" : "fast",
94
- queryPullRequests: dependencies.queryPullRequests,
98
+ catalog: withPullRequestQueries(runtime, dependencies.queryPullRequests),
95
99
  });
96
100
  const selectedPath = await prompt(promptEntries, scope);
97
101
  if (!selectedPath) {
@@ -99,30 +103,40 @@ export function createGoCommand(dependencies = {}) {
99
103
  return 1;
100
104
  }
101
105
  const selected = promptSources.find((source) => source.worktree.path === selectedPath);
102
- return navigateToExistingWorktree(options, selectedPath, selected?.worktree);
106
+ return navigateToExistingWorktree(options, selectedPath, selected?.worktree, runtime);
103
107
  }
104
108
  const resolution = await resolveGoBranch({
105
109
  branch: options.branch,
106
- configStderr: options.json ? undefined : options.stderr,
107
110
  cwd: options.cwd,
108
111
  currentSources,
109
112
  repository,
113
+ repositoryPort: runtime.repositoryRefs,
114
+ sourceDependencies: {
115
+ ...runtime.repositoryContext,
116
+ ...runtime.repositoryRegistry,
117
+ ...runtime.worktrees,
118
+ },
119
+ configPort: runtime.config,
110
120
  });
111
- return handleGoBranchResolution(options, resolution, confirmBranchCreation, prompt, dependencies.queryPullRequests);
121
+ return handleGoBranchResolution(options, resolution, confirmBranchCreation, prompt, runtime, dependencies.queryPullRequests);
112
122
  };
113
123
  }
114
124
  export const runGoCommand = createGoCommand();
115
- async function handleGoBranchResolution(options, resolution, confirmBranchCreation, prompt, queryPullRequests) {
125
+ async function handleGoBranchResolution(options, resolution, confirmBranchCreation, prompt, runtime, queryPullRequests) {
126
+ if (!options.json) {
127
+ for (const warning of resolution.configWarnings ?? [])
128
+ options.stderr(warning);
129
+ }
116
130
  switch (resolution.kind) {
117
131
  case "existing":
118
- return navigateToExistingWorktree(options, resolution.source.worktree.path, resolution.source.worktree);
132
+ return navigateToExistingWorktree(options, resolution.source.worktree.path, resolution.source.worktree, runtime);
119
133
  case "ambiguous": {
120
134
  if (options.json || isHeadless() || options.print) {
121
135
  return emitAmbiguousCrossRepoError(options, resolution.matches);
122
136
  }
123
137
  const candidates = await buildWorktreePromptEntries(resolution.matches, {
124
138
  metadata: "fast",
125
- queryPullRequests,
139
+ catalog: withPullRequestQueries(runtime, queryPullRequests),
126
140
  });
127
141
  const selectedPath = await prompt(candidates);
128
142
  if (!selectedPath) {
@@ -130,10 +144,10 @@ async function handleGoBranchResolution(options, resolution, confirmBranchCreati
130
144
  return 1;
131
145
  }
132
146
  const selected = resolution.matches.find((source) => source.worktree.path === selectedPath);
133
- return navigateToExistingWorktree(options, selectedPath, selected?.worktree);
147
+ return navigateToExistingWorktree(options, selectedPath, selected?.worktree, runtime);
134
148
  }
135
149
  case "create":
136
- return createExistingBranchWorktree(options, confirmBranchCreation, resolution.repository, resolution.branch, resolution.mode, resolution.remote);
150
+ return createExistingBranchWorktree(options, confirmBranchCreation, resolution.repository, resolution.branch, resolution.mode, resolution.remote, runtime);
137
151
  case "pull-request":
138
152
  if (options.json || isHeadless() || options.print) {
139
153
  return emitError(options, "PR navigation creates a worktree; use `gji pr <ref>` in an interactive shell");
@@ -144,6 +158,7 @@ async function handleGoBranchResolution(options, resolution, confirmBranchCreati
144
158
  outputEnv: GO_OUTPUT_FILE_ENV,
145
159
  stderr: options.stderr,
146
160
  stdout: options.stdout,
161
+ runtime,
147
162
  });
148
163
  case "no-repository":
149
164
  return emitNoRepositoryError(options, resolution.staleRegisteredRepos);
@@ -153,11 +168,11 @@ async function handleGoBranchResolution(options, resolution, confirmBranchCreati
153
168
  return emitError(options, resolution.message);
154
169
  }
155
170
  }
156
- async function readCurrentRepository(cwd) {
171
+ async function readCurrentRepository(cwd, runtime) {
157
172
  try {
158
173
  const [repository, worktrees] = await Promise.all([
159
- detectRepository(cwd),
160
- listWorktrees(cwd),
174
+ runtime.repositoryContext.detectRepository(cwd),
175
+ runtime.worktrees.listWorktrees(cwd),
161
176
  ]);
162
177
  return [repository, worktrees];
163
178
  }
@@ -179,7 +194,7 @@ function deduplicateSources(sources) {
179
194
  }
180
195
  return deduplicated;
181
196
  }
182
- async function createExistingBranchWorktree(options, confirmBranchCreation, repository, branch, mode, remote) {
197
+ async function createExistingBranchWorktree(options, confirmBranchCreation, repository, branch, mode, remote, runtime) {
183
198
  if (options.json || isHeadless() || options.print) {
184
199
  return emitError(options, `branch "${branch}" exists but has no worktree; use interactive gji go ${branch} to create one`);
185
200
  }
@@ -195,41 +210,42 @@ async function createExistingBranchWorktree(options, confirmBranchCreation, repo
195
210
  remote,
196
211
  stderr: options.stderr,
197
212
  stdout: options.stdout,
213
+ runtime,
198
214
  });
199
215
  }
200
- async function navigateToExistingWorktree(options, path, worktree) {
201
- const repository = await detectRepository(path);
216
+ async function navigateToExistingWorktree(options, path, worktree, runtime) {
217
+ const repository = await runtime.repositoryContext.detectRepository(path);
202
218
  if (options.json) {
203
219
  options.stdout(`${JSON.stringify(createNavigationTarget(createNavigationRepository(repository.repoName, repository.repoRoot), path, worktree?.branch ?? null), null, 2)}\n`);
204
220
  return 0;
205
221
  }
206
- const config = await loadEffectiveConfig(repository.repoRoot, undefined, options.stderr);
207
- const hooks = extractHooks(config);
208
- await runHook(hooks["after-enter"], path, {
222
+ const config = await runtime.config.loadEffectiveConfig(repository.repoRoot, undefined, options.stderr);
223
+ const hooks = runtime.hooks.extractHooks(config);
224
+ await runtime.hooks.runHook(hooks["after-enter"], path, {
209
225
  branch: worktree?.branch ?? undefined,
210
226
  path,
211
227
  repo: basename(repository.repoRoot),
212
- slot: await getWorktreeSlot(path),
228
+ slot: await runtime.slots.getWorktreeSlot(path),
213
229
  }, options.stderr);
214
230
  if (!options.quiet) {
215
- const card = await renderContextCard(path);
231
+ const card = await loadContextCardModel(path, runtime.contextCard);
216
232
  if (card)
217
- options.stderr(`${card}\n`);
233
+ options.stderr(`${renderContextCard(card)}\n`);
218
234
  }
219
- await recordWorktreeUsage(path, worktree?.branch ?? null);
235
+ await runtime.history.recordWorktreeUsage(path, worktree?.branch ?? null);
220
236
  await writeShellOutput(GO_OUTPUT_FILE_ENV, path, options.stdout);
221
237
  return 0;
222
238
  }
223
- async function navigateToRepositoryRoot(options) {
239
+ async function navigateToRepositoryRoot(options, runtime) {
224
240
  let repository;
225
241
  try {
226
- repository = await detectRepository(options.cwd);
242
+ repository = await runtime.repositoryContext.detectRepository(options.cwd);
227
243
  }
228
244
  catch {
229
245
  return emitError(options, "not inside a git repository");
230
246
  }
231
247
  if (options.json) {
232
- const rootWorktree = (await listWorktrees(repository.repoRoot)).find((worktree) => worktree.path === repository.repoRoot);
248
+ const rootWorktree = (await runtime.worktrees.listWorktrees(repository.repoRoot)).find((worktree) => worktree.path === repository.repoRoot);
233
249
  options.stdout(`${JSON.stringify(createNavigationTarget(createNavigationRepository(repository.repoName, repository.repoRoot), repository.repoRoot, rootWorktree?.branch ?? null), null, 2)}\n`);
234
250
  return 0;
235
251
  }
@@ -1,7 +1,9 @@
1
+ import { type CliRuntime } from "../dependencies.js";
1
2
  export interface HistoryCommandOptions {
2
3
  cwd: string;
3
4
  home?: string;
4
5
  json?: boolean;
6
+ runtime?: CliRuntime<"historyStore">;
5
7
  stdout: (chunk: string) => void;
6
8
  }
7
9
  export declare function runHistoryCommand(options: HistoryCommandOptions): Promise<number>;
@@ -1,6 +1,8 @@
1
+ import { defaultCliDependencies } from "../dependencies.js";
1
2
  import { formatHistoryList } from "./back.js";
2
- import { loadHistory } from "./history.js";
3
3
  export async function runHistoryCommand(options) {
4
+ const { loadHistory } = (options.runtime ?? defaultCliDependencies)
5
+ .historyStore;
4
6
  const history = await loadHistory(options.home);
5
7
  if (options.json) {
6
8
  options.stdout(`${JSON.stringify(history, null, 2)}\n`);
@@ -1,5 +1,6 @@
1
- import { type PullRequestInfo } from "./pull-requests.js";
2
- import { type WorktreeInfo } from "./worktree-info.js";
1
+ import type { WorktreeInfo } from "../../application/worktree/read-models.js";
2
+ import type { PullRequestInfo } from "../../ports/pull-requests.js";
3
+ import { type CliRuntime } from "../dependencies.js";
3
4
  export interface HubCommandOptions {
4
5
  cwd: string;
5
6
  columns?: number;
@@ -7,6 +8,7 @@ export interface HubCommandOptions {
7
8
  now?: number;
8
9
  stderr: (chunk: string) => void;
9
10
  stdout: (chunk: string) => void;
11
+ runtime?: HubRuntime;
10
12
  }
11
13
  export interface HubCommandDependencies {
12
14
  queryRepositoryPullRequests: (repoRoot: string) => Promise<PullRequestInfo[]>;
@@ -22,10 +24,12 @@ export interface HubRepository {
22
24
  root: string;
23
25
  worktrees: HubWorktree[];
24
26
  }
27
+ type HubRuntime = CliRuntime<"pullRequests" | "repositoryContext" | "repositoryRegistry" | "worktrees" | "worktreeCatalog">;
25
28
  export interface HubData {
26
29
  currentRepository: string | null;
27
30
  repositories: HubRepository[];
28
31
  }
29
- export declare function buildHubData(cwd: string, dependencies?: Partial<HubCommandDependencies>): Promise<HubData>;
32
+ export declare function buildHubData(cwd: string, dependencies?: Partial<HubCommandDependencies>, runtime?: HubRuntime): Promise<HubData>;
30
33
  export declare function runHubCommand(options: HubCommandOptions, dependencies?: Partial<HubCommandDependencies>): Promise<number>;
31
34
  export declare function formatHubOutput(data: HubData, columns?: number, now?: number): string;
35
+ export {};
@@ -1,23 +1,24 @@
1
1
  import { basename } from "node:path";
2
2
  import { stdin, stdout } from "node:process";
3
3
  import { spinner } from "@clack/prompts";
4
- import { loadHistory } from "./history.js";
5
- import { createPullRequestQuery, } from "./pull-requests.js";
6
- import { detectRepository } from "./repo.js";
7
- import { middleEllipsize, sanitizeTerminalText, terminalWidth, } from "./terminal-text.js";
8
- import { formatRelativeAge, formatUpstreamState, readWorktreeInfos, } from "./worktree-info.js";
9
- import { listDiscoverableWorktreeSources } from "./worktree-sources.js";
4
+ import { loadWorktreeCatalog } from "../../application/worktree/catalog.js";
5
+ import { listDiscoverableWorktreeSources } from "../../application/worktree/sources.js";
6
+ import { mapWithConcurrency } from "../../domain/shared/concurrency.js";
7
+ import { middleEllipsize, sanitizeTerminalText, terminalWidth, } from "../../presentation/terminal/text.js";
8
+ import { formatRelativeAge, formatUpstreamState, } from "../../presentation/worktree/format.js";
9
+ import { defaultCliDependencies } from "../dependencies.js";
10
10
  const MAX_HUB_REPOSITORY_CONCURRENCY = 4;
11
- const defaultDependencies = {
12
- queryRepositoryPullRequests: createPullRequestQuery().listOpenPullRequestsForRepository,
13
- };
14
- export async function buildHubData(cwd, dependencies = {}) {
11
+ export async function buildHubData(cwd, dependencies = {}, runtime = defaultCliDependencies) {
12
+ const { detectRepository } = runtime.repositoryContext;
13
+ const sourceDependencies = {
14
+ ...runtime.repositoryContext,
15
+ ...runtime.repositoryRegistry,
16
+ ...runtime.worktrees,
17
+ };
15
18
  const queryRepositoryPullRequests = dependencies.queryRepositoryPullRequests ??
16
- defaultDependencies.queryRepositoryPullRequests;
19
+ runtime.pullRequests.listOpenPullRequestsForRepository;
17
20
  const currentRepository = await detectRepository(cwd).catch(() => null);
18
- const history = await loadHistory();
19
- const lastUsedByPath = new Map(history.map((entry) => [entry.path, entry.timestamp]));
20
- const sources = await listDiscoverableWorktreeSources(cwd);
21
+ const sources = await listDiscoverableWorktreeSources(cwd, sourceDependencies);
21
22
  const groups = new Map();
22
23
  for (const source of sources) {
23
24
  if (!source.repoRoot)
@@ -35,7 +36,6 @@ export async function buildHubData(cwd, dependencies = {}) {
35
36
  }
36
37
  }
37
38
  const repositories = await mapWithConcurrency([...groups.entries()], MAX_HUB_REPOSITORY_CONCURRENCY, async ([root, group]) => {
38
- const infos = await readWorktreeInfos(group.sources.map((source) => source.worktree));
39
39
  let pullRequests = [];
40
40
  try {
41
41
  pullRequests = await queryRepositoryPullRequests(root);
@@ -43,10 +43,14 @@ export async function buildHubData(cwd, dependencies = {}) {
43
43
  catch {
44
44
  // PR metadata is optional. The hub remains useful when a provider is unavailable.
45
45
  }
46
- const worktrees = infos.map((info) => ({
46
+ const hydrated = await loadWorktreeCatalog(group.sources, "full", {
47
+ ...runtime.worktreeCatalog,
48
+ queryRepositoryPullRequests: async () => pullRequests,
49
+ });
50
+ const worktrees = hydrated.map(({ info, lastUsedTimestamp, pullRequests }) => ({
47
51
  ...info,
48
- lastUsedTimestamp: lastUsedByPath.get(info.path) ?? null,
49
- pullRequests: pullRequests.filter((pullRequest) => pullRequest.sourceBranch === info.branch),
52
+ lastUsedTimestamp,
53
+ pullRequests,
50
54
  }));
51
55
  return {
52
56
  current: currentRepository?.repoRoot === root,
@@ -73,7 +77,7 @@ export async function runHubCommand(options, dependencies = {}) {
73
77
  loading?.start("Loading worktrees");
74
78
  let data;
75
79
  try {
76
- data = await buildHubData(options.cwd, dependencies);
80
+ data = await buildHubData(options.cwd, dependencies, options.runtime);
77
81
  }
78
82
  finally {
79
83
  loading?.stop();
@@ -283,17 +287,3 @@ function formatHumanText(value, maxLength) {
283
287
  ? sanitized
284
288
  : `${sanitized.slice(0, Math.max(0, maxLength - 1))}…`;
285
289
  }
286
- async function mapWithConcurrency(items, limit, mapper) {
287
- const results = new Array(items.length);
288
- let nextIndex = 0;
289
- async function readNext() {
290
- for (;;) {
291
- const index = nextIndex++;
292
- if (index >= items.length)
293
- return;
294
- results[index] = await mapper(items[index]);
295
- }
296
- }
297
- await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => readNext()));
298
- return results;
299
- }
@@ -1,4 +1,5 @@
1
- import { type SupportedShell } from "./shell.js";
1
+ import { type SupportedShell } from "../../presentation/shell/shell.js";
2
+ import { type CliRuntime } from "../dependencies.js";
2
3
  export type InstallSaveTarget = "local" | "global";
3
4
  export type ShellIntegrationChoice = "existing" | "install" | "skip";
4
5
  export interface SetupWizardResult {
@@ -29,6 +30,7 @@ export interface InitCommandOptions {
29
30
  promptForOnboarding?: (context: InitOnboardingContext) => Promise<InitOnboardingResult | null>;
30
31
  promptForSetup?: () => Promise<SetupWizardResult | null>;
31
32
  shell?: string;
33
+ runtime?: CliRuntime<"integrations" | "configStore">;
32
34
  stderr?: (chunk: string) => void;
33
35
  stdout: (chunk: string) => void;
34
36
  write?: boolean;
@@ -3,12 +3,11 @@ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname } from "node:path";
5
5
  import { confirm, intro, isCancel, log, outro, select, text, } from "@clack/prompts";
6
- import { loadConfig, loadGlobalConfig, saveGlobalConfig, saveLocalConfig, updateGlobalConfigKey, } from "./config.js";
7
- import { EDITORS } from "./editor.js";
8
- import { isHeadless } from "./headless.js";
9
- import { resolveSupportedShell } from "./shell.js";
10
- import { renderShellCompletion } from "./shell-completion.js";
11
- import { SHELL_INTEGRATION_END_MARKER as END_MARKER, executableExists, hasShellIntegration, ONBOARDING_SHELL_INTEGRATION_END_MARKER as ONBOARDING_END_MARKER, ONBOARDING_SHELL_INTEGRATION_START_MARKER as ONBOARDING_START_MARKER, resolveCompletionPath, resolveShellConfigPath, SHELL_INTEGRATION_START_MARKER as START_MARKER, } from "./shell-setup.js";
6
+ import { renderShellCompletion } from "../../presentation/shell/completion.js";
7
+ import { SHELL_INTEGRATION_END_MARKER as END_MARKER, executableExists, hasShellIntegration, ONBOARDING_SHELL_INTEGRATION_END_MARKER as ONBOARDING_END_MARKER, ONBOARDING_SHELL_INTEGRATION_START_MARKER as ONBOARDING_START_MARKER, resolveCompletionPath, resolveShellConfigPath, SHELL_INTEGRATION_START_MARKER as START_MARKER, } from "../../presentation/shell/setup.js";
8
+ import { resolveSupportedShell, } from "../../presentation/shell/shell.js";
9
+ import { defaultCliDependencies, } from "../dependencies.js";
10
+ import { isHeadless } from "../runtime/headless.js";
12
11
  const ZSH_COMPLETION_PATH_LINE = "fpath=(~/.zsh/completions $fpath)";
13
12
  const SHELL_WRAPPED_COMMANDS = [
14
13
  {
@@ -69,12 +68,13 @@ const SHELL_WRAPPED_COMMANDS = [
69
68
  },
70
69
  ];
71
70
  export async function runInitCommand(options) {
71
+ const runtime = options.runtime ?? defaultCliDependencies;
72
72
  if (options.shell === undefined) {
73
- return runOnboardingInitCommand(options);
73
+ return runOnboardingInitCommand(options, runtime);
74
74
  }
75
- return runLegacyInitCommand(options);
75
+ return runLegacyInitCommand(options, runtime);
76
76
  }
77
- async function runOnboardingInitCommand(options) {
77
+ async function runOnboardingInitCommand(options, runtime) {
78
78
  if (options.json || isHeadless() || !canRunOnboarding(options)) {
79
79
  return writeNonInteractiveInitError(options);
80
80
  }
@@ -84,14 +84,14 @@ async function runOnboardingInitCommand(options) {
84
84
  home,
85
85
  };
86
86
  if (options.promptForOnboarding === undefined) {
87
- return runDefaultOnboarding(context);
87
+ return runDefaultOnboarding(context, runtime);
88
88
  }
89
89
  const result = await options.promptForOnboarding(context);
90
90
  if (!result) {
91
91
  options.stderr?.("Aborted.\n");
92
92
  return 1;
93
93
  }
94
- await applyOnboardingResult(result, home);
94
+ await applyOnboardingResult(result, home, runtime);
95
95
  return 0;
96
96
  }
97
97
  function canRunOnboarding(options) {
@@ -104,7 +104,7 @@ function writeNonInteractiveInitError(options) {
104
104
  options.stderr?.(options.json ? `${JSON.stringify({ error })}\n` : `${error}\n`);
105
105
  return 1;
106
106
  }
107
- async function runDefaultOnboarding(context) {
107
+ async function runDefaultOnboarding(context, runtime) {
108
108
  intro("gji init");
109
109
  const shell = await select({
110
110
  message: "Which shell should gji configure?",
@@ -124,7 +124,7 @@ async function runDefaultOnboarding(context) {
124
124
  return abortDefaultOnboarding();
125
125
  const completionPath = resolveCompletionPath(shell, context.home);
126
126
  const completionAlreadyInstalled = await fileExists(completionPath);
127
- await applyShellIntegration(shell, shellIntegration, context.home);
127
+ await applyShellIntegration(shell, shellIntegration, context.home, runtime);
128
128
  const installCompletion = await promptForCompletion(shell, completionPath);
129
129
  if (installCompletion === null)
130
130
  return abortDefaultOnboarding();
@@ -135,21 +135,21 @@ async function runDefaultOnboarding(context) {
135
135
  if (shell === "zsh" && (completionAlreadyInstalled || installCompletion)) {
136
136
  await ensureZshCompletionPath(rcPath);
137
137
  }
138
- const editor = await promptForEditor();
138
+ const editor = await promptForEditor(runtime.integrations.EDITORS);
139
139
  if (editor === null)
140
140
  return abortDefaultOnboarding();
141
141
  if (editor) {
142
- await updateGlobalConfigKey("editor", editor, context.home);
142
+ await runtime.configStore.updateGlobalConfigKey("editor", editor, context.home);
143
143
  }
144
144
  outro(`Setup complete. Restart your shell or run: source ${rcPath}\nVerify with: gji doctor`);
145
145
  return 0;
146
146
  }
147
- async function applyShellIntegration(shell, choice, home) {
147
+ async function applyShellIntegration(shell, choice, home, runtime) {
148
148
  if (choice === "install") {
149
149
  await installOnboardingShellIntegration(shell, home);
150
150
  }
151
151
  if (choice !== "skip") {
152
- await updateGlobalConfigKey("shellIntegration", true, home);
152
+ await runtime.configStore.updateGlobalConfigKey("shellIntegration", true, home);
153
153
  }
154
154
  }
155
155
  function abortDefaultOnboarding() {
@@ -187,8 +187,8 @@ async function promptForCompletion(shell, completionPath) {
187
187
  return null;
188
188
  return confirmed;
189
189
  }
190
- async function promptForEditor() {
191
- const availableEditors = await findAvailableEditors();
190
+ async function promptForEditor(editors) {
191
+ const availableEditors = await findAvailableEditors(editors);
192
192
  if (availableEditors.length === 0) {
193
193
  log.info("No supported editor CLIs found on PATH; skipping editor setup.");
194
194
  return undefined;
@@ -209,20 +209,20 @@ async function promptForEditor() {
209
209
  return null;
210
210
  return selected === skipValue ? undefined : selected;
211
211
  }
212
- async function findAvailableEditors() {
212
+ async function findAvailableEditors(editors) {
213
213
  const availableEditors = [];
214
- for (const editor of EDITORS) {
214
+ for (const editor of editors) {
215
215
  if (await executableExists(editor.cli)) {
216
216
  availableEditors.push(editor);
217
217
  }
218
218
  }
219
219
  return availableEditors;
220
220
  }
221
- async function applyOnboardingResult(result, home) {
221
+ async function applyOnboardingResult(result, home, runtime) {
222
222
  const rcPath = resolveShellConfigPath(result.shell, home);
223
223
  const completionPath = resolveCompletionPath(result.shell, home);
224
224
  const completionAlreadyInstalled = await fileExists(completionPath);
225
- await applyShellIntegration(result.shell, result.shellIntegration, home);
225
+ await applyShellIntegration(result.shell, result.shellIntegration, home, runtime);
226
226
  if (result.installCompletion) {
227
227
  await mkdir(dirname(completionPath), { recursive: true });
228
228
  await writeFile(completionPath, renderShellCompletion(result.shell), "utf8");
@@ -232,7 +232,7 @@ async function applyOnboardingResult(result, home) {
232
232
  await ensureZshCompletionPath(rcPath);
233
233
  }
234
234
  if (result.editor) {
235
- await updateGlobalConfigKey("editor", result.editor, home);
235
+ await runtime.configStore.updateGlobalConfigKey("editor", result.editor, home);
236
236
  }
237
237
  }
238
238
  async function installOnboardingShellIntegration(shell, home) {
@@ -290,7 +290,7 @@ async function fileExists(path) {
290
290
  return false;
291
291
  }
292
292
  }
293
- async function runLegacyInitCommand(options) {
293
+ async function runLegacyInitCommand(options, runtime) {
294
294
  const shell = resolveSupportedShell(options.shell, process.env.SHELL);
295
295
  const home = options.home ?? homedir();
296
296
  if (!shell) {
@@ -309,7 +309,7 @@ async function runLegacyInitCommand(options) {
309
309
  await writeFile(rcPath, next, "utf8");
310
310
  options.stdout(`${rcPath}\n`);
311
311
  // Run the setup wizard on the first-ever init (not on subsequent re-runs).
312
- const { config: globalConfig } = await loadGlobalConfig(home);
312
+ const { config: globalConfig } = await runtime.configStore.loadGlobalConfig(home);
313
313
  const alreadyConfigured = "shellIntegration" in globalConfig || "installSaveTarget" in globalConfig;
314
314
  const hasCustomPrompt = options.promptForSetup !== undefined;
315
315
  const canPrompt = !isHeadless() && (hasCustomPrompt || process.stdout.isTTY === true);
@@ -317,12 +317,12 @@ async function runLegacyInitCommand(options) {
317
317
  const prompt = options.promptForSetup ?? defaultPromptForSetup;
318
318
  const result = await prompt();
319
319
  if (result) {
320
- await updateGlobalConfigKey("installSaveTarget", result.installSaveTarget, home);
321
- await saveWizardConfig(result, options.cwd, home);
320
+ await runtime.configStore.updateGlobalConfigKey("installSaveTarget", result.installSaveTarget, home);
321
+ await saveWizardConfig(result, options.cwd, home, runtime);
322
322
  }
323
323
  }
324
324
  // Mark shell integration as installed so the first-run nudge is suppressed.
325
- await updateGlobalConfigKey("shellIntegration", true, home);
325
+ await runtime.configStore.updateGlobalConfigKey("shellIntegration", true, home);
326
326
  return 0;
327
327
  }
328
328
  export function renderShellIntegration(shell) {
@@ -361,7 +361,7 @@ export function upsertShellIntegration(existingConfig, script) {
361
361
  }
362
362
  return ensureTrailingNewline(`${prefix}\n\n${trimmedScript}`);
363
363
  }
364
- async function saveWizardConfig(result, cwd, home) {
364
+ async function saveWizardConfig(result, cwd, home, runtime) {
365
365
  const values = {};
366
366
  if (result.branchPrefix)
367
367
  values.branchPrefix = result.branchPrefix;
@@ -379,12 +379,15 @@ async function saveWizardConfig(result, cwd, home) {
379
379
  if (Object.keys(values).length === 0)
380
380
  return;
381
381
  if (result.installSaveTarget === "local") {
382
- const loaded = await loadConfig(cwd);
383
- await saveLocalConfig(cwd, { ...loaded.config, ...values });
382
+ const loaded = await runtime.configStore.loadConfig(cwd);
383
+ await runtime.configStore.saveLocalConfig(cwd, {
384
+ ...loaded.config,
385
+ ...values,
386
+ });
384
387
  }
385
388
  else {
386
- const { config: existing } = await loadGlobalConfig(home);
387
- await saveGlobalConfig({ ...existing, ...values }, home);
389
+ const { config: existing } = await runtime.configStore.loadGlobalConfig(home);
390
+ await runtime.configStore.saveGlobalConfig({ ...existing, ...values }, home);
388
391
  }
389
392
  }
390
393
  async function readExistingConfig(path) {
@@ -1,9 +1,11 @@
1
- import { type WorktreeEntry } from "./repo.js";
2
- import { type WorktreeInfo } from "./worktree-info.js";
1
+ import type { WorktreeInfo } from "../../application/worktree/read-models.js";
2
+ import type { WorktreeEntry } from "../../domain/worktree/types.js";
3
+ import { type CliRuntime } from "../dependencies.js";
3
4
  export interface LsCommandOptions {
4
5
  compact?: boolean;
5
6
  cwd: string;
6
7
  json?: boolean;
8
+ runtime?: CliRuntime<"worktrees" | "worktreeInfo">;
7
9
  stdout: (chunk: string) => void;
8
10
  }
9
11
  export declare function runLsCommand(options: LsCommandOptions): Promise<number>;
@@ -1,7 +1,10 @@
1
- import { comparePaths } from "./paths.js";
2
- import { listWorktrees } from "./repo.js";
3
- import { formatLastCommit, formatUpstreamState, readWorktreeInfos, } from "./worktree-info.js";
1
+ import { comparePaths } from "../../domain/shared/paths.js";
2
+ import { formatLastCommit, formatUpstreamState, } from "../../presentation/worktree/format.js";
3
+ import { defaultCliDependencies } from "../dependencies.js";
4
4
  export async function runLsCommand(options) {
5
+ const runtime = options.runtime ?? defaultCliDependencies;
6
+ const { listWorktrees } = runtime.worktrees;
7
+ const { readWorktreeInfos } = runtime.worktreeInfo;
5
8
  const worktrees = sortWorktrees(await listWorktrees(options.cwd));
6
9
  if (options.compact) {
7
10
  if (options.json) {
@@ -44,7 +47,7 @@ export function formatDetailedWorktreeTable(worktrees) {
44
47
  "UPSTREAM".padEnd(upstreamWidth, " ") +
45
48
  " " +
46
49
  "LAST".padEnd(lastCommitWidth, " ") +
47
- (hasTasks ? " " + "TASK".padEnd(taskWidth, " ") : "") +
50
+ (hasTasks ? ` ${"TASK".padEnd(taskWidth, " ")}` : "") +
48
51
  " PATH",
49
52
  ];
50
53
  for (const row of rows) {
@@ -68,7 +71,7 @@ export function formatWorktreeTable(worktrees) {
68
71
  path: worktree.path,
69
72
  }));
70
73
  const branchWidth = Math.max("BRANCH".length, ...rows.map((row) => row.branch.length));
71
- const lines = [" " + "BRANCH".padEnd(branchWidth, " ") + " PATH"];
74
+ const lines = [` ${"BRANCH".padEnd(branchWidth, " ")} PATH`];
72
75
  for (const row of rows) {
73
76
  lines.push(`${row.isCurrent ? "*" : " "} ${row.branch.padEnd(branchWidth, " ")} ${row.path}`);
74
77
  }