@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,5 +1,6 @@
1
- import { type EditorDefinition } from "./editor.js";
2
- import { type QueryWorktreePullRequests, type WorktreePromptEntry } from "./worktree-picker.js";
1
+ import type { EditorDefinition } from "../../ports/editor.js";
2
+ import { type QueryWorktreePullRequests, type WorktreePromptEntry } from "../../presentation/worktree/picker.js";
3
+ import { type CliRuntime } from "../dependencies.js";
3
4
  export type { EditorDefinition };
4
5
  export interface OpenCommandOptions {
5
6
  branch?: string;
@@ -7,6 +8,7 @@ export interface OpenCommandOptions {
7
8
  editor?: string;
8
9
  save?: boolean;
9
10
  select?: boolean;
11
+ runtime?: CliRuntime<"history" | "config" | "repositoryContext" | "worktrees" | "integrations" | "configStore" | "worktreeCatalog">;
10
12
  stderr: (chunk: string) => void;
11
13
  stdout: (chunk: string) => void;
12
14
  workspace?: boolean;
@@ -3,19 +3,24 @@ import { access, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { promisify } from "node:util";
5
5
  import { isCancel, select } from "@clack/prompts";
6
- import { loadEffectiveConfig, resolveConfigString, updateGlobalConfigKey, } from "./config.js";
7
- import { defaultSpawnEditor, EDITORS, } from "./editor.js";
8
- import { isHeadless } from "./headless.js";
9
- import { recordWorktreeUsage } from "./history.js";
10
- import { detectRepository, listWorktrees } from "./repo.js";
11
- import { buildWorktreePromptEntries, promptForSingleWorktree, resolveWorktreeQuery, } from "./worktree-picker.js";
6
+ import { resolveWorktreeQuery } from "../../domain/worktree/matching.js";
7
+ import { buildWorktreePromptEntries, promptForSingleWorktree, } from "../../presentation/worktree/picker.js";
8
+ import { defaultCliDependencies, withPullRequestQueries, } from "../dependencies.js";
9
+ import { isHeadless } from "../runtime/headless.js";
12
10
  const execFileAsync = promisify(execFile);
13
11
  export function createOpenCommand(dependencies = {}) {
14
- const detectEditors = dependencies.detectEditors ?? detectInstalledEditors;
15
12
  const promptForEditor = dependencies.promptForEditor ?? defaultPromptForEditor;
16
13
  const promptForWorktree = dependencies.promptForWorktree ?? defaultPromptForWorktree;
17
- const spawnEditor = dependencies.spawnEditor ?? defaultSpawnEditor;
18
14
  return async function runOpenCommand(options) {
15
+ const runtime = options.runtime ?? defaultCliDependencies;
16
+ const { recordWorktreeUsage } = runtime.history;
17
+ const { detectRepository } = runtime.repositoryContext;
18
+ const { listWorktrees } = runtime.worktrees;
19
+ const { defaultSpawnEditor, EDITORS } = runtime.integrations;
20
+ const detectEditors = dependencies.detectEditors ?? (() => detectInstalledEditors(EDITORS));
21
+ const { loadEffectiveConfig, resolveConfigString } = runtime.config;
22
+ const { updateGlobalConfigKey } = runtime.configStore;
23
+ const spawnEditorWithRuntime = dependencies.spawnEditor ?? defaultSpawnEditor;
19
24
  if (options.select && options.branch !== undefined) {
20
25
  options.stderr("gji open: --select cannot be used with a branch\n");
21
26
  return 1;
@@ -57,7 +62,9 @@ export function createOpenCommand(dependencies = {}) {
57
62
  repoRoot: repository.repoRoot,
58
63
  repoName: repository.repoName,
59
64
  worktree,
60
- })), { queryPullRequests: dependencies.queryPullRequests });
65
+ })), {
66
+ catalog: withPullRequestQueries(runtime, dependencies.queryPullRequests),
67
+ });
61
68
  const chosen = await promptForWorktree(entries);
