clawt 2.9.1 → 2.10.1

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 (48) hide show
  1. package/.claude/agent-memory/docs-sync-updater/MEMORY.md +14 -10
  2. package/.claude/agents/docs-sync-updater.md +11 -0
  3. package/README.md +63 -268
  4. package/dist/index.js +420 -103
  5. package/dist/postinstall.js +242 -0
  6. package/docs/spec.md +162 -14
  7. package/package.json +1 -1
  8. package/src/commands/remove.ts +21 -28
  9. package/src/commands/status.ts +327 -0
  10. package/src/constants/index.ts +1 -1
  11. package/src/constants/messages/common.ts +41 -0
  12. package/src/constants/messages/config.ts +5 -0
  13. package/src/constants/messages/create.ts +5 -0
  14. package/src/constants/messages/index.ts +29 -0
  15. package/src/constants/messages/merge.ts +42 -0
  16. package/src/constants/messages/remove.ts +15 -0
  17. package/src/constants/messages/reset.ts +7 -0
  18. package/src/constants/messages/resume.ts +12 -0
  19. package/src/constants/messages/run.ts +16 -0
  20. package/src/constants/messages/status.ts +25 -0
  21. package/src/constants/messages/sync.ts +24 -0
  22. package/src/constants/messages/validate.ts +25 -0
  23. package/src/constants/messages.ts +22 -0
  24. package/src/index.ts +2 -0
  25. package/src/types/command.ts +6 -0
  26. package/src/types/index.ts +2 -1
  27. package/src/types/status.ts +49 -0
  28. package/src/utils/git.ts +16 -0
  29. package/src/utils/index.ts +4 -3
  30. package/src/utils/validate-snapshot.ts +17 -0
  31. package/src/utils/worktree-matcher.ts +92 -0
  32. package/tests/unit/commands/config.test.ts +110 -0
  33. package/tests/unit/commands/create.test.ts +115 -0
  34. package/tests/unit/commands/list.test.ts +118 -0
  35. package/tests/unit/commands/merge.test.ts +323 -0
  36. package/tests/unit/commands/remove.test.ts +240 -0
  37. package/tests/unit/commands/reset.test.ts +124 -0
  38. package/tests/unit/commands/resume.test.ts +91 -0
  39. package/tests/unit/commands/run.test.ts +207 -0
  40. package/tests/unit/commands/status.test.ts +214 -0
  41. package/tests/unit/commands/sync.test.ts +208 -0
  42. package/tests/unit/commands/validate.test.ts +382 -0
  43. package/tests/unit/constants/messages.test.ts +1 -1
  44. package/tests/unit/utils/config.test.ts +21 -1
  45. package/tests/unit/utils/formatter.test.ts +44 -1
  46. package/tests/unit/utils/git.test.ts +44 -0
  47. package/tests/unit/utils/validate-snapshot.test.ts +25 -0
  48. package/tests/unit/utils/worktree-matcher.test.ts +81 -5
package/dist/index.js CHANGED
@@ -18,8 +18,8 @@ var VALIDATE_SNAPSHOTS_DIR = join(CLAWT_HOME, "validate-snapshots");
18
18
  // src/constants/branch.ts
19
19
  var INVALID_BRANCH_CHARS = /[\/\\.\s~:*?[\]^]+/g;
20
20
 
