vde-worktree 0.0.15 → 0.0.17

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
@@ -22,7 +22,7 @@
22
22
  - Node.js 22+
23
23
  - pnpm 10+
24
24
  - `fzf`(`cd` に必須)
25
- - `gh`(PR merged 判定に任意)
25
+ - `gh`(PR 状態判定に任意)
26
26
 
27
27
  ## インストール / ビルド
28
28
 
@@ -111,7 +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
+ - `--no-gh`: 今回の実行で `gh` による PR 状態判定を無効化
115
115
  - `--hook-timeout-ms <ms>`: hook timeout 上書き
116
116
  - `--lock-timeout-ms <ms>`: repo lock timeout 上書き
117
117
 
@@ -140,8 +140,8 @@ vw list --no-gh
140
140
  機能:
141
141
 
142
142
  - Git の porcelain 情報から worktree 一覧を取得
143
- - branch/path/dirty/lock/merged/upstream を表示
144
- - `--no-gh` 指定時は PR merged 判定をスキップ(`merged.byPR` は `null`)
143
+ - branch/path/dirty/lock/merged/PR/upstream を表示
144
+ - `--no-gh` 指定時は PR 状態判定をスキップ(`pr.status` は `unknown`、`merged.byPR` は `null`)
145
145
  - 対話ターミナルでは Catppuccin 風の ANSI 色で表示
146
146
 
147
147
  ### `status`
@@ -424,14 +424,20 @@ vw completion zsh --install
424
424
  - `merged.byAncestry`: ローカル履歴判定(`git merge-base --is-ancestor`)
425
425
  - `merged.byPR`: GitHub PR merged 判定(`gh`)
426
426
  - `merged.overall`: 最終判定
427
+ - `pr.status`: PR 状態(`none` / `open` / `merged` / `closed_unmerged` / `unknown`)
427
428
 
428
429
  `overall` ポリシー:
429
430
 
430
- - `byPR === true` -> `overall = true`
431
- - `byPR === false` -> `overall = false`
432
- - `byPR === null` -> `byAncestry` にフォールバック
431
+ - `byPR === true` -> `overall = true`(squash/rebase merge を含む)
432
+ - `byAncestry === false` -> `overall = false`
433
+ - `byAncestry === true` の場合は、分岐の証跡があるときだけ merged 扱い
434
+ - `.vde/worktree/state/branches/*.json` の lifecycle 記録
435
+ - lifecycle がない場合の `git reflog` フォールバック
436
+ - 分岐証跡が `baseBranch` に取り込まれていれば `overall = true`
437
+ - `byPR === false` または lifecycle が明示的に未取り込みなら `overall = false`
438
+ - それ以外は `overall = null`
433
439
 
434
- `byPR` が `null` になる例:
440
+ `byPR` が `null` かつ `pr.status` が `unknown` になる例:
435
441
 
436
442
  - `gh` 未導入
437
443
  - `gh auth` 未設定
package/README.md CHANGED
@@ -111,7 +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
+ - `--no-gh`: disable GitHub CLI based PR status checks for this run
115
115
  - `--hook-timeout-ms <ms>`: hook timeout override
116
116
  - `--lock-timeout-ms <ms>`: repository lock timeout override
117
117
 
@@ -140,8 +140,8 @@ vw list --no-gh
140
140
  What it does:
141
141
 
