@solaqua/gji 0.6.2 → 0.7.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 (80) hide show
  1. package/README.md +26 -1
  2. package/dist/back.d.ts +1 -1
  3. package/dist/back.js +23 -17
  4. package/dist/clean.d.ts +1 -1
  5. package/dist/clean.js +44 -35
  6. package/dist/cli.d.ts +1 -1
  7. package/dist/cli.js +264 -164
  8. package/dist/completion.js +3 -3
  9. package/dist/config-command.js +5 -5
  10. package/dist/config.js +41 -35
  11. package/dist/conflict.d.ts +1 -1
  12. package/dist/conflict.js +14 -6
  13. package/dist/editor.js +29 -9
  14. package/dist/file-sync.d.ts +1 -0
  15. package/dist/file-sync.js +15 -11
  16. package/dist/git.d.ts +1 -1
  17. package/dist/git.js +21 -19
  18. package/dist/gji-bundle.mjs +1624 -819
  19. package/dist/go.d.ts +2 -2
  20. package/dist/go.js +39 -26
  21. package/dist/headless.js +1 -1
  22. package/dist/history-command.js +3 -3
  23. package/dist/history.js +12 -12
  24. package/dist/hooks.js +16 -16
  25. package/dist/index.js +13 -9
  26. package/dist/init.d.ts +2 -2
  27. package/dist/init.js +106 -94
  28. package/dist/install-prompt.d.ts +3 -3
  29. package/dist/install-prompt.js +46 -28
  30. package/dist/ls.d.ts +2 -2
  31. package/dist/ls.js +29 -29
  32. package/dist/new.d.ts +2 -2
  33. package/dist/new.js +96 -81
  34. package/dist/open.d.ts +2 -2
  35. package/dist/open.js +24 -21
  36. package/dist/package-manager.js +96 -45
  37. package/dist/pr.d.ts +2 -2
  38. package/dist/pr.js +47 -34
  39. package/dist/remove.d.ts +1 -1
  40. package/dist/remove.js +39 -27
  41. package/dist/repo-registry.js +14 -14
  42. package/dist/repo.js +29 -28
  43. package/dist/root.js +3 -3
  44. package/dist/shell-completion.d.ts +1 -1
  45. package/dist/shell-completion.js +65 -37
  46. package/dist/shell-handoff.js +2 -2
  47. package/dist/shell.d.ts +1 -1
  48. package/dist/shell.js +4 -4
  49. package/dist/status.d.ts +5 -5
  50. package/dist/status.js +23 -23
  51. package/dist/sync-files-command.d.ts +10 -0
  52. package/dist/sync-files-command.js +137 -0
  53. package/dist/sync.js +23 -15
  54. package/dist/trigger-hook.js +9 -5
  55. package/dist/warp.js +37 -33
  56. package/dist/worktree-info.d.ts +9 -9
  57. package/dist/worktree-info.js +31 -29
  58. package/dist/worktree-management.d.ts +1 -1
  59. package/dist/worktree-management.js +26 -11
  60. package/dist/worktree-prompts.js +5 -5
  61. package/man/man1/gji-back.1 +1 -1
  62. package/man/man1/gji-clean.1 +1 -1
  63. package/man/man1/gji-completion.1 +1 -1
  64. package/man/man1/gji-config.1 +1 -1
  65. package/man/man1/gji-go.1 +1 -1
  66. package/man/man1/gji-history.1 +1 -1
  67. package/man/man1/gji-init.1 +1 -1
  68. package/man/man1/gji-ls.1 +1 -1
  69. package/man/man1/gji-new.1 +1 -1
  70. package/man/man1/gji-open.1 +1 -1
  71. package/man/man1/gji-pr.1 +1 -1
  72. package/man/man1/gji-remove.1 +1 -1
  73. package/man/man1/gji-root.1 +1 -1
  74. package/man/man1/gji-status.1 +1 -1
  75. package/man/man1/gji-sync-files.1 +23 -0
  76. package/man/man1/gji-sync.1 +1 -1
  77. package/man/man1/gji-trigger-hook.1 +1 -1
  78. package/man/man1/gji-warp.1 +1 -1
  79. package/man/man1/gji.1 +5 -1
  80. package/package.json +8 -2
