clawt 2.20.0 → 3.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 (71) hide show
  1. package/README.md +10 -0
  2. package/dist/index.js +395 -133
  3. package/dist/postinstall.js +35 -6
  4. package/docs/alias.md +108 -0
  5. package/docs/completion.md +55 -0
  6. package/docs/config-file.md +43 -0
  7. package/docs/config.md +91 -0
  8. package/docs/create.md +85 -0
  9. package/docs/init.md +65 -0
  10. package/docs/list.md +67 -0
  11. package/docs/log.md +67 -0
  12. package/docs/merge.md +137 -0
  13. package/docs/notification.md +94 -0
  14. package/docs/projects.md +135 -0
  15. package/docs/remove.md +79 -0
  16. package/docs/reset.md +35 -0
  17. package/docs/resume.md +99 -0
  18. package/docs/run.md +146 -0
  19. package/docs/spec.md +156 -1905
  20. package/docs/status.md +155 -0
  21. package/docs/sync.md +114 -0
  22. package/docs/update-check.md +95 -0
  23. package/docs/validate.md +368 -0
  24. package/package.json +1 -1
  25. package/src/commands/alias.ts +1 -1
  26. package/src/commands/create.ts +10 -5
  27. package/src/commands/init.ts +75 -0
  28. package/src/commands/list.ts +1 -1
  29. package/src/commands/merge.ts +11 -4
  30. package/src/commands/remove.ts +10 -3
  31. package/src/commands/reset.ts +3 -0
  32. package/src/commands/resume.ts +1 -1
  33. package/src/commands/run.ts +9 -3
  34. package/src/commands/status.ts +1 -1
  35. package/src/commands/sync.ts +18 -6
  36. package/src/commands/validate.ts +46 -52
  37. package/src/constants/branch.ts +3 -0
  38. package/src/constants/config.ts +1 -1
  39. package/src/constants/index.ts +2 -2
  40. package/src/constants/messages/completion.ts +1 -1
  41. package/src/constants/messages/create.ts +3 -0
  42. package/src/constants/messages/index.ts +2 -0
  43. package/src/constants/messages/init.ts +18 -0
  44. package/src/constants/messages/remove.ts +2 -0
  45. package/src/constants/messages/sync.ts +3 -0
  46. package/src/constants/messages/validate.ts +6 -0
  47. package/src/constants/paths.ts +3 -0
  48. package/src/index.ts +2 -0
  49. package/src/types/command.ts +7 -1
  50. package/src/types/index.ts +2 -1
  51. package/src/types/projectConfig.ts +5 -0
  52. package/src/utils/config.ts +2 -1
  53. package/src/utils/git.ts +18 -0
  54. package/src/utils/index.ts +5 -0
  55. package/src/utils/json.ts +67 -0
  56. package/src/utils/project-config.ts +77 -0
  57. package/src/utils/validate-branch.ts +166 -0
  58. package/src/utils/worktree.ts +6 -2
  59. package/tests/unit/commands/create.test.ts +20 -16
  60. package/tests/unit/commands/init.test.ts +146 -0
  61. package/tests/unit/commands/merge.test.ts +7 -1
  62. package/tests/unit/commands/remove.test.ts +4 -0
  63. package/tests/unit/commands/reset.test.ts +2 -0
  64. package/tests/unit/commands/run.test.ts +2 -0
  65. package/tests/unit/commands/sync.test.ts +6 -0
  66. package/tests/unit/commands/validate.test.ts +13 -0
  67. package/tests/unit/utils/config.test.ts +2 -2
  68. package/tests/unit/utils/project-config.test.ts +136 -0
  69. package/tests/unit/utils/update-checker.test.ts +28 -7
  70. package/tests/unit/utils/validate-branch.test.ts +272 -0
  71. package/tests/unit/utils/worktree.test.ts +6 -0
package/dist/index.js CHANGED
@@ -16,9 +16,11 @@ var WORKTREES_DIR = join(CLAWT_HOME, "worktrees");
16
16
  var VALIDATE_SNAPSHOTS_DIR = join(CLAWT_HOME, "validate-snapshots");
17
17
  var CLAUDE_PROJECTS_DIR = join(homedir(), ".claude", "projects");
18
18
  var UPDATE_CHECK_PATH = join(CLAWT_HOME, "update-check.json");
19
+ var PROJECTS_CONFIG_DIR = join(CLAWT_HOME, "projects");
19
20
 
20
21
  // src/constants/branch.ts
21
22
  var INVALID_BRANCH_CHARS = /[\/\\.\s~:*?[\]^]+/g;
23
+ var VALIDATE_BRANCH_PREFIX = "clawt-validate-";
22
24
 
23
25
  // src/constants/messages/common.ts
