clawt 3.4.5 → 3.5.0

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.
Files changed (45) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/README.md +0 -4
  3. package/dist/index.js +430 -306
  4. package/dist/postinstall.js +12 -1
  5. package/docs/alias.md +7 -1
  6. package/docs/completion.md +1 -1
  7. package/docs/config.md +4 -3
  8. package/docs/cover-validate.md +4 -3
  9. package/docs/create.md +28 -12
  10. package/docs/home.md +12 -8
  11. package/docs/init.md +16 -9
  12. package/docs/list.md +13 -7
  13. package/docs/merge.md +12 -12
  14. package/docs/remove.md +24 -13
  15. package/docs/reset.md +6 -4
  16. package/docs/resume.md +3 -4
  17. package/docs/status.md +75 -30
  18. package/docs/sync.md +26 -26
  19. package/docs/validate.md +13 -7
  20. package/package.json +1 -1
  21. package/src/commands/init.ts +6 -2
  22. package/src/commands/tasks.ts +51 -0
  23. package/src/constants/index.ts +3 -0
  24. package/src/constants/interactive-panel.ts +6 -0
  25. package/src/constants/messages/index.ts +4 -2
  26. package/src/constants/messages/interactive-panel.ts +12 -0
  27. package/src/constants/messages/tasks.ts +9 -0
  28. package/src/constants/tasks-template.ts +28 -0
  29. package/src/index.ts +2 -0
  30. package/src/types/command.ts +6 -0
  31. package/src/types/index.ts +1 -1
  32. package/src/utils/formatter.ts +19 -0
  33. package/src/utils/git-branch.ts +116 -0
  34. package/src/utils/git-core.ts +369 -0
  35. package/src/utils/git-worktree.ts +40 -0
  36. package/src/utils/git.ts +3 -521
  37. package/src/utils/index.ts +1 -1
  38. package/src/utils/interactive-panel-render.ts +12 -6
  39. package/src/utils/interactive-panel-state.ts +137 -0
  40. package/src/utils/interactive-panel.ts +44 -188
  41. package/src/utils/keyboard-controller.ts +48 -0
  42. package/src/utils/ui-prompts.ts +240 -0
  43. package/src/utils/worktree-matcher.ts +21 -251
  44. package/tests/unit/commands/tasks.test.ts +153 -0
  45. package/tests/unit/utils/formatter.test.ts +26 -1
