vde-worktree 0.0.12 → 0.0.13

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/README.ja.md CHANGED
@@ -111,6 +111,7 @@ autoload -Uz compinit && compinit
111
111
  - `--verbose`: 詳細ログ
112
112
  - `--no-hooks`: 今回のみ hook 無効化(`--allow-unsafe` 必須)
113
113
  - `--allow-unsafe`: unsafe 操作の明示同意
114
+ - `--no-gh`: 今回の実行で `gh` による PR merged 判定を無効化
114
115
  - `--hook-timeout-ms <ms>`: hook timeout 上書き
115
116
  - `--lock-timeout-ms <ms>`: repo lock timeout 上書き
116
117
 
@@ -133,12 +134,14 @@ vw init
133
134
  ```bash
134
135
  vw list
135
136
  vw list --json
137
+ vw list --no-gh
136
138
  ```
137
139
 
138
140
  機能:
139
141
 
140
142
  - Git の porcelain 情報から worktree 一覧を取得
141
143
  - branch/path/dirty/lock/merged/upstream を表示
144
+ - `--no-gh` 指定時は PR merged 判定をスキップ(`merged.byPR` は `null`)
142
145
  - 対話ターミナルでは Catppuccin 風の ANSI 色で表示
143
146
 
144
147
  ### `status`
@@ -434,6 +437,7 @@ vw completion zsh --install
434
437
  - `gh auth` 未設定
435
438
  - API 失敗
436
439
  - `git config vde-worktree.enableGh false`
440
+ - `--no-gh` を指定して実行
437
441
 
438
442
  ## JSON 契約
439
443
 
package/README.md CHANGED
@@ -111,6 +111,7 @@ After `vw init`, the tool manages:
111
111
  - `--verbose`: verbose logging
112
112
  - `--no-hooks`: disable hooks for this run (requires `--allow-unsafe`)
113
113
  - `--allow-unsafe`: explicit unsafe override
114
+ - `--no-gh`: disable GitHub CLI based PR merge checks for this run
114
115
  - `--hook-timeout-ms <ms>`: hook timeout override
115
116
  - `--lock-timeout-ms <ms>`: repository lock timeout override
116
117
 
@@ -133,12 +134,14 @@ What it does:
133
134
  ```bash
134
135
  vw list
135
136
  vw list --json
137
+ vw list --no-gh
136
138
  ```
137
139
 
138
140
  What it does:
139
141
 
140
142
  - Lists all worktrees from Git porcelain output
141
143
  - Includes metadata such as branch, path, dirty, lock, merged, and upstream status
144
+ - With `--no-gh`, skips PR-based merge checks (`merged.byPR` becomes `null`)
142
145
  - In interactive terminal, uses Catppuccin-style ANSI colors
143
146
 
144
147
  ### `status`
@@ -428,7 +431,7 @@ Overall policy:
428
431
  - `byPR === false` => `overall = false`
429
432
  - `byPR === null` => fallback to `byAncestry`
430
433
 
431
- `byPR` becomes `null` when PR lookup is unavailable (for example: `gh` missing, auth missing, API error, or `vde-worktree.enableGh=false`).
434
+ `byPR` becomes `null` when PR lookup is unavailable (for example: `gh` missing, auth missing, API error, `vde-worktree.enableGh=false`, or `--no-gh`).
432
435
 
433
436
  ## JSON Contract
434
437
 
package/dist/index.mjs CHANGED
@@ -833,21 +833,45 @@ const defaultRunGh = async ({ cwd, args }) => {
833
833
  stderr: result.stderr
834
834
  };
835
835
  };
