vde-worktree 0.0.15 → 0.0.16

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
@@ -427,9 +427,14 @@ vw completion zsh --install
427
427
 
428
428
  `overall` ポリシー:
429
429
 
430
- - `byPR === true` -> `overall = true`
431
- - `byPR === false` -> `overall = false`
432
- - `byPR === null` -> `byAncestry` にフォールバック
430
+ - `byPR === true` -> `overall = true`(squash/rebase merge を含む)
431
+ - `byAncestry === false` -> `overall = false`
432
+ - `byAncestry === true` の場合は、分岐の証跡があるときだけ merged 扱い
433
+ - `.vde/worktree/state/branches/*.json` の lifecycle 記録
434
+ - lifecycle がない場合の `git reflog` フォールバック
435
+ - 分岐証跡が `baseBranch` に取り込まれていれば `overall = true`
436
+ - `byPR === false` または lifecycle が明示的に未取り込みなら `overall = false`
437
+ - それ以外は `overall = null`
433
438
 
434
439
  `byPR` が `null` になる例:
435
440
 
package/README.md CHANGED
@@ -427,9 +427,14 @@ Each worktree reports:
427
427
 
428
428
  Overall policy:
429
429
 
430
- - `byPR === true` => `overall = true`
431
- - `byPR === false` => `overall = false`
432
- - `byPR === null` => fallback to `byAncestry`
430
+ - `byPR === true` => `overall = true` (includes squash/rebase merges)
431
+ - `byAncestry === false` => `overall = false`
432
+ - when `byAncestry === true`, require divergence evidence before treating as merged
433
+ - lifecycle evidence from `.vde/worktree/state/branches/*.json`
434
+ - reflog fallback (`git reflog`) when lifecycle evidence is missing
435
+ - if divergence evidence is contained in `baseBranch`, `overall = true`
436
+ - `byPR === false` or explicit lifecycle "not merged" evidence => `overall = false`
437
+ - otherwise `overall = null`
433
438
 
434
439
  `byPR` becomes `null` when PR lookup is unavailable (for example: `gh` missing, auth missing, API error, `vde-worktree.enableGh=false`, or `--no-gh`).
435
440
 
