@aku11i/phantom 6.1.1-1 → 6.2.0-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 (2) hide show
  1. package/package.json +1 -1
  2. package/phantom.js +574 -424
package/phantom.js CHANGED
@@ -28,15 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  enumerable: true
29
29
  }) : target, mod));
30
30
  //#endregion
31
- //#region ../shared/src/constants/exit-codes.ts
32
- const exitCodes = {
33
- success: 0,
34
- generalError: 1,
35
- notFound: 2,
36
- validationError: 3
37
- };
38
- //#endregion
39
- //#region ../shared/src/types/result.ts
31
+ //#region ../utils/src/types/result.ts
40
32
  /**
41
33
  * Creates a successful Result containing the given value.
42
34
  *
@@ -4375,6 +4367,8 @@ const execFile$1 = promisify(execFile);
4375
4367
  * Execute a git command with consistent error handling
4376
4368
  */
4377
4369
  async function executeGitCommand(args, options = {}) {
4370
+ const trimStdout = options.trimStdout ?? true;
4371
+ const trimStderr = options.trimStderr ?? true;
4378
4372
  try {
4379
4373
  const result = await execFile$1("git", args, {
4380
4374
  cwd: options.cwd,
@@ -4382,58 +4376,33 @@ async function executeGitCommand(args, options = {}) {
4382
4376
  encoding: "utf8"
4383
4377
  });
4384
4378
  return {
4385
- stdout: result.stdout.trim(),
4386
- stderr: result.stderr.trim()
4379
+ stdout: trimStdout ? result.stdout.trim() : result.stdout,
4380
+ stderr: trimStderr ? result.stderr.trim() : result.stderr
4387
4381
  };
4388
4382
  } catch (error) {
4389
4383
  if (error && typeof error === "object" && "stdout" in error && "stderr" in error) {
4390
4384
  const execError = error;
4391
4385
  if (execError.stderr?.trim()) throw new Error(execError.stderr.trim());
4392
4386
  return {
4393
- stdout: execError.stdout?.trim() || "",
4394
- stderr: execError.stderr?.trim() || ""
4387
+ stdout: trimStdout ? execError.stdout?.trim() ?? "" : execError.stdout ?? "",
4388
+ stderr: trimStderr ? execError.stderr?.trim() ?? "" : execError.stderr ?? ""
4395
4389
  };
4396
4390
  }
4397
4391
  throw error;
4398
4392
  }
4399
4393
  }
4400
- /**
4401
- * Execute a git command in a specific directory
4402
- */
4403
- async function executeGitCommandInDirectory(directory, args) {
4404
- return executeGitCommand([
4405
- "-C",
4406
- directory,
4407
- ...args
4408
- ], {});
4409
- }
4410
4394
  //#endregion
4411
4395
  //#region ../git/src/libs/add-worktree.ts
4412
4396
  async function addWorktree(options) {
4413
- const { path, branch, base = "HEAD" } = options;
4414
- await executeGitCommand([
4397
+ const { path, branch, base = "HEAD", createBranch = true, cwd } = options;
4398
+ const args = [
4415
4399
  "worktree",
4416
4400
  "add",
4417
- path,
4418
- "-b",
4419
- branch,
4420
- base
4421
- ]);
4422
- }
4423
- //#endregion
4424
- //#region ../git/src/libs/attach-worktree.ts
4425
- async function attachWorktree(gitRoot, worktreePath, branchName) {
4426
- try {
4427
- await executeGitCommand([
4428
- "worktree",
4429
- "add",
4430
- worktreePath,
4431
- branchName
4432
- ], { cwd: gitRoot });
4433
- return ok(void 0);
4434
- } catch (error) {
4435
- return err(error instanceof Error ? error : /* @__PURE__ */ new Error(`Failed to attach worktree: ${String(error)}`));
4436
- }
4401
+ path
4402
+ ];
4403
+ if (createBranch) args.push("-b", branch, base);
4404
+ else args.push(branch);
4405
+ await executeGitCommand(args, { cwd });
4437
4406
  }
4438
4407
  //#endregion
4439
4408
  //#region ../git/src/libs/branch-exists.ts
@@ -4449,6 +4418,45 @@ async function branchExists(gitRoot, branchName) {
4449
4418
  }
4450
4419
  }
4451
4420
  //#endregion
4421
+ //#region ../git/src/libs/config-get-regexp.ts
4422
+ async function configGetRegexp(options) {
4423
+ const { pattern, global = false, nullSeparated = false, cwd } = options;
4424
+ const args = ["config"];
4425
+ if (global) args.push("--global");
4426
+ if (nullSeparated) args.push("--null");
4427
+ args.push("--get-regexp", pattern);
4428
+ const { stdout } = await executeGitCommand(args, { cwd });
4429
+ return stdout;
4430
+ }
4431
+ //#endregion
4432
+ //#region ../git/src/libs/config-set.ts
4433
+ async function configSet(options) {
4434
+ const { key, value, global = false, cwd } = options;
4435
+ const args = ["config"];
4436
+ if (global) args.push("--global");
4437
+ args.push(key, value);
4438
+ await executeGitCommand(args, { cwd });
4439
+ }
4440
+ //#endregion
4441
+ //#region ../git/src/libs/config-unset.ts
4442
+ async function configUnset(options) {
4443
+ const { key, global = false, cwd } = options;
4444
+ const args = ["config"];
4445
+ if (global) args.push("--global");
4446
+ args.push("--unset", key);
4447
+ await executeGitCommand(args, { cwd });
4448
+ }
4449
+ //#endregion
4450
+ //#region ../git/src/libs/delete-branch.ts
4451
+ async function deleteBranch$1(options) {
4452
+ const { gitRoot, branch, force = true } = options;
4453
+ await executeGitCommand([
4454
+ "branch",
4455
+ force ? "-D" : "-d",
4456
+ branch
4457
+ ], { cwd: gitRoot });
4458
+ }
4459
+ //#endregion
4452
4460
  //#region ../git/src/libs/fetch.ts
4453
4461
  async function fetch(options = {}) {
4454
4462
  const { remote = "origin", refspec, cwd } = options;
@@ -4463,6 +4471,51 @@ async function fetch(options = {}) {
4463
4471
  }
4464
4472
  }
4465
4473
  //#endregion
4474
+ //#region ../git/src/libs/get-current-branch.ts
4475
+ async function getCurrentBranch(options = {}) {
4476
+ const { stdout } = await executeGitCommand(["branch", "--show-current"], { cwd: options.cwd });
4477
+ return stdout;
4478
+ }
4479
+ //#endregion
4480
+ //#region ../git/src/libs/get-git-root.ts
4481
+ async function getGitRoot() {
4482
+ const { stdout } = await executeGitCommand(["rev-parse", "--git-common-dir"]);
4483
+ if (stdout.endsWith("/.git") || stdout === ".git") return resolve(process.cwd(), dirname(stdout));
4484
+ const { stdout: toplevel } = await executeGitCommand(["rev-parse", "--show-toplevel"]);
4485
+ return toplevel;
4486
+ }
4487
+ //#endregion
4488
+ //#region ../git/src/libs/get-status.ts
4489
+ function parseStatusLine(line) {
4490
+ const indexStatus = line[0] ?? " ";
4491
+ const workingTreeStatus = line[1] ?? " ";
4492
+ const pathPart = line.slice(3);
4493
+ const renamedPaths = indexStatus === "R" || indexStatus === "C" || workingTreeStatus === "R" || workingTreeStatus === "C" ? pathPart.match(/^(?<originalPath>.+) -> (?<path>.+)$/) : null;
4494
+ return {
4495
+ indexStatus,
4496
+ workingTreeStatus,
4497
+ path: renamedPaths?.groups?.path ?? pathPart,
4498
+ originalPath: renamedPaths?.groups?.originalPath
4499
+ };
4500
+ }
4501
+ async function getStatus(options) {
4502
+ const { stdout } = await executeGitCommand(["status", "--porcelain"], {
4503
+ cwd: options.cwd,
4504
+ trimStdout: false
4505
+ });
4506
+ const entries = stdout.split("\n").filter((line) => line.length > 0).map(parseStatusLine);
4507
+ return {
4508
+ entries,
4509
+ isClean: entries.length === 0
4510
+ };
4511
+ }
4512
+ //#endregion
4513
+ //#region ../git/src/libs/get-top-level.ts
4514
+ async function getTopLevel(options = {}) {
4515
+ const { stdout } = await executeGitCommand(["rev-parse", "--show-toplevel"], { cwd: options.cwd });
4516
+ return stdout;
4517
+ }
4518
+ //#endregion
4466
4519
  //#region ../git/src/libs/list-worktrees.ts