836
- const parseMergedResult = (raw) => {
836
+ const toTargetBranches = ({ branches, baseBranch }) => {
837
+ const uniqueBranches = /* @__PURE__ */ new Set();
838
+ for (const branch of branches) {
839
+ if (typeof branch !== "string" || branch.length === 0) continue;
840
+ if (branch === baseBranch) continue;
841
+ uniqueBranches.add(branch);
842
+ }
843
+ return [...uniqueBranches];
844
+ };
845
+ const buildUnknownBranchMap = (branches) => {
846
+ return new Map(branches.map((branch) => [branch, null]));
847
+ };
848
+ const parseMergedBranches = ({ raw, targetBranches }) => {
837
849
  try {
838
850
  const parsed = JSON.parse(raw);
839
851
  if (Array.isArray(parsed) !== true) return null;
840
852
  const records = parsed;
841
- if (records.length === 0) return false;
842
- return records.some((record) => typeof record?.mergedAt === "string" && record.mergedAt.length > 0);
853
+ const mergedBranches = /* @__PURE__ */ new Set();
854
+ for (const record of records) {
855
+ if (typeof record?.headRefName !== "string" || record.headRefName.length === 0) continue;
856
+ if (targetBranches.has(record.headRefName) !== true) continue;
857
+ if (typeof record.mergedAt !== "string" || record.mergedAt.length === 0) continue;
858
+ mergedBranches.add(record.headRefName);
859
+ }
860
+ return mergedBranches;
843
861
  } catch {
844
862
  return null;
845
863
  }
846
864
  };
847
- const resolveMergedByPr = async ({ repoRoot, branch, baseBranch, enabled = true, runGh = defaultRunGh }) => {
848
- if (enabled !== true) return null;
849
- if (baseBranch === null) return null;
865
+ const resolveMergedByPrBatch = async ({ repoRoot, baseBranch, branches, enabled = true, runGh = defaultRunGh }) => {
866
+ if (enabled !== true) return /* @__PURE__ */ new Map();
867
+ if (baseBranch === null) return /* @__PURE__ */ new Map();
868
+ const targetBranches = toTargetBranches({
869
+ branches,
870
+ baseBranch
871
+ });
872
+ if (targetBranches.length === 0) return /* @__PURE__ */ new Map();
850
873
  try {
874
+ const targetBranchSet = new Set(targetBranches);
851
875
  const result = await runGh({
852
876
  cwd: repoRoot,
853
877
  args: [
@@ -855,21 +879,28 @@ const resolveMergedByPr = async ({ repoRoot, branch, baseBranch, enabled = true,
855
879
  "list",
856
880
  "--state",
857
881
  "merged",
858
- "--head",
859
- branch,
860
882
  "--base",
861
883
  baseBranch,
884
+ "--search",
885
+ targetBranches.map((branch) => `head:${branch}`).join(" OR "),
862
886
  "--limit",
863
- "1",
887
+ "1000",
864
888
  "--json",
865
- "mergedAt"
889
+ "headRefName,mergedAt"
866
890
  ]
867
891
  });
868
- if (result.exitCode !== 0) return null;
869
- return parseMergedResult(result.stdout);
892
+ if (result.exitCode !== 0) return buildUnknownBranchMap(targetBranches);
893
+ const mergedBranches = parseMergedBranches({
894
+ raw: result.stdout,
895
+ targetBranches: targetBranchSet
896
+ });
897
+ if (mergedBranches === null) return buildUnknownBranchMap(targetBranches);
898
+ return new Map(targetBranches.map((branch) => {
899
+ return [branch, mergedBranches.has(branch)];
900
+ }));
870
901
  } catch (error) {
871
- if (error.code === "ENOENT") return null;
872
- return null;
902
+ if (error.code === "ENOENT") return buildUnknownBranchMap(targetBranches);
903
+ return buildUnknownBranchMap(targetBranches);
873
904
  }
874
905
  };
875
906
 
@@ -1018,7 +1049,7 @@ const resolveLockState = async ({ repoRoot, branch }) => {
1018
1049
  };
1019
1050
  }
1020
1051
  };
1021
- const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, enableGh }) => {
1052
+ const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, mergedByPrByBranch }) => {
1022
1053
  if (branch === null) return {
1023
1054
  byAncestry: null,
1024
1055
  byPR: null,
@@ -1039,12 +1070,7 @@ const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, enableGh
1039
1070
  if (result.exitCode === 0) byAncestry = true;
1040
1071
  else if (result.exitCode === 1) byAncestry = false;
1041
1072
  }
1042
- const byPR = await resolveMergedByPr({
1043
- repoRoot,
1044
- branch,
1045
- baseBranch,
1046
- enabled: enableGh
1047
- });
1073
+ const byPR = branch === baseBranch ? null : mergedByPrByBranch.get(branch) ?? null;
1048
1074
  let byLifecycle = null;
1049
1075
  if (baseBranch !== null) {
1050
1076
  const lifecycle = await upsertWorktreeMergeLifecycle({
@@ -1112,7 +1138,7 @@ const resolveUpstreamState = async (worktreePath) => {
1112
1138
  remote: upstreamRef.stdout.trim()
1113
1139
  };
1114
1140
  };
1115
- const enrichWorktree = async ({ repoRoot, worktree, baseBranch, enableGh }) => {
1141
+ const enrichWorktree = async ({ repoRoot, worktree, baseBranch, mergedByPrByBranch }) => {
1116
1142
  const [dirty, locked, merged, upstream] = await Promise.all([
1117
1143
  resolveDirty(worktree.path),
1118
1144
  resolveLockState({
@@ -1124,7 +1150,7 @@ const enrichWorktree = async ({ repoRoot, worktree, baseBranch, enableGh }) => {
1124
1150
  branch: worktree.branch,
1125
1151
  head: worktree.head,
1126
1152
  baseBranch,
1127
- enableGh
1153
+ mergedByPrByBranch
1128
1154
  }),
1129
1155
  resolveUpstreamState(worktree.path)
1130
1156
  ]);
@@ -1138,12 +1164,18 @@ const enrichWorktree = async ({ repoRoot, worktree, baseBranch, enableGh }) => {
1138
1164
  upstream
1139
1165
  };
1140
1166
  };
1141
- const collectWorktreeSnapshot = async (repoRoot) => {
1167
+ const collectWorktreeSnapshot = async (repoRoot, { noGh = false } = {}) => {
1142
1168
  const [baseBranch, worktrees, enableGh] = await Promise.all([
1143
1169
  resolveBaseBranch$1(repoRoot),
1144
1170
  listGitWorktrees(repoRoot),
1145
1171
  resolveEnableGh(repoRoot)
1146
1172
  ]);
1173
+ const mergedByPrByBranch = await resolveMergedByPrBatch({
1174
+ repoRoot,
1175
+ baseBranch,
1176
+ branches: worktrees.map((worktree) => worktree.branch),
1177
+ enabled: enableGh && noGh !== true
1178
+ });
1147
1179
  return {
1148
1180
  repoRoot,
1149
1181
  baseBranch,
@@ -1152,7 +1184,7 @@ const collectWorktreeSnapshot = async (repoRoot) => {
1152
1184
  repoRoot,
1153
1185
  worktree,
1154
1186
  baseBranch,
1155
- enableGh
1187
+ mergedByPrByBranch
1156
1188
  });
1157
1189
  }))
1158
1190
  };
@@ -2436,6 +2468,7 @@ const renderGeneralHelpText = ({ version }) => {
2436
2468
  " --json Output machine-readable JSON.",
2437
2469
  " --verbose Enable verbose logs.",
2438
2470
  " --no-hooks Disable hooks for this run (requires --allow-unsafe).",
2471
+ " --no-gh Disable GitHub CLI based PR merge checks for this run.",
2439
2472
  " --allow-unsafe Explicitly allow unsafe behavior in non-TTY mode.",
2440
2473
  " --hook-timeout-ms <ms> Override hook timeout.",
2441
2474
  " --lock-timeout-ms <ms> Override repository lock timeout.",
@@ -2536,6 +2569,11 @@ const createCli = (options = {}) => {
2536
2569
  description: "Enable hooks (disable with --no-hooks)",
2537
2570
  default: true
2538
2571
  },
2572
+ gh: {
2573
+ type: "boolean",
2574
+ description: "Enable GitHub CLI based PR merge checks (disable with --no-gh)",
2575
+ default: true
2576
+ },
2539
2577
  allowUnsafe: {
2540
2578
  type: "boolean",
2541
2579
  description: "Allow unsafe operations"
@@ -2764,6 +2802,7 @@ const createCli = (options = {}) => {
2764
2802
  command,
2765
2803
  json: jsonEnabled,
2766
2804
  hooksEnabled: parsedArgs.hooks !== false && configuredHooksEnabled !== false,
2805
+ ghEnabled: parsedArgs.gh !== false,
2767
2806
  strictPostHooks: parsedArgs.strictPostHooks === true,
2768
2807
  hookTimeoutMs: readNumberFromEnvOrDefault({
2769
2808
  rawValue: toNumberOption({
@@ -2786,6 +2825,9 @@ const createCli = (options = {}) => {
2786
2825
  rawValue: configuredStaleTTL,
2787
2826
  defaultValue: DEFAULT_STALE_LOCK_TTL_SECONDS
2788
2827
  });
2828
+ const collectWorktreeSnapshot$1 = async (_ignoredRepoRoot) => {
2829
+ return collectWorktreeSnapshot(repoRoot, { noGh: runtime.ghEnabled !== true });
2830
+ };
2789
2831
  const runWriteOperation = async (task) => {
2790
2832
  if (WRITE_COMMANDS.has(command) !== true) return task();
2791
2833
  if (command !== "init") await validateInitializedForWrite(repoRoot);
@@ -2844,7 +2886,7 @@ const createCli = (options = {}) => {
2844
2886
  min: 0,
2845
2887
  max: 0
2846
2888
  });
2847
- const snapshot = await collectWorktreeSnapshot(repoRoot);
2889
+ const snapshot = await collectWorktreeSnapshot$1(repoRoot);
2848
2890
  if (runtime.json) {
2849
2891
  stdout(JSON.stringify(buildJsonSuccess({
2850
2892
  command,
@@ -2892,7 +2934,7 @@ const createCli = (options = {}) => {
2892
2934
  min: 0,
2893
2935
  max: 1
2894
2936
  });
2895
- const snapshot = await collectWorktreeSnapshot(repoRoot);
2937
+ const snapshot = await collectWorktreeSnapshot$1(repoRoot);
2896
2938
  const targetBranch = commandArgs[0];
2897
2939
  const targetWorktree = typeof targetBranch === "string" && targetBranch.length > 0 ? resolveTargetWorktreeByBranch({
2898
2940
  branch: targetBranch,
@@ -2926,7 +2968,7 @@ const createCli = (options = {}) => {
2926
2968
  const branch = commandArgs[0];
2927
2969
  const target = resolveTargetWorktreeByBranch({
2928
2970
  branch,
2929
- worktrees: (await collectWorktreeSnapshot(repoRoot)).worktrees
2971
+ worktrees: (await collectWorktreeSnapshot$1(repoRoot)).worktrees
2930
2972
  });
2931
2973
  if (runtime.json) {
2932
2974
  stdout(JSON.stringify(buildJsonSuccess({
@@ -2954,7 +2996,7 @@ const createCli = (options = {}) => {
2954
2996
  const result = await runWriteOperation(async () => {
2955
2997
  if (containsBranch({
2956
2998
  branch,
2957
- worktrees: (await collectWorktreeSnapshot(repoRoot)).worktrees
2999
+ worktrees: (await collectWorktreeSnapshot$1(repoRoot)).worktrees
2958
3000
  })) throw createCliError("BRANCH_ALREADY_ATTACHED", {
2959
3001
  message: `Branch is already attached to a worktree: ${branch}`,
2960
3002
  details: { branch }
@@ -3028,7 +3070,7 @@ const createCli = (options = {}) => {
3028
3070
  });
3029
3071
  const branch = commandArgs[0];
3030
3072
  const result = await runWriteOperation(async () => {
3031
- const snapshot = await collectWorktreeSnapshot(repoRoot);
3073
+ const snapshot = await collectWorktreeSnapshot$1(repoRoot);
3032
3074
  const existing = snapshot.worktrees.find((worktree) => worktree.branch === branch);
3033
3075
  if (existing !== void 0) {
3034
3076
  if (snapshot.baseBranch !== null) {
@@ -3134,7 +3176,7 @@ const createCli = (options = {}) => {
3134
3176
  });
3135
3177
  const newBranch = commandArgs[0];
3136
3178
  const result = await runWriteOperation(async () => {
3137
- const snapshot = await collectWorktreeSnapshot(repoRoot);
3179
+ const snapshot = await collectWorktreeSnapshot$1(repoRoot);
3138
3180
  const current = resolveCurrentWorktree({
3139
3181
  snapshot,
3140
3182
  currentWorktreeRoot: repoContext.currentWorktreeRoot
@@ -3247,7 +3289,7 @@ const createCli = (options = {}) => {
3247
3289
  });
3248
3290
  const branchArg = commandArgs[0];
3249
3291
  const result = await runWriteOperation(async () => {
3250
- const snapshot = await collectWorktreeSnapshot(repoRoot);
3292
+ const snapshot = await collectWorktreeSnapshot$1(repoRoot);
3251
3293
  const target = typeof branchArg === "string" && branchArg.length > 0 ? resolveTargetWorktreeByBranch({
3252
3294
  branch: branchArg,
3253
3295
  worktrees: snapshot.worktrees
@@ -3336,7 +3378,7 @@ const createCli = (options = {}) => {
3336
3378
  if (parsedArgs.apply === true && parsedArgs.dryRun === true) throw createCliError("INVALID_ARGUMENT", { message: "Cannot use --apply and --dry-run together" });
3337
3379
  const dryRun = parsedArgs.apply !== true;
3338
3380
  const execute = async () => {
3339
- const candidates = (await collectWorktreeSnapshot(repoRoot)).worktrees.filter((worktree) => worktree.branch !== null).filter((worktree) => worktree.path !== repoRoot).filter((worktree) => worktree.dirty === false).filter((worktree) => worktree.locked.value === false).filter((worktree) => worktree.merged.overall === true).map((worktree) => worktree.branch);
3381
+ const candidates = (await collectWorktreeSnapshot$1(repoRoot)).worktrees.filter((worktree) => worktree.branch !== null).filter((worktree) => worktree.path !== repoRoot).filter((worktree) => worktree.dirty === false).filter((worktree) => worktree.locked.value === false).filter((worktree) => worktree.merged.overall === true).map((worktree) => worktree.branch);
3340
3382
  if (dryRun) return {
3341
3383
  deleted: [],
3342
3384
  candidates,
@@ -3363,7 +3405,7 @@ const createCli = (options = {}) => {
3363
3405
  "remove",
3364
3406
  resolveTargetWorktreeByBranch({
3365
3407
  branch,
3366
- worktrees: (await collectWorktreeSnapshot(repoRoot)).worktrees
3408
+ worktrees: (await collectWorktreeSnapshot$1(repoRoot)).worktrees
3367
3409
  }).path
3368
3410
  ]
3369
3411
  });
@@ -3472,7 +3514,7 @@ const createCli = (options = {}) => {
3472
3514
  `${remote}/${branch}`
3473
3515
  ]
3474
3516
  });
3475
- const snapshot = await collectWorktreeSnapshot(repoRoot);
3517
+ const snapshot = await collectWorktreeSnapshot$1(repoRoot);
3476
3518
  const lifecycleBaseBranch = snapshot.baseBranch;
3477
3519
  const existing = snapshot.worktrees.find((worktree) => worktree.branch === branch);
3478
3520
  if (existing !== void 0) {
@@ -3550,7 +3592,7 @@ const createCli = (options = {}) => {
3550
3592
  const fromPath = typeof parsedArgs.from === "string" ? parsedArgs.from : void 0;
3551
3593
  if (fromPath !== void 0 && parsedArgs.current === true) throw createCliError("INVALID_ARGUMENT", { message: "extract cannot use --current and --from together" });
3552
3594
  const result = await runWriteOperation(async () => {
3553
- const snapshot = await collectWorktreeSnapshot(repoRoot);
3595
+ const snapshot = await collectWorktreeSnapshot$1(repoRoot);
3554
3596
  const resolvedSourcePath = ensurePathInsideRepo({
3555
3597
  repoRoot,
3556
3598
  path: fromPath !== void 0 ? resolvePathFromCwd({
@@ -3702,7 +3744,7 @@ const createCli = (options = {}) => {
3702
3744
  const sourceWorktree = resolveManagedNonPrimaryWorktreeByBranch({
3703
3745
  repoRoot,
3704
3746
  branch,
3705
- worktrees: (await collectWorktreeSnapshot(repoRoot)).worktrees,
3747
+ worktrees: (await collectWorktreeSnapshot$1(repoRoot)).worktrees,
3706
3748
  optionName: "--from",
3707
3749
  worktreeName: fromWorktreeName,
3708
3750
  role: "source"
@@ -3836,7 +3878,7 @@ const createCli = (options = {}) => {
3836
3878
  const targetWorktree = resolveManagedNonPrimaryWorktreeByBranch({
3837
3879
  repoRoot,
3838
3880
  branch,
3839
- worktrees: (await collectWorktreeSnapshot(repoRoot)).worktrees,
3881
+ worktrees: (await collectWorktreeSnapshot$1(repoRoot)).worktrees,
3840
3882
  optionName: "--to",
3841
3883
  worktreeName: targetWorktreeName,
3842
3884
  role: "target"
@@ -3952,7 +3994,7 @@ const createCli = (options = {}) => {
3952
3994
  message: "use requires clean primary worktree",
3953
3995
  details: { repoRoot }
3954
3996
  });
3955
- const branchCheckedOutInOtherWorktree = (await collectWorktreeSnapshot(repoRoot)).worktrees.find((worktree) => {
3997
+ const branchCheckedOutInOtherWorktree = (await collectWorktreeSnapshot$1(repoRoot)).worktrees.find((worktree) => {
3956
3998
  return worktree.branch === branch && worktree.path !== repoRoot;
3957
3999
  });
3958
4000
  if (branchCheckedOutInOtherWorktree !== void 0 && allowShared !== true) throw createCliError("BRANCH_IN_USE", {
@@ -4035,7 +4077,7 @@ const createCli = (options = {}) => {
4035
4077
  const branch = commandArgs[0];
4036
4078
  const target = resolveTargetWorktreeByBranch({
4037
4079
  branch,
4038
- worktrees: (await collectWorktreeSnapshot(repoRoot)).worktrees
4080
+ worktrees: (await collectWorktreeSnapshot$1(repoRoot)).worktrees
4039
4081
  });
4040
4082
  const executable = afterDoubleDash[0];
4041
4083
  if (typeof executable !== "string" || executable.length === 0) throw createCliError("INVALID_ARGUMENT", { message: "exec requires executable after --" });
@@ -4086,7 +4128,7 @@ const createCli = (options = {}) => {
4086
4128
  });
4087
4129
  const hookName = normalizeHookName(commandArgs[0]);
4088
4130
  const current = resolveCurrentWorktree({
4089
- snapshot: await collectWorktreeSnapshot(repoRoot),
4131
+ snapshot: await collectWorktreeSnapshot$1(repoRoot),
4090
4132
  currentWorktreeRoot: repoContext.currentWorktreeRoot
4091
4133
  });
4092
4134
  await invokeHook({
@@ -4237,7 +4279,7 @@ const createCli = (options = {}) => {
4237
4279
  const result = await runWriteOperation(async () => {
4238
4280
  resolveTargetWorktreeByBranch({
4239
4281
  branch,
4240
- worktrees: (await collectWorktreeSnapshot(repoRoot)).worktrees
4282
+ worktrees: (await collectWorktreeSnapshot$1(repoRoot)).worktrees
4241
4283
  });
4242
4284
  const existing = await readWorktreeLock({
4243
4285
  repoRoot,
@@ -4351,7 +4393,7 @@ const createCli = (options = {}) => {
4351
4393
  min: 0,
4352
4394
  max: 0
4353
4395
  });
4354
- const snapshot = await collectWorktreeSnapshot(repoRoot);
4396
+ const snapshot = await collectWorktreeSnapshot$1(repoRoot);
4355
4397
  const theme = createCatppuccinTheme({ enabled: shouldUseAnsiColors({ interactive: runtime.isInteractive || process.stderr.isTTY === true }) });
4356
4398
  const branchColumnWidth = snapshot.worktrees.reduce((maxWidth, worktree) => {
4357
4399
  const label = buildCdBranchLabel({