package/dist/index.mjs CHANGED
@@ -617,7 +617,8 @@ const hasStateDirectory = async (repoRoot) => {
617
617
  const parseLifecycle = (content) => {
618
618
  try {
619
619
  const parsed = JSON.parse(content);
620
- if (parsed.schemaVersion !== 1 || typeof parsed.branch !== "string" || typeof parsed.worktreeId !== "string" || typeof parsed.baseBranch !== "string" || typeof parsed.createdHead !== "string" || parsed.createdHead.length === 0 || typeof parsed.createdAt !== "string" || typeof parsed.updatedAt !== "string") return {
620
+ const isLastDivergedHeadValid = parsed.lastDivergedHead === null || typeof parsed.lastDivergedHead === "string" && parsed.lastDivergedHead.length > 0;
621
+ if (parsed.schemaVersion !== 2 || typeof parsed.branch !== "string" || typeof parsed.worktreeId !== "string" || typeof parsed.baseBranch !== "string" || typeof parsed.everDiverged !== "boolean" || isLastDivergedHeadValid !== true || typeof parsed.createdAt !== "string" || typeof parsed.updatedAt !== "string") return {
621
622
  valid: false,
622
623
  record: null
623
624
  };
@@ -665,15 +666,17 @@ const readWorktreeMergeLifecycle = async ({ repoRoot, branch }) => {
665
666
  };
666
667
  }
667
668
  };
668
- const upsertWorktreeMergeLifecycle = async ({ repoRoot, branch, baseBranch, createdHead }) => {
669
+ const upsertWorktreeMergeLifecycle = async ({ repoRoot, branch, baseBranch, observedDivergedHead }) => {
670
+ const normalizedObservedHead = typeof observedDivergedHead === "string" && observedDivergedHead.length > 0 ? observedDivergedHead : null;
669
671
  if (await hasStateDirectory(repoRoot) !== true) {
670
672
  const now = (/* @__PURE__ */ new Date()).toISOString();
671
673
  return {
672
- schemaVersion: 1,
674
+ schemaVersion: 2,
673
675
  branch,
674
676
  worktreeId: branchToWorktreeId(branch),
675
677
  baseBranch,
676
- createdHead,
678
+ everDiverged: normalizedObservedHead !== null,
679
+ lastDivergedHead: normalizedObservedHead,
677
680
  createdAt: now,
678
681
  updatedAt: now
679
682
  };
@@ -682,14 +685,17 @@ const upsertWorktreeMergeLifecycle = async ({ repoRoot, branch, baseBranch, crea
682
685
  repoRoot,
683
686
  branch
684
687
  });
685
- if (current.valid && current.record !== null && current.record.baseBranch === baseBranch) return current.record;
688
+ if (current.valid && current.record !== null && current.record.baseBranch === baseBranch && normalizedObservedHead === null) return current.record;
686
689
  const now = (/* @__PURE__ */ new Date()).toISOString();
690
+ const everDiverged = current.record?.everDiverged === true || normalizedObservedHead !== null;
691
+ const lastDivergedHead = normalizedObservedHead ?? current.record?.lastDivergedHead ?? null;
687
692
  const next = {
688
- schemaVersion: 1,
693
+ schemaVersion: 2,
689
694
  branch,
690
695
  worktreeId: branchToWorktreeId(branch),
691
696
  baseBranch,
692
- createdHead: current.record?.createdHead ?? createdHead,
697
+ everDiverged,
698
+ lastDivergedHead,
693
699
  createdAt: current.record?.createdAt ?? now,
694
700
  updatedAt: now
695
701
  };
@@ -699,15 +705,17 @@ const upsertWorktreeMergeLifecycle = async ({ repoRoot, branch, baseBranch, crea
699
705
  });
700
706
  return next;
701
707
  };
702
- const moveWorktreeMergeLifecycle = async ({ repoRoot, fromBranch, toBranch, baseBranch, createdHead }) => {
708
+ const moveWorktreeMergeLifecycle = async ({ repoRoot, fromBranch, toBranch, baseBranch, observedDivergedHead }) => {
709
+ const normalizedObservedHead = typeof observedDivergedHead === "string" && observedDivergedHead.length > 0 ? observedDivergedHead : null;
703
710
  if (await hasStateDirectory(repoRoot) !== true) {
704
711
  const now = (/* @__PURE__ */ new Date()).toISOString();
705
712
  return {
706
- schemaVersion: 1,
713
+ schemaVersion: 2,
707
714
  branch: toBranch,
708
715
  worktreeId: branchToWorktreeId(toBranch),
709
716
  baseBranch,
710
- createdHead,
717
+ everDiverged: normalizedObservedHead !== null,
718
+ lastDivergedHead: normalizedObservedHead,
711
719
  createdAt: now,
712
720
  updatedAt: now
713
721
  };
@@ -718,12 +726,15 @@ const moveWorktreeMergeLifecycle = async ({ repoRoot, fromBranch, toBranch, base
718
726
  });
719
727
  const targetPath = lifecycleFilePath(repoRoot, toBranch);
720
728
  const now = (/* @__PURE__ */ new Date()).toISOString();
729
+ const everDiverged = source.record?.everDiverged === true || normalizedObservedHead !== null;
730
+ const lastDivergedHead = normalizedObservedHead ?? source.record?.lastDivergedHead ?? null;
721
731
  const next = {
722
- schemaVersion: 1,
732
+ schemaVersion: 2,
723
733
  branch: toBranch,
724
734
  worktreeId: branchToWorktreeId(toBranch),
725
735
  baseBranch,
726
- createdHead: source.record?.createdHead ?? createdHead,
736
+ everDiverged,
737
+ lastDivergedHead,
727
738
  createdAt: source.record?.createdAt ?? now,
728
739
  updatedAt: now
729
740
  };
@@ -1049,6 +1060,56 @@ const resolveLockState = async ({ repoRoot, branch }) => {
1049
1060
  };
1050
1061
  }
1051
1062
  };
1063
+ const WORK_REFLOG_MESSAGE_PATTERN = /^(commit(?: \([^)]*\))?|cherry-pick|revert|rebase \(pick\)|merge):/;
1064
+ const resolveLifecycleFromReflog = async ({ repoRoot, branch, baseBranch }) => {
1065
+ const reflog = await runGitCommand({
1066
+ cwd: repoRoot,
1067
+ args: [
1068
+ "reflog",
1069
+ "show",
1070
+ "--format=%H%x09%gs",
1071
+ branch
1072
+ ],
1073
+ reject: false
1074
+ });
1075
+ if (reflog.exitCode !== 0) return {
1076
+ merged: null,
1077
+ divergedHead: null
1078
+ };
1079
+ let latestWorkHead = null;
1080
+ for (const line of reflog.stdout.split("\n")) {
1081
+ const trimmed = line.trim();
1082
+ if (trimmed.length === 0) continue;
1083
+ const separatorIndex = trimmed.indexOf(" ");
1084
+ if (separatorIndex <= 0) continue;
1085
+ const head = trimmed.slice(0, separatorIndex).trim();
1086
+ const message = trimmed.slice(separatorIndex + 1).trim();
1087
+ if (head.length === 0 || WORK_REFLOG_MESSAGE_PATTERN.test(message) !== true) continue;
1088
+ if (latestWorkHead === null) latestWorkHead = head;
1089
+ const result = await runGitCommand({
1090
+ cwd: repoRoot,
1091
+ args: [
1092
+ "merge-base",
1093
+ "--is-ancestor",
1094
+ head,
1095
+ baseBranch
1096
+ ],
1097
+ reject: false
1098
+ });
1099
+ if (result.exitCode === 0) return {
1100
+ merged: true,
1101
+ divergedHead: head
1102
+ };
1103
+ if (result.exitCode !== 1) return {
1104
+ merged: null,
1105
+ divergedHead: latestWorkHead
1106
+ };
1107
+ }
1108
+ return {
1109
+ merged: false,
1110
+ divergedHead: latestWorkHead
1111
+ };
1112
+ };
1052
1113
  const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, mergedByPrByBranch }) => {
1053
1114
  if (branch === null) return {
1054
1115
  byAncestry: null,
@@ -1077,10 +1138,39 @@ const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, mergedBy
1077
1138
  repoRoot,
1078
1139
  branch,
1079
1140
  baseBranch,
1080
- createdHead: head
1141
+ observedDivergedHead: byAncestry === false ? head : null
1081
1142
  });
1082
- if (byAncestry === true) byLifecycle = lifecycle.createdHead !== head;
1083
- else if (byAncestry === false) byLifecycle = false;
1143
+ if (byAncestry === false) byLifecycle = false;
1144
+ else if (byAncestry === true) if (lifecycle.everDiverged !== true || lifecycle.lastDivergedHead === null) if (byPR === true) byLifecycle = null;
1145
+ else {
1146
+ const probe = await resolveLifecycleFromReflog({
1147
+ repoRoot,
1148
+ branch,
1149
+ baseBranch
1150
+ });
1151
+ byLifecycle = probe.merged;
1152
+ if (probe.divergedHead !== null) await upsertWorktreeMergeLifecycle({
1153
+ repoRoot,
1154
+ branch,
1155
+ baseBranch,
1156
+ observedDivergedHead: probe.divergedHead
1157
+ });
1158
+ }
1159
+ else {
1160
+ const lifecycleResult = await runGitCommand({
1161
+ cwd: repoRoot,
1162
+ args: [
1163
+ "merge-base",
1164
+ "--is-ancestor",
1165
+ lifecycle.lastDivergedHead,
1166
+ baseBranch
1167
+ ],
1168
+ reject: false
1169
+ });
1170
+ if (lifecycleResult.exitCode === 0) byLifecycle = true;
1171
+ else if (lifecycleResult.exitCode === 1) byLifecycle = false;
1172
+ else byLifecycle = null;
1173
+ }
1084
1174
  }
1085
1175
  return {
1086
1176
  byAncestry,
@@ -1897,22 +1987,6 @@ const resolveBaseBranch = async (repoRoot) => {
1897
1987
  for (const candidate of ["main", "master"]) if (await doesGitRefExist(repoRoot, `refs/heads/${candidate}`)) return candidate;
1898
1988
  throw createCliError("INVALID_ARGUMENT", { message: "Unable to resolve base branch. Configure vde-worktree.baseBranch." });
1899
1989
  };
1900
- const resolveBranchHead = async ({ repoRoot, branch }) => {
1901
- const resolved = await runGitCommand({
1902
- cwd: repoRoot,
1903
- args: [
1904
- "rev-parse",
1905
- "--verify",
1906
- branch
1907
- ],
1908
- reject: false
1909
- });
1910
- if (resolved.exitCode !== 0 || resolved.stdout.trim().length === 0) throw createCliError("INVALID_ARGUMENT", {
1911
- message: `Failed to resolve branch head: ${branch}`,
1912
- details: { branch }
1913
- });
1914
- return resolved.stdout.trim();
1915
- };
1916
1990
  const ensureTargetPathWritable = async (targetPath) => {
1917
1991
  try {
1918
1992
  await access(targetPath, constants.F_OK);
@@ -3095,10 +3169,7 @@ const createCli = (options = {}) => {
3095
3169
  repoRoot,
3096
3170
  branch,
3097
3171
  baseBranch,
3098
- createdHead: await resolveBranchHead({
3099
- repoRoot,
3100
- branch
3101
- })
3172
+ observedDivergedHead: null
3102
3173
  });
3103
3174
  await runPostHook({
3104
3175
  name: "new",
@@ -3133,18 +3204,12 @@ const createCli = (options = {}) => {
3133
3204
  const snapshot = await collectWorktreeSnapshot$1(repoRoot);
3134
3205
  const existing = snapshot.worktrees.find((worktree) => worktree.branch === branch);
3135
3206
  if (existing !== void 0) {
3136
- if (snapshot.baseBranch !== null) {
3137
- const branchHead = await resolveBranchHead({
3138
- repoRoot,
3139
- branch
3140
- });
3141
- await upsertWorktreeMergeLifecycle({
3142
- repoRoot,
3143
- branch,
3144
- baseBranch: snapshot.baseBranch,
3145
- createdHead: branchHead
3146
- });
3147
- }
3207
+ if (snapshot.baseBranch !== null) await upsertWorktreeMergeLifecycle({
3208
+ repoRoot,
3209
+ branch,
3210
+ baseBranch: snapshot.baseBranch,
3211
+ observedDivergedHead: null
3212
+ });
3148
3213
  return {
3149
3214
  status: "existing",
3150
3215
  branch,
@@ -3190,18 +3255,12 @@ const createCli = (options = {}) => {
3190
3255
  ]
3191
3256
  });
3192
3257
  }
3193
- if (lifecycleBaseBranch !== null) {
3194
- const branchHead = await resolveBranchHead({
3195
- repoRoot,
3196
- branch
3197
- });
3198
- await upsertWorktreeMergeLifecycle({
3199
- repoRoot,
3200
- branch,
3201
- baseBranch: lifecycleBaseBranch,
3202
- createdHead: branchHead
3203
- });
3204
- }
3258
+ if (lifecycleBaseBranch !== null) await upsertWorktreeMergeLifecycle({
3259
+ repoRoot,
3260
+ branch,
3261
+ baseBranch: lifecycleBaseBranch,
3262
+ observedDivergedHead: null
3263
+ });
3205
3264
  await runPostHook({
3206
3265
  name: "switch",
3207
3266
  context: hookContext
@@ -3301,19 +3360,13 @@ const createCli = (options = {}) => {
3301
3360
  newPath
3302
3361
  ]
3303
3362
  });
3304
- if (snapshot.baseBranch !== null) {
3305
- const branchHead = await resolveBranchHead({
3306
- repoRoot,
3307
- branch: newBranch
3308
- });
3309
- await moveWorktreeMergeLifecycle({
3310
- repoRoot,
3311
- fromBranch: oldBranch,
3312
- toBranch: newBranch,
3313
- baseBranch: snapshot.baseBranch,
3314
- createdHead: branchHead
3315
- });
3316
- }
3363
+ if (snapshot.baseBranch !== null) await moveWorktreeMergeLifecycle({
3364
+ repoRoot,
3365
+ fromBranch: oldBranch,
3366
+ toBranch: newBranch,
3367
+ baseBranch: snapshot.baseBranch,
3368
+ observedDivergedHead: null
3369
+ });
3317
3370
  await runPostHook({
3318
3371
  name: "mv",
3319
3372
  context: hookContext
@@ -3582,10 +3635,7 @@ const createCli = (options = {}) => {
3582
3635
  repoRoot,
3583
3636
  branch,
3584
3637
  baseBranch: lifecycleBaseBranch,
3585
- createdHead: await resolveBranchHead({
3586
- repoRoot,
3587
- branch
3588
- })
3638
+ observedDivergedHead: null
3589
3639
  });
3590
3640
  await runPostHook({
3591
3641
  name: "get",
@@ -3612,10 +3662,7 @@ const createCli = (options = {}) => {
3612
3662
  repoRoot,
3613
3663
  branch,
3614
3664
  baseBranch: lifecycleBaseBranch,
3615
- createdHead: await resolveBranchHead({
3616
- repoRoot,
3617
- branch
3618
- })
3665
+ observedDivergedHead: null
3619
3666
  });
3620
3667
  await runPostHook({
3621
3668
  name: "get",
@@ -3727,10 +3774,7 @@ const createCli = (options = {}) => {
3727
3774
  repoRoot,
3728
3775
  branch,
3729
3776
  baseBranch,
3730
- createdHead: await resolveBranchHead({
3731
- repoRoot,
3732
- branch
3733
- })
3777
+ observedDivergedHead: null
3734
3778
  });
3735
3779
  if (stashOid !== null) {
3736
3780
  if ((await runGitCommand({