@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
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { homedir } from "node:os";
3
- import { runCli } from "./cli.js";
4
- import { loadGlobalConfig } from "./config.js";
5
- import { isHeadless } from "./headless.js";
3
+ import { runCli } from "./cli/program.js";
4
+ import { isHeadless } from "./cli/runtime/headless.js";
5
+ import { loadGlobalConfig } from "./infrastructure/persistence/config.js";
6
6
  async function main() {
7
7
  try {
8
8
  const argv = process.argv.slice(2);
@@ -1,8 +1,7 @@
1
- import { type CommandRunner } from "./command-runner.js";
2
- export type BootstrapKind = "dependency" | "build-cache" | "sync-file";
3
- export type BootstrapState = "installed" | "failed";
4
- export type DependencyBootstrapMode = "off" | "install";
1
+ import type { BootstrapKind, DependencyBootstrapMode, DependencyBootstrapPreview, DependencyBootstrapReport, DependencyBootstrapReporter } from "../../ports/bootstrap.js";
2
+ import type { CommandRunner } from "../../ports/process.js";
5
3
  export type BootstrapCommandRunner = CommandRunner;
4
+ export type { BootstrapEvent, BootstrapKind, DependencyBootstrapMode, DependencyBootstrapPreview, DependencyBootstrapReport, DependencyBootstrapReporter, } from "../../ports/bootstrap.js";
6
5
  export declare function resolveDependencyBootstrapMode(dependencyBootstrap: "off" | undefined, noInstall?: boolean): DependencyBootstrapMode;
7
6
  export interface BootstrapPreparationContext {
8
7
  detectionRoot?: string;
@@ -44,36 +43,11 @@ export interface PlannedBootstrapTarget {
44
43
  adapter: BootstrapAdapter;
45
44
  target: BootstrapTarget;
46
45
  }
47
- export interface BootstrapEvent {
48
- adapter: string;
49
- kind: BootstrapKind;
50
- reason?: string;
51
- state: BootstrapState;
52
- target: string;
53
- message: string;
54
- }
55
- export interface DependencyBootstrapReporter {
56
- dependency(event: BootstrapEvent): void;
57
- }
58
- export interface DependencyBootstrapReport {
59
- mode: DependencyBootstrapMode;
60
- ready: boolean;
61
- events: readonly BootstrapEvent[];
62
- }
63
46
  export interface DependencyBootstrapDependencies {
64
47
  runCommand?: BootstrapCommandRunner;
65
48
  stderr?: (chunk: string) => void;
66
49
  stdout?: (chunk: string) => void;
67
50
  }
68
- export interface DependencyBootstrapPreview {
69
- mode: DependencyBootstrapMode;
70
- targets: readonly {
71
- adapter: string;
72
- kind: BootstrapKind;
73
- target: string;
74
- command: string;
75
- }[];
76
- }
77
51
  export declare function prepareDependencyBootstrap(mode: DependencyBootstrapMode, context: {
78
52
  repoRoot: string;
79
53
  currentRoot?: string;
@@ -85,4 +59,3 @@ export declare function previewDependencyBootstrap(plan: DependencyBootstrapPlan
85
59
  export declare function executeDependencyBootstrap(plan: DependencyBootstrapPlan, options: DependencyBootstrapDependencies & {
86
60
  reporter: DependencyBootstrapReporter;
87
61
  }): Promise<DependencyBootstrapReport>;
88
- export {};
@@ -1,9 +1,9 @@
1
1
  import { readFile, realpath } from "node:fs/promises";
2
2
  import { join, relative, resolve, sep } from "node:path";
3
- import { runCommand } from "./command-runner.js";
4
- import { pathExists } from "./fs-utils.js";
5
- import { inspectDestination } from "./safe-destination.js";
6
- import { validateUvInstallation, validateUvStructure, } from "./uv-validation.js";
3
+ import { pathExists } from "../filesystem/fs-utils.js";
4
+ import { inspectDestination } from "../filesystem/safe-destination.js";
5
+ import { runCommand } from "../process/command-runner.js";
6
+ import { validateUvInstallation, validateUvStructure, } from "../validation/uv.js";
7
7
  export function resolveDependencyBootstrapMode(dependencyBootstrap, noInstall = false) {
8
8
  if (noInstall || dependencyBootstrap === "off")
9
9
  return "off";
@@ -0,0 +1,9 @@
1
+ export interface WorktreeHealth {
2
+ ahead: number;
3
+ behind: number;
4
+ hasUpstream: boolean;
5
+ upstreamGone: boolean;
6
+ status: "clean" | "dirty";
7
+ }
8
+ export declare function readWorktreeHealth(cwd: string): Promise<WorktreeHealth>;
9
+ export declare function isDirtyWorktree(cwd: string): Promise<boolean>;
@@ -0,0 +1,44 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execFileAsync = promisify(execFile);
4
+ export async function readWorktreeHealth(cwd) {
5
+ const { stdout } = await execFileAsync("git", ["status", "--porcelain=v2", "--branch"], { cwd });
6
+ return parseWorktreeHealth(stdout);
7
+ }
8
+ export async function isDirtyWorktree(cwd) {
9
+ const health = await readWorktreeHealth(cwd);
10
+ return health.status === "dirty";
11
+ }
12
+ function parseWorktreeHealth(output) {
13
+ let ahead = 0;
14
+ let behind = 0;
15
+ let hasUpstream = false;
16
+ let hasAb = false;
17
+ let dirty = false;
18
+ for (const line of output.split("\n").filter(Boolean)) {
19
+ if (line.startsWith("# branch.upstream ")) {
20
+ hasUpstream = true;
21
+ continue;
22
+ }
23
+ if (line.startsWith("# branch.ab ")) {
24
+ const match = /^# branch\.ab \+(\d+) -(\d+)$/.exec(line);
25
+ if (!match) {
26
+ throw new Error(`Unexpected branch.ab output: '${line}'`);
27
+ }
28
+ hasAb = true;
29
+ ahead = Number(match[1]);
30
+ behind = Number(match[2]);
31
+ continue;
32
+ }
33
+ if (!line.startsWith("# ")) {
34
+ dirty = true;
35
+ }
36
+ }
37
+ return {
38
+ ahead,
39
+ behind,
40
+ hasUpstream,
41
+ upstreamGone: hasUpstream && !hasAb,
42
+ status: dirty ? "dirty" : "clean",
43
+ };
44
+ }
@@ -0,0 +1,6 @@
1
+ import type { RemoteBase } from "../../ports/git.js";
2
+ export type { RemoteBase } from "../../ports/git.js";
3
+ export declare function isBranchMergedInto(cwd: string, branch: string, base?: string): Promise<boolean>;
4
+ export declare function resolveRemoteDefaultBranch(cwd: string, remote: string): Promise<string | null>;
5
+ export declare function resolveRemoteBase(cwd: string, remote: string, configuredBranch?: string): Promise<RemoteBase | null>;
6
+ export declare function readBranchLastCommitTimestamp(cwd: string, branch: string): Promise<number | null>;
@@ -1,24 +1,7 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
+ import { runGit } from "./runner.js";
3
4
  const execFileAsync = promisify(execFile);
4
- export async function runGit(cwd, args) {
5
- try {
6
- const { stdout } = await execFileAsync("git", args, { cwd });
7
- return stdout.trim();
8
- }
9
- catch (error) {
10
- const message = error instanceof Error ? error.message : String(error);
11
- throw new Error(`Git command failed in '${cwd}': ${message}`);
12
- }
13
- }
14
- export async function readWorktreeHealth(cwd) {
15
- const { stdout } = await execFileAsync("git", ["status", "--porcelain=v2", "--branch"], { cwd });
16
- return parseWorktreeHealth(stdout);
17
- }
18
- export async function isDirtyWorktree(cwd) {
19
- const health = await readWorktreeHealth(cwd);
20
- return health.status === "dirty";
21
- }
22
5
  export async function isBranchMergedInto(cwd, branch, base = "HEAD") {
23
6
  try {
24
7
  await execFileAsync("git", ["merge-base", "--is-ancestor", branch, base], {
@@ -104,39 +87,6 @@ export async function readBranchLastCommitTimestamp(cwd, branch) {
104
87
  return null;
105
88
  }
106
89
  }
107
- function parseWorktreeHealth(output) {
108
- let ahead = 0;
109
- let behind = 0;
110
- let hasUpstream = false;
111
- let hasAb = false;
112
- let dirty = false;
113
- for (const line of output.split("\n").filter(Boolean)) {
114
- if (line.startsWith("# branch.upstream ")) {
115
- hasUpstream = true;
116
- continue;
117
- }
118
- if (line.startsWith("# branch.ab ")) {
119
- const match = /^# branch\.ab \+(\d+) -(\d+)$/.exec(line);
120
- if (!match) {
121
- throw new Error(`Unexpected branch.ab output: '${line}'`);
122
- }
123
- hasAb = true;
124
- ahead = Number(match[1]);
125
- behind = Number(match[2]);
126
- continue;
127
- }
128
- if (!line.startsWith("# ")) {
129
- dirty = true;
130
- }
131
- }
132
- return {
133
- ahead,
134
- behind,
135
- hasUpstream,
136
- upstreamGone: hasUpstream && !hasAb,
137
- status: dirty ? "dirty" : "clean",
138
- };
139
- }
140
90
  function hasExitCode(error, code) {
141
91
  return (error instanceof Error &&
142
92
  "code" in error &&
@@ -0,0 +1,2 @@
1
+ export declare function runGit(cwd: string, args: string[]): Promise<string>;
2
+ export declare function runGitRaw(cwd: string, args: string[]): Promise<string>;
@@ -0,0 +1,16 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execFileAsync = promisify(execFile);
4
+ export async function runGit(cwd, args) {
5
+ return (await runGitRaw(cwd, args)).trim();
6
+ }
7
+ export async function runGitRaw(cwd, args) {
8
+ try {
9
+ const { stdout } = await execFileAsync("git", args, { cwd });
10
+ return stdout;
11
+ }
12
+ catch (error) {
13
+ const message = error instanceof Error ? error.message : String(error);
14
+ throw new Error(`Git command failed in '${cwd}': ${message}`);
15
+ }
16
+ }
@@ -1,8 +1,4 @@
1
- export interface EditorDefinition {
2
- cli: string;
3
- name: string;
4
- newWindowFlag?: string;
5
- supportsWorkspace: boolean;
6
- }
1
+ import type { EditorDefinition } from "../../ports/editor.js";
2
+ export type { EditorDefinition } from "../../ports/editor.js";
7
3
  export declare const EDITORS: EditorDefinition[];
8
4
  export declare function defaultSpawnEditor(cli: string, args: string[]): Promise<void>;
@@ -1,10 +1,6 @@
1
+ import type { PullRequestPort } from "../../ports/pull-requests.js";
1
2
  export type PullRequestForge = "bitbucket" | "github" | "gitlab";
2
- export interface PullRequestInfo {
3
- number: number;
4
- sourceBranch: string;
5
- title?: string;
6
- url: string;
7
- }
3
+ export type { PullRequestInfo } from "../../ports/pull-requests.js";
8
4
  export interface PullRequestRemote {
9
5
  forge: PullRequestForge;
10
6
  host: string;
@@ -25,10 +21,6 @@ export interface PullRequestQueryDependencies {
25
21
  fetch?: typeof fetch;
26
22
  runCommand?: PullRequestCommandRunner;
27
23
  }
28
- export interface PullRequestQuery {
29
- findOpenPullRequest: (repoRoot: string, number: number) => Promise<PullRequestInfo | null>;
30
- listOpenPullRequests: (repoRoot: string, sourceBranch: string) => Promise<PullRequestInfo[]>;
31
- listOpenPullRequestsForRepository: (repoRoot: string) => Promise<PullRequestInfo[]>;
32
- }
24
+ export type PullRequestQuery = PullRequestPort;
33
25
  export declare function parsePullRequestRemote(remoteUrl: string): PullRequestRemote | null;
34
26
  export declare function createPullRequestQuery(dependencies?: PullRequestQueryDependencies): PullRequestQuery;
@@ -1,23 +1,9 @@
1
+ import type { DependencyBootstrapSetting, EffectiveGjiConfig, GjiConfig } from "../../ports/config.js";
2
+ export type { DependencyBootstrapSetting, EffectiveGjiConfig, GjiConfig, } from "../../ports/config.js";
3
+ export { KNOWN_CONFIG_KEYS, KNOWN_GLOBAL_CONFIG_KEYS, } from "../../ports/config.js";
1
4
  export declare const CONFIG_FILE_NAME = ".gji.json";
2
5
  export declare const GLOBAL_CONFIG_DIRECTORY = ".config/gji";
3
6
  export declare const GLOBAL_CONFIG_NAME = "config.json";
4
- export declare const KNOWN_CONFIG_KEYS: ReadonlySet<string>;
5
- export declare const KNOWN_GLOBAL_CONFIG_KEYS: ReadonlySet<string>;
6
- export type GjiConfig = Record<string, unknown>;
7
- export type DependencyBootstrapSetting = "off";
8
- export interface EffectiveGjiConfig extends GjiConfig {
9
- branchPrefix?: string;
10
- dependencyBuildCommand?: string;
11
- dependencyBootstrap?: DependencyBootstrapSetting;
12
- editor?: string;
13
- hooks?: Record<string, unknown>;
14
- installSaveTarget?: string;
15
- shellIntegration?: string;
16
- syncFiles?: readonly string[];
17
- syncDefaultBranch?: string;
18
- syncRemote?: string;
19
- worktreePath?: string;
20
- }
21
7
  export interface LoadedConfig {
22
8
  config: GjiConfig;
23
9
  exists: boolean;
@@ -1,26 +1,11 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, join, resolve } from "node:path";
4
+ import { KNOWN_CONFIG_KEYS, KNOWN_GLOBAL_CONFIG_KEYS, } from "../../ports/config.js";
5
+ export { KNOWN_CONFIG_KEYS, KNOWN_GLOBAL_CONFIG_KEYS, } from "../../ports/config.js";
4
6
  export const CONFIG_FILE_NAME = ".gji.json";
5
7
  export const GLOBAL_CONFIG_DIRECTORY = ".config/gji";
6
8
  export const GLOBAL_CONFIG_NAME = "config.json";
7
- export const KNOWN_CONFIG_KEYS = new Set([
8
- "branchPrefix",
9
- "dependencyBuildCommand",
10
- "dependencyBootstrap",
11
- "editor",
12
- "hooks",
13
- "installSaveTarget",
14
- "shellIntegration",
15
- "syncDefaultBranch",
16
- "syncFiles",
17
- "syncRemote",
18
- "worktreePath",
19
- ]);
20
- export const KNOWN_GLOBAL_CONFIG_KEYS = new Set([
21
- ...KNOWN_CONFIG_KEYS,
22
- "repos",
23
- ]);
24
9
  const REMOVED_CONFIG_KEYS = new Set(["syncDirs"]);
25
10
  export const DEFAULT_CONFIG = Object.freeze({});
26
11
  export async function loadConfig(root) {
@@ -1,9 +1,6 @@
1
+ import type { HistoryEntry } from "../../ports/history.js";
2
+ export type { HistoryEntry } from "../../ports/history.js";
1
3
  export declare const HISTORY_FILE_NAME = "history.json";
2
- export interface HistoryEntry {
3
- branch: string | null;
4
- path: string;
5
- timestamp: number;
6
- }
7
4
  export declare function HISTORY_FILE_PATH(home?: string): string;
8
5
  export declare function loadHistory(home?: string): Promise<HistoryEntry[]>;
9
6
  export declare function appendHistory(path: string, branch: string | null, home?: string): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  import { readFile, unlink, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
- import { worktreeGitDir } from "./repo.js";
3
+ import { worktreeGitDir } from "../repository/context.js";
4
4
  export async function readTask(worktreePath) {
5
5
  try {
6
6
  const parsed = JSON.parse(await readFile(await taskPath(worktreePath), "utf8"));
@@ -0,0 +1,3 @@
1
+ import type { CommandRunner } from "../../ports/process.js";
2
+ export type { CommandRunner, CommandRunnerOptions, } from "../../ports/process.js";
3
+ export declare const runCommand: CommandRunner;
@@ -1,15 +1,5 @@
1
- export type GjiHookCommand = string | string[];
2
- export interface GjiHooks {
3
- "after-create"?: GjiHookCommand;
4
- "after-enter"?: GjiHookCommand;
5
- "before-remove"?: GjiHookCommand;
6
- }
7
- export interface HookContext {
8
- branch?: string;
9
- path: string;
10
- repo: string;
11
- slot?: number | null;
12
- }
1
+ import type { GjiHookCommand, GjiHooks, HookContext } from "../../ports/hooks.js";
2
+ export type { GjiHookCommand, GjiHooks, HookContext, } from "../../ports/hooks.js";
13
3
  export declare function runHook(hookCmd: GjiHookCommand | undefined, cwd: string, context: HookContext, stderr: (chunk: string) => void, stdout?: (chunk: string) => void): Promise<void>;
14
4
  export declare function interpolate(template: string, context: HookContext): string;
15
5
  export declare function extractHooks(config: Record<string, unknown>): GjiHooks;
@@ -0,0 +1,7 @@
1
+ import type { ConfigPort } from "../../ports/config.js";
2
+ import type { RepositoryContextPort, RepositoryRefPort, RepositoryRegistryPort, WorktreePort } from "../../ports/repository.js";
3
+ export declare const configPort: ConfigPort;
4
+ export declare const repositoryContextPort: RepositoryContextPort;
5
+ export declare const worktreePort: WorktreePort;
6
+ export declare const repositoryRefPort: RepositoryRefPort;
7
+ export declare const repositoryRegistryPort: RepositoryRegistryPort;
@@ -0,0 +1,23 @@
1
+ import { loadEffectiveConfig, resolveConfigString, } from "../persistence/config.js";
2
+ import { detectRepository } from "./context.js";
3
+ import { getRepositoryRemoteUrl, hasLocalBranch, hasRemoteBranch, } from "./refs.js";
4
+ import { loadRegistry } from "./registry.js";
5
+ import { listWorktrees } from "./worktrees.js";
6
+ export const configPort = {
7
+ loadEffectiveConfig,
8
+ resolveConfigString,
9
+ };
10
+ export const repositoryContextPort = {
11
+ detectRepository,
12
+ };
13
+ export const worktreePort = {
14
+ listWorktrees,
15
+ };
16
+ export const repositoryRefPort = {
17
+ getRepositoryRemoteUrl,
18
+ hasLocalBranch,
19
+ hasRemoteBranch,
20
+ };
21
+ export const repositoryRegistryPort = {
22
+ loadRegistry,
23
+ };
@@ -0,0 +1,3 @@
1
+ import type { RepositoryContext } from "../../domain/repository/context.js";
2
+ export declare function detectRepository(cwd: string): Promise<RepositoryContext>;
3
+ export declare function worktreeGitDir(worktreePath: string): Promise<string>;
@@ -0,0 +1,20 @@
1
+ import { basename, dirname, isAbsolute, resolve } from "node:path";
2
+ import { runGit } from "../git/runner.js";
3
+ export async function detectRepository(cwd) {
4
+ const currentRoot = await runGit(cwd, ["rev-parse", "--show-toplevel"]);
5
+ const rawCommonDir = await runGit(cwd, ["rev-parse", "--git-common-dir"]);
6
+ const gitCommonDir = isAbsolute(rawCommonDir)
7
+ ? rawCommonDir
8
+ : resolve(currentRoot, rawCommonDir);
9
+ const repoRoot = dirname(gitCommonDir);
10
+ return {
11
+ currentRoot,
12
+ isWorktree: currentRoot !== repoRoot,
13
+ repoName: basename(repoRoot),
14
+ repoRoot,
15
+ };
16
+ }
17
+ export async function worktreeGitDir(worktreePath) {
18
+ const rawGitDir = await runGit(worktreePath, ["rev-parse", "--git-dir"]);
19
+ return isAbsolute(rawGitDir) ? rawGitDir : resolve(worktreePath, rawGitDir);
20
+ }
@@ -0,0 +1,3 @@
1
+ export declare function hasLocalBranch(repoRoot: string, branch: string): Promise<boolean>;
2
+ export declare function hasRemoteBranch(repoRoot: string, remote: string, branch: string): Promise<boolean>;
3
+ export declare function getRepositoryRemoteUrl(repoRoot: string, remote: string): Promise<string | null>;
@@ -0,0 +1,37 @@
1
+ import { runGit } from "../git/runner.js";
2
+ export async function hasLocalBranch(repoRoot, branch) {
3
+ try {
4
+ await runGit(repoRoot, [
5
+ "show-ref",
6
+ "--verify",
7
+ "--quiet",
8
+ `refs/heads/${branch}`,
9
+ ]);
10
+ return true;
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
16
+ export async function hasRemoteBranch(repoRoot, remote, branch) {
17
+ try {
18
+ await runGit(repoRoot, [
19
+ "show-ref",
20
+ "--verify",
21
+ "--quiet",
22
+ `refs/remotes/${remote}/${branch}`,
23
+ ]);
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ export async function getRepositoryRemoteUrl(repoRoot, remote) {
31
+ try {
32
+ return await runGit(repoRoot, ["remote", "get-url", remote]);
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
@@ -1,8 +1,4 @@
1
- export interface RepoRegistryEntry {
2
- lastUsed: number;
3
- name: string;
4
- path: string;
5
- }
1
+ import type { RepoRegistryEntry } from "../../domain/repository/registry.js";
6
2
  export interface RegistryRemovalResult {
7
3
  removedPaths: string[];
8
4
  skippedPaths: string[];
@@ -3,7 +3,7 @@ import { constants } from "node:fs";
3
3
  import { access, mkdir, readFile, realpath, rename, rm, writeFile, } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { basename, dirname, join, resolve } from "node:path";
6
- import { GLOBAL_CONFIG_DIRECTORY } from "./config.js";
6
+ import { GLOBAL_CONFIG_DIRECTORY } from "../persistence/config.js";
7
7
  const REGISTRY_FILE_NAME = "repos.json";
8
8
  const MAX_REGISTRY_ENTRIES = 100;
9
9
  const registryLocks = new Map();
@@ -0,0 +1,2 @@
1
+ import type { WorktreeEntry } from "../../domain/worktree/types.js";
2
+ export declare function listWorktrees(cwd: string): Promise<WorktreeEntry[]>;
@@ -0,0 +1,48 @@
1
+ import { access } from "node:fs/promises";
2
+ import { runGit } from "../git/runner.js";
3
+ export async function listWorktrees(cwd) {
4
+ const [output, currentRoot] = await Promise.all([
5
+ runGit(cwd, ["worktree", "list", "--porcelain"]),
6
+ runGit(cwd, ["rev-parse", "--show-toplevel"]),
7
+ ]);
8
+ const entries = output.split("\n\n").filter(Boolean);
9
+ const worktrees = entries.flatMap((entry) => {
10
+ if (findOptionalPorcelainValue(entry, "prunable") !== null)
11
+ return [];
12
+ const path = findPorcelainValue(entry, "worktree");
13
+ const branchRef = findOptionalPorcelainValue(entry, "branch");
14
+ return [
15
+ {
16
+ branch: branchRef ? branchRef.replace("refs/heads/", "") : null,
17
+ isCurrent: path === currentRoot,
18
+ path,
19
+ },
20
+ ];
21
+ });
22
+ const accessible = await Promise.all(worktrees.map(async (worktree) => {
23
+ try {
24
+ await access(worktree.path);
25
+ return worktree;
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }));
31
+ return accessible.filter((worktree) => worktree !== null);
32
+ }
33
+ function findPorcelainValue(block, key) {
34
+ const value = findOptionalPorcelainValue(block, key);
35
+ if (!value) {
36
+ throw new Error(`Missing '${key}' in git worktree output.`);
37
+ }
38
+ return value;
39
+ }
40
+ function findOptionalPorcelainValue(block, key) {
41
+ const line = block
42
+ .split("\n")
43
+ .find((candidate) => candidate.startsWith(`${key} `));
44
+ if (!line) {
45
+ return null;
46
+ }
47
+ return line.slice(key.length + 1);
48
+ }
@@ -1,4 +1,4 @@
1
- import type { BootstrapExecutionContext, BootstrapTarget } from "./dependency-bootstrap.js";
1
+ import type { BootstrapExecutionContext, BootstrapTarget } from "../bootstrap/dependency-bootstrap.js";
2
2
  export declare function validateUvRelocation(target: BootstrapTarget, context: BootstrapExecutionContext): Promise<void>;
3
3
  export declare function validateUvInstallation(target: BootstrapTarget, context: BootstrapExecutionContext): Promise<void>;
4
4
  export declare function validateUvStructure(target: BootstrapTarget, context: BootstrapExecutionContext): Promise<void>;
@@ -3,7 +3,7 @@ import { constants } from "node:fs";
3
3
  import { lstat, open, readdir, realpath } from "node:fs/promises";
4
4
  import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
5
5
  import { promisify, TextDecoder } from "node:util";
6
- import { isNotFoundError } from "./fs-utils.js";
6
+ import { isNotFoundError } from "../filesystem/fs-utils.js";
7
7
  const execFileAsync = promisify(execFile);
8
8
  const UV_VALIDATION_MAX_ENTRIES = 10_000;
9
9
  const UV_VALIDATION_MAX_FILE_BYTES = 1024 * 1024;
@@ -0,0 +1,3 @@
1
+ import type { WorktreeBootstrapOptions, WorktreeBootstrapResult } from "../../ports/bootstrap.js";
2
+ export type { WorktreeBootstrapOptions, WorktreeBootstrapResult, } from "../../ports/bootstrap.js";
3
+ export declare function bootstrapWorktree(options: WorktreeBootstrapOptions): Promise<WorktreeBootstrapResult>;
@@ -1,8 +1,8 @@
1
1
  import { basename } from "node:path";
2
- import { executeDependencyBootstrap, prepareDependencyBootstrap, } from "./dependency-bootstrap.js";
3
- import { syncFiles } from "./file-sync.js";
4
- import { extractHooks, runHook } from "./hooks.js";
5
- import { assignWorktreeSlot } from "./slots.js";
2
+ import { executeDependencyBootstrap, prepareDependencyBootstrap, } from "../bootstrap/dependency-bootstrap.js";
3
+ import { syncFiles } from "../filesystem/file-sync.js";
4
+ import { assignWorktreeSlot } from "../persistence/slots.js";
5
+ import { extractHooks, runHook } from "../process/hooks.js";
6
6
  export async function bootstrapWorktree(options) {
7
7
  const dependencyMode = options.dependencyMode;
8
8
  const dependencyPlan = await prepareDependencyBootstrap(dependencyMode, {
@@ -19,8 +19,7 @@ export async function bootstrapWorktree(options) {
19
19
  }
20
20
  catch (error) {
21
21
  const message = `failed to sync file "${pattern}": ${toErrorMessage(error)}`;
22
- if (!options.json)
23
- options.reporter.write(`Warning: ${message}\n`);
22
+ options.commandStderr?.(`Warning: ${message}\n`);
24
23
  syncFileFailures.push({
25
24
  adapter: "syncFiles",
26
25
  kind: "sync-file",
@@ -39,7 +38,7 @@ export async function bootstrapWorktree(options) {
39
38
  ? { mode: dependencyMode, ready: false, events: [] }
40
39
  : await executeDependencyBootstrap(dependencyPlan, {
41
40
  reporter: options.reporter,
42
- stderr: options.commandStderr ?? options.reporter.write,
41
+ stderr: options.commandStderr,
43
42
  stdout: options.commandStdout,
44
43
  runCommand: options.runCommand,
45
44
  });
@@ -57,9 +56,7 @@ export async function bootstrapWorktree(options) {
57
56
  path: options.worktreePath,
58
57
  repo: basename(options.repoRoot),
59
58
  slot,
60
- }, options.reporter.write, options.json
61
- ? () => undefined
62
- : (options.commandStdout ?? ((chunk) => process.stdout.write(chunk))));
59
+ }, options.commandStderr ?? (() => undefined), options.commandStdout ?? (() => undefined));
63
60
  return {
64
61
  dependencyBootstrap,
65
62
  ready: true,
@@ -0,0 +1,6 @@
1
+ import type { SerializedWorktreeInfo, UpstreamState, WorktreeInfo } from "../../application/worktree/read-models.js";
2
+ import type { WorktreeEntry } from "../../domain/worktree/types.js";
3
+ export type { SerializedWorktreeInfo, UpstreamState, WorktreeInfo };
4
+ export declare function readWorktreeInfos(worktrees: WorktreeEntry[]): Promise<WorktreeInfo[]>;
5
+ export declare function readWorktreeInfo(worktree: WorktreeEntry): Promise<WorktreeInfo>;
6
+ export declare function serializeWorktreeInfo(info: WorktreeInfo): SerializedWorktreeInfo;