vde-worktree 0.0.16 → 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,6 +424,7 @@ 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
 
@@ -436,7 +437,7 @@ vw completion zsh --install
436
437
  - `byPR === false` または lifecycle が明示的に未取り込みなら `overall = false`
437
438
  - それ以外は `overall = null`
438
439
 
439
- `byPR` が `null` になる例:
440
+ `byPR` が `null` かつ `pr.status` が `unknown` になる例:
440
441
 
441
442
  - `gh` 未導入
442
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,6 +424,7 @@ 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
 
@@ -436,7 +437,7 @@ Overall policy:
436
437
  - `byPR === false` or explicit lifecycle "not merged" evidence => `overall = false`
437
438
  - otherwise `overall = null`
438
439
 
439
- `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`).
440
441
 
441
442
  ## JSON Contract
442
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
  }
package/dist/index.mjs CHANGED
@@ -853,43 +853,68 @@ const toTargetBranches = ({ branches, baseBranch }) => {
853
853
  }
854
854
  return [...uniqueBranches];
855
855
  };
856
- const buildUnknownBranchMap = (branches) => {
857
- return new Map(branches.map((branch) => [branch, null]));
856
+ const buildUnknownPrStatusMap = (branches) => {
857
+ return new Map(branches.map((branch) => [branch, "unknown"]));
858
858
  };
859
- const parseMergedBranches = ({ raw, targetBranches }) => {
859
+ const parseUpdatedAtMillis = (value) => {
860
+ if (typeof value !== "string" || value.length === 0) return Number.NEGATIVE_INFINITY;
861
+ const parsed = Date.parse(value);
862
+ if (Number.isNaN(parsed)) return Number.NEGATIVE_INFINITY;
863
+ return parsed;
864
+ };
865
+ const toPrStatus = (record) => {
866
+ if (typeof record.mergedAt === "string" && record.mergedAt.length > 0) return "merged";
867
+ const state = typeof record.state === "string" ? record.state.toUpperCase() : "";
868
+ if (state === "MERGED") return "merged";
869
+ if (state === "OPEN") return "open";
870
+ if (state === "CLOSED") return "closed_unmerged";
871
+ return "unknown";
872
+ };
873
+ const parsePrStatusByBranch = ({ raw, targetBranches }) => {
860
874
  try {
861
875
  const parsed = JSON.parse(raw);
862
876
  if (Array.isArray(parsed) !== true) return null;
877
+ const targetBranchSet = new Set(targetBranches);
863
878
  const records = parsed;
864
- const mergedBranches = /* @__PURE__ */ new Set();
865
- for (const record of records) {
879
+ const latestByBranch = /* @__PURE__ */ new Map();
880
+ for (const [index, record] of records.entries()) {
866
881
  if (typeof record?.headRefName !== "string" || record.headRefName.length === 0) continue;
867
- if (targetBranches.has(record.headRefName) !== true) continue;
868
- if (typeof record.mergedAt !== "string" || record.mergedAt.length === 0) continue;
869
- mergedBranches.add(record.headRefName);
882
+ if (targetBranchSet.has(record.headRefName) !== true) continue;
883
+ const updatedAtMillis = parseUpdatedAtMillis(record.updatedAt);
884
+ const status = toPrStatus(record);
885
+ const current = latestByBranch.get(record.headRefName);
886
+ if (current === void 0 || updatedAtMillis > current.updatedAtMillis || updatedAtMillis === current.updatedAtMillis && index > current.index) latestByBranch.set(record.headRefName, {
887
+ updatedAtMillis,
888
+ index,
889
+ status
890
+ });
891
+ }
892
+ const result = /* @__PURE__ */ new Map();
893
+ for (const branch of targetBranches) {
894
+ const latest = latestByBranch.get(branch);
895
+ result.set(branch, latest?.status ?? "none");
870
896
  }
871
- return mergedBranches;
897
+ return result;
872
898
  } catch {
873
899
  return null;
874
900
  }
875
901
  };
876
- const resolveMergedByPrBatch = async ({ repoRoot, baseBranch, branches, enabled = true, runGh = defaultRunGh }) => {
877
- if (enabled !== true) return /* @__PURE__ */ new Map();
902
+ const resolvePrStatusByBranchBatch = async ({ repoRoot, baseBranch, branches, enabled = true, runGh = defaultRunGh }) => {
878
903
  if (baseBranch === null) return /* @__PURE__ */ new Map();
879
904
  const targetBranches = toTargetBranches({
880
905
  branches,
881
906
  baseBranch
882
907
  });
883
908
  if (targetBranches.length === 0) return /* @__PURE__ */ new Map();
909
+ if (enabled !== true) return buildUnknownPrStatusMap(targetBranches);
884
910
  try {
885
- const targetBranchSet = new Set(targetBranches);
886
911
  const result = await runGh({
887
912
  cwd: repoRoot,
888
913
  args: [
889
914
  "pr",
890
915
  "list",
891
916
  "--state",
892
- "merged",
917
+ "all",
893
918
  "--base",
894
919
  baseBranch,
895
920
  "--search",
@@ -897,21 +922,19 @@ const resolveMergedByPrBatch = async ({ repoRoot, baseBranch, branches, enabled
897
922
  "--limit",
898
923
  "1000",
899
924
  "--json",
900
- "headRefName,mergedAt"
925
+ "headRefName,state,mergedAt,updatedAt"
901
926
  ]
902
927
  });
903
- if (result.exitCode !== 0) return buildUnknownBranchMap(targetBranches);
904
- const mergedBranches = parseMergedBranches({
928
+ if (result.exitCode !== 0) return buildUnknownPrStatusMap(targetBranches);
929
+ const prStatusByBranch = parsePrStatusByBranch({
905
930
  raw: result.stdout,
906
- targetBranches: targetBranchSet
931
+ targetBranches
907
932
  });
908
- if (mergedBranches === null) return buildUnknownBranchMap(targetBranches);
909
- return new Map(targetBranches.map((branch) => {
910
- return [branch, mergedBranches.has(branch)];
911
- }));
933
+ if (prStatusByBranch === null) return buildUnknownPrStatusMap(targetBranches);
934
+ return prStatusByBranch;
912
935
  } catch (error) {
913
- if (error.code === "ENOENT") return buildUnknownBranchMap(targetBranches);
914
- return buildUnknownBranchMap(targetBranches);
936
+ if (error.code === "ENOENT") return buildUnknownPrStatusMap(targetBranches);
937
+ return buildUnknownPrStatusMap(targetBranches);
915
938
  }
916
939
  };
917
940
 
@@ -1110,7 +1133,7 @@ const resolveLifecycleFromReflog = async ({ repoRoot, branch, baseBranch }) => {
1110
1133
  divergedHead: latestWorkHead
1111
1134
  };
1112
1135
  };
1113
- const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, mergedByPrByBranch }) => {
1136
+ const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, prStatusByBranch }) => {
1114
1137
  if (branch === null) return {
1115
1138
  byAncestry: null,
1116
1139
  byPR: null,
@@ -1131,7 +1154,10 @@ const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, mergedBy
1131
1154
  if (result.exitCode === 0) byAncestry = true;
1132
1155
  else if (result.exitCode === 1) byAncestry = false;
1133
1156
  }
1134
- const byPR = branch === baseBranch ? null : mergedByPrByBranch.get(branch) ?? null;
1157
+ const prStatus = branch === baseBranch ? null : prStatusByBranch.get(branch) ?? null;
1158
+ let byPR = null;
1159
+ if (prStatus === "merged") byPR = true;
1160
+ else if (prStatus === "none" || prStatus === "open" || prStatus === "closed_unmerged") byPR = false;
1135
1161
  let byLifecycle = null;
1136
1162
  if (baseBranch !== null) {
1137
1163
  const lifecycle = await upsertWorktreeMergeLifecycle({
@@ -1182,6 +1208,10 @@ const resolveMergedState = async ({ repoRoot, branch, head, baseBranch, mergedBy
1182
1208
  })
1183
1209
  };
1184
1210
  };
1211
+ const resolvePrState = ({ branch, baseBranch, prStatusByBranch }) => {
1212
+ if (branch === null || branch === baseBranch) return { status: null };
1213
+ return { status: prStatusByBranch.get(branch) ?? null };
1214
+ };
1185
1215
  const resolveMergedOverall = ({ byAncestry, byPR, byLifecycle }) => {
1186
1216
  if (byPR === true || byLifecycle === true) return true;
1187
1217
  if (byAncestry === false) return false;
@@ -1228,7 +1258,7 @@ const resolveUpstreamState = async (worktreePath) => {
1228
1258
  remote: upstreamRef.stdout.trim()
1229
1259
  };
1230
1260
  };
1231
- const enrichWorktree = async ({ repoRoot, worktree, baseBranch, mergedByPrByBranch }) => {
1261
+ const enrichWorktree = async ({ repoRoot, worktree, baseBranch, prStatusByBranch }) => {
1232
1262
  const [dirty, locked, merged, upstream] = await Promise.all([
1233
1263
  resolveDirty(worktree.path),
1234
1264
  resolveLockState({
@@ -1240,10 +1270,15 @@ const enrichWorktree = async ({ repoRoot, worktree, baseBranch, mergedByPrByBran
1240
1270
  branch: worktree.branch,
1241
1271
  head: worktree.head,
1242
1272
  baseBranch,
1243
- mergedByPrByBranch
1273
+ prStatusByBranch
1244
1274
  }),
1245
1275
  resolveUpstreamState(worktree.path)
1246
1276
  ]);
1277
+ const pr = resolvePrState({
1278
+ branch: worktree.branch,
1279
+ baseBranch,
1280
+ prStatusByBranch
1281
+ });
1247
1282
  return {
1248
1283
  branch: worktree.branch,
1249
1284
  path: worktree.path,
@@ -1251,6 +1286,7 @@ const enrichWorktree = async ({ repoRoot, worktree, baseBranch, mergedByPrByBran
1251
1286
  dirty,
1252
1287
  locked,
1253
1288
  merged,
1289
+ pr,
1254
1290
  upstream
1255
1291
  };
1256
1292
  };
@@ -1260,7 +1296,7 @@ const collectWorktreeSnapshot = async (repoRoot, { noGh = false } = {}) => {
1260
1296
  listGitWorktrees(repoRoot),
1261
1297
  resolveEnableGh(repoRoot)
1262
1298
  ]);
1263
- const mergedByPrByBranch = await resolveMergedByPrBatch({
1299
+ const prStatusByBranch = await resolvePrStatusByBranchBatch({
1264
1300
  repoRoot,
1265
1301
  baseBranch,
1266
1302
  branches: worktrees.map((worktree) => worktree.branch),
@@ -1274,7 +1310,7 @@ const collectWorktreeSnapshot = async (repoRoot, { noGh = false } = {}) => {
1274
1310
  repoRoot,
1275
1311
  worktree,
1276
1312
  baseBranch,
1277
- mergedByPrByBranch
1313
+ prStatusByBranch
1278
1314
  });
1279
1315
  }))
1280
1316
  };
@@ -1530,9 +1566,9 @@ const colorizeListTableLine = ({ line, theme }) => {
1530
1566
  const segments = line.split("│");
1531
1567
  if (segments.length < 3) return line;
1532
1568
  const cells = segments.slice(1, -1);
1533
- if (cells.length !== 7) return line;
1569
+ if (cells.length !== 8) return line;
1534
1570
  const headers = cells.map((cell) => cell.trim());
1535
- if (headers[0] === "branch" && headers[1] === "dirty" && headers[2] === "merged" && headers[3] === "locked" && headers[4] === "ahead" && headers[5] === "behind" && headers[6] === "path") {
1571
+ if (headers[0] === "branch" && headers[1] === "dirty" && headers[2] === "merged" && headers[3] === "pr" && headers[4] === "locked" && headers[5] === "ahead" && headers[6] === "behind" && headers[7] === "path") {
1536
1572
  const nextCells = cells.map((cell) => colorizeCellContent({
1537
1573
  cell,
1538
1574
  color: theme.header
@@ -1546,15 +1582,18 @@ const colorizeListTableLine = ({ line, theme }) => {
1546
1582
  const branchCell = cells[0];
1547
1583
  const dirtyCell = cells[1];
1548
1584
  const mergedCell = cells[2];
1549
- const lockedCell = cells[3];
1550
- const aheadCell = cells[4];
1551
- const behindCell = cells[5];
1552
- const pathCell = cells[6];
1585
+ const prCell = cells[3];
1586
+ const lockedCell = cells[4];
1587
+ const aheadCell = cells[5];
1588
+ const behindCell = cells[6];
1589
+ const pathCell = cells[7];
1553
1590
  const branchColor = branchCell.includes("(detached)") === true ? theme.branchDetached : branchCell.trimStart().startsWith("*") ? theme.branchCurrent : theme.branch;
1554
1591
  const dirtyTrimmed = dirtyCell.trim();
1555
1592
  const dirtyColor = dirtyTrimmed === "dirty" ? theme.dirty : dirtyTrimmed === "clean" ? theme.clean : theme.value;
1556
1593
  const mergedTrimmed = mergedCell.trim();
1557
1594
  const mergedColor = mergedTrimmed === "merged" ? theme.merged : mergedTrimmed === "unmerged" ? theme.unmerged : mergedTrimmed === "-" ? theme.base : theme.unknown;
1595
+ const prTrimmed = prCell.trim();
1596
+ const prColor = prTrimmed === "merged" ? theme.merged : prTrimmed === "open" ? theme.value : prTrimmed === "closed_unmerged" ? theme.unmerged : prTrimmed === "none" ? theme.muted : prTrimmed === "-" ? theme.base : theme.unknown;
1558
1597
  const lockedColor = lockedCell.trim() === "locked" ? theme.locked : theme.muted;
1559
1598
  const aheadTrimmed = aheadCell.trim();
1560
1599
  const aheadValue = Number.parseInt(aheadTrimmed, 10);
@@ -1575,6 +1614,10 @@ const colorizeListTableLine = ({ line, theme }) => {
1575
1614
  cell: mergedCell,
1576
1615
  color: mergedColor
1577
1616
  }),
1617
+ colorizeCellContent({
1618
+ cell: prCell,
1619
+ color: prColor
1620
+ }),
1578
1621
  colorizeCellContent({
1579
1622
  cell: lockedCell,
1580
1623
  color: lockedColor
@@ -1615,7 +1658,7 @@ const commandHelpEntries = [
1615
1658
  name: "list",
1616
1659
  usage: "vw list [--json]",
1617
1660
  summary: "List worktrees with status metadata.",
1618
- details: ["Table output includes branch, path, dirty, lock, merged, and ahead/behind vs base branch.", "JSON output includes upstream metadata fields."]
1661
+ details: ["Table output includes branch, path, dirty, lock, merged, PR state, and ahead/behind vs base branch.", "JSON output includes PR and upstream metadata fields."]
1619
1662
  },
1620
1663
  {
1621
1664
  name: "status",
@@ -2432,6 +2475,11 @@ const formatMergedColor = ({ mergedState, theme }) => {
2432
2475
  if (normalized === "base") return theme.base(mergedState);
2433
2476
  return theme.unknown(mergedState);
2434
2477
  };
2478
+ const formatPrDisplayState = ({ prStatus, isBaseBranch }) => {
2479
+ if (isBaseBranch || prStatus === null) return "-";
2480
+ if (prStatus === "none" || prStatus === "open" || prStatus === "merged" || prStatus === "closed_unmerged" || prStatus === "unknown") return prStatus;
2481
+ return "unknown";
2482
+ };
2435
2483
  const formatListUpstreamCount = (value) => {
2436
2484
  if (value === null) return "-";
2437
2485
  return String(value);
@@ -2593,7 +2641,7 @@ const renderGeneralHelpText = ({ version }) => {
2593
2641
  " --json Output machine-readable JSON.",
2594
2642
  " --verbose Enable verbose logs.",
2595
2643
  " --no-hooks Disable hooks for this run (requires --allow-unsafe).",
2596
- " --no-gh Disable GitHub CLI based PR merge checks for this run.",
2644
+ " --no-gh Disable GitHub CLI based PR status checks for this run.",
2597
2645
  " --allow-unsafe Explicitly allow unsafe behavior in non-TTY mode.",
2598
2646
  " --hook-timeout-ms <ms> Override hook timeout.",
2599
2647
  " --lock-timeout-ms <ms> Override repository lock timeout.",
@@ -2696,7 +2744,7 @@ const createCli = (options = {}) => {
2696
2744
  },
2697
2745
  gh: {
2698
2746
  type: "boolean",
2699
- description: "Enable GitHub CLI based PR merge checks (disable with --no-gh)",
2747
+ description: "Enable GitHub CLI based PR status checks (disable with --no-gh)",
2700
2748
  default: true
2701
2749
  },
2702
2750
  allowUnsafe: {
@@ -3030,6 +3078,7 @@ const createCli = (options = {}) => {
3030
3078
  "branch",
3031
3079
  "dirty",
3032
3080
  "merged",
3081
+ "pr",
3033
3082
  "locked",
3034
3083
  "ahead",
3035
3084
  "behind",
@@ -3040,11 +3089,17 @@ const createCli = (options = {}) => {
3040
3089
  baseBranch: snapshot.baseBranch,
3041
3090
  worktree
3042
3091
  });
3043
- const mergedState = (worktree.branch !== null && snapshot.baseBranch !== null && worktree.branch === snapshot.baseBranch) === true ? "-" : worktree.merged.overall === true ? "merged" : worktree.merged.overall === false ? "unmerged" : "unknown";
3092
+ const isBaseBranch = worktree.branch !== null && snapshot.baseBranch !== null && worktree.branch === snapshot.baseBranch;
3093
+ const mergedState = isBaseBranch === true ? "-" : worktree.merged.overall === true ? "merged" : worktree.merged.overall === false ? "unmerged" : "unknown";
3094
+ const prState = formatPrDisplayState({
3095
+ prStatus: worktree.pr.status,
3096
+ isBaseBranch
3097
+ });
3044
3098
  return [
3045
3099
  `${worktree.path === repoContext.currentWorktreeRoot ? "*" : " "} ${worktree.branch ?? "(detached)"}`,
3046
3100
  worktree.dirty ? "dirty" : "clean",
3047
3101
  mergedState,
3102
+ prState,
3048
3103
  worktree.locked.value ? "locked" : "-",
3049
3104
  formatListUpstreamCount(distanceFromBase.ahead),
3050
3105
  formatListUpstreamCount(distanceFromBase.behind),