vde-worktree 0.0.9 → 0.0.10

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/index.mjs CHANGED
@@ -41,6 +41,8 @@ const COMMAND_NAMES = {
41
41
  GONE: "gone",
42
42
  GET: "get",
43
43
  EXTRACT: "extract",
44
+ ABSORB: "absorb",
45
+ UNABSORB: "unabsorb",
44
46
  USE: "use",
45
47
  EXEC: "exec",
46
48
  INVOKE: "invoke",
@@ -60,6 +62,8 @@ const WRITE_COMMANDS = new Set([
60
62
  COMMAND_NAMES.GONE,
61
63
  COMMAND_NAMES.GET,
62
64
  COMMAND_NAMES.EXTRACT,
65
+ COMMAND_NAMES.ABSORB,
66
+ COMMAND_NAMES.UNABSORB,
63
67
  COMMAND_NAMES.USE,
64
68
  COMMAND_NAMES.LOCK,
65
69
  COMMAND_NAMES.UNLOCK
@@ -1383,6 +1387,30 @@ const commandHelpEntries = [
1383
1387
  "--from <path>"
1384
1388
  ]
1385
1389
  },
1390
+ {
1391
+ name: "absorb",
1392
+ usage: "vw absorb <branch> [--from <worktree-name>] [--keep-stash] [--allow-agent --allow-unsafe]",
1393
+ summary: "Bring non-primary worktree changes (including uncommitted) into primary worktree.",
1394
+ details: ["Stashes source worktree changes, checks out target branch in primary, then applies stash.", "Non-TTY execution requires --allow-agent and --allow-unsafe."],
1395
+ options: [
1396
+ "--from <worktree-name>",
1397
+ "--keep-stash",
1398
+ "--allow-agent",
1399
+ "--allow-unsafe"
1400
+ ]
1401
+ },
1402
+ {
1403
+ name: "unabsorb",
1404
+ usage: "vw unabsorb <branch> [--to <worktree-name>] [--keep-stash] [--allow-agent --allow-unsafe]",
1405
+ summary: "Push primary worktree changes (including uncommitted) into non-primary worktree.",
1406
+ details: ["Stashes primary worktree changes, applies them in target non-primary worktree, then optionally drops stash.", "Non-TTY execution requires --allow-agent and --allow-unsafe."],
1407
+ options: [
1408
+ "--to <worktree-name>",
1409
+ "--keep-stash",
1410
+ "--allow-agent",
1411
+ "--allow-unsafe"
1412
+ ]
1413
+ },
1386
1414
  {
1387
1415
  name: "use",
1388
1416
  usage: "vw use <branch> [--allow-shared] [--allow-agent --allow-unsafe]",
@@ -1878,6 +1906,212 @@ const ensureBranchIsNotPrimary = ({ branch, baseBranch }) => {
1878
1906
  }
1879
1907
  });
1880
1908
  };