24
26
  var COMMON_MESSAGES = {
@@ -121,7 +123,9 @@ var RUN_MESSAGES = {
121
123
  // src/constants/messages/create.ts
122
124
  var CREATE_MESSAGES = {
123
125
  /** 创建数量参数无效 */
124
- INVALID_COUNT: (value) => `\u65E0\u6548\u7684\u521B\u5EFA\u6570\u91CF: "${value}"\uFF0C\u8BF7\u8F93\u5165\u6B63\u6574\u6570`
126
+ INVALID_COUNT: (value) => `\u65E0\u6548\u7684\u521B\u5EFA\u6570\u91CF: "${value}"\uFF0C\u8BF7\u8F93\u5165\u6B63\u6574\u6570`,
127
+ /** 当前不在主工作分支上的警告 */
128
+ CREATE_WARN_NOT_ON_MAIN_BRANCH: (mainBranch, currentBranch) => `\u5F53\u524D\u4E0D\u5728\u4E3B\u5DE5\u4F5C\u5206\u652F ${mainBranch} \u4E0A\uFF08\u5F53\u524D: ${currentBranch}\uFF09`
125
129
  };
126
130
 
127
131
  // src/constants/messages/merge.ts
@@ -218,7 +222,12 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
218
222
  /** 自动 sync 开始提示 */
219
223
  VALIDATE_AUTO_SYNC_START: (branch) => `\u6B63\u5728\u81EA\u52A8\u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch} ...`,
220
224
  /** 用户拒绝自动 sync */
221
- VALIDATE_AUTO_SYNC_DECLINED: (branch) => `\u8BF7\u624B\u52A8\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`
225
+ VALIDATE_AUTO_SYNC_DECLINED: (branch) => `\u8BF7\u624B\u52A8\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`,
226
+ /** 验证分支不存在 */
227
+ VALIDATE_BRANCH_NOT_FOUND: (validateBranch, branch) => `\u9A8C\u8BC1\u5206\u652F ${validateBranch} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C clawt create \u6216 clawt run \u521B\u5EFA\u5206\u652F ${branch}`,
228
+ /** validate 成功(含验证分支信息) */
229
+ VALIDATE_SUCCESS_WITH_BRANCH: (branch, validateBranch) => `\u2713 \u5DF2\u5207\u6362\u5230\u9A8C\u8BC1\u5206\u652F ${validateBranch} \u5E76\u5E94\u7528\u5206\u652F ${branch} \u7684\u53D8\u66F4
230
+ \u53EF\u4EE5\u5F00\u59CB\u9A8C\u8BC1\u4E86`
222
231
  };
223
232
 
224
233
  // src/constants/messages/sync.ts
@@ -243,7 +252,9 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
243
252
  /** sync 交互选择提示 */
244
253
  SYNC_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u540C\u6B65\u7684\u5206\u652F",
245
254
  /** sync 模糊匹配到多个结果提示 */
246
- SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`
255
+ SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
256
+ /** sync 后验证分支已重建提示 */
257
+ SYNC_VALIDATE_BRANCH_REBUILT: (validateBranch) => `\u9A8C\u8BC1\u5206\u652F ${validateBranch} \u5DF2\u91CD\u5EFA`
247
258
  };
248
259
 
249
260
  // src/constants/messages/resume.ts
@@ -284,7 +295,9 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
284
295
  REMOVE_PARTIAL_FAILURE: (failures) => `\u4EE5\u4E0B worktree \u79FB\u9664\u5931\u8D25\uFF1A
285
296
  ${failures.map((f) => ` \u2717 ${f.path}: ${f.error}`).join("\n")}`,
286
297
  /** 用户选择保留本地分支 */
287
- REMOVE_BRANCHES_KEPT: "\u5DF2\u4FDD\u7559\u672C\u5730\u5206\u652F\uFF0C\u53EF\u7A0D\u540E\u4F7F\u7528 git branch -D <\u5206\u652F\u540D> \u624B\u52A8\u5220\u9664"
298
+ REMOVE_BRANCHES_KEPT: "\u5DF2\u4FDD\u7559\u672C\u5730\u5206\u652F\uFF0C\u53EF\u7A0D\u540E\u4F7F\u7528 git branch -D <\u5206\u652F\u540D> \u624B\u52A8\u5220\u9664",
299
+ /** 确认删除本地分支和验证分支 */
300
+ REMOVE_CONFIRM_DELETE_BRANCHES: "\u662F\u5426\u540C\u65F6\u5220\u9664\u5BF9\u5E94\u7684\u672C\u5730\u5206\u652F\u548C\u9A8C\u8BC1\u5206\u652F\uFF1F"
288
301
  };
289
302
 
290
303
  // src/constants/messages/reset.ts
@@ -402,7 +415,7 @@ var PROJECTS_MESSAGES = {
402
415
  // src/constants/messages/completion.ts
403
416
  var COMPLETION_MESSAGES = {
404
417
  /** completion 命令的主描述 */
405
- COMPLETION_COMMAND_DESC: "\u751F\u6210\u548C\u5B89\u88C5 shell \u81EA\u52A8\u8865\u5168\u811A\u672C",
418
+ COMPLETION_COMMAND_DESC: "\u4E3A\u7EC8\u7AEF\u63D0\u4F9B shell \u81EA\u52A8\u8865\u5168\u529F\u80FD\uFF08bash/zsh\uFF09",
406
419
  /** bash 子命令描述 */
407
420
  COMPLETION_BASH_DESC: "\u8F93\u51FA bash \u81EA\u52A8\u8865\u5168\u811A\u672C",
408
421
  /** zsh 子命令描述 */
@@ -434,6 +447,22 @@ var UPDATE_MESSAGES = {
434
447
  UPDATE_HINT: (command) => `\u6267\u884C ${command} \u8FDB\u884C\u66F4\u65B0`
435
448
  };
436
449
 
450
+ // src/constants/messages/init.ts
451
+ var INIT_MESSAGES = {
452
+ /** init 成功 */
453
+ INIT_SUCCESS: (branch) => `\u2713 \u9879\u76EE\u521D\u59CB\u5316\u6210\u529F\uFF0C\u4E3B\u5DE5\u4F5C\u5206\u652F\u8BBE\u7F6E\u4E3A: ${branch}`,
454
+ /** init 更新 */
455
+ INIT_UPDATED: (oldBranch, newBranch) => `\u2713 \u5DF2\u5C06\u4E3B\u5DE5\u4F5C\u5206\u652F\u4ECE ${oldBranch} \u66F4\u65B0\u4E3A ${newBranch}`,
456
+ /** init show 查看当前项目完整配置(JSON 格式) */
457
+ INIT_SHOW: (configJson) => `${configJson}`,
458
+ /** init 未初始化 */
459
+ INIT_NOT_INITIALIZED: "\u9879\u76EE\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F",
460
+ /** 项目未初始化(requireProjectConfig 使用) */
461
+ PROJECT_NOT_INITIALIZED: "\u9879\u76EE\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F",
462
+ /** 项目配置缺少 clawtMainWorkBranch 字段 */
463
+ PROJECT_CONFIG_MISSING_BRANCH: "\u9879\u76EE\u914D\u7F6E\u7F3A\u5C11\u4E3B\u5DE5\u4F5C\u5206\u652F\u4FE1\u606F\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F"
464
+ };
465
+
437
466
  // src/constants/messages/index.ts
438
467
  var MESSAGES = {
439
468
  ...COMMON_MESSAGES,
@@ -449,7 +478,8 @@ var MESSAGES = {
449
478
  ...STATUS_MESSAGES,
450
479
  ...ALIAS_MESSAGES,
451
480
  ...PROJECTS_MESSAGES,
452
- ...COMPLETION_MESSAGES
481
+ ...COMPLETION_MESSAGES,
482
+ ...INIT_MESSAGES
453
483
  };
454
484
 
455
485
  // src/constants/exitCodes.ts
@@ -902,6 +932,12 @@ function getBranchCreatedAt(branchName, cwd) {
902
932
  return null;
903
933
  }
904
934
  }
935
+ function gitCheckout(branchName, cwd) {
936
+ execCommand(`git checkout ${branchName}`, { cwd });
937
+ }
938
+ function createBranch(branchName, cwd) {
939
+ execCommand(`git branch ${branchName}`, { cwd });
940
+ }
905
941
 
906
942
  // src/utils/formatter.ts
907
943
  import chalk3 from "chalk";
@@ -1076,8 +1112,8 @@ function validateClaudeCodeInstalled() {
1076
1112
  }
1077
1113
 
1078
1114
  // src/utils/worktree.ts
1079
- import { join as join3 } from "path";
1080
- import { existsSync as existsSync3, readdirSync as readdirSync2 } from "fs";
1115
+ import { join as join4 } from "path";
1116
+ import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
1081
1117
 
1082
1118
  // src/utils/fs.ts
1083
1119
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmdirSync, statSync } from "fs";
@@ -1118,10 +1154,152 @@ function calculateDirSize(dirPath) {
1118
1154
  return totalSize;
1119
1155
  }
1120
1156
 
1157
+ // src/utils/validate-branch.ts
1158
+ import Enquirer from "enquirer";
1159
+
1160
+ // src/utils/project-config.ts
1161
+ import { existsSync as existsSync3, readFileSync, writeFileSync } from "fs";
1162
+ import { join as join3 } from "path";
1163
+ function getProjectConfigPath(projectName) {
1164
+ return join3(PROJECTS_CONFIG_DIR, projectName, "config.json");
1165
+ }
1166
+ function loadProjectConfig() {
1167
+ const projectName = getProjectName();
1168
+ const configPath = getProjectConfigPath(projectName);
1169
+ if (!existsSync3(configPath)) {
1170
+ return null;
1171
+ }
1172
+ try {
1173
+ const raw = readFileSync(configPath, "utf-8");
1174
+ return JSON.parse(raw);
1175
+ } catch (error) {
1176
+ logger.warn(`\u9879\u76EE\u914D\u7F6E\u6587\u4EF6\u89E3\u6790\u5931\u8D25: ${error}`);
1177
+ return null;
1178
+ }
1179
+ }
1180
+ function saveProjectConfig(config2) {
1181
+ const projectName = getProjectName();
1182
+ const configPath = getProjectConfigPath(projectName);
1183
+ const projectDir = join3(PROJECTS_CONFIG_DIR, projectName);
1184
+ ensureDir(projectDir);
1185
+ writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf-8");
1186
+ logger.info(`\u9879\u76EE\u914D\u7F6E\u5DF2\u4FDD\u5B58: ${configPath}`);
1187
+ }
1188
+ function requireProjectConfig() {
1189
+ const config2 = loadProjectConfig();
1190
+ if (!config2) {
1191
+ throw new ClawtError(MESSAGES.PROJECT_NOT_INITIALIZED);
1192
+ }
1193
+ if (!config2.clawtMainWorkBranch) {
1194
+ throw new ClawtError(MESSAGES.PROJECT_CONFIG_MISSING_BRANCH);
1195
+ }
1196
+ return config2;
1197
+ }
1198
+ function getMainWorkBranch() {
1199
+ const config2 = requireProjectConfig();
1200
+ return config2.clawtMainWorkBranch;
1201
+ }
1202
+
1203
+ // src/utils/validate-branch.ts
1204
+ function getValidateBranchName(branchName) {
1205
+ return `${VALIDATE_BRANCH_PREFIX}${branchName}`;
1206
+ }
1207
+ function createValidateBranch(branchName, cwd) {
1208
+ const validateBranchName = getValidateBranchName(branchName);
1209
+ if (checkBranchExists(validateBranchName, cwd)) {
1210
+ logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA: ${validateBranchName}`);
1211
+ return;
1212
+ }
1213
+ createBranch(validateBranchName, cwd);
1214
+ logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u521B\u5EFA: ${validateBranchName}`);
1215
+ }
1216
+ function deleteValidateBranch(branchName, cwd) {
1217
+ const validateBranchName = getValidateBranchName(branchName);
1218
+ if (!checkBranchExists(validateBranchName, cwd)) {
1219
+ logger.info(`\u9A8C\u8BC1\u5206\u652F\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u5220\u9664: ${validateBranchName}`);
1220
+ return;
1221
+ }
1222
+ deleteBranch(validateBranchName, cwd);
1223
+ logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u5220\u9664: ${validateBranchName}`);
1224
+ }
1225
+ async function rebuildValidateBranch(branchName, cwd) {
1226
+ const mainBranch = getMainWorkBranch();
1227
+ const currentBranch = getCurrentBranch(cwd);
1228
+ if (currentBranch === mainBranch) {
1229
+ } else if (currentBranch.startsWith(VALIDATE_BRANCH_PREFIX)) {
1230
+ gitResetHard(cwd);
1231
+ gitCleanForce(cwd);
1232
+ gitCheckout(mainBranch, cwd);
1233
+ } else {
1234
+ if (!isWorkingDirClean(cwd)) {
1235
+ await handleDirtyWorkingDir(cwd);
1236
+ }
1237
+ gitCheckout(mainBranch, cwd);
1238
+ }
1239
+ deleteValidateBranch(branchName, cwd);
1240
+ createValidateBranch(branchName, cwd);
1241
+ logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u91CD\u5EFA: ${getValidateBranchName(branchName)}`);
1242
+ }
1243
+ async function handleDirtyWorkingDir(cwd) {
1244
+ printWarning("\u5F53\u524D\u5206\u652F\u6709\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF0C\u8BF7\u9009\u62E9\u5904\u7406\u65B9\u5F0F\uFF1A\n");
1245
+ const choice = await new Enquirer.Select({
1246
+ message: "\u9009\u62E9\u5904\u7406\u65B9\u5F0F",
1247
+ choices: [
1248
+ {
1249
+ name: "reset",
1250
+ message: "reset - \u4E22\u5F03\u6240\u6709\u66F4\u6539 (git reset --hard HEAD && git clean -fd)"
1251
+ },
1252
+ {
1253
+ name: "stash",
1254
+ message: "stash - \u6682\u5B58\u66F4\u6539 (git add . && git stash)"
1255
+ },
1256
+ {
1257
+ name: "exit",
1258
+ message: "exit - \u9000\u51FA\uFF0C\u624B\u52A8\u5904\u7406"
1259
+ }
1260
+ ],
1261
+ initial: 0
1262
+ }).run();
1263
+ if (choice === "exit") {
1264
+ throw new ClawtError("\u7528\u6237\u9009\u62E9\u9000\u51FA\uFF0C\u8BF7\u624B\u52A8\u5904\u7406\u5DE5\u4F5C\u533A\u66F4\u6539\u540E\u91CD\u8BD5");
1265
+ }
1266
+ if (choice === "reset") {
1267
+ gitResetHard(cwd);
1268
+ gitCleanForce(cwd);
1269
+ } else if (choice === "stash") {
1270
+ gitAddAll(cwd);
1271
+ gitStashPush("clawt:auto-stash", cwd);
1272
+ }
1273
+ if (!isWorkingDirClean(cwd)) {
1274
+ throw new ClawtError("\u5DE5\u4F5C\u533A\u4ECD\u7136\u4E0D\u5E72\u51C0\uFF0C\u8BF7\u624B\u52A8\u5904\u7406");
1275
+ }
1276
+ }
1277
+ async function ensureOnMainWorkBranch(cwd) {
1278
+ const mainBranch = getMainWorkBranch();
1279
+ const currentBranch = getCurrentBranch(cwd);
1280
+ if (currentBranch === mainBranch) {
1281
+ return;
1282
+ }
1283
+ if (currentBranch.startsWith(VALIDATE_BRANCH_PREFIX)) {
1284
+ logger.info(`\u5F53\u524D\u5728\u9A8C\u8BC1\u5206\u652F ${currentBranch} \u4E0A\uFF0C\u81EA\u52A8\u5207\u56DE\u4E3B\u5DE5\u4F5C\u5206\u652F ${mainBranch}`);
1285
+ if (!isWorkingDirClean(cwd)) {
1286
+ gitResetHard(cwd);
1287
+ gitCleanForce(cwd);
1288
+ }
1289
+ gitCheckout(mainBranch, cwd);
1290
+ return;
1291
+ }
1292
+ logger.info(`\u5F53\u524D\u5728\u5206\u652F ${currentBranch} \u4E0A\uFF0C\u9700\u5207\u6362\u5230\u4E3B\u5DE5\u4F5C\u5206\u652F ${mainBranch}`);
1293
+ if (!isWorkingDirClean(cwd)) {
1294
+ await handleDirtyWorkingDir(cwd);
1295
+ }
1296
+ gitCheckout(mainBranch, cwd);
1297
+ }
1298
+
1121
1299
  // src/utils/worktree.ts
1122
1300
  function getProjectWorktreeDir() {
1123
1301
  const projectName = getProjectName();
1124
- return join3(WORKTREES_DIR, projectName);
1302
+ return join4(WORKTREES_DIR, projectName);
1125
1303
  }
1126
1304
  function createWorktrees(branchName, count) {
1127
1305
  const sanitized = sanitizeBranchName(branchName);
@@ -1131,8 +1309,9 @@ function createWorktrees(branchName, count) {
1131
1309
  ensureDir(projectDir);
1132
1310
  const results = [];
1133
1311
  for (const name of branchNames) {
1134
- const worktreePath = join3(projectDir, name);
1312
+ const worktreePath = join4(projectDir, name);
1135
1313
  createWorktree(name, worktreePath);
1314
+ createValidateBranch(name);
1136
1315
  results.push({ path: worktreePath, branch: name });
1137
1316
  logger.info(`worktree \u521B\u5EFA\u5B8C\u6210: ${worktreePath} (\u5206\u652F: ${name})`);
1138
1317
  }
@@ -1144,8 +1323,9 @@ function createWorktreesByBranches(branchNames) {
1144
1323
  ensureDir(projectDir);
1145
1324
  const results = [];
1146
1325
  for (const name of branchNames) {
1147
- const worktreePath = join3(projectDir, name);
1326
+ const worktreePath = join4(projectDir, name);
1148
1327
  createWorktree(name, worktreePath);
1328
+ createValidateBranch(name);
1149
1329
  results.push({ path: worktreePath, branch: name });
1150
1330
  logger.info(`worktree \u521B\u5EFA\u5B8C\u6210: ${worktreePath} (\u5206\u652F: ${name})`);
1151
1331
  }
@@ -1153,7 +1333,7 @@ function createWorktreesByBranches(branchNames) {
1153
1333
  }
1154
1334
  function getProjectWorktrees() {
1155
1335
  const projectDir = getProjectWorktreeDir();
1156
- if (!existsSync3(projectDir)) {
1336
+ if (!existsSync4(projectDir)) {
1157
1337
  return [];
1158
1338
  }
1159
1339
  const worktreeListOutput = gitWorktreeList();
@@ -1166,7 +1346,7 @@ function getProjectWorktrees() {
1166
1346
  if (!entry.isDirectory()) {
1167
1347
  continue;
1168
1348
  }
1169
- const fullPath = join3(projectDir, entry.name);
1349
+ const fullPath = join4(projectDir, entry.name);
1170
1350
  if (registeredPaths.has(fullPath)) {
1171
1351
  worktrees.push({
1172
1352
  path: fullPath,
@@ -1181,6 +1361,7 @@ function cleanupWorktrees(worktrees) {
1181
1361
  try {
1182
1362
  removeWorktreeByPath(wt.path);
1183
1363
  deleteBranch(wt.branch);
1364
+ deleteValidateBranch(wt.branch);
1184
1365
  logger.info(`\u5DF2\u6E05\u7406 worktree \u548C\u5206\u652F: ${wt.branch}`);
1185
1366
  } catch (error) {
1186
1367
  logger.error(`\u6E05\u7406 worktree \u5931\u8D25: ${wt.path} - ${error}`);
@@ -1203,13 +1384,13 @@ function getWorktreeStatus(worktree) {
1203
1384
  }
1204
1385
 
1205
1386
  // src/utils/config.ts
1206
- import { existsSync as existsSync4, readFileSync, writeFileSync } from "fs";
1387
+ import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1207
1388
  function loadConfig() {
1208
- if (!existsSync4(CONFIG_PATH)) {
1389
+ if (!existsSync5(CONFIG_PATH)) {
1209
1390
  return { ...DEFAULT_CONFIG };
1210
1391
  }
1211
1392
  try {
1212
- const raw = readFileSync(CONFIG_PATH, "utf-8");
1393
+ const raw = readFileSync2(CONFIG_PATH, "utf-8");
1213
1394
  return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
1214
1395
  } catch {
1215
1396
  logger.warn(MESSAGES.CONFIG_CORRUPTED);
@@ -1218,13 +1399,13 @@ function loadConfig() {
1218
1399
  }
1219
1400
  }
1220
1401
  function writeConfig(config2) {
1221
- writeFileSync(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
1402
+ writeFileSync2(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
1222
1403
  }
1223
1404
  function writeDefaultConfig() {
1224
1405
  writeConfig(DEFAULT_CONFIG);
1225
1406
  }
1226
1407
  function saveConfig(config2) {
1227
- writeFileSync(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
1408
+ writeFileSync2(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
1228
1409
  }
1229
1410
  function getConfigValue(key) {
1230
1411
  const config2 = loadConfig();
@@ -1234,6 +1415,7 @@ function ensureClawtDirs() {
1234
1415
  ensureDir(CLAWT_HOME);
1235
1416
  ensureDir(LOGS_DIR);
1236
1417
  ensureDir(WORKTREES_DIR);
1418
+ ensureDir(PROJECTS_CONFIG_DIR);
1237
1419
  }
1238
1420
  function parseConcurrency(optionValue, configValue) {
1239
1421
  if (optionValue === void 0) {
@@ -1247,18 +1429,18 @@ function parseConcurrency(optionValue, configValue) {
1247
1429
  }
1248
1430
 
1249
1431
  // src/utils/prompt.ts
1250
- import Enquirer from "enquirer";
1432
+ import Enquirer2 from "enquirer";
1251
1433
 
1252
1434
  // src/utils/claude.ts
1253
1435
  import { spawnSync as spawnSync2 } from "child_process";
1254
- import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
1255
- import { join as join4 } from "path";
1436
+ import { existsSync as existsSync7, readdirSync as readdirSync3 } from "fs";
1437
+ import { join as join5 } from "path";
1256
1438
 
1257
1439
  // src/utils/terminal.ts
1258
1440
  import { execFileSync as execFileSync2 } from "child_process";
1259
- import { existsSync as existsSync5 } from "fs";
1441
+ import { existsSync as existsSync6 } from "fs";
1260
1442
  function isITerm2Installed() {
1261
- return existsSync5(ITERM2_APP_PATH);
1443
+ return existsSync6(ITERM2_APP_PATH);
1262
1444
  }
1263
1445
  function detectTerminalApp() {
1264
1446
  const configured = getConfigValue("terminalApp");
@@ -1331,8 +1513,8 @@ function encodeClaudeProjectPath(absolutePath) {
1331
1513
  }
1332
1514
  function hasClaudeSessionHistory(worktreePath) {
1333
1515
  const encodedName = encodeClaudeProjectPath(worktreePath);
1334
- const projectDir = join4(CLAUDE_PROJECTS_DIR, encodedName);
1335
- if (!existsSync6(projectDir)) {
1516
+ const projectDir = join5(CLAUDE_PROJECTS_DIR, encodedName);
1517
+ if (!existsSync7(projectDir)) {
1336
1518
  return false;
1337
1519
  }
1338
1520
  const entries = readdirSync3(projectDir);
@@ -1388,20 +1570,20 @@ function launchInteractiveClaudeInNewTerminal(worktree, hasPreviousSession) {
1388
1570
  }
1389
1571
 
1390
1572
  // src/utils/validate-snapshot.ts
1391
- import { join as join5 } from "path";
1392
- import { existsSync as existsSync7, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, readdirSync as readdirSync4, rmdirSync as rmdirSync2, statSync as statSync2 } from "fs";
1573
+ import { join as join6 } from "path";
1574
+ import { existsSync as existsSync8, readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync, readdirSync as readdirSync4, rmdirSync as rmdirSync2, statSync as statSync2 } from "fs";
1393
1575
  function getSnapshotPath(projectName, branchName) {
1394
- return join5(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.tree`);
1576
+ return join6(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.tree`);
1395
1577
  }
1396
1578
  function getSnapshotHeadPath(projectName, branchName) {
1397
- return join5(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.head`);
1579
+ return join6(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.head`);
1398
1580
  }
1399
1581
  function hasSnapshot(projectName, branchName) {
1400
- return existsSync7(getSnapshotPath(projectName, branchName));
1582
+ return existsSync8(getSnapshotPath(projectName, branchName));
1401
1583
  }
1402
1584
  function getSnapshotModifiedTime(projectName, branchName) {
1403
1585
  const snapshotPath = getSnapshotPath(projectName, branchName);
1404
- if (!existsSync7(snapshotPath)) return null;
1586
+ if (!existsSync8(snapshotPath)) return null;
1405
1587
  const stat = statSync2(snapshotPath);
1406
1588
  return stat.mtime.toISOString();
1407
1589
  }
@@ -1409,47 +1591,47 @@ function readSnapshot(projectName, branchName) {
1409
1591
  const snapshotPath = getSnapshotPath(projectName, branchName);
1410
1592
  const headPath = getSnapshotHeadPath(projectName, branchName);
1411
1593
  logger.debug(`\u8BFB\u53D6 validate \u5FEB\u7167: ${snapshotPath}`);
1412
- const treeHash = existsSync7(snapshotPath) ? readFileSync2(snapshotPath, "utf-8").trim() : "";
1413
- const headCommitHash = existsSync7(headPath) ? readFileSync2(headPath, "utf-8").trim() : "";
1594
+ const treeHash = existsSync8(snapshotPath) ? readFileSync3(snapshotPath, "utf-8").trim() : "";
1595
+ const headCommitHash = existsSync8(headPath) ? readFileSync3(headPath, "utf-8").trim() : "";
1414
1596
  return { treeHash, headCommitHash };
1415
1597
  }
1416
1598
  function writeSnapshot(projectName, branchName, treeHash, headCommitHash) {
1417
1599
  const snapshotPath = getSnapshotPath(projectName, branchName);
1418
1600
  const headPath = getSnapshotHeadPath(projectName, branchName);
1419
- const snapshotDir = join5(VALIDATE_SNAPSHOTS_DIR, projectName);
1601
+ const snapshotDir = join6(VALIDATE_SNAPSHOTS_DIR, projectName);
1420
1602
  ensureDir(snapshotDir);
1421
- writeFileSync2(snapshotPath, treeHash, "utf-8");
1422
- writeFileSync2(headPath, headCommitHash, "utf-8");
1603
+ writeFileSync3(snapshotPath, treeHash, "utf-8");
1604
+ writeFileSync3(headPath, headCommitHash, "utf-8");
1423
1605
  logger.info(`\u5DF2\u4FDD\u5B58 validate \u5FEB\u7167: ${snapshotPath}, ${headPath}`);
1424
1606
  }
1425
1607
  function removeSnapshot(projectName, branchName) {
1426
1608
  const snapshotPath = getSnapshotPath(projectName, branchName);
1427
1609
  const headPath = getSnapshotHeadPath(projectName, branchName);
1428
- if (existsSync7(snapshotPath)) {
1610
+ if (existsSync8(snapshotPath)) {
1429
1611
  unlinkSync(snapshotPath);
1430
1612
  logger.info(`\u5DF2\u5220\u9664 validate \u5FEB\u7167: ${snapshotPath}`);
1431
1613
  }
1432
- if (existsSync7(headPath)) {
1614
+ if (existsSync8(headPath)) {
1433
1615
  unlinkSync(headPath);
1434
1616
  logger.info(`\u5DF2\u5220\u9664 validate \u5FEB\u7167: ${headPath}`);
1435
1617
  }
1436
1618
  }
1437
1619
  function getProjectSnapshotBranches(projectName) {
1438
- const projectDir = join5(VALIDATE_SNAPSHOTS_DIR, projectName);
1439
- if (!existsSync7(projectDir)) {
1620
+ const projectDir = join6(VALIDATE_SNAPSHOTS_DIR, projectName);
1621
+ if (!existsSync8(projectDir)) {
1440
1622
  return [];
1441
1623
  }
1442
1624
  const files = readdirSync4(projectDir);
1443
1625
  return files.filter((f) => f.endsWith(".tree")).map((f) => f.replace(/\.tree$/, ""));
1444
1626
  }
1445
1627
  function removeProjectSnapshots(projectName) {
1446
- const projectDir = join5(VALIDATE_SNAPSHOTS_DIR, projectName);
1447
- if (!existsSync7(projectDir)) {
1628
+ const projectDir = join6(VALIDATE_SNAPSHOTS_DIR, projectName);
1629
+ if (!existsSync8(projectDir)) {
1448
1630
  return;
1449
1631
  }
1450
1632
  const files = readdirSync4(projectDir);
1451
1633
  for (const file of files) {
1452
- unlinkSync(join5(projectDir, file));
1634
+ unlinkSync(join6(projectDir, file));
1453
1635
  }
1454
1636
  try {
1455
1637
  rmdirSync2(projectDir);
@@ -1459,7 +1641,7 @@ function removeProjectSnapshots(projectName) {
1459
1641
  }
1460
1642
 
1461
1643
  // src/utils/worktree-matcher.ts
1462
- import Enquirer2 from "enquirer";
1644
+ import Enquirer3 from "enquirer";
1463
1645
  import { statSync as statSync3 } from "fs";
1464
1646
  function findExactMatch(worktrees, branchName) {
1465
1647
  return worktrees.find((wt) => wt.branch === branchName);
@@ -1469,7 +1651,7 @@ function findFuzzyMatches(worktrees, keyword) {
1469
1651
  return worktrees.filter((wt) => wt.branch.toLowerCase().includes(lowerKeyword));
1470
1652
  }
1471
1653
  async function promptSelectBranch(worktrees, message) {
1472
- const selectedBranch = await new Enquirer2.Select({
1654
+ const selectedBranch = await new Enquirer3.Select({
1473
1655
  message,
1474
1656
  choices: worktrees.map((wt) => ({
1475
1657
  name: wt.branch,
@@ -1487,7 +1669,7 @@ async function promptMultiSelectBranches(worktrees, message) {
1487
1669
  { name: SELECT_ALL_NAME, message: SELECT_ALL_LABEL },
1488
1670
  ...branchChoices
1489
1671
  ];
1490
- const MultiSelect = Enquirer2.MultiSelect;
1672
+ const MultiSelect = Enquirer3.MultiSelect;
1491
1673
  class MultiSelectWithSelectAll extends MultiSelect {
1492
1674
  space() {
1493
1675
  if (!this.focused) return;
@@ -1642,7 +1824,7 @@ async function promptGroupedMultiSelectBranches(worktrees, message) {
1642
1824
  const groupMembershipMap = buildGroupMembershipMap(groups);
1643
1825
  const groupSelectAllNames = new Set(groupMembershipMap.keys());
1644
1826
  const allBranchNames = new Set(worktrees.map((wt) => wt.branch));
1645
- const MultiSelect = Enquirer2.MultiSelect;
1827
+ const MultiSelect = Enquirer3.MultiSelect;
1646
1828
  class MultiSelectWithGroupSelectAll extends MultiSelect {
1647
1829
  space() {
1648
1830
  if (!this.focused) return;
@@ -1998,7 +2180,7 @@ var ProgressRenderer = class {
1998
2180
 
1999
2181
  // src/utils/task-file.ts
2000
2182
  import { resolve } from "path";
2001
- import { existsSync as existsSync8, readFileSync as readFileSync3 } from "fs";
2183
+ import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
2002
2184
  var TASK_BLOCK_REGEX = /<!-- CLAWT-TASKS:START -->([\s\S]*?)<!-- CLAWT-TASKS:END -->/g;
2003
2185
  var BRANCH_LINE_REGEX = /^#\s*branch:\s*(.+)$/;
2004
2186
  var EMPTY_TASKS_MESSAGE = "\u4EFB\u52A1\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A";
@@ -2044,10 +2226,10 @@ function parseTaskFile(content, options) {
2044
2226
  }
2045
2227
  function loadTaskFile(filePath, options) {
2046
2228
  const absolutePath = resolve(filePath);
2047
- if (!existsSync8(absolutePath)) {
2229
+ if (!existsSync9(absolutePath)) {
2048
2230
  throw new ClawtError(MESSAGES.TASK_FILE_NOT_FOUND(absolutePath));
2049
2231
  }
2050
- const content = readFileSync3(absolutePath, "utf-8");
2232
+ const content = readFileSync4(absolutePath, "utf-8");
2051
2233
  const entries = parseTaskFile(content, options);
2052
2234
  if (entries.length === 0) {
2053
2235
  throw new ClawtError(MESSAGES.TASK_FILE_EMPTY);
@@ -2382,7 +2564,7 @@ async function executeBatchTasks(worktrees, tasks, concurrency) {
2382
2564
 
2383
2565
  // src/utils/dry-run.ts
2384
2566
  import chalk5 from "chalk";
2385
- import { join as join6 } from "path";
2567
+ import { join as join7 } from "path";
2386
2568
  var DRY_RUN_TASK_DESC_MAX_LENGTH = 80;
2387
2569
  function truncateTaskDesc(task) {
2388
2570
  const oneLine = task.replace(/\n/g, " ").trim();
@@ -2410,7 +2592,7 @@ function printDryRunPreview(branchNames, tasks, concurrency) {
2410
2592
  let hasConflict = false;
2411
2593
  for (let i = 0; i < branchNames.length; i++) {
2412
2594
  const branch = branchNames[i];
2413
- const worktreePath = join6(projectDir, branch);
2595
+ const worktreePath = join7(projectDir, branch);
2414
2596
  const exists = checkBranchExists(branch);
2415
2597
  if (exists) hasConflict = true;
2416
2598
  const indexLabel = `[${i + 1}/${branchNames.length}]`;
@@ -2448,7 +2630,7 @@ function applyAliases(program2, aliases) {
2448
2630
 
2449
2631
  // src/utils/config-strategy.ts
2450
2632
  import chalk6 from "chalk";
2451
- import Enquirer3 from "enquirer";
2633
+ import Enquirer4 from "enquirer";
2452
2634
  function isValidConfigKey(key) {
2453
2635
  return key in DEFAULT_CONFIG;
2454
2636
  }
@@ -2500,7 +2682,7 @@ async function promptBooleanValue(key, currentValue) {
2500
2682
  { name: "true", message: "true" },
2501
2683
  { name: "false", message: "false" }
2502
2684
  ];
2503
- const selected = await new Enquirer3.Select({
2685
+ const selected = await new Enquirer4.Select({
2504
2686
  message: MESSAGES.CONFIG_INPUT_PROMPT(key),
2505
2687
  choices,
2506
2688
  initial: currentValue ? 0 : 1
@@ -2508,7 +2690,7 @@ async function promptBooleanValue(key, currentValue) {
2508
2690
  return selected === "true";
2509
2691
  }
2510
2692
  async function promptNumberValue(key, currentValue) {
2511
- const input = await new Enquirer3.Input({
2693
+ const input = await new Enquirer4.Input({
2512
2694
  message: MESSAGES.CONFIG_INPUT_PROMPT(key),
2513
2695
  initial: String(currentValue),
2514
2696
  validate: (val) => {
@@ -2523,28 +2705,28 @@ async function promptEnumValue(key, currentValue, allowedValues) {
2523
2705
  name: v,
2524
2706
  message: v
2525
2707
  }));
2526
- return await new Enquirer3.Select({
2708
+ return await new Enquirer4.Select({
2527
2709
  message: MESSAGES.CONFIG_INPUT_PROMPT(key),
2528
2710
  choices,
2529
2711
  initial: allowedValues.indexOf(currentValue)
2530
2712
  }).run();
2531
2713
  }
2532
2714
  async function promptStringValue(key, currentValue) {
2533
- return await new Enquirer3.Input({
2715
+ return await new Enquirer4.Input({
2534
2716
  message: MESSAGES.CONFIG_INPUT_PROMPT(key),
2535
2717
  initial: currentValue
2536
2718
  }).run();
2537
2719
  }
2538
2720
 
2539
2721
  // src/utils/update-checker.ts
2540
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
2722
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
2541
2723
  import { execSync as execSync3 } from "child_process";
2542
2724
  import { request } from "https";
2543
2725
  import chalk7 from "chalk";
2544
2726
  import stringWidth2 from "string-width";
2545
2727
  function readUpdateCache() {
2546
2728
  try {
2547
- const raw = readFileSync4(UPDATE_CHECK_PATH, "utf-8");
2729
+ const raw = readFileSync5(UPDATE_CHECK_PATH, "utf-8");
2548
2730
  return JSON.parse(raw);
2549
2731
  } catch {
2550
2732
  return null;
@@ -2552,7 +2734,7 @@ function readUpdateCache() {
2552
2734
  }
2553
2735
  function writeUpdateCache(cache) {
2554
2736
  try {
2555
- writeFileSync3(UPDATE_CHECK_PATH, JSON.stringify(cache, null, 2), "utf-8");
2737
+ writeFileSync4(UPDATE_CHECK_PATH, JSON.stringify(cache, null, 2), "utf-8");
2556
2738
  } catch {
2557
2739
  }
2558
2740
  }
@@ -2667,10 +2849,60 @@ async function checkForUpdates(currentVersion) {
2667
2849
  }
2668
2850
  }
2669
2851
 
2852
+ // src/utils/json.ts
2853
+ function primitiveToString(value) {
2854
+ if (value === void 0) {
2855
+ return "undefined";
2856
+ }
2857
+ if (value === null) {
2858
+ return "null";
2859
+ }
2860
+ if (typeof value === "symbol") {
2861
+ return value.toString();
2862
+ }
2863
+ if (typeof value === "function") {
2864
+ return `[Function: ${value.name || "anonymous"}]`;
2865
+ }
2866
+ return String(value);
2867
+ }
2868
+ function safeStringify(value, indent = 2) {
2869
+ if (value === null || typeof value !== "object") {
2870
+ return primitiveToString(value);
2871
+ }
2872
+ try {
2873
+ const seen = /* @__PURE__ */ new WeakSet();
2874
+ return JSON.stringify(
2875
+ value,
2876
+ (_key, val) => {
2877
+ if (typeof val === "bigint") {
2878
+ return val.toString();
2879
+ }
2880
+ if (typeof val === "undefined" || typeof val === "function" || typeof val === "symbol") {
2881
+ return primitiveToString(val);
2882
+ }
2883
+ if (typeof val === "object" && val !== null) {
2884
+ if (seen.has(val)) {
2885
+ return "[Circular]";
2886
+ }
2887
+ seen.add(val);
2888
+ }
2889
+ return val;
2890
+ },
2891
+ indent
2892
+ );
2893
+ } catch {
2894
+ try {
2895
+ return JSON.stringify(String(value), null, indent);
2896
+ } catch {
2897
+ return "[Unserializable]";
2898
+ }
2899
+ }
2900
+ }
2901
+
2670
2902
  // src/commands/list.ts
2671
2903
  import chalk8 from "chalk";
2672
2904
  function registerListCommand(program2) {
2673
- program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
2905
+ program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree\uFF08\u652F\u6301 --json \u683C\u5F0F\u8F93\u51FA\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
2674
2906
  handleList(options);
2675
2907
  });
2676
2908
  }
@@ -2720,12 +2952,13 @@ function printListAsText(projectName, worktrees) {
2720
2952
 
2721
2953
  // src/commands/create.ts
2722
2954
  function registerCreateCommand(program2) {
2723
- 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) => {
2724
- handleCreate(options);
2955
+ program2.command("create").description("\u6279\u91CF\u521B\u5EFA worktree \u53CA\u5BF9\u5E94\u5206\u652F\uFF08\u542B\u9A8C\u8BC1\u5206\u652F\uFF09").requiredOption("-b, --branch <branchName>", "\u5206\u652F\u540D").option("-n, --number <count>", "\u521B\u5EFA\u6570\u91CF", "1").action(async (options) => {
2956
+ await handleCreate(options);
2725
2957
  });
2726
2958
  }
2727
- function handleCreate(options) {
2959
+ async function handleCreate(options) {
2728
2960
  validateMainWorktree();
2961
+ await ensureOnMainWorkBranch();
2729
2962
  const count = Number(options.number);
2730
2963
  if (!Number.isInteger(count) || count <= 0) {
2731
2964
  throw new ClawtError(
@@ -2741,6 +2974,7 @@ function handleCreate(options) {
2741
2974
  printInfo(`\u76EE\u5F55\u8DEF\u5F84${index + 1}\uFF1A`);
2742
2975
  printInfo(` ${wt.path}`);
2743
2976
  printInfo(` \u5206\u652F\u540D: ${wt.branch}`);
2977
+ printInfo(` \u9A8C\u8BC1\u5206\u652F: ${getValidateBranchName(wt.branch)}`);
2744
2978
  printSeparator();
2745
2979
  });
2746
2980
  }
@@ -2753,7 +2987,7 @@ var REMOVE_RESOLVE_MESSAGES = {
2753
2987
  noMatch: MESSAGES.REMOVE_NO_MATCH
2754
2988
  };
2755
2989
  function registerRemoveCommand(program2) {
2756
- 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) => {
2990
+ program2.command("remove").description("\u79FB\u9664 worktree\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D/\u591A\u9009/\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) => {
2757
2991
  await handleRemove(options);
2758
2992
  });
2759
2993
  }
@@ -2774,13 +3008,13 @@ async function handleRemove(options) {
2774
3008
  }
2775
3009
  printInfo("\u5373\u5C06\u79FB\u9664\u4EE5\u4E0B worktree \u53CA\u672C\u5730\u5206\u652F\uFF1A\n");
2776
3010
  worktreesToRemove.forEach((wt, index) => {
2777
- printInfo(` ${index + 1}. ${wt.path} \u2192 \u5206\u652F: ${wt.branch}`);
3011
+ printInfo(` ${index + 1}. ${wt.path} \u2192 \u5206\u652F: ${wt.branch} \u9A8C\u8BC1\u5206\u652F: ${getValidateBranchName(wt.branch)}`);
2778
3012
  });
2779
3013
  printInfo("");
2780
3014
  const autoDelete = getConfigValue("autoDeleteBranch");
2781
3015
  let shouldDeleteBranch = autoDelete;
2782
3016
  if (!autoDelete) {
2783
- shouldDeleteBranch = await confirmAction("\u662F\u5426\u540C\u65F6\u5220\u9664\u5BF9\u5E94\u7684\u672C\u5730\u5206\u652F\uFF1F");
3017
+ shouldDeleteBranch = await confirmAction(MESSAGES.REMOVE_CONFIRM_DELETE_BRANCHES);
2784
3018
  if (!shouldDeleteBranch) {
2785
3019
  printHint(MESSAGES.REMOVE_BRANCHES_KEPT);
2786
3020
  }
@@ -2788,10 +3022,12 @@ async function handleRemove(options) {
2788
3022
  const failures = [];
2789
3023
  for (const wt of worktreesToRemove) {
2790
3024
  try {
3025
+ await ensureOnMainWorkBranch();
2791
3026
  removeWorktreeByPath(wt.path);
2792
3027
  if (shouldDeleteBranch) {
2793
3028
  deleteBranch(wt.branch);
2794
3029
  }
3030
+ deleteValidateBranch(wt.branch);
2795
3031
  removeSnapshot(projectName, wt.branch);
2796
3032
  printSuccess(MESSAGES.WORKTREE_REMOVED(wt.path));
2797
3033
  } catch (error) {
@@ -2816,7 +3052,7 @@ async function handleRemove(options) {
2816
3052
 
2817
3053
  // src/commands/run.ts
2818
3054
  function registerRunCommand(program2) {
2819
- 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").option("-d, --dry-run", "\u9884\u89C8\u6A21\u5F0F\uFF0C\u4EC5\u5C55\u793A\u4EFB\u52A1\u8BA1\u5212\u4E0D\u5B9E\u9645\u6267\u884C").action(async (options) => {
3055
+ program2.command("run").description("\u6279\u91CF\u521B\u5EFA worktree + \u542F\u52A8 Claude Code \u6267\u884C\u4EFB\u52A1\uFF08\u652F\u6301\u4EFB\u52A1\u6587\u4EF6\uFF09").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").option("--dry-run", "\u9884\u89C8\u6A21\u5F0F\uFF0C\u4EC5\u5C55\u793A\u4EFB\u52A1\u8BA1\u5212\u4E0D\u5B9E\u9645\u6267\u884C").action(async (options) => {
2820
3056
  await handleRun(options);
2821
3057
  });
2822
3058
  }
@@ -2854,6 +3090,9 @@ function handleDryRunFromFile(options) {
2854
3090
  }
2855
3091
  async function handleRun(options) {
2856
3092
  validateMainWorktree();
3093
+ if (!options.dryRun) {
3094
+ await ensureOnMainWorkBranch();
3095
+ }
2857
3096
  if (options.file && options.tasks) {
2858
3097
  throw new ClawtError(MESSAGES.FILE_AND_TASKS_CONFLICT);
2859
3098
  }
@@ -2909,7 +3148,7 @@ var RESUME_RESOLVE_MESSAGES = {
2909
3148
  noMatch: MESSAGES.RESUME_NO_MATCH
2910
3149
  };
2911
3150
  function registerResumeCommand(program2) {
2912
- program2.command("resume").description("\u5728\u5DF2\u6709 worktree \u4E2D\u6062\u590D Claude Code \u4EA4\u4E92\u5F0F\u4F1A\u8BDD").option("-b, --branch <branchName>", "\u8981\u6062\u590D\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").action(async (options) => {
3151
+ program2.command("resume").description("\u5728\u5DF2\u6709 worktree \u4E2D\u6062\u590D Claude Code \u4F1A\u8BDD\uFF08\u652F\u6301\u591A\u9009\u6279\u91CF\u6062\u590D\uFF09").option("-b, --branch <branchName>", "\u8981\u6062\u590D\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").action(async (options) => {
2913
3152
  await handleResume(options);
2914
3153
  });
2915
3154
  }
@@ -2961,9 +3200,6 @@ async function handleBatchResume(worktrees) {
2961
3200
  printSuccess(MESSAGES.RESUME_ALL_SUCCESS(worktrees.length));
2962
3201
  }
2963
3202
 
2964
- // src/commands/validate.ts
2965
- import Enquirer4 from "enquirer";
2966
-
2967
3203
  // src/commands/sync.ts
2968
3204
  function registerSyncCommand(program2) {
2969
3205
  program2.command("sync").description("\u5C06\u4E3B\u5206\u652F\u6700\u65B0\u4EE3\u7801\u540C\u6B65\u5230\u76EE\u6807 worktree").option("-b, --branch <branchName>", "\u8981\u540C\u6B65\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").action(async (options) => {
@@ -2993,9 +3229,9 @@ function mergeMainBranch(worktreePath, mainBranch) {
2993
3229
  throw new ClawtError(`\u5408\u5E76 ${mainBranch} \u5931\u8D25`);
2994
3230
  }
2995
3231
  }
2996
- function executeSyncForBranch(targetWorktreePath, branch) {
3232
+ async function executeSyncForBranch(targetWorktreePath, branch) {
2997
3233
  const mainWorktreePath = getGitTopLevel();
2998
- const mainBranch = getCurrentBranch(mainWorktreePath);
3234
+ const mainBranch = getMainWorkBranch();
2999
3235
  if (!isWorkingDirClean(targetWorktreePath)) {
3000
3236
  autoSaveChanges(targetWorktreePath, branch);
3001
3237
  }
@@ -3011,15 +3247,20 @@ function executeSyncForBranch(targetWorktreePath, branch) {
3011
3247
  logger.info(`\u5DF2\u6E05\u9664\u5206\u652F ${branch} \u7684 validate \u5FEB\u7167`);
3012
3248
  }
3013
3249
  printSuccess(MESSAGES.SYNC_SUCCESS(branch, mainBranch));
3250
+ await rebuildValidateBranch(branch, mainWorktreePath);
3251
+ const validateBranchName = getValidateBranchName(branch);
3252
+ printInfo(MESSAGES.SYNC_VALIDATE_BRANCH_REBUILT(validateBranchName));
3014
3253
  return { success: true, hasConflict: false };
3015
3254
  }
3016
3255
  async function handleSync(options) {
3017
3256
  validateMainWorktree();
3257
+ requireProjectConfig();
3258
+ await ensureOnMainWorkBranch();
3018
3259
  logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
3019
3260
  const worktrees = getProjectWorktrees();
3020
3261
  const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
3021
3262
  const { path: targetWorktreePath, branch } = worktree;
3022
- executeSyncForBranch(targetWorktreePath, branch);
3263
+ await executeSyncForBranch(targetWorktreePath, branch);
3023
3264
  }
3024
3265
 
3025
3266
  // src/commands/validate.ts
@@ -3030,43 +3271,12 @@ var VALIDATE_RESOLVE_MESSAGES = {
3030
3271
  noMatch: MESSAGES.VALIDATE_NO_MATCH
3031
3272
  };
3032
3273
  function registerValidateCommand(program2) {
3033
- program2.command("validate").description("\u5728\u4E3B worktree \u9A8C\u8BC1\u67D0\u4E2A worktree \u5206\u652F\u7684\u53D8\u66F4").option("-b, --branch <branchName>", "\u8981\u9A8C\u8BC1\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").option("--clean", "\u6E05\u7406 validate \u72B6\u6001\uFF08\u91CD\u7F6E\u4E3B worktree \u5E76\u5220\u9664\u5FEB\u7167\uFF09").option("-r, --run <command>", "validate \u6210\u529F\u540E\u5728\u4E3B worktree \u4E2D\u6267\u884C\u7684\u547D\u4EE4").action(async (options) => {
3274
+ program2.command("validate").description("\u5728\u4E3B worktree \u9A8C\u8BC1\u67D0\u4E2A worktree \u5206\u652F\u7684\u53D8\u66F4\uFF08\u901A\u8FC7\u9A8C\u8BC1\u5206\u652F\uFF09").option("-b, --branch <branchName>", "\u8981\u9A8C\u8BC1\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").option("--clean", "\u6E05\u7406 validate \u72B6\u6001\uFF08\u91CD\u7F6E\u4E3B worktree \u5E76\u5220\u9664\u5FEB\u7167\uFF09").option("-r, --run <command>", "validate \u6210\u529F\u540E\u5728\u4E3B worktree \u4E2D\u6267\u884C\u7684\u547D\u4EE4").action(async (options) => {
3034
3275
  await handleValidate(options);
3035
3276
  });
3036
3277
  }
3037
3278
  async function handleDirtyMainWorktree(mainWorktreePath) {
3038
- printWarning("\u4E3B worktree \u5F53\u524D\u5206\u652F\u6709\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF0C\u8BF7\u9009\u62E9\u5904\u7406\u65B9\u5F0F\uFF1A\n");
3039
- const choice = await new Enquirer4.Select({
3040
- message: "\u9009\u62E9\u5904\u7406\u65B9\u5F0F",
3041
- choices: [
3042
- {
3043
- name: "reset",
3044
- message: "reset (\u63A8\u8350) - \u4E22\u5F03\u6240\u6709\u66F4\u6539 (git reset --hard HEAD && git clean -fd)"
3045
- },
3046
- {
3047
- name: "stash",
3048
- message: "stash - \u6682\u5B58\u66F4\u6539 (git add . && git stash)"
3049
- },
3050
- {
3051
- name: "exit",
3052
- message: "exit - \u9000\u51FA\uFF0C\u624B\u52A8\u5904\u7406"
3053
- }
3054
- ],
3055
- initial: 0
3056
- }).run();
3057
- if (choice === "exit") {
3058
- throw new ClawtError("\u7528\u6237\u9009\u62E9\u9000\u51FA");
3059
- }
3060
- if (choice === "reset") {
3061
- gitResetHard(mainWorktreePath);
3062
- gitCleanForce(mainWorktreePath);
3063
- } else if (choice === "stash") {
3064
- gitAddAll(mainWorktreePath);
3065
- gitStashPush("clawt:auto-stash", mainWorktreePath);
3066
- }
3067
- if (!isWorkingDirClean(mainWorktreePath)) {
3068
- throw new ClawtError("\u5DE5\u4F5C\u533A\u4ECD\u7136\u4E0D\u5E72\u51C0\uFF0C\u8BF7\u624B\u52A8\u5904\u7406");
3069
- }
3279
+ await handleDirtyWorkingDir(mainWorktreePath);
3070
3280
  }
3071
3281
  function migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted) {
3072
3282
  let didTempCommit = false;
@@ -3109,7 +3319,7 @@ async function handlePatchApplyFailure(targetWorktreePath, branchName) {
3109
3319
  return;
3110
3320
  }
3111
3321
  printInfo(MESSAGES.VALIDATE_AUTO_SYNC_START(branchName));
3112
- const syncResult = executeSyncForBranch(targetWorktreePath, branchName);
3322
+ const syncResult = await executeSyncForBranch(targetWorktreePath, branchName);
3113
3323
  }
3114
3324
  function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName) {
3115
3325
  gitAddAll(mainWorktreePath);
@@ -3121,6 +3331,7 @@ function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName) {
3121
3331
  }
3122
3332
  async function handleValidateClean(options) {
3123
3333
  validateMainWorktree();
3334
+ requireProjectConfig();
3124
3335
  const projectName = getProjectName();
3125
3336
  const mainWorktreePath = getGitTopLevel();
3126
3337
  const worktrees = getProjectWorktrees();
@@ -3141,17 +3352,24 @@ async function handleValidateClean(options) {
3141
3352
  gitResetHard(mainWorktreePath);
3142
3353
  gitCleanForce(mainWorktreePath);
3143
3354
  }
3355
+ await ensureOnMainWorkBranch(mainWorktreePath);
3144
3356
  removeSnapshot(projectName, branchName);
3145
3357
  printSuccess(MESSAGES.VALIDATE_CLEANED(branchName));
3146
3358
  }
3147
3359
  async function handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
3360
+ const validateBranchName = getValidateBranchName(branchName);
3361
+ if (!checkBranchExists(validateBranchName)) {
3362
+ throw new ClawtError(MESSAGES.VALIDATE_BRANCH_NOT_FOUND(validateBranchName, branchName));
3363
+ }
3364
+ gitCheckout(validateBranchName, mainWorktreePath);
3148
3365
  const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
3149
3366
  if (!result.success) {
3367
+ await ensureOnMainWorkBranch(mainWorktreePath);
3150
3368
  await handlePatchApplyFailure(targetWorktreePath, branchName);
3151
3369
  return;
3152
3370
  }
3153
3371
  saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName);
3154
- printSuccess(MESSAGES.VALIDATE_SUCCESS(branchName));
3372
+ printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
3155
3373
  }
3156
3374
  async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
3157
3375
  const { treeHash: oldTreeHash, headCommitHash: oldHeadCommitHash } = readSnapshot(projectName, branchName);
@@ -3159,8 +3377,17 @@ async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, p
3159
3377
  gitResetHard(mainWorktreePath);
3160
3378
  gitCleanForce(mainWorktreePath);
3161
3379
  }
3380
+ const validateBranchName = getValidateBranchName(branchName);
3381
+ if (!checkBranchExists(validateBranchName)) {
3382
+ throw new ClawtError(MESSAGES.VALIDATE_BRANCH_NOT_FOUND(validateBranchName, branchName));
3383
+ }
3384
+ const currentBranch = getCurrentBranch(mainWorktreePath);
3385
+ if (currentBranch !== validateBranchName) {
3386
+ gitCheckout(validateBranchName, mainWorktreePath);
3387
+ }
3162
3388
  const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
3163
3389
  if (!result.success) {
3390
+ await ensureOnMainWorkBranch(mainWorktreePath);
3164
3391
  await handlePatchApplyFailure(targetWorktreePath, branchName);
3165
3392
  return;
3166
3393
  }
@@ -3175,7 +3402,7 @@ async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, p
3175
3402
  } else if (oldChangePatch.length > 0) {
3176
3403
  logger.warn("\u65E7\u53D8\u66F4 patch \u4E0E\u5F53\u524D HEAD \u51B2\u7A81\uFF0C\u964D\u7EA7\u4E3A\u5168\u91CF\u6A21\u5F0F");
3177
3404
  printWarning(MESSAGES.INCREMENTAL_VALIDATE_FALLBACK);
3178
- printSuccess(MESSAGES.VALIDATE_SUCCESS(branchName));
3405
+ printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
3179
3406
  return;
3180
3407
  }
3181
3408
  } else {
@@ -3184,7 +3411,7 @@ async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, p
3184
3411
  } catch (error) {
3185
3412
  logger.warn(`\u589E\u91CF read-tree \u5931\u8D25: ${error}`);
3186
3413
  printWarning(MESSAGES.INCREMENTAL_VALIDATE_FALLBACK);
3187
- printSuccess(MESSAGES.VALIDATE_SUCCESS(branchName));
3414
+ printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
3188
3415
  return;
3189
3416
  }
3190
3417
  printSuccess(MESSAGES.INCREMENTAL_VALIDATE_SUCCESS(branchName));
@@ -3248,6 +3475,7 @@ async function handleValidate(options) {
3248
3475
  return;
3249
3476
  }
3250
3477
  validateMainWorktree();
3478
+ requireProjectConfig();
3251
3479
  const projectName = getProjectName();
3252
3480
  const mainWorktreePath = getGitTopLevel();
3253
3481
  const worktrees = getProjectWorktrees();
@@ -3280,7 +3508,7 @@ async function handleValidate(options) {
3280
3508
 
3281
3509
  // src/commands/merge.ts
3282
3510
  function registerMergeCommand(program2) {
3283
- program2.command("merge").description("\u5408\u5E76\u67D0\u4E2A\u5DF2\u9A8C\u8BC1\u7684 worktree \u5206\u652F\u5230\u4E3B worktree").option("-b, --branch <branchName>", "\u8981\u5408\u5E76\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").option("-m, --message <message>", "\u63D0\u4EA4\u4FE1\u606F\uFF08\u5DE5\u4F5C\u533A\u6709\u4FEE\u6539\u65F6\u5FC5\u586B\uFF09").action(async (options) => {
3511
+ program2.command("merge").description("\u5408\u5E76\u67D0\u4E2A\u5DF2\u9A8C\u8BC1\u7684 worktree \u5206\u652F\u5230\u4E3B worktree").option("-b, --branch <branchName>", "\u8981\u5408\u5E76\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\u4F9B\u9009\u62E9\uFF09").option("-m, --message <commitMessage>", "\u63D0\u4EA4\u4FE1\u606F\uFF08\u76EE\u6807 worktree \u5DE5\u4F5C\u533A\u6709\u4FEE\u6539\u65F6\u5FC5\u586B\uFF09").action(async (options) => {
3284
3512
  await handleMerge(options);
3285
3513
  });
3286
3514
  }
@@ -3298,7 +3526,7 @@ async function handleSquashIfNeeded(targetWorktreePath, mainWorktreePath, branch
3298
3526
  if (!shouldSquash) {
3299
3527
  return false;
3300
3528
  }
3301
- const mainBranch = getCurrentBranch(mainWorktreePath);
3529
+ const mainBranch = getMainWorkBranch();
3302
3530
  const mergeBase = gitMergeBase(mainBranch, branchName, mainWorktreePath);
3303
3531
  logger.info(`squash: merge-base = ${mergeBase}, \u5206\u652F = ${branchName}`);
3304
3532
  gitResetSoftTo(mergeBase, targetWorktreePath);
@@ -3325,6 +3553,7 @@ function cleanupWorktreeAndBranch(worktreePath, branchName) {
3325
3553
  async function handleMerge(options) {
3326
3554
  validateMainWorktree();
3327
3555
  const mainWorktreePath = getGitTopLevel();
3556
+ await ensureOnMainWorkBranch(mainWorktreePath);
3328
3557
  logger.info(`merge \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}\uFF0C\u63D0\u4EA4\u4FE1\u606F: ${options.message ?? "(\u672A\u63D0\u4F9B)"}`);
3329
3558
  const worktrees = getProjectWorktrees();
3330
3559
  const worktree = await resolveTargetWorktree(worktrees, MERGE_RESOLVE_MESSAGES, options.branch);
@@ -3492,6 +3721,7 @@ function registerResetCommand(program2) {
3492
3721
  }
3493
3722
  async function handleReset() {
3494
3723
  validateMainWorktree();
3724
+ requireProjectConfig();
3495
3725
  const mainWorktreePath = getGitTopLevel();
3496
3726
  logger.info("reset \u547D\u4EE4\u6267\u884C");
3497
3727
  if (!isWorkingDirClean(mainWorktreePath)) {
@@ -3516,7 +3746,7 @@ async function handleReset() {
3516
3746
  // src/commands/status.ts
3517
3747
  import chalk10 from "chalk";
3518
3748
  function registerStatusCommand(program2) {
3519
- program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
3749
+ program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8\uFF08\u652F\u6301 --json \u683C\u5F0F\u8F93\u51FA\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
3520
3750
  handleStatus(options);
3521
3751
  });
3522
3752
  }
@@ -3764,7 +3994,7 @@ function handleAliasRemove(alias) {
3764
3994
  printSuccess(MESSAGES.ALIAS_REMOVE_SUCCESS(alias));
3765
3995
  }
3766
3996
  function registerAliasCommand(program2) {
3767
- const aliasCmd = program2.command("alias").description("\u7BA1\u7406\u547D\u4EE4\u522B\u540D").action(() => {
3997
+ const aliasCmd = program2.command("alias").description("\u7BA1\u7406\u547D\u4EE4\u522B\u540D\uFF08\u5217\u51FA / \u8BBE\u7F6E / \u79FB\u9664\uFF09").action(() => {
3768
3998
  handleAliasList();
3769
3999
  });
3770
4000
  aliasCmd.command("list").description("\u5217\u51FA\u6240\u6709\u522B\u540D").action(() => {
@@ -3779,8 +4009,8 @@ function registerAliasCommand(program2) {
3779
4009
  }
3780
4010
 
3781
4011
  // src/commands/projects.ts
3782
- import { existsSync as existsSync9, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
3783
- import { join as join7 } from "path";
4012
+ import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
4013
+ import { join as join8 } from "path";
3784
4014
  import chalk12 from "chalk";
3785
4015
  function registerProjectsCommand(program2) {
3786
4016
  program2.command("projects [name]").description("\u5C55\u793A\u6240\u6709\u9879\u76EE\u7684 worktree \u6982\u89C8\uFF0C\u6216\u67E5\u770B\u6307\u5B9A\u9879\u76EE\u7684 worktree \u8BE6\u60C5").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((name, options) => {
@@ -3804,8 +4034,8 @@ function handleProjectsOverview(json) {
3804
4034
  printProjectsOverviewAsText(result);
3805
4035
  }
3806
4036
  function handleProjectDetail(name, json) {
3807
- const projectDir = join7(WORKTREES_DIR, name);
3808
- if (!existsSync9(projectDir)) {
4037
+ const projectDir = join8(WORKTREES_DIR, name);
4038
+ if (!existsSync10(projectDir)) {
3809
4039
  printError(MESSAGES.PROJECTS_NOT_FOUND(name));
3810
4040
  process.exit(1);
3811
4041
  }
@@ -3818,7 +4048,7 @@ function handleProjectDetail(name, json) {
3818
4048
  printProjectDetailAsText(result);
3819
4049
  }
3820
4050
  function collectProjectsOverview() {
3821
- if (!existsSync9(WORKTREES_DIR)) {
4051
+ if (!existsSync10(WORKTREES_DIR)) {
3822
4052
  return { projects: [], totalProjects: 0, totalDiskUsage: 0 };
3823
4053
  }
3824
4054
  const entries = readdirSync5(WORKTREES_DIR, { withFileTypes: true });
@@ -3827,7 +4057,7 @@ function collectProjectsOverview() {
3827
4057
  if (!entry.isDirectory()) {
3828
4058
  continue;
3829
4059
  }
3830
- const projectDir = join7(WORKTREES_DIR, entry.name);
4060
+ const projectDir = join8(WORKTREES_DIR, entry.name);
3831
4061
  const overview = collectSingleProjectOverview(entry.name, projectDir);
3832
4062
  projects.push(overview);
3833
4063
  }
@@ -3844,7 +4074,7 @@ function collectSingleProjectOverview(name, projectDir) {
3844
4074
  const worktreeDirs = subEntries.filter((e) => e.isDirectory());
3845
4075
  const worktreeCount = worktreeDirs.length;
3846
4076
  const diskUsage = calculateDirSize(projectDir);
3847
- const lastActiveTime = resolveProjectLastActiveTime(projectDir, worktreeDirs.map((e) => join7(projectDir, e.name)));
4077
+ const lastActiveTime = resolveProjectLastActiveTime(projectDir, worktreeDirs.map((e) => join8(projectDir, e.name)));
3848
4078
  return {
3849
4079
  name,
3850
4080
  worktreeCount,
@@ -3859,7 +4089,7 @@ function collectProjectDetail(name, projectDir) {
3859
4089
  if (!entry.isDirectory()) {
3860
4090
  continue;
3861
4091
  }
3862
- const wtPath = join7(projectDir, entry.name);
4092
+ const wtPath = join8(projectDir, entry.name);
3863
4093
  const detail = collectSingleWorktreeDetail(entry.name, wtPath);
3864
4094
  worktrees.push(detail);
3865
4095
  }
@@ -3958,7 +4188,7 @@ function printWorktreeDetailItem(wt) {
3958
4188
  }
3959
4189
 
3960
4190
  // src/commands/completion.ts
3961
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync11 } from "fs";
4191
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync12 } from "fs";
3962
4192
  import { resolve as resolve2 } from "path";
3963
4193
  import { homedir as homedir2 } from "os";
3964
4194
 
@@ -4009,14 +4239,14 @@ compdef _clawt_completion clawt
4009
4239
  }
4010
4240
 
4011
4241
  // src/utils/completion-engine.ts
4012
- import { existsSync as existsSync10, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
4013
- import { join as join8, dirname, basename as basename2 } from "path";
4242
+ import { existsSync as existsSync11, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
4243
+ import { join as join9, dirname, basename as basename2 } from "path";
4014
4244
  function completeFilePath(partial) {
4015
4245
  const cwd = process.cwd();
4016
4246
  const hasDir = partial.includes("/");
4017
- const searchDir = hasDir ? join8(cwd, dirname(partial)) : cwd;
4247
+ const searchDir = hasDir ? join9(cwd, dirname(partial)) : cwd;
4018
4248
  const prefix = hasDir ? basename2(partial) : partial;
4019
- if (!existsSync10(searchDir)) {
4249
+ if (!existsSync11(searchDir)) {
4020
4250
  return [];
4021
4251
  }
4022
4252
  const entries = readdirSync6(searchDir);
@@ -4025,7 +4255,7 @@ function completeFilePath(partial) {
4025
4255
  for (const entry of entries) {
4026
4256
  if (!entry.startsWith(prefix)) continue;
4027
4257
  if (entry.startsWith(".")) continue;
4028
- const fullPath = join8(searchDir, entry);
4258
+ const fullPath = join9(searchDir, entry);
4029
4259
  try {
4030
4260
  const stat = statSync5(fullPath);
4031
4261
  if (stat.isDirectory()) {
@@ -4114,15 +4344,15 @@ function generateCompletions(program2, args) {
4114
4344
 
4115
4345
  // src/commands/completion.ts
4116
4346
  function appendToFile(filePath, content) {
4117
- if (existsSync11(filePath)) {
4118
- const current = readFileSync5(filePath, "utf-8");
4347
+ if (existsSync12(filePath)) {
4348
+ const current = readFileSync6(filePath, "utf-8");
4119
4349
  if (current.includes("clawt completion")) {
4120
4350
  printInfo(MESSAGES.COMPLETION_INSTALL_EXISTS + ": " + filePath);
4121
4351
  return;
4122
4352
  }
4123
4353
  content = current + content;
4124
4354
  }
4125
- writeFileSync4(filePath, content, "utf-8");
4355
+ writeFileSync5(filePath, content, "utf-8");
4126
4356
  printSuccess(MESSAGES.COMPLETION_INSTALL_SUCCESS + ": " + filePath);
4127
4357
  printInfo(MESSAGES.COMPLETION_INSTALL_RESTART(filePath));
4128
4358
  }
@@ -4169,6 +4399,37 @@ function registerCompletionCommand(program2) {
4169
4399
  });
4170
4400
  }
4171
4401
 
4402
+ // src/commands/init.ts
4403
+ import { Command as Cmd } from "commander";
4404
+ function registerInitCommand(program2) {
4405
+ const initCmd = program2.command("init").description("\u521D\u59CB\u5316\u9879\u76EE\u7EA7\u914D\u7F6E\uFF0C\u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F").option("-b, --branch <branchName>", "\u6307\u5B9A\u4E3B\u5DE5\u4F5C\u5206\u652F\u540D\uFF08\u9ED8\u8BA4\u4F7F\u7528\u5F53\u524D\u5206\u652F\uFF09").action(async (options) => {
4406
+ await handleInit(options);
4407
+ });
4408
+ initCmd.addCommand(
4409
+ new Cmd("show").description("\u5C55\u793A\u5F53\u524D\u9879\u76EE\u7684 init \u914D\u7F6E").action(() => {
4410
+ handleInitShow();
4411
+ })
4412
+ );
4413
+ }
4414
+ function handleInitShow() {
4415
+ validateMainWorktree();
4416
+ const config2 = requireProjectConfig();
4417
+ const configJson = safeStringify(config2);
4418
+ printInfo(MESSAGES.INIT_SHOW(configJson));
4419
+ }
4420
+ async function handleInit(options) {
4421
+ validateMainWorktree();
4422
+ const existingConfig = loadProjectConfig();
4423
+ const branchName = options.branch || getCurrentBranch();
4424
+ logger.info(`init \u547D\u4EE4\u6267\u884C\uFF0C\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`);
4425
+ saveProjectConfig({ clawtMainWorkBranch: branchName });
4426
+ if (existingConfig) {
4427
+ printSuccess(MESSAGES.INIT_UPDATED(existingConfig.clawtMainWorkBranch, branchName));
4428
+ } else {
4429
+ printSuccess(MESSAGES.INIT_SUCCESS(branchName));
4430
+ }
4431
+ }
4432
+
4172
4433
  // src/index.ts
4173
4434
  var require2 = createRequire(import.meta.url);
4174
4435
  var { version } = require2("../package.json");
@@ -4194,6 +4455,7 @@ registerStatusCommand(program);
4194
4455
  registerAliasCommand(program);
4195
4456
  registerProjectsCommand(program);
4196
4457
  registerCompletionCommand(program);
4458
+ registerInitCommand(program);
4197
4459
  var config = loadConfig();
4198
4460
  applyAliases(program, config.aliases);
4199
4461
  process.on("uncaughtException", (error) => {