clawt 2.10.0 → 2.11.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.
- package/.claude/agent-memory/docs-sync-updater/MEMORY.md +14 -7
- package/.claude/agents/docs-sync-updater.md +11 -0
- package/README.md +80 -284
- package/dist/index.js +839 -307
- package/dist/postinstall.js +272 -0
- package/docs/spec.md +84 -22
- package/package.json +1 -1
- package/src/commands/remove.ts +21 -28
- package/src/commands/run.ts +68 -206
- package/src/constants/config.ts +4 -0
- package/src/constants/index.ts +11 -1
- package/src/constants/messages/common.ts +41 -0
- package/src/constants/messages/config.ts +5 -0
- package/src/constants/messages/create.ts +5 -0
- package/src/constants/messages/index.ts +29 -0
- package/src/constants/messages/merge.ts +42 -0
- package/src/constants/messages/remove.ts +15 -0
- package/src/constants/messages/reset.ts +7 -0
- package/src/constants/messages/resume.ts +12 -0
- package/src/constants/messages/run.ts +46 -0
- package/src/constants/messages/status.ts +25 -0
- package/src/constants/messages/sync.ts +24 -0
- package/src/constants/messages/validate.ts +25 -0
- package/src/constants/progress.ts +39 -0
- package/src/types/command.ts +4 -0
- package/src/types/config.ts +2 -0
- package/src/types/index.ts +1 -0
- package/src/types/taskFile.ts +13 -0
- package/src/utils/formatter.ts +16 -0
- package/src/utils/index.ts +8 -4
- package/src/utils/progress-render.ts +90 -0
- package/src/utils/progress.ts +213 -0
- package/src/utils/task-executor.ts +365 -0
- package/src/utils/task-file.ts +87 -0
- package/src/utils/worktree-matcher.ts +92 -0
- package/src/utils/worktree.ts +27 -0
- package/tests/unit/commands/config.test.ts +110 -0
- package/tests/unit/commands/create.test.ts +115 -0
- package/tests/unit/commands/list.test.ts +118 -0
- package/tests/unit/commands/merge.test.ts +323 -0
- package/tests/unit/commands/remove.test.ts +240 -0
- package/tests/unit/commands/reset.test.ts +124 -0
- package/tests/unit/commands/resume.test.ts +91 -0
- package/tests/unit/commands/run.test.ts +456 -0
- package/tests/unit/commands/status.test.ts +214 -0
- package/tests/unit/commands/sync.test.ts +208 -0
- package/tests/unit/commands/validate.test.ts +382 -0
- package/tests/unit/constants/config.test.ts +1 -0
- package/tests/unit/constants/messages.test.ts +1 -1
- package/tests/unit/utils/config.test.ts +21 -1
- package/tests/unit/utils/formatter.test.ts +70 -1
- package/tests/unit/utils/git.test.ts +44 -0
- package/tests/unit/utils/progress.test.ts +255 -0
- package/tests/unit/utils/task-file.test.ts +236 -0
- package/tests/unit/utils/validate-snapshot.test.ts +25 -0
- package/tests/unit/utils/worktree-matcher.test.ts +81 -5
- package/tests/unit/utils/worktree.test.ts +26 -1
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
|
|
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,24 +44,24 @@ 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
|
-
/**
|
|
50
|
-
|
|
51
|
-
\u53EF\u4EE5\u5F00\u59CB\u9A8C\u8BC1\u4E86`,
|
|
52
|
-
/** merge 成功 */
|
|
53
|
-
MERGE_SUCCESS: (branch, message, pushed) => `\u2713 \u5206\u652F ${branch} \u5DF2\u6210\u529F\u5408\u5E76\u5230\u5F53\u524D\u5206\u652F
|
|
54
|
-
\u63D0\u4EA4\u4FE1\u606F: ${message}${pushed ? "\n \u5DF2\u63A8\u9001\u5230\u8FDC\u7A0B\u4ED3\u5E93" : ""}`,
|
|
55
|
-
/** merge 成功(无提交信息,目标 worktree 已提交过) */
|
|
56
|
-
MERGE_SUCCESS_NO_MESSAGE: (branch, pushed) => `\u2713 \u5206\u652F ${branch} \u5DF2\u6210\u529F\u5408\u5E76\u5230\u5F53\u524D\u5206\u652F${pushed ? "\n \u5DF2\u63A8\u9001\u5230\u8FDC\u7A0B\u4ED3\u5E93" : ""}`,
|
|
57
|
-
/** merge 冲突 */
|
|
58
|
-
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
|
-
/** merge 后清理 worktree 和分支成功 */
|
|
60
|
-
WORKTREE_CLEANED: (branch) => `\u2713 \u5DF2\u6E05\u7406 worktree \u548C\u5206\u652F: ${branch}`,
|
|
47
|
+
/** 用户取消破坏性操作 */
|
|
48
|
+
DESTRUCTIVE_OP_CANCELLED: "\u5DF2\u53D6\u6D88\u64CD\u4F5C",
|
|
61
49
|
/** 请提供提交信息 */
|
|
62
50
|
COMMIT_MESSAGE_REQUIRED: "\u8BF7\u63D0\u4F9B\u63D0\u4EA4\u4FE1\u606F\uFF08-m \u53C2\u6570\uFF09",
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
|
|
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`,
|
|
67
65
|
/** 检测到用户中断 */
|
|
68
66
|
INTERRUPTED: "\u68C0\u6D4B\u5230\u9000\u51FA\u6307\u4EE4\uFF0C\u5DF2\u505C\u6B62 Claude Code \u4EFB\u52A1",
|
|
69
67
|
/** 中断后自动清理完成 */
|
|
@@ -74,41 +72,57 @@ var MESSAGES = {
|
|
|
74
72
|
INTERRUPT_CLEANED: (count) => `\u2713 \u5DF2\u6E05\u7406 ${count} \u4E2A worktree \u548C\u5BF9\u5E94\u5206\u652F`,
|
|
75
73
|
/** 中断后保留 worktree */
|
|
76
74
|
INTERRUPT_KEPT: "\u5DF2\u4FDD\u7559 worktree\uFF0C\u53EF\u7A0D\u540E\u4F7F\u7528 clawt remove \u624B\u52A8\u6E05\u7406",
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
|
|
75
|
+
/** 非 TTY 环境降级输出:任务启动 */
|
|
76
|
+
PROGRESS_TASK_STARTED: (index, total, branch, path) => `[${index}/${total}] ${branch} \u542F\u52A8 ${path}`,
|
|
77
|
+
/** 非 TTY 环境降级输出:任务完成 */
|
|
78
|
+
PROGRESS_TASK_DONE: (index, total, branch, duration, cost, path) => `[${index}/${total}] ${branch} \u2713 \u5B8C\u6210 ${duration} ${cost} ${path}`,
|
|
79
|
+
/** 非 TTY 环境降级输出:任务失败 */
|
|
80
|
+
PROGRESS_TASK_FAILED: (index, total, branch, duration, path) => `[${index}/${total}] ${branch} \u2717 \u5931\u8D25 ${duration} ${path}`,
|
|
81
|
+
/** 并发限制提示 */
|
|
82
|
+
CONCURRENCY_INFO: (concurrency, total) => `\u5E76\u53D1\u9650\u5236: ${concurrency}\uFF0C\u5171 ${total} \u4E2A\u4EFB\u52A1`,
|
|
83
|
+
/** 并发数无效提示 */
|
|
84
|
+
CONCURRENCY_INVALID: "\u5E76\u53D1\u6570\u5FC5\u987B\u4E3A\u6B63\u6574\u6570",
|
|
85
|
+
/** 任务文件不存在 */
|
|
86
|
+
TASK_FILE_NOT_FOUND: (path) => `\u4EFB\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728: ${path}`,
|
|
87
|
+
/** 任务文件中没有解析到有效任务 */
|
|
88
|
+
TASK_FILE_EMPTY: "\u4EFB\u52A1\u6587\u4EF6\u4E2D\u6CA1\u6709\u89E3\u6790\u5230\u6709\u6548\u4EFB\u52A1",
|
|
89
|
+
/** 任务文件中某个任务块缺少分支名 */
|
|
90
|
+
TASK_FILE_MISSING_BRANCH: (blockIndex) => `\u4EFB\u52A1\u6587\u4EF6\u7B2C ${blockIndex} \u4E2A\u4EFB\u52A1\u5757\u7F3A\u5C11\u5206\u652F\u540D\uFF08# branch: ...\uFF09`,
|
|
91
|
+
/** 任务文件中某个任务块缺少任务描述 */
|
|
92
|
+
TASK_FILE_MISSING_TASK: (branch) => `\u4EFB\u52A1\u6587\u4EF6\u4E2D\u5206\u652F ${branch} \u7F3A\u5C11\u4EFB\u52A1\u63CF\u8FF0`,
|
|
93
|
+
/** 任务文件中某个任务块缺少任务描述(无分支名时按索引定位) */
|
|
94
|
+
TASK_FILE_MISSING_TASK_BY_INDEX: (blockIndex) => `\u4EFB\u52A1\u6587\u4EF6\u7B2C ${blockIndex} \u4E2A\u4EFB\u52A1\u5757\u7F3A\u5C11\u4EFB\u52A1\u63CF\u8FF0`,
|
|
95
|
+
/** --file 和 --tasks 不能同时使用 */
|
|
96
|
+
FILE_AND_TASKS_CONFLICT: "--file \u548C --tasks \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528",
|
|
97
|
+
/** 任务文件加载成功 */
|
|
98
|
+
TASK_FILE_LOADED: (count, path) => `\u2713 \u4ECE ${path} \u52A0\u8F7D\u4E86 ${count} \u4E2A\u4EFB\u52A1`,
|
|
99
|
+
/** 未指定 -b 或 -f */
|
|
100
|
+
BRANCH_OR_FILE_REQUIRED: "\u8BF7\u6307\u5B9A -b \u5206\u652F\u540D\u6216 -f \u4EFB\u52A1\u6587\u4EF6"
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/constants/messages/create.ts
|
|
104
|
+
var CREATE_MESSAGES = {
|
|
85
105
|
/** 创建数量参数无效 */
|
|
86
|
-
INVALID_COUNT: (value) => `\u65E0\u6548\u7684\u521B\u5EFA\u6570\u91CF: "${value}"\uFF0C\u8BF7\u8F93\u5165\u6B63\u6574\u6570
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
|
|
106
|
+
INVALID_COUNT: (value) => `\u65E0\u6548\u7684\u521B\u5EFA\u6570\u91CF: "${value}"\uFF0C\u8BF7\u8F93\u5165\u6B63\u6574\u6570`
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// src/constants/messages/merge.ts
|
|
110
|
+
var MERGE_MESSAGES = {
|
|
111
|
+
/** merge 成功 */
|
|
112
|
+
MERGE_SUCCESS: (branch, message, pushed) => `\u2713 \u5206\u652F ${branch} \u5DF2\u6210\u529F\u5408\u5E76\u5230\u5F53\u524D\u5206\u652F
|
|
113
|
+
\u63D0\u4EA4\u4FE1\u606F: ${message}${pushed ? "\n \u5DF2\u63A8\u9001\u5230\u8FDC\u7A0B\u4ED3\u5E93" : ""}`,
|
|
114
|
+
/** merge 成功(无提交信息,目标 worktree 已提交过) */
|
|
115
|
+
MERGE_SUCCESS_NO_MESSAGE: (branch, pushed) => `\u2713 \u5206\u652F ${branch} \u5DF2\u6210\u529F\u5408\u5E76\u5230\u5F53\u524D\u5206\u652F${pushed ? "\n \u5DF2\u63A8\u9001\u5230\u8FDC\u7A0B\u4ED3\u5E93" : ""}`,
|
|
116
|
+
/** merge 冲突 */
|
|
117
|
+
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",
|
|
118
|
+
/** merge 后清理 worktree 和分支成功 */
|
|
119
|
+
WORKTREE_CLEANED: (branch) => `\u2713 \u5DF2\u6E05\u7406 worktree \u548C\u5206\u652F: ${branch}`,
|
|
120
|
+
/** 目标 worktree 有未提交修改但未指定 -m */
|
|
121
|
+
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",
|
|
122
|
+
/** 目标 worktree 既干净又无本地提交 */
|
|
123
|
+
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",
|
|
96
124
|
/** merge 命令检测到 validate 状态的提示 */
|
|
97
125
|
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
126
|
/** merge 检测到 auto-save 提交,提示用户是否压缩 */
|
|
113
127
|
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
128
|
/** squash 完成且通过 -m 直接提交后的提示 */
|
|
@@ -118,25 +132,37 @@ var MESSAGES = {
|
|
|
118
132
|
\u8BF7\u5728\u76EE\u6807 worktree \u4E2D\u63D0\u4EA4\u540E\u91CD\u65B0\u6267\u884C merge\uFF1A
|
|
119
133
|
cd ${worktreePath}
|
|
120
134
|
\u63D0\u4EA4\u5B8C\u6210\u540E\u6267\u884C\uFF1Aclawt merge -b ${branch}`,
|
|
121
|
-
/**
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
|
|
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
|
|
135
|
+
/** merge 后 pull 冲突 */
|
|
136
|
+
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",
|
|
137
|
+
/** push 失败 */
|
|
138
|
+
PUSH_FAILED: "\u81EA\u52A8 push \u5931\u8D25\uFF0Cmerge \u548C pull \u5DF2\u5B8C\u6210\n \u8BF7\u624B\u52A8\u6267\u884C git push",
|
|
139
|
+
/** merge 无可用 worktree */
|
|
140
|
+
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",
|
|
141
|
+
/** merge 模糊匹配无结果,列出可用分支 */
|
|
142
|
+
MERGE_NO_MATCH: (name, branches) => `\u672A\u627E\u5230\u4E0E "${name}" \u5339\u914D\u7684\u5206\u652F
|
|
134
143
|
\u53EF\u7528\u5206\u652F\uFF1A
|
|
135
144
|
${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
136
|
-
/**
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
|
|
145
|
+
/** merge 交互选择提示 */
|
|
146
|
+
MERGE_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u5408\u5E76\u7684\u5206\u652F",
|
|
147
|
+
/** merge 模糊匹配到多个结果提示 */
|
|
148
|
+
MERGE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// src/constants/messages/validate.ts
|
|
152
|
+
var VALIDATE_MESSAGES = {
|
|
153
|
+
/** validate 成功 */
|
|
154
|
+
VALIDATE_SUCCESS: (branch) => `\u2713 \u5DF2\u5C06\u5206\u652F ${branch} \u7684\u53D8\u66F4\u5E94\u7528\u5230\u4E3B worktree
|
|
155
|
+
\u53EF\u4EE5\u5F00\u59CB\u9A8C\u8BC1\u4E86`,
|
|
156
|
+
/** 增量 validate 成功提示 */
|
|
157
|
+
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
|
|
158
|
+
\u6682\u5B58\u533A = \u4E0A\u6B21\u5FEB\u7167\uFF0C\u5DE5\u4F5C\u76EE\u5F55 = \u6700\u65B0\u53D8\u66F4`,
|
|
159
|
+
/** 增量 validate 降级为全量模式提示 */
|
|
160
|
+
INCREMENTAL_VALIDATE_FALLBACK: "\u589E\u91CF\u5BF9\u6BD4\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u4E3A\u5168\u91CF\u6A21\u5F0F",
|
|
161
|
+
/** validate 状态已清理 */
|
|
162
|
+
VALIDATE_CLEANED: (branch) => `\u2713 \u5206\u652F ${branch} \u7684 validate \u72B6\u6001\u5DF2\u6E05\u7406`,
|
|
163
|
+
/** validate patch apply 失败,提示用户同步主分支 */
|
|
164
|
+
VALIDATE_PATCH_APPLY_FAILED: (branch) => `\u53D8\u66F4\u8FC1\u79FB\u5931\u8D25\uFF1A\u76EE\u6807\u5206\u652F\u4E0E\u4E3B\u5206\u652F\u5DEE\u5F02\u8FC7\u5927
|
|
165
|
+
\u8BF7\u5148\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`,
|
|
140
166
|
/** validate 无可用 worktree */
|
|
141
167
|
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
168
|
/** validate 模糊匹配无结果,列出可用分支 */
|
|
@@ -146,17 +172,22 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
|
146
172
|
/** validate 交互选择提示 */
|
|
147
173
|
VALIDATE_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u9A8C\u8BC1\u7684\u5206\u652F",
|
|
148
174
|
/** validate 模糊匹配到多个结果提示 */
|
|
149
|
-
VALIDATE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
|
|
175
|
+
VALIDATE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/constants/messages/sync.ts
|
|
179
|
+
var SYNC_MESSAGES = {
|
|
180
|
+
/** sync 自动保存未提交变更 */
|
|
181
|
+
SYNC_AUTO_COMMITTED: (branch) => `\u5DF2\u81EA\u52A8\u4FDD\u5B58 ${branch} \u5206\u652F\u7684\u672A\u63D0\u4EA4\u53D8\u66F4`,
|
|
182
|
+
/** sync 开始合并 */
|
|
183
|
+
SYNC_MERGING: (targetBranch, mainBranch) => `\u6B63\u5728\u5C06 ${mainBranch} \u5408\u5E76\u5230 ${targetBranch} ...`,
|
|
184
|
+
/** sync 成功 */
|
|
185
|
+
SYNC_SUCCESS: (targetBranch, mainBranch) => `\u2713 \u5DF2\u5C06 ${mainBranch} \u7684\u6700\u65B0\u4EE3\u7801\u540C\u6B65\u5230 ${targetBranch}`,
|
|
186
|
+
/** sync 冲突 */
|
|
187
|
+
SYNC_CONFLICT: (worktreePath) => `\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u8FDB\u5165\u76EE\u6807 worktree \u624B\u52A8\u89E3\u51B3\uFF1A
|
|
188
|
+
cd ${worktreePath}
|
|
189
|
+
\u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue
|
|
190
|
+
clawt validate -b <branch> \u9A8C\u8BC1\u53D8\u66F4`,
|
|
160
191
|
/** sync 无可用 worktree */
|
|
161
192
|
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
193
|
/** sync 模糊匹配无结果,列出可用分支 */
|
|
@@ -166,7 +197,56 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
|
166
197
|
/** sync 交互选择提示 */
|
|
167
198
|
SYNC_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u540C\u6B65\u7684\u5206\u652F",
|
|
168
199
|
/** sync 模糊匹配到多个结果提示 */
|
|
169
|
-
SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A
|
|
200
|
+
SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// src/constants/messages/resume.ts
|
|
204
|
+
var RESUME_MESSAGES = {
|
|
205
|
+
/** resume 无可用 worktree */
|
|
206
|
+
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",
|
|
207
|
+
/** resume 模糊匹配无结果,列出可用分支 */
|
|
208
|
+
RESUME_NO_MATCH: (name, branches) => `\u672A\u627E\u5230\u4E0E "${name}" \u5339\u914D\u7684\u5206\u652F
|
|
209
|
+
\u53EF\u7528\u5206\u652F\uFF1A
|
|
210
|
+
${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
211
|
+
/** resume 交互选择提示 */
|
|
212
|
+
RESUME_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u6062\u590D\u7684\u5206\u652F",
|
|
213
|
+
/** resume 模糊匹配到多个结果提示 */
|
|
214
|
+
RESUME_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// src/constants/messages/remove.ts
|
|
218
|
+
var REMOVE_MESSAGES = {
|
|
219
|
+
/** remove 无可用 worktree */
|
|
220
|
+
REMOVE_NO_WORKTREES: "\u5F53\u524D\u9879\u76EE\u6CA1\u6709\u53EF\u7528\u7684 worktree\uFF0C\u65E0\u9700\u79FB\u9664",
|
|
221
|
+
/** remove 多选交互提示 */
|
|
222
|
+
REMOVE_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u79FB\u9664\u7684\u5206\u652F\uFF08\u7A7A\u683C\u9009\u62E9\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09",
|
|
223
|
+
/** remove 模糊匹配到多个结果提示 */
|
|
224
|
+
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`,
|
|
225
|
+
/** remove 模糊匹配无结果,列出可用分支 */
|
|
226
|
+
REMOVE_NO_MATCH: (name, branches) => `\u672A\u627E\u5230\u4E0E "${name}" \u5339\u914D\u7684\u5206\u652F
|
|
227
|
+
\u53EF\u7528\u5206\u652F\uFF1A
|
|
228
|
+
${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
229
|
+
/** 批量移除部分失败 */
|
|
230
|
+
REMOVE_PARTIAL_FAILURE: (failures) => `\u4EE5\u4E0B worktree \u79FB\u9664\u5931\u8D25\uFF1A
|
|
231
|
+
${failures.map((f) => ` \u2717 ${f.path}: ${f.error}`).join("\n")}`
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// src/constants/messages/reset.ts
|
|
235
|
+
var RESET_MESSAGES = {
|
|
236
|
+
/** reset 成功 */
|
|
237
|
+
RESET_SUCCESS: "\u2713 \u4E3B worktree \u5DE5\u4F5C\u533A\u548C\u6682\u5B58\u533A\u5DF2\u91CD\u7F6E",
|
|
238
|
+
/** reset 时工作区和暂存区已干净 */
|
|
239
|
+
RESET_ALREADY_CLEAN: "\u4E3B worktree \u5DE5\u4F5C\u533A\u548C\u6682\u5B58\u533A\u5DF2\u662F\u5E72\u51C0\u72B6\u6001\uFF0C\u65E0\u9700\u91CD\u7F6E"
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// src/constants/messages/config.ts
|
|
243
|
+
var CONFIG_CMD_MESSAGES = {
|
|
244
|
+
/** 配置已恢复为默认值 */
|
|
245
|
+
CONFIG_RESET_SUCCESS: "\u2713 \u914D\u7F6E\u5DF2\u6062\u590D\u4E3A\u9ED8\u8BA4\u503C"
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// src/constants/messages/status.ts
|
|
249
|
+
var STATUS_MESSAGES = {
|
|
170
250
|
/** status 命令标题 */
|
|
171
251
|
STATUS_TITLE: (projectName) => `\u9879\u76EE\u72B6\u6001\u603B\u89C8: ${projectName}`,
|
|
172
252
|
/** status 主 worktree 区块标题 */
|
|
@@ -188,11 +268,22 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
|
188
268
|
/** status 变更状态:无变更 */
|
|
189
269
|
STATUS_CHANGE_CLEAN: "\u65E0\u53D8\u66F4",
|
|
190
270
|
/** status 快照对应 worktree 已不存在 */
|
|
191
|
-
STATUS_SNAPSHOT_ORPHANED: "(\u5BF9\u5E94 worktree \u5DF2\u4E0D\u5B58\u5728)"
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
271
|
+
STATUS_SNAPSHOT_ORPHANED: "(\u5BF9\u5E94 worktree \u5DF2\u4E0D\u5B58\u5728)"
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// src/constants/messages/index.ts
|
|
275
|
+
var MESSAGES = {
|
|
276
|
+
...COMMON_MESSAGES,
|
|
277
|
+
...RUN_MESSAGES,
|
|
278
|
+
...CREATE_MESSAGES,
|
|
279
|
+
...MERGE_MESSAGES,
|
|
280
|
+
...VALIDATE_MESSAGES,
|
|
281
|
+
...SYNC_MESSAGES,
|
|
282
|
+
...RESUME_MESSAGES,
|
|
283
|
+
...REMOVE_MESSAGES,
|
|
284
|
+
...RESET_MESSAGES,
|
|
285
|
+
...CONFIG_CMD_MESSAGES,
|
|
286
|
+
...STATUS_MESSAGES
|
|
196
287
|
};
|
|
197
288
|
|
|
198
289
|
// src/constants/exitCodes.ts
|
|
@@ -223,6 +314,10 @@ var CONFIG_DEFINITIONS = {
|
|
|
223
314
|
confirmDestructiveOps: {
|
|
224
315
|
defaultValue: true,
|
|
225
316
|
description: "\u6267\u884C\u7834\u574F\u6027\u64CD\u4F5C\uFF08reset\u3001validate --clean\uFF09\u524D\u662F\u5426\u63D0\u793A\u786E\u8BA4"
|
|
317
|
+
},
|
|
318
|
+
maxConcurrency: {
|
|
319
|
+
defaultValue: 0,
|
|
320
|
+
description: "run \u547D\u4EE4\u9ED8\u8BA4\u6700\u5927\u5E76\u53D1\u6570\uFF0C0 \u8868\u793A\u4E0D\u9650\u5236"
|
|
226
321
|
}
|
|
227
322
|
};
|
|
228
323
|
function deriveDefaultConfig(definitions) {
|
|
@@ -246,6 +341,32 @@ var AUTO_SAVE_COMMIT_MESSAGE = "chore: auto-save before sync";
|
|
|
246
341
|
// src/constants/logger.ts
|
|
247
342
|
var DEBUG_TIMESTAMP_FORMAT = "HH:mm:ss.SSS";
|
|
248
343
|
|
|
344
|
+
// src/constants/progress.ts
|
|
345
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
346
|
+
var SPINNER_INTERVAL_MS = 100;
|
|
347
|
+
var CURSOR_UP = (n) => `\x1B[${n}A`;
|
|
348
|
+
var CLEAR_LINE = "\x1B[0K";
|
|
349
|
+
var CURSOR_HIDE = "\x1B[?25l";
|
|
350
|
+
var CURSOR_SHOW = "\x1B[?25h";
|
|
351
|
+
var TASK_STATUS_ICONS = {
|
|
352
|
+
/** 排队中 */
|
|
353
|
+
PENDING: "\u25E6",
|
|
354
|
+
/** 完成 */
|
|
355
|
+
DONE: "\u2713",
|
|
356
|
+
/** 失败 */
|
|
357
|
+
FAILED: "\u2717"
|
|
358
|
+
};
|
|
359
|
+
var TASK_STATUS_LABELS = {
|
|
360
|
+
/** 排队中 */
|
|
361
|
+
PENDING: "\u6392\u961F\u4E2D",
|
|
362
|
+
/** 运行中 */
|
|
363
|
+
RUNNING: "\u8FD0\u884C\u4E2D",
|
|
364
|
+
/** 完成 */
|
|
365
|
+
DONE: "\u5B8C\u6210",
|
|
366
|
+
/** 失败 */
|
|
367
|
+
FAILED: "\u5931\u8D25"
|
|
368
|
+
};
|
|
369
|
+
|
|
249
370
|
// src/errors/index.ts
|
|
250
371
|
var ClawtError = class extends Error {
|
|
251
372
|
/** 退出码 */
|
|
@@ -551,14 +672,14 @@ function printDoubleSeparator() {
|
|
|
551
672
|
console.log(MESSAGES.DOUBLE_SEPARATOR);
|
|
552
673
|
}
|
|
553
674
|
function confirmAction(question) {
|
|
554
|
-
return new Promise((
|
|
675
|
+
return new Promise((resolve2) => {
|
|
555
676
|
const rl = createInterface({
|
|
556
677
|
input: process.stdin,
|
|
557
678
|
output: process.stdout
|
|
558
679
|
});
|
|
559
680
|
rl.question(`${question} (y/N) `, (answer) => {
|
|
560
681
|
rl.close();
|
|
561
|
-
|
|
682
|
+
resolve2(answer.toLowerCase() === "y");
|
|
562
683
|
});
|
|
563
684
|
});
|
|
564
685
|
}
|
|
@@ -585,6 +706,15 @@ function formatWorktreeStatus(status) {
|
|
|
585
706
|
}
|
|
586
707
|
return parts.join(" ");
|
|
587
708
|
}
|
|
709
|
+
function formatDuration(ms) {
|
|
710
|
+
const totalSeconds = ms / 1e3;
|
|
711
|
+
if (totalSeconds < 60) {
|
|
712
|
+
return `${totalSeconds.toFixed(1)}s`;
|
|
713
|
+
}
|
|
714
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
715
|
+
const seconds = Math.floor(totalSeconds % 60);
|
|
716
|
+
return `${minutes}m${String(seconds).padStart(2, "0")}s`;
|
|
717
|
+
}
|
|
588
718
|
|
|
589
719
|
// src/utils/branch.ts
|
|
590
720
|
function sanitizeBranchName(branchName) {
|
|
@@ -677,6 +807,19 @@ function createWorktrees(branchName, count) {
|
|
|
677
807
|
}
|
|
678
808
|
return results;
|
|
679
809
|
}
|
|
810
|
+
function createWorktreesByBranches(branchNames) {
|
|
811
|
+
validateBranchesNotExist(branchNames);
|
|
812
|
+
const projectDir = getProjectWorktreeDir();
|
|
813
|
+
ensureDir(projectDir);
|
|
814
|
+
const results = [];
|
|
815
|
+
for (const name of branchNames) {
|
|
816
|
+
const worktreePath = join2(projectDir, name);
|
|
817
|
+
createWorktree(name, worktreePath);
|
|
818
|
+
results.push({ path: worktreePath, branch: name });
|
|
819
|
+
logger.info(`worktree \u521B\u5EFA\u5B8C\u6210: ${worktreePath} (\u5206\u652F: ${name})`);
|
|
820
|
+
}
|
|
821
|
+
return results;
|
|
822
|
+
}
|
|
680
823
|
function getProjectWorktrees() {
|
|
681
824
|
const projectDir = getProjectWorktreeDir();
|
|
682
825
|
if (!existsSync3(projectDir)) {
|
|
@@ -871,6 +1014,44 @@ async function promptSelectBranch(worktrees, message) {
|
|
|
871
1014
|
}).run();
|
|
872
1015
|
return worktrees.find((wt) => wt.branch === selectedBranch);
|
|
873
1016
|
}
|
|
1017
|
+
async function promptMultiSelectBranches(worktrees, message) {
|
|
1018
|
+
const selectedBranches = await new Enquirer2.MultiSelect({
|
|
1019
|
+
message,
|
|
1020
|
+
choices: worktrees.map((wt) => ({
|
|
1021
|
+
name: wt.branch,
|
|
1022
|
+
message: wt.branch
|
|
1023
|
+
})),
|
|
1024
|
+
// 使用空心圆/实心圆作为选中指示符
|
|
1025
|
+
symbols: {
|
|
1026
|
+
indicator: { on: "\u25CF", off: "\u25CB" }
|
|
1027
|
+
}
|
|
1028
|
+
}).run();
|
|
1029
|
+
return worktrees.filter((wt) => selectedBranches.includes(wt.branch));
|
|
1030
|
+
}
|
|
1031
|
+
async function resolveTargetWorktrees(worktrees, messages, branchName) {
|
|
1032
|
+
if (worktrees.length === 0) {
|
|
1033
|
+
throw new ClawtError(messages.noWorktrees);
|
|
1034
|
+
}
|
|
1035
|
+
if (!branchName) {
|
|
1036
|
+
if (worktrees.length === 1) {
|
|
1037
|
+
return [worktrees[0]];
|
|
1038
|
+
}
|
|
1039
|
+
return promptMultiSelectBranches(worktrees, messages.selectBranch);
|
|
1040
|
+
}
|
|
1041
|
+
const exactMatch = findExactMatch(worktrees, branchName);
|
|
1042
|
+
if (exactMatch) {
|
|
1043
|
+
return [exactMatch];
|
|
1044
|
+
}
|
|
1045
|
+
const fuzzyMatches = findFuzzyMatches(worktrees, branchName);
|
|
1046
|
+
if (fuzzyMatches.length === 1) {
|
|
1047
|
+
return [fuzzyMatches[0]];
|
|
1048
|
+
}
|
|
1049
|
+
if (fuzzyMatches.length > 1) {
|
|
1050
|
+
return promptMultiSelectBranches(fuzzyMatches, messages.multipleMatches(branchName));
|
|
1051
|
+
}
|
|
1052
|
+
const allBranches = worktrees.map((wt) => wt.branch);
|
|
1053
|
+
throw new ClawtError(messages.noMatch(branchName, allBranches));
|
|
1054
|
+
}
|
|
874
1055
|
async function resolveTargetWorktree(worktrees, messages, branchName) {
|
|
875
1056
|
if (worktrees.length === 0) {
|
|
876
1057
|
throw new ClawtError(messages.noWorktrees);
|
|
@@ -896,160 +1077,262 @@ async function resolveTargetWorktree(worktrees, messages, branchName) {
|
|
|
896
1077
|
throw new ClawtError(messages.noMatch(branchName, allBranches));
|
|
897
1078
|
}
|
|
898
1079
|
|
|
899
|
-
// src/
|
|
1080
|
+
// src/utils/progress-render.ts
|
|
900
1081
|
import chalk3 from "chalk";
|
|
901
|
-
function
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1082
|
+
function getMaxBranchWidth(tasks) {
|
|
1083
|
+
return Math.max(...tasks.map((t) => t.branch.length));
|
|
1084
|
+
}
|
|
1085
|
+
function renderTaskLine(task, total, maxBranchWidth, spinnerChar) {
|
|
1086
|
+
const indexStr = `[${task.index}/${total}]`;
|
|
1087
|
+
const branchStr = task.branch.padEnd(maxBranchWidth);
|
|
1088
|
+
switch (task.status) {
|
|
1089
|
+
case "pending": {
|
|
1090
|
+
return `${indexStr} ${branchStr} ${chalk3.gray(TASK_STATUS_ICONS.PENDING)} ${chalk3.gray(TASK_STATUS_LABELS.PENDING)} ${chalk3.dim(task.path)}`;
|
|
1091
|
+
}
|
|
1092
|
+
case "running": {
|
|
1093
|
+
const elapsed = formatDuration(Date.now() - task.startedAt);
|
|
1094
|
+
return `${indexStr} ${branchStr} ${chalk3.cyan(spinnerChar)} ${chalk3.cyan(TASK_STATUS_LABELS.RUNNING)} ${chalk3.gray(elapsed)} ${chalk3.dim(task.path)}`;
|
|
1095
|
+
}
|
|
1096
|
+
case "done": {
|
|
1097
|
+
const duration = task.durationMs != null ? formatDuration(task.durationMs) : "N/A";
|
|
1098
|
+
const cost = task.costUsd != null ? `$${task.costUsd.toFixed(2)}` : "";
|
|
1099
|
+
return `${indexStr} ${branchStr} ${chalk3.green(TASK_STATUS_ICONS.DONE)} ${chalk3.green(TASK_STATUS_LABELS.DONE)} ${chalk3.gray(duration)} ${chalk3.yellow(cost)} ${chalk3.dim(task.path)}`;
|
|
1100
|
+
}
|
|
1101
|
+
case "failed": {
|
|
1102
|
+
const duration = task.durationMs != null ? formatDuration(task.durationMs) : "N/A";
|
|
1103
|
+
return `${indexStr} ${branchStr} ${chalk3.red(TASK_STATUS_ICONS.FAILED)} ${chalk3.red(TASK_STATUS_LABELS.FAILED)} ${chalk3.gray(duration)} ${chalk3.dim(task.path)}`;
|
|
1104
|
+
}
|
|
914
1105
|
}
|
|
915
|
-
printListAsText(projectName, worktrees);
|
|
916
1106
|
}
|
|
917
|
-
function
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
};
|
|
926
|
-
|
|
1107
|
+
function renderSummaryLine(tasks, total) {
|
|
1108
|
+
const running = tasks.filter((t) => t.status === "running").length;
|
|
1109
|
+
const done = tasks.filter((t) => t.status === "done").length;
|
|
1110
|
+
const failed = tasks.filter((t) => t.status === "failed").length;
|
|
1111
|
+
const pending = tasks.filter((t) => t.status === "pending").length;
|
|
1112
|
+
const parts = [];
|
|
1113
|
+
if (running > 0) parts.push(chalk3.cyan(`${running}/${total} ${TASK_STATUS_LABELS.RUNNING}`));
|
|
1114
|
+
if (done > 0) parts.push(chalk3.green(`${done}/${total} ${TASK_STATUS_LABELS.DONE}`));
|
|
1115
|
+
if (failed > 0) parts.push(chalk3.red(`${failed}/${total} ${TASK_STATUS_LABELS.FAILED}`));
|
|
1116
|
+
if (pending > 0) parts.push(chalk3.gray(`${pending}/${total} ${TASK_STATUS_LABELS.PENDING}`));
|
|
1117
|
+
return `[${parts.join(", ")}]`;
|
|
927
1118
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1119
|
+
|
|
1120
|
+
// src/utils/progress.ts
|
|
1121
|
+
var ProgressRenderer = class {
|
|
1122
|
+
/** 所有任务的进度状态 */
|
|
1123
|
+
tasks;
|
|
1124
|
+
/** 总任务数 */
|
|
1125
|
+
total;
|
|
1126
|
+
/** 当前 spinner 帧索引 */
|
|
1127
|
+
frameIndex;
|
|
1128
|
+
/** 定时器引用 */
|
|
1129
|
+
timer;
|
|
1130
|
+
/** 是否为 TTY 环境 */
|
|
1131
|
+
isTTY;
|
|
1132
|
+
/** 已渲染的行数(用于回退光标) */
|
|
1133
|
+
renderedLineCount;
|
|
1134
|
+
/** 是否已停止 */
|
|
1135
|
+
stopped;
|
|
1136
|
+
/** 是否存在排队任务(启用汇总行渲染) */
|
|
1137
|
+
hasPendingTasks;
|
|
1138
|
+
/**
|
|
1139
|
+
* 创建进度面板渲染器
|
|
1140
|
+
* @param {string[]} branches - 分支名列表,顺序对应任务列表
|
|
1141
|
+
* @param {string[]} paths - worktree 路径列表,完成/失败后显示
|
|
1142
|
+
* @param {boolean} [allRunning=true] - 是否将所有任务初始化为 running 状态,false 时初始化为 pending
|
|
1143
|
+
*/
|
|
1144
|
+
constructor(branches, paths, allRunning = true) {
|
|
1145
|
+
const now = Date.now();
|
|
1146
|
+
this.total = branches.length;
|
|
1147
|
+
this.frameIndex = 0;
|
|
1148
|
+
this.timer = null;
|
|
1149
|
+
this.isTTY = !!process.stdout.isTTY;
|
|
1150
|
+
this.renderedLineCount = 0;
|
|
1151
|
+
this.stopped = false;
|
|
1152
|
+
this.hasPendingTasks = !allRunning;
|
|
1153
|
+
this.tasks = branches.map((branch, i) => ({
|
|
1154
|
+
index: i + 1,
|
|
1155
|
+
branch,
|
|
1156
|
+
path: paths[i],
|
|
1157
|
+
status: allRunning ? "running" : "pending",
|
|
1158
|
+
startedAt: allRunning ? now : 0,
|
|
1159
|
+
finishedAt: null,
|
|
1160
|
+
lastActiveAt: allRunning ? now : 0,
|
|
1161
|
+
durationMs: null,
|
|
1162
|
+
costUsd: null
|
|
1163
|
+
}));
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* 启动定时渲染循环
|
|
1167
|
+
* TTY 模式下每 SPINNER_INTERVAL_MS 毫秒刷新一次面板
|
|
1168
|
+
* 非 TTY 模式下输出状态为 running 的任务的启动信息
|
|
1169
|
+
*/
|
|
1170
|
+
start() {
|
|
1171
|
+
if (this.stopped) return;
|
|
1172
|
+
if (!this.isTTY) {
|
|
1173
|
+
for (const task of this.tasks) {
|
|
1174
|
+
if (task.status === "running") {
|
|
1175
|
+
console.log(MESSAGES.PROGRESS_TASK_STARTED(task.index, this.total, task.branch, task.path));
|
|
1176
|
+
}
|
|
943
1177
|
}
|
|
944
|
-
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
process.stdout.write(CURSOR_HIDE);
|
|
1181
|
+
this.render();
|
|
1182
|
+
this.timer = setInterval(() => {
|
|
1183
|
+
this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
|
|
1184
|
+
this.render();
|
|
1185
|
+
}, SPINNER_INTERVAL_MS);
|
|
1186
|
+
if (this.timer.unref) {
|
|
1187
|
+
this.timer.unref();
|
|
945
1188
|
}
|
|
946
|
-
printInfo(`\u5171 ${worktrees.length} \u4E2A worktree`);
|
|
947
1189
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
}
|
|
956
|
-
function handleCreate(options) {
|
|
957
|
-
validateMainWorktree();
|
|
958
|
-
const count = Number(options.number);
|
|
959
|
-
if (!Number.isInteger(count) || count <= 0) {
|
|
960
|
-
throw new ClawtError(
|
|
961
|
-
MESSAGES.INVALID_COUNT(options.number),
|
|
962
|
-
EXIT_CODES.ARGUMENT_ERROR
|
|
963
|
-
);
|
|
1190
|
+
/**
|
|
1191
|
+
* 更新指定任务的最后活动时间戳
|
|
1192
|
+
* 当 child.stderr 有输出时调用,表示任务仍然活跃
|
|
1193
|
+
* @param {number} index - 任务索引(从 0 开始)
|
|
1194
|
+
*/
|
|
1195
|
+
updateActivity(index) {
|
|
1196
|
+
this.tasks[index].lastActiveAt = Date.now();
|
|
964
1197
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
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) => {
|
|
980
|
-
await handleRemove(options);
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
function resolveWorktreesToRemove(options) {
|
|
984
|
-
const allWorktrees = getProjectWorktrees();
|
|
985
|
-
if (options.all) {
|
|
986
|
-
return allWorktrees;
|
|
1198
|
+
/**
|
|
1199
|
+
* 标记指定任务为运行中状态
|
|
1200
|
+
* 将 pending 任务标记为 running 并设置启动时间戳
|
|
1201
|
+
* @param {number} index - 任务索引(从 0 开始)
|
|
1202
|
+
*/
|
|
1203
|
+
markRunning(index) {
|
|
1204
|
+
const task = this.tasks[index];
|
|
1205
|
+
const now = Date.now();
|
|
1206
|
+
task.status = "running";
|
|
1207
|
+
task.startedAt = now;
|
|
1208
|
+
task.lastActiveAt = now;
|
|
1209
|
+
if (!this.isTTY) {
|
|
1210
|
+
console.log(MESSAGES.PROGRESS_TASK_STARTED(task.index, this.total, task.branch, task.path));
|
|
1211
|
+
}
|
|
987
1212
|
}
|
|
988
|
-
|
|
989
|
-
|
|
1213
|
+
/**
|
|
1214
|
+
* 标记指定任务为完成状态
|
|
1215
|
+
* @param {number} index - 任务索引(从 0 开始)
|
|
1216
|
+
* @param {number} durationMs - 耗时(毫秒)
|
|
1217
|
+
* @param {number} costUsd - 费用(美元)
|
|
1218
|
+
*/
|
|
1219
|
+
markDone(index, durationMs, costUsd) {
|
|
1220
|
+
const task = this.tasks[index];
|
|
1221
|
+
task.status = "done";
|
|
1222
|
+
task.finishedAt = Date.now();
|
|
1223
|
+
task.durationMs = durationMs;
|
|
1224
|
+
task.costUsd = costUsd;
|
|
1225
|
+
if (!this.isTTY) {
|
|
1226
|
+
const duration = formatDuration(durationMs);
|
|
1227
|
+
const cost = `$${costUsd.toFixed(2)}`;
|
|
1228
|
+
console.log(MESSAGES.PROGRESS_TASK_DONE(task.index, this.total, task.branch, duration, cost, task.path));
|
|
1229
|
+
}
|
|
990
1230
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1231
|
+
/**
|
|
1232
|
+
* 标记指定任务为失败状态
|
|
1233
|
+
* @param {number} index - 任务索引(从 0 开始)
|
|
1234
|
+
* @param {number} durationMs - 耗时(毫秒)
|
|
1235
|
+
*/
|
|
1236
|
+
markFailed(index, durationMs) {
|
|
1237
|
+
const task = this.tasks[index];
|
|
1238
|
+
task.status = "failed";
|
|
1239
|
+
task.finishedAt = Date.now();
|
|
1240
|
+
task.durationMs = durationMs;
|
|
1241
|
+
if (!this.isTTY) {
|
|
1242
|
+
const duration = formatDuration(durationMs);
|
|
1243
|
+
console.log(MESSAGES.PROGRESS_TASK_FAILED(task.index, this.total, task.branch, duration, task.path));
|
|
1244
|
+
}
|
|
996
1245
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1246
|
+
/**
|
|
1247
|
+
* 停止渲染循环并恢复光标
|
|
1248
|
+
* 在所有任务完成或 SIGINT 中断时调用
|
|
1249
|
+
*/
|
|
1250
|
+
stop() {
|
|
1251
|
+
if (this.stopped) return;
|
|
1252
|
+
this.stopped = true;
|
|
1253
|
+
if (this.timer) {
|
|
1254
|
+
clearInterval(this.timer);
|
|
1255
|
+
this.timer = null;
|
|
1256
|
+
}
|
|
1257
|
+
if (this.isTTY) {
|
|
1258
|
+
this.render();
|
|
1259
|
+
process.stdout.write(CURSOR_SHOW);
|
|
1260
|
+
}
|
|
1007
1261
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1262
|
+
/**
|
|
1263
|
+
* 执行一次完整的面板渲染
|
|
1264
|
+
* 先回退光标到面板起始位置,再逐行输出
|
|
1265
|
+
*/
|
|
1266
|
+
render() {
|
|
1267
|
+
const maxBranchWidth = getMaxBranchWidth(this.tasks);
|
|
1268
|
+
const spinnerChar = SPINNER_FRAMES[this.frameIndex];
|
|
1269
|
+
const lines = this.tasks.map((task) => renderTaskLine(task, this.total, maxBranchWidth, spinnerChar));
|
|
1270
|
+
if (this.hasPendingTasks) {
|
|
1271
|
+
lines.push(renderSummaryLine(this.tasks, this.total));
|
|
1272
|
+
}
|
|
1273
|
+
if (this.renderedLineCount > 0) {
|
|
1274
|
+
process.stdout.write(CURSOR_UP(this.renderedLineCount));
|
|
1275
|
+
}
|
|
1276
|
+
for (const line of lines) {
|
|
1277
|
+
process.stdout.write(`${line}${CLEAR_LINE}
|
|
1278
|
+
`);
|
|
1279
|
+
}
|
|
1280
|
+
this.renderedLineCount = lines.length;
|
|
1017
1281
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1282
|
+
};
|
|
1283
|
+
|
|
1284
|
+
// src/utils/task-file.ts
|
|
1285
|
+
import { resolve } from "path";
|
|
1286
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
1287
|
+
var TASK_BLOCK_REGEX = /<!-- CLAWT-TASKS:START -->([\s\S]*?)<!-- CLAWT-TASKS:END -->/g;
|
|
1288
|
+
var BRANCH_LINE_REGEX = /^#\s*branch:\s*(.+)$/;
|
|
1289
|
+
function parseTaskFile(content, options) {
|
|
1290
|
+
const branchRequired = options?.branchRequired ?? true;
|
|
1291
|
+
const entries = [];
|
|
1292
|
+
let match;
|
|
1293
|
+
let blockIndex = 0;
|
|
1294
|
+
TASK_BLOCK_REGEX.lastIndex = 0;
|
|
1295
|
+
while ((match = TASK_BLOCK_REGEX.exec(content)) !== null) {
|
|
1296
|
+
blockIndex++;
|
|
1297
|
+
const blockContent = match[1].trim();
|
|
1298
|
+
const lines = blockContent.split("\n");
|
|
1299
|
+
let branch;
|
|
1300
|
+
const taskLines = [];
|
|
1301
|
+
for (const line of lines) {
|
|
1302
|
+
const branchMatch = line.trim().match(BRANCH_LINE_REGEX);
|
|
1303
|
+
if (branchMatch) {
|
|
1304
|
+
branch = branchMatch[1].trim();
|
|
1305
|
+
} else {
|
|
1306
|
+
taskLines.push(line);
|
|
1024
1307
|
}
|
|
1025
|
-
removeSnapshot(projectName, wt.branch);
|
|
1026
|
-
printSuccess(MESSAGES.WORKTREE_REMOVED(wt.path));
|
|
1027
|
-
} catch (error) {
|
|
1028
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1029
|
-
logger.error(`\u79FB\u9664 worktree \u5931\u8D25: ${wt.path} - ${error}`);
|
|
1030
|
-
failures.push({ path: wt.path, error: errorMessage });
|
|
1031
1308
|
}
|
|
1309
|
+
if (branchRequired && !branch) {
|
|
1310
|
+
throw new ClawtError(MESSAGES.TASK_FILE_MISSING_BRANCH(blockIndex));
|
|
1311
|
+
}
|
|
1312
|
+
const task = taskLines.join("\n").trim();
|
|
1313
|
+
if (!task) {
|
|
1314
|
+
throw new ClawtError(
|
|
1315
|
+
branch ? MESSAGES.TASK_FILE_MISSING_TASK(branch) : MESSAGES.TASK_FILE_MISSING_TASK_BY_INDEX(blockIndex)
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
entries.push({ branch, task });
|
|
1032
1319
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1320
|
+
return entries;
|
|
1321
|
+
}
|
|
1322
|
+
function loadTaskFile(filePath, options) {
|
|
1323
|
+
const absolutePath = resolve(filePath);
|
|
1324
|
+
if (!existsSync6(absolutePath)) {
|
|
1325
|
+
throw new ClawtError(MESSAGES.TASK_FILE_NOT_FOUND(absolutePath));
|
|
1035
1326
|
}
|
|
1036
|
-
|
|
1037
|
-
const
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
printError(MESSAGES.REMOVE_PARTIAL_FAILURE(failures));
|
|
1041
|
-
throw new ClawtError(
|
|
1042
|
-
`${failures.length} \u4E2A worktree \u79FB\u9664\u5931\u8D25`
|
|
1043
|
-
);
|
|
1327
|
+
const content = readFileSync3(absolutePath, "utf-8");
|
|
1328
|
+
const entries = parseTaskFile(content, options);
|
|
1329
|
+
if (entries.length === 0) {
|
|
1330
|
+
throw new ClawtError(MESSAGES.TASK_FILE_EMPTY);
|
|
1044
1331
|
}
|
|
1332
|
+
return entries;
|
|
1045
1333
|
}
|
|
1046
1334
|
|
|
1047
|
-
// src/
|
|
1048
|
-
function registerRunCommand(program2) {
|
|
1049
|
-
program2.command("run").description("\u6279\u91CF\u521B\u5EFA worktree \u5E76\u542F\u52A8 Claude Code \u6267\u884C\u4EFB\u52A1").requiredOption("-b, --branch <branchName>", "\u5206\u652F\u540D").option("--tasks <task...>", "\u4EFB\u52A1\u5217\u8868\uFF08\u53EF\u591A\u6B21\u6307\u5B9A\uFF09\uFF0C\u4E0D\u4F20\u5219\u5728 worktree \u4E2D\u6253\u5F00 Claude Code \u4EA4\u4E92\u5F0F\u754C\u9762").action(async (options) => {
|
|
1050
|
-
await handleRun(options);
|
|
1051
|
-
});
|
|
1052
|
-
}
|
|
1335
|
+
// src/utils/task-executor.ts
|
|
1053
1336
|
function executeClaudeTask(worktree, task) {
|
|
1054
1337
|
const child = spawnProcess(
|
|
1055
1338
|
"claude",
|
|
@@ -1063,7 +1346,7 @@ function executeClaudeTask(worktree, task) {
|
|
|
1063
1346
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1064
1347
|
}
|
|
1065
1348
|
);
|
|
1066
|
-
const promise = new Promise((
|
|
1349
|
+
const promise = new Promise((resolve2) => {
|
|
1067
1350
|
let stdout = "";
|
|
1068
1351
|
let stderr = "";
|
|
1069
1352
|
child.stdout?.on("data", (data) => {
|
|
@@ -1083,7 +1366,7 @@ function executeClaudeTask(worktree, task) {
|
|
|
1083
1366
|
} catch {
|
|
1084
1367
|
logger.warn(`\u89E3\u6790 Claude Code \u8F93\u51FA\u5931\u8D25: ${stdout.substring(0, 200)}`);
|
|
1085
1368
|
}
|
|
1086
|
-
|
|
1369
|
+
resolve2({
|
|
1087
1370
|
task,
|
|
1088
1371
|
branch: worktree.branch,
|
|
1089
1372
|
worktreePath: worktree.path,
|
|
@@ -1093,7 +1376,7 @@ function executeClaudeTask(worktree, task) {
|
|
|
1093
1376
|
});
|
|
1094
1377
|
});
|
|
1095
1378
|
child.on("error", (err) => {
|
|
1096
|
-
|
|
1379
|
+
resolve2({
|
|
1097
1380
|
task,
|
|
1098
1381
|
branch: worktree.branch,
|
|
1099
1382
|
worktreePath: worktree.path,
|
|
@@ -1105,24 +1388,6 @@ function executeClaudeTask(worktree, task) {
|
|
|
1105
1388
|
});
|
|
1106
1389
|
return { child, promise };
|
|
1107
1390
|
}
|
|
1108
|
-
function printTaskNotification(taskResult) {
|
|
1109
|
-
const { success, worktreePath, branch, result } = taskResult;
|
|
1110
|
-
const status = success ? "\u5B8C\u6210" : "\u5931\u8D25";
|
|
1111
|
-
const icon = success ? "\u2713" : "\u2717";
|
|
1112
|
-
const durationStr = result ? `${(result.duration_ms / 1e3).toFixed(1)}s` : "N/A";
|
|
1113
|
-
const costStr = result ? `$${result.total_cost_usd.toFixed(2)}` : "N/A";
|
|
1114
|
-
const resultStr = success ? "success" : "failed";
|
|
1115
|
-
if (success) {
|
|
1116
|
-
printSuccess(`${icon} [${status}] worktree: ${worktreePath}`);
|
|
1117
|
-
} else {
|
|
1118
|
-
printError(`${icon} [${status}] worktree: ${worktreePath}`);
|
|
1119
|
-
}
|
|
1120
|
-
printInfo(` \u5206\u652F: ${branch}`);
|
|
1121
|
-
printInfo(` \u8017\u65F6: ${durationStr}`);
|
|
1122
|
-
printInfo(` \u82B1\u8D39: ${costStr}`);
|
|
1123
|
-
printInfo(` \u7ED3\u679C: ${resultStr}`);
|
|
1124
|
-
printSeparator();
|
|
1125
|
-
}
|
|
1126
1391
|
function printTaskSummary(summary) {
|
|
1127
1392
|
printDoubleSeparator();
|
|
1128
1393
|
printInfo(`\u5168\u90E8\u4EFB\u52A1\u5DF2\u5B8C\u6210 (${summary.total}/${summary.total})`);
|
|
@@ -1147,60 +1412,117 @@ async function handleInterruptCleanup(worktrees) {
|
|
|
1147
1412
|
printInfo(MESSAGES.INTERRUPT_KEPT);
|
|
1148
1413
|
}
|
|
1149
1414
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
return;
|
|
1163
|
-
}
|
|
1164
|
-
const tasks = options.tasks.map((t) => t.trim()).filter(Boolean);
|
|
1165
|
-
if (tasks.length === 0) {
|
|
1166
|
-
throw new ClawtError("\u4EFB\u52A1\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A");
|
|
1167
|
-
}
|
|
1168
|
-
const count = tasks.length;
|
|
1169
|
-
logger.info(`run \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}\uFF0C\u4EFB\u52A1\u6570: ${count}`);
|
|
1170
|
-
const worktrees = createWorktrees(options.branch, count);
|
|
1171
|
-
printSuccess(MESSAGES.WORKTREE_CREATED(worktrees.length));
|
|
1172
|
-
for (const wt of worktrees) {
|
|
1173
|
-
printInfo(` \u5206\u652F: ${wt.branch} \u8DEF\u5F84: ${wt.path}`);
|
|
1415
|
+
function updateRendererStatus(renderer, index, result, startTime) {
|
|
1416
|
+
if (result.success) {
|
|
1417
|
+
renderer.markDone(
|
|
1418
|
+
index,
|
|
1419
|
+
result.result?.duration_ms ?? Date.now() - startTime,
|
|
1420
|
+
result.result?.total_cost_usd ?? 0
|
|
1421
|
+
);
|
|
1422
|
+
} else {
|
|
1423
|
+
renderer.markFailed(
|
|
1424
|
+
index,
|
|
1425
|
+
result.result?.duration_ms ?? Date.now() - startTime
|
|
1426
|
+
);
|
|
1174
1427
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1428
|
+
}
|
|
1429
|
+
async function executeWithConcurrency(worktrees, tasks, concurrency, renderer, startTime, isInterrupted, childProcesses) {
|
|
1430
|
+
const total = tasks.length;
|
|
1431
|
+
const results = new Array(total);
|
|
1432
|
+
let nextIndex = 0;
|
|
1433
|
+
let completedCount = 0;
|
|
1434
|
+
return new Promise((resolve2) => {
|
|
1435
|
+
function launchNext() {
|
|
1436
|
+
if (nextIndex >= total || isInterrupted()) return;
|
|
1437
|
+
const index = nextIndex;
|
|
1438
|
+
nextIndex++;
|
|
1439
|
+
const wt = worktrees[index];
|
|
1440
|
+
const task = tasks[index];
|
|
1441
|
+
logger.info(`\u542F\u52A8\u4EFB\u52A1 ${index + 1}: ${task} (worktree: ${wt.path})`);
|
|
1442
|
+
renderer.markRunning(index);
|
|
1443
|
+
const handle = executeClaudeTask(wt, task);
|
|
1444
|
+
childProcesses.push(handle.child);
|
|
1445
|
+
handle.child.stderr?.on("data", () => {
|
|
1446
|
+
renderer.updateActivity(index);
|
|
1447
|
+
});
|
|
1448
|
+
handle.promise.then((result) => {
|
|
1449
|
+
results[index] = result;
|
|
1450
|
+
completedCount++;
|
|
1451
|
+
if (!isInterrupted()) {
|
|
1452
|
+
updateRendererStatus(renderer, index, result, startTime);
|
|
1453
|
+
}
|
|
1454
|
+
launchNext();
|
|
1455
|
+
if (completedCount === total) {
|
|
1456
|
+
resolve2(results);
|
|
1457
|
+
}
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
const initialBatch = Math.min(concurrency, total);
|
|
1461
|
+
for (let i = 0; i < initialBatch; i++) {
|
|
1462
|
+
launchNext();
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
async function executeAllParallel(worktrees, tasks, renderer, startTime, isInterrupted, childProcesses) {
|
|
1177
1467
|
const handles = worktrees.map((wt, index) => {
|
|
1178
1468
|
const task = tasks[index];
|
|
1179
1469
|
logger.info(`\u542F\u52A8\u4EFB\u52A1 ${index + 1}: ${task} (worktree: ${wt.path})`);
|
|
1180
|
-
|
|
1470
|
+
const handle = executeClaudeTask(wt, task);
|
|
1471
|
+
childProcesses.push(handle.child);
|
|
1472
|
+
handle.child.stderr?.on("data", () => {
|
|
1473
|
+
renderer.updateActivity(index);
|
|
1474
|
+
});
|
|
1475
|
+
return handle;
|
|
1181
1476
|
});
|
|
1182
|
-
const
|
|
1477
|
+
const results = await Promise.all(
|
|
1478
|
+
handles.map(
|
|
1479
|
+
(handle, index) => handle.promise.then((result) => {
|
|
1480
|
+
if (!isInterrupted()) {
|
|
1481
|
+
updateRendererStatus(renderer, index, result, startTime);
|
|
1482
|
+
}
|
|
1483
|
+
return result;
|
|
1484
|
+
})
|
|
1485
|
+
)
|
|
1486
|
+
);
|
|
1487
|
+
return results;
|
|
1488
|
+
}
|
|
1489
|
+
async function executeBatchTasks(worktrees, tasks, concurrency) {
|
|
1490
|
+
const count = tasks.length;
|
|
1491
|
+
if (concurrency > 0) {
|
|
1492
|
+
printInfo(MESSAGES.CONCURRENCY_INFO(concurrency, count));
|
|
1493
|
+
printInfo("");
|
|
1494
|
+
}
|
|
1495
|
+
const startTime = Date.now();
|
|
1496
|
+
const branches = worktrees.map((wt) => wt.branch);
|
|
1497
|
+
const paths = worktrees.map((wt) => wt.path);
|
|
1498
|
+
const allRunning = concurrency === 0;
|
|
1499
|
+
const renderer = new ProgressRenderer(branches, paths, allRunning);
|
|
1500
|
+
renderer.start();
|
|
1183
1501
|
let interrupted = false;
|
|
1502
|
+
const isInterrupted = () => interrupted;
|
|
1503
|
+
const childProcesses = [];
|
|
1184
1504
|
const sigintHandler = async () => {
|
|
1185
1505
|
if (interrupted) return;
|
|
1186
1506
|
interrupted = true;
|
|
1507
|
+
renderer.stop();
|
|
1187
1508
|
printInfo("");
|
|
1188
1509
|
printWarning(MESSAGES.INTERRUPTED);
|
|
1189
1510
|
killAllChildProcesses(childProcesses);
|
|
1190
|
-
await Promise.allSettled(
|
|
1511
|
+
await Promise.allSettled(childProcesses.map(
|
|
1512
|
+
(cp) => new Promise((resolve2) => {
|
|
1513
|
+
if (cp.exitCode !== null) {
|
|
1514
|
+
resolve2();
|
|
1515
|
+
} else {
|
|
1516
|
+
cp.on("close", () => resolve2());
|
|
1517
|
+
}
|
|
1518
|
+
})
|
|
1519
|
+
));
|
|
1191
1520
|
await handleInterruptCleanup(worktrees);
|
|
1192
1521
|
process.exit(1);
|
|
1193
1522
|
};
|
|
1194
1523
|
process.on("SIGINT", sigintHandler);
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1197
|
-
if (!interrupted) {
|
|
1198
|
-
printTaskNotification(result);
|
|
1199
|
-
}
|
|
1200
|
-
return result;
|
|
1201
|
-
})
|
|
1202
|
-
);
|
|
1203
|
-
const results = await Promise.all(taskPromises);
|
|
1524
|
+
const results = concurrency > 0 ? await executeWithConcurrency(worktrees, tasks, concurrency, renderer, startTime, isInterrupted, childProcesses) : await executeAllParallel(worktrees, tasks, renderer, startTime, isInterrupted, childProcesses);
|
|
1525
|
+
renderer.stop();
|
|
1204
1526
|
process.removeListener("SIGINT", sigintHandler);
|
|
1205
1527
|
if (interrupted) return;
|
|
1206
1528
|
const totalDurationMs = Date.now() - startTime;
|
|
@@ -1214,6 +1536,216 @@ async function handleRun(options) {
|
|
|
1214
1536
|
printTaskSummary(summary);
|
|
1215
1537
|
}
|
|
1216
1538
|
|
|
1539
|
+
// src/commands/list.ts
|
|
1540
|
+
import chalk4 from "chalk";
|
|
1541
|
+
function registerListCommand(program2) {
|
|
1542
|
+
program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
1543
|
+
handleList(options);
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
function handleList(options) {
|
|
1547
|
+
validateMainWorktree();
|
|
1548
|
+
const projectName = getProjectName();
|
|
1549
|
+
const worktrees = getProjectWorktrees();
|
|
1550
|
+
logger.info(`list \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}\uFF0C\u5171 ${worktrees.length} \u4E2A worktree`);
|
|
1551
|
+
if (options.json) {
|
|
1552
|
+
printListAsJson(projectName, worktrees);
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
printListAsText(projectName, worktrees);
|
|
1556
|
+
}
|
|
1557
|
+
function printListAsJson(projectName, worktrees) {
|
|
1558
|
+
const result = {
|
|
1559
|
+
project: projectName,
|
|
1560
|
+
total: worktrees.length,
|
|
1561
|
+
worktrees: worktrees.map((wt) => ({
|
|
1562
|
+
path: wt.path,
|
|
1563
|
+
branch: wt.branch
|
|
1564
|
+
}))
|
|
1565
|
+
};
|
|
1566
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1567
|
+
}
|
|
1568
|
+
function printListAsText(projectName, worktrees) {
|
|
1569
|
+
printInfo(`\u5F53\u524D\u9879\u76EE: ${projectName}
|
|
1570
|
+
`);
|
|
1571
|
+
if (worktrees.length === 0) {
|
|
1572
|
+
printInfo(` ${MESSAGES.NO_WORKTREES}`);
|
|
1573
|
+
} else {
|
|
1574
|
+
for (const wt of worktrees) {
|
|
1575
|
+
const status = getWorktreeStatus(wt);
|
|
1576
|
+
const isIdle = status ? isWorktreeIdle(status) : false;
|
|
1577
|
+
const pathDisplay = isIdle ? chalk4.hex("#FF8C00")(wt.path) : wt.path;
|
|
1578
|
+
printInfo(` ${pathDisplay} [${wt.branch}]`);
|
|
1579
|
+
if (status) {
|
|
1580
|
+
printInfo(` ${formatWorktreeStatus(status)}`);
|
|
1581
|
+
} else {
|
|
1582
|
+
printInfo(` ${chalk4.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
|
|
1583
|
+
}
|
|
1584
|
+
printInfo("");
|
|
1585
|
+
}
|
|
1586
|
+
printInfo(`\u5171 ${worktrees.length} \u4E2A worktree`);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// src/commands/create.ts
|
|
1591
|
+
function registerCreateCommand(program2) {
|
|
1592
|
+
program2.command("create").description("\u6279\u91CF\u521B\u5EFA worktree \u53CA\u5BF9\u5E94\u5206\u652F").requiredOption("-b, --branch <branchName>", "\u5206\u652F\u540D").option("-n, --number <count>", "\u521B\u5EFA\u6570\u91CF", "1").action((options) => {
|
|
1593
|
+
handleCreate(options);
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
function handleCreate(options) {
|
|
1597
|
+
validateMainWorktree();
|
|
1598
|
+
const count = Number(options.number);
|
|
1599
|
+
if (!Number.isInteger(count) || count <= 0) {
|
|
1600
|
+
throw new ClawtError(
|
|
1601
|
+
MESSAGES.INVALID_COUNT(options.number),
|
|
1602
|
+
EXIT_CODES.ARGUMENT_ERROR
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
logger.info(`create \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}\uFF0C\u6570\u91CF: ${count}`);
|
|
1606
|
+
const worktrees = createWorktrees(options.branch, count);
|
|
1607
|
+
printSuccess(MESSAGES.WORKTREE_CREATED(worktrees.length));
|
|
1608
|
+
printInfo("");
|
|
1609
|
+
worktrees.forEach((wt, index) => {
|
|
1610
|
+
printInfo(`\u76EE\u5F55\u8DEF\u5F84${index + 1}\uFF1A`);
|
|
1611
|
+
printInfo(` ${wt.path}`);
|
|
1612
|
+
printInfo(` \u5206\u652F\u540D: ${wt.branch}`);
|
|
1613
|
+
printSeparator();
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
// src/commands/remove.ts
|
|
1618
|
+
var REMOVE_RESOLVE_MESSAGES = {
|
|
1619
|
+
noWorktrees: MESSAGES.REMOVE_NO_WORKTREES,
|
|
1620
|
+
selectBranch: MESSAGES.REMOVE_SELECT_BRANCH,
|
|
1621
|
+
multipleMatches: MESSAGES.REMOVE_MULTIPLE_MATCHES,
|
|
1622
|
+
noMatch: MESSAGES.REMOVE_NO_MATCH
|
|
1623
|
+
};
|
|
1624
|
+
function registerRemoveCommand(program2) {
|
|
1625
|
+
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) => {
|
|
1626
|
+
await handleRemove(options);
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
async function handleRemove(options) {
|
|
1630
|
+
validateMainWorktree();
|
|
1631
|
+
const projectName = getProjectName();
|
|
1632
|
+
logger.info(`remove \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}`);
|
|
1633
|
+
const allWorktrees = getProjectWorktrees();
|
|
1634
|
+
let worktreesToRemove;
|
|
1635
|
+
if (options.all) {
|
|
1636
|
+
worktreesToRemove = allWorktrees;
|
|
1637
|
+
} else {
|
|
1638
|
+
worktreesToRemove = await resolveTargetWorktrees(allWorktrees, REMOVE_RESOLVE_MESSAGES, options.branch);
|
|
1639
|
+
}
|
|
1640
|
+
if (worktreesToRemove.length === 0) {
|
|
1641
|
+
printInfo(MESSAGES.NO_WORKTREES);
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
printInfo("\u5373\u5C06\u79FB\u9664\u4EE5\u4E0B worktree \u53CA\u672C\u5730\u5206\u652F\uFF1A\n");
|
|
1645
|
+
worktreesToRemove.forEach((wt, index) => {
|
|
1646
|
+
printInfo(` ${index + 1}. ${wt.path} \u2192 \u5206\u652F: ${wt.branch}`);
|
|
1647
|
+
});
|
|
1648
|
+
printInfo("");
|
|
1649
|
+
const autoDelete = getConfigValue("autoDeleteBranch");
|
|
1650
|
+
let shouldDeleteBranch = autoDelete;
|
|
1651
|
+
if (!autoDelete) {
|
|
1652
|
+
shouldDeleteBranch = await confirmAction("\u662F\u5426\u540C\u65F6\u5220\u9664\u5BF9\u5E94\u7684\u672C\u5730\u5206\u652F\uFF1F");
|
|
1653
|
+
}
|
|
1654
|
+
const failures = [];
|
|
1655
|
+
for (const wt of worktreesToRemove) {
|
|
1656
|
+
try {
|
|
1657
|
+
removeWorktreeByPath(wt.path);
|
|
1658
|
+
if (shouldDeleteBranch) {
|
|
1659
|
+
deleteBranch(wt.branch);
|
|
1660
|
+
}
|
|
1661
|
+
removeSnapshot(projectName, wt.branch);
|
|
1662
|
+
printSuccess(MESSAGES.WORKTREE_REMOVED(wt.path));
|
|
1663
|
+
} catch (error) {
|
|
1664
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1665
|
+
logger.error(`\u79FB\u9664 worktree \u5931\u8D25: ${wt.path} - ${error}`);
|
|
1666
|
+
failures.push({ path: wt.path, error: errorMessage });
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
if (options.all) {
|
|
1670
|
+
removeProjectSnapshots(projectName);
|
|
1671
|
+
}
|
|
1672
|
+
gitWorktreePrune();
|
|
1673
|
+
const projectDir = getProjectWorktreeDir();
|
|
1674
|
+
removeEmptyDir(projectDir);
|
|
1675
|
+
if (failures.length > 0) {
|
|
1676
|
+
printError(MESSAGES.REMOVE_PARTIAL_FAILURE(failures));
|
|
1677
|
+
throw new ClawtError(
|
|
1678
|
+
`${failures.length} \u4E2A worktree \u79FB\u9664\u5931\u8D25`
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
// src/commands/run.ts
|
|
1684
|
+
function registerRunCommand(program2) {
|
|
1685
|
+
program2.command("run").description("\u6279\u91CF\u521B\u5EFA worktree \u5E76\u542F\u52A8 Claude Code \u6267\u884C\u4EFB\u52A1").option("-b, --branch <branchName>", "\u5206\u652F\u540D").option("--tasks <task...>", "\u4EFB\u52A1\u5217\u8868\uFF08\u53EF\u591A\u6B21\u6307\u5B9A\uFF09\uFF0C\u4E0D\u4F20\u5219\u5728 worktree \u4E2D\u6253\u5F00 Claude Code \u4EA4\u4E92\u5F0F\u754C\u9762").option("-c, --concurrency <n>", "\u6700\u5927\u5E76\u53D1\u6570\uFF0C0 \u8868\u793A\u4E0D\u9650\u5236").option("-f, --file <path>", "\u4ECE\u4EFB\u52A1\u6587\u4EF6\u8BFB\u53D6\u4EFB\u52A1\u5217\u8868\uFF08\u4E0E --tasks \u4E92\u65A5\uFF09").action(async (options) => {
|
|
1686
|
+
await handleRun(options);
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
function parseConcurrency(optionValue, configValue) {
|
|
1690
|
+
if (optionValue === void 0) {
|
|
1691
|
+
return configValue;
|
|
1692
|
+
}
|
|
1693
|
+
const parsed = parseInt(optionValue, 10);
|
|
1694
|
+
if (Number.isNaN(parsed) || parsed < 0) {
|
|
1695
|
+
throw new ClawtError(MESSAGES.CONCURRENCY_INVALID);
|
|
1696
|
+
}
|
|
1697
|
+
return parsed;
|
|
1698
|
+
}
|
|
1699
|
+
async function handleRunFromFile(options) {
|
|
1700
|
+
const branchRequired = !options.branch;
|
|
1701
|
+
const entries = loadTaskFile(options.file, { branchRequired });
|
|
1702
|
+
printSuccess(MESSAGES.TASK_FILE_LOADED(entries.length, options.file));
|
|
1703
|
+
const tasks = entries.map((e) => e.task);
|
|
1704
|
+
let worktrees;
|
|
1705
|
+
if (options.branch) {
|
|
1706
|
+
worktrees = createWorktrees(options.branch, entries.length);
|
|
1707
|
+
} else {
|
|
1708
|
+
const branches = entries.map((e) => sanitizeBranchName(e.branch));
|
|
1709
|
+
worktrees = createWorktreesByBranches(branches);
|
|
1710
|
+
}
|
|
1711
|
+
const concurrency = parseConcurrency(options.concurrency, getConfigValue("maxConcurrency"));
|
|
1712
|
+
logger.info(`run \u547D\u4EE4\uFF08\u6587\u4EF6\u6A21\u5F0F\uFF09\u6267\u884C\uFF0C\u4EFB\u52A1\u6570: ${entries.length}\uFF0C\u5E76\u53D1\u6570: ${concurrency || "\u4E0D\u9650\u5236"}`);
|
|
1713
|
+
await executeBatchTasks(worktrees, tasks, concurrency);
|
|
1714
|
+
}
|
|
1715
|
+
async function handleRun(options) {
|
|
1716
|
+
validateMainWorktree();
|
|
1717
|
+
validateClaudeCodeInstalled();
|
|
1718
|
+
if (options.file && options.tasks) {
|
|
1719
|
+
throw new ClawtError(MESSAGES.FILE_AND_TASKS_CONFLICT);
|
|
1720
|
+
}
|
|
1721
|
+
if (options.file) {
|
|
1722
|
+
return handleRunFromFile(options);
|
|
1723
|
+
}
|
|
1724
|
+
if (!options.branch) {
|
|
1725
|
+
throw new ClawtError(MESSAGES.BRANCH_OR_FILE_REQUIRED);
|
|
1726
|
+
}
|
|
1727
|
+
if (!options.tasks || options.tasks.length === 0) {
|
|
1728
|
+
const sanitized = sanitizeBranchName(options.branch);
|
|
1729
|
+
if (checkBranchExists(sanitized)) {
|
|
1730
|
+
throw new ClawtError(MESSAGES.BRANCH_EXISTS_USE_RESUME(sanitized));
|
|
1731
|
+
}
|
|
1732
|
+
const worktrees2 = createWorktrees(options.branch, 1);
|
|
1733
|
+
const worktree = worktrees2[0];
|
|
1734
|
+
printSuccess(MESSAGES.WORKTREE_CREATED(1));
|
|
1735
|
+
launchInteractiveClaude(worktree);
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
const tasks = options.tasks.map((t) => t.trim()).filter(Boolean);
|
|
1739
|
+
if (tasks.length === 0) {
|
|
1740
|
+
throw new ClawtError("\u4EFB\u52A1\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A");
|
|
1741
|
+
}
|
|
1742
|
+
const count = tasks.length;
|
|
1743
|
+
const concurrency = parseConcurrency(options.concurrency, getConfigValue("maxConcurrency"));
|
|
1744
|
+
logger.info(`run \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}\uFF0C\u4EFB\u52A1\u6570: ${count}\uFF0C\u5E76\u53D1\u6570: ${concurrency || "\u4E0D\u9650\u5236"}`);
|
|
1745
|
+
const worktrees = createWorktrees(options.branch, count);
|
|
1746
|
+
await executeBatchTasks(worktrees, tasks, concurrency);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1217
1749
|
// src/commands/resume.ts
|
|
1218
1750
|
var RESUME_RESOLVE_MESSAGES = {
|
|
1219
1751
|
noWorktrees: MESSAGES.RESUME_NO_WORKTREES,
|
|
@@ -1538,7 +2070,7 @@ async function handleMerge(options) {
|
|
|
1538
2070
|
}
|
|
1539
2071
|
|
|
1540
2072
|
// src/commands/config.ts
|
|
1541
|
-
import
|
|
2073
|
+
import chalk5 from "chalk";
|
|
1542
2074
|
function registerConfigCommand(program2) {
|
|
1543
2075
|
const configCmd = program2.command("config").description("\u67E5\u770B\u548C\u7BA1\u7406\u5168\u5C40\u914D\u7F6E").action(() => {
|
|
1544
2076
|
handleConfig();
|
|
@@ -1551,7 +2083,7 @@ function handleConfig() {
|
|
|
1551
2083
|
const config = loadConfig();
|
|
1552
2084
|
logger.info("config \u547D\u4EE4\u6267\u884C\uFF0C\u5C55\u793A\u5168\u5C40\u914D\u7F6E");
|
|
1553
2085
|
printInfo(`
|
|
1554
|
-
${
|
|
2086
|
+
${chalk5.dim("\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84:")} ${CONFIG_PATH}
|
|
1555
2087
|
`);
|
|
1556
2088
|
printSeparator();
|
|
1557
2089
|
const keys = Object.keys(DEFAULT_CONFIG);
|
|
@@ -1561,8 +2093,8 @@ ${chalk4.dim("\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84:")} ${CONFIG_PATH}
|
|
|
1561
2093
|
const description = CONFIG_DESCRIPTIONS[key];
|
|
1562
2094
|
const formattedValue = formatConfigValue(value);
|
|
1563
2095
|
if (i === 0) printInfo("");
|
|
1564
|
-
printInfo(` ${
|
|
1565
|
-
printInfo(` ${
|
|
2096
|
+
printInfo(` ${chalk5.bold(key)}: ${formattedValue}`);
|
|
2097
|
+
printInfo(` ${chalk5.dim(description)}`);
|
|
1566
2098
|
printInfo("");
|
|
1567
2099
|
}
|
|
1568
2100
|
printSeparator();
|
|
@@ -1582,9 +2114,9 @@ async function handleConfigReset() {
|
|
|
1582
2114
|
}
|
|
1583
2115
|
function formatConfigValue(value) {
|
|
1584
2116
|
if (typeof value === "boolean") {
|
|
1585
|
-
return value ?
|
|
2117
|
+
return value ? chalk5.green("true") : chalk5.yellow("false");
|
|
1586
2118
|
}
|
|
1587
|
-
return
|
|
2119
|
+
return chalk5.cyan(String(value));
|
|
1588
2120
|
}
|
|
1589
2121
|
|
|
1590
2122
|
// src/commands/sync.ts
|
|
@@ -1671,7 +2203,7 @@ async function handleReset() {
|
|
|
1671
2203
|
}
|
|
1672
2204
|
|
|
1673
2205
|
// src/commands/status.ts
|
|
1674
|
-
import
|
|
2206
|
+
import chalk6 from "chalk";
|
|
1675
2207
|
function registerStatusCommand(program2) {
|
|
1676
2208
|
program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
1677
2209
|
handleStatus(options);
|
|
@@ -1767,7 +2299,7 @@ function printStatusAsJson(result) {
|
|
|
1767
2299
|
}
|
|
1768
2300
|
function printStatusAsText(result) {
|
|
1769
2301
|
printDoubleSeparator();
|
|
1770
|
-
printInfo(` ${
|
|
2302
|
+
printInfo(` ${chalk6.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
|
|
1771
2303
|
printDoubleSeparator();
|
|
1772
2304
|
printInfo("");
|
|
1773
2305
|
printMainSection(result.main);
|
|
@@ -1780,17 +2312,17 @@ function printStatusAsText(result) {
|
|
|
1780
2312
|
printDoubleSeparator();
|
|
1781
2313
|
}
|
|
1782
2314
|
function printMainSection(main) {
|
|
1783
|
-
printInfo(` ${
|
|
1784
|
-
printInfo(` \u5206\u652F: ${
|
|
2315
|
+
printInfo(` ${chalk6.bold("\u25C6")} ${chalk6.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
|
|
2316
|
+
printInfo(` \u5206\u652F: ${chalk6.bold(main.branch)}`);
|
|
1785
2317
|
if (main.isClean) {
|
|
1786
|
-
printInfo(` \u72B6\u6001: ${
|
|
2318
|
+
printInfo(` \u72B6\u6001: ${chalk6.green("\u2713 \u5E72\u51C0")}`);
|
|
1787
2319
|
} else {
|
|
1788
|
-
printInfo(` \u72B6\u6001: ${
|
|
2320
|
+
printInfo(` \u72B6\u6001: ${chalk6.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
|
|
1789
2321
|
}
|
|
1790
2322
|
printInfo("");
|
|
1791
2323
|
}
|
|
1792
2324
|
function printWorktreesSection(worktrees, total) {
|
|
1793
|
-
printInfo(` ${
|
|
2325
|
+
printInfo(` ${chalk6.bold("\u25C6")} ${chalk6.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
|
|
1794
2326
|
printInfo("");
|
|
1795
2327
|
if (worktrees.length === 0) {
|
|
1796
2328
|
printInfo(` ${MESSAGES.STATUS_NO_WORKTREES}`);
|
|
@@ -1802,39 +2334,39 @@ function printWorktreesSection(worktrees, total) {
|
|
|
1802
2334
|
}
|
|
1803
2335
|
function printWorktreeItem(wt) {
|
|
1804
2336
|
const statusLabel = formatChangeStatusLabel(wt.changeStatus);
|
|
1805
|
-
printInfo(` ${
|
|
2337
|
+
printInfo(` ${chalk6.bold("\u25CF")} ${chalk6.bold(wt.branch)} [${statusLabel}]`);
|
|
1806
2338
|
const parts = [];
|
|
1807
2339
|
if (wt.insertions > 0 || wt.deletions > 0) {
|
|
1808
|
-
parts.push(`${
|
|
2340
|
+
parts.push(`${chalk6.green(`+${wt.insertions}`)} ${chalk6.red(`-${wt.deletions}`)}`);
|
|
1809
2341
|
}
|
|
1810
2342
|
if (wt.commitsAhead > 0) {
|
|
1811
|
-
parts.push(
|
|
2343
|
+
parts.push(chalk6.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`));
|
|
1812
2344
|
}
|
|
1813
2345
|
if (wt.commitsBehind > 0) {
|
|
1814
|
-
parts.push(
|
|
2346
|
+
parts.push(chalk6.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`));
|
|
1815
2347
|
} else {
|
|
1816
|
-
parts.push(
|
|
2348
|
+
parts.push(chalk6.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65"));
|
|
1817
2349
|
}
|
|
1818
2350
|
printInfo(` ${parts.join(" ")}`);
|
|
1819
2351
|
if (wt.hasSnapshot) {
|
|
1820
|
-
printInfo(` ${
|
|
2352
|
+
printInfo(` ${chalk6.blue("\u6709 validate \u5FEB\u7167")}`);
|
|
1821
2353
|
}
|
|
1822
2354
|
printInfo("");
|
|
1823
2355
|
}
|
|
1824
2356
|
function formatChangeStatusLabel(status) {
|
|
1825
2357
|
switch (status) {
|
|
1826
2358
|
case "committed":
|
|
1827
|
-
return
|
|
2359
|
+
return chalk6.green(MESSAGES.STATUS_CHANGE_COMMITTED);
|
|
1828
2360
|
case "uncommitted":
|
|
1829
|
-
return
|
|
2361
|
+
return chalk6.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
|
|
1830
2362
|
case "conflict":
|
|
1831
|
-
return
|
|
2363
|
+
return chalk6.red(MESSAGES.STATUS_CHANGE_CONFLICT);
|
|
1832
2364
|
case "clean":
|
|
1833
|
-
return
|
|
2365
|
+
return chalk6.gray(MESSAGES.STATUS_CHANGE_CLEAN);
|
|
1834
2366
|
}
|
|
1835
2367
|
}
|
|
1836
2368
|
function printSnapshotsSection(snapshots) {
|
|
1837
|
-
printInfo(` ${
|
|
2369
|
+
printInfo(` ${chalk6.bold("\u25C6")} ${chalk6.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.length} \u4E2A)`);
|
|
1838
2370
|
printInfo("");
|
|
1839
2371
|
if (snapshots.length === 0) {
|
|
1840
2372
|
printInfo(` ${MESSAGES.STATUS_NO_SNAPSHOTS}`);
|
|
@@ -1842,8 +2374,8 @@ function printSnapshotsSection(snapshots) {
|
|
|
1842
2374
|
return;
|
|
1843
2375
|
}
|
|
1844
2376
|
for (const snap of snapshots) {
|
|
1845
|
-
const orphanLabel = snap.worktreeExists ? "" : ` ${
|
|
1846
|
-
const icon = snap.worktreeExists ?
|
|
2377
|
+
const orphanLabel = snap.worktreeExists ? "" : ` ${chalk6.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED)}`;
|
|
2378
|
+
const icon = snap.worktreeExists ? chalk6.blue("\u25CF") : chalk6.yellow("\u26A0");
|
|
1847
2379
|
printInfo(` ${icon} ${snap.branch}${orphanLabel}`);
|
|
1848
2380
|
}
|
|
1849
2381
|
printInfo("");
|