clawt 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.claude/agent-memory/docs-sync-updater/MEMORY.md +48 -0
  2. package/.claude/agents/docs-sync-updater.md +128 -0
  3. package/CLAUDE.md +71 -0
  4. package/README.md +168 -0
  5. package/dist/index.js +923 -0
  6. package/dist/postinstall.js +71 -0
  7. package/docs/spec.md +710 -0
  8. package/package.json +38 -0
  9. package/scripts/postinstall.ts +116 -0
  10. package/src/commands/create.ts +49 -0
  11. package/src/commands/list.ts +45 -0
  12. package/src/commands/merge.ts +142 -0
  13. package/src/commands/remove.ts +127 -0
  14. package/src/commands/run.ts +310 -0
  15. package/src/commands/validate.ts +137 -0
  16. package/src/constants/branch.ts +6 -0
  17. package/src/constants/config.ts +8 -0
  18. package/src/constants/exitCodes.ts +9 -0
  19. package/src/constants/index.ts +6 -0
  20. package/src/constants/messages.ts +61 -0
  21. package/src/constants/paths.ts +14 -0
  22. package/src/constants/terminal.ts +13 -0
  23. package/src/errors/index.ts +20 -0
  24. package/src/index.ts +55 -0
  25. package/src/logger/index.ts +34 -0
  26. package/src/types/claudeCode.ts +14 -0
  27. package/src/types/command.ts +39 -0
  28. package/src/types/config.ts +7 -0
  29. package/src/types/index.ts +5 -0
  30. package/src/types/taskResult.ts +31 -0
  31. package/src/types/worktree.ts +7 -0
  32. package/src/utils/branch.ts +51 -0
  33. package/src/utils/config.ts +35 -0
  34. package/src/utils/formatter.ts +67 -0
  35. package/src/utils/fs.ts +28 -0
  36. package/src/utils/git.ts +243 -0
  37. package/src/utils/index.ts +35 -0
  38. package/src/utils/prompt.ts +18 -0
  39. package/src/utils/shell.ts +53 -0
  40. package/src/utils/validation.ts +48 -0
  41. package/src/utils/worktree.ts +107 -0
  42. package/tsconfig.json +17 -0
  43. package/tsup.config.ts +25 -0