142
142
  - Lists all worktrees from Git porcelain output
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`)
143
+ - Includes metadata such as branch, path, dirty, lock, merged, PR status, and upstream status
144
+ - With `--no-gh`, skips PR status checks (`pr.status` becomes `unknown`, `merged.byPR` becomes `null`)
145
145
  - In interactive terminal, uses Catppuccin-style ANSI colors
146
146
 
147
147
  ### `status`
@@ -424,14 +424,20 @@ Each worktree reports:
424
424
  - `merged.byAncestry`: local ancestry check (`git merge-base --is-ancestor <branch> <baseBranch>`)
425
425
  - `merged.byPR`: PR-based merged check via GitHub CLI
426
426
  - `merged.overall`: final decision
427
+ - `pr.status`: PR state (`none` / `open` / `merged` / `closed_unmerged` / `unknown`)
427
428
 
428
429
  Overall policy:
429
430
 
430
- - `byPR === true` => `overall = true`
431
- - `byPR === false` => `overall = false`
432
- - `byPR === null` => fallback to `byAncestry`
431
+ - `byPR === true` => `overall = true` (includes squash/rebase merges)
432
+ - `byAncestry === false` => `overall = false`
433
+ - when `byAncestry === true`, require divergence evidence before treating as merged
434
+ - lifecycle evidence from `.vde/worktree/state/branches/*.json`
435
+ - reflog fallback (`git reflog`) when lifecycle evidence is missing
436
+ - if divergence evidence is contained in `baseBranch`, `overall = true`
437
+ - `byPR === false` or explicit lifecycle "not merged" evidence => `overall = false`
438
+ - otherwise `overall = null`
433
439
 
434
- `byPR` becomes `null` when PR lookup is unavailable (for example: `gh` missing, auth missing, API error, `vde-worktree.enableGh=false`, or `--no-gh`).
440
+ `byPR` becomes `null` and `pr.status` becomes `unknown` when PR lookup is unavailable (for example: `gh` missing, auth missing, API error, `vde-worktree.enableGh=false`, or `--no-gh`).
435
441
 
436
442
  ## JSON Contract
437
443
 
@@ -68,6 +68,10 @@ const toFlag = (value) => {
68
68
  if (value === false) return "no"
69
69
  return "unknown"
70
70
  }
71
+ const toPrStatus = (value) => {
72
+ if (typeof value !== "string" || value.length === 0) return "n/a"
73
+ return value
74
+ }
71
75
  let payload
72
76
  try {
73
77
  payload = JSON.parse(fs.readFileSync(0, "utf8"))
@@ -78,10 +82,11 @@ const worktrees = Array.isArray(payload.worktrees) ? payload.worktrees : []
78
82
  for (const worktree of worktrees) {
79
83
  if (typeof worktree?.branch !== "string" || worktree.branch.length === 0) continue
80
84
  const merged = toFlag(worktree?.merged?.overall)
85
+ const pr = toPrStatus(worktree?.pr?.status)
81
86
  const dirty = worktree?.dirty === true ? "yes" : "no"
82
87
  const locked = worktree?.locked?.value === true ? "yes" : "no"
83
88
  const path = toDisplayPath(worktree?.path)
84
- const summary = `merged=${merged} dirty=${dirty} locked=${locked}${path ? ` path=${path}` : ""}`
89
+ const summary = `merged=${merged} pr=${pr} dirty=${dirty} locked=${locked}${path ? ` path=${path}` : ""}`
85
90
  const sanitized = summary.replace(/[\t\r\n]+/g, " ").trim()
86
91
  process.stdout.write(`${worktree.branch}\t${sanitized}\n`)
87
92
  }
@@ -101,6 +106,10 @@ const toFlag = (value) => {
101
106
  if (value === false) return "no"
102
107
  return "unknown"
103
108
  }
109
+ const toPrStatus = (value) => {
110
+ if (typeof value !== "string" || value.length === 0) return "n/a"
111
+ return value
112
+ }
104
113
  let payload
105
114
  try {
106
115
  payload = JSON.parse(fs.readFileSync(0, "utf8"))
@@ -118,9 +127,10 @@ for (const worktree of worktrees) {
118
127
  const name = rel.split(path.sep).join("/")
119
128
  const branch = typeof worktree?.branch === "string" && worktree.branch.length > 0 ? worktree.branch : "(detached)"
120
129
  const merged = toFlag(worktree?.merged?.overall)
130
+ const pr = toPrStatus(worktree?.pr?.status)
121
131
  const dirty = worktree?.dirty === true ? "yes" : "no"
122
132
  const locked = worktree?.locked?.value === true ? "yes" : "no"
123
- const summary = `branch=${branch} merged=${merged} dirty=${dirty} locked=${locked}`
133
+ const summary = `branch=${branch} merged=${merged} pr=${pr} dirty=${dirty} locked=${locked}`
124
134
  const sanitized = summary.replace(/[\t\r\n]+/g, " ").trim()
125
135
  process.stdout.write(`${name}\t${sanitized}\n`)
126
136
  }
@@ -45,6 +45,10 @@ const toFlag = (value) => {
45
45
  if (value === false) return "no"
46
46
  return "unknown"
47
47
  }
48
+ const toPrStatus = (value) => {
49
+ if (typeof value !== "string" || value.length === 0) return "n/a"
50
+ return value
51
+ }
48
52
  let payload
49
53
  try {
50
54
  payload = JSON.parse(fs.readFileSync(0, "utf8"))
@@ -55,10 +59,11 @@ const worktrees = Array.isArray(payload.worktrees) ? payload.worktrees : []
55
59
  for (const worktree of worktrees) {
56
60
  if (typeof worktree?.branch !== "string" || worktree.branch.length === 0) continue
57
61
  const merged = toFlag(worktree?.merged?.overall)
62
+ const pr = toPrStatus(worktree?.pr?.status)
58
63
  const dirty = worktree?.dirty === true ? "yes" : "no"
59
64
  const locked = worktree?.locked?.value === true ? "yes" : "no"
60
65
  const path = toDisplayPath(worktree?.path)
61
- const summary = `merged=${merged} dirty=${dirty} locked=${locked}${path ? ` path=${path}` : ""}`
66
+ const summary = `merged=${merged} pr=${pr} dirty=${dirty} locked=${locked}${path ? ` path=${path}` : ""}`
62
67
  const sanitized = summary.replace(/[\t\r\n]+/g, " ").trim()
63
68
  process.stdout.write(`${worktree.branch}\t${sanitized}\n`)
64
69
  }
@@ -78,6 +83,10 @@ const toFlag = (value) => {
78
83
  if (value === false) return "no"
79
84
  return "unknown"
80
85
  }
86
+ const toPrStatus = (value) => {
87
+ if (typeof value !== "string" || value.length === 0) return "n/a"
88
+ return value
89
+ }
81
90
  let payload
82
91
  try {
83
92
  payload = JSON.parse(fs.readFileSync(0, "utf8"))
@@ -95,9 +104,10 @@ for (const worktree of worktrees) {
95
104
  const name = rel.split(path.sep).join("/")
96
105
  const branch = typeof worktree?.branch === "string" && worktree.branch.length > 0 ? worktree.branch : "(detached)"
97
106
  const merged = toFlag(worktree?.merged?.overall)
107
+ const pr = toPrStatus(worktree?.pr?.status)
98
108
  const dirty = worktree?.dirty === true ? "yes" : "no"
99
109
  const locked = worktree?.locked?.value === true ? "yes" : "no"
100
- const summary = `branch=${branch} merged=${merged} dirty=${dirty} locked=${locked}`
110
+ const summary = `branch=${branch} merged=${merged} pr=${pr} dirty=${dirty} locked=${locked}`
101
111
  const sanitized = summary.replace(/[\t\r\n]+/g, " ").trim()
102
112
  process.stdout.write(`${name}\t${sanitized}\n`)
103
113
  }