21
- // src/constants/messages.ts
22
- var MESSAGES = {
21
+ // src/constants/messages/common.ts
22
+ var COMMON_MESSAGES = {
23
23
  /** 不在主 worktree 根目录 */
24
24
  NOT_MAIN_WORKTREE: "\u8BF7\u5728\u4E3B worktree \u7684\u6839\u76EE\u5F55\u4E0B\u6267\u884C clawt",
25
25
  /** Git 未安装 */
@@ -28,8 +28,6 @@ var MESSAGES = {
28
28
  CLAUDE_NOT_INSTALLED: "Claude Code CLI \u672A\u5B89\u88C5\uFF0C\u8BF7\u5148\u5B89\u88C5\uFF1Anpm install -g @anthropic-ai/claude-code",
29
29
  /** 分支已存在 */
30
30
  BRANCH_EXISTS: (name) => `\u5206\u652F ${name} \u5DF2\u5B58\u5728\uFF0C\u65E0\u6CD5\u521B\u5EFA`,
31
- /** 分支已存在时提示使用 resume */
32
- BRANCH_EXISTS_USE_RESUME: (name) => `\u5206\u652F ${name} \u5DF2\u5B58\u5728\uFF0C\u8BF7\u4F7F\u7528 clawt resume -b ${name} \u6062\u590D\u4F1A\u8BDD`,
33
31
  /** 分支名清理后为空 */
34
32
  BRANCH_NAME_EMPTY: (original) => `\u5206\u652F\u540D "${original}" \u4E2D\u4E0D\u5305\u542B\u5408\u6CD5\u5B57\u7B26\uFF0C\u65E0\u6CD5\u521B\u5EFA\u5206\u652F`,
35
33
  /** 分支名被转换 */
@@ -46,9 +44,44 @@ var MESSAGES = {
46
44
  MAIN_WORKTREE_DIRTY: "\u4E3B worktree \u6709\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF0C\u8BF7\u5148\u5904\u7406",
47
45
  /** 目标 worktree 无更改 */
48
46
  TARGET_WORKTREE_CLEAN: "\u8BE5 worktree \u7684\u5206\u652F\u4E0A\u6CA1\u6709\u4EFB\u4F55\u66F4\u6539\uFF0C\u65E0\u9700\u9A8C\u8BC1",
49
- /** validate 成功 */
50
- VALIDATE_SUCCESS: (branch) => `\u2713 \u5DF2\u5C06\u5206\u652F ${branch} \u7684\u53D8\u66F4\u5E94\u7528\u5230\u4E3B worktree
51
- \u53EF\u4EE5\u5F00\u59CB\u9A8C\u8BC1\u4E86`,
47
+ /** 用户取消破坏性操作 */
48
+ DESTRUCTIVE_OP_CANCELLED: "\u5DF2\u53D6\u6D88\u64CD\u4F5C",
49
+ /** 请提供提交信息 */
50
+ COMMIT_MESSAGE_REQUIRED: "\u8BF7\u63D0\u4F9B\u63D0\u4EA4\u4FE1\u606F\uFF08-m \u53C2\u6570\uFF09",
51
+ /** 配置文件损坏,已重新生成默认配置 */
52
+ CONFIG_CORRUPTED: "\u914D\u7F6E\u6587\u4EF6\u635F\u574F\u6216\u65E0\u6CD5\u89E3\u6790\uFF0C\u5DF2\u91CD\u65B0\u751F\u6210\u9ED8\u8BA4\u914D\u7F6E",
53
+ /** worktree 状态获取失败 */
54
+ WORKTREE_STATUS_UNAVAILABLE: "(\u72B6\u6001\u4E0D\u53EF\u7528)",
55
+ /** 分隔线 */
56
+ SEPARATOR: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
57
+ /** 粗分隔线 */
58
+ DOUBLE_SEPARATOR: "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"
59
+ };
60
+
61
+ // src/constants/messages/run.ts
62
+ var RUN_MESSAGES = {
63
+ /** 分支已存在时提示使用 resume */
64
+ BRANCH_EXISTS_USE_RESUME: (name) => `\u5206\u652F ${name} \u5DF2\u5B58\u5728\uFF0C\u8BF7\u4F7F\u7528 clawt resume -b ${name} \u6062\u590D\u4F1A\u8BDD`,
65
+ /** 检测到用户中断 */
66
+ INTERRUPTED: "\u68C0\u6D4B\u5230\u9000\u51FA\u6307\u4EE4\uFF0C\u5DF2\u505C\u6B62 Claude Code \u4EFB\u52A1",
67
+ /** 中断后自动清理完成 */
68
+ INTERRUPT_AUTO_CLEANED: (count) => `\u2713 \u5DF2\u81EA\u52A8\u6E05\u7406 ${count} \u4E2A worktree \u548C\u5BF9\u5E94\u5206\u652F`,
69
+ /** 中断后手动确认清理 */
70
+ INTERRUPT_CONFIRM_CLEANUP: "\u662F\u5426\u79FB\u9664\u521A\u521A\u521B\u5EFA\u7684 worktree \u548C\u5BF9\u5E94\u5206\u652F\uFF1F",
71
+ /** 中断后清理完成 */
72
+ INTERRUPT_CLEANED: (count) => `\u2713 \u5DF2\u6E05\u7406 ${count} \u4E2A worktree \u548C\u5BF9\u5E94\u5206\u652F`,
73
+ /** 中断后保留 worktree */
74
+ INTERRUPT_KEPT: "\u5DF2\u4FDD\u7559 worktree\uFF0C\u53EF\u7A0D\u540E\u4F7F\u7528 clawt remove \u624B\u52A8\u6E05\u7406"
75
+ };
76
+
77
+ // src/constants/messages/create.ts
78
+ var CREATE_MESSAGES = {
79
+ /** 创建数量参数无效 */
80
+ INVALID_COUNT: (value) => `\u65E0\u6548\u7684\u521B\u5EFA\u6570\u91CF: "${value}"\uFF0C\u8BF7\u8F93\u5165\u6B63\u6574\u6570`
81
+ };
82
+
83
+ // src/constants/messages/merge.ts
84
+ var MERGE_MESSAGES = {
52
85
  /** merge 成功 */
53
86
  MERGE_SUCCESS: (branch, message, pushed) => `\u2713 \u5206\u652F ${branch} \u5DF2\u6210\u529F\u5408\u5E76\u5230\u5F53\u524D\u5206\u652F
54
87
  \u63D0\u4EA4\u4FE1\u606F: ${message}${pushed ? "\n \u5DF2\u63A8\u9001\u5230\u8FDC\u7A0B\u4ED3\u5E93" : ""}`,
@@ -58,57 +91,12 @@ var MESSAGES = {
58
91
  MERGE_CONFLICT: "\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u5904\u7406\uFF1A\n \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue",
59
92
  /** merge 后清理 worktree 和分支成功 */
60
93
  WORKTREE_CLEANED: (branch) => `\u2713 \u5DF2\u6E05\u7406 worktree \u548C\u5206\u652F: ${branch}`,
61
- /** 请提供提交信息 */
62
- COMMIT_MESSAGE_REQUIRED: "\u8BF7\u63D0\u4F9B\u63D0\u4EA4\u4FE1\u606F\uFF08-m \u53C2\u6570\uFF09",
63
94
  /** 目标 worktree 有未提交修改但未指定 -m */
64
95
  TARGET_WORKTREE_DIRTY_NO_MESSAGE: "\u76EE\u6807 worktree \u6709\u672A\u63D0\u4EA4\u7684\u4FEE\u6539\uFF0C\u8BF7\u901A\u8FC7 -m \u53C2\u6570\u63D0\u4F9B\u63D0\u4EA4\u4FE1\u606F",
65
96
  /** 目标 worktree 既干净又无本地提交 */
66
97
  TARGET_WORKTREE_NO_CHANGES: "\u76EE\u6807 worktree \u6CA1\u6709\u4EFB\u4F55\u53EF\u5408\u5E76\u7684\u53D8\u66F4\uFF08\u5DE5\u4F5C\u533A\u5E72\u51C0\u4E14\u65E0\u672C\u5730\u63D0\u4EA4\uFF09",
67
- /** 检测到用户中断 */
68
- INTERRUPTED: "\u68C0\u6D4B\u5230\u9000\u51FA\u6307\u4EE4\uFF0C\u5DF2\u505C\u6B62 Claude Code \u4EFB\u52A1",
69
- /** 中断后自动清理完成 */
70
- INTERRUPT_AUTO_CLEANED: (count) => `\u2713 \u5DF2\u81EA\u52A8\u6E05\u7406 ${count} \u4E2A worktree \u548C\u5BF9\u5E94\u5206\u652F`,
71
- /** 中断后手动确认清理 */
72
- INTERRUPT_CONFIRM_CLEANUP: "\u662F\u5426\u79FB\u9664\u521A\u521A\u521B\u5EFA\u7684 worktree \u548C\u5BF9\u5E94\u5206\u652F\uFF1F",
73
- /** 中断后清理完成 */
74
- INTERRUPT_CLEANED: (count) => `\u2713 \u5DF2\u6E05\u7406 ${count} \u4E2A worktree \u548C\u5BF9\u5E94\u5206\u652F`,
75
- /** 中断后保留 worktree */
76
- INTERRUPT_KEPT: "\u5DF2\u4FDD\u7559 worktree\uFF0C\u53EF\u7A0D\u540E\u4F7F\u7528 clawt remove \u624B\u52A8\u6E05\u7406",
77
- /** 配置文件损坏,已重新生成默认配置 */
78
- CONFIG_CORRUPTED: "\u914D\u7F6E\u6587\u4EF6\u635F\u574F\u6216\u65E0\u6CD5\u89E3\u6790\uFF0C\u5DF2\u91CD\u65B0\u751F\u6210\u9ED8\u8BA4\u914D\u7F6E",
79
- /** 配置已恢复为默认值 */
80
- CONFIG_RESET_SUCCESS: "\u2713 \u914D\u7F6E\u5DF2\u6062\u590D\u4E3A\u9ED8\u8BA4\u503C",
81
- /** 分隔线 */
82
- SEPARATOR: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
83
- /** 粗分隔线 */
84
- DOUBLE_SEPARATOR: "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
85
- /** 创建数量参数无效 */
86
- INVALID_COUNT: (value) => `\u65E0\u6548\u7684\u521B\u5EFA\u6570\u91CF: "${value}"\uFF0C\u8BF7\u8F93\u5165\u6B63\u6574\u6570`,
87
- /** worktree 状态获取失败 */
88
- WORKTREE_STATUS_UNAVAILABLE: "(\u72B6\u6001\u4E0D\u53EF\u7528)",
89
- /** 增量 validate 成功提示 */
90
- INCREMENTAL_VALIDATE_SUCCESS: (branch) => `\u2713 \u5DF2\u5C06\u5206\u652F ${branch} \u7684\u6700\u65B0\u53D8\u66F4\u5E94\u7528\u5230\u4E3B worktree\uFF08\u589E\u91CF\u6A21\u5F0F\uFF09
91
- \u6682\u5B58\u533A = \u4E0A\u6B21\u5FEB\u7167\uFF0C\u5DE5\u4F5C\u76EE\u5F55 = \u6700\u65B0\u53D8\u66F4`,
92
- /** 增量 validate 降级为全量模式提示 */
93
- INCREMENTAL_VALIDATE_FALLBACK: "\u589E\u91CF\u5BF9\u6BD4\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u4E3A\u5168\u91CF\u6A21\u5F0F",
94
- /** validate 状态已清理 */
95
- VALIDATE_CLEANED: (branch) => `\u2713 \u5206\u652F ${branch} \u7684 validate \u72B6\u6001\u5DF2\u6E05\u7406`,
96
98
  /** merge 命令检测到 validate 状态的提示 */
97
99
  MERGE_VALIDATE_STATE_HINT: (branch) => `\u4E3B worktree \u53EF\u80FD\u5B58\u5728 validate \u6B8B\u7559\u72B6\u6001\uFF0C\u53EF\u5148\u6267\u884C clawt validate -b ${branch} --clean \u6E05\u7406`,
98
- /** sync 自动保存未提交变更 */
99
- SYNC_AUTO_COMMITTED: (branch) => `\u5DF2\u81EA\u52A8\u4FDD\u5B58 ${branch} \u5206\u652F\u7684\u672A\u63D0\u4EA4\u53D8\u66F4`,
100
- /** sync 开始合并 */
101
- SYNC_MERGING: (targetBranch, mainBranch) => `\u6B63\u5728\u5C06 ${mainBranch} \u5408\u5E76\u5230 ${targetBranch} ...`,
102
- /** sync 成功 */
103
- SYNC_SUCCESS: (targetBranch, mainBranch) => `\u2713 \u5DF2\u5C06 ${mainBranch} \u7684\u6700\u65B0\u4EE3\u7801\u540C\u6B65\u5230 ${targetBranch}`,
104
- /** sync 冲突 */
105
- SYNC_CONFLICT: (worktreePath) => `\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u8FDB\u5165\u76EE\u6807 worktree \u624B\u52A8\u89E3\u51B3\uFF1A
106
- cd ${worktreePath}
107
- \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue
108
- clawt validate -b <branch> \u9A8C\u8BC1\u53D8\u66F4`,
109
- /** validate patch apply 失败,提示用户同步主分支 */
110
- VALIDATE_PATCH_APPLY_FAILED: (branch) => `\u53D8\u66F4\u8FC1\u79FB\u5931\u8D25\uFF1A\u76EE\u6807\u5206\u652F\u4E0E\u4E3B\u5206\u652F\u5DEE\u5F02\u8FC7\u5927
111
- \u8BF7\u5148\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`,
112
100
  /** merge 检测到 auto-save 提交,提示用户是否压缩 */
113
101
  MERGE_SQUASH_PROMPT: "\u68C0\u6D4B\u5230 sync \u4EA7\u751F\u7684\u4E34\u65F6\u63D0\u4EA4\uFF0C\u662F\u5426\u5C06\u6240\u6709\u63D0\u4EA4\u538B\u7F29\u4E3A\u4E00\u4E2A\uFF1F\n \u538B\u7F29\u540E\u53D8\u66F4\u5C06\u4FDD\u7559\u5728\u76EE\u6807worktree\u7684\u6682\u5B58\u533A\uFF0C\u9700\u8981\u91CD\u65B0\u63D0\u4EA4\uFF08\u53EF\u4F7F\u7528 Claude Code Cli\u6216\u5176\u4ED6\u5DE5\u5177\u751F\u6210\u63D0\u4EA4\u4FE1\u606F\uFF09",
114
102
  /** squash 完成且通过 -m 直接提交后的提示 */
@@ -118,25 +106,37 @@ var MESSAGES = {
118
106
  \u8BF7\u5728\u76EE\u6807 worktree \u4E2D\u63D0\u4EA4\u540E\u91CD\u65B0\u6267\u884C merge\uFF1A
119
107
  cd ${worktreePath}
120
108
  \u63D0\u4EA4\u5B8C\u6210\u540E\u6267\u884C\uFF1Aclawt merge -b ${branch}`,
121
- /** 用户取消破坏性操作 */
122
- DESTRUCTIVE_OP_CANCELLED: "\u5DF2\u53D6\u6D88\u64CD\u4F5C",
123
- /** reset 成功 */
124
- RESET_SUCCESS: "\u2713 \u4E3B worktree \u5DE5\u4F5C\u533A\u548C\u6682\u5B58\u533A\u5DF2\u91CD\u7F6E",
125
- /** reset 时工作区和暂存区已干净 */
126
- RESET_ALREADY_CLEAN: "\u4E3B worktree \u5DE5\u4F5C\u533A\u548C\u6682\u5B58\u533A\u5DF2\u662F\u5E72\u51C0\u72B6\u6001\uFF0C\u65E0\u9700\u91CD\u7F6E",
127
- /** 批量移除部分失败 */
128
- REMOVE_PARTIAL_FAILURE: (failures) => `\u4EE5\u4E0B worktree \u79FB\u9664\u5931\u8D25\uFF1A
129
- ${failures.map((f) => ` \u2717 ${f.path}: ${f.error}`).join("\n")}`,
130
- /** resume 无可用 worktree */
131
- RESUME_NO_WORKTREES: "\u5F53\u524D\u9879\u76EE\u6CA1\u6709\u53EF\u7528\u7684 worktree\uFF0C\u8BF7\u5148\u901A\u8FC7 clawt run \u6216 clawt create \u521B\u5EFA",
132
- /** resume 模糊匹配无结果,列出可用分支 */
133
- RESUME_NO_MATCH: (name, branches) => `\u672A\u627E\u5230\u4E0E "${name}" \u5339\u914D\u7684\u5206\u652F
109
+ /** merge 后 pull 冲突 */
110
+ PULL_CONFLICT: "\u81EA\u52A8 pull \u65F6\u53D1\u751F\u51B2\u7A81\uFF0Cmerge \u5DF2\u5B8C\u6210\u4F46\u8FDC\u7A0B\u540C\u6B65\u5931\u8D25\n \u8BF7\u624B\u52A8\u89E3\u51B3\u51B2\u7A81\uFF1A\n \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git commit\n \u7136\u540E\u6267\u884C git push \u63A8\u9001\u5230\u8FDC\u7A0B",
111
+ /** push 失败 */
112
+ PUSH_FAILED: "\u81EA\u52A8 push \u5931\u8D25\uFF0Cmerge \u548C pull \u5DF2\u5B8C\u6210\n \u8BF7\u624B\u52A8\u6267\u884C git push",
113
+ /** merge 无可用 worktree */
114
+ MERGE_NO_WORKTREES: "\u5F53\u524D\u9879\u76EE\u6CA1\u6709\u53EF\u7528\u7684 worktree\uFF0C\u8BF7\u5148\u901A\u8FC7 clawt run \u6216 clawt create \u521B\u5EFA",
115
+ /** merge 模糊匹配无结果,列出可用分支 */
116
+ MERGE_NO_MATCH: (name, branches) => `\u672A\u627E\u5230\u4E0E "${name}" \u5339\u914D\u7684\u5206\u652F
134
117
  \u53EF\u7528\u5206\u652F\uFF1A
135
118
  ${branches.map((b) => ` - ${b}`).join("\n")}`,
136
- /** resume 交互选择提示 */
137
- RESUME_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u6062\u590D\u7684\u5206\u652F",
138
- /** resume 模糊匹配到多个结果提示 */
139
- RESUME_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
119
+ /** merge 交互选择提示 */
120
+ MERGE_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u5408\u5E76\u7684\u5206\u652F",
121
+ /** merge 模糊匹配到多个结果提示 */
122
+ MERGE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`
123
+ };
124
+
125
+ // src/constants/messages/validate.ts
126
+ var VALIDATE_MESSAGES = {
127
+ /** validate 成功 */
128
+ VALIDATE_SUCCESS: (branch) => `\u2713 \u5DF2\u5C06\u5206\u652F ${branch} \u7684\u53D8\u66F4\u5E94\u7528\u5230\u4E3B worktree
129
+ \u53EF\u4EE5\u5F00\u59CB\u9A8C\u8BC1\u4E86`,
130
+ /** 增量 validate 成功提示 */
131
+ INCREMENTAL_VALIDATE_SUCCESS: (branch) => `\u2713 \u5DF2\u5C06\u5206\u652F ${branch} \u7684\u6700\u65B0\u53D8\u66F4\u5E94\u7528\u5230\u4E3B worktree\uFF08\u589E\u91CF\u6A21\u5F0F\uFF09
132
+ \u6682\u5B58\u533A = \u4E0A\u6B21\u5FEB\u7167\uFF0C\u5DE5\u4F5C\u76EE\u5F55 = \u6700\u65B0\u53D8\u66F4`,
133
+ /** 增量 validate 降级为全量模式提示 */
134
+ INCREMENTAL_VALIDATE_FALLBACK: "\u589E\u91CF\u5BF9\u6BD4\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u4E3A\u5168\u91CF\u6A21\u5F0F",
135
+ /** validate 状态已清理 */
136
+ VALIDATE_CLEANED: (branch) => `\u2713 \u5206\u652F ${branch} \u7684 validate \u72B6\u6001\u5DF2\u6E05\u7406`,
137
+ /** validate patch apply 失败,提示用户同步主分支 */
138
+ VALIDATE_PATCH_APPLY_FAILED: (branch) => `\u53D8\u66F4\u8FC1\u79FB\u5931\u8D25\uFF1A\u76EE\u6807\u5206\u652F\u4E0E\u4E3B\u5206\u652F\u5DEE\u5F02\u8FC7\u5927
139
+ \u8BF7\u5148\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`,
140
140
  /** validate 无可用 worktree */
141
141
  VALIDATE_NO_WORKTREES: "\u5F53\u524D\u9879\u76EE\u6CA1\u6709\u53EF\u7528\u7684 worktree\uFF0C\u8BF7\u5148\u901A\u8FC7 clawt run \u6216 clawt create \u521B\u5EFA",
142
142
  /** validate 模糊匹配无结果,列出可用分支 */
@@ -146,17 +146,22 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
146
146
  /** validate 交互选择提示 */
147
147
  VALIDATE_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u9A8C\u8BC1\u7684\u5206\u652F",
148
148
  /** validate 模糊匹配到多个结果提示 */
149
- VALIDATE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
150
- /** merge 无可用 worktree */
151
- MERGE_NO_WORKTREES: "\u5F53\u524D\u9879\u76EE\u6CA1\u6709\u53EF\u7528\u7684 worktree\uFF0C\u8BF7\u5148\u901A\u8FC7 clawt run \u6216 clawt create \u521B\u5EFA",
152
- /** merge 模糊匹配无结果,列出可用分支 */
153
- MERGE_NO_MATCH: (name, branches) => `\u672A\u627E\u5230\u4E0E "${name}" \u5339\u914D\u7684\u5206\u652F
154
- \u53EF\u7528\u5206\u652F\uFF1A
155
- ${branches.map((b) => ` - ${b}`).join("\n")}`,
156
- /** merge 交互选择提示 */
157
- MERGE_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u5408\u5E76\u7684\u5206\u652F",
158
- /** merge 模糊匹配到多个结果提示 */
159
- MERGE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
149
+ VALIDATE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`
150
+ };
151
+
152
+ // src/constants/messages/sync.ts
153
+ var SYNC_MESSAGES = {
154
+ /** sync 自动保存未提交变更 */
155
+ SYNC_AUTO_COMMITTED: (branch) => `\u5DF2\u81EA\u52A8\u4FDD\u5B58 ${branch} \u5206\u652F\u7684\u672A\u63D0\u4EA4\u53D8\u66F4`,
156
+ /** sync 开始合并 */
157
+ SYNC_MERGING: (targetBranch, mainBranch) => `\u6B63\u5728\u5C06 ${mainBranch} \u5408\u5E76\u5230 ${targetBranch} ...`,
158
+ /** sync 成功 */
159
+ SYNC_SUCCESS: (targetBranch, mainBranch) => `\u2713 \u5DF2\u5C06 ${mainBranch} \u7684\u6700\u65B0\u4EE3\u7801\u540C\u6B65\u5230 ${targetBranch}`,
160
+ /** sync 冲突 */
161
+ SYNC_CONFLICT: (worktreePath) => `\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u8FDB\u5165\u76EE\u6807 worktree \u624B\u52A8\u89E3\u51B3\uFF1A
162
+ cd ${worktreePath}
163
+ \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue
164
+ clawt validate -b <branch> \u9A8C\u8BC1\u53D8\u66F4`,
160
165
  /** sync 无可用 worktree */
161
166
  SYNC_NO_WORKTREES: "\u5F53\u524D\u9879\u76EE\u6CA1\u6709\u53EF\u7528\u7684 worktree\uFF0C\u8BF7\u5148\u901A\u8FC7 clawt run \u6216 clawt create \u521B\u5EFA",
162
167
  /** sync 模糊匹配无结果,列出可用分支 */
@@ -166,11 +171,93 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
166
171
  /** sync 交互选择提示 */
167
172
  SYNC_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u540C\u6B65\u7684\u5206\u652F",
168
173
  /** sync 模糊匹配到多个结果提示 */
169
- SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
170
- /** merge 后 pull 冲突 */
171
- PULL_CONFLICT: "\u81EA\u52A8 pull \u65F6\u53D1\u751F\u51B2\u7A81\uFF0Cmerge \u5DF2\u5B8C\u6210\u4F46\u8FDC\u7A0B\u540C\u6B65\u5931\u8D25\n \u8BF7\u624B\u52A8\u89E3\u51B3\u51B2\u7A81\uFF1A\n \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git commit\n \u7136\u540E\u6267\u884C git push \u63A8\u9001\u5230\u8FDC\u7A0B",
172
- /** push 失败 */
173
- PUSH_FAILED: "\u81EA\u52A8 push \u5931\u8D25\uFF0Cmerge \u548C pull \u5DF2\u5B8C\u6210\n \u8BF7\u624B\u52A8\u6267\u884C git push"
174
+ SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`
175
+ };
176
+
177
+ // src/constants/messages/resume.ts
178
+ var RESUME_MESSAGES = {
179
+ /** resume 无可用 worktree */
180
+ RESUME_NO_WORKTREES: "\u5F53\u524D\u9879\u76EE\u6CA1\u6709\u53EF\u7528\u7684 worktree\uFF0C\u8BF7\u5148\u901A\u8FC7 clawt run \u6216 clawt create \u521B\u5EFA",
181
+ /** resume 模糊匹配无结果,列出可用分支 */
182
+ RESUME_NO_MATCH: (name, branches) => `\u672A\u627E\u5230\u4E0E "${name}" \u5339\u914D\u7684\u5206\u652F
183
+ \u53EF\u7528\u5206\u652F\uFF1A
184
+ ${branches.map((b) => ` - ${b}`).join("\n")}`,
185
+ /** resume 交互选择提示 */
186
+ RESUME_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u6062\u590D\u7684\u5206\u652F",
187
+ /** resume 模糊匹配到多个结果提示 */
188
+ RESUME_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`
189
+ };
190
+
191
+ // src/constants/messages/remove.ts
192
+ var REMOVE_MESSAGES = {
193
+ /** remove 无可用 worktree */
194
+ REMOVE_NO_WORKTREES: "\u5F53\u524D\u9879\u76EE\u6CA1\u6709\u53EF\u7528\u7684 worktree\uFF0C\u65E0\u9700\u79FB\u9664",
195
+ /** remove 多选交互提示 */
196
+ REMOVE_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u79FB\u9664\u7684\u5206\u652F\uFF08\u7A7A\u683C\u9009\u62E9\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09",
197
+ /** remove 模糊匹配到多个结果提示 */
198
+ REMOVE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\u8981\u79FB\u9664\u7684\uFF08\u7A7A\u683C\u9009\u62E9\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09\uFF1A`,
199
+ /** remove 模糊匹配无结果,列出可用分支 */
200
+ REMOVE_NO_MATCH: (name, branches) => `\u672A\u627E\u5230\u4E0E "${name}" \u5339\u914D\u7684\u5206\u652F
201
+ \u53EF\u7528\u5206\u652F\uFF1A
202
+ ${branches.map((b) => ` - ${b}`).join("\n")}`,
203
+ /** 批量移除部分失败 */
204
+ REMOVE_PARTIAL_FAILURE: (failures) => `\u4EE5\u4E0B worktree \u79FB\u9664\u5931\u8D25\uFF1A
205
+ ${failures.map((f) => ` \u2717 ${f.path}: ${f.error}`).join("\n")}`
206
+ };
207
+
208
+ // src/constants/messages/reset.ts
209
+ var RESET_MESSAGES = {
210
+ /** reset 成功 */
211
+ RESET_SUCCESS: "\u2713 \u4E3B worktree \u5DE5\u4F5C\u533A\u548C\u6682\u5B58\u533A\u5DF2\u91CD\u7F6E",
212
+ /** reset 时工作区和暂存区已干净 */
213
+ RESET_ALREADY_CLEAN: "\u4E3B worktree \u5DE5\u4F5C\u533A\u548C\u6682\u5B58\u533A\u5DF2\u662F\u5E72\u51C0\u72B6\u6001\uFF0C\u65E0\u9700\u91CD\u7F6E"
214
+ };
215
+
216
+ // src/constants/messages/config.ts
217
+ var CONFIG_CMD_MESSAGES = {
218
+ /** 配置已恢复为默认值 */
219
+ CONFIG_RESET_SUCCESS: "\u2713 \u914D\u7F6E\u5DF2\u6062\u590D\u4E3A\u9ED8\u8BA4\u503C"
220
+ };
221
+
222
+ // src/constants/messages/status.ts
223
+ var STATUS_MESSAGES = {
224
+ /** status 命令标题 */
225
+ STATUS_TITLE: (projectName) => `\u9879\u76EE\u72B6\u6001\u603B\u89C8: ${projectName}`,
226
+ /** status 主 worktree 区块标题 */
227
+ STATUS_MAIN_SECTION: "\u4E3B Worktree",
228
+ /** status worktrees 区块标题 */
229
+ STATUS_WORKTREES_SECTION: "Worktree \u5217\u8868",
230
+ /** status 快照区块标题 */
231
+ STATUS_SNAPSHOTS_SECTION: "\u672A\u6E05\u7406\u7684 Validate \u5FEB\u7167",
232
+ /** status 无 worktree */
233
+ STATUS_NO_WORKTREES: "(\u65E0\u6D3B\u8DC3 worktree)",
234
+ /** status 无未清理快照 */
235
+ STATUS_NO_SNAPSHOTS: "(\u65E0\u672A\u6E05\u7406\u7684\u5FEB\u7167)",
236
+ /** status 变更状态:已提交 */
237
+ STATUS_CHANGE_COMMITTED: "\u5DF2\u63D0\u4EA4",
238
+ /** status 变更状态:未提交修改 */
239
+ STATUS_CHANGE_UNCOMMITTED: "\u672A\u63D0\u4EA4\u4FEE\u6539",
240
+ /** status 变更状态:合并冲突 */
241
+ STATUS_CHANGE_CONFLICT: "\u5408\u5E76\u51B2\u7A81",
242
+ /** status 变更状态:无变更 */
243
+ STATUS_CHANGE_CLEAN: "\u65E0\u53D8\u66F4",
244
+ /** status 快照对应 worktree 已不存在 */
245
+ STATUS_SNAPSHOT_ORPHANED: "(\u5BF9\u5E94 worktree \u5DF2\u4E0D\u5B58\u5728)"
246
+ };
247
+
248
+ // src/constants/messages/index.ts
249
+ var MESSAGES = {
250
+ ...COMMON_MESSAGES,
251
+ ...RUN_MESSAGES,
252
+ ...CREATE_MESSAGES,
253
+ ...MERGE_MESSAGES,
254
+ ...VALIDATE_MESSAGES,
255
+ ...SYNC_MESSAGES,
256
+ ...RESUME_MESSAGES,
257
+ ...REMOVE_MESSAGES,
258
+ ...RESET_MESSAGES,
259
+ ...CONFIG_CMD_MESSAGES,
260
+ ...STATUS_MESSAGES
174
261
  };
175
262
 
176
263
  // src/constants/exitCodes.ts
@@ -420,6 +507,14 @@ function getCommitCountAhead(branchName, cwd) {
420
507
  const output = execCommand(`git rev-list --count HEAD..${branchName}`, { cwd });
421
508
  return parseInt(output, 10) || 0;
422
509
  }
510
+ function getCommitCountBehind(branchName, cwd) {
511
+ try {
512
+ const output = execCommand(`git rev-list --count ${branchName}..HEAD`, { cwd });
513
+ return parseInt(output, 10) || 0;
514
+ } catch {
515
+ return 0;
516
+ }
517
+ }
423
518
  function parseShortStat(output) {
424
519
  let insertions = 0;
425
520
  let deletions = 0;
@@ -798,6 +893,14 @@ function removeSnapshot(projectName, branchName) {
798
893
  logger.info(`\u5DF2\u5220\u9664 validate \u5FEB\u7167: ${headPath}`);
799
894
  }
800
895
  }
896
+ function getProjectSnapshotBranches(projectName) {
897
+ const projectDir = join3(VALIDATE_SNAPSHOTS_DIR, projectName);
898
+ if (!existsSync5(projectDir)) {
899
+ return [];
900
+ }
901
+ const files = readdirSync3(projectDir);
902
+ return files.filter((f) => f.endsWith(".tree")).map((f) => f.replace(/\.tree$/, ""));
903
+ }
801
904
  function removeProjectSnapshots(projectName) {
802
905
  const projectDir = join3(VALIDATE_SNAPSHOTS_DIR, projectName);
803
906
  if (!existsSync5(projectDir)) {
@@ -833,6 +936,44 @@ async function promptSelectBranch(worktrees, message) {
833
936
  }).run();
834
937
  return worktrees.find((wt) => wt.branch === selectedBranch);
835
938
  }
939
+ async function promptMultiSelectBranches(worktrees, message) {
940
+ const selectedBranches = await new Enquirer2.MultiSelect({
941
+ message,
942
+ choices: worktrees.map((wt) => ({
943
+ name: wt.branch,
944
+ message: wt.branch
945
+ })),
946
+ // 使用空心圆/实心圆作为选中指示符
947
+ symbols: {
948
+ indicator: { on: "\u25CF", off: "\u25CB" }
949
+ }
950
+ }).run();
951
+ return worktrees.filter((wt) => selectedBranches.includes(wt.branch));
952
+ }
953
+ async function resolveTargetWorktrees(worktrees, messages, branchName) {
954
+ if (worktrees.length === 0) {
955
+ throw new ClawtError(messages.noWorktrees);
956
+ }
957
+ if (!branchName) {
958
+ if (worktrees.length === 1) {
959
+ return [worktrees[0]];
960
+ }
961
+ return promptMultiSelectBranches(worktrees, messages.selectBranch);
962
+ }
963
+ const exactMatch = findExactMatch(worktrees, branchName);
964
+ if (exactMatch) {
965
+ return [exactMatch];
966
+ }
967
+ const fuzzyMatches = findFuzzyMatches(worktrees, branchName);
968
+ if (fuzzyMatches.length === 1) {
969
+ return [fuzzyMatches[0]];
970
+ }
971
+ if (fuzzyMatches.length > 1) {
972
+ return promptMultiSelectBranches(fuzzyMatches, messages.multipleMatches(branchName));
973
+ }
974
+ const allBranches = worktrees.map((wt) => wt.branch);
975
+ throw new ClawtError(messages.noMatch(branchName, allBranches));
976
+ }
836
977
  async function resolveTargetWorktree(worktrees, messages, branchName) {
837
978
  if (worktrees.length === 0) {
838
979
  throw new ClawtError(messages.noWorktrees);
@@ -937,32 +1078,28 @@ function handleCreate(options) {
937
1078
  }
938
1079
 
939
1080
  // src/commands/remove.ts
1081
+ var REMOVE_RESOLVE_MESSAGES = {
1082
+ noWorktrees: MESSAGES.REMOVE_NO_WORKTREES,
1083
+ selectBranch: MESSAGES.REMOVE_SELECT_BRANCH,
1084
+ multipleMatches: MESSAGES.REMOVE_MULTIPLE_MATCHES,
1085
+ noMatch: MESSAGES.REMOVE_NO_MATCH
1086
+ };
940
1087
  function registerRemoveCommand(program2) {
941
- program2.command("remove").description("\u79FB\u9664 worktree\uFF08\u652F\u6301\u5355\u4E2A/\u6279\u91CF/\u5168\u90E8\uFF09").option("--all", "\u79FB\u9664\u5F53\u524D\u9879\u76EE\u4E0B\u6240\u6709 worktree").option("-b, --branch <branchName>", "\u6307\u5B9A\u5206\u652F\u540D\uFF08\u5B8C\u6574\u5206\u652F\u540D\u7CBE\u786E\u5339\u914D\uFF09").action(async (options) => {
1088
+ program2.command("remove").description("\u79FB\u9664 worktree\uFF08\u652F\u6301\u5355\u4E2A/\u6279\u91CF/\u5168\u90E8\uFF09").option("--all", "\u79FB\u9664\u5F53\u524D\u9879\u76EE\u4E0B\u6240\u6709 worktree").option("-b, --branch <branchName>", "\u6307\u5B9A\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").action(async (options) => {
942
1089
  await handleRemove(options);
943
1090
  });
944
1091
  }
945
- function resolveWorktreesToRemove(options) {
946
- const allWorktrees = getProjectWorktrees();
947
- if (options.all) {
948
- return allWorktrees;
949
- }
950
- if (!options.branch) {
951
- throw new ClawtError("\u8BF7\u6307\u5B9A --all \u6216 -b <branchName> \u53C2\u6570");
952
- }
953
- const matched = allWorktrees.filter(
954
- (wt) => wt.branch === options.branch || wt.branch.startsWith(`${options.branch}-`)
955
- );
956
- if (matched.length === 0) {
957
- throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch));
958
- }
959
- return matched;
960
- }
961
1092
  async function handleRemove(options) {
962
1093
  validateMainWorktree();
963
1094
  const projectName = getProjectName();
964
1095
  logger.info(`remove \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}`);
965
- const worktreesToRemove = resolveWorktreesToRemove(options);
1096
+ const allWorktrees = getProjectWorktrees();
1097
+ let worktreesToRemove;
1098
+ if (options.all) {
1099
+ worktreesToRemove = allWorktrees;
1100
+ } else {
1101
+ worktreesToRemove = await resolveTargetWorktrees(allWorktrees, REMOVE_RESOLVE_MESSAGES, options.branch);
1102
+ }
966
1103
  if (worktreesToRemove.length === 0) {
967
1104
  printInfo(MESSAGES.NO_WORKTREES);
968
1105
  return;
@@ -1632,6 +1769,185 @@ async function handleReset() {
1632
1769
  }
1633
1770
  }
1634
1771
 
1772
+ // src/commands/status.ts
1773
+ import chalk5 from "chalk";
1774
+ function registerStatusCommand(program2) {
1775
+ program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
1776
+ handleStatus(options);
1777
+ });
1778
+ }
1779
+ function handleStatus(options) {
1780
+ validateMainWorktree();
1781
+ const statusResult = collectStatus();
1782
+ logger.info(`status \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${statusResult.main.projectName}\uFF0C\u5171 ${statusResult.totalWorktrees} \u4E2A worktree`);
1783
+ if (options.json) {
1784
+ printStatusAsJson(statusResult);
1785
+ return;
1786
+ }
1787
+ printStatusAsText(statusResult);
1788
+ }
1789
+ function collectStatus() {
1790
+ const projectName = getProjectName();
1791
+ const currentBranch = getCurrentBranch();
1792
+ const isClean = isWorkingDirClean();
1793
+ const main = {
1794
+ branch: currentBranch,
1795
+ isClean,
1796
+ projectName
1797
+ };
1798
+ const worktrees = getProjectWorktrees();
1799
+ const worktreeStatuses = worktrees.map((wt) => collectWorktreeDetailedStatus(wt, projectName));
1800
+ const snapshots = collectSnapshots(projectName, worktrees);
1801
+ return {
1802
+ main,
1803
+ worktrees: worktreeStatuses,
1804
+ snapshots,
1805
+ totalWorktrees: worktrees.length
1806
+ };
1807
+ }
1808
+ function collectWorktreeDetailedStatus(worktree, projectName) {
1809
+ const changeStatus = detectChangeStatus(worktree);
1810
+ const { commitsAhead, commitsBehind } = countCommitDivergence(worktree.branch);
1811
+ const { insertions, deletions } = countDiffStat(worktree.path);
1812
+ return {
1813
+ path: worktree.path,
1814
+ branch: worktree.branch,
1815
+ changeStatus,
1816
+ commitsAhead,
1817
+ commitsBehind,
1818
+ hasSnapshot: hasSnapshot(projectName, worktree.branch),
1819
+ insertions,
1820
+ deletions
1821
+ };
1822
+ }
1823
+ function detectChangeStatus(worktree) {
1824
+ try {
1825
+ if (hasMergeConflict(worktree.path)) {
1826
+ return "conflict";
1827
+ }
1828
+ if (!isWorkingDirClean(worktree.path)) {
1829
+ return "uncommitted";
1830
+ }
1831
+ if (hasLocalCommits(worktree.branch)) {
1832
+ return "committed";
1833
+ }
1834
+ return "clean";
1835
+ } catch {
1836
+ return "clean";
1837
+ }
1838
+ }
1839
+ function countCommitDivergence(branchName) {
1840
+ try {
1841
+ return {
1842
+ commitsAhead: getCommitCountAhead(branchName),
1843
+ commitsBehind: getCommitCountBehind(branchName)
1844
+ };
1845
+ } catch {
1846
+ return { commitsAhead: 0, commitsBehind: 0 };
1847
+ }
1848
+ }
1849
+ function countDiffStat(worktreePath) {
1850
+ try {
1851
+ return getDiffStat(worktreePath);
1852
+ } catch {
1853
+ return { insertions: 0, deletions: 0 };
1854
+ }
1855
+ }
1856
+ function collectSnapshots(projectName, worktrees) {
1857
+ const snapshotBranches = getProjectSnapshotBranches(projectName);
1858
+ const worktreeBranchSet = new Set(worktrees.map((wt) => wt.branch));
1859
+ return snapshotBranches.map((branch) => ({
1860
+ branch,
1861
+ worktreeExists: worktreeBranchSet.has(branch)
1862
+ }));
1863
+ }
1864
+ function printStatusAsJson(result) {
1865
+ console.log(JSON.stringify(result, null, 2));
1866
+ }
1867
+ function printStatusAsText(result) {
1868
+ printDoubleSeparator();
1869
+ printInfo(` ${chalk5.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
1870
+ printDoubleSeparator();
1871
+ printInfo("");
1872
+ printMainSection(result.main);
1873
+ printSeparator();
1874
+ printInfo("");
1875
+ printWorktreesSection(result.worktrees, result.totalWorktrees);
1876
+ printSeparator();
1877
+ printInfo("");
1878
+ printSnapshotsSection(result.snapshots);
1879
+ printDoubleSeparator();
1880
+ }
1881
+ function printMainSection(main) {
1882
+ printInfo(` ${chalk5.bold("\u25C6")} ${chalk5.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
1883
+ printInfo(` \u5206\u652F: ${chalk5.bold(main.branch)}`);
1884
+ if (main.isClean) {
1885
+ printInfo(` \u72B6\u6001: ${chalk5.green("\u2713 \u5E72\u51C0")}`);
1886
+ } else {
1887
+ printInfo(` \u72B6\u6001: ${chalk5.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
1888
+ }
1889
+ printInfo("");
1890
+ }
1891
+ function printWorktreesSection(worktrees, total) {
1892
+ printInfo(` ${chalk5.bold("\u25C6")} ${chalk5.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
1893
+ printInfo("");
1894
+ if (worktrees.length === 0) {
1895
+ printInfo(` ${MESSAGES.STATUS_NO_WORKTREES}`);
1896
+ return;
1897
+ }
1898
+ for (const wt of worktrees) {
1899
+ printWorktreeItem(wt);
1900
+ }
1901
+ }
1902
+ function printWorktreeItem(wt) {
1903
+ const statusLabel = formatChangeStatusLabel(wt.changeStatus);
1904
+ printInfo(` ${chalk5.bold("\u25CF")} ${chalk5.bold(wt.branch)} [${statusLabel}]`);
1905
+ const parts = [];
1906
+ if (wt.insertions > 0 || wt.deletions > 0) {
1907
+ parts.push(`${chalk5.green(`+${wt.insertions}`)} ${chalk5.red(`-${wt.deletions}`)}`);
1908
+ }
1909
+ if (wt.commitsAhead > 0) {
1910
+ parts.push(chalk5.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`));
1911
+ }
1912
+ if (wt.commitsBehind > 0) {
1913
+ parts.push(chalk5.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`));
1914
+ } else {
1915
+ parts.push(chalk5.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65"));
1916
+ }
1917
+ printInfo(` ${parts.join(" ")}`);
1918
+ if (wt.hasSnapshot) {
1919
+ printInfo(` ${chalk5.blue("\u6709 validate \u5FEB\u7167")}`);
1920
+ }
1921
+ printInfo("");
1922
+ }
1923
+ function formatChangeStatusLabel(status) {
1924
+ switch (status) {
1925
+ case "committed":
1926
+ return chalk5.green(MESSAGES.STATUS_CHANGE_COMMITTED);
1927
+ case "uncommitted":
1928
+ return chalk5.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
1929
+ case "conflict":
1930
+ return chalk5.red(MESSAGES.STATUS_CHANGE_CONFLICT);
1931
+ case "clean":
1932
+ return chalk5.gray(MESSAGES.STATUS_CHANGE_CLEAN);
1933
+ }
1934
+ }
1935
+ function printSnapshotsSection(snapshots) {
1936
+ printInfo(` ${chalk5.bold("\u25C6")} ${chalk5.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.length} \u4E2A)`);
1937
+ printInfo("");
1938
+ if (snapshots.length === 0) {
1939
+ printInfo(` ${MESSAGES.STATUS_NO_SNAPSHOTS}`);
1940
+ printInfo("");
1941
+ return;
1942
+ }
1943
+ for (const snap of snapshots) {
1944
+ const orphanLabel = snap.worktreeExists ? "" : ` ${chalk5.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED)}`;
1945
+ const icon = snap.worktreeExists ? chalk5.blue("\u25CF") : chalk5.yellow("\u26A0");
1946
+ printInfo(` ${icon} ${snap.branch}${orphanLabel}`);
1947
+ }
1948
+ printInfo("");
1949
+ }
1950
+
1635
1951
  // src/index.ts
1636
1952
  var require2 = createRequire(import.meta.url);
1637
1953
  var { version } = require2("../package.json");
@@ -1653,6 +1969,7 @@ registerMergeCommand(program);
1653
1969
  registerConfigCommand(program);
1654
1970
  registerSyncCommand(program);
1655
1971
  registerResetCommand(program);
1972
+ registerStatusCommand(program);
1656
1973
  process.on("uncaughtException", (error) => {
1657
1974
  if (error instanceof ClawtError) {
1658
1975
  printError(error.message);