62
69
  if (!chosen) {
63
70
  options.stderr("Aborted\n");
@@ -118,7 +125,7 @@ export function createOpenCommand(dependencies = {}) {
118
125
  }
119
126
  args.push(openTarget);
120
127
  try {
121
- await spawnEditor(editorCli, args);
128
+ await spawnEditorWithRuntime(editorCli, args);
122
129
  }
123
130
  catch (error) {
124
131
  const message = error instanceof Error ? error.message : String(error);
@@ -132,8 +139,8 @@ export function createOpenCommand(dependencies = {}) {
132
139
  };
133
140
  }
134
141
  export const runOpenCommand = createOpenCommand();
135
- async function detectInstalledEditors() {
136
- const results = await Promise.all(EDITORS.map(async (editor) => ({
142
+ async function detectInstalledEditors(editors) {
143
+ const results = await Promise.all(editors.map(async (editor) => ({
137
144
  editor,
138
145
  available: await isCommandAvailable(editor.cli),
139
146
  })));
@@ -1,11 +1,13 @@
1
- import { type PullRequestInfo } from "./pull-requests.js";
2
- import { type QueryRepositoryPullRequests, type QueryWorktreePullRequests, type WorktreePromptEntry } from "./worktree-picker.js";
1
+ import type { PullRequestInfo } from "../../ports/pull-requests.js";
2
+ import { type QueryRepositoryPullRequests, type QueryWorktreePullRequests, type WorktreePromptEntry } from "../../presentation/worktree/picker.js";
3
+ import { type CliRuntime } from "../dependencies.js";
3
4
  export interface PrOpenCommandOptions {
4
5
  cwd: string;
5
6
  stderr: (chunk: string) => void;
6
7
  stdout: (chunk: string) => void;
7
8
  select?: boolean;
8
9
  target?: string;
10
+ runtime?: CliRuntime<"pullRequests" | "repositoryContext" | "worktrees" | "integrations" | "worktreeCatalog">;
9
11
  }
10
12
  export interface PrOpenCommandDependencies {
11
13
  findOpenPullRequest: (repoRoot: string, number: number) => Promise<PullRequestInfo | null>;
@@ -1,21 +1,23 @@
1
1
  import { isCancel, select } from "@clack/prompts";
2
- import { openBrowser } from "./browser.js";
3
- import { isHeadless } from "./headless.js";
4
- import { createPullRequestQuery, } from "./pull-requests.js";
5
- import { detectRepository, listWorktrees } from "./repo.js";
6
- import { buildWorktreePromptEntries, promptForSingleWorktree, resolveWorktreeQuery, } from "./worktree-picker.js";
2
+ import { resolveWorktreeQuery } from "../../domain/worktree/matching.js";
3
+ import { buildWorktreePromptEntries, promptForSingleWorktree, } from "../../presentation/worktree/picker.js";
4
+ import { defaultCliDependencies, withPullRequestQueries, } from "../dependencies.js";
5
+ import { isHeadless } from "../runtime/headless.js";
7
6
  export function createPrOpenCommand(dependencies = {}) {
8
- const query = createPullRequestQuery();
9
- const findOpenPullRequest = dependencies.findOpenPullRequest ?? query.findOpenPullRequest;
10
- const openInBrowser = dependencies.openBrowser ?? openBrowser;
11
7
  const promptForPullRequest = dependencies.promptForPullRequest ?? defaultPromptForPullRequest;
12
8
  const promptForWorktree = dependencies.promptForWorktree ?? defaultPromptForWorktree;
13
- const queryPullRequests = dependencies.queryPullRequests ?? query.listOpenPullRequests;
14
- const queryRepositoryPullRequests = dependencies.queryRepositoryPullRequests ??
15
- (dependencies.queryPullRequests === undefined
16
- ? query.listOpenPullRequestsForRepository
17
- : undefined);
18
9
  return async function runPrOpenCommand(options) {
10
+ const runtime = options.runtime ?? defaultCliDependencies;
11
+ const query = runtime.pullRequests;
12
+ const { detectRepository } = runtime.repositoryContext;
13
+ const { listWorktrees } = runtime.worktrees;
14
+ const openInBrowser = dependencies.openBrowser ?? runtime.integrations.openBrowser;
15
+ const findOpenPullRequest = dependencies.findOpenPullRequest ?? query.findOpenPullRequest;
16
+ const queryPullRequests = dependencies.queryPullRequests ?? query.listOpenPullRequests;
17
+ const queryRepositoryPullRequests = dependencies.queryRepositoryPullRequests ??
18
+ (dependencies.queryPullRequests === undefined
19
+ ? query.listOpenPullRequestsForRepository
20
+ : undefined);
19
21
  if (options.select && options.target !== undefined) {
20
22
  options.stderr("gji pr open: --select cannot be used with a target\n");
21
23
  return 1;
@@ -32,9 +34,9 @@ export function createPrOpenCommand(dependencies = {}) {
32
34
  return 1;
33
35
  if (options.target === undefined) {
34
36
  if (!options.select) {
35
- return openCurrentWorktree(repository.repoRoot, options, queryPullRequests, promptForPullRequest, openInBrowser);
37
+ return openCurrentWorktree(repository.repoRoot, options, listWorktrees, queryPullRequests, promptForPullRequest, openInBrowser);
36
38
  }
37
- return openFromWorktreeSelector(repository.repoRoot, repository.repoName, options, promptForWorktree, promptForPullRequest, queryPullRequests, queryRepositoryPullRequests, openInBrowser);
39
+ return openFromWorktreeSelector(repository.repoRoot, repository.repoName, options, listWorktrees, promptForWorktree, promptForPullRequest, queryPullRequests, queryRepositoryPullRequests, openInBrowser, runtime);
38
40
  }
39
41
  const number = parsePrNumberTarget(options.target);
40
42
  if (number !== null) {
@@ -59,7 +61,7 @@ export function createPrOpenCommand(dependencies = {}) {
59
61
  };
60
62
  }
61
63
  export const runPrOpenCommand = createPrOpenCommand();
62
- async function openCurrentWorktree(repoRoot, options, queryPullRequests, promptForPullRequest, openInBrowser) {
64
+ async function openCurrentWorktree(repoRoot, options, listWorktrees, queryPullRequests, promptForPullRequest, openInBrowser) {
63
65
  const worktrees = await listWorktrees(options.cwd).catch((error) => {
64
66
  options.stderr(`gji pr open: unable to list worktrees: ${formatError(error)}\n`);
65
67
  return null;
@@ -91,7 +93,7 @@ async function openCurrentWorktree(repoRoot, options, queryPullRequests, promptF
91
93
  }
92
94
  return openPullRequests(pullRequests, options, promptForPullRequest, openInBrowser);
93
95
  }
94
- async function openFromWorktreeSelector(repoRoot, repoName, options, promptForWorktree, promptForPullRequest, queryPullRequests, queryRepositoryPullRequests, openInBrowser) {
96
+ async function openFromWorktreeSelector(repoRoot, repoName, options, listWorktrees, promptForWorktree, promptForPullRequest, queryPullRequests, queryRepositoryPullRequests, openInBrowser, runtime) {
95
97
  const worktrees = await listWorktrees(options.cwd).catch((error) => {
96
98
  options.stderr(`gji pr open: unable to list worktrees: ${formatError(error)}\n`);
97
99
  return null;
@@ -128,7 +130,7 @@ async function openFromWorktreeSelector(repoRoot, repoName, options, promptForWo
128
130
  }
129
131
  const { pullRequestsByBranch, sources } = connectedWorktrees;
130
132
  const entries = await buildWorktreePromptEntries(sources, {
131
- queryPullRequests: async (_root, branch) => pullRequestsByBranch.get(branch) ?? [],
133
+ catalog: withPullRequestQueries(runtime, async (_root, branch) => pullRequestsByBranch.get(branch) ?? []),
132
134
  });
133
135
  const connectedEntries = entries.filter((entry) => (entry.pullRequests?.length ?? 0) > 0);
134
136
  if (connectedEntries.length === 0) {
@@ -1,5 +1,6 @@
1
- import { type PathConflictChoice } from "./conflict.js";
2
- import { type BootstrapCommandRunner } from "./dependency-bootstrap.js";
1
+ import type { CommandRunner as BootstrapCommandRunner } from "../../ports/process.js";
2
+ import { type PathConflictChoice } from "../../presentation/prompts/path-conflict.js";
3
+ import { type CliRuntime } from "../dependencies.js";
3
4
  export type { PathConflictChoice };
4
5
  export interface PrCommandOptions {
5
6
  cwd: string;
@@ -10,11 +11,12 @@ export interface PrCommandOptions {
10
11
  outputEnv?: string;
11
12
  stderr: (chunk: string) => void;
12
13
  stdout: (chunk: string) => void;
14
+ runtime?: PrRuntime;
13
15
  }
16
+ type PrRuntime = CliRuntime<"bootstrap" | "config" | "git" | "history" | "repositoryContext">;
14
17
  export interface PrCommandDependencies {
15
18
  promptForPathConflict: (path: string) => Promise<PathConflictChoice>;
16
19
  runCommand: BootstrapCommandRunner;
17
20
  }
18
- export declare function parsePrInput(input: string): string | null;
19
21
  export declare function createPrCommand(dependencies?: Partial<PrCommandDependencies>): (options: PrCommandOptions) => Promise<number>;
20
22
  export declare const runPrCommand: (options: PrCommandOptions) => Promise<number>;
@@ -1,34 +1,19 @@
1
- import { execFile } from "node:child_process";
2
1
  import { mkdir } from "node:fs/promises";
3
2
  import { dirname } from "node:path";
4
- import { promisify } from "node:util";
5
- import { createBootstrapReporter } from "./bootstrap-output.js";
6
- import { createDependencyBootstrapPreview, formatDependencyBootstrapPreview, } from "./bootstrap-preview.js";
7
- import { loadEffectiveConfig, resolveConfigString, } from "./config.js";
8
- import { pathExists, promptForPathConflict, } from "./conflict.js";
9
- import { resolveDependencyBootstrapMode, } from "./dependency-bootstrap.js";
10
- import { isHeadless } from "./headless.js";
11
- import { recordWorktreeUsage } from "./history.js";
12
- import { createNavigationRepository, createNavigationTarget, } from "./navigation-output.js";
13
- import { detectRepository, resolveWorktreePath } from "./repo.js";
14
- import { writeShellOutput } from "./shell-handoff.js";
15
- import { bootstrapWorktree } from "./worktree-bootstrap.js";
16
- const execFileAsync = promisify(execFile);
3
+ import { resolveWorktreePath } from "../../domain/worktree/policy.js";
4
+ import { parsePrInput } from "../../domain/worktree/pr-reference.js";
5
+ import { createBootstrapReporter } from "../../presentation/bootstrap/output.js";
6
+ import { formatDependencyBootstrapPreview } from "../../presentation/bootstrap/preview.js";
7
+ import { pathExists, promptForPathConflict, } from "../../presentation/prompts/path-conflict.js";
8
+ import { writeShellOutput } from "../../presentation/shell/handoff.js";
9
+ import { createNavigationRepository, createNavigationTarget, } from "../../presentation/terminal/navigation.js";
10
+ import { defaultCliDependencies, } from "../dependencies.js";
11
+ import { isHeadless } from "../runtime/headless.js";
17
12
  const PR_OUTPUT_FILE_ENV = "GJI_PR_OUTPUT_FILE";
18
- export function parsePrInput(input) {
19
- if (/^\d+$/.test(input))
20
- return input;
21
- const hashMatch = input.match(/^#(\d+)$/);
22
- if (hashMatch)
23
- return hashMatch[1];
24
- const urlMatch = input.match(/\/(?:pull|pull-requests|merge_requests)\/(\d+)/);
25
- if (urlMatch)
26
- return urlMatch[1];
27
- return null;
28
- }
29
13
  export function createPrCommand(dependencies = {}) {
30
14
  const prompt = dependencies.promptForPathConflict ?? promptForPathConflict;
31
15
  return async function runPrCommand(options) {
16
+ const runtime = options.runtime ?? defaultCliDependencies;
32
17
  const prNumber = parsePrInput(options.number);
33
18
  if (!prNumber) {
34
19
  const message = `Invalid PR reference: ${options.number}`;
@@ -40,10 +25,10 @@ export function createPrCommand(dependencies = {}) {
40
25
  }
41
26
  return 1;
42
27
  }
43
- const repository = await detectRepository(options.cwd);
28
+ const repository = await runtime.repositoryContext.detectRepository(options.cwd);
44
29
  let config;
45
30
  try {
46
- config = await loadEffectiveConfig(repository.repoRoot, undefined, options.json ? undefined : options.stderr);
31
+ config = await runtime.config.loadEffectiveConfig(repository.repoRoot, undefined, options.json ? undefined : options.stderr);
47
32
  }
48
33
  catch (error) {
49
34
  const message = error instanceof Error ? error.message : String(error);
@@ -57,7 +42,7 @@ export function createPrCommand(dependencies = {}) {
57
42
  }
58
43
  const branchName = `pr/${prNumber}`;
59
44
  const remoteRef = `refs/remotes/origin/pull/${prNumber}/head`;
60
- const rawBasePath = resolveConfigString(config, "worktreePath");
45
+ const rawBasePath = runtime.config.resolveConfigString(config, "worktreePath");
61
46
  const configuredBasePath = rawBasePath?.startsWith("/") || rawBasePath?.startsWith("~")
62
47
  ? rawBasePath
63
48
  : undefined;
@@ -76,16 +61,16 @@ export function createPrCommand(dependencies = {}) {
76
61
  }
77
62
  const choice = await prompt(worktreePath);
78
63
  if (choice === "reuse") {
79
- await recordWorktreeUsage(worktreePath, branchName);
64
+ await runtime.history.recordWorktreeUsage(worktreePath, branchName);
80
65
  await writeOutput(worktreePath, options.stdout, options.outputEnv);
81
66
  return 0;
82
67
  }
83
68
  options.stderr(`Aborted because target worktree path already exists: ${worktreePath}\n`);
84
69
  return 1;
85
70
  }
86
- const dependencyMode = resolveDependencyBootstrapMode(config.dependencyBootstrap, options.noInstall);
71
+ const dependencyMode = runtime.bootstrap.resolveMode(config.dependencyBootstrap, options.noInstall);
87
72
  const dryRunDependencyBootstrap = options.dryRun
88
- ? await createDependencyBootstrapPreview(dependencyMode, {
73
+ ? await runtime.bootstrap.preview(dependencyMode, {
89
74
  currentRoot: repository.currentRoot,
90
75
  repoRoot: repository.repoRoot,
91
76
  cargoBuildCommand: config.dependencyBuildCommand,
@@ -108,7 +93,7 @@ export function createPrCommand(dependencies = {}) {
108
93
  return 0;
109
94
  }
110
95
  try {
111
- await fetchPullRequestRef(repository.repoRoot, options.number, prNumber, remoteRef);
96
+ await fetchPullRequestRef(repository.repoRoot, options.number, prNumber, remoteRef, runtime);
112
97
  }
113
98
  catch {
114
99
  const message = `Failed to fetch PR #${prNumber} from origin`;
@@ -122,12 +107,12 @@ export function createPrCommand(dependencies = {}) {
122
107
  return 1;
123
108
  }
124
109
  await mkdir(dirname(worktreePath), { recursive: true });
125
- const branchAlreadyExists = await localBranchExists(repository.repoRoot, branchName);
110
+ const branchAlreadyExists = await localBranchExists(repository.repoRoot, branchName, runtime);
126
111
  const worktreeArgs = branchAlreadyExists
127
112
  ? ["worktree", "add", worktreePath, branchName]
128
113
  : ["worktree", "add", "-b", branchName, worktreePath, remoteRef];
129
- await execFileAsync("git", worktreeArgs, { cwd: repository.repoRoot });
130
- const bootstrap = await bootstrapWorktree({
114
+ await runtime.git.runGit(repository.repoRoot, worktreeArgs);
115
+ const bootstrap = await runtime.bootstrap.bootstrapWorktree({
131
116
  branch: branchName,
132
117
  config,
133
118
  currentRoot: repository.currentRoot,
@@ -138,7 +123,6 @@ export function createPrCommand(dependencies = {}) {
138
123
  runCommand: dependencies.runCommand,
139
124
  commandStdout: options.json ? () => undefined : options.stdout,
140
125
  commandStderr: options.json ? () => undefined : options.stderr,
141
- json: options.json,
142
126
  worktreePath,
143
127
  });
144
128
  if (!bootstrap.ready) {
@@ -165,15 +149,20 @@ export function createPrCommand(dependencies = {}) {
165
149
  options.stdout(`${JSON.stringify(output, null, 2)}\n`);
166
150
  }
167
151
  else {
168
- await recordWorktreeUsage(worktreePath, branchName);
152
+ await runtime.history.recordWorktreeUsage(worktreePath, branchName);
169
153
  await writeOutput(worktreePath, options.stdout, options.outputEnv);
170
154
  }
171
155
  return 0;
172
156
  };
173
157
  }
174
- async function localBranchExists(repoRoot, branchName) {
158
+ async function localBranchExists(repoRoot, branchName, runtime) {
175
159
  try {
176
- await execFileAsync("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`], { cwd: repoRoot });
160
+ await runtime.git.runGit(repoRoot, [
161
+ "show-ref",
162
+ "--verify",
163
+ "--quiet",
164
+ `refs/heads/${branchName}`,
165
+ ]);
177
166
  return true;
178
167
  }
179
168
  catch {
@@ -181,10 +170,14 @@ async function localBranchExists(repoRoot, branchName) {
181
170
  }
182
171
  }
183
172
  export const runPrCommand = createPrCommand();
184
- async function fetchPullRequestRef(repoRoot, input, prNumber, remoteRef) {
173
+ async function fetchPullRequestRef(repoRoot, input, prNumber, remoteRef, runtime) {
185
174
  for (const sourceRef of listPullRequestSourceRefs(input, prNumber)) {
186
175
  try {
187
- await execFileAsync("git", ["fetch", "origin", `${sourceRef}:${remoteRef}`], { cwd: repoRoot });
176
+ await runtime.git.runGit(repoRoot, [
177
+ "fetch",
178
+ "origin",
179
+ `${sourceRef}:${remoteRef}`,
180
+ ]);
188
181
  return;
189
182
  }
190
183
  catch {
@@ -1,11 +1,13 @@
1
- import type { WorktreeEntry } from "./repo.js";
2
- import { type WorktreePromptEntry } from "./worktree-picker.js";
1
+ import type { WorktreeEntry } from "../../domain/worktree/types.js";
2
+ import { type WorktreePromptEntry } from "../../presentation/worktree/picker.js";
3
+ import { type CliRuntime } from "../dependencies.js";
3
4
  export interface RemoveCommandOptions {
4
5
  branch?: string;
5
6
  cwd: string;
6
7
  dryRun?: boolean;
7
8
  force?: boolean;
8
9
  json?: boolean;
10
+ runtime?: CliRuntime<"git" | "config" | "configStore" | "slots" | "hooks" | "repositoryContext" | "repositoryRegistry" | "worktrees" | "worktreeLifecycle" | "worktreeCatalog">;
9
11
  stderr: (chunk: string) => void;
10
12
  stdout: (chunk: string) => void;
11
13
  }
@@ -1,14 +1,12 @@
1
1
  import { basename } from "node:path";
2
2
  import { confirm, isCancel } from "@clack/prompts";
3
- import { loadEffectiveConfig } from "./config.js";
4
- import { isHeadless } from "./headless.js";
5
- import { extractHooks, runHook } from "./hooks.js";
6
- import { writeShellOutput } from "./shell-handoff.js";
7
- import { releaseWorktreeSlot } from "./slots.js";
3
+ import { loadLinkedWorktrees } from "../../application/worktree/catalog.js";
4
+ import { writeShellOutput } from "../../presentation/shell/handoff.js";
5
+ import { buildWorktreePromptEntries, promptForSingleWorktree, } from "../../presentation/worktree/picker.js";
6
+ import { defaultConfirmForceDeleteBranch, defaultConfirmForceRemoveWorktree, } from "../../presentation/worktree/prompts.js";
7
+ import { defaultCliDependencies } from "../dependencies.js";
8
+ import { isHeadless } from "../runtime/headless.js";
8
9
  import { finalizeUndoOperation, recordUndoOperation } from "./undo.js";
9
- import { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isWorktreeForceRemovalError, loadLinkedWorktrees, removeWorktree, } from "./worktree-management.js";
10
- import { buildWorktreePromptEntries, promptForSingleWorktree, } from "./worktree-picker.js";
11
- import { defaultConfirmForceDeleteBranch, defaultConfirmForceRemoveWorktree, } from "./worktree-prompts.js";
12
10
  const REMOVE_OUTPUT_FILE_ENV = "GJI_REMOVE_OUTPUT_FILE";
13
11
  export function createRemoveCommand(dependencies = {}) {
14
12
  const promptForWorktree = dependencies.promptForWorktree ?? defaultPromptForWorktree;
@@ -17,7 +15,17 @@ export function createRemoveCommand(dependencies = {}) {
17
15
  defaultConfirmForceRemoveWorktree;
18
16
  const confirmForceDeleteBranch = dependencies.confirmForceDeleteBranch ?? defaultConfirmForceDeleteBranch;
19
17
  return async function runRemoveCommand(options) {
20
- const { linkedWorktrees, repository } = await loadLinkedWorktrees(options.cwd);
18
+ const runtime = options.runtime ?? defaultCliDependencies;
19
+ const { loadEffectiveConfig } = runtime.config;
20
+ const { releaseWorktreeSlot } = runtime.slots;
21
+ const { extractHooks, runHook } = runtime.hooks;
22
+ const sourceDependencies = {
23
+ ...runtime.repositoryContext,
24
+ ...runtime.repositoryRegistry,
25
+ ...runtime.worktrees,
26
+ };
27
+ const { deleteBranch, forceDeleteBranch, forceRemoveWorktree, isBranchUnmergedError, isWorktreeForceRemovalError, removeWorktree, } = runtime.worktreeLifecycle;
28
+ const { linkedWorktrees, repository } = await loadLinkedWorktrees(options.cwd, sourceDependencies);
21
29
  if (linkedWorktrees.length === 0) {
22
30
  emitError(options, "No linked worktrees to finish");
23
31
  return 1;
@@ -36,7 +44,7 @@ export function createRemoveCommand(dependencies = {}) {
36
44
  (await promptForWorktree(await buildWorktreePromptEntries(linkedWorktrees.map((worktree) => ({
37
45
  repoName: repository.repoName,
38
46
  worktree,
39
- })))));
47
+ })), { catalog: runtime.worktreeCatalog })));
40
48
  if (!selection) {
41
49
  options.stderr("Aborted\n");
42
50
  return 1;
@@ -78,9 +86,7 @@ export function createRemoveCommand(dependencies = {}) {
78
86
  const hooks = extractHooks(config);
79
87
  let journal;
80
88
  try {
81
- journal = await recordUndoOperation("remove", repository.repoRoot, [
82
- worktree,
83
- ]);
89
+ journal = await recordUndoOperation("remove", repository.repoRoot, [worktree], undefined, runtime);
84
90
  }
85
91
  catch (error) {
86
92
  emitError(options, `could not write undo journal; no worktree was removed: ${toMessage(error)}`);
@@ -100,12 +106,12 @@ export function createRemoveCommand(dependencies = {}) {
100
106
  }
101
107
  catch (error) {
102
108
  if (!isWorktreeForceRemovalError(error)) {
103
- await finalizeUndoOperation(journal, []);
109
+ await finalizeUndoOperation(journal, [], undefined, runtime.configStore.GLOBAL_CONFIG_DIRECTORY);
104
110
  throw error;
105
111
  }
106
112
  if (!options.force &&
107
113
  !(await confirmForceRemoveWorktree(worktree.path))) {
108
- await finalizeUndoOperation(journal, []);
114
+ await finalizeUndoOperation(journal, [], undefined, runtime.configStore.GLOBAL_CONFIG_DIRECTORY);
109
115
  options.stderr("Aborted\n");
110
116
  return 1;
111
117
  }
@@ -113,12 +119,12 @@ export function createRemoveCommand(dependencies = {}) {
113
119
  await forceRemoveWorktree(repository.repoRoot, worktree.path);
114
120
  }
115
121
  catch (forceError) {
116
- await finalizeUndoOperation(journal, []);
122
+ await finalizeUndoOperation(journal, [], undefined, runtime.configStore.GLOBAL_CONFIG_DIRECTORY);
117
123
  emitError(options, `Failed to remove worktree at ${worktree.path}: ${toMessage(forceError)}`);
118
124
  return 1;
119
125
  }
120
126
  }
121
- await finalizeUndoOperation(journal, [worktree]);
127
+ await finalizeUndoOperation(journal, [worktree], undefined, runtime.configStore.GLOBAL_CONFIG_DIRECTORY);
122
128
  await releaseWorktreeSlot(worktree.path);
123
129
  if (worktree.branch) {
124
130
  try {
@@ -1,6 +1,8 @@
1
+ import { type CliRuntime } from "../dependencies.js";
1
2
  export interface RootCommandOptions {
2
3
  cwd: string;
3
4
  print?: boolean;
5
+ runtime?: CliRuntime<"repositoryContext">;
4
6
  stdout: (chunk: string) => void;
5
7
  }
6
8
  export declare function runRootCommand(options: RootCommandOptions): Promise<number>;
@@ -1,7 +1,9 @@
1
- import { detectRepository } from "./repo.js";
2
- import { writeShellOutput } from "./shell-handoff.js";
1
+ import { writeShellOutput } from "../../presentation/shell/handoff.js";
2
+ import { defaultCliDependencies } from "../dependencies.js";
3
3
  const ROOT_OUTPUT_FILE_ENV = "GJI_ROOT_OUTPUT_FILE";
4
4
  export async function runRootCommand(options) {
5
+ const { detectRepository } = (options.runtime ?? defaultCliDependencies)
6
+ .repositoryContext;
5
7
  const repository = await detectRepository(options.cwd);
6
8
  if (!options.print && process.env[ROOT_OUTPUT_FILE_ENV]) {
7
9
  await writeShellOutput(ROOT_OUTPUT_FILE_ENV, repository.repoRoot, options.stdout);
@@ -1,6 +1,8 @@
1
+ import { type CliRuntime } from "../dependencies.js";
1
2
  export interface RunHookCommandOptions {
2
3
  cwd: string;
3
4
  hook: string;
5
+ runtime?: CliRuntime<"config" | "slots" | "hooks" | "repositoryContext" | "worktrees">;
4
6
  stderr: (chunk: string) => void;
5
7
  }
6
8
  export declare function runHookCommand(options: RunHookCommandOptions): Promise<number>;
@@ -1,7 +1,4 @@
1
- import { loadEffectiveConfig } from "./config.js";
2
- import { extractHooks, runHook } from "./hooks.js";
3
- import { detectRepository, listWorktrees } from "./repo.js";
4
- import { getWorktreeSlot } from "./slots.js";
1
+ import { defaultCliDependencies } from "../dependencies.js";
5
2
  const VALID_HOOKS = [
6
3
  "after-create",
7
4
  "after-enter",
@@ -16,6 +13,12 @@ function isValidHook(hook) {
16
13
  return VALID_HOOKS.includes(hook);
17
14
  }
18
15
  export async function runHookCommand(options) {
16
+ const runtime = options.runtime ?? defaultCliDependencies;
17
+ const { loadEffectiveConfig } = runtime.config;
18
+ const { getWorktreeSlot } = runtime.slots;
19
+ const { extractHooks, runHook } = runtime.hooks;
20
+ const { detectRepository } = runtime.repositoryContext;
21
+ const { listWorktrees } = runtime.worktrees;
19
22
  const normalized = CAMEL_ALIASES[options.hook] ?? options.hook;
20
23
  if (!isValidHook(normalized)) {
21
24
  options.stderr(`gji run-hook: unknown hook '${options.hook}'. Valid hooks: ${VALID_HOOKS.join(", ")}\n`);
@@ -1,6 +1,8 @@
1
+ import { type CliRuntime } from "../dependencies.js";
1
2
  export interface StatusCommandOptions {
2
3
  cwd: string;
3
4
  json?: boolean;
5
+ runtime?: CliRuntime<"repositoryContext" | "worktrees" | "slots" | "tasks" | "git">;
4
6
  stdout: (chunk: string) => void;
5
7
  }
6
8
  interface WorktreeStatusRow {
@@ -1,12 +1,15 @@
1
- import { readWorktreeHealth } from "./git.js";
2
- import { comparePaths } from "./paths.js";
3
- import { detectRepository, listWorktrees } from "./repo.js";
4
- import { getWorktreeSlot } from "./slots.js";
5
- import { readTask } from "./task.js";
1
+ import { comparePaths } from "../../domain/shared/paths.js";
2
+ import { defaultCliDependencies, } from "../dependencies.js";
6
3
  export async function runStatusCommand(options) {
4
+ const runtime = options.runtime ?? defaultCliDependencies;
5
+ const { detectRepository } = runtime.repositoryContext;
6
+ const { listWorktrees } = runtime.worktrees;
7
+ const { getWorktreeSlot } = runtime.slots;
8
+ const { readTask } = runtime.tasks;
9
+ const { readWorktreeHealth } = runtime.git;
7
10
  const repository = await detectRepository(options.cwd);
8
11
  const worktrees = sortWorktreesByPath(await listWorktrees(options.cwd));
9
- const rows = await Promise.all(worktrees.map(async (worktree) => buildStatusRow(worktree)));
12
+ const rows = await Promise.all(worktrees.map(async (worktree) => buildStatusRow(worktree, readWorktreeHealth, getWorktreeSlot, readTask)));
10
13
  if (options.json) {
11
14
  options.stdout(`${JSON.stringify(formatStatusJson(repository.repoRoot, repository.currentRoot, rows), null, 2)}\n`);
12
15
  return 0;
@@ -37,7 +40,7 @@ export function formatStatusJson(repoRoot, currentRoot, rows) {
37
40
  worktrees: rows,
38
41
  };
39
42
  }
40
- async function buildStatusRow(worktree) {
43
+ async function buildStatusRow(worktree, readWorktreeHealth, getWorktreeSlot, readTask) {
41
44
  const [health, slot, task] = await Promise.all([
42
45
  readWorktreeHealth(worktree.path),
43
46
  getWorktreeSlot(worktree.path),
@@ -1,9 +1,11 @@
1
+ import { type CliRuntime } from "../dependencies.js";
1
2
  export interface SyncFilesCommandOptions {
2
3
  action?: string;
3
4
  cwd: string;
4
5
  home?: string;
5
6
  json?: boolean;
6
7
  paths?: string[];
8
+ runtime?: CliRuntime<"filesystem" | "configStore" | "repositoryContext">;
7
9
  stderr: (chunk: string) => void;
8
10
  stdout: (chunk: string) => void;
9
11
  }
@@ -1,9 +1,11 @@
1
1
  import { homedir } from "node:os";
2
2
  import { join } from "node:path";
3
- import { loadGlobalConfig, saveGlobalConfig, } from "./config.js";
4
- import { validateSyncFilePattern } from "./file-sync.js";
5
- import { detectRepository } from "./repo.js";
3
+ import { defaultCliDependencies, } from "../dependencies.js";
6
4
  export async function runSyncFilesCommand(options) {
5
+ const runtime = options.runtime ?? defaultCliDependencies;
6
+ const { validateSyncFilePattern } = runtime.filesystem;
7
+ const { loadGlobalConfig, saveGlobalConfig } = runtime.configStore;
8
+ const { detectRepository } = runtime.repositoryContext;
7
9
  const repository = await detectRepository(options.cwd);
8
10
  const home = options.home ?? homedir();
9
11
  const loaded = await loadGlobalConfig(home);
@@ -16,22 +18,22 @@ export async function runSyncFilesCommand(options) {
16
18
  return 0;
17
19
  }
18
20
  case "add": {
19
- const paths = validatePaths(options.paths ?? [], options);
21
+ const paths = validatePaths(options.paths ?? [], options, validateSyncFilePattern);
20
22
  if (!paths)
21
23
  return 1;
22
24
  const nextFiles = mergeSyncFiles(readSyncFiles(repoConfig), paths);
23
- await saveRepoSyncFiles(loaded.config, repoEntry?.key ?? repository.repoRoot, nextFiles, home);
25
+ await saveRepoSyncFiles(loaded.config, repoEntry?.key ?? repository.repoRoot, nextFiles, home, saveGlobalConfig);
24
26
  writeSyncFiles(options.stdout, nextFiles, !!options.json);
25
27
  return 0;
26
28
  }
27
29
  case "remove": {
28
- const paths = validatePaths(options.paths ?? [], options);
30
+ const paths = validatePaths(options.paths ?? [], options, validateSyncFilePattern);
29
31
  if (!paths)
30
32
  return 1;
31
33
  const existingFiles = readSyncFiles(repoConfig);
32
34
  const nextFiles = removeSyncFiles(existingFiles, paths);
33
35
  if (repoEntry && nextFiles.length !== existingFiles.length) {
34
- await saveRepoSyncFiles(loaded.config, repoEntry.key, nextFiles, home);
36
+ await saveRepoSyncFiles(loaded.config, repoEntry.key, nextFiles, home, saveGlobalConfig);
35
37
  }
36
38
  writeSyncFiles(options.stdout, nextFiles, !!options.json);
37
39
  return 0;
@@ -68,7 +70,7 @@ function writeSyncFiles(stdout, files, json) {
68
70
  }
69
71
  stdout(`${files.join("\n")}\n`);
70
72
  }
71
- function validatePaths(paths, options) {
73
+ function validatePaths(paths, options, validateSyncFilePattern) {
72
74
  if (paths.length === 0) {
73
75
  writeError(options, "at least one path is required");
74
76
  return null;
@@ -98,7 +100,7 @@ function removeSyncFiles(existing, removals) {
98
100
  const removalSet = new Set(removals);
99
101
  return existing.filter((path) => !removalSet.has(path));
100
102
  }
101
- async function saveRepoSyncFiles(config, repoKey, syncFiles, home) {
103
+ async function saveRepoSyncFiles(config, repoKey, syncFiles, home, saveGlobalConfig) {
102
104
  const repos = isPlainObject(config.repos) ? { ...config.repos } : {};
103
105
  const repoConfig = isPlainObject(repos[repoKey])
104
106
  ? repos[repoKey]