1909
+ const toManagedWorktreeName = ({ repoRoot, worktreePath }) => {
1910
+ const relativePath = relative(getWorktreeRootPath(repoRoot), worktreePath);
1911
+ if (relativePath.length === 0 || relativePath === "." || relativePath === ".." || relativePath.startsWith(`..${sep}`)) return null;
1912
+ return relativePath.split(sep).join("/");
1913
+ };
1914
+ const resolveManagedWorktreePathFromName = ({ repoRoot, optionName, worktreeName }) => {
1915
+ const normalized = worktreeName.trim();
1916
+ if (normalized.length === 0) throw createCliError("INVALID_ARGUMENT", {
1917
+ message: `${optionName} requires non-empty worktree name`,
1918
+ details: {
1919
+ optionName,
1920
+ worktreeName
1921
+ }
1922
+ });
1923
+ if (normalized === ".worktree" || normalized.startsWith(".worktree/") || normalized.startsWith(".worktree\\")) throw createCliError("INVALID_ARGUMENT", {
1924
+ message: `${optionName} expects vw-managed worktree name (without .worktree/ prefix)`,
1925
+ details: {
1926
+ optionName,
1927
+ worktreeName
1928
+ }
1929
+ });
1930
+ const worktreeRoot = getWorktreeRootPath(repoRoot);
1931
+ let resolvedPath;
1932
+ try {
1933
+ resolvedPath = resolveRepoRelativePath({
1934
+ repoRoot: worktreeRoot,
1935
+ relativePath: normalized
1936
+ });
1937
+ } catch (error) {
1938
+ throw createCliError("INVALID_ARGUMENT", {
1939
+ message: `${optionName} expects vw-managed worktree name`,
1940
+ details: {
1941
+ optionName,
1942
+ worktreeName
1943
+ },
1944
+ cause: error
1945
+ });
1946
+ }
1947
+ if (resolvedPath === worktreeRoot) throw createCliError("INVALID_ARGUMENT", {
1948
+ message: `${optionName} expects vw-managed worktree name`,
1949
+ details: {
1950
+ optionName,
1951
+ worktreeName
1952
+ }
1953
+ });
1954
+ return resolvedPath;
1955
+ };
1956
+ const resolveManagedNonPrimaryWorktreeByBranch = ({ repoRoot, branch, worktrees, optionName, worktreeName, role }) => {
1957
+ const managedCandidates = worktrees.filter((worktree) => {
1958
+ return worktree.branch === branch && worktree.path !== repoRoot && toManagedWorktreeName({
1959
+ repoRoot,
1960
+ worktreePath: worktree.path
1961
+ }) !== null;
1962
+ });
1963
+ if (typeof worktreeName === "string") {
1964
+ const resolvedPath = resolveManagedWorktreePathFromName({
1965
+ repoRoot,
1966
+ optionName,
1967
+ worktreeName
1968
+ });
1969
+ const selected = managedCandidates.find((worktree) => worktree.path === resolvedPath);
1970
+ if (selected === void 0) throw createCliError("WORKTREE_NOT_FOUND", {
1971
+ message: `${role} worktree not found for branch '${branch}' and name '${worktreeName}'`,
1972
+ details: {
1973
+ branch,
1974
+ worktreeName,
1975
+ optionName,
1976
+ role
1977
+ }
1978
+ });
1979
+ return selected;
1980
+ }
1981
+ if (managedCandidates.length === 0) throw createCliError("WORKTREE_NOT_FOUND", {
1982
+ message: `No managed ${role} worktree found for branch: ${branch}`,
1983
+ details: {
1984
+ branch,
1985
+ role
1986
+ }
1987
+ });
1988
+ if (managedCandidates.length > 1) throw createCliError("INVALID_ARGUMENT", {
1989
+ message: `Multiple managed ${role} worktrees found; use ${optionName} <worktree-name>`,
1990
+ details: {
1991
+ branch,
1992
+ role,
1993
+ optionName,
1994
+ candidates: managedCandidates.map((worktree) => {
1995
+ return toManagedWorktreeName({
1996
+ repoRoot,
1997
+ worktreePath: worktree.path
1998
+ }) ?? worktree.path;
1999
+ })
2000
+ }
2001
+ });
2002
+ return managedCandidates[0];
2003
+ };
2004
+ const createStashEntry = async ({ cwd, message }) => {
2005
+ await runGitCommand({
2006
+ cwd,
2007
+ args: [
2008
+ "stash",
2009
+ "push",
2010
+ "-u",
2011
+ "-m",
2012
+ message
2013
+ ]
2014
+ });
2015
+ const stashTop = await runGitCommand({
2016
+ cwd,
2017
+ args: [
2018
+ "rev-parse",
2019
+ "--verify",
2020
+ "-q",
2021
+ "stash@{0}"
2022
+ ],
2023
+ reject: false
2024
+ });
2025
+ const stashOid = stashTop.stdout.trim();
2026
+ if (stashTop.exitCode === 0 && stashOid.length > 0) return stashOid;
2027
+ throw createCliError("INTERNAL_ERROR", {
2028
+ message: "Failed to resolve created stash entry",
2029
+ details: {
2030
+ cwd,
2031
+ message
2032
+ }
2033
+ });
2034
+ };
2035
+ const restoreStashedChanges = async ({ cwd, stashOid }) => {
2036
+ if ((await runGitCommand({
2037
+ cwd,
2038
+ args: [
2039
+ "stash",
2040
+ "apply",
2041
+ stashOid
2042
+ ],
2043
+ reject: false
2044
+ })).exitCode !== 0) throw createCliError("STASH_APPLY_FAILED", {
2045
+ message: "Failed to auto-restore stashed changes after pre-hook failure",
2046
+ details: {
2047
+ cwd,
2048
+ stashOid
2049
+ }
2050
+ });
2051
+ await dropStashByOid({
2052
+ cwd,
2053
+ stashOid
2054
+ });
2055
+ };
2056
+ const runPreHookWithAutoRestore = async ({ name, context, restore }) => {
2057
+ try {
2058
+ await runPreHook({
2059
+ name,
2060
+ context
2061
+ });
2062
+ } catch (error) {
2063
+ if (restore !== void 0) try {
2064
+ await restore();
2065
+ } catch (restoreError) {
2066
+ const hookError = ensureCliError(error);
2067
+ const restoreCliError = ensureCliError(restoreError);
2068
+ throw createCliError(hookError.code, {
2069
+ message: `${hookError.message} (auto-restore failed)`,
2070
+ details: {
2071
+ ...hookError.details,
2072
+ autoRestoreFailed: true,
2073
+ autoRestoreError: {
2074
+ code: restoreCliError.code,
2075
+ message: restoreCliError.message,
2076
+ details: restoreCliError.details
2077
+ }
2078
+ },
2079
+ cause: error
2080
+ });
2081
+ }
2082
+ throw error;
2083
+ }
2084
+ };
2085
+ const resolveStashRefByOid = async ({ cwd, stashOid }) => {
2086
+ const lines = (await runGitCommand({
2087
+ cwd,
2088
+ args: [
2089
+ "stash",
2090
+ "list",
2091
+ "--format=%gd%x09%H"
2092
+ ]
2093
+ })).stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
2094
+ for (const line of lines) {
2095
+ const [ref, oid] = line.split(" ");
2096
+ if (typeof ref === "string" && typeof oid === "string" && ref.length > 0 && oid === stashOid) return ref;
2097
+ }
2098
+ return null;
2099
+ };
2100
+ const dropStashByOid = async ({ cwd, stashOid }) => {
2101
+ const stashRef = await resolveStashRefByOid({
2102
+ cwd,
2103
+ stashOid
2104
+ });
2105
+ if (stashRef === null) return;
2106
+ await runGitCommand({
2107
+ cwd,
2108
+ args: [
2109
+ "stash",
2110
+ "drop",
2111
+ stashRef
2112
+ ]
2113
+ });
2114
+ };
1881
2115
  const formatDisplayPath = (absolutePath) => {
1882
2116
  const homeDirectory = homedir();
1883
2117
  if (homeDirectory.length === 0) return absolutePath;
@@ -2201,13 +2435,22 @@ const createCli = (options = {}) => {
2201
2435
  },
2202
2436
  from: {
2203
2437
  type: "string",
2204
- valueHint: "path",
2205
- description: "Path used by extract --from"
2438
+ valueHint: "value",
2439
+ description: "For extract: filesystem path. For absorb: managed worktree name without .worktree/ prefix."
2440
+ },
2441
+ to: {
2442
+ type: "string",
2443
+ valueHint: "worktree-name",
2444
+ description: "Worktree name used by unabsorb --to"
2206
2445
  },
2207
2446
  stash: {
2208
2447
  type: "boolean",
2209
2448
  description: "Allow stash for extract"
2210
2449
  },
2450
+ keepStash: {
2451
+ type: "boolean",
2452
+ description: "Keep stash entry after absorb/unabsorb"
2453
+ },
2211
2454
  fallback: {
2212
2455
  type: "boolean",
2213
2456
  description: "Enable fallback behavior (disable with --no-fallback)",
@@ -3089,29 +3332,11 @@ const createCli = (options = {}) => {
3089
3332
  reject: false
3090
3333
  })).stdout.trim().length > 0;