package/dist/warp.js CHANGED
@@ -1,20 +1,20 @@
1
- import { isCancel, select } from '@clack/prompts';
2
- import { realpath } from 'node:fs/promises';
3
- import { basename, resolve } from 'node:path';
4
- import { readWorktreeHealth } from './git.js';
5
- import { isHeadless } from './headless.js';
6
- import { appendHistory } from './history.js';
7
- import { runNewCommand } from './new.js';
8
- import { loadRegistry } from './repo-registry.js';
9
- import { listWorktrees } from './repo.js';
10
- import { writeShellOutput } from './shell-handoff.js';
11
- const WARP_OUTPUT_FILE_ENV = 'GJI_WARP_OUTPUT_FILE';
1
+ import { realpath } from "node:fs/promises";
2
+ import { basename, resolve } from "node:path";
3
+ import { isCancel, select } from "@clack/prompts";
4
+ import { readWorktreeHealth } from "./git.js";
5
+ import { isHeadless } from "./headless.js";
6
+ import { appendHistory } from "./history.js";
7
+ import { runNewCommand } from "./new.js";
8
+ import { listWorktrees } from "./repo.js";
9
+ import { loadRegistry } from "./repo-registry.js";
10
+ import { writeShellOutput } from "./shell-handoff.js";
11
+ const WARP_OUTPUT_FILE_ENV = "GJI_WARP_OUTPUT_FILE";
12
12
  export async function runWarpCommand(options) {
13
13
  if (options.newWorktree) {
14
14
  const registry = await loadRegistry();
15
15
  if (registry.length === 0) {
16
- options.stderr('gji warp: no repos registered yet.\n' +
17
- 'Use any gji command in a repository to register it automatically.\n');
16
+ options.stderr("gji warp: no repos registered yet.\n" +
17
+ "Use any gji command in a repository to register it automatically.\n");
18
18
  return 1;
19
19
  }
20
20
  return runWarpNew(options, registry);
@@ -23,16 +23,20 @@ export async function runWarpCommand(options) {
23
23
  }
24
24
  async function runWarpNavigate(options) {
25
25
  if ((isHeadless() || options.json) && !options.branch) {
26
- const message = 'branch argument is required';
26
+ const message = "branch argument is required";
27
27
  if (options.json) {
28
28
  options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
29
29
  }
30
30
  else {
31
- options.stderr('gji warp: branch argument is required in non-interactive mode (GJI_NO_TUI=1)\n');
31
+ options.stderr("gji warp: branch argument is required in non-interactive mode (GJI_NO_TUI=1)\n");
32
32
  }
33
33
  return 1;
34
34
  }
35
- const target = await resolveWarpTarget({ ...options, commandName: 'gji warp', json: options.json });
35
+ const target = await resolveWarpTarget({
36
+ ...options,
37
+ commandName: "gji warp",
38
+ json: options.json,
39
+ });
36
40
  if (!target)
37
41
  return 1;
38
42
  if (options.json) {
@@ -52,11 +56,11 @@ async function runWarpNew(options, registry) {
52
56
  }
53
57
  else {
54
58
  if (isHeadless()) {
55
- options.stderr('gji warp: repo argument is required in non-interactive mode (GJI_NO_TUI=1)\n');
59
+ options.stderr("gji warp: repo argument is required in non-interactive mode (GJI_NO_TUI=1)\n");
56
60
  return 1;
57
61
  }
58
62
  const choice = await select({
59
- message: 'Create worktree in which repo?',
63
+ message: "Create worktree in which repo?",
60
64
  options: deduplicatedRegistry.map((entry) => ({
61
65
  value: entry.path,
62
66
  label: entry.name,
@@ -64,7 +68,7 @@ async function runWarpNew(options, registry) {
64
68
  })),
65
69
  });
66
70
  if (isCancel(choice)) {
67
- options.stderr('Aborted\n');
71
+ options.stderr("Aborted\n");
68
72
  return 1;
69
73
  }
70
74
  targetRepoRoot = choice;
@@ -81,7 +85,7 @@ async function runWarpNew(options, registry) {
81
85
  // runNewCommand writes the created path to options.stdout via writeShellOutput.
82
86
  // Since GJI_NEW_OUTPUT_FILE is not set in the warp shell context, it falls
83
87
  // through to our captured stdout, giving us the path to hand off.
84
- let capturedPath = '';
88
+ let capturedPath = "";
85
89
  const captureStdout = (chunk) => {
86
90
  capturedPath = chunk.trim();
87
91
  };
@@ -95,7 +99,7 @@ async function runWarpNew(options, registry) {
95
99
  return exitCode;
96
100
  }
97
101
  if (!capturedPath) {
98
- options.stderr('gji warp: could not determine new worktree path\n');
102
+ options.stderr("gji warp: could not determine new worktree path\n");
99
103
  return 1;
100
104
  }
101
105
  await writeShellOutput(WARP_OUTPUT_FILE_ENV, capturedPath, options.stdout);
@@ -127,7 +131,7 @@ async function canonicalizeRepoPath(repoPath) {
127
131
  }
128
132
  }
129
133
  function findByQuery(items, query) {
130
- const slashIdx = query.indexOf('/');
134
+ const slashIdx = query.indexOf("/");
131
135
  if (slashIdx !== -1) {
132
136
  const repoQuery = query.slice(0, slashIdx);
133
137
  const branchQuery = query.slice(slashIdx + 1);
@@ -138,7 +142,7 @@ function findByQuery(items, query) {
138
142
  return items.find((item) => item.worktree.branch === query) ?? null;
139
143
  }
140
144
  export async function resolveWarpTarget(options) {
141
- const cmd = options.commandName ?? 'gji';
145
+ const cmd = options.commandName ?? "gji";
142
146
  const emitError = (message, hint) => {
143
147
  if (options.json) {
144
148
  options.stderr(`${JSON.stringify({ error: message }, null, 2)}\n`);
@@ -151,7 +155,7 @@ export async function resolveWarpTarget(options) {
151
155
  };
152
156
  const registry = await loadRegistry();
153
157
  if (registry.length === 0) {
154
- emitError('not in a git repository and no repos registered yet.', 'Use any gji command inside a repository to register it.\n');
158
+ emitError("not in a git repository and no repos registered yet.", "Use any gji command inside a repository to register it.\n");
155
159
  return null;
156
160
  }
157
161
  const results = await Promise.allSettled(registry.map(async (entry) => {
@@ -160,7 +164,7 @@ export async function resolveWarpTarget(options) {
160
164
  }));
161
165
  const allItems = [];
162
166
  for (const result of results) {
163
- if (result.status === 'rejected')
167
+ if (result.status === "rejected")
164
168
  continue;
165
169
  const { repoName, worktrees } = result.value;
166
170
  for (const worktree of worktrees) {
@@ -168,7 +172,7 @@ export async function resolveWarpTarget(options) {
168
172
  }
169
173
  }
170
174
  if (allItems.length === 0) {
171
- emitError('no accessible worktrees found in any registered repo.');
175
+ emitError("no accessible worktrees found in any registered repo.");
172
176
  return null;
173
177
  }
174
178
  if (options.branch) {
@@ -181,7 +185,7 @@ export async function resolveWarpTarget(options) {
181
185
  }
182
186
  const path = await promptForWarpTarget(allItems);
183
187
  if (!path) {
184
- options.stderr('Aborted\n');
188
+ options.stderr("Aborted\n");
185
189
  return null;
186
190
  }
187
191
  const chosen = allItems.find((item) => item.worktree.path === path);
@@ -190,11 +194,11 @@ export async function resolveWarpTarget(options) {
190
194
  async function promptForWarpTarget(items) {
191
195
  const healthResults = await Promise.allSettled(items.map((item) => readWorktreeHealth(item.worktree.path)));
192
196
  const choice = await select({
193
- message: 'Warp to a worktree',
197
+ message: "Warp to a worktree",
194
198
  options: items.map((item, i) => {
195
- const health = healthResults[i].status === 'fulfilled' ? healthResults[i].value : null;
199
+ const health = healthResults[i].status === "fulfilled" ? healthResults[i].value : null;
196
200
  const upstream = health ? formatHint(item.worktree.branch, health) : null;
197
- const label = `${item.repoName} › ${item.worktree.branch ?? '(detached)'}`;
201
+ const label = `${item.repoName} › ${item.worktree.branch ?? "(detached)"}`;
198
202
  const pathHint = item.worktree.isCurrent
199
203
  ? `${item.worktree.path} (current)`
200
204
  : item.worktree.path;
@@ -211,11 +215,11 @@ function formatHint(branch, health) {
211
215
  if (branch === null)
212
216
  return null;
213
217
  if (!health.hasUpstream)
214
- return 'no upstream';
218
+ return "no upstream";
215
219
  if (health.upstreamGone)
216
- return 'upstream gone';
220
+ return "upstream gone";
217
221
  if (health.ahead === 0 && health.behind === 0)
218
- return 'up to date';
222
+ return "up to date";
219
223
  if (health.ahead === 0)
220
224
  return `behind ${health.behind}`;
221
225
  if (health.behind === 0)
@@ -1,29 +1,29 @@
1
- import { type WorktreeHealth } from './git.js';
2
- import type { WorktreeEntry } from './repo.js';
1
+ import { type WorktreeHealth } from "./git.js";
2
+ import type { WorktreeEntry } from "./repo.js";
3
3
  export interface WorktreeInfo extends WorktreeEntry {
4
4
  lastCommitTimestamp: number | null;
5
- status: WorktreeHealth['status'] | 'unknown';
5
+ status: WorktreeHealth["status"] | "unknown";
6
6
  upstream: UpstreamState;
7
7
  }
8
8
  export interface SerializedWorktreeInfo {
9
9
  branch: string | null;
10
10
  lastCommitTimestamp: number | null;
11
11
  path: string;
12
- status: WorktreeInfo['status'];
12
+ status: WorktreeInfo["status"];
13
13
  upstream: UpstreamState;
14
14
  }
15
15
  export type UpstreamState = {
16
- kind: 'detached';
16
+ kind: "detached";
17
17
  } | {
18
- kind: 'no-upstream';
18
+ kind: "no-upstream";
19
19
  } | {
20
- kind: 'stale';
20
+ kind: "stale";
21
21
  } | {
22
- kind: 'tracked';
22
+ kind: "tracked";
23
23
  ahead: number;
24
24
  behind: number;
25
25
  } | {
26
- kind: 'unknown';
26
+ kind: "unknown";
27
27
  };
28
28
  export declare function readWorktreeInfos(worktrees: WorktreeEntry[]): Promise<WorktreeInfo[]>;
29
29
  export declare function serializeWorktreeInfo(info: WorktreeInfo): SerializedWorktreeInfo;
@@ -1,40 +1,40 @@
1
- import { readBranchLastCommitTimestamp, readWorktreeHealth } from './git.js';
1
+ import { readBranchLastCommitTimestamp, readWorktreeHealth, } from "./git.js";
2
2
  export async function readWorktreeInfos(worktrees) {
3
3
  return Promise.all(worktrees.map((worktree) => readWorktreeInfo(worktree)));
4
4
  }
5
5
  async function readWorktreeInfo(worktree) {
6
6
  const [healthResult, lastCommitResult] = await Promise.allSettled([
7
7
  readWorktreeHealth(worktree.path),
8
- worktree.branch === null ? null : readBranchLastCommitTimestamp(worktree.path, worktree.branch),
8
+ worktree.branch === null
9
+ ? null
10
+ : readBranchLastCommitTimestamp(worktree.path, worktree.branch),
9
11
  ]);
10
- const health = healthResult.status === 'fulfilled' ? healthResult.value : null;
11
- const lastCommitTimestamp = lastCommitResult.status === 'fulfilled'
12
- ? lastCommitResult.value
13
- : null;
12
+ const health = healthResult.status === "fulfilled" ? healthResult.value : null;
13
+ const lastCommitTimestamp = lastCommitResult.status === "fulfilled" ? lastCommitResult.value : null;
14
14
  return {
15
15
  ...worktree,
16
16
  lastCommitTimestamp,
17
- status: health?.status ?? 'unknown',
17
+ status: health?.status ?? "unknown",
18
18
  upstream: buildUpstreamState(worktree.branch, health),
19
19
  };
20
20
  }
21
21
  function buildUpstreamState(branch, health) {
22
22
  if (branch === null) {
23
- return { kind: 'detached' };
23
+ return { kind: "detached" };
24
24
  }
25
25
  if (health === null) {
26
- return { kind: 'unknown' };
26
+ return { kind: "unknown" };
27
27
  }
28
28
  if (!health.hasUpstream) {
29
- return { kind: 'no-upstream' };
29
+ return { kind: "no-upstream" };
30
30
  }
31
31
  if (health.upstreamGone) {
32
- return { kind: 'stale' };
32
+ return { kind: "stale" };
33
33
  }
34
34
  return {
35
35
  ahead: health.ahead,
36
36
  behind: health.behind,
37
- kind: 'tracked',
37
+ kind: "tracked",
38
38
  };
39
39
  }
40
40
  export function serializeWorktreeInfo(info) {
@@ -54,26 +54,26 @@ export function formatWorktreeHint(info) {
54
54
  if (info.lastCommitTimestamp !== null) {
55
55
  details.push(`last: ${formatRelativeAge(info.lastCommitTimestamp)}`);
56
56
  }
57
- return `${info.path} (${details.join(', ')})`;
57
+ return `${info.path} (${details.join(", ")})`;
58
58
  }
59
59
  export function formatUpstreamState(upstream) {
60
- if (upstream.kind === 'detached') {
61
- return 'n/a';
60
+ if (upstream.kind === "detached") {
61
+ return "n/a";
62
62
  }
63
- if (upstream.kind === 'no-upstream') {
64
- return 'no-upstream';
63
+ if (upstream.kind === "no-upstream") {
64
+ return "no-upstream";
65
65
  }
66
- if (upstream.kind === 'stale') {
67
- return 'gone';
66
+ if (upstream.kind === "stale") {
67
+ return "gone";
68
68
  }
69
- if (upstream.kind === 'unknown') {
70
- return 'unknown';
69
+ if (upstream.kind === "unknown") {
70
+ return "unknown";
71
71
  }
72
72
  return formatAheadBehind(upstream.ahead, upstream.behind);
73
73
  }
74
74
  function formatAheadBehind(ahead, behind) {
75
75
  if (ahead === 0 && behind === 0) {
76
- return 'up to date';
76
+ return "up to date";
77
77
  }
78
78
  if (ahead === 0) {
79
79
  return `behind ${behind}`;
@@ -84,16 +84,18 @@ function formatAheadBehind(ahead, behind) {
84
84
  return `ahead ${ahead}, behind ${behind}`;
85
85
  }
86
86
  export function formatLastCommit(timestampSeconds) {
87
- return timestampSeconds === null ? 'n/a' : formatRelativeAge(timestampSeconds);
87
+ return timestampSeconds === null
88
+ ? "n/a"
89
+ : formatRelativeAge(timestampSeconds);
88
90
  }
89
91
  export function formatRelativeAge(timestampSeconds) {
90
92
  const ageSeconds = Math.max(0, Math.floor(Date.now() / 1000) - timestampSeconds);
91
93
  const units = [
92
- { label: 'y', seconds: 365 * 24 * 60 * 60 },
93
- { label: 'mo', seconds: 30 * 24 * 60 * 60 },
94
- { label: 'd', seconds: 24 * 60 * 60 },
95
- { label: 'h', seconds: 60 * 60 },
96
- { label: 'm', seconds: 60 },
94
+ { label: "y", seconds: 365 * 24 * 60 * 60 },
95
+ { label: "mo", seconds: 30 * 24 * 60 * 60 },
96
+ { label: "d", seconds: 24 * 60 * 60 },
97
+ { label: "h", seconds: 60 * 60 },
98
+ { label: "m", seconds: 60 },
97
99
  ];
98
100
  for (const unit of units) {
99
101
  const value = Math.floor(ageSeconds / unit.seconds);
@@ -101,5 +103,5 @@ export function formatRelativeAge(timestampSeconds) {
101
103
  return `${value}${unit.label} ago`;
102
104
  }
103
105
  }
104
- return 'just now';
106
+ return "just now";
105
107
  }
@@ -1,4 +1,4 @@
1
- import { type RepositoryContext, type WorktreeEntry } from './repo.js';
1
+ import { type RepositoryContext, type WorktreeEntry } from "./repo.js";
2
2
  export interface LinkedWorktreeContext {
3
3
  linkedWorktrees: WorktreeEntry[];
4
4
  repository: RepositoryContext;
@@ -1,9 +1,9 @@
1
- import { execFile } from 'node:child_process';
2
- import { promisify } from 'node:util';
3
- import { detectRepository, listWorktrees } from './repo.js';
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { detectRepository, listWorktrees, } from "./repo.js";
4
4
  const execFileAsync = promisify(execFile);
5
5
  // Force English output so error message string matching is locale-independent.
6
- const GIT_ENV = { ...process.env, LC_ALL: 'C' };
6
+ const GIT_ENV = { ...process.env, LC_ALL: "C" };
7
7
  export async function loadLinkedWorktrees(cwd) {
8
8
  const repository = await detectRepository(cwd);
9
9
  const linkedWorktrees = (await listWorktrees(cwd)).filter((worktree) => worktree.path !== repository.repoRoot);
@@ -13,23 +13,38 @@ export async function loadLinkedWorktrees(cwd) {
13
13
  };
14
14
  }
15
15
  export async function removeWorktree(repoRoot, worktreePath) {
16
- await execFileAsync('git', ['worktree', 'remove', worktreePath], { cwd: repoRoot, env: GIT_ENV });
16
+ await execFileAsync("git", ["worktree", "remove", worktreePath], {
17
+ cwd: repoRoot,
18
+ env: GIT_ENV,
19
+ });
17
20
  }
18
21
  export async function forceRemoveWorktree(repoRoot, worktreePath) {
19
- await execFileAsync('git', ['worktree', 'remove', '--force', worktreePath], { cwd: repoRoot, env: GIT_ENV });
22
+ await execFileAsync("git", ["worktree", "remove", "--force", worktreePath], {
23
+ cwd: repoRoot,
24
+ env: GIT_ENV,
25
+ });
20
26
  }
21
27
  export async function deleteBranch(repoRoot, branch) {
22
- await execFileAsync('git', ['branch', '-d', branch], { cwd: repoRoot, env: GIT_ENV });
28
+ await execFileAsync("git", ["branch", "-d", branch], {
29
+ cwd: repoRoot,
30
+ env: GIT_ENV,
31
+ });
23
32
  }
24
33
  export async function forceDeleteBranch(repoRoot, branch) {
25
- await execFileAsync('git', ['branch', '-D', branch], { cwd: repoRoot, env: GIT_ENV });
34
+ await execFileAsync("git", ["branch", "-D", branch], {
35
+ cwd: repoRoot,
36
+ env: GIT_ENV,
37
+ });
26
38
  }
27
39
  export function isWorktreeDirtyError(error) {
28
- return hasStderr(error) && error.stderr.includes('contains modified or untracked files');
40
+ return (hasStderr(error) &&
41
+ error.stderr.includes("contains modified or untracked files"));
29
42
  }
30
43
  export function isBranchUnmergedError(error) {
31
- return hasStderr(error) && error.stderr.includes('is not fully merged');
44
+ return hasStderr(error) && error.stderr.includes("is not fully merged");
32
45
  }
33
46
  function hasStderr(error) {
34
- return error instanceof Error && 'stderr' in error && typeof error.stderr === 'string';
47
+ return (error instanceof Error &&
48
+ "stderr" in error &&
49
+ typeof error.stderr === "string");
35
50
  }
@@ -1,8 +1,8 @@
1
- import { confirm, isCancel } from '@clack/prompts';
1
+ import { confirm, isCancel } from "@clack/prompts";
2
2
  export async function defaultConfirmForceRemoveWorktree(worktreePath) {
3
3
  const choice = await confirm({
4
- active: 'Yes',
5
- inactive: 'No',
4
+ active: "Yes",
5
+ inactive: "No",
6
6
  initialValue: false,
7
7
  message: `Worktree at ${worktreePath} has untracked or modified files. Force remove?`,
8
8
  });
@@ -10,8 +10,8 @@ export async function defaultConfirmForceRemoveWorktree(worktreePath) {
10
10
  }
11
11
  export async function defaultConfirmForceDeleteBranch(branch) {
12
12
  const choice = await confirm({
13
- active: 'Yes',
14
- inactive: 'No',
13
+ active: "Yes",
14
+ inactive: "No",
15
15
  initialValue: false,
16
16
  message: `Branch ${branch} has unmerged commits. Force delete?`,
17
17
  });
@@ -1,4 +1,4 @@
1
- .TH GJI\-BACK 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-BACK 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-back \- navigate to the previously visited worktree, optionally N steps back
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-CLEAN 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-CLEAN 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-clean \- interactively prune linked worktrees
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-COMPLETION 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-COMPLETION 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-completion \- print shell completion definitions
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-CONFIG 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-CONFIG 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-config \- manage global config defaults
4
4
  .SH SYNOPSIS
package/man/man1/gji-go.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH GJI\-GO 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-GO 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-go \- print or select a worktree path
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-HISTORY 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-HISTORY 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-history \- show navigation history
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-INIT 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-INIT 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-init \- print or install shell integration
4
4
  .SH SYNOPSIS
package/man/man1/gji-ls.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH GJI\-LS 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-LS 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-ls \- list active worktrees
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-NEW 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-NEW 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-new \- create a new branch or detached linked worktree
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-OPEN 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-OPEN 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-open \- open the worktree in an editor
4
4
  .SH SYNOPSIS
package/man/man1/gji-pr.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH GJI\-PR 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-PR 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-pr \- fetch a pull request by number, #number, or URL into a linked worktree
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-REMOVE 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-REMOVE 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-remove \- remove a linked worktree and delete its branch when present
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-ROOT 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-ROOT 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-root \- print the main repository root path
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-STATUS 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-STATUS 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-status \- summarize repository and worktree health
4
4
  .SH SYNOPSIS
@@ -0,0 +1,23 @@
1
+ .TH GJI\-SYNC\-FILES 1 "May 2026" "gji 0.7.0" "User Commands"
2
+ .SH NAME
3
+ gji\-sync\-files \- manage local files copied into new worktrees
4
+ .SH SYNOPSIS
5
+ .B gji sync\-files [\fIoptions\fR] [options] [command]
6
+ .SH DESCRIPTION
7
+ manage local files copied into new worktrees
8
+ .SH OPTIONS
9
+ .TP
10
+ .B \-\-json
11
+ emit JSON instead of human\-readable output
12
+ .SH SUBCOMMANDS
13
+ .TP
14
+ .B list
15
+ list files synced into new worktrees for this repo
16
+ .TP
17
+ .B add
18
+ add repo\-local sync files to global config
19
+ .TP
20
+ .B remove
21
+ remove repo\-local sync files from global config
22
+ .SH "SEE ALSO"
23
+ .BR gji (1)
@@ -1,4 +1,4 @@
1
- .TH GJI\-SYNC 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-SYNC 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-sync \- fetch and update one or all worktrees
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-TRIGGER\-HOOK 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-TRIGGER\-HOOK 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-trigger\-hook \- run a named hook (afterCreate, afterEnter, beforeRemove) in the current worktree
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-WARP 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI\-WARP 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji\-warp \- jump to any worktree across all known repos
4
4
  .SH SYNOPSIS
package/man/man1/gji.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH GJI 1 "May 2026" "gji 0.6.2" "User Commands"
1
+ .TH GJI 1 "May 2026" "gji 0.7.0" "User Commands"
2
2
  .SH NAME
3
3
  gji \- Context switching without the mess.
4
4
  .SH SYNOPSIS
@@ -46,6 +46,9 @@ summarize repository and worktree health
46
46
  .B sync [options]
47
47
  fetch and update one or all worktrees
48
48
  .TP
49
+ .B sync\-files [options] [command]
50
+ manage local files copied into new worktrees
51
+ .TP
49
52
  .B ls [options]
50
53
  list active worktrees
51
54
  .TP
@@ -81,6 +84,7 @@ output the version number
81
84
  .BR gji\-root (1),
82
85
  .BR gji\-status (1),
83
86
  .BR gji\-sync (1),
87
+ .BR gji\-sync\-files (1),
84
88
  .BR gji\-ls (1),
85
89
  .BR gji\-clean (1),
86
90
  .BR gji\-remove (1),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solaqua/gji",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "description": "Git worktree CLI for fast context switching.",
5
5
  "license": "MIT",
6
6
  "author": "sjquant",
@@ -49,7 +49,11 @@
49
49
  "docs:start": "pnpm --dir website start",
50
50
  "prepublishOnly": "pnpm test && pnpm build && pnpm generate-man",
51
51
  "test": "vitest run --passWithNoTests",
52
- "test:watch": "vitest"
52
+ "test:watch": "vitest",
53
+ "lint": "biome lint --write .",
54
+ "format": "biome format --write .",
55
+ "typecheck": "tsc --noEmit",
56
+ "prepare": "prek install || true"
53
57
  },
54
58
  "dependencies": {
55
59
  "@clack/core": "0.5.0",
@@ -58,6 +62,8 @@
58
62
  "update-notifier": "^7.3.1"
59
63
  },
60
64
  "devDependencies": {
65
+ "@biomejs/biome": "^2.4.15",
66
+ "@j178/prek": "^0.3.13",
61
67
  "@types/node": "^24.6.0",
62
68
  "esbuild": "^0.27.0",
63
69
  "typescript": "^5.9.3",