@solaqua/gji 0.7.0 → 0.7.1

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.
package/dist/back.js CHANGED
@@ -39,7 +39,7 @@ export async function runBackCommand(options) {
39
39
  const repository = await detectRepository(target.path);
40
40
  const config = await loadEffectiveConfig(repository.repoRoot, options.home, options.stderr);
41
41
  const hooks = extractHooks(config);
42
- await runHook(hooks.afterEnter, target.path, {
42
+ await runHook(hooks["after-enter"], target.path, {
43
43
  branch: target.branch ?? undefined,
44
44
  path: target.path,
45
45
  repo: basename(repository.repoRoot),
package/dist/cli.js CHANGED
@@ -17,10 +17,10 @@ import { runRemoveCommand } from "./remove.js";
17
17
  import { detectRepository } from "./repo.js";
18
18
  import { registerRepo } from "./repo-registry.js";
19
19
  import { runRootCommand } from "./root.js";
20
+ import { runHookCommand } from "./run-hook.js";
20
21
  import { runStatusCommand } from "./status.js";
21
22
  import { runSyncCommand } from "./sync.js";
22
23
  import { runSyncFilesCommand } from "./sync-files-command.js";
23
- import { runTriggerHookCommand } from "./trigger-hook.js";
24
24
  import { runWarpCommand } from "./warp.js";
25
25
  export function createProgram() {
26
26
  const program = new Command();
@@ -214,9 +214,10 @@ function registerCommands(program) {
214
214
  .option("--json", "emit JSON on success or error instead of human-readable output")
215
215
  .action(notImplemented("remove"));
216
216
  program
217
- .command("trigger-hook <hook>")
218
- .description("run a named hook (afterCreate, afterEnter, beforeRemove) in the current worktree")
219
- .action(notImplemented("trigger-hook"));
217
+ .command("run-hook <hook>")
218
+ .alias("trigger-hook")
219
+ .description("run a named hook (after-create, after-enter, before-remove) in the current worktree")
220
+ .action(notImplemented("run-hook"));
220
221
  program
221
222
  .command("warp [branch]")
222
223
  .description("jump to any worktree across all known repos")
@@ -506,9 +507,9 @@ function attachCommandActions(program, options) {
506
507
  .find((command) => command.name() === "remove")
507
508
  ?.action(runRemovalCommand);
508
509
  program.commands
509
- .find((command) => command.name() === "trigger-hook")
510
+ .find((command) => command.name() === "run-hook")
510
511
  ?.action(async (hook) => {
511
- const exitCode = await runTriggerHookCommand({
512
+ const exitCode = await runHookCommand({
512
513
  cwd: options.cwd,
513
514
  hook,
514
515
  stderr: options.stderr,
@@ -13176,9 +13176,13 @@ function extractHooks(config) {
13176
13176
  }
13177
13177
  const hooks = raw;
13178
13178
  return {
13179
- afterCreate: parseHookCommand(hooks.afterCreate),
13180
- afterEnter: parseHookCommand(hooks.afterEnter),
13181
- beforeRemove: parseHookCommand(hooks.beforeRemove)
13179
+ "after-create": parseHookCommand(
13180
+ hooks["after-create"] ?? hooks.afterCreate
13181
+ ),
13182
+ "after-enter": parseHookCommand(hooks["after-enter"] ?? hooks.afterEnter),
13183
+ "before-remove": parseHookCommand(
13184
+ hooks["before-remove"] ?? hooks.beforeRemove
13185
+ )
13182
13186
  };
13183
13187
  }
13184
13188
  function parseHookCommand(value) {
@@ -13450,7 +13454,7 @@ async function runBackCommand(options) {
13450
13454
  );
13451
13455
  const hooks = extractHooks(config);
13452
13456
  await runHook(
13453
- hooks.afterEnter,
13457
+ hooks["after-enter"],
13454
13458
  target.path,
13455
13459
  {
13456
13460
  branch: target.branch ?? void 0,
@@ -14623,14 +14627,14 @@ var TOP_LEVEL_COMMANDS = [
14623
14627
  },
14624
14628
  { name: "rm", description: "alias of remove" },
14625
14629
  {
14626
- name: "trigger-hook",
14630
+ name: "run-hook",
14627
14631
  description: "run a named hook in the current worktree"
14628
14632
  },
14629
14633
  { name: "warp", description: "jump to any worktree across all known repos" },
14630
14634
  { name: "config", description: "manage global config defaults" }
14631
14635
  ];
14632
14636
  var SHELL_NAMES = ["bash", "fish", "zsh"];
14633
- var HOOK_NAMES = ["afterCreate", "afterEnter", "beforeRemove"];
14637
+ var HOOK_NAMES = ["after-create", "after-enter", "before-remove"];
14634
14638
  var CONFIG_KEYS = Array.from(KNOWN_GLOBAL_CONFIG_KEYS);
14635
14639
  function renderShellCompletion(shell) {
14636
14640
  switch (shell) {
@@ -14714,7 +14718,7 @@ _gji_completion() {
14714
14718
  remove|rm)
14715
14719
  COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) -f --force --dry-run --json --help" -- "$cur") )
14716
14720
  ;;
14717
- trigger-hook)
14721
+ run-hook)
14718
14722
  COMPREPLY=( $(compgen -W "${hooks} --help" -- "$cur") )
14719
14723
  ;;
14720
14724
  warp)
@@ -14752,7 +14756,7 @@ function renderFishCompletion() {
14752
14756
  (shell) => `complete -c gji -n '__fish_seen_subcommand_from init' -a '${shell}' -d 'shell'`
14753
14757
  ).join("\n");
14754
14758
  const hookLines = HOOK_NAMES.map(
14755
- (hook) => `complete -c gji -n '__fish_seen_subcommand_from trigger-hook' -a '${hook}' -d 'hook'`
14759
+ (hook) => `complete -c gji -n '__fish_seen_subcommand_from run-hook' -a '${hook}' -d 'hook'`
14756
14760
  ).join("\n");
14757
14761
  const configKeyLines = CONFIG_KEYS.map(
14758
14762
  (key) => `complete -c gji -n '__gji_should_complete_config_key' -a '${key}' -d 'config key'`
@@ -14917,7 +14921,7 @@ case "\${words[2]}" in
14917
14921
  remove|rm)
14918
14922
  _arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch]' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch:->worktrees'
14919
14923
  ;;
14920
- trigger-hook)
14924
+ run-hook)
14921
14925
  _arguments "2:hook:(${hooks})"
14922
14926
  ;;
14923
14927
  warp)
@@ -15303,8 +15307,7 @@ async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stderr, dep
15303
15307
  if (isHeadless() || nonInteractive) {
15304
15308
  return;
15305
15309
  }
15306
- const hooks = isPlainObject2(config.hooks) ? config.hooks : null;
15307
- if (isConfiguredHookCommand(hooks?.afterCreate)) {
15310
+ if (extractHooks(config)["after-create"]) {
15308
15311
  return;
15309
15312
  }
15310
15313
  if (config.skipInstallPrompt === true) {
@@ -15337,17 +15340,28 @@ async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stderr, dep
15337
15340
  if (choice === "always") {
15338
15341
  try {
15339
15342
  if (saveGlobal) {
15340
- const existingHooks = await loadExistingGlobalRepoHooks(repoRoot);
15343
+ const existingRaw = await loadExistingGlobalRepoHooks(repoRoot);
15344
+ const existing = extractHooks({ hooks: existingRaw });
15341
15345
  await writeGlobalKey(repoRoot, "hooks", {
15342
- ...existingHooks,
15343
- afterCreate: pm.installCommand
15346
+ ...existing["after-enter"] !== void 0 && {
15347
+ "after-enter": existing["after-enter"]
15348
+ },
15349
+ ...existing["before-remove"] !== void 0 && {
15350
+ "before-remove": existing["before-remove"]
15351
+ },
15352
+ "after-create": pm.installCommand
15344
15353
  });
15345
15354
  } else {
15346
15355
  const { config: localConfig } = await loadConfig(repoRoot);
15347
- const existingLocalHooks = isPlainObject2(localConfig.hooks) ? localConfig.hooks : {};
15356
+ const existing = extractHooks(localConfig);
15348
15357
  await writeKey(repoRoot, "hooks", {
15349
- ...existingLocalHooks,
15350
- afterCreate: pm.installCommand
15358
+ ...existing["after-enter"] !== void 0 && {
15359
+ "after-enter": existing["after-enter"]
15360
+ },
15361
+ ...existing["before-remove"] !== void 0 && {
15362
+ "before-remove": existing["before-remove"]
15363
+ },
15364
+ "after-create": pm.installCommand
15351
15365
  });
15352
15366
  }
15353
15367
  } catch (error) {
@@ -15412,7 +15426,7 @@ async function defaultPromptForInstallChoice(pm) {
15412
15426
  options: [
15413
15427
  { value: "yes", label: "Yes", hint: "run once" },
15414
15428
  { value: "no", label: "No", hint: "skip this time" },
15415
- { value: "always", label: "Always", hint: "save as afterCreate hook" },
15429
+ { value: "always", label: "Always", hint: "save as after-create hook" },
15416
15430
  {
15417
15431
  value: "never",
15418
15432
  label: "Never",
@@ -15428,10 +15442,6 @@ async function defaultPromptForInstallChoice(pm) {
15428
15442
  function isPlainObject2(value) {
15429
15443
  return typeof value === "object" && value !== null && !Array.isArray(value);
15430
15444
  }
15431
- function isConfiguredHookCommand(value) {
15432
- if (typeof value === "string") return value.length > 0;
15433
- return Array.isArray(value) && value.length > 0 && value[0] !== "" && value.every((item) => typeof item === "string");
15434
- }
15435
15445
 
15436
15446
  // src/new.ts
15437
15447
  var execFileAsync3 = promisify4(execFile3);
@@ -15549,7 +15559,7 @@ function createNewCommand(dependencies = {}) {
15549
15559
  `
15550
15560
  );
15551
15561
  options.stderr(
15552
- `Hint: Use 'gji trigger-hook afterCreate' inside the worktree to re-run setup hooks
15562
+ `Hint: Use 'gji run-hook after-create' inside the worktree to re-run setup hooks
15553
15563
  `
15554
15564
  );
15555
15565
  }
@@ -15610,7 +15620,7 @@ function createNewCommand(dependencies = {}) {
15610
15620
  );
15611
15621
  const hooks = extractHooks(config);
15612
15622
  await runHook(
15613
- hooks.afterCreate,
15623
+ hooks["after-create"],
15614
15624
  worktreePath,
15615
15625
  {
15616
15626
  branch: worktreeName,
@@ -16116,7 +16126,7 @@ function createGoCommand(dependencies = {}) {
16116
16126
  );
16117
16127
  const hooks = extractHooks(config);
16118
16128
  await runHook(
16119
- hooks.afterEnter,
16129
+ hooks["after-enter"],
16120
16130
  resolvedPath,
16121
16131
  {
16122
16132
  branch: chosenWorktree?.branch ?? void 0,
@@ -16329,10 +16339,12 @@ async function saveWizardConfig(result, cwd, home) {
16329
16339
  if (result.branchPrefix) values.branchPrefix = result.branchPrefix;
16330
16340
  if (result.worktreePath) values.worktreePath = result.worktreePath;
16331
16341
  const hooks = {};
16332
- if (result.hooks?.afterCreate) hooks.afterCreate = result.hooks.afterCreate;
16333
- if (result.hooks?.afterEnter) hooks.afterEnter = result.hooks.afterEnter;
16334
- if (result.hooks?.beforeRemove)
16335
- hooks.beforeRemove = result.hooks.beforeRemove;
16342
+ if (result.hooks?.["after-create"])
16343
+ hooks["after-create"] = result.hooks["after-create"];
16344
+ if (result.hooks?.["after-enter"])
16345
+ hooks["after-enter"] = result.hooks["after-enter"];
16346
+ if (result.hooks?.["before-remove"])
16347
+ hooks["before-remove"] = result.hooks["before-remove"];
16336
16348
  if (Object.keys(hooks).length > 0) values.hooks = hooks;
16337
16349
  if (Object.keys(values).length === 0) return;
16338
16350
  if (result.installSaveTarget === "local") {
@@ -16463,7 +16475,7 @@ async function defaultPromptForSetup() {
16463
16475
  return null;
16464
16476
  }
16465
16477
  const afterCreate = await he({
16466
- message: "afterCreate hook \u2014 run after creating a worktree?",
16478
+ message: "after-create hook \u2014 run after creating a worktree?",
16467
16479
  placeholder: "e.g. pnpm install \u2014 leave blank to skip"
16468
16480
  });
16469
16481
  if (pD(afterCreate)) {
@@ -16471,7 +16483,7 @@ async function defaultPromptForSetup() {
16471
16483
  return null;
16472
16484
  }
16473
16485
  const afterEnter = await he({
16474
- message: "afterEnter hook \u2014 run after entering a worktree?",
16486
+ message: "after-enter hook \u2014 run after entering a worktree?",
16475
16487
  placeholder: "e.g. nvm use \u2014 leave blank to skip"
16476
16488
  });
16477
16489
  if (pD(afterEnter)) {
@@ -16479,7 +16491,7 @@ async function defaultPromptForSetup() {
16479
16491
  return null;
16480
16492
  }
16481
16493
  const beforeRemove = await he({
16482
- message: "beforeRemove hook \u2014 run before removing a worktree?",
16494
+ message: "before-remove hook \u2014 run before removing a worktree?",
16483
16495
  placeholder: "leave blank to skip"
16484
16496
  });
16485
16497
  if (pD(beforeRemove)) {
@@ -16488,9 +16500,9 @@ async function defaultPromptForSetup() {
16488
16500
  }
16489
16501
  Se("Setup complete!");
16490
16502
  const hooks = {};
16491
- if (afterCreate) hooks.afterCreate = afterCreate;
16492
- if (afterEnter) hooks.afterEnter = afterEnter;
16493
- if (beforeRemove) hooks.beforeRemove = beforeRemove;
16503
+ if (afterCreate) hooks["after-create"] = afterCreate;
16504
+ if (afterEnter) hooks["after-enter"] = afterEnter;
16505
+ if (beforeRemove) hooks["before-remove"] = beforeRemove;
16494
16506
  return {
16495
16507
  branchPrefix: branchPrefix || void 0,
16496
16508
  hooks: Object.keys(hooks).length > 0 ? hooks : void 0,
@@ -16899,7 +16911,7 @@ function createPrCommand(dependencies = {}) {
16899
16911
  );
16900
16912
  const hooks = extractHooks(config);
16901
16913
  await runHook(
16902
- hooks.afterCreate,
16914
+ hooks["after-create"],
16903
16915
  worktreePath,
16904
16916
  {
16905
16917
  branch: branchName,
@@ -17062,7 +17074,7 @@ function createRemoveCommand(dependencies = {}) {
17062
17074
  );
17063
17075
  const hooks = extractHooks(config);
17064
17076
  await runHook(
17065
- hooks.beforeRemove,
17077
+ hooks["before-remove"],
17066
17078
  worktree.path,
17067
17079
  {
17068
17080
  branch: worktree.branch ?? void 0,
@@ -17180,6 +17192,54 @@ async function runRootCommand(options) {
17180
17192
  return 0;
17181
17193
  }
17182
17194
 
17195
+ // src/run-hook.ts
17196
+ var VALID_HOOKS = [
17197
+ "after-create",
17198
+ "after-enter",
17199
+ "before-remove"
17200
+ ];
17201
+ var CAMEL_ALIASES = {
17202
+ afterCreate: "after-create",
17203
+ afterEnter: "after-enter",
17204
+ beforeRemove: "before-remove"
17205
+ };
17206
+ function isValidHook(hook) {
17207
+ return VALID_HOOKS.includes(hook);
17208
+ }
17209
+ async function runHookCommand(options) {
17210
+ const normalized = CAMEL_ALIASES[options.hook] ?? options.hook;
17211
+ if (!isValidHook(normalized)) {
17212
+ options.stderr(
17213
+ `gji run-hook: unknown hook '${options.hook}'. Valid hooks: ${VALID_HOOKS.join(", ")}
17214
+ `
17215
+ );
17216
+ return 1;
17217
+ }
17218
+ const hookName = normalized;
17219
+ const repository = await detectRepository(options.cwd);
17220
+ const config = await loadEffectiveConfig(
17221
+ repository.repoRoot,
17222
+ void 0,
17223
+ options.stderr
17224
+ );
17225
+ const hooks = extractHooks(config);
17226
+ const worktrees = await listWorktrees(options.cwd);
17227
+ const currentWorktree = worktrees.find(
17228
+ (w2) => w2.path === repository.currentRoot
17229
+ );
17230
+ await runHook(
17231
+ hooks[hookName],
17232
+ repository.currentRoot,
17233
+ {
17234
+ branch: currentWorktree?.branch ?? void 0,
17235
+ path: repository.currentRoot,
17236
+ repo: repository.repoName
17237
+ },
17238
+ options.stderr
17239
+ );
17240
+ return 0;
17241
+ }
17242
+
17183
17243
  // src/status.ts
17184
17244
  async function runStatusCommand(options) {
17185
17245
  const repository = await detectRepository(options.cwd);
@@ -17552,48 +17612,6 @@ function expandTilde2(value, home) {
17552
17612
  return value;
17553
17613
  }
17554
17614
 
17555
- // src/trigger-hook.ts
17556
- var VALID_HOOKS = [
17557
- "afterCreate",
17558
- "afterEnter",
17559
- "beforeRemove"
17560
- ];
17561
- function isValidHook(hook) {
17562
- return VALID_HOOKS.includes(hook);
17563
- }
17564
- async function runTriggerHookCommand(options) {
17565
- if (!isValidHook(options.hook)) {
17566
- options.stderr(
17567
- `gji trigger-hook: unknown hook '${options.hook}'. Valid hooks: ${VALID_HOOKS.join(", ")}
17568
- `
17569
- );
17570
- return 1;
17571
- }
17572
- const hookName = options.hook;
17573
- const repository = await detectRepository(options.cwd);
17574
- const config = await loadEffectiveConfig(
17575
- repository.repoRoot,
17576
- void 0,
17577
- options.stderr
17578
- );
17579
- const hooks = extractHooks(config);
17580
- const worktrees = await listWorktrees(options.cwd);
17581
- const currentWorktree = worktrees.find(
17582
- (w2) => w2.path === repository.currentRoot
17583
- );
17584
- await runHook(
17585
- hooks[hookName],
17586
- repository.currentRoot,
17587
- {
17588
- branch: currentWorktree?.branch ?? void 0,
17589
- path: repository.currentRoot,
17590
- repo: repository.repoName
17591
- },
17592
- options.stderr
17593
- );
17594
- return 0;
17595
- }
17596
-
17597
17615
  // src/cli.ts
17598
17616
  function createProgram() {
17599
17617
  const program2 = new Command();
@@ -17724,9 +17742,9 @@ function registerCommands(program2) {
17724
17742
  "--json",
17725
17743
  "emit JSON on success or error instead of human-readable output"
17726
17744
  ).action(notImplemented("remove"));
17727
- program2.command("trigger-hook <hook>").description(
17728
- "run a named hook (afterCreate, afterEnter, beforeRemove) in the current worktree"
17729
- ).action(notImplemented("trigger-hook"));
17745
+ program2.command("run-hook <hook>").alias("trigger-hook").description(
17746
+ "run a named hook (after-create, after-enter, before-remove) in the current worktree"
17747
+ ).action(notImplemented("run-hook"));
17730
17748
  program2.command("warp [branch]").description("jump to any worktree across all known repos").option("-n, --new [branch]", "create a new worktree in a registered repo").option(
17731
17749
  "--print",
17732
17750
  "print the resolved worktree path without changing directory"
@@ -17984,8 +18002,8 @@ function attachCommandActions(program2, options) {
17984
18002
  }
17985
18003
  };
17986
18004
  program2.commands.find((command) => command.name() === "remove")?.action(runRemovalCommand);
17987
- program2.commands.find((command) => command.name() === "trigger-hook")?.action(async (hook) => {
17988
- const exitCode = await runTriggerHookCommand({
18005
+ program2.commands.find((command) => command.name() === "run-hook")?.action(async (hook) => {
18006
+ const exitCode = await runHookCommand({
17989
18007
  cwd: options.cwd,
17990
18008
  hook,
17991
18009
  stderr: options.stderr
package/dist/go.js CHANGED
@@ -59,7 +59,7 @@ export function createGoCommand(dependencies = {}) {
59
59
  const chosenWorktree = worktrees.find((w) => w.path === resolvedPath);
60
60
  const config = await loadEffectiveConfig(repository.repoRoot, undefined, options.stderr);
61
61
  const hooks = extractHooks(config);
62
- await runHook(hooks.afterEnter, resolvedPath, {
62
+ await runHook(hooks["after-enter"], resolvedPath, {
63
63
  branch: chosenWorktree?.branch ?? undefined,
64
64
  path: resolvedPath,
65
65
  repo: basename(repository.repoRoot),
package/dist/hooks.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export type GjiHookCommand = string | string[];
2
2
  export interface GjiHooks {
3
- afterCreate?: GjiHookCommand;
4
- afterEnter?: GjiHookCommand;
5
- beforeRemove?: GjiHookCommand;
3
+ "after-create"?: GjiHookCommand;
4
+ "after-enter"?: GjiHookCommand;
5
+ "before-remove"?: GjiHookCommand;
6
6
  }
7
7
  export interface HookContext {
8
8
  branch?: string;
package/dist/hooks.js CHANGED
@@ -84,9 +84,9 @@ export function extractHooks(config) {
84
84
  }
85
85
  const hooks = raw;
86
86
  return {
87
- afterCreate: parseHookCommand(hooks.afterCreate),
88
- afterEnter: parseHookCommand(hooks.afterEnter),
89
- beforeRemove: parseHookCommand(hooks.beforeRemove),
87
+ "after-create": parseHookCommand(hooks["after-create"] ?? hooks.afterCreate),
88
+ "after-enter": parseHookCommand(hooks["after-enter"] ?? hooks.afterEnter),
89
+ "before-remove": parseHookCommand(hooks["before-remove"] ?? hooks.beforeRemove),
90
90
  };
91
91
  }
92
92
  function parseHookCommand(value) {
package/dist/init.d.ts CHANGED
@@ -3,9 +3,9 @@ export type InstallSaveTarget = "local" | "global";
3
3
  export interface SetupWizardResult {
4
4
  branchPrefix?: string;
5
5
  hooks?: {
6
- afterCreate?: string;
7
- afterEnter?: string;
8
- beforeRemove?: string;
6
+ "after-create"?: string;
7
+ "after-enter"?: string;
8
+ "before-remove"?: string;
9
9
  };
10
10
  installSaveTarget: InstallSaveTarget;
11
11
  worktreePath?: string;
package/dist/init.js CHANGED
@@ -135,12 +135,12 @@ async function saveWizardConfig(result, cwd, home) {
135
135
  if (result.worktreePath)
136
136
  values.worktreePath = result.worktreePath;
137
137
  const hooks = {};
138
- if (result.hooks?.afterCreate)
139
- hooks.afterCreate = result.hooks.afterCreate;
140
- if (result.hooks?.afterEnter)
141
- hooks.afterEnter = result.hooks.afterEnter;
142
- if (result.hooks?.beforeRemove)
143
- hooks.beforeRemove = result.hooks.beforeRemove;
138
+ if (result.hooks?.["after-create"])
139
+ hooks["after-create"] = result.hooks["after-create"];
140
+ if (result.hooks?.["after-enter"])
141
+ hooks["after-enter"] = result.hooks["after-enter"];
142
+ if (result.hooks?.["before-remove"])
143
+ hooks["before-remove"] = result.hooks["before-remove"];
144
144
  if (Object.keys(hooks).length > 0)
145
145
  values.hooks = hooks;
146
146
  if (Object.keys(values).length === 0)
@@ -283,7 +283,7 @@ async function defaultPromptForSetup() {
283
283
  return null;
284
284
  }
285
285
  const afterCreate = await text({
286
- message: "afterCreate hook — run after creating a worktree?",
286
+ message: "after-create hook — run after creating a worktree?",
287
287
  placeholder: "e.g. pnpm install — leave blank to skip",
288
288
  });
289
289
  if (isCancel(afterCreate)) {
@@ -291,7 +291,7 @@ async function defaultPromptForSetup() {
291
291
  return null;
292
292
  }
293
293
  const afterEnter = await text({
294
- message: "afterEnter hook — run after entering a worktree?",
294
+ message: "after-enter hook — run after entering a worktree?",
295
295
  placeholder: "e.g. nvm use — leave blank to skip",
296
296
  });
297
297
  if (isCancel(afterEnter)) {
@@ -299,7 +299,7 @@ async function defaultPromptForSetup() {
299
299
  return null;
300
300
  }
301
301
  const beforeRemove = await text({
302
- message: "beforeRemove hook — run before removing a worktree?",
302
+ message: "before-remove hook — run before removing a worktree?",
303
303
  placeholder: "leave blank to skip",
304
304
  });
305
305
  if (isCancel(beforeRemove)) {
@@ -309,11 +309,11 @@ async function defaultPromptForSetup() {
309
309
  outro("Setup complete!");
310
310
  const hooks = {};
311
311
  if (afterCreate)
312
- hooks.afterCreate = afterCreate;
312
+ hooks["after-create"] = afterCreate;
313
313
  if (afterEnter)
314
- hooks.afterEnter = afterEnter;
314
+ hooks["after-enter"] = afterEnter;
315
315
  if (beforeRemove)
316
- hooks.beforeRemove = beforeRemove;
316
+ hooks["before-remove"] = beforeRemove;
317
317
  return {
318
318
  branchPrefix: branchPrefix || undefined,
319
319
  hooks: Object.keys(hooks).length > 0 ? hooks : undefined,
@@ -2,15 +2,15 @@ import { spawn } from "node:child_process";
2
2
  import { isCancel, select } from "@clack/prompts";
3
3
  import { loadConfig, loadGlobalConfig, updateGlobalRepoConfigKey, updateLocalConfigKey, } from "./config.js";
4
4
  import { isHeadless } from "./headless.js";
5
+ import { extractHooks } from "./hooks.js";
5
6
  import { detectPackageManager, } from "./package-manager.js";
6
7
  export async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stderr, dependencies = {}, nonInteractive = false) {
7
8
  // Skip in non-interactive mode — no prompt can be shown.
8
9
  if (isHeadless() || nonInteractive) {
9
10
  return;
10
11
  }
11
- // Skip if afterCreate hook is already configured in effective config.
12
- const hooks = isPlainObject(config.hooks) ? config.hooks : null;
13
- if (isConfiguredHookCommand(hooks?.afterCreate)) {
12
+ // Skip if an after-create hook is already configured (accepts both kebab and legacy camelCase).
13
+ if (extractHooks(config)["after-create"]) {
14
14
  return;
15
15
  }
16
16
  // Skip if user has permanently opted out of install prompts.
@@ -43,21 +43,30 @@ export async function maybeRunInstallPrompt(worktreePath, repoRoot, config, stde
43
43
  try {
44
44
  if (saveGlobal) {
45
45
  // Deep-merge with any existing per-repo global hooks so other keys are preserved.
46
- const existingHooks = await loadExistingGlobalRepoHooks(repoRoot);
46
+ const existingRaw = await loadExistingGlobalRepoHooks(repoRoot);
47
+ const existing = extractHooks({ hooks: existingRaw });
47
48
  await writeGlobalKey(repoRoot, "hooks", {
48
- ...existingHooks,
49
- afterCreate: pm.installCommand,
49
+ ...(existing["after-enter"] !== undefined && {
50
+ "after-enter": existing["after-enter"],
51
+ }),
52
+ ...(existing["before-remove"] !== undefined && {
53
+ "before-remove": existing["before-remove"],
54
+ }),
55
+ "after-create": pm.installCommand,
50
56
  });
51
57
  }
52
58
  else {
53
- // Read local config hooks to deep-merge so other hook keys (e.g. afterEnter) are preserved.
59
+ // Read local config hooks to deep-merge so other hook keys (e.g. after-enter) are preserved.
54
60
  const { config: localConfig } = await loadConfig(repoRoot);
55
- const existingLocalHooks = isPlainObject(localConfig.hooks)
56
- ? localConfig.hooks
57
- : {};
61
+ const existing = extractHooks(localConfig);
58
62
  await writeKey(repoRoot, "hooks", {
59
- ...existingLocalHooks,
60
- afterCreate: pm.installCommand,
63
+ ...(existing["after-enter"] !== undefined && {
64
+ "after-enter": existing["after-enter"],
65
+ }),
66
+ ...(existing["before-remove"] !== undefined && {
67
+ "before-remove": existing["before-remove"],
68
+ }),
69
+ "after-create": pm.installCommand,
61
70
  });
62
71
  }
63
72
  }
@@ -122,7 +131,7 @@ async function defaultPromptForInstallChoice(pm) {
122
131
  options: [
123
132
  { value: "yes", label: "Yes", hint: "run once" },
124
133
  { value: "no", label: "No", hint: "skip this time" },
125
- { value: "always", label: "Always", hint: "save as afterCreate hook" },
134
+ { value: "always", label: "Always", hint: "save as after-create hook" },
126
135
  {
127
136
  value: "never",
128
137
  label: "Never",
@@ -138,11 +147,3 @@ async function defaultPromptForInstallChoice(pm) {
138
147
  function isPlainObject(value) {
139
148
  return typeof value === "object" && value !== null && !Array.isArray(value);
140
149
  }
141
- function isConfiguredHookCommand(value) {
142
- if (typeof value === "string")
143
- return value.length > 0;
144
- return (Array.isArray(value) &&
145
- value.length > 0 &&
146
- value[0] !== "" &&
147
- value.every((item) => typeof item === "string"));
148
- }
package/dist/new.js CHANGED
@@ -110,7 +110,7 @@ export function createNewCommand(dependencies = {}) {
110
110
  else {
111
111
  options.stderr(`gji new: ${message} in non-interactive mode (GJI_NO_TUI=1)\n`);
112
112
  options.stderr(`Hint: Use 'gji remove ${worktreeName}' or 'gji clean' to remove the existing worktree\n`);
113
- options.stderr(`Hint: Use 'gji trigger-hook afterCreate' inside the worktree to re-run setup hooks\n`);
113
+ options.stderr(`Hint: Use 'gji run-hook after-create' inside the worktree to re-run setup hooks\n`);
114
114
  }
115
115
  return 1;
116
116
  }
@@ -161,7 +161,7 @@ export function createNewCommand(dependencies = {}) {
161
161
  }
162
162
  await maybeRunInstallPrompt(worktreePath, repository.repoRoot, config, options.stderr, dependencies, !!options.json);
163
163
  const hooks = extractHooks(config);
164
- await runHook(hooks.afterCreate, worktreePath, {
164
+ await runHook(hooks["after-create"], worktreePath, {
165
165
  branch: worktreeName,
166
166
  path: worktreePath,
167
167
  repo: basename(repository.repoRoot),
package/dist/pr.js CHANGED
@@ -111,7 +111,7 @@ export function createPrCommand(dependencies = {}) {
111
111
  }
112
112
  await maybeRunInstallPrompt(worktreePath, repository.repoRoot, config, options.stderr, dependencies, !!options.json);
113
113
  const hooks = extractHooks(config);
114
- await runHook(hooks.afterCreate, worktreePath, {
114
+ await runHook(hooks["after-create"], worktreePath, {
115
115
  branch: branchName,
116
116
  path: worktreePath,
117
117
  repo: basename(repository.repoRoot),
package/dist/remove.js CHANGED
@@ -71,7 +71,7 @@ export function createRemoveCommand(dependencies = {}) {
71
71
  }
72
72
  const config = await loadEffectiveConfig(repository.repoRoot, undefined, options.stderr);
73
73
  const hooks = extractHooks(config);
74
- await runHook(hooks.beforeRemove, worktree.path, {
74
+ await runHook(hooks["before-remove"], worktree.path, {
75
75
  branch: worktree.branch ?? undefined,
76
76
  path: worktree.path,
77
77
  repo: basename(repository.repoRoot),
@@ -0,0 +1,6 @@
1
+ export interface RunHookCommandOptions {
2
+ cwd: string;
3
+ hook: string;
4
+ stderr: (chunk: string) => void;
5
+ }
6
+ export declare function runHookCommand(options: RunHookCommandOptions): Promise<number>;
@@ -2,19 +2,25 @@ import { loadEffectiveConfig } from "./config.js";
2
2
  import { extractHooks, runHook } from "./hooks.js";
3
3
  import { detectRepository, listWorktrees } from "./repo.js";
4
4
  const VALID_HOOKS = [
5
- "afterCreate",
6
- "afterEnter",
7
- "beforeRemove",
5
+ "after-create",
6
+ "after-enter",
7
+ "before-remove",
8
8
  ];
9
+ const CAMEL_ALIASES = {
10
+ afterCreate: "after-create",
11
+ afterEnter: "after-enter",
12
+ beforeRemove: "before-remove",
13
+ };
9
14
  function isValidHook(hook) {
10
15
  return VALID_HOOKS.includes(hook);
11
16
  }
12
- export async function runTriggerHookCommand(options) {
13
- if (!isValidHook(options.hook)) {
14
- options.stderr(`gji trigger-hook: unknown hook '${options.hook}'. Valid hooks: ${VALID_HOOKS.join(", ")}\n`);
17
+ export async function runHookCommand(options) {
18
+ const normalized = CAMEL_ALIASES[options.hook] ?? options.hook;
19
+ if (!isValidHook(normalized)) {
20
+ options.stderr(`gji run-hook: unknown hook '${options.hook}'. Valid hooks: ${VALID_HOOKS.join(", ")}\n`);
15
21
  return 1;
16
22
  }
17
- const hookName = options.hook;
23
+ const hookName = normalized;
18
24
  const repository = await detectRepository(options.cwd);
19
25
  const config = await loadEffectiveConfig(repository.repoRoot, undefined, options.stderr);
20
26
  const hooks = extractHooks(config);
@@ -27,14 +27,14 @@ const TOP_LEVEL_COMMANDS = [
27
27
  },
28
28
  { name: "rm", description: "alias of remove" },
29
29
  {
30
- name: "trigger-hook",
30
+ name: "run-hook",
31
31
  description: "run a named hook in the current worktree",
32
32
  },
33
33
  { name: "warp", description: "jump to any worktree across all known repos" },
34
34
  { name: "config", description: "manage global config defaults" },
35
35
  ];
36
36
  const SHELL_NAMES = ["bash", "fish", "zsh"];
37
- const HOOK_NAMES = ["afterCreate", "afterEnter", "beforeRemove"];
37
+ const HOOK_NAMES = ["after-create", "after-enter", "before-remove"];
38
38
  const CONFIG_KEYS = Array.from(KNOWN_GLOBAL_CONFIG_KEYS);
39
39
  export function renderShellCompletion(shell) {
40
40
  switch (shell) {
@@ -116,7 +116,7 @@ _gji_completion() {
116
116
  remove|rm)
117
117
  COMPREPLY=( $(compgen -W "$(__gji_worktree_branches) -f --force --dry-run --json --help" -- "$cur") )
118
118
  ;;
119
- trigger-hook)
119
+ run-hook)
120
120
  COMPREPLY=( $(compgen -W "${hooks} --help" -- "$cur") )
121
121
  ;;
122
122
  warp)
@@ -149,7 +149,7 @@ complete -F _gji_completion gji`;
149
149
  function renderFishCompletion() {
150
150
  const commandLines = TOP_LEVEL_COMMANDS.map((command) => `complete -c gji -n '__fish_use_subcommand' -a '${command.name}' -d '${escapeSingleQuotes(command.description)}'`).join("\n");
151
151
  const shellLines = SHELL_NAMES.map((shell) => `complete -c gji -n '__fish_seen_subcommand_from init' -a '${shell}' -d 'shell'`).join("\n");
152
- const hookLines = HOOK_NAMES.map((hook) => `complete -c gji -n '__fish_seen_subcommand_from trigger-hook' -a '${hook}' -d 'hook'`).join("\n");
152
+ const hookLines = HOOK_NAMES.map((hook) => `complete -c gji -n '__fish_seen_subcommand_from run-hook' -a '${hook}' -d 'hook'`).join("\n");
153
153
  const configKeyLines = CONFIG_KEYS.map((key) => `complete -c gji -n '__gji_should_complete_config_key' -a '${key}' -d 'config key'`).join("\n");
154
154
  return `function __gji_worktree_branches
155
155
  command gji ls --compact 2>/dev/null | awk 'NR > 1 { branch = ($1 == "*" ? $2 : $1); if (branch != "(detached)") print branch }'
@@ -309,7 +309,7 @@ case "\${words[2]}" in
309
309
  remove|rm)
310
310
  _arguments '(-f --force)'{-f,--force}'[bypass prompts, force-remove a dirty worktree, and force-delete an unmerged branch]' '--dry-run[show what would be deleted without removing anything]' '--json[emit JSON on success or error instead of human-readable output]' '2:branch:->worktrees'
311
311
  ;;
312
- trigger-hook)
312
+ run-hook)
313
313
  _arguments "2:hook:(${hooks})"
314
314
  ;;
315
315
  warp)
@@ -1,4 +1,4 @@
1
- .TH GJI\-BACK 1 "May 2026" "gji 0.7.0" "User Commands"
1
+ .TH GJI\-BACK 1 "June 2026" "gji 0.7.1" "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.7.0" "User Commands"
1
+ .TH GJI\-CLEAN 1 "June 2026" "gji 0.7.1" "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.7.0" "User Commands"
1
+ .TH GJI\-COMPLETION 1 "June 2026" "gji 0.7.1" "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.7.0" "User Commands"
1
+ .TH GJI\-CONFIG 1 "June 2026" "gji 0.7.1" "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.7.0" "User Commands"
1
+ .TH GJI\-GO 1 "June 2026" "gji 0.7.1" "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.7.0" "User Commands"
1
+ .TH GJI\-HISTORY 1 "June 2026" "gji 0.7.1" "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.7.0" "User Commands"
1
+ .TH GJI\-INIT 1 "June 2026" "gji 0.7.1" "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.7.0" "User Commands"
1
+ .TH GJI\-LS 1 "June 2026" "gji 0.7.1" "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.7.0" "User Commands"
1
+ .TH GJI\-NEW 1 "June 2026" "gji 0.7.1" "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.7.0" "User Commands"
1
+ .TH GJI\-OPEN 1 "June 2026" "gji 0.7.1" "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.7.0" "User Commands"
1
+ .TH GJI\-PR 1 "June 2026" "gji 0.7.1" "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.7.0" "User Commands"
1
+ .TH GJI\-REMOVE 1 "June 2026" "gji 0.7.1" "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.7.0" "User Commands"
1
+ .TH GJI\-ROOT 1 "June 2026" "gji 0.7.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-root \- print the main repository root path
4
4
  .SH SYNOPSIS
@@ -0,0 +1,9 @@
1
+ .TH GJI\-RUN\-HOOK 1 "June 2026" "gji 0.7.1" "User Commands"
2
+ .SH NAME
3
+ gji\-run\-hook \- run a named hook (after\-create, after\-enter, before\-remove) in the current worktree
4
+ .SH SYNOPSIS
5
+ .B gji run\-hook [options] <hook>
6
+ .SH DESCRIPTION
7
+ run a named hook (after\-create, after\-enter, before\-remove) in the current worktree
8
+ .SH "SEE ALSO"
9
+ .BR gji (1)
@@ -1,4 +1,4 @@
1
- .TH GJI\-STATUS 1 "May 2026" "gji 0.7.0" "User Commands"
1
+ .TH GJI\-STATUS 1 "June 2026" "gji 0.7.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-status \- summarize repository and worktree health
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-SYNC\-FILES 1 "May 2026" "gji 0.7.0" "User Commands"
1
+ .TH GJI\-SYNC\-FILES 1 "June 2026" "gji 0.7.1" "User Commands"
2
2
  .SH NAME
3
3
  gji\-sync\-files \- manage local files copied into new worktrees
4
4
  .SH SYNOPSIS
@@ -1,4 +1,4 @@
1
- .TH GJI\-SYNC 1 "May 2026" "gji 0.7.0" "User Commands"
1
+ .TH GJI\-SYNC 1 "June 2026" "gji 0.7.1" "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\-WARP 1 "May 2026" "gji 0.7.0" "User Commands"
1
+ .TH GJI\-WARP 1 "June 2026" "gji 0.7.1" "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.7.0" "User Commands"
1
+ .TH GJI 1 "June 2026" "gji 0.7.1" "User Commands"
2
2
  .SH NAME
3
3
  gji \- Context switching without the mess.
4
4
  .SH SYNOPSIS
@@ -60,8 +60,10 @@ remove a linked worktree and delete its branch when present
60
60
  .br
61
61
  Alias: \fBrm\fR
62
62
  .TP
63
- .B trigger\-hook [options] <hook>
64
- run a named hook (afterCreate, afterEnter, beforeRemove) in the current worktree
63
+ .B run\-hook [options] <hook>
64
+ run a named hook (after\-create, after\-enter, before\-remove) in the current worktree
65
+ .br
66
+ Alias: \fBtrigger\-hook\fR
65
67
  .TP
66
68
  .B warp [options] [branch]
67
69
  jump to any worktree across all known repos
@@ -88,6 +90,6 @@ output the version number
88
90
  .BR gji\-ls (1),
89
91
  .BR gji\-clean (1),
90
92
  .BR gji\-remove (1),
91
- .BR gji\-trigger\-hook (1),
93
+ .BR gji\-run\-hook (1),
92
94
  .BR gji\-warp (1),
93
95
  .BR gji\-config (1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solaqua/gji",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Git worktree CLI for fast context switching.",
5
5
  "license": "MIT",
6
6
  "author": "sjquant",
@@ -1,6 +0,0 @@
1
- export interface TriggerHookCommandOptions {
2
- cwd: string;
3
- hook: string;
4
- stderr: (chunk: string) => void;
5
- }
6
- export declare function runTriggerHookCommand(options: TriggerHookCommandOptions): Promise<number>;
@@ -1,9 +0,0 @@
1
- .TH GJI\-TRIGGER\-HOOK 1 "May 2026" "gji 0.7.0" "User Commands"
2
- .SH NAME
3
- gji\-trigger\-hook \- run a named hook (afterCreate, afterEnter, beforeRemove) in the current worktree
4
- .SH SYNOPSIS
5
- .B gji trigger\-hook [options] <hook>
6
- .SH DESCRIPTION
7
- run a named hook (afterCreate, afterEnter, beforeRemove) in the current worktree
8
- .SH "SEE ALSO"
9
- .BR gji (1)