package/src/utils/git.ts CHANGED
@@ -1,521 +1,3 @@
1
- import { basename } from 'node:path';
2
- import { execSync } from 'node:child_process';
3
- import { execCommand, execCommandWithInput } from './shell.js';
4
- import { logger } from '../logger/index.js';
5
-
6
- /**
7
- * 获取 git common dir(用于判断是否为主 worktree)
8
- * @param {string} cwd - 工作目录
9
- * @returns {string} git common dir 路径
10
- */
11
- export function getGitCommonDir(cwd?: string): string {
12
- return execCommand('git rev-parse --git-common-dir', { cwd });
13
- }
14
-
15
- /**
16
- * 获取 git 仓库根目录的绝对路径
17
- * @param {string} cwd - 工作目录
18
- * @returns {string} 仓库根目录路径
19
- */
20
- export function getGitTopLevel(cwd?: string): string {
21
- return execCommand('git rev-parse --show-toplevel', { cwd });
22
- }
23
-
24
- /**
25
- * 获取项目名(仓库根目录名称)
26
- * @param {string} cwd - 工作目录
27
- * @returns {string} 项目名
28
- */
29
- export function getProjectName(cwd?: string): string {
30
- const topLevel = getGitTopLevel(cwd);
31
- return basename(topLevel);
32
- }
33
-
34
- /**
35
- * 检查本地分支是否存在
36
- * @param {string} branchName - 分支名
37
- * @param {string} cwd - 工作目录
38
- * @returns {boolean} 分支是否存在
39
- */
40
- export function checkBranchExists(branchName: string, cwd?: string): boolean {
41
- try {
42
- execCommand(`git show-ref --verify refs/heads/${branchName}`, { cwd });
43
- return true;
44
- } catch {
45
- return false;
46
- }
47
- }
48
-
49
- /**
50
- * 创建 worktree 并同时创建新分支
51
- * @param {string} branchName - 新分支名
52
- * @param {string} worktreePath - worktree 目录路径
53
- * @param {string} cwd - 工作目录
54
- */
55
- export function createWorktree(branchName: string, worktreePath: string, cwd?: string): void {
56
- logger.info(`创建 worktree: ${worktreePath}`);
57
- execCommand(`git worktree add -b ${branchName} "${worktreePath}"`, { cwd });
58
- }
59
-
60
- /**
61
- * 强制移除 worktree
62
- * @param {string} worktreePath - worktree 目录路径
63
- * @param {string} cwd - 工作目录
64
- */
65
- export function removeWorktreeByPath(worktreePath: string, cwd?: string): void {
66
- logger.info(`移除 worktree: ${worktreePath}`);
67
- execCommand(`git worktree remove -f "${worktreePath}"`, { cwd });
68
- }
69
-
70
- /**
71
- * 强制删除本地分支
72
- * @param {string} branchName - 分支名
73
- * @param {string} cwd - 工作目录
74
- */
75
- export function deleteBranch(branchName: string, cwd?: string): void {
76
- logger.info(`删除分支: ${branchName}`);
77
- execCommand(`git branch -D ${branchName}`, { cwd });
78
- }
79
-
80
- /**
81
- * 获取工作区状态(git status --porcelain)
82
- * @param {string} cwd - 工作目录
83
- * @returns {string} porcelain 格式输出,为空表示干净
84
- */
85
- export function getStatusPorcelain(cwd?: string): string {
86
- return execCommand('git status --porcelain', { cwd });
87
- }
88
-
89
- /**
90
- * 判断工作区是否干净
91
- * @param {string} cwd - 工作目录
92
- * @returns {boolean} 是否干净
93
- */
94
- export function isWorkingDirClean(cwd?: string): boolean {
95
- return getStatusPorcelain(cwd) === '';
96
- }
97
-
98
- /**
99
- * git add 所有文件
100
- * @param {string} cwd - 工作目录
101
- */
102
- export function gitAddAll(cwd?: string): void {
103
- execCommand('git add .', { cwd });
104
- }
105
-
106
- /**
107
- * git commit
108
- * @param {string} message - 提交信息
109
- * @param {string} cwd - 工作目录
110
- */
111
- export function gitCommit(message: string, cwd?: string): void {
112
- execCommand(`git commit -m '${message.replace(/'/g, "'\\''")}'`, { cwd });
113
- }
114
-
115
- /**
116
- * git merge
117
- * @param {string} branchName - 要合并的分支名
118
- * @param {string} cwd - 工作目录
119
- */
120
- export function gitMerge(branchName: string, cwd?: string): void {
121
- execCommand(`git merge ${branchName}`, { cwd });
122
- }
123
-
124
- /**
125
- * 检查是否有合并冲突
126
- * @param {string} cwd - 工作目录
127
- * @returns {boolean} 是否有冲突
128
- */
129
- export function hasMergeConflict(cwd?: string): boolean {
130
- const status = getStatusPorcelain(cwd);
131
- // UU = 双方修改冲突, AA = 双方新增冲突, DD = 双方删除
132
- return status.split('\n').some((line) => /^(UU|AA|DD|DU|UD|AU|UA)/.test(line));
133
- }
134
-
135
- /**
136
- * git pull
137
- * @param {string} cwd - 工作目录
138
- */
139
- export function gitPull(cwd?: string): void {
140
- execCommand('git pull', { cwd });
141
- }
142
-
143
- /**
144
- * git push
145
- * @param {string} cwd - 工作目录
146
- */
147
- export function gitPush(cwd?: string): void {
148
- execCommand('git push', { cwd });
149
- }
150
-
151
- /**
152
- * git reset --hard HEAD
153
- * @param {string} cwd - 工作目录
154
- */
155
- export function gitResetHard(cwd?: string): void {
156
- execCommand('git reset --hard HEAD', { cwd });
157
- }
158
-
159
- /**
160
- * git clean -fd(删除未跟踪文件)
161
- * @param {string} cwd - 工作目录
162
- */
163
- export function gitCleanForce(cwd?: string): void {
164
- execCommand('git clean -fd', { cwd });
165
- }
166
-
167
- /**
168
- * git stash push -m <message>
169
- * @param {string} message - stash 消息
170
- * @param {string} cwd - 工作目录
171
- */
172
- export function gitStashPush(message: string, cwd?: string): void {
173
- execCommand(`git stash push -m "${message}"`, { cwd });
174
- }
175
-
176
- /**
177
- * git stash apply
178
- * @param {string} cwd - 工作目录
179
- */
180
- export function gitStashApply(cwd?: string): void {
181
- execCommand('git stash apply', { cwd });
182
- }
183
-
184
- /**
185
- * git stash pop stash@{index}
186
- * @param {number} index - stash 索引
187
- * @param {string} cwd - 工作目录
188
- */
189
- export function gitStashPop(index: number = 0, cwd?: string): void {
190
- execCommand(`git stash pop stash@{${index}}`, { cwd });
191
- }
192
-
193
- /**
194
- * git stash drop stash@{index}
195
- * @param {number} index - stash 索引
196
- * @param {string} [cwd] - 工作目录
197
- */
198
- export function gitStashDrop(index: number = 0, cwd?: string): void {
199
- execCommand(`git stash drop stash@{${index}}`, { cwd });
200
- }
201
-
202
- /**
203
- * git stash list
204
- * @param {string} cwd - 工作目录
205
- * @returns {string} stash 列表输出
206
- */
207
- export function gitStashList(cwd?: string): string {
208
- try {
209
- return execCommand('git stash list', { cwd });
210
- } catch {
211
- return '';
212
- }
213
- }
214
-
215
- /**
216
- * git restore --staged .
217
- * @param {string} cwd - 工作目录
218
- */
219
- export function gitRestoreStaged(cwd?: string): void {
220
- execCommand('git restore --staged .', { cwd });
221
- }
222
-
223
- /**
224
- * 获取 git worktree list 的输出
225
- * @param {string} cwd - 工作目录
226
- * @returns {string} worktree 列表
227
- */
228
- export function gitWorktreeList(cwd?: string): string {
229
- return execCommand('git worktree list', { cwd });
230
- }
231
-
232
- /**
233
- * 执行 git worktree prune
234
- * @param {string} cwd - 工作目录
235
- */
236
- export function gitWorktreePrune(cwd?: string): void {
237
- execCommand('git worktree prune', { cwd });
238
- }
239
-
240
- /**
241
- * 检查目标分支相对于当前分支是否有本地提交
242
- * @param {string} branchName - 目标分支名
243
- * @param {string} cwd - 工作目录
244
- * @returns {boolean} 是否有本地提交
245
- */
246
- export function hasLocalCommits(branchName: string, cwd?: string): boolean {
247
- try {
248
- const output = execCommand(`git log HEAD..${branchName} --oneline`, { cwd });
249
- return output.trim() !== '';
250
- } catch {
251
- return false;
252
- }
253
- }
254
-
255
- /**
256
- * 获取目标分支相对于当前分支的新增提交数
257
- * @param {string} branchName - 目标分支名
258
- * @param {string} [cwd] - 工作目录
259
- * @returns {number} 新增提交数
260
- */
261
- export function getCommitCountAhead(branchName: string, cwd?: string): number {
262
- const output = execCommand(`git rev-list --count HEAD..${branchName}`, { cwd });
263
- return parseInt(output, 10) || 0;
264
- }
265
-
266
- /**
267
- * 获取目标分支落后于当前分支的提交数
268
- * 即当前分支有多少提交是目标分支没有的
269
- * @param {string} branchName - 目标分支名
270
- * @param {string} [cwd] - 工作目录
271
- * @returns {number} 落后的提交数
272
- */
273
- export function getCommitCountBehind(branchName: string, cwd?: string): number {
274
- try {
275
- const output = execCommand(`git rev-list --count ${branchName}..HEAD`, { cwd });
276
- return parseInt(output, 10) || 0;
277
- } catch {
278
- return 0;
279
- }
280
- }
281
-
282
- /**
283
- * 解析 git diff --shortstat 输出,提取新增行数和删除行数
284
- * @param {string} output - shortstat 输出字符串
285
- * @returns {{ insertions: number; deletions: number }} 新增和删除行数
286
- */
287
- function parseShortStat(output: string): { insertions: number; deletions: number } {
288
- let insertions = 0;
289
- let deletions = 0;
290
-
291
- const insertMatch = output.match(/(\d+)\s+insertion/);
292
- if (insertMatch) {
293
- insertions = parseInt(insertMatch[1], 10);
294
- }
295
-
296
- const deleteMatch = output.match(/(\d+)\s+deletion/);
297
- if (deleteMatch) {
298
- deletions = parseInt(deleteMatch[1], 10);
299
- }
300
-
301
- return { insertions, deletions };
302
- }
303
-
304
- /**
305
- * 获取 worktree 中工作区和暂存区的变更统计
306
- * @param {string} worktreePath - worktree 目录路径
307
- * @returns {{ insertions: number; deletions: number }} 新增和删除行数
308
- */
309
- export function getDiffStat(worktreePath: string): { insertions: number; deletions: number } {
310
- // 工作区和暂存区相对于 HEAD 的变更
311
- const output = execCommand('git diff --shortstat HEAD', { cwd: worktreePath });
312
- return parseShortStat(output);
313
- }
314
-
315
- /**
316
- * 获取暂存区相对于 HEAD 的完整 diff(含二进制文件)
317
- * 注意:返回原始输出不做 trim,保留 patch 格式完整性
318
- * @param {string} [cwd] - 工作目录
319
- * @returns {Buffer} diff 原始输出(Buffer 格式,保留二进制数据完整性)
320
- */
321
- export function gitDiffCachedBinary(cwd?: string): Buffer {
322
- logger.debug(`执行命令: git diff --cached --binary${cwd ? ` (cwd: ${cwd})` : ''}`);
323
- return execSync('git diff --cached --binary', {
324
- cwd,
325
- stdio: ['pipe', 'pipe', 'pipe'],
326
- });
327
- }
328
-
329
- /**
330
- * 将 patch 内容通过 stdin 应用到暂存区
331
- * @param {Buffer} patchContent - patch 内容(Buffer 格式)
332
- * @param {string} [cwd] - 工作目录
333
- */
334
- export function gitApplyCachedFromStdin(patchContent: Buffer, cwd?: string): void {
335
- execCommandWithInput('git', ['apply', '--cached'], { input: patchContent, cwd });
336
- }
337
-
338
- /**
339
- * 获取当前分支名
340
- * @param {string} [cwd] - 工作目录
341
- * @returns {string} 当前分支名
342
- */
343
- export function getCurrentBranch(cwd?: string): string {
344
- return execCommand('git rev-parse --abbrev-ref HEAD', { cwd });
345
- }
346
-
347
- /**
348
- * 获取当前 HEAD 的 commit hash
349
- * @param {string} [cwd] - 工作目录
350
- * @returns {string} commit hash
351
- */
352
- export function getHeadCommitHash(cwd?: string): string {
353
- return execCommand('git rev-parse HEAD', { cwd });
354
- }
355
-
356
- /**
357
- * 获取目标分支相对于当前分支的已提交变更(含二进制文件)
358
- * 使用三点 diff(HEAD...branchName)获取自分叉点以来的变更
359
- * @param {string} branchName - 目标分支名
360
- * @param {string} [cwd] - 工作目录(应在主 worktree 中执行)
361
- * @returns {Buffer} diff 原始输出
362
- */
363
- export function gitDiffBinaryAgainstBranch(branchName: string, cwd?: string): Buffer {
364
- logger.debug(`执行命令: git diff HEAD...${branchName} --binary${cwd ? ` (cwd: ${cwd})` : ''}`);
365
- return execSync(`git diff HEAD...${branchName} --binary`, {
366
- cwd,
367
- stdio: ['pipe', 'pipe', 'pipe'],
368
- });
369
- }
370
-
371
- /**
372
- * 将 patch 内容通过 stdin 应用到工作目录(不带 --cached)
373
- * @param {Buffer} patchContent - patch 内容
374
- * @param {string} [cwd] - 工作目录
375
- */
376
- export function gitApplyFromStdin(patchContent: Buffer, cwd?: string): void {
377
- execCommandWithInput('git', ['apply'], { input: patchContent, cwd });
378
- }
379
-
380
- /**
381
- * git reset --soft HEAD~<count>,撤销 commit 但保留变更在暂存区
382
- * @param {number} count - 撤销的 commit 数量
383
- * @param {string} [cwd] - 工作目录
384
- */
385
- export function gitResetSoft(count: number = 1, cwd?: string): void {
386
- execCommand(`git reset --soft HEAD~${count}`, { cwd });
387
- }
388
-
389
- /**
390
- * 获取两个分支的分叉点(merge-base)
391
- * @param {string} branchA - 分支 A
392
- * @param {string} branchB - 分支 B
393
- * @param {string} [cwd] - 工作目录
394
- * @returns {string} merge-base 的 commit hash
395
- */
396
- export function gitMergeBase(branchA: string, branchB: string, cwd?: string): string {
397
- return execCommand(`git merge-base ${branchA} ${branchB}`, { cwd });
398
- }
399
-
400
- /**
401
- * 检查目标分支相对于当前分支是否存在指定前缀的 commit message
402
- * 通过 git log HEAD..<branch> 遍历所有新增 commit 的 message
403
- * @param {string} branchName - 目标分支名
404
- * @param {string} messagePrefix - 要匹配的 commit message 前缀
405
- * @param {string} [cwd] - 工作目录
406
- * @returns {boolean} 是否存在匹配的 commit
407
- */
408
- export function hasCommitWithMessage(branchName: string, messagePrefix: string, cwd?: string): boolean {
409
- try {
410
- const output = execCommand(`git log HEAD..${branchName} --format=%s`, { cwd });
411
- if (!output.trim()) return false;
412
- return output.trim().split('\n').some((msg) => msg.startsWith(messagePrefix));
413
- } catch {
414
- return false;
415
- }
416
- }
417
-
418
- /**
419
- * git reset --soft <commitHash>,将指定 commit 之后的所有提交撤销到暂存区
420
- * @param {string} commitHash - 目标 commit hash(reset 到此处)
421
- * @param {string} [cwd] - 工作目录
422
- */
423
- export function gitResetSoftTo(commitHash: string, cwd?: string): void {
424
- execCommand(`git reset --soft ${commitHash}`, { cwd });
425
- }
426
-
427
- /**
428
- * 将当前暂存区内容写入 git tree 对象并返回其 hash
429
- * @param {string} [cwd] - 工作目录
430
- * @returns {string} tree 对象的 hash
431
- */
432
- export function gitWriteTree(cwd?: string): string {
433
- return execCommand('git write-tree', { cwd });
434
- }
435
-
436
- /**
437
- * 将指定 tree 对象的内容载入暂存区(不影响工作目录)
438
- * @param {string} treeHash - tree 对象的 hash
439
- * @param {string} [cwd] - 工作目录
440
- */
441
- export function gitReadTree(treeHash: string, cwd?: string): void {
442
- execCommand(`git read-tree ${treeHash}`, { cwd });
443
- }
444
-
445
- /**
446
- * 获取指定 commit 对应的 tree 对象 hash
447
- * @param {string} commitHash - commit hash
448
- * @param {string} [cwd] - 工作目录
449
- * @returns {string} tree 对象的 hash
450
- */
451
- export function getCommitTreeHash(commitHash: string, cwd?: string): string {
452
- return execCommand(`git rev-parse ${commitHash}^{tree}`, { cwd });
453
- }
454
-
455
- /**
456
- * 获取两个 tree 对象之间的 diff(patch 格式,含二进制)
457
- * @param {string} baseTreeHash - 基准 tree hash
458
- * @param {string} targetTreeHash - 目标 tree hash
459
- * @param {string} [cwd] - 工作目录
460
- * @returns {Buffer} diff patch 内容
461
- */
462
- export function gitDiffTree(baseTreeHash: string, targetTreeHash: string, cwd?: string): Buffer {
463
- logger.debug(`执行命令: git diff-tree -p --binary ${baseTreeHash} ${targetTreeHash}${cwd ? ` (cwd: ${cwd})` : ''}`);
464
- return execSync(`git diff-tree -p --binary ${baseTreeHash} ${targetTreeHash}`, {
465
- cwd,
466
- stdio: ['pipe', 'pipe', 'pipe'],
467
- });
468
- }
469
-
470
- /**
471
- * 检测 patch 能否无冲突地应用到暂存区(干运行,不实际修改)
472
- * @param {Buffer} patchContent - patch 内容(Buffer 格式)
473
- * @param {string} [cwd] - 工作目录
474
- * @returns {boolean} patch 能否成功应用
475
- */
476
- export function gitApplyCachedCheck(patchContent: Buffer, cwd?: string): boolean {
477
- try {
478
- execCommandWithInput('git', ['apply', '--cached', '--check'], { input: patchContent, cwd });
479
- return true;
480
- } catch {
481
- return false;
482
- }
483
- }
484
-
485
- /**
486
- * 获取分支的创建时间(通过 reflog 获取分支创建时的时间戳)
487
- * reflog 的最后一条记录即为分支创建时的记录
488
- * @param {string} branchName - 目标分支名
489
- * @param {string} [cwd] - 工作目录
490
- * @returns {string | null} ISO 8601 格式的时间字符串,无法获取时返回 null
491
- */
492
- export function getBranchCreatedAt(branchName: string, cwd?: string): string | null {
493
- try {
494
- const output = execCommand(`git reflog show ${branchName} --format=%cI`, { cwd });
495
- if (!output.trim()) return null;
496
- // 取最后一行,即分支创建时的 reflog 记录
497
- const lines = output.trim().split('\n');
498
- const lastLine = lines[lines.length - 1];
499
- return lastLine || null;
500
- } catch {
501
- return null;
502
- }
503
- }
504
-
505
- /**
506
- * 切换到指定分支
507
- * @param {string} branchName - 目标分支名
508
- * @param {string} [cwd] - 工作目录
509
- */
510
- export function gitCheckout(branchName: string, cwd?: string): void {
511
- execCommand(`git checkout ${branchName}`, { cwd });
512
- }
513
-
514
- /**
515
- * 创建本地分支(不切换)
516
- * @param {string} branchName - 新分支名
517
- * @param {string} [cwd] - 工作目录
518
- */
519
- export function createBranch(branchName: string, cwd?: string): void {
520
- execCommand(`git branch ${branchName}`, { cwd });
521
- }
1
+ export * from './git-core.js';
2
+ export * from './git-branch.js';
3
+ export * from './git-worktree.js';
@@ -54,7 +54,7 @@ export { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled
54
54
  export type { PreCheckOptions } from './validation.js';
55
55
  export { createWorktrees, getProjectWorktrees, getProjectWorktreeDir, cleanupWorktrees, getWorktreeStatus, createWorktreesByBranches } from './worktree.js';
56
56
  export { loadConfig, writeDefaultConfig, writeConfig, saveConfig, getConfigValue, ensureClawtDirs, parseConcurrency } from './config.js';
57
- export { printSuccess, printError, printWarning, printInfo, printHint, printSeparator, printDoubleSeparator, confirmAction, confirmDestructiveAction, formatWorktreeStatus, isWorktreeIdle, formatDuration, formatRelativeTime, formatDiskSize, formatLocalISOString } from './formatter.js';
57
+ export { printSuccess, printError, printWarning, printInfo, printHint, printSeparator, printDoubleSeparator, confirmAction, confirmDestructiveAction, formatWorktreeStatus, isWorktreeIdle, formatDuration, formatRelativeTime, formatDiskSize, formatLocalISOString, generateTaskFilename } from './formatter.js';
58
58
  export { ensureDir, removeEmptyDir, calculateDirSize } from './fs.js';
59
59
  export { multilineInput } from './prompt.js';
60
60
  export { launchInteractiveClaude, hasClaudeSessionHistory, launchInteractiveClaudeInNewTerminal } from './claude.js';
@@ -6,6 +6,8 @@ import {
6
6
  UNSELECTED_INDICATOR,
7
7
  PANEL_DATE_SEPARATOR_PREFIX,
8
8
  PANEL_FIXED_ROWS,
9
+ PANEL_SEPARATOR_MAX_WIDTH,
10
+ PANEL_DATE_COLOR,
9
11
  UNKNOWN_DATE_GROUP,
10
12
  VALIDATE_BRANCH_PREFIX,
11
13
  } from '../constants/index.js';
@@ -21,6 +23,10 @@ import {
21
23
  PANEL_CONFIGURED_BRANCH_DELETED,
22
24
  PANEL_CONFIGURED_BRANCH_MISMATCH,
23
25
  PANEL_NOT_INITIALIZED,
26
+ PANEL_UNKNOWN_DATE,
27
+ PANEL_SYNCED_WITH_MAIN,
28
+ PANEL_COMMITS_AHEAD,
29
+ PANEL_COMMITS_BEHIND,
24
30
  } from '../constants/messages/index.js';
25
31
  import type { StatusResult, WorktreeDetailedStatus, MainWorktreeStatus } from '../types/index.js';
26
32
  import { formatRelativeTime, groupWorktreesByDate, formatRelativeDate } from './index.js';
@@ -43,7 +49,7 @@ export interface PanelLine {
43
49
  * @returns {string} 格式化的分隔线
44
50
  */
45
51
  function buildSeparatorWithHint(cols: number, hint: string): string {
46
- const maxWidth = Math.min(cols, 60);
52
+ const maxWidth = Math.min(cols, PANEL_SEPARATOR_MAX_WIDTH);
47
53
  if (!hint) {
48
54
  return chalk.gray('─'.repeat(maxWidth));
49
55
  }
@@ -207,10 +213,10 @@ export function renderDateSeparator(dateKey: string): string {
207
213
  // 左侧空格与指示器 '▶ ' 等宽对齐
208
214
  const leftPad = ' ';
209
215
  if (dateKey === UNKNOWN_DATE_GROUP) {
210
- return `${leftPad}${chalk.gray(PANEL_DATE_SEPARATOR_PREFIX)} ${chalk.bold.hex('#FF8C00')('未知日期')} ${chalk.gray(PANEL_DATE_SEPARATOR_PREFIX)}`;
216
+ return `${leftPad}${chalk.gray(PANEL_DATE_SEPARATOR_PREFIX)} ${chalk.bold.hex(PANEL_DATE_COLOR)(PANEL_UNKNOWN_DATE)} ${chalk.gray(PANEL_DATE_SEPARATOR_PREFIX)}`;
211
217
  }
212
218
  const relativeDate = formatRelativeDate(dateKey);
213
- return `${leftPad}${chalk.gray(PANEL_DATE_SEPARATOR_PREFIX)} ${chalk.bold.hex('#FF8C00')(dateKey)}${chalk.hex('#FF8C00')(`(${relativeDate})`)} ${chalk.gray(PANEL_DATE_SEPARATOR_PREFIX)}`;
219
+ return `${leftPad}${chalk.gray(PANEL_DATE_SEPARATOR_PREFIX)} ${chalk.bold.hex(PANEL_DATE_COLOR)(dateKey)}${chalk.hex(PANEL_DATE_COLOR)(`(${relativeDate})`)} ${chalk.gray(PANEL_DATE_SEPARATOR_PREFIX)}`;
214
220
  }
215
221
 
216
222
  /**
@@ -239,14 +245,14 @@ export function renderWorktreeBlock(wt: WorktreeDetailedStatus, isSelected: bool
239
245
 
240
246
  // 本地提交数
241
247
  if (wt.commitsAhead > 0) {
242
- lines.push(`${indent}${chalk.yellow(`${wt.commitsAhead} 个本地提交`)}`);
248
+ lines.push(`${indent}${chalk.yellow(PANEL_COMMITS_AHEAD(wt.commitsAhead))}`);
243
249
  }
244
250
 
245
251
  // 与主分支的同步状态
246
252
  if (wt.commitsBehind > 0) {
247
- lines.push(`${indent}${chalk.yellow(`落后主分支 ${wt.commitsBehind} 个提交`)}`);
253
+ lines.push(`${indent}${chalk.yellow(PANEL_COMMITS_BEHIND(wt.commitsBehind))}`);
248
254
  } else {
249
- lines.push(`${indent}${chalk.green('与主分支同步')}`);
255
+ lines.push(`${indent}${chalk.green(PANEL_SYNCED_WITH_MAIN)}`);
250
256
  }
251
257
 
252
258
  // 分支创建时间