3091
3334
  if (dirty && parsedArgs.stash !== true) throw createCliError("DIRTY_WORKTREE", { message: "extract requires clean worktree unless --stash is specified" });
3092
- let stashRef = null;
3093
- if (dirty && parsedArgs.stash === true) {
3094
- await runGitCommand({
3095
- cwd: repoRoot,
3096
- args: [
3097
- "stash",
3098
- "push",
3099
- "-u",
3100
- "-m",
3101
- `vde-worktree extract ${branch}`
3102
- ]
3103
- });
3104
- const stashTop = await runGitCommand({
3105
- cwd: repoRoot,
3106
- args: [
3107
- "stash",
3108
- "list",
3109
- "--max-count=1",
3110
- "--format=%gd"
3111
- ]
3112
- });
3113
- stashRef = stashTop.stdout.trim().length > 0 ? stashTop.stdout.trim() : null;
3114
- }
3335
+ let stashOid = null;
3336
+ if (dirty && parsedArgs.stash === true) stashOid = await createStashEntry({
3337
+ cwd: repoRoot,
3338
+ message: `vde-worktree extract ${branch}`
3339
+ });
3115
3340
  const hookContext = createHookContext({
3116
3341
  runtime,
3117
3342
  repoRoot,
@@ -3120,9 +3345,15 @@ const createCli = (options = {}) => {
3120
3345
  worktreePath: targetPath,
3121
3346
  stderr
3122
3347
  });
3123
- await runPreHook({
3348
+ await runPreHookWithAutoRestore({
3124
3349
  name: "extract",
3125
- context: hookContext
3350
+ context: hookContext,
3351
+ restore: stashOid !== null ? async () => {
3352
+ await restoreStashedChanges({
3353
+ cwd: repoRoot,
3354
+ stashOid
3355
+ });
3356
+ } : void 0
3126
3357
  });
3127
3358
  await runGitCommand({
3128
3359
  cwd: repoRoot,
@@ -3137,30 +3368,26 @@ const createCli = (options = {}) => {
3137
3368
  branch
3138
3369
  ]
3139
3370
  });
3140
- if (stashRef !== null) {
3371
+ if (stashOid !== null) {
3141
3372
  if ((await runGitCommand({
3142
3373
  cwd: targetPath,
3143
3374
  args: [
3144
3375
  "stash",
3145
3376
  "apply",
3146
- stashRef
3377
+ stashOid
3147
3378
  ],
3148
3379
  reject: false
3149
3380
  })).exitCode !== 0) throw createCliError("STASH_APPLY_FAILED", {
3150
3381
  message: "Failed to apply stash to extracted worktree",
3151
3382
  details: {
3152
- stashRef,
3383
+ stashOid,
3153
3384
  branch,
3154
3385
  path: targetPath
3155
3386
  }
3156
3387
  });
3157
- await runGitCommand({
3388
+ await dropStashByOid({
3158
3389
  cwd: repoRoot,
3159
- args: [
3160
- "stash",
3161
- "drop",
3162
- stashRef
3163
- ]
3390
+ stashOid
3164
3391
  });
3165
3392
  }
3166
3393
  await runPostHook({
@@ -3184,6 +3411,260 @@ const createCli = (options = {}) => {
3184
3411
  stdout(result.path);
3185
3412
  return EXIT_CODE.OK;
3186
3413
  }
3414
+ if (command === "absorb") {
3415
+ ensureArgumentCount({
3416
+ command,
3417
+ args: commandArgs,
3418
+ min: 1,
3419
+ max: 1
3420
+ });
3421
+ const branch = commandArgs[0];
3422
+ const fromWorktreeName = typeof parsedArgs.from === "string" ? parsedArgs.from : void 0;
3423
+ const keepStash = parsedArgs.keepStash === true;
3424
+ if (runtime.isInteractive !== true) {
3425
+ if (parsedArgs.allowAgent !== true) throw createCliError("UNSAFE_FLAG_REQUIRED", { message: "UNSAFE_FLAG_REQUIRED: absorb in non-TTY requires --allow-agent" });
3426
+ ensureUnsafeForNonTty({
3427
+ runtime,
3428
+ reason: "absorb in non-TTY mode requires --allow-unsafe"
3429
+ });
3430
+ }
3431
+ const result = await runWriteOperation(async () => {
3432
+ if ((await runGitCommand({
3433
+ cwd: repoRoot,
3434
+ args: ["status", "--porcelain"],
3435
+ reject: false
3436
+ })).stdout.trim().length > 0) throw createCliError("DIRTY_WORKTREE", {
3437
+ message: "absorb requires clean primary worktree",
3438
+ details: { repoRoot }
3439
+ });
3440
+ const sourceWorktree = resolveManagedNonPrimaryWorktreeByBranch({
3441
+ repoRoot,
3442
+ branch,
3443
+ worktrees: (await collectWorktreeSnapshot(repoRoot)).worktrees,
3444
+ optionName: "--from",
3445
+ worktreeName: fromWorktreeName,
3446
+ role: "source"
3447
+ });
3448
+ const sourceDirty = (await runGitCommand({
3449
+ cwd: sourceWorktree.path,
3450
+ args: ["status", "--porcelain"],
3451
+ reject: false
3452
+ })).stdout.trim().length > 0;
3453
+ let stashOid = null;
3454
+ if (sourceDirty) stashOid = await createStashEntry({
3455
+ cwd: sourceWorktree.path,
3456
+ message: `vde-worktree absorb ${branch}`
3457
+ });
3458
+ const hookContext = createHookContext({
3459
+ runtime,
3460
+ repoRoot,
3461
+ action: "absorb",
3462
+ branch,
3463
+ worktreePath: repoRoot,
3464
+ stderr,
3465
+ extraEnv: { WT_SOURCE_WORKTREE_PATH: sourceWorktree.path }
3466
+ });
3467
+ await runPreHookWithAutoRestore({
3468
+ name: "absorb",
3469
+ context: hookContext,
3470
+ restore: stashOid !== null ? async () => {
3471
+ await restoreStashedChanges({
3472
+ cwd: sourceWorktree.path,
3473
+ stashOid
3474
+ });
3475
+ } : void 0
3476
+ });
3477
+ await runGitCommand({
3478
+ cwd: repoRoot,
3479
+ args: [
3480
+ "checkout",
3481
+ "--ignore-other-worktrees",
3482
+ branch
3483
+ ]
3484
+ });
3485
+ if (stashOid !== null) {
3486
+ if ((await runGitCommand({
3487
+ cwd: repoRoot,
3488
+ args: [
3489
+ "stash",
3490
+ "apply",
3491
+ stashOid
3492
+ ],
3493
+ reject: false
3494
+ })).exitCode !== 0) throw createCliError("STASH_APPLY_FAILED", {
3495
+ message: "Failed to apply stash to primary worktree",
3496
+ details: {
3497
+ stashOid,
3498
+ branch,
3499
+ sourcePath: sourceWorktree.path,
3500
+ path: repoRoot
3501
+ }
3502
+ });
3503
+ if (!keepStash) await dropStashByOid({
3504
+ cwd: repoRoot,
3505
+ stashOid
3506
+ });
3507
+ }
3508
+ await runPostHook({
3509
+ name: "absorb",
3510
+ context: hookContext
3511
+ });
3512
+ const stashOutputRef = keepStash && stashOid !== null ? await resolveStashRefByOid({
3513
+ cwd: repoRoot,
3514
+ stashOid
3515
+ }) ?? stashOid : null;
3516
+ return {
3517
+ branch,
3518
+ path: repoRoot,
3519
+ sourcePath: sourceWorktree.path,
3520
+ stashed: sourceDirty,
3521
+ stashRef: stashOutputRef
3522
+ };
3523
+ });
3524
+ if (runtime.json) {
3525
+ stdout(JSON.stringify(buildJsonSuccess({
3526
+ command,
3527
+ status: "ok",
3528
+ repoRoot,
3529
+ details: result
3530
+ })));
3531
+ return EXIT_CODE.OK;
3532
+ }
3533
+ stdout(result.path);
3534
+ return EXIT_CODE.OK;
3535
+ }
3536
+ if (command === "unabsorb") {
3537
+ ensureArgumentCount({
3538
+ command,
3539
+ args: commandArgs,
3540
+ min: 1,
3541
+ max: 1
3542
+ });
3543
+ const branch = commandArgs[0];
3544
+ const targetWorktreeName = typeof parsedArgs.to === "string" ? parsedArgs.to : void 0;
3545
+ const keepStash = parsedArgs.keepStash === true;
3546
+ if (runtime.isInteractive !== true) {
3547
+ if (parsedArgs.allowAgent !== true) throw createCliError("UNSAFE_FLAG_REQUIRED", { message: "UNSAFE_FLAG_REQUIRED: unabsorb in non-TTY requires --allow-agent" });
3548
+ ensureUnsafeForNonTty({
3549
+ runtime,
3550
+ reason: "unabsorb in non-TTY mode requires --allow-unsafe"
3551
+ });
3552
+ }
3553
+ const result = await runWriteOperation(async () => {
3554
+ const currentBranch = (await runGitCommand({
3555
+ cwd: repoRoot,
3556
+ args: ["branch", "--show-current"],
3557
+ reject: false
3558
+ })).stdout.trim();
3559
+ if (currentBranch !== branch) throw createCliError("INVALID_ARGUMENT", {
3560
+ message: "unabsorb requires primary worktree to be on the target branch",
3561
+ details: {
3562
+ branch,
3563
+ currentBranch
3564
+ }
3565
+ });
3566
+ if ((await runGitCommand({
3567
+ cwd: repoRoot,
3568
+ args: ["status", "--porcelain"],
3569
+ reject: false
3570
+ })).stdout.trim().length === 0) throw createCliError("DIRTY_WORKTREE", {
3571
+ message: "unabsorb requires dirty primary worktree",
3572
+ details: { repoRoot }
3573
+ });
3574
+ const targetWorktree = resolveManagedNonPrimaryWorktreeByBranch({
3575
+ repoRoot,
3576
+ branch,
3577
+ worktrees: (await collectWorktreeSnapshot(repoRoot)).worktrees,
3578
+ optionName: "--to",
3579
+ worktreeName: targetWorktreeName,
3580
+ role: "target"
3581
+ });
3582
+ if ((await runGitCommand({
3583
+ cwd: targetWorktree.path,
3584
+ args: ["status", "--porcelain"],
3585
+ reject: false
3586
+ })).stdout.trim().length > 0) throw createCliError("DIRTY_WORKTREE", {
3587
+ message: "unabsorb requires clean target worktree",
3588
+ details: {
3589
+ branch,
3590
+ path: targetWorktree.path
3591
+ }
3592
+ });
3593
+ const stashOid = await createStashEntry({
3594
+ cwd: repoRoot,
3595
+ message: `vde-worktree unabsorb ${branch}`
3596
+ });
3597
+ const hookContext = createHookContext({
3598
+ runtime,
3599
+ repoRoot,
3600
+ action: "unabsorb",
3601
+ branch,
3602
+ worktreePath: targetWorktree.path,
3603
+ stderr,
3604
+ extraEnv: {
3605
+ WT_SOURCE_WORKTREE_PATH: repoRoot,
3606
+ WT_TARGET_WORKTREE_PATH: targetWorktree.path
3607
+ }
3608
+ });
3609
+ await runPreHookWithAutoRestore({
3610
+ name: "unabsorb",
3611
+ context: hookContext,
3612
+ restore: async () => {
3613
+ await restoreStashedChanges({
3614
+ cwd: repoRoot,
3615
+ stashOid
3616
+ });
3617
+ }
3618
+ });
3619
+ if ((await runGitCommand({
3620
+ cwd: targetWorktree.path,
3621
+ args: [
3622
+ "stash",
3623
+ "apply",
3624
+ stashOid
3625
+ ],
3626
+ reject: false
3627
+ })).exitCode !== 0) throw createCliError("STASH_APPLY_FAILED", {
3628
+ message: "Failed to apply stash to target worktree",
3629
+ details: {
3630
+ stashOid,
3631
+ branch,
3632
+ sourcePath: repoRoot,
3633
+ targetPath: targetWorktree.path
3634
+ }
3635
+ });
3636
+ if (!keepStash) await dropStashByOid({
3637
+ cwd: repoRoot,
3638
+ stashOid
3639
+ });
3640
+ await runPostHook({
3641
+ name: "unabsorb",
3642
+ context: hookContext
3643
+ });
3644
+ const stashOutputRef = keepStash ? await resolveStashRefByOid({
3645
+ cwd: repoRoot,
3646
+ stashOid
3647
+ }) ?? stashOid : null;
3648
+ return {
3649
+ branch,
3650
+ path: targetWorktree.path,
3651
+ sourcePath: repoRoot,
3652
+ stashed: true,
3653
+ stashRef: stashOutputRef
3654
+ };
3655
+ });
3656
+ if (runtime.json) {
3657
+ stdout(JSON.stringify(buildJsonSuccess({
3658
+ command,
3659
+ status: "ok",
3660
+ repoRoot,
3661
+ details: result
3662
+ })));
3663
+ return EXIT_CODE.OK;
3664
+ }
3665
+ stdout(result.path);
3666
+ return EXIT_CODE.OK;
3667
+ }
3187
3668
  if (command === "use") {
3188
3669
  ensureArgumentCount({
3189
3670
  command,