4467
4520
  async function listWorktrees$1(_gitRoot) {
4468
4521
  const { stdout } = await executeGitCommand([
@@ -4491,25 +4544,13 @@ async function listWorktrees$1(_gitRoot) {
4491
4544
  return worktrees;
4492
4545
  }
4493
4546
  //#endregion
4494
- //#region ../git/src/libs/get-current-worktree.ts
4495
- async function getCurrentWorktree(gitRoot) {
4496
- try {
4497
- const { stdout: currentPath } = await executeGitCommand(["rev-parse", "--show-toplevel"]);
4498
- const currentPathTrimmed = currentPath.trim();
4499
- const currentWorktree = (await listWorktrees$1(gitRoot)).find((wt) => wt.path === currentPathTrimmed);
4500
- if (!currentWorktree || currentWorktree.path === gitRoot) return null;
4501
- return currentWorktree.branch;
4502
- } catch {
4503
- return null;
4504
- }
4505
- }
4506
- //#endregion
4507
- //#region ../git/src/libs/get-git-root.ts
4508
- async function getGitRoot() {
4509
- const { stdout } = await executeGitCommand(["rev-parse", "--git-common-dir"]);
4510
- if (stdout.endsWith("/.git") || stdout === ".git") return resolve(process.cwd(), dirname(stdout));
4511
- const { stdout: toplevel } = await executeGitCommand(["rev-parse", "--show-toplevel"]);
4512
- return toplevel;
4547
+ //#region ../git/src/libs/remove-worktree.ts
4548
+ async function removeWorktree$1(options) {
4549
+ const { gitRoot, path, force = false } = options;
4550
+ const args = ["worktree", "remove"];
4551
+ if (force) args.push("--force");
4552
+ args.push(path);
4553
+ await executeGitCommand(args, { cwd: gitRoot });
4513
4554
  }
4514
4555
  //#endregion
4515
4556
  //#region ../git/src/libs/set-upstream-branch.ts
@@ -4538,7 +4579,8 @@ const preferencesSchema = object$1({
4538
4579
  editor: string().optional(),
4539
4580
  ai: string().optional(),
4540
4581
  worktreesDirectory: string().optional(),
4541
- directoryNameSeparator: string().optional()
4582
+ directoryNameSeparator: string().optional(),
4583
+ keepBranch: boolean().optional()
4542
4584
  }).passthrough();
4543
4585
  function parsePreferences(output) {
4544
4586
  if (!output) return {};
@@ -4556,20 +4598,18 @@ function parsePreferences(output) {
4556
4598
  else if (strippedKey === "ai") preferences.ai = value;
4557
4599
  else if (strippedKey === "worktreesdirectory") preferences.worktreesDirectory = value;
4558
4600
  else if (strippedKey === "directorynameseparator") preferences.directoryNameSeparator = value;
4601
+ else if (strippedKey === "keepbranch") preferences.keepBranch = value === "true" ? true : value === "false" ? false : void 0;
4559
4602
  }
4560
4603
  const parsed = preferencesSchema.safeParse(preferences);
4561
4604
  if (!parsed.success) throw new PreferencesValidationError(parsed.error.issues[0]?.message ?? parsed.error.message);
4562
4605
  return parsed.data;
4563
4606
  }
4564
4607
  async function loadPreferences() {
4565
- const { stdout } = await executeGitCommand([
4566
- "config",
4567
- "--global",
4568
- "--null",
4569
- "--get-regexp",
4570
- "^phantom\\."
4571
- ]);
4572
- return parsePreferences(stdout);
4608
+ return parsePreferences(await configGetRegexp({
4609
+ pattern: "^phantom\\.",
4610
+ global: true,
4611
+ nullSeparated: true
4612
+ }));
4573
4613
  }
4574
4614
  //#endregion
4575
4615
  //#region ../core/src/context.ts
@@ -4703,35 +4743,6 @@ async function spawnProcess(config) {
4703
4743
  });
4704
4744
  }
4705
4745
  //#endregion
4706
- //#region ../process/src/tmux.ts
4707
- async function isInsideTmux() {
4708
- return process.env.TMUX !== void 0;
4709
- }
4710
- async function executeTmuxCommand(options) {
4711
- const { direction, command, args, cwd, env, windowName } = options;
4712
- const tmuxArgs = [];
4713
- switch (direction) {
4714
- case "new":
4715
- tmuxArgs.push("new-window");
4716
- if (windowName) tmuxArgs.push("-n", windowName);
4717
- break;
4718
- case "vertical":
4719
- tmuxArgs.push("split-window", "-v");
4720
- break;
4721
- case "horizontal":
4722
- tmuxArgs.push("split-window", "-h");
4723
- break;
4724
- }
4725
- if (cwd) tmuxArgs.push("-c", cwd);
4726
- if (env) for (const [key, value] of Object.entries(env)) tmuxArgs.push("-e", `${key}=${value}`);
4727
- tmuxArgs.push(command);
4728
- if (args && args.length > 0) tmuxArgs.push(...args);
4729
- return await spawnProcess({
4730
- command: "tmux",
4731
- args: tmuxArgs
4732
- });
4733
- }
4734
- //#endregion
4735
4746
  //#region ../core/src/worktree/errors.ts
4736
4747
  var WorktreeError = class extends Error {
4737
4748
  constructor(message) {
@@ -4757,12 +4768,23 @@ var BranchNotFoundError = class extends WorktreeError {
4757
4768
  this.name = "BranchNotFoundError";
4758
4769
  }
4759
4770
  };
4771
+ var WorktreeActionConflictError = class extends WorktreeError {
4772
+ constructor() {
4773
+ super("Cannot use --shell, --exec, and --tmux options together");
4774
+ this.name = "WorktreeActionConflictError";
4775
+ }
4776
+ };
4777
+ var TmuxSessionRequiredError = class extends WorktreeError {
4778
+ constructor() {
4779
+ super("The --tmux option can only be used inside a tmux session");
4780
+ this.name = "TmuxSessionRequiredError";
4781
+ }
4782
+ };
4760
4783
  //#endregion
4761
4784
  //#region ../core/src/worktree/list.ts
4762
4785
  async function getWorktreeStatus(worktreePath) {
4763
4786
  try {
4764
- const { stdout } = await executeGitCommandInDirectory(worktreePath, ["status", "--porcelain"]);
4765
- return !stdout;
4787
+ return (await getStatus({ cwd: worktreePath })).isClean;
4766
4788
  } catch {
4767
4789
  return true;
4768
4790
  }
@@ -4862,6 +4884,91 @@ async function shellInWorktree(gitRoot, worktreeDirectory, worktreeName) {
4862
4884
  });
4863
4885
  }
4864
4886
  //#endregion
4887
+ //#region ../tmux/src/tmux.ts
4888
+ async function isInsideTmux() {
4889
+ return process.env.TMUX !== void 0;
4890
+ }
4891
+ async function executeTmuxCommand(options) {
4892
+ const { direction, command, args, cwd, env, windowName } = options;
4893
+ const tmuxArgs = [];
4894
+ switch (direction) {
4895
+ case "new":
4896
+ tmuxArgs.push("new-window");
4897
+ if (windowName) tmuxArgs.push("-n", windowName);
4898
+ break;
4899
+ case "vertical":
4900
+ tmuxArgs.push("split-window", "-v");
4901
+ break;
4902
+ case "horizontal":
4903
+ tmuxArgs.push("split-window", "-h");
4904
+ break;
4905
+ }
4906
+ if (cwd) tmuxArgs.push("-c", cwd);
4907
+ if (env) for (const [key, value] of Object.entries(env)) tmuxArgs.push("-e", `${key}=${value}`);
4908
+ tmuxArgs.push(command);
4909
+ if (args && args.length > 0) tmuxArgs.push(...args);
4910
+ return spawnProcess({
4911
+ command: "tmux",
4912
+ args: tmuxArgs
4913
+ });
4914
+ }
4915
+ //#endregion
4916
+ //#region ../core/src/worktree/action.ts
4917
+ function mergeWorktreeCopyFiles(configuredFiles, requestedFiles) {
4918
+ const files = [...new Set([...configuredFiles ?? [], ...requestedFiles ?? []])];
4919
+ return files.length > 0 ? files : void 0;
4920
+ }
4921
+ function resolveWorktreeAction(options) {
4922
+ const actions = [];
4923
+ if (options?.shell) actions.push({ kind: "shell" });
4924
+ if (options?.exec !== void 0) actions.push({
4925
+ kind: "exec",
4926
+ command: options.exec
4927
+ });
4928
+ if (options?.tmuxDirection) actions.push({
4929
+ kind: "tmux",
4930
+ direction: options.tmuxDirection
4931
+ });
4932
+ if (actions.length > 1) return err(new WorktreeActionConflictError());
4933
+ return ok(actions[0]);
4934
+ }
4935
+ async function validateWorktreeAction(action) {
4936
+ if (action?.kind === "tmux" && !await isInsideTmux()) return err(new TmuxSessionRequiredError());
4937
+ return ok(void 0);
4938
+ }
4939
+ async function runWorktreeAction(options) {
4940
+ const { gitRoot, worktreeDirectory, worktreeName, worktreePath, action, logger, exitWithProcessCode = false } = options;
4941
+ if (!action) return ok({});
4942
+ if (action.kind === "shell") {
4943
+ logger?.log(`\nEntering worktree '${worktreeName}' at ${worktreePath}`);
4944
+ logger?.log("Type 'exit' to return to your original directory\n");
4945
+ const shellResult = await shellInWorktree(gitRoot, worktreeDirectory, worktreeName);
4946
+ if (isErr(shellResult)) return err(shellResult.error);
4947
+ return ok({ exitProcessCode: exitWithProcessCode ? shellResult.value.exitCode ?? 0 : void 0 });
4948
+ }
4949
+ if (action.kind === "exec") {
4950
+ logger?.log(`\nExecuting command in worktree '${worktreeName}': ${action.command}`);
4951
+ const execResult = await execInWorktree(gitRoot, worktreeDirectory, worktreeName, [
4952
+ process.env.SHELL || "/bin/sh",
4953
+ "-c",
4954
+ action.command
4955
+ ], { interactive: true });
4956
+ if (isErr(execResult)) return err(execResult.error);
4957
+ return ok({ exitProcessCode: exitWithProcessCode ? execResult.value.exitCode ?? 0 : void 0 });
4958
+ }
4959
+ logger?.log(`\nOpening worktree '${worktreeName}' in tmux ${action.direction === "new" ? "window" : "pane"}...`);
4960
+ const shell = process.env.SHELL || "/bin/sh";
4961
+ const tmuxResult = await executeTmuxCommand({
4962
+ direction: action.direction,
4963
+ command: shell,
4964
+ cwd: worktreePath,
4965
+ env: getPhantomEnv(worktreeName, worktreePath),
4966
+ windowName: action.direction === "new" ? worktreeName : void 0
4967
+ });
4968
+ if (isErr(tmuxResult)) return err(tmuxResult.error);
4969
+ return ok({});
4970
+ }
4971
+ //#endregion
4865
4972
  //#region ../core/src/worktree/file-copier.ts
4866
4973
  var FileCopyError = class extends Error {
4867
4974
  file;
@@ -4898,10 +5005,10 @@ async function copyFiles(sourceDir, targetDir, files) {
4898
5005
  //#endregion
4899
5006
  //#region ../core/src/worktree/post-create.ts
4900
5007
  async function executePostCreateCommands(options) {
4901
- const { gitRoot, worktreesDirectory, worktreeName, commands } = options;
5008
+ const { gitRoot, worktreesDirectory, worktreeName, commands, logger } = options;
4902
5009
  const executedCommands = [];
4903
5010
  for (const command of commands) {
4904
- console.log(`Executing: ${command}`);
5011
+ logger?.log(`Executing: ${command}`);
4905
5012
  const cmdResult = await execInWorktree(gitRoot, worktreesDirectory, worktreeName, [
4906
5013
  process.env.SHELL || "/bin/sh",
4907
5014
  "-c",
@@ -4916,14 +5023,9 @@ async function executePostCreateCommands(options) {
4916
5023
  }
4917
5024
  return ok({ executedCommands });
4918
5025
  }
4919
- async function copyFilesToWorktree(gitRoot, worktreesDirectory, worktreeName, filesToCopy, directoryNameSeparator) {
4920
- const copyResult = await copyFiles(gitRoot, getWorktreePathFromDirectory(worktreesDirectory, worktreeName, directoryNameSeparator), filesToCopy);
4921
- if (isErr(copyResult)) return err(copyResult.error);
4922
- return ok(void 0);
4923
- }
4924
5026
  //#endregion
4925
5027
  //#region ../core/src/worktree/attach.ts
4926
- async function attachWorktreeCore(gitRoot, worktreeDirectory, name, postCreateCopyFiles, postCreateCommands, directoryNameSeparator) {
5028
+ async function attachWorktreeCore(gitRoot, worktreeDirectory, name, postCreateCopyFiles, postCreateCommands, directoryNameSeparator, logger) {
4927
5029
  const validation = validateWorktreeName(name);
4928
5030
  if (isErr(validation)) return validation;
4929
5031
  const worktreePath = getWorktreePathFromDirectory(worktreeDirectory, name, directoryNameSeparator);
@@ -4931,170 +5033,59 @@ async function attachWorktreeCore(gitRoot, worktreeDirectory, name, postCreateCo
4931
5033
  const branchCheckResult = await branchExists(gitRoot, name);
4932
5034
  if (isErr(branchCheckResult)) return err(branchCheckResult.error);
4933
5035
  if (!branchCheckResult.value) return err(new BranchNotFoundError(name));
4934
- const attachResult = await attachWorktree(gitRoot, worktreePath, name);
4935
- if (isErr(attachResult)) return err(attachResult.error);
5036
+ try {
5037
+ await addWorktree({
5038
+ path: worktreePath,
5039
+ branch: name,
5040
+ createBranch: false,
5041
+ cwd: gitRoot
5042
+ });
5043
+ } catch (error) {
5044
+ return err(error instanceof Error ? error : /* @__PURE__ */ new Error(`Failed to attach worktree: ${String(error)}`));
5045
+ }
4936
5046
  if (postCreateCopyFiles && postCreateCopyFiles.length > 0) {
4937
- const copyResult = await copyFilesToWorktree(gitRoot, worktreeDirectory, name, postCreateCopyFiles, directoryNameSeparator);
4938
- if (isErr(copyResult)) console.warn(`Warning: Failed to copy some files: ${copyResult.error.message}`);
5047
+ const copyResult = await copyFiles(gitRoot, worktreePath, postCreateCopyFiles);
5048
+ if (isErr(copyResult)) logger?.warn?.(`Warning: Failed to copy some files: ${copyResult.error.message}`);
4939
5049
  }
4940
5050
  if (postCreateCommands && postCreateCommands.length > 0) {
4941
- console.log("\nRunning post-create commands...");
5051
+ logger?.log?.("\nRunning post-create commands...");
4942
5052
  const commandsResult = await executePostCreateCommands({
4943
5053
  gitRoot,
4944
5054
  worktreesDirectory: worktreeDirectory,
4945
5055
  worktreeName: name,
4946
- commands: postCreateCommands
5056
+ commands: postCreateCommands,
5057
+ logger
4947
5058
  });
4948
5059
  if (isErr(commandsResult)) return err(new WorktreeError(commandsResult.error.message));
4949
5060
  }
4950
5061
  return ok(worktreePath);
4951
5062
  }
4952
- //#endregion
4953
- //#region ../core/src/worktree/create.ts
4954
- async function createWorktree(gitRoot, worktreeDirectory, name, options, postCreateCopyFiles, postCreateCommands, directoryNameSeparator) {
4955
- const nameValidation = validateWorktreeName(name);
4956
- if (isErr(nameValidation)) return nameValidation;
4957
- const { branch = name, base = "HEAD" } = options;
4958
- const worktreePath = getWorktreePathFromDirectory(worktreeDirectory, name, directoryNameSeparator);
4959
- try {
4960
- await fs.access(worktreeDirectory);
4961
- } catch {
4962
- await fs.mkdir(worktreeDirectory, { recursive: true });
4963
- }
4964
- const validation = await validateWorktreeDoesNotExist(gitRoot, worktreeDirectory, name);
4965
- if (isErr(validation)) return err(validation.error);
4966
- try {
4967
- await addWorktree({
4968
- path: worktreePath,
4969
- branch,
4970
- base
4971
- });
4972
- let copiedFiles;
4973
- let skippedFiles;
4974
- let copyError;
4975
- if (options.copyFiles && options.copyFiles.length > 0) {
4976
- const copyResult = await copyFiles(gitRoot, worktreePath, options.copyFiles);
4977
- if (isOk(copyResult)) {
4978
- copiedFiles = copyResult.value.copiedFiles;
4979
- skippedFiles = copyResult.value.skippedFiles;
4980
- } else copyError = copyResult.error.message;
4981
- }
4982
- if (postCreateCopyFiles && postCreateCopyFiles.length > 0) {
4983
- const copyResult = await copyFilesToWorktree(gitRoot, worktreeDirectory, name, postCreateCopyFiles, directoryNameSeparator);
4984
- if (isErr(copyResult)) {
4985
- if (!copyError) copyError = copyResult.error.message;
4986
- }
4987
- }
4988
- if (postCreateCommands && postCreateCommands.length > 0) {
4989
- console.log("\nRunning post-create commands...");
4990
- const commandsResult = await executePostCreateCommands({
4991
- gitRoot,
4992
- worktreesDirectory: worktreeDirectory,
4993
- worktreeName: name,
4994
- commands: postCreateCommands
4995
- });
4996
- if (isErr(commandsResult)) return err(new WorktreeError(commandsResult.error.message));
4997
- }
4998
- return ok({
4999
- message: `Created worktree '${name}' at ${worktreePath}`,
5000
- path: worktreePath,
5001
- copiedFiles,
5002
- skippedFiles,
5003
- copyError
5004
- });
5005
- } catch (error) {
5006
- return err(new WorktreeError(`worktree add failed: ${error instanceof Error ? error.message : String(error)}`));
5007
- }
5008
- }
5009
- //#endregion
5010
- //#region ../core/src/worktree/pre-delete.ts
5011
- async function executePreDeleteCommands(options) {
5012
- const { gitRoot, worktreesDirectory, worktreeName, commands } = options;
5013
- const executedCommands = [];
5014
- for (const command of commands) {
5015
- console.log(`Executing pre-delete command: ${command}`);
5016
- const cmdResult = await execInWorktree(gitRoot, worktreesDirectory, worktreeName, [
5017
- process.env.SHELL || "/bin/sh",
5018
- "-c",
5019
- command
5020
- ]);
5021
- if (isErr(cmdResult)) {
5022
- const errorMessage = cmdResult.error instanceof Error ? cmdResult.error.message : String(cmdResult.error);
5023
- return err(/* @__PURE__ */ new Error(`Failed to execute pre-delete command "${command}": ${errorMessage}`));
5024
- }
5025
- if (cmdResult.value.exitCode !== 0) return err(/* @__PURE__ */ new Error(`Pre-delete command failed with exit code ${cmdResult.value.exitCode}: ${command}`));
5026
- executedCommands.push(command);
5027
- }
5028
- return ok({ executedCommands });
5029
- }
5030
- //#endregion
5031
- //#region ../core/src/worktree/delete.ts
5032
- async function getWorktreeChangesStatus(worktreePath) {
5063
+ async function runAttachWorktree(options) {
5064
+ const actionResult = resolveWorktreeAction(options.action);
5065
+ if (isErr(actionResult)) return actionResult;
5066
+ const actionValidation = await validateWorktreeAction(actionResult.value);
5067
+ if (isErr(actionValidation)) return actionValidation;
5033
5068
  try {
5034
- const { stdout } = await executeGitCommandInDirectory(worktreePath, ["status", "--porcelain"]);
5035
- if (stdout) return {
5036
- hasUncommittedChanges: true,
5037
- changedFiles: stdout.split("\n").length
5038
- };
5039
- } catch {}
5040
- return {
5041
- hasUncommittedChanges: false,
5042
- changedFiles: 0
5043
- };
5044
- }
5045
- async function removeWorktree(gitRoot, worktreePath, force = false) {
5046
- const args = ["worktree", "remove"];
5047
- if (force) args.push("--force");
5048
- args.push(worktreePath);
5049
- await executeGitCommand(args, { cwd: gitRoot });
5050
- }
5051
- async function deleteBranch(gitRoot, branchName) {
5052
- try {
5053
- await executeGitCommand([
5054
- "branch",
5055
- "-D",
5056
- branchName
5057
- ], { cwd: gitRoot });
5058
- return ok(true);
5059
- } catch (error) {
5060
- return err(new WorktreeError(`branch delete failed: ${error instanceof Error ? error.message : String(error)}`));
5061
- }
5062
- }
5063
- async function deleteWorktree(gitRoot, worktreeDirectory, name, options, preDeleteCommands) {
5064
- const { force = false } = options || {};
5065
- const validation = await validateWorktreeExists(gitRoot, worktreeDirectory, name, { excludeDefault: true });
5066
- if (isErr(validation)) return err(validation.error);
5067
- const worktreePath = validation.value.path;
5068
- const status = await getWorktreeChangesStatus(worktreePath);
5069
- if (status.hasUncommittedChanges && !force) return err(new WorktreeError(`Worktree '${name}' has uncommitted changes (${status.changedFiles} files). Use --force to delete anyway.`));
5070
- if (preDeleteCommands && preDeleteCommands.length > 0) {
5071
- console.log("\nRunning pre-delete commands...");
5072
- const preDeleteResult = await executePreDeleteCommands({
5073
- gitRoot,
5074
- worktreesDirectory: worktreeDirectory,
5075
- worktreeName: name,
5076
- commands: preDeleteCommands
5069
+ const context = await createContext(await getGitRoot());
5070
+ const filesToCopy = mergeWorktreeCopyFiles(context.config?.postCreate?.copyFiles, options.copyFiles);
5071
+ const attachResult = await attachWorktreeCore(context.gitRoot, context.worktreesDirectory, options.name, filesToCopy, context.config?.postCreate?.commands, context.directoryNameSeparator, options.logger);
5072
+ if (isErr(attachResult)) return err(attachResult.error);
5073
+ options.logger?.log(`Attached phantom: ${options.name}`);
5074
+ const worktreeActionResult = await runWorktreeAction({
5075
+ gitRoot: context.gitRoot,
5076
+ worktreeDirectory: context.worktreesDirectory,
5077
+ worktreeName: options.name,
5078
+ worktreePath: attachResult.value,
5079
+ action: actionResult.value,
5080
+ logger: options.logger
5077
5081
  });
5078
- if (isErr(preDeleteResult)) return err(new WorktreeError(preDeleteResult.error.message));
5079
- }
5080
- try {
5081
- await removeWorktree(gitRoot, worktreePath, force);
5082
- const branchName = name;
5083
- const branchResult = await deleteBranch(gitRoot, branchName);
5084
- let message;
5085
- if (isOk(branchResult)) message = `Deleted worktree '${name}' and its branch '${branchName}'`;
5086
- else {
5087
- message = `Deleted worktree '${name}'`;
5088
- message += `\nNote: Branch '${branchName}' could not be deleted: ${branchResult.error.message}`;
5089
- }
5090
- if (status.hasUncommittedChanges) message = `Warning: Worktree '${name}' had uncommitted changes (${status.changedFiles} files)\n${message}`;
5082
+ if (isErr(worktreeActionResult)) return err(worktreeActionResult.error);
5091
5083
  return ok({
5092
- message,
5093
- hasUncommittedChanges: status.hasUncommittedChanges,
5094
- changedFiles: status.hasUncommittedChanges ? status.changedFiles : void 0
5084
+ name: options.name,
5085
+ path: attachResult.value
5095
5086
  });
5096
5087
  } catch (error) {
5097
- return err(new WorktreeError(`worktree remove failed: ${error instanceof Error ? error.message : String(error)}`));
5088
+ return err(error instanceof Error ? error : new Error(String(error)));
5098
5089
  }
5099
5090
  }
5100
5091
  //#endregion
@@ -5956,6 +5947,208 @@ async function generateUniqueName(gitRoot, worktreesDirectory, directoryNameSepa
5956
5947
  return err(/* @__PURE__ */ new Error("Failed to generate a unique worktree name after maximum retries"));
5957
5948
  }
5958
5949
  //#endregion
5950
+ //#region ../core/src/worktree/create.ts
5951
+ async function createWorktree(gitRoot, worktreeDirectory, name, options, postCreateCopyFiles, postCreateCommands, directoryNameSeparator) {
5952
+ const nameValidation = validateWorktreeName(name);
5953
+ if (isErr(nameValidation)) return nameValidation;
5954
+ const { branch = name, base = "HEAD", copyFiles: requestedCopyFiles, logger } = options;
5955
+ const worktreePath = getWorktreePathFromDirectory(worktreeDirectory, name, directoryNameSeparator);
5956
+ try {
5957
+ await fs.access(worktreeDirectory);
5958
+ } catch {
5959
+ await fs.mkdir(worktreeDirectory, { recursive: true });
5960
+ }
5961
+ const validation = await validateWorktreeDoesNotExist(gitRoot, worktreeDirectory, name);
5962
+ if (isErr(validation)) return err(validation.error);
5963
+ try {
5964
+ await addWorktree({
5965
+ path: worktreePath,
5966
+ branch,
5967
+ base
5968
+ });
5969
+ let copiedFiles;
5970
+ let skippedFiles;
5971
+ let copyError;
5972
+ const filesToCopy = mergeWorktreeCopyFiles(requestedCopyFiles, postCreateCopyFiles);
5973
+ if (filesToCopy) {
5974
+ const copyResult = await copyFiles(gitRoot, worktreePath, filesToCopy);
5975
+ if (isOk(copyResult)) {
5976
+ copiedFiles = copyResult.value.copiedFiles;
5977
+ skippedFiles = copyResult.value.skippedFiles;
5978
+ } else copyError = copyResult.error.message;
5979
+ }
5980
+ if (postCreateCommands && postCreateCommands.length > 0) {
5981
+ logger?.log?.("\nRunning post-create commands...");
5982
+ const commandsResult = await executePostCreateCommands({
5983
+ gitRoot,
5984
+ worktreesDirectory: worktreeDirectory,
5985
+ worktreeName: name,
5986
+ commands: postCreateCommands,
5987
+ logger
5988
+ });
5989
+ if (isErr(commandsResult)) return err(new WorktreeError(commandsResult.error.message));
5990
+ }
5991
+ return ok({
5992
+ message: `Created worktree '${name}' at ${worktreePath}`,
5993
+ path: worktreePath,
5994
+ copiedFiles,
5995
+ skippedFiles,
5996
+ copyError
5997
+ });
5998
+ } catch (error) {
5999
+ return err(new WorktreeError(`worktree add failed: ${error instanceof Error ? error.message : String(error)}`));
6000
+ }
6001
+ }
6002
+ async function runCreateWorktree(options) {
6003
+ const actionResult = resolveWorktreeAction(options.action);
6004
+ if (isErr(actionResult)) return actionResult;
6005
+ const actionValidation = await validateWorktreeAction(actionResult.value);
6006
+ if (isErr(actionValidation)) return actionValidation;
6007
+ try {
6008
+ const context = await createContext(await getGitRoot());
6009
+ let worktreeName = options.name;
6010
+ if (!worktreeName) {
6011
+ const nameResult = await generateUniqueName(context.gitRoot, context.worktreesDirectory, context.directoryNameSeparator);
6012
+ if (isErr(nameResult)) return err(nameResult.error);
6013
+ worktreeName = nameResult.value;
6014
+ }
6015
+ const filesToCopy = mergeWorktreeCopyFiles(context.config?.postCreate?.copyFiles, options.copyFiles);
6016
+ const createResult = await createWorktree(context.gitRoot, context.worktreesDirectory, worktreeName, {
6017
+ base: options.base,
6018
+ copyFiles: filesToCopy,
6019
+ logger: options.logger
6020
+ }, void 0, context.config?.postCreate?.commands, context.directoryNameSeparator);
6021
+ if (isErr(createResult)) return err(createResult.error);
6022
+ options.logger?.log(createResult.value.message);
6023
+ if (createResult.value.copyError) options.logger?.warn?.(`\nWarning: Failed to copy some files: ${createResult.value.copyError}`);
6024
+ const worktreeActionResult = await runWorktreeAction({
6025
+ gitRoot: context.gitRoot,
6026
+ worktreeDirectory: context.worktreesDirectory,
6027
+ worktreeName,
6028
+ worktreePath: createResult.value.path,
6029
+ action: actionResult.value,
6030
+ logger: options.logger,
6031
+ exitWithProcessCode: true
6032
+ });
6033
+ if (isErr(worktreeActionResult)) return err(worktreeActionResult.error);
6034
+ return ok({
6035
+ name: worktreeName,
6036
+ path: createResult.value.path,
6037
+ message: createResult.value.message,
6038
+ copyError: createResult.value.copyError,
6039
+ exitProcessCode: worktreeActionResult.value.exitProcessCode
6040
+ });
6041
+ } catch (error) {
6042
+ return err(error instanceof Error ? error : new Error(String(error)));
6043
+ }
6044
+ }
6045
+ //#endregion
6046
+ //#region ../core/src/worktree/current.ts
6047
+ async function getCurrentWorktreeName(gitRoot) {
6048
+ try {
6049
+ const currentTopLevel = await getTopLevel();
6050
+ if (currentTopLevel === gitRoot) return null;
6051
+ return await getCurrentBranch({ cwd: currentTopLevel }) || null;
6052
+ } catch {
6053
+ return null;
6054
+ }
6055
+ }
6056
+ //#endregion
6057
+ //#region ../core/src/worktree/pre-delete.ts
6058
+ async function executePreDeleteCommands(options) {
6059
+ const { gitRoot, worktreesDirectory, worktreeName, commands } = options;
6060
+ const executedCommands = [];
6061
+ for (const command of commands) {
6062
+ console.log(`Executing pre-delete command: ${command}`);
6063
+ const cmdResult = await execInWorktree(gitRoot, worktreesDirectory, worktreeName, [
6064
+ process.env.SHELL || "/bin/sh",
6065
+ "-c",
6066
+ command
6067
+ ]);
6068
+ if (isErr(cmdResult)) {
6069
+ const errorMessage = cmdResult.error instanceof Error ? cmdResult.error.message : String(cmdResult.error);
6070
+ return err(/* @__PURE__ */ new Error(`Failed to execute pre-delete command "${command}": ${errorMessage}`));
6071
+ }
6072
+ if (cmdResult.value.exitCode !== 0) return err(/* @__PURE__ */ new Error(`Pre-delete command failed with exit code ${cmdResult.value.exitCode}: ${command}`));
6073
+ executedCommands.push(command);
6074
+ }
6075
+ return ok({ executedCommands });
6076
+ }
6077
+ //#endregion
6078
+ //#region ../core/src/worktree/delete.ts
6079
+ async function getWorktreeChangesStatus(worktreePath) {
6080
+ try {
6081
+ const status = await getStatus({ cwd: worktreePath });
6082
+ if (!status.isClean) return {
6083
+ hasUncommittedChanges: true,
6084
+ changedFiles: status.entries.length
6085
+ };
6086
+ } catch {}
6087
+ return {
6088
+ hasUncommittedChanges: false,
6089
+ changedFiles: 0
6090
+ };
6091
+ }
6092
+ async function removeWorktree(gitRoot, worktreePath, force = false) {
6093
+ await removeWorktree$1({
6094
+ gitRoot,
6095
+ path: worktreePath,
6096
+ force
6097
+ });
6098
+ }
6099
+ async function deleteBranch(gitRoot, branchName) {
6100
+ try {
6101
+ await deleteBranch$1({
6102
+ gitRoot,
6103
+ branch: branchName
6104
+ });
6105
+ return ok(true);
6106
+ } catch (error) {
6107
+ return err(new WorktreeError(`branch delete failed: ${error instanceof Error ? error.message : String(error)}`));
6108
+ }
6109
+ }
6110
+ async function deleteWorktree(gitRoot, worktreeDirectory, name, options, preDeleteCommands) {
6111
+ const { force = false } = options || {};
6112
+ const keepBranch = options?.keepBranch ?? false;
6113
+ const validation = await validateWorktreeExists(gitRoot, worktreeDirectory, name, { excludeDefault: true });
6114
+ if (isErr(validation)) return err(validation.error);
6115
+ const worktreePath = validation.value.path;
6116
+ const status = await getWorktreeChangesStatus(worktreePath);
6117
+ if (status.hasUncommittedChanges && !force) return err(new WorktreeError(`Worktree '${name}' has uncommitted changes (${status.changedFiles} files). Use --force to delete anyway.`));
6118
+ if (preDeleteCommands && preDeleteCommands.length > 0) {
6119
+ console.log("\nRunning pre-delete commands...");
6120
+ const preDeleteResult = await executePreDeleteCommands({
6121
+ gitRoot,
6122
+ worktreesDirectory: worktreeDirectory,
6123
+ worktreeName: name,
6124
+ commands: preDeleteCommands
6125
+ });
6126
+ if (isErr(preDeleteResult)) return err(new WorktreeError(preDeleteResult.error.message));
6127
+ }
6128
+ try {
6129
+ await removeWorktree(gitRoot, worktreePath, force);
6130
+ const branchName = name;
6131
+ let message;
6132
+ if (keepBranch) message = `Deleted worktree '${name}' and kept its branch '${branchName}'`;
6133
+ else {
6134
+ const branchResult = await deleteBranch(gitRoot, branchName);
6135
+ if (isOk(branchResult)) message = `Deleted worktree '${name}' and its branch '${branchName}'`;
6136
+ else {
6137
+ message = `Deleted worktree '${name}'`;
6138
+ message += `\nNote: Branch '${branchName}' could not be deleted: ${branchResult.error.message}`;
6139
+ }
6140
+ }
6141
+ if (status.hasUncommittedChanges) message = `Warning: Worktree '${name}' had uncommitted changes (${status.changedFiles} files)\n${message}`;
6142
+ return ok({
6143
+ message,
6144
+ hasUncommittedChanges: status.hasUncommittedChanges,
6145
+ changedFiles: status.hasUncommittedChanges ? status.changedFiles : void 0
6146
+ });
6147
+ } catch (error) {
6148
+ return err(new WorktreeError(`worktree remove failed: ${error instanceof Error ? error.message : String(error)}`));
6149
+ }
6150
+ }
6151
+ //#endregion
5959
6152
  //#region ../core/src/worktree/select.ts
5960
6153
  async function selectWorktreeWithFzf(gitRoot, options = {}) {
5961
6154
  const listResult = await listWorktrees(gitRoot, { excludeDefault: options.excludeDefault });
@@ -6000,6 +6193,14 @@ async function whereWorktree(gitRoot, worktreeDirectory, name) {
6000
6193
  return ok({ path: validation.value.path });
6001
6194
  }
6002
6195
  //#endregion
6196
+ //#region src/constants/exit-codes.ts
6197
+ const exitCodes = {
6198
+ success: 0,
6199
+ generalError: 1,
6200
+ notFound: 2,
6201
+ validationError: 3
6202
+ };
6203
+ //#endregion
6003
6204
  //#region src/output.ts
6004
6205
  const output = {
6005
6206
  log: (message) => {
@@ -6021,6 +6222,10 @@ const output = {
6021
6222
  };
6022
6223
  //#endregion
6023
6224
  //#region src/errors.ts
6225
+ function getProcessExitCode(error) {
6226
+ if (!error || typeof error !== "object" || !("exitCode" in error) || typeof error.exitCode !== "number") return;
6227
+ return error.exitCode;
6228
+ }
6024
6229
  function exitWithSuccess() {
6025
6230
  process.exit(exitCodes.success);
6026
6231
  }
@@ -6105,54 +6310,23 @@ async function attachHandler(args) {
6105
6310
  }
6106
6311
  }
6107
6312
  });
6108
- if (positionals.length === 0) exitWithError("Missing required argument: branch name", exitCodes.validationError);
6109
6313
  const [branchName] = positionals;
6110
- const tmuxOption = values.tmux || values["tmux-vertical"] || values["tmux-v"] || values["tmux-horizontal"] || values["tmux-h"];
6111
- const copyFileOptions = values["copy-file"];
6314
+ if (!branchName) exitWithError("Missing required argument: branch name", exitCodes.validationError);
6112
6315
  const tmuxDirection = values.tmux ? "new" : values["tmux-vertical"] || values["tmux-v"] ? "vertical" : values["tmux-horizontal"] || values["tmux-h"] ? "horizontal" : void 0;
6113
- if ([
6114
- values.shell,
6115
- values.exec,
6116
- tmuxOption
6117
- ].filter(Boolean).length > 1) exitWithError("Cannot use --shell, --exec, and --tmux options together", exitCodes.validationError);
6118
- const context = await createContext(await getGitRoot());
6119
- let copyFiles = context.config?.postCreate?.copyFiles ?? [];
6120
- if (copyFileOptions && copyFileOptions.length > 0) {
6121
- const cliCopyFiles = Array.isArray(copyFileOptions) ? copyFileOptions : [copyFileOptions];
6122
- copyFiles = [...new Set([...copyFiles, ...cliCopyFiles])];
6123
- }
6124
- const postCreateCopyFiles = copyFiles.length > 0 ? copyFiles : void 0;
6125
- if (tmuxOption && !await isInsideTmux()) exitWithError("The --tmux option can only be used inside a tmux session", exitCodes.validationError);
6126
- const result = await attachWorktreeCore(context.gitRoot, context.worktreesDirectory, branchName, postCreateCopyFiles, context.config?.postCreate?.commands, context.directoryNameSeparator);
6316
+ const result = await runAttachWorktree({
6317
+ name: branchName,
6318
+ copyFiles: values["copy-file"],
6319
+ action: {
6320
+ shell: values.shell ?? false,
6321
+ exec: values.exec,
6322
+ tmuxDirection
6323
+ },
6324
+ logger: output
6325
+ });
6127
6326
  if (isErr(result)) {
6128
- const error = result.error;
6129
- if (error instanceof WorktreeAlreadyExistsError) exitWithError(error.message, exitCodes.validationError);
6130
- if (error instanceof BranchNotFoundError) exitWithError(error.message, exitCodes.notFound);
6131
- exitWithError(error.message, exitCodes.generalError);
6132
- }
6133
- output.log(`Attached phantom: ${branchName}`);
6134
- const worktreePath = result.value;
6135
- if (values.shell) {
6136
- const shellResult = await shellInWorktree(context.gitRoot, context.worktreesDirectory, branchName);
6137
- if (isErr(shellResult)) exitWithError(shellResult.error.message, exitCodes.generalError);
6138
- } else if (values.exec) {
6139
- const shell = process.env.SHELL || "/bin/sh";
6140
- const execResult = await execInWorktree(context.gitRoot, context.worktreesDirectory, branchName, [
6141
- shell,
6142
- "-c",
6143
- values.exec
6144
- ], { interactive: true });
6145
- if (isErr(execResult)) exitWithError(execResult.error.message, exitCodes.generalError);
6146
- } else if (tmuxDirection) {
6147
- output.log(`Opening worktree '${branchName}' in tmux ${tmuxDirection === "new" ? "window" : "pane"}...`);
6148
- const tmuxResult = await executeTmuxCommand({
6149
- direction: tmuxDirection,
6150
- command: process.env.SHELL || "/bin/sh",
6151
- cwd: worktreePath,
6152
- env: getPhantomEnv(branchName, worktreePath),
6153
- windowName: tmuxDirection === "new" ? branchName : void 0
6154
- });
6155
- if (isErr(tmuxResult)) exitWithError(tmuxResult.error.message, exitCodes.generalError);
6327
+ if (result.error instanceof WorktreeAlreadyExistsError || result.error instanceof WorktreeActionConflictError || result.error instanceof TmuxSessionRequiredError) exitWithError(result.error.message, exitCodes.validationError);
6328
+ if (result.error instanceof BranchNotFoundError) exitWithError(result.error.message, exitCodes.notFound);
6329
+ exitWithError(result.error.message, getProcessExitCode(result.error) ?? exitCodes.generalError);
6156
6330
  }
6157
6331
  }
6158
6332
  //#endregion
@@ -6305,7 +6479,7 @@ _phantom_completion() {
6305
6479
  ;;
6306
6480
  delete)
6307
6481
  if [[ "\${cur}" == -* ]]; then
6308
- local opts="--force --current --fzf"
6482
+ local opts="--force --keep-branch --current --fzf"
6309
6483
  COMPREPLY=( $(compgen -W "\${opts}" -- "\${cur}") )
6310
6484
  else
6311
6485
  local worktrees=$(_phantom_list_worktrees_no_default)
@@ -6431,19 +6605,19 @@ _phantom_completion() {
6431
6605
  return 0
6432
6606
  elif [[ \${words[2]} == "get" ]]; then
6433
6607
  if [[ \${cword} -eq 3 ]]; then
6434
- local keys="editor ai worktreesDirectory directoryNameSeparator"
6608
+ local keys="editor ai worktreesDirectory directoryNameSeparator keepBranch"
6435
6609
  COMPREPLY=( $(compgen -W "\${keys}" -- "\${cur}") )
6436
6610
  return 0
6437
6611
  fi
6438
6612
  elif [[ \${words[2]} == "set" ]]; then
6439
6613
  if [[ \${cword} -eq 3 ]]; then
6440
- local keys="editor ai worktreesDirectory directoryNameSeparator"
6614
+ local keys="editor ai worktreesDirectory directoryNameSeparator keepBranch"
6441
6615
  COMPREPLY=( $(compgen -W "\${keys}" -- "\${cur}") )
6442
6616
  return 0
6443
6617
  fi
6444
6618
  elif [[ \${words[2]} == "remove" ]]; then
6445
6619
  if [[ \${cword} -eq 3 ]]; then
6446
- local keys="editor ai worktreesDirectory directoryNameSeparator"
6620
+ local keys="editor ai worktreesDirectory directoryNameSeparator keepBranch"
6447
6621
  COMPREPLY=( $(compgen -W "\${keys}" -- "\${cur}") )
6448
6622
  return 0
6449
6623
  fi
@@ -6639,6 +6813,7 @@ complete -c phantom -n "__phantom_using_command where" -a "(__phantom_list_workt
6639
6813
 
6640
6814
  # delete command options
6641
6815
  complete -c phantom -n "__phantom_using_command delete" -l force -d "Force deletion even if worktree has uncommitted changes (-f)"
6816
+ complete -c phantom -n "__phantom_using_command delete" -l keep-branch -d "Delete the worktree but keep its branch"
6642
6817
  complete -c phantom -n "__phantom_using_command delete" -l current -d "Delete the current worktree"
6643
6818
  complete -c phantom -n "__phantom_using_command delete" -l fzf -d "Use fzf for interactive selection"
6644
6819
  complete -c phantom -n "__phantom_using_command delete" -a "(__phantom_list_worktrees_no_default)"
@@ -6663,9 +6838,9 @@ complete -c phantom -n "__phantom_using_command ai" -a "(__phantom_list_worktree
6663
6838
 
6664
6839
  # preferences command
6665
6840
  complete -c phantom -n "__phantom_using_command preferences" -a "get set remove" -d "Manage preferences"
6666
- complete -c phantom -n "__phantom_using_command preferences get" -a "editor ai worktreesDirectory directoryNameSeparator" -d "Preference key"
6667
- complete -c phantom -n "__phantom_using_command preferences set" -a "editor ai worktreesDirectory directoryNameSeparator" -d "Preference key"
6668
- complete -c phantom -n "__phantom_using_command preferences remove" -a "editor ai worktreesDirectory directoryNameSeparator" -d "Preference key"
6841
+ complete -c phantom -n "__phantom_using_command preferences get" -a "editor ai worktreesDirectory directoryNameSeparator keepBranch" -d "Preference key"
6842
+ complete -c phantom -n "__phantom_using_command preferences set" -a "editor ai worktreesDirectory directoryNameSeparator keepBranch" -d "Preference key"
6843
+ complete -c phantom -n "__phantom_using_command preferences remove" -a "editor ai worktreesDirectory directoryNameSeparator keepBranch" -d "Preference key"
6669
6844
 
6670
6845
  # shell command options
6671
6846
  complete -c phantom -n "__phantom_using_command shell" -l fzf -d "Use fzf for interactive selection"
@@ -6787,6 +6962,7 @@ _phantom() {
6787
6962
  worktrees=(\${(f)"$(phantom list --names --no-default 2>/dev/null)"})
6788
6963
  _arguments \
6789
6964
  '--force[Force deletion even if worktree has uncommitted changes (-f)]' \
6965
+ '--keep-branch[Delete the worktree but keep its branch]' \
6790
6966
  '--current[Delete the current worktree]' \
6791
6967
  '--fzf[Use fzf for interactive selection]' \
6792
6968
  '*:worktree:(\${(q)worktrees[@]})'
@@ -6819,7 +6995,7 @@ _phantom() {
6819
6995
  preferences)
6820
6996
  _arguments \
6821
6997
  '1:subcommand:(get set remove)' \
6822
- '2:key:(editor ai worktreesDirectory directoryNameSeparator)'
6998
+ '2:key:(editor ai worktreesDirectory directoryNameSeparator keepBranch)'
6823
6999
  ;;
6824
7000
  completion)
6825
7001
  _arguments \
@@ -6910,89 +7086,24 @@ async function createHandler(args) {
6910
7086
  strict: true,
6911
7087
  allowPositionals: true
6912
7088
  });
6913
- let worktreeName = positionals[0];
6914
- const openShell = values.shell ?? false;
6915
- const execCommand = values.exec;
6916
- const copyFileOptions = values["copy-file"];
6917
- const baseOption = values.base;
6918
- const tmuxOption = values.tmux || values["tmux-vertical"] || values["tmux-v"] || values["tmux-horizontal"] || values["tmux-h"];
6919
- let tmuxDirection;
6920
- if (values.tmux) tmuxDirection = "new";
6921
- else if (values["tmux-vertical"] || values["tmux-v"]) tmuxDirection = "vertical";
6922
- else if (values["tmux-horizontal"] || values["tmux-h"]) tmuxDirection = "horizontal";
6923
- if ([
6924
- openShell,
6925
- execCommand !== void 0,
6926
- tmuxOption
6927
- ].filter(Boolean).length > 1) exitWithError("Cannot use --shell, --exec, and --tmux options together", exitCodes.validationError);
6928
- if (tmuxOption && !await isInsideTmux()) exitWithError("The --tmux option can only be used inside a tmux session", exitCodes.validationError);
6929
- try {
6930
- const gitRoot = await getGitRoot();
6931
- const context = await createContext(gitRoot);
6932
- if (!worktreeName) {
6933
- const nameResult = await generateUniqueName(gitRoot, context.worktreesDirectory, context.directoryNameSeparator);
6934
- if (isErr(nameResult)) exitWithError(nameResult.error.message, exitCodes.generalError);
6935
- worktreeName = nameResult.value;
6936
- }
6937
- let filesToCopy = [];
6938
- if (context.config?.postCreate?.copyFiles) filesToCopy = [...context.config.postCreate.copyFiles];
6939
- if (copyFileOptions && copyFileOptions.length > 0) {
6940
- const cliFiles = Array.isArray(copyFileOptions) ? copyFileOptions : [copyFileOptions];
6941
- filesToCopy = [...new Set([...filesToCopy, ...cliFiles])];
6942
- }
6943
- const result = await createWorktree(context.gitRoot, context.worktreesDirectory, worktreeName, {
6944
- copyFiles: filesToCopy.length > 0 ? filesToCopy : void 0,
6945
- base: baseOption
6946
- }, filesToCopy.length > 0 ? filesToCopy : void 0, context.config?.postCreate?.commands, context.directoryNameSeparator);
6947
- if (isErr(result)) {
6948
- const exitCode = result.error instanceof WorktreeAlreadyExistsError ? exitCodes.validationError : exitCodes.generalError;
6949
- exitWithError(result.error.message, exitCode);
6950
- }
6951
- output.log(result.value.message);
6952
- if (result.value.copyError) output.error(`\nWarning: Failed to copy some files: ${result.value.copyError}`);
6953
- if (execCommand && isOk(result)) {
6954
- output.log(`\nExecuting command in worktree '${worktreeName}': ${execCommand}`);
6955
- const shell = process.env.SHELL || "/bin/sh";
6956
- const execResult = await execInWorktree(context.gitRoot, context.worktreesDirectory, worktreeName, [
6957
- shell,
6958
- "-c",
6959
- execCommand
6960
- ], { interactive: true });
6961
- if (isErr(execResult)) {
6962
- output.error(execResult.error.message);
6963
- exitWithError("", "exitCode" in execResult.error ? execResult.error.exitCode ?? exitCodes.generalError : exitCodes.generalError);
6964
- }
6965
- return process.exit(execResult.value.exitCode ?? 0);
6966
- }
6967
- if (openShell && isOk(result)) {
6968
- output.log(`\nEntering worktree '${worktreeName}' at ${result.value.path}`);
6969
- output.log("Type 'exit' to return to your original directory\n");
6970
- const shellResult = await shellInWorktree(context.gitRoot, context.worktreesDirectory, worktreeName);
6971
- if (isErr(shellResult)) {
6972
- output.error(shellResult.error.message);
6973
- exitWithError("", "exitCode" in shellResult.error ? shellResult.error.exitCode ?? exitCodes.generalError : exitCodes.generalError);
6974
- }
6975
- return process.exit(shellResult.value.exitCode ?? 0);
6976
- }
6977
- if (tmuxDirection && isOk(result)) {
6978
- output.log(`\nOpening worktree '${worktreeName}' in tmux ${tmuxDirection === "new" ? "window" : "pane"}...`);
6979
- const shell = process.env.SHELL || "/bin/sh";
6980
- const tmuxResult = await executeTmuxCommand({
6981
- direction: tmuxDirection,
6982
- command: shell,
6983
- cwd: result.value.path,
6984
- env: getPhantomEnv(worktreeName, result.value.path),
6985
- windowName: tmuxDirection === "new" ? worktreeName : void 0
6986
- });
6987
- if (isErr(tmuxResult)) {
6988
- output.error(tmuxResult.error.message);
6989
- exitWithError("", "exitCode" in tmuxResult.error ? tmuxResult.error.exitCode ?? exitCodes.generalError : exitCodes.generalError);
6990
- }
6991
- }
6992
- exitWithSuccess();
6993
- } catch (error) {
6994
- exitWithError(error instanceof Error ? error.message : String(error), exitCodes.generalError);
7089
+ const tmuxDirection = values.tmux ? "new" : values["tmux-vertical"] || values["tmux-v"] ? "vertical" : values["tmux-horizontal"] || values["tmux-h"] ? "horizontal" : void 0;
7090
+ const result = await runCreateWorktree({
7091
+ name: positionals[0],
7092
+ base: values.base,
7093
+ copyFiles: values["copy-file"],
7094
+ action: {
7095
+ shell: values.shell ?? false,
7096
+ exec: values.exec,
7097
+ tmuxDirection
7098
+ },
7099
+ logger: output
7100
+ });
7101
+ if (isErr(result)) {
7102
+ const exitCode = result.error instanceof WorktreeAlreadyExistsError || result.error instanceof WorktreeActionConflictError || result.error instanceof TmuxSessionRequiredError ? exitCodes.validationError : getProcessExitCode(result.error) ?? exitCodes.generalError;
7103
+ exitWithError(result.error.message, exitCode);
6995
7104
  }
7105
+ if (result.value.exitProcessCode !== void 0) return process.exit(result.value.exitProcessCode);
7106
+ exitWithSuccess();
6996
7107
  }
6997
7108
  //#endregion
6998
7109
  //#region src/handlers/delete.ts
@@ -7004,6 +7115,7 @@ async function deleteHandler(args) {
7004
7115
  type: "boolean",
7005
7116
  short: "f"
7006
7117
  },
7118
+ "keep-branch": { type: "boolean" },
7007
7119
  current: { type: "boolean" },
7008
7120
  fzf: {
7009
7121
  type: "boolean",
@@ -7022,9 +7134,10 @@ async function deleteHandler(args) {
7022
7134
  try {
7023
7135
  const gitRoot = await getGitRoot();
7024
7136
  const context = await createContext(gitRoot);
7137
+ const keepBranch = values["keep-branch"] ?? context.preferences.keepBranch ?? false;
7025
7138
  const worktreeNames = [];
7026
7139
  if (deleteCurrent) {
7027
- const currentWorktree = await getCurrentWorktree(gitRoot);
7140
+ const currentWorktree = await getCurrentWorktreeName(gitRoot);
7028
7141
  if (!currentWorktree) exitWithError("Not in a worktree directory. The --current option can only be used from within a worktree.", exitCodes.validationError);
7029
7142
  worktreeNames.push(currentWorktree);
7030
7143
  } else if (useFzf) {
@@ -7034,7 +7147,10 @@ async function deleteHandler(args) {
7034
7147
  worktreeNames.push(selectResult.value.name);
7035
7148
  } else worktreeNames.push(...positionals);
7036
7149
  for (const worktreeName of worktreeNames) {
7037
- const result = await deleteWorktree(context.gitRoot, context.worktreesDirectory, worktreeName, { force: forceDelete }, context.config?.preDelete?.commands);
7150
+ const result = await deleteWorktree(context.gitRoot, context.worktreesDirectory, worktreeName, {
7151
+ force: forceDelete,
7152
+ keepBranch
7153
+ }, context.config?.preDelete?.commands);
7038
7154
  if (isErr(result)) {
7039
7155
  const exitCode = result.error instanceof WorktreeNotFoundError ? exitCodes.validationError : result.error instanceof WorktreeError && result.error.message.includes("uncommitted changes") ? exitCodes.validationError : exitCodes.generalError;
7040
7156
  exitWithError(result.error.message, exitCode);
@@ -24381,7 +24497,7 @@ var StdioServerTransport = class {
24381
24497
  };
24382
24498
  //#endregion
24383
24499
  //#region ../mcp/package.json
24384
- var version$1 = "6.1.1-1";
24500
+ var version$1 = "6.2.0-0";
24385
24501
  const createWorktreeTool = {
24386
24502
  name: "phantom_create_worktree",
24387
24503
  description: "Create a new Git worktree (phantom)",
@@ -24393,8 +24509,9 @@ const createWorktreeTool = {
24393
24509
  const context = await createContext(await getGitRoot());
24394
24510
  const result = await createWorktree(context.gitRoot, context.worktreesDirectory, name, {
24395
24511
  branch: name,
24396
- base: baseBranch
24397
- }, context.config?.postCreate?.copyFiles, context.config?.postCreate?.commands, context.directoryNameSeparator);
24512
+ base: baseBranch,
24513
+ copyFiles: context.config?.postCreate?.copyFiles
24514
+ }, void 0, context.config?.postCreate?.commands, context.directoryNameSeparator);
24398
24515
  if (!isOk(result)) throw new Error(result.error.message);
24399
24516
  return { content: [{
24400
24517
  type: "text",
@@ -24412,11 +24529,15 @@ const deleteWorktreeTool = {
24412
24529
  description: "Delete a Git worktree (phantom)",
24413
24530
  inputSchema: object$1({
24414
24531
  name: string().describe("Name of the worktree to delete"),
24415
- force: boolean().optional().describe("Force deletion even if there are uncommitted changes")
24532
+ force: boolean().optional().describe("Force deletion even if there are uncommitted changes"),
24533
+ keepBranch: boolean().optional().describe("Keep the branch after deleting the worktree")
24416
24534
  }),
24417
- handler: async ({ name, force }) => {
24535
+ handler: async ({ name, force, keepBranch }) => {
24418
24536
  const context = await createContext(await getGitRoot());
24419
- const result = await deleteWorktree(context.gitRoot, context.worktreesDirectory, name, { force }, context.config?.preDelete?.commands);
24537
+ const result = await deleteWorktree(context.gitRoot, context.worktreesDirectory, name, {
24538
+ force,
24539
+ keepBranch: keepBranch ?? context.preferences.keepBranch
24540
+ }, context.config?.preDelete?.commands);
24420
24541
  if (!isOk(result)) throw new Error(result.error.message);
24421
24542
  return { content: [{
24422
24543
  type: "text",
@@ -24576,6 +24697,10 @@ const preferencesHelp = {
24576
24697
  command: "phantom preferences set directoryNameSeparator \"-\"",
24577
24698
  description: "Store a separator for flattening worktree directory names while keeping branch names unchanged"
24578
24699
  },
24700
+ {
24701
+ command: "phantom preferences set keepBranch true",
24702
+ description: "Keep branches by default when deleting worktrees with phantom delete or MCP"
24703
+ },
24579
24704
  {
24580
24705
  command: "phantom preferences remove editor",
24581
24706
  description: "Remove the editor preference (fallback to env/default)"
@@ -24592,7 +24717,8 @@ const preferencesHelp = {
24592
24717
  " editor - used by 'phantom edit', preferred over $EDITOR",
24593
24718
  " ai - used by 'phantom ai'",
24594
24719
  " worktreesDirectory - path relative to the Git repo root for storing worktrees (defaults to .git/phantom/worktrees)",
24595
- " directoryNameSeparator - replaces '/' in worktree directory names only (defaults to / for nested directories)"
24720
+ " directoryNameSeparator - replaces '/' in worktree directory names only (defaults to / for nested directories)",
24721
+ " keepBranch - keeps the branch when deleting a worktree (defaults to false)"
24596
24722
  ]
24597
24723
  };
24598
24724
  const preferencesGetHelp = {
@@ -24615,9 +24741,13 @@ const preferencesGetHelp = {
24615
24741
  {
24616
24742
  command: "phantom preferences get directoryNameSeparator",
24617
24743
  description: "Show the preferred worktree directory name separator"
24744
+ },
24745
+ {
24746
+ command: "phantom preferences get keepBranch",
24747
+ description: "Show whether delete keeps branches by default"
24618
24748
  }
24619
24749
  ],
24620
- notes: ["Supported keys: editor, ai, worktreesDirectory, directoryNameSeparator"]
24750
+ notes: ["Supported keys: editor, ai, worktreesDirectory, directoryNameSeparator, keepBranch"]
24621
24751
  };
24622
24752
  const preferencesSetHelp = {
24623
24753
  name: "preferences set",
@@ -24639,12 +24769,17 @@ const preferencesSetHelp = {
24639
24769
  {
24640
24770
  command: "phantom preferences set directoryNameSeparator \"-\"",
24641
24771
  description: "Flatten worktree directory names such as feature/test to feature-test"
24772
+ },
24773
+ {
24774
+ command: "phantom preferences set keepBranch true",
24775
+ description: "Keep branches when deleting worktrees by default"
24642
24776
  }
24643
24777
  ],
24644
24778
  notes: [
24645
- "Supported keys: editor, ai, worktreesDirectory, directoryNameSeparator",
24779
+ "Supported keys: editor, ai, worktreesDirectory, directoryNameSeparator, keepBranch",
24646
24780
  "For worktreesDirectory, provide a path relative to the Git repository root; defaults to .git/phantom/worktrees when unset",
24647
- "For directoryNameSeparator, '/' keeps nested directories; any other string replaces '/' only in the directory path"
24781
+ "For directoryNameSeparator, '/' keeps nested directories; any other string replaces '/' only in the directory path",
24782
+ "For keepBranch, use true to preserve branches on delete or false to keep the default branch deletion behavior"
24648
24783
  ]
24649
24784
  };
24650
24785
  const preferencesRemoveHelp = {
@@ -24667,9 +24802,13 @@ const preferencesRemoveHelp = {
24667
24802
  {
24668
24803
  command: "phantom preferences remove directoryNameSeparator",
24669
24804
  description: "Restore nested worktree directories that follow branch slashes"
24805
+ },
24806
+ {
24807
+ command: "phantom preferences remove keepBranch",
24808
+ description: "Restore the default behavior of deleting branches with worktrees"
24670
24809
  }
24671
24810
  ],
24672
- notes: ["Supported keys: editor, ai, worktreesDirectory, directoryNameSeparator"]
24811
+ notes: ["Supported keys: editor, ai, worktreesDirectory, directoryNameSeparator, keepBranch"]
24673
24812
  };
24674
24813
  //#endregion
24675
24814
  //#region src/handlers/preferences.ts
@@ -24686,7 +24825,8 @@ const supportedKeys$2 = [
24686
24825
  "editor",
24687
24826
  "ai",
24688
24827
  "worktreesDirectory",
24689
- "directoryNameSeparator"
24828
+ "directoryNameSeparator",
24829
+ "keepBranch"
24690
24830
  ];
24691
24831
  async function preferencesGetHandler(args) {
24692
24832
  const { positionals } = parseArgs({
@@ -24700,7 +24840,7 @@ async function preferencesGetHandler(args) {
24700
24840
  if (!supportedKeys$2.includes(inputKey)) exitWithError(`Unknown preference '${inputKey}'. Supported keys: ${supportedKeys$2.join(", ")}`, exitCodes.validationError);
24701
24841
  try {
24702
24842
  const preferences = await loadPreferences();
24703
- const value = inputKey === "editor" ? preferences.editor : inputKey === "ai" ? preferences.ai : inputKey === "worktreesDirectory" ? preferences.worktreesDirectory : inputKey === "directoryNameSeparator" ? preferences.directoryNameSeparator : void 0;
24843
+ const value = inputKey === "editor" ? preferences.editor : inputKey === "ai" ? preferences.ai : inputKey === "worktreesDirectory" ? preferences.worktreesDirectory : inputKey === "directoryNameSeparator" ? preferences.directoryNameSeparator : inputKey === "keepBranch" ? preferences.keepBranch?.toString() : void 0;
24704
24844
  if (value === void 0) output.log(`Preference '${inputKey}' is not set (git config --global phantom.${inputKey})`);
24705
24845
  else output.log(value);
24706
24846
  exitWithSuccess();
@@ -24714,7 +24854,8 @@ const supportedKeys$1 = [
24714
24854
  "editor",
24715
24855
  "ai",
24716
24856
  "worktreesDirectory",
24717
- "directoryNameSeparator"
24857
+ "directoryNameSeparator",
24858
+ "keepBranch"
24718
24859
  ];
24719
24860
  async function preferencesRemoveHandler(args) {
24720
24861
  const { positionals } = parseArgs({
@@ -24727,12 +24868,10 @@ async function preferencesRemoveHandler(args) {
24727
24868
  const inputKey = positionals[0];
24728
24869
  if (!supportedKeys$1.includes(inputKey)) exitWithError(`Unknown preference '${inputKey}'. Supported keys: ${supportedKeys$1.join(", ")}`, exitCodes.validationError);
24729
24870
  try {
24730
- await executeGitCommand([
24731
- "config",
24732
- "--global",
24733
- "--unset",
24734
- `phantom.${inputKey}`
24735
- ]);
24871
+ await configUnset({
24872
+ key: `phantom.${inputKey}`,
24873
+ global: true
24874
+ });
24736
24875
  output.log(`Removed phantom.${inputKey} from global git config`);
24737
24876
  exitWithSuccess();
24738
24877
  } catch (error) {
@@ -24745,7 +24884,8 @@ const supportedKeys = [
24745
24884
  "editor",
24746
24885
  "ai",
24747
24886
  "worktreesDirectory",
24748
- "directoryNameSeparator"
24887
+ "directoryNameSeparator",
24888
+ "keepBranch"
24749
24889
  ];
24750
24890
  async function preferencesSetHandler(args) {
24751
24891
  if (args.length < 2) exitWithError("Usage: phantom preferences set <key> <value>", exitCodes.validationError);
@@ -24753,13 +24893,13 @@ async function preferencesSetHandler(args) {
24753
24893
  if (!supportedKeys.includes(inputKey)) exitWithError(`Unknown preference '${inputKey}'. Supported keys: ${supportedKeys.join(", ")}`, exitCodes.validationError);
24754
24894
  const value = valueParts.join(" ");
24755
24895
  if (!value) exitWithError(`Preference '${inputKey}' requires a value`, exitCodes.validationError);
24896
+ if (inputKey === "keepBranch" && value !== "true" && value !== "false") exitWithError("Preference 'keepBranch' must be 'true' or 'false'", exitCodes.validationError);
24756
24897
  try {
24757
- await executeGitCommand([
24758
- "config",
24759
- "--global",
24760
- `phantom.${inputKey}`,
24761
- value
24762
- ]);
24898
+ await configSet({
24899
+ key: `phantom.${inputKey}`,
24900
+ value,
24901
+ global: true
24902
+ });
24763
24903
  output.log(`Set phantom.${inputKey} (global) to '${value}'`);
24764
24904
  exitWithSuccess();
24765
24905
  } catch (error) {
@@ -24838,7 +24978,7 @@ async function shellHandler(args) {
24838
24978
  }
24839
24979
  //#endregion
24840
24980
  //#region package.json
24841
- var version = "6.1.1-1";
24981
+ var version = "6.2.0-0";
24842
24982
  //#endregion
24843
24983
  //#region src/version.ts
24844
24984
  function getVersion() {
@@ -25184,6 +25324,11 @@ const commands = [
25184
25324
  type: "boolean",
25185
25325
  description: "Delete the current worktree"
25186
25326
  },
25327
+ {
25328
+ name: "--keep-branch",
25329
+ type: "boolean",
25330
+ description: "Delete the worktree but keep its branch"
25331
+ },
25187
25332
  {
25188
25333
  name: "--fzf",
25189
25334
  type: "boolean",
@@ -25207,6 +25352,10 @@ const commands = [
25207
25352
  description: "Delete the current worktree",
25208
25353
  command: "phantom delete --current"
25209
25354
  },
25355
+ {
25356
+ description: "Delete a worktree while keeping its branch",
25357
+ command: "phantom delete feature-auth --keep-branch"
25358
+ },
25210
25359
  {
25211
25360
  description: "Delete a worktree with interactive fzf selection",
25212
25361
  command: "phantom delete --fzf"
@@ -25216,6 +25365,7 @@ const commands = [
25216
25365
  "By default, deletion will fail if the worktree has uncommitted changes",
25217
25366
  "You can pass multiple worktree names to delete them at once",
25218
25367
  "The associated branch will also be deleted if it's not checked out elsewhere",
25368
+ "Use --keep-branch to delete only the worktree and preserve the branch",
25219
25369
  "With --fzf, you can interactively select the worktree to delete"
25220
25370
  ]
25221
25371
  }