package/dist/index.js ADDED
@@ -0,0 +1,923 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __clawt_createRequire } from "module";
3
+ const require = __clawt_createRequire(import.meta.url);
4
+
5
+ // src/index.ts
6
+ import { Command } from "commander";
7
+
8
+ // src/constants/paths.ts
9
+ import { homedir } from "os";
10
+ import { join } from "path";
11
+ var CLAWT_HOME = join(homedir(), ".clawt");
12
+ var CONFIG_PATH = join(CLAWT_HOME, "config.json");
13
+ var LOGS_DIR = join(CLAWT_HOME, "logs");
14
+ var WORKTREES_DIR = join(CLAWT_HOME, "worktrees");
15
+
16
+ // src/constants/branch.ts
17
+ var INVALID_BRANCH_CHARS = /[\/\\.\s~:*?[\]^]+/g;
18
+
19
+ // src/constants/messages.ts
20
+ var MESSAGES = {
21
+ /** 不在主 worktree 根目录 */
22
+ NOT_MAIN_WORKTREE: "\u8BF7\u5728\u4E3B worktree \u7684\u6839\u76EE\u5F55\u4E0B\u6267\u884C clawt",
23
+ /** Git 未安装 */
24
+ GIT_NOT_INSTALLED: "Git \u672A\u5B89\u88C5\u6216\u4E0D\u5728 PATH \u4E2D\uFF0C\u8BF7\u5148\u5B89\u88C5 Git",
25
+ /** Claude Code CLI 未安装 */
26
+ CLAUDE_NOT_INSTALLED: "Claude Code CLI \u672A\u5B89\u88C5\uFF0C\u8BF7\u5148\u5B89\u88C5\uFF1Anpm install -g @anthropic-ai/claude-code",
27
+ /** 分支已存在 */
28
+ BRANCH_EXISTS: (name) => `\u5206\u652F ${name} \u5DF2\u5B58\u5728\uFF0C\u65E0\u6CD5\u521B\u5EFA`,
29
+ /** 分支名被转换 */
30
+ BRANCH_SANITIZED: (original, sanitized) => `\u5206\u652F\u540D\u5DF2\u8F6C\u6362: ${original} \u2192 ${sanitized}`,
31
+ /** worktree 创建成功 */
32
+ WORKTREE_CREATED: (count) => `\u2713 \u5DF2\u521B\u5EFA ${count} \u4E2A worktree`,
33
+ /** worktree 移除成功 */
34
+ WORKTREE_REMOVED: (path) => `\u2713 \u5DF2\u79FB\u9664 worktree: ${path}`,
35
+ /** 没有 worktree */
36
+ NO_WORKTREES: "(\u65E0 worktree)",
37
+ /** 目标 worktree 不存在 */
38
+ WORKTREE_NOT_FOUND: (name) => `worktree ${name} \u4E0D\u5B58\u5728`,
39
+ /** 主 worktree 有未提交更改 */
40
+ MAIN_WORKTREE_DIRTY: "\u4E3B worktree \u6709\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF0C\u8BF7\u5148\u5904\u7406",
41
+ /** 目标 worktree 无更改 */
42
+ TARGET_WORKTREE_CLEAN: "\u8BE5 worktree \u7684\u5206\u652F\u4E0A\u6CA1\u6709\u4EFB\u4F55\u66F4\u6539\uFF0C\u65E0\u9700\u9A8C\u8BC1",
43
+ /** stash 已变更 */
44
+ STASH_CHANGED: "git stash list \u5DF2\u53D8\u66F4\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C",
45
+ /** validate 成功 */
46
+ VALIDATE_SUCCESS: (branch) => `\u2713 \u5DF2\u5C06\u5206\u652F ${branch} \u7684\u53D8\u66F4\u5E94\u7528\u5230\u4E3B worktree
47
+ \u53EF\u4EE5\u5F00\u59CB\u9A8C\u8BC1\u4E86`,
48
+ /** merge 成功 */
49
+ MERGE_SUCCESS: (branch, message) => `\u2713 \u5206\u652F ${branch} \u5DF2\u6210\u529F\u5408\u5E76\u5230\u5F53\u524D\u5206\u652F
50
+ \u63D0\u4EA4\u4FE1\u606F: ${message}
51
+ \u5DF2\u63A8\u9001\u5230\u8FDC\u7A0B\u4ED3\u5E93`,
52
+ /** merge 成功(无提交信息,目标 worktree 已提交过) */
53
+ MERGE_SUCCESS_NO_MESSAGE: (branch) => `\u2713 \u5206\u652F ${branch} \u5DF2\u6210\u529F\u5408\u5E76\u5230\u5F53\u524D\u5206\u652F
54
+ \u5DF2\u63A8\u9001\u5230\u8FDC\u7A0B\u4ED3\u5E93`,
55
+ /** merge 冲突 */
56
+ MERGE_CONFLICT: "\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u5904\u7406",
57
+ /** merge 后清理 worktree 和分支成功 */
58
+ WORKTREE_CLEANED: (branch) => `\u2713 \u5DF2\u6E05\u7406 worktree \u548C\u5206\u652F: ${branch}`,
59
+ /** 请提供提交信息 */
60
+ COMMIT_MESSAGE_REQUIRED: "\u8BF7\u63D0\u4F9B\u63D0\u4EA4\u4FE1\u606F\uFF08-m \u53C2\u6570\uFF09",
61
+ /** 目标 worktree 有未提交修改但未指定 -m */
62
+ 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",
63
+ /** 目标 worktree 既干净又无本地提交 */
64
+ 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",
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
+ 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",
77
+ /** 粗分隔线 */
78
+ 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"
79
+ };
80
+
81
+ // src/constants/exitCodes.ts
82
+ var EXIT_CODES = {
83
+ /** 成功 */
84
+ SUCCESS: 0,
85
+ /** 一般错误 */
86
+ ERROR: 1,
87
+ /** 参数错误 */
88
+ ARGUMENT_ERROR: 2
89
+ };
90
+
91
+ // src/constants/config.ts
92
+ var DEFAULT_CONFIG = {
93
+ autoDeleteBranch: false,
94
+ /** 默认 Claude Code CLI 启动指令,用于不传 --tasks 时在 worktree 中打开交互式界面 */
95
+ claudeCodeCommand: "claude"
96
+ };
97
+
98
+ // src/errors/index.ts
99
+ var ClawtError = class extends Error {
100
+ /** 退出码 */
101
+ exitCode;
102
+ /**
103
+ * @param {string} message - 错误消息
104
+ * @param {number} exitCode - 退出码,默认为 EXIT_CODES.ERROR (1)
105
+ */
106
+ constructor(message, exitCode = EXIT_CODES.ERROR) {
107
+ super(message);
108
+ this.name = "ClawtError";
109
+ this.exitCode = exitCode;
110
+ }
111
+ };
112
+
113
+ // src/logger/index.ts
114
+ import winston from "winston";
115
+ import DailyRotateFile from "winston-daily-rotate-file";
116
+ import { existsSync, mkdirSync } from "fs";
117
+ if (!existsSync(LOGS_DIR)) {
118
+ mkdirSync(LOGS_DIR, { recursive: true });
119
+ }
120
+ var logFormat = winston.format.printf(({ level, message, timestamp }) => {
121
+ const upperLevel = level.toUpperCase().padEnd(5);
122
+ return `[${timestamp}] [${upperLevel}] ${message}`;
123
+ });
124
+ var dailyRotateTransport = new DailyRotateFile({
125
+ dirname: LOGS_DIR,
126
+ filename: "clawt-%DATE%.log",
127
+ datePattern: "YYYY-MM-DD",
128
+ maxSize: "10m",
129
+ maxFiles: "30d"
130
+ });
131
+ var logger = winston.createLogger({
132
+ level: "debug",
133
+ format: winston.format.combine(
134
+ winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
135
+ logFormat
136
+ ),
137
+ transports: [dailyRotateTransport]
138
+ });
139
+
140
+ // src/utils/shell.ts
141
+ import { execSync, spawn } from "child_process";
142
+ function execCommand(command, options) {
143
+ logger.debug(`\u6267\u884C\u547D\u4EE4: ${command}${options?.cwd ? ` (cwd: ${options.cwd})` : ""}`);
144
+ const result = execSync(command, {
145
+ cwd: options?.cwd,
146
+ encoding: "utf-8",
147
+ stdio: ["pipe", "pipe", "pipe"]
148
+ });
149
+ return result.trim();
150
+ }
151
+ function spawnProcess(command, args, options) {
152
+ logger.debug(`\u542F\u52A8\u5B50\u8FDB\u7A0B: ${command} ${args.join(" ")}${options?.cwd ? ` (cwd: ${options.cwd})` : ""}`);
153
+ return spawn(command, args, {
154
+ cwd: options?.cwd,
155
+ stdio: options?.stdio ?? ["pipe", "pipe", "pipe"]
156
+ });
157
+ }
158
+ function killAllChildProcesses(children) {
159
+ for (const child of children) {
160
+ if (!child.killed) {
161
+ child.kill("SIGTERM");
162
+ }
163
+ }
164
+ }
165
+
166
+ // src/utils/git.ts
167
+ import { basename } from "path";
168
+ function getGitCommonDir(cwd) {
169
+ return execCommand("git rev-parse --git-common-dir", { cwd });
170
+ }
171
+ function getGitTopLevel(cwd) {
172
+ return execCommand("git rev-parse --show-toplevel", { cwd });
173
+ }
174
+ function getProjectName(cwd) {
175
+ const topLevel = getGitTopLevel(cwd);
176
+ return basename(topLevel);
177
+ }
178
+ function checkBranchExists(branchName, cwd) {
179
+ try {
180
+ execCommand(`git show-ref --verify refs/heads/${branchName}`, { cwd });
181
+ return true;
182
+ } catch {
183
+ return false;
184
+ }
185
+ }
186
+ function createWorktree(branchName, worktreePath, cwd) {
187
+ logger.info(`\u521B\u5EFA worktree: ${worktreePath}`);
188
+ execCommand(`git worktree add -b ${branchName} "${worktreePath}"`, { cwd });
189
+ }
190
+ function removeWorktreeByPath(worktreePath, cwd) {
191
+ logger.info(`\u79FB\u9664 worktree: ${worktreePath}`);
192
+ execCommand(`git worktree remove -f "${worktreePath}"`, { cwd });
193
+ }
194
+ function deleteBranch(branchName, cwd) {
195
+ logger.info(`\u5220\u9664\u5206\u652F: ${branchName}`);
196
+ execCommand(`git branch -D ${branchName}`, { cwd });
197
+ }
198
+ function getStatusPorcelain(cwd) {
199
+ return execCommand("git status --porcelain", { cwd });
200
+ }
201
+ function isWorkingDirClean(cwd) {
202
+ return getStatusPorcelain(cwd) === "";
203
+ }
204
+ function gitAddAll(cwd) {
205
+ execCommand("git add .", { cwd });
206
+ }
207
+ function gitCommit(message, cwd) {
208
+ execCommand(`git commit -m '${message.replace(/'/g, "'\\''")}'`, { cwd });
209
+ }
210
+ function gitMerge(branchName, cwd) {
211
+ execCommand(`git merge ${branchName}`, { cwd });
212
+ }
213
+ function hasMergeConflict(cwd) {
214
+ const status = getStatusPorcelain(cwd);
215
+ return status.split("\n").some((line) => /^(UU|AA|DD|DU|UD|AU|UA)/.test(line));
216
+ }
217
+ function gitPull(cwd) {
218
+ execCommand("git pull", { cwd });
219
+ }
220
+ function gitPush(cwd) {
221
+ execCommand("git push", { cwd });
222
+ }
223
+ function gitResetHard(cwd) {
224
+ execCommand("git reset --hard HEAD", { cwd });
225
+ }
226
+ function gitCleanForce(cwd) {
227
+ execCommand("git clean -fd", { cwd });
228
+ }
229
+ function gitStashPush(message, cwd) {
230
+ execCommand(`git stash push -m "${message}"`, { cwd });
231
+ }
232
+ function gitStashApply(cwd) {
233
+ execCommand("git stash apply", { cwd });
234
+ }
235
+ function gitStashPop(index = 0, cwd) {
236
+ execCommand(`git stash pop stash@{${index}}`, { cwd });
237
+ }
238
+ function gitStashList(cwd) {
239
+ try {
240
+ return execCommand("git stash list", { cwd });
241
+ } catch {
242
+ return "";
243
+ }
244
+ }
245
+ function gitRestoreStaged(cwd) {
246
+ execCommand("git restore --staged .", { cwd });
247
+ }
248
+ function gitWorktreeList(cwd) {
249
+ return execCommand("git worktree list", { cwd });
250
+ }
251
+ function gitWorktreePrune(cwd) {
252
+ execCommand("git worktree prune", { cwd });
253
+ }
254
+ function hasLocalCommits(branchName, cwd) {
255
+ try {
256
+ const output = execCommand(`git log HEAD..${branchName} --oneline`, { cwd });
257
+ return output.trim() !== "";
258
+ } catch {
259
+ return false;
260
+ }
261
+ }
262
+
263
+ // src/utils/formatter.ts
264
+ import chalk from "chalk";
265
+ import { createInterface } from "readline";
266
+ function printSuccess(message) {
267
+ console.log(chalk.green(message));
268
+ }
269
+ function printError(message) {
270
+ console.error(chalk.red(`\u2717 ${message}`));
271
+ }
272
+ function printWarning(message) {
273
+ console.log(chalk.yellow(`\u26A0 ${message}`));
274
+ }
275
+ function printInfo(message) {
276
+ console.log(message);
277
+ }
278
+ function printSeparator() {
279
+ console.log(MESSAGES.SEPARATOR);
280
+ }
281
+ function printDoubleSeparator() {
282
+ console.log(MESSAGES.DOUBLE_SEPARATOR);
283
+ }
284
+ function confirmAction(question) {
285
+ return new Promise((resolve) => {
286
+ const rl = createInterface({
287
+ input: process.stdin,
288
+ output: process.stdout
289
+ });
290
+ rl.question(`${question} (y/N) `, (answer) => {
291
+ rl.close();
292
+ resolve(answer.toLowerCase() === "y");
293
+ });
294
+ });
295
+ }
296
+
297
+ // src/utils/branch.ts
298
+ function sanitizeBranchName(branchName) {
299
+ const sanitized = branchName.replace(INVALID_BRANCH_CHARS, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
300
+ if (sanitized !== branchName) {
301
+ logger.warn(MESSAGES.BRANCH_SANITIZED(branchName, sanitized));
302
+ printWarning(MESSAGES.BRANCH_SANITIZED(branchName, sanitized));
303
+ }
304
+ return sanitized;
305
+ }
306
+ function generateBranchNames(branchName, count) {
307
+ if (count === 1) {
308
+ return [branchName];
309
+ }
310
+ return Array.from({ length: count }, (_, i) => `${branchName}-${i + 1}`);
311
+ }
312
+ function validateBranchesNotExist(branchNames) {
313
+ for (const name of branchNames) {
314
+ if (checkBranchExists(name)) {
315
+ throw new ClawtError(MESSAGES.BRANCH_EXISTS(name));
316
+ }
317
+ }
318
+ }
319
+
320
+ // src/utils/validation.ts
321
+ function validateMainWorktree() {
322
+ try {
323
+ const gitCommonDir = getGitCommonDir();
324
+ if (gitCommonDir !== ".git") {
325
+ throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
326
+ }
327
+ } catch (error) {
328
+ if (error instanceof ClawtError) {
329
+ throw error;
330
+ }
331
+ throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
332
+ }
333
+ }
334
+ function validateClaudeCodeInstalled() {
335
+ try {
336
+ execCommand("claude --version");
337
+ } catch {
338
+ throw new ClawtError(MESSAGES.CLAUDE_NOT_INSTALLED);
339
+ }
340
+ }
341
+
342
+ // src/utils/worktree.ts
343
+ import { join as join2 } from "path";
344
+ import { existsSync as existsSync3, readdirSync as readdirSync2 } from "fs";
345
+
346
+ // src/utils/fs.ts
347
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmdirSync } from "fs";
348
+ function ensureDir(dirPath) {
349
+ if (!existsSync2(dirPath)) {
350
+ mkdirSync2(dirPath, { recursive: true });
351
+ }
352
+ }
353
+ function removeEmptyDir(dirPath) {
354
+ if (!existsSync2(dirPath)) {
355
+ return false;
356
+ }
357
+ const entries = readdirSync(dirPath);
358
+ if (entries.length === 0) {
359
+ rmdirSync(dirPath);
360
+ return true;
361
+ }
362
+ return false;
363
+ }
364
+
365
+ // src/utils/worktree.ts
366
+ function getProjectWorktreeDir() {
367
+ const projectName = getProjectName();
368
+ return join2(WORKTREES_DIR, projectName);
369
+ }
370
+ function createWorktrees(branchName, count) {
371
+ const sanitized = sanitizeBranchName(branchName);
372
+ const branchNames = generateBranchNames(sanitized, count);
373
+ validateBranchesNotExist(branchNames);
374
+ const projectDir = getProjectWorktreeDir();
375
+ ensureDir(projectDir);
376
+ const results = [];
377
+ for (const name of branchNames) {
378
+ const worktreePath = join2(projectDir, name);
379
+ createWorktree(name, worktreePath);
380
+ results.push({ path: worktreePath, branch: name });
381
+ logger.info(`worktree \u521B\u5EFA\u5B8C\u6210: ${worktreePath} (\u5206\u652F: ${name})`);
382
+ }
383
+ return results;
384
+ }
385
+ function getProjectWorktrees() {
386
+ const projectDir = getProjectWorktreeDir();
387
+ if (!existsSync3(projectDir)) {
388
+ return [];
389
+ }
390
+ const worktreeListOutput = gitWorktreeList();
391
+ const registeredPaths = new Set(
392
+ worktreeListOutput.split("\n").map((line) => line.split(/\s+/)[0])
393
+ );
394
+ const entries = readdirSync2(projectDir, { withFileTypes: true });
395
+ const worktrees = [];
396
+ for (const entry of entries) {
397
+ if (!entry.isDirectory()) {
398
+ continue;
399
+ }
400
+ const fullPath = join2(projectDir, entry.name);
401
+ if (registeredPaths.has(fullPath)) {
402
+ worktrees.push({
403
+ path: fullPath,
404
+ branch: entry.name
405
+ });
406
+ }
407
+ }
408
+ return worktrees;
409
+ }
410
+ function cleanupWorktrees(worktrees) {
411
+ for (const wt of worktrees) {
412
+ try {
413
+ removeWorktreeByPath(wt.path);
414
+ deleteBranch(wt.branch);
415
+ logger.info(`\u5DF2\u6E05\u7406 worktree \u548C\u5206\u652F: ${wt.branch}`);
416
+ } catch (error) {
417
+ logger.error(`\u6E05\u7406 worktree \u5931\u8D25: ${wt.path} - ${error}`);
418
+ }
419
+ }
420
+ gitWorktreePrune();
421
+ const projectDir = getProjectWorktreeDir();
422
+ removeEmptyDir(projectDir);
423
+ }
424
+
425
+ // src/utils/config.ts
426
+ import { existsSync as existsSync4, readFileSync } from "fs";
427
+ function loadConfig() {
428
+ if (!existsSync4(CONFIG_PATH)) {
429
+ return { ...DEFAULT_CONFIG };
430
+ }
431
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
432
+ return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
433
+ }
434
+ function getConfigValue(key) {
435
+ const config = loadConfig();
436
+ return config[key];
437
+ }
438
+ function ensureClawtDirs() {
439
+ ensureDir(CLAWT_HOME);
440
+ ensureDir(LOGS_DIR);
441
+ ensureDir(WORKTREES_DIR);
442
+ }
443
+
444
+ // src/utils/prompt.ts
445
+ import Enquirer from "enquirer";
446
+
447
+ // src/commands/list.ts
448
+ function registerListCommand(program2) {
449
+ program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").action(() => {
450
+ handleList();
451
+ });
452
+ }
453
+ function handleList() {
454
+ validateMainWorktree();
455
+ const projectName = getProjectName();
456
+ const worktrees = getProjectWorktrees();
457
+ logger.info(`list \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}\uFF0C\u5171 ${worktrees.length} \u4E2A worktree`);
458
+ printInfo(`\u5F53\u524D\u9879\u76EE: ${projectName}
459
+ `);
460
+ if (worktrees.length === 0) {
461
+ printInfo(` ${MESSAGES.NO_WORKTREES}`);
462
+ } else {
463
+ for (const wt of worktrees) {
464
+ printInfo(` ${wt.path} [${wt.branch}]`);
465
+ }
466
+ printInfo(`
467
+ \u5171 ${worktrees.length} \u4E2A worktree`);
468
+ }
469
+ }
470
+
471
+ // src/commands/create.ts
472
+ function registerCreateCommand(program2) {
473
+ 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) => {
474
+ handleCreate(options);
475
+ });
476
+ }
477
+ function handleCreate(options) {
478
+ validateMainWorktree();
479
+ const count = Number(options.number);
480
+ logger.info(`create \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}\uFF0C\u6570\u91CF: ${count}`);
481
+ const worktrees = createWorktrees(options.branch, count);
482
+ printSuccess(MESSAGES.WORKTREE_CREATED(worktrees.length));
483
+ printInfo("");
484
+ worktrees.forEach((wt, index) => {
485
+ printInfo(`\u76EE\u5F55\u8DEF\u5F84${index + 1}\uFF1A`);
486
+ printInfo(` ${wt.path}`);
487
+ printInfo(` \u5206\u652F\u540D: ${wt.branch}`);
488
+ printSeparator();
489
+ });
490
+ }
491
+
492
+ // src/commands/remove.ts
493
+ import { join as join3 } from "path";
494
+ function registerRemoveCommand(program2) {
495
+ 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").option("-i, --index <index>", "\u6307\u5B9A\u7D22\u5F15\uFF08\u914D\u5408 -b \u4F7F\u7528\uFF09").action(async (options) => {
496
+ await handleRemove(options);
497
+ });
498
+ }
499
+ function resolveWorktreesToRemove(options) {
500
+ const projectDir = getProjectWorktreeDir();
501
+ const allWorktrees = getProjectWorktrees();
502
+ if (options.all) {
503
+ return allWorktrees;
504
+ }
505
+ if (!options.branch) {
506
+ throw new ClawtError("\u8BF7\u6307\u5B9A --all \u6216 -b <branchName> \u53C2\u6570");
507
+ }
508
+ if (options.index !== void 0) {
509
+ const targetName = `${options.branch}-${options.index}`;
510
+ const targetPath = join3(projectDir, targetName);
511
+ const found = allWorktrees.find((wt) => wt.path === targetPath);
512
+ if (!found) {
513
+ throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(targetName));
514
+ }
515
+ return [found];
516
+ }
517
+ const matched = allWorktrees.filter(
518
+ (wt) => wt.branch === options.branch || wt.branch.startsWith(`${options.branch}-`)
519
+ );
520
+ if (matched.length === 0) {
521
+ throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch));
522
+ }
523
+ return matched;
524
+ }
525
+ async function handleRemove(options) {
526
+ validateMainWorktree();
527
+ const projectName = getProjectName();
528
+ logger.info(`remove \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}`);
529
+ const worktreesToRemove = resolveWorktreesToRemove(options);
530
+ if (worktreesToRemove.length === 0) {
531
+ printInfo(MESSAGES.NO_WORKTREES);
532
+ return;
533
+ }
534
+ printInfo("\u5373\u5C06\u79FB\u9664\u4EE5\u4E0B worktree \u53CA\u672C\u5730\u5206\u652F\uFF1A\n");
535
+ worktreesToRemove.forEach((wt, index) => {
536
+ printInfo(` ${index + 1}. ${wt.path} \u2192 \u5206\u652F: ${wt.branch}`);
537
+ });
538
+ printInfo("");
539
+ const autoDelete = getConfigValue("autoDeleteBranch");
540
+ let shouldDeleteBranch = autoDelete;
541
+ if (!autoDelete) {
542
+ shouldDeleteBranch = await confirmAction("\u662F\u5426\u540C\u65F6\u5220\u9664\u5BF9\u5E94\u7684\u672C\u5730\u5206\u652F\uFF1F");
543
+ }
544
+ for (const wt of worktreesToRemove) {
545
+ try {
546
+ removeWorktreeByPath(wt.path);
547
+ if (shouldDeleteBranch) {
548
+ deleteBranch(wt.branch);
549
+ }
550
+ printSuccess(MESSAGES.WORKTREE_REMOVED(wt.path));
551
+ } catch (error) {
552
+ logger.error(`\u79FB\u9664 worktree \u5931\u8D25: ${wt.path} - ${error}`);
553
+ throw error;
554
+ }
555
+ }
556
+ gitWorktreePrune();
557
+ const projectDir = getProjectWorktreeDir();
558
+ removeEmptyDir(projectDir);
559
+ }
560
+
561
+ // src/commands/run.ts
562
+ import { spawnSync } from "child_process";
563
+ function registerRunCommand(program2) {
564
+ 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) => {
565
+ await handleRun(options);
566
+ });
567
+ }
568
+ function launchInteractiveClaude(worktree) {
569
+ const commandStr = getConfigValue("claudeCodeCommand");
570
+ const parts = commandStr.split(/\s+/).filter(Boolean);
571
+ const cmd = parts[0];
572
+ const args = parts.slice(1);
573
+ printInfo(`\u6B63\u5728 worktree \u4E2D\u542F\u52A8 Claude Code \u4EA4\u4E92\u5F0F\u754C\u9762...`);
574
+ printInfo(` \u5206\u652F: ${worktree.branch}`);
575
+ printInfo(` \u8DEF\u5F84: ${worktree.path}`);
576
+ printInfo(` \u6307\u4EE4: ${commandStr}`);
577
+ printInfo("");
578
+ const result = spawnSync(cmd, args, {
579
+ cwd: worktree.path,
580
+ stdio: "inherit"
581
+ });
582
+ if (result.error) {
583
+ throw new ClawtError(`\u542F\u52A8 Claude Code \u5931\u8D25: ${result.error.message}`);
584
+ }
585
+ if (result.status !== null && result.status !== 0) {
586
+ printWarning(`Claude Code \u9000\u51FA\u7801: ${result.status}`);
587
+ }
588
+ }
589
+ function executeClaudeTask(worktree, task) {
590
+ const child = spawnProcess(
591
+ "claude",
592
+ ["-p", task, "--output-format", "json", "--permission-mode", "bypassPermissions"],
593
+ {
594
+ cwd: worktree.path,
595
+ // stdin 必须设置为 'ignore',不能用 'pipe'
596
+ // 原因:claude -p 是非交互模式,不需要 stdin 输入。如果 stdin 为 'pipe',
597
+ // 父进程会创建一个可写流连接到子进程但从不写入也不关闭,
598
+ // claude 检测到 stdin 是管道后会尝试读取输入,导致进程永远卡住
599
+ stdio: ["ignore", "pipe", "pipe"]
600
+ }
601
+ );
602
+ const promise = new Promise((resolve) => {
603
+ let stdout = "";
604
+ let stderr = "";
605
+ child.stdout?.on("data", (data) => {
606
+ stdout += data.toString();
607
+ });
608
+ child.stderr?.on("data", (data) => {
609
+ stderr += data.toString();
610
+ });
611
+ child.on("close", (code) => {
612
+ let result = null;
613
+ let success = code === 0;
614
+ try {
615
+ if (stdout.trim()) {
616
+ result = JSON.parse(stdout.trim());
617
+ success = !result.is_error;
618
+ }
619
+ } catch {
620
+ logger.warn(`\u89E3\u6790 Claude Code \u8F93\u51FA\u5931\u8D25: ${stdout.substring(0, 200)}`);
621
+ }
622
+ resolve({
623
+ task,
624
+ branch: worktree.branch,
625
+ worktreePath: worktree.path,
626
+ success,
627
+ result,
628
+ error: success ? void 0 : stderr || "\u4EFB\u52A1\u6267\u884C\u5931\u8D25"
629
+ });
630
+ });
631
+ child.on("error", (err) => {
632
+ resolve({
633
+ task,
634
+ branch: worktree.branch,
635
+ worktreePath: worktree.path,
636
+ success: false,
637
+ result: null,
638
+ error: err.message
639
+ });
640
+ });
641
+ });
642
+ return { child, promise };
643
+ }
644
+ function printTaskNotification(taskResult) {
645
+ const { success, worktreePath, branch, result } = taskResult;
646
+ const status = success ? "\u5B8C\u6210" : "\u5931\u8D25";
647
+ const icon = success ? "\u2713" : "\u2717";
648
+ const durationStr = result ? `${(result.duration_ms / 1e3).toFixed(1)}s` : "N/A";
649
+ const costStr = result ? `$${result.total_cost_usd.toFixed(2)}` : "N/A";
650
+ const resultStr = success ? "success" : "failed";
651
+ if (success) {
652
+ printSuccess(`${icon} [${status}] worktree: ${worktreePath}`);
653
+ } else {
654
+ printError(`${icon} [${status}] worktree: ${worktreePath}`);
655
+ }
656
+ printInfo(` \u5206\u652F: ${branch}`);
657
+ printInfo(` \u8017\u65F6: ${durationStr}`);
658
+ printInfo(` \u82B1\u8D39: ${costStr}`);
659
+ printInfo(` \u7ED3\u679C: ${resultStr}`);
660
+ printSeparator();
661
+ }
662
+ function printTaskSummary(summary) {
663
+ printDoubleSeparator();
664
+ printInfo(`\u5168\u90E8\u4EFB\u52A1\u5DF2\u5B8C\u6210 (${summary.total}/${summary.total})`);
665
+ printInfo(` \u6210\u529F: ${summary.succeeded}`);
666
+ printInfo(` \u5931\u8D25: ${summary.failed}`);
667
+ printInfo(` \u603B\u8017\u65F6: ${(summary.totalDurationMs / 1e3).toFixed(1)}s`);
668
+ printInfo(` \u603B\u82B1\u8D39: $${summary.totalCostUsd.toFixed(2)}`);
669
+ printDoubleSeparator();
670
+ }
671
+ async function handleInterruptCleanup(worktrees) {
672
+ const autoDelete = getConfigValue("autoDeleteBranch");
673
+ if (autoDelete) {
674
+ cleanupWorktrees(worktrees);
675
+ printSuccess(MESSAGES.INTERRUPT_AUTO_CLEANED(worktrees.length));
676
+ return;
677
+ }
678
+ const shouldClean = await confirmAction(MESSAGES.INTERRUPT_CONFIRM_CLEANUP);
679
+ if (shouldClean) {
680
+ cleanupWorktrees(worktrees);
681
+ printSuccess(MESSAGES.INTERRUPT_CLEANED(worktrees.length));
682
+ } else {
683
+ printInfo(MESSAGES.INTERRUPT_KEPT);
684
+ }
685
+ }
686
+ async function handleRun(options) {
687
+ validateMainWorktree();
688
+ validateClaudeCodeInstalled();
689
+ if (!options.tasks || options.tasks.length === 0) {
690
+ const worktrees2 = createWorktrees(options.branch, 1);
691
+ const worktree = worktrees2[0];
692
+ printSuccess(MESSAGES.WORKTREE_CREATED(1));
693
+ launchInteractiveClaude(worktree);
694
+ return;
695
+ }
696
+ const tasks = options.tasks.map((t) => t.trim()).filter(Boolean);
697
+ if (tasks.length === 0) {
698
+ throw new ClawtError("\u4EFB\u52A1\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A");
699
+ }
700
+ const count = tasks.length;
701
+ logger.info(`run \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}\uFF0C\u4EFB\u52A1\u6570: ${count}`);
702
+ const worktrees = createWorktrees(options.branch, count);
703
+ printSuccess(MESSAGES.WORKTREE_CREATED(worktrees.length));
704
+ for (const wt of worktrees) {
705
+ printInfo(` \u5206\u652F: ${wt.branch} \u8DEF\u5F84: ${wt.path}`);
706
+ }
707
+ printInfo("");
708
+ const startTime = Date.now();
709
+ const handles = worktrees.map((wt, index) => {
710
+ const task = tasks[index];
711
+ logger.info(`\u542F\u52A8\u4EFB\u52A1 ${index + 1}: ${task} (worktree: ${wt.path})`);
712
+ return executeClaudeTask(wt, task);
713
+ });
714
+ const childProcesses = handles.map((h) => h.child);
715
+ let interrupted = false;
716
+ const sigintHandler = async () => {
717
+ if (interrupted) return;
718
+ interrupted = true;
719
+ printInfo("");
720
+ printWarning(MESSAGES.INTERRUPTED);
721
+ killAllChildProcesses(childProcesses);
722
+ await Promise.allSettled(handles.map((h) => h.promise));
723
+ await handleInterruptCleanup(worktrees);
724
+ process.exit(1);
725
+ };
726
+ process.on("SIGINT", sigintHandler);
727
+ const taskPromises = handles.map(
728
+ (handle) => handle.promise.then((result) => {
729
+ if (!interrupted) {
730
+ printTaskNotification(result);
731
+ }
732
+ return result;
733
+ })
734
+ );
735
+ const results = await Promise.all(taskPromises);
736
+ process.removeListener("SIGINT", sigintHandler);
737
+ if (interrupted) return;
738
+ const totalDurationMs = Date.now() - startTime;
739
+ const summary = {
740
+ total: results.length,
741
+ succeeded: results.filter((r) => r.success).length,
742
+ failed: results.filter((r) => !r.success).length,
743
+ totalDurationMs,
744
+ totalCostUsd: results.reduce((sum, r) => sum + (r.result?.total_cost_usd ?? 0), 0)
745
+ };
746
+ printTaskSummary(summary);
747
+ }
748
+
749
+ // src/commands/validate.ts
750
+ import { join as join4 } from "path";
751
+ import { existsSync as existsSync5 } from "fs";
752
+ import Enquirer2 from "enquirer";
753
+ function registerValidateCommand(program2) {
754
+ program2.command("validate").description("\u5728\u4E3B worktree \u9A8C\u8BC1\u67D0\u4E2A worktree \u5206\u652F\u7684\u53D8\u66F4").requiredOption("-b, --branch <branchName>", "\u8981\u9A8C\u8BC1\u7684\u5206\u652F\u540D").action(async (options) => {
755
+ await handleValidate(options);
756
+ });
757
+ }
758
+ async function handleDirtyMainWorktree(mainWorktreePath) {
759
+ printWarning("\u4E3B worktree \u5F53\u524D\u5206\u652F\u6709\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF0C\u8BF7\u9009\u62E9\u5904\u7406\u65B9\u5F0F\uFF1A\n");
760
+ const choice = await new Enquirer2.Select({
761
+ message: "\u9009\u62E9\u5904\u7406\u65B9\u5F0F",
762
+ choices: [
763
+ {
764
+ name: "reset",
765
+ message: "reset (\u63A8\u8350) - \u4E22\u5F03\u6240\u6709\u66F4\u6539 (git reset --hard HEAD && git clean -fd)"
766
+ },
767
+ {
768
+ name: "stash",
769
+ message: "stash - \u6682\u5B58\u66F4\u6539 (git add . && git stash)"
770
+ },
771
+ {
772
+ name: "exit",
773
+ message: "exit - \u9000\u51FA\uFF0C\u624B\u52A8\u5904\u7406"
774
+ }
775
+ ],
776
+ initial: 0
777
+ }).run();
778
+ if (choice === "exit") {
779
+ throw new ClawtError("\u7528\u6237\u9009\u62E9\u9000\u51FA");
780
+ }
781
+ if (choice === "reset") {
782
+ gitResetHard(mainWorktreePath);
783
+ gitCleanForce(mainWorktreePath);
784
+ } else if (choice === "stash") {
785
+ gitAddAll(mainWorktreePath);
786
+ gitStashPush("clawt:auto-stash", mainWorktreePath);
787
+ }
788
+ if (!isWorkingDirClean(mainWorktreePath)) {
789
+ throw new ClawtError("\u5DE5\u4F5C\u533A\u4ECD\u7136\u4E0D\u5E72\u51C0\uFF0C\u8BF7\u624B\u52A8\u5904\u7406");
790
+ }
791
+ }
792
+ async function handleValidate(options) {
793
+ validateMainWorktree();
794
+ const projectName = getProjectName();
795
+ const mainWorktreePath = getGitTopLevel();
796
+ const projectDir = getProjectWorktreeDir();
797
+ const targetWorktreePath = join4(projectDir, options.branch);
798
+ logger.info(`validate \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}`);
799
+ if (!existsSync5(targetWorktreePath)) {
800
+ throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch));
801
+ }
802
+ if (!isWorkingDirClean(mainWorktreePath)) {
803
+ await handleDirtyMainWorktree(mainWorktreePath);
804
+ }
805
+ if (isWorkingDirClean(targetWorktreePath)) {
806
+ printInfo(MESSAGES.TARGET_WORKTREE_CLEAN);
807
+ return;
808
+ }
809
+ const stashMessage = `clawt:validate:${options.branch}`;
810
+ gitAddAll(targetWorktreePath);
811
+ gitStashPush(stashMessage, targetWorktreePath);
812
+ gitStashApply(targetWorktreePath);
813
+ gitRestoreStaged(targetWorktreePath);
814
+ const stashList = gitStashList(mainWorktreePath);
815
+ const firstLine = stashList.split("\n")[0] || "";
816
+ if (!firstLine.includes(stashMessage)) {
817
+ throw new ClawtError(MESSAGES.STASH_CHANGED);
818
+ }
819
+ gitStashPop(0, mainWorktreePath);
820
+ printSuccess(MESSAGES.VALIDATE_SUCCESS(options.branch));
821
+ }
822
+
823
+ // src/commands/merge.ts
824
+ import { join as join5 } from "path";
825
+ import { existsSync as existsSync6 } from "fs";
826
+ function registerMergeCommand(program2) {
827
+ program2.command("merge").description("\u5408\u5E76\u67D0\u4E2A\u5DF2\u9A8C\u8BC1\u7684 worktree \u5206\u652F\u5230\u4E3B worktree").requiredOption("-b, --branch <branchName>", "\u8981\u5408\u5E76\u7684\u5206\u652F\u540D").option("-m, --message <message>", "\u63D0\u4EA4\u4FE1\u606F\uFF08\u5DE5\u4F5C\u533A\u6709\u4FEE\u6539\u65F6\u5FC5\u586B\uFF09").action(async (options) => {
828
+ await handleMerge(options);
829
+ });
830
+ }
831
+ async function shouldCleanupAfterMerge(branchName) {
832
+ const autoDelete = getConfigValue("autoDeleteBranch");
833
+ if (autoDelete) {
834
+ printInfo(`\u5DF2\u914D\u7F6E\u81EA\u52A8\u5220\u9664\uFF0Cmerge \u6210\u529F\u540E\u5C06\u81EA\u52A8\u6E05\u7406 worktree \u548C\u5206\u652F: ${branchName}`);
835
+ return true;
836
+ }
837
+ return confirmAction(`merge \u6210\u529F\u540E\u662F\u5426\u5220\u9664\u5BF9\u5E94\u7684 worktree \u548C\u5206\u652F (${branchName})\uFF1F`);
838
+ }
839
+ function cleanupWorktreeAndBranch(worktreePath, branchName) {
840
+ cleanupWorktrees([{ path: worktreePath, branch: branchName }]);
841
+ printSuccess(MESSAGES.WORKTREE_CLEANED(branchName));
842
+ }
843
+ async function handleMerge(options) {
844
+ validateMainWorktree();
845
+ const mainWorktreePath = getGitTopLevel();
846
+ const projectDir = getProjectWorktreeDir();
847
+ const targetWorktreePath = join5(projectDir, options.branch);
848
+ logger.info(`merge \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}\uFF0C\u63D0\u4EA4\u4FE1\u606F: ${options.message ?? "(\u672A\u63D0\u4F9B)"}`);
849
+ if (!existsSync6(targetWorktreePath)) {
850
+ throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch));
851
+ }
852
+ if (!isWorkingDirClean(mainWorktreePath)) {
853
+ throw new ClawtError(MESSAGES.MAIN_WORKTREE_DIRTY);
854
+ }
855
+ const shouldCleanup = await shouldCleanupAfterMerge(options.branch);
856
+ const targetClean = isWorkingDirClean(targetWorktreePath);
857
+ if (!targetClean) {
858
+ if (!options.message) {
859
+ throw new ClawtError(MESSAGES.TARGET_WORKTREE_DIRTY_NO_MESSAGE);
860
+ }
861
+ gitAddAll(targetWorktreePath);
862
+ gitCommit(options.message, targetWorktreePath);
863
+ } else {
864
+ if (!hasLocalCommits(options.branch, mainWorktreePath)) {
865
+ throw new ClawtError(MESSAGES.TARGET_WORKTREE_NO_CHANGES);
866
+ }
867
+ }
868
+ try {
869
+ gitMerge(options.branch, mainWorktreePath);
870
+ } catch (error) {
871
+ if (hasMergeConflict(mainWorktreePath)) {
872
+ throw new ClawtError(MESSAGES.MERGE_CONFLICT);
873
+ }
874
+ throw error;
875
+ }
876
+ if (hasMergeConflict(mainWorktreePath)) {
877
+ throw new ClawtError(MESSAGES.MERGE_CONFLICT);
878
+ }
879
+ gitPull(mainWorktreePath);
880
+ gitPush(mainWorktreePath);
881
+ if (options.message) {
882
+ printSuccess(MESSAGES.MERGE_SUCCESS(options.branch, options.message));
883
+ } else {
884
+ printSuccess(MESSAGES.MERGE_SUCCESS_NO_MESSAGE(options.branch));
885
+ }
886
+ if (shouldCleanup) {
887
+ cleanupWorktreeAndBranch(targetWorktreePath, options.branch);
888
+ }
889
+ }
890
+
891
+ // src/index.ts
892
+ ensureClawtDirs();
893
+ var program = new Command();
894
+ program.name("clawt").description("\u672C\u5730\u5E76\u884C\u6267\u884C\u591A\u4E2AClaude Code Agent\u4EFB\u52A1\uFF0C\u878D\u5408 Git Worktree \u4E0E Claude Code CLI \u7684\u547D\u4EE4\u884C\u5DE5\u5177").version("1.0.0");
895
+ registerListCommand(program);
896
+ registerCreateCommand(program);
897
+ registerRemoveCommand(program);
898
+ registerRunCommand(program);
899
+ registerValidateCommand(program);
900
+ registerMergeCommand(program);
901
+ process.on("uncaughtException", (error) => {
902
+ if (error instanceof ClawtError) {
903
+ printError(error.message);
904
+ logger.error(error.message);
905
+ process.exit(error.exitCode);
906
+ }
907
+ printError(error.message || "\u672A\u77E5\u9519\u8BEF");
908
+ logger.error(`\u672A\u6355\u83B7\u5F02\u5E38: ${error.message}
909
+ ${error.stack}`);
910
+ process.exit(EXIT_CODES.ERROR);
911
+ });
912
+ process.on("unhandledRejection", (reason) => {
913
+ const error = reason instanceof Error ? reason : new Error(String(reason));
914
+ if (error instanceof ClawtError) {
915
+ printError(error.message);
916
+ logger.error(error.message);
917
+ process.exit(error.exitCode);
918
+ }
919
+ printError(error.message || "\u672A\u77E5\u9519\u8BEF");
920
+ logger.error(`\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD: ${error.message}`);
921
+ process.exit(EXIT_CODES.ERROR);
922
+ });
923
+ program.parse(process.argv);