clawt 3.4.1 → 3.4.3

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 (40) hide show
  1. package/README.md +22 -13
  2. package/dist/index.js +141 -56
  3. package/dist/postinstall.js +15 -2
  4. package/package.json +1 -1
  5. package/src/commands/cover-validate.ts +2 -4
  6. package/src/commands/create.ts +5 -2
  7. package/src/commands/home.ts +3 -4
  8. package/src/commands/init.ts +7 -3
  9. package/src/commands/list.ts +2 -2
  10. package/src/commands/merge.ts +7 -2
  11. package/src/commands/remove.ts +2 -4
  12. package/src/commands/reset.ts +2 -4
  13. package/src/commands/resume.ts +2 -2
  14. package/src/commands/run.ts +4 -2
  15. package/src/commands/status.ts +24 -3
  16. package/src/commands/sync.ts +4 -4
  17. package/src/commands/validate.ts +3 -7
  18. package/src/constants/interactive-panel.ts +2 -2
  19. package/src/constants/messages/common.ts +8 -0
  20. package/src/constants/messages/index.ts +2 -2
  21. package/src/constants/messages/interactive-panel.ts +27 -0
  22. package/src/constants/messages/status.ts +9 -0
  23. package/src/types/status.ts +4 -0
  24. package/src/utils/index.ts +2 -2
  25. package/src/utils/interactive-panel-render.ts +28 -2
  26. package/src/utils/project-config.ts +39 -2
  27. package/src/utils/validation.ts +50 -0
  28. package/tests/unit/commands/cover-validate.test.ts +3 -1
  29. package/tests/unit/commands/create.test.ts +7 -5
  30. package/tests/unit/commands/init.test.ts +4 -1
  31. package/tests/unit/commands/list.test.ts +5 -5
  32. package/tests/unit/commands/merge.test.ts +3 -1
  33. package/tests/unit/commands/remove.test.ts +5 -3
  34. package/tests/unit/commands/reset.test.ts +3 -1
  35. package/tests/unit/commands/resume.test.ts +5 -5
  36. package/tests/unit/commands/run.test.ts +3 -1
  37. package/tests/unit/commands/status.test.ts +7 -1
  38. package/tests/unit/commands/sync.test.ts +3 -1
  39. package/tests/unit/commands/validate.test.ts +3 -1
  40. package/tests/unit/utils/validation.test.ts +43 -1
package/README.md CHANGED
@@ -17,24 +17,33 @@ npm i -g clawt
17
17
  ## 快速开始
18
18
 
19
19
  ```bash
20
- # 1. 在项目根目录(包含 .git 的目录)下初始化
20
+ # 1. 在项目根目录(包含 .git 的目录)下初始化,确认主工作分支
21
21
  clawt init
22
22
 
23
- # 2. 并行执行 3 个任务,每个任务在独立的 worktree 中运行
24
- clawt run -b <branch-1>
25
- clawt run -b <branch-2>
26
- clawt run -b <branch-3>
23
+ # 2. 并行执行任务,每个任务在独立的 worktree 中运行
24
+ clawt run -b feat-login
25
+ clawt run -b feat-search
26
+ clawt run -b fix-bug
27
27
 
28
- # 3. 查看所有 worktree 状态
29
- clawt status
30
-
31
- # 4. 验证某个分支的变更(在主 worktree 中测试)
32
- clawt validate -b branch-1
33
-
34
- # 5. 确认无误后合并到主分支
35
- clawt merge -b branch-1 -m "feat: 实现xxx功能"
28
+ # 3. 打开交互式面板,实时查看所有任务状态,一站式完成后续操作
29
+ clawt status -i
36
30
  ```
37
31
 
32
+ `clawt status -i` 提供实时刷新的 TUI 面板,用方向键选中 worktree 后可直接按快捷键操作:
33
+
34
+ | 快捷键 | 操作 | 等同命令 |
35
+ | ------ | ---- | -------- |
36
+ | `v` | 验证分支变更 | `clawt validate -b <branch>` |
37
+ | `m` | 合并到主分支 | `clawt merge -b <branch>` |
38
+ | `r` | 恢复 Claude Code 会话 | `clawt resume -b <branch>` |
39
+ | `s` | 同步主分支代码 | `clawt sync -b <branch>` |
40
+ | `d` | 删除 worktree | `clawt remove -b <branch>` |
41
+ | `q` | 退出面板 | — |
42
+
43
+ 示例:
44
+ ![](https://p3.ssl.qhimg.com/d/inn/8a3779be2486/upload_screenshot_1772675658.png)
45
+ > 所有操作也可通过独立命令执行,详见下方「命令一览」。
46
+
38
47
  ## 命令一览
39
48
 
40
49
  > 除 `config`、`alias`、`projects`、`completion` 外,其余命令需在**主 worktree 的仓库根目录**下执行。`-b` 参数支持模糊匹配。大部分操作命令(`run`、`create`、`validate`、`merge` 等)需要先执行 `clawt init`。
package/dist/index.js CHANGED
@@ -30,6 +30,8 @@ var COMMON_MESSAGES = {
30
30
  GIT_NOT_INSTALLED: "Git \u672A\u5B89\u88C5\u6216\u4E0D\u5728 PATH \u4E2D\uFF0C\u8BF7\u5148\u5B89\u88C5 Git",
31
31
  /** Claude Code CLI 未安装 */
32
32
  CLAUDE_NOT_INSTALLED: "Claude Code CLI \u672A\u5B89\u88C5\uFF0C\u8BF7\u5148\u5B89\u88C5\uFF1Anpm install -g @anthropic-ai/claude-code",
33
+ /** HEAD 不存在(仓库无任何 commit) */
34
+ HEAD_NOT_FOUND: "\u5F53\u524D\u4ED3\u5E93\u5C1A\u672A\u521B\u5EFA\u4EFB\u4F55\u63D0\u4EA4\uFF0C\u8BF7\u5148\u6267\u884C git commit \u521B\u5EFA\u9996\u6B21\u63D0\u4EA4\u540E\u518D\u4F7F\u7528 clawt",
33
35
  /** 分支已存在 */
34
36
  BRANCH_EXISTS: (name) => `\u5206\u652F ${name} \u5DF2\u5B58\u5728\uFF0C\u65E0\u6CD5\u521B\u5EFA`,
35
37
  /** 分支名清理后为空 */
@@ -59,7 +61,11 @@ var COMMON_MESSAGES = {
59
61
  /** 分隔线 */
60
62
  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",
61
63
  /** 粗分隔线 */
62
- 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"
64
+ 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",
65
+ /** 守卫检测:配置的主工作分支已不存在 */
66
+ GUARD_BRANCH_NOT_EXISTS: (branchName) => `\u914D\u7F6E\u7684\u4E3B\u5DE5\u4F5C\u5206\u652F ${branchName} \u5DF2\u4E0D\u5B58\u5728\uFF0C\u8BF7\u6267\u884C clawt init \u91CD\u65B0\u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F`,
67
+ /** 守卫检测:当前分支与配置的主工作分支不一致 */
68
+ GUARD_BRANCH_MISMATCH: (configuredBranch, currentBranch) => `\u5F53\u524D\u5206\u652F ${currentBranch} \u4E0E\u914D\u7F6E\u7684\u4E3B\u5DE5\u4F5C\u5206\u652F ${configuredBranch} \u4E0D\u4E00\u81F4\uFF0C\u5982\u9700\u66F4\u65B0\u8BF7\u6267\u884C clawt init`
63
69
  };
64
70
 
65
71
  // src/constants/messages/run.ts
@@ -371,7 +377,13 @@ var STATUS_MESSAGES = {
371
377
  /** status 上次验证时间标签 */
372
378
  STATUS_LAST_VALIDATED: (relativeTime) => `\u4E0A\u6B21\u9A8C\u8BC1: ${relativeTime}`,
373
379
  /** status 未验证警示 */
374
- STATUS_NOT_VALIDATED: "\u2717 \u672A\u9A8C\u8BC1"
380
+ STATUS_NOT_VALIDATED: "\u2717 \u672A\u9A8C\u8BC1",
381
+ /** status 配置的主工作分支(正常状态) */
382
+ STATUS_CONFIGURED_BRANCH: (branchName) => `\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`,
383
+ /** status 配置的主工作分支已不存在 */
384
+ STATUS_CONFIGURED_BRANCH_DELETED: (branchName) => `\u2717 \u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}\uFF08\u5DF2\u4E0D\u5B58\u5728\uFF0C\u8BF7\u6267\u884C clawt init \u91CD\u65B0\u8BBE\u7F6E\uFF09`,
385
+ /** status 当前分支与配置的主工作分支不一致 */
386
+ STATUS_CONFIGURED_BRANCH_MISMATCH: (branchName) => `\u26A0 \u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}\uFF08\u5F53\u524D\u5206\u652F\u4E0D\u4E00\u81F4\uFF0C\u5982\u9700\u66F4\u65B0\u8BF7\u6267\u884C clawt init\uFF09`
375
387
  };
376
388
 
377
389
  // src/constants/messages/alias.ts
@@ -529,7 +541,7 @@ var PANEL_SHORTCUT_KEYS = {
529
541
  QUIT: "q"
530
542
  };
531
543
  var PANEL_DATE_SEPARATOR_PREFIX = "\u2550\u2550\u2550\u2550";
532
- var PANEL_FIXED_ROWS = 4;
544
+ var PANEL_FIXED_ROWS = 5;
533
545
 
534
546
  // src/constants/messages/interactive-panel.ts
535
547
  var SHORTCUT_LABELS = {
@@ -556,6 +568,10 @@ var PANEL_NO_WORKTREES = "(\u65E0\u6D3B\u8DC3 worktree)";
556
568
  var PANEL_PRESS_ENTER_TO_RETURN = chalk.gray("\n\u6309 Enter \u8FD4\u56DE\u9762\u677F...");
557
569
  var PANEL_NOT_TTY = "\u4EA4\u4E92\u5F0F\u9762\u677F\u9700\u8981 TTY \u7EC8\u7AEF\u73AF\u5883\uFF0C\u8BF7\u76F4\u63A5\u5728\u7EC8\u7AEF\u4E2D\u8FD0\u884C clawt status -i";
558
570
  var PANEL_TITLE = (projectName) => chalk.bold.cyan(`\u9879\u76EE\u72B6\u6001\u603B\u89C8: ${projectName}`);
571
+ var PANEL_CONFIGURED_BRANCH = (branchName) => chalk.gray(`\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`);
572
+ var PANEL_CONFIGURED_BRANCH_DELETED = (branchName) => chalk.red(`\u2717 \u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}\uFF08\u5DF2\u4E0D\u5B58\u5728\uFF09`);
573
+ var PANEL_CONFIGURED_BRANCH_MISMATCH = (branchName) => chalk.yellow(`\u26A0 \u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}\uFF08\u4E0D\u4E00\u81F4\uFF09`);
574
+ var PANEL_NOT_INITIALIZED = chalk.gray("\u672A\u521D\u59CB\u5316\uFF08\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F\uFF09");
559
575
 
560
576
  // src/constants/messages/index.ts
561
577
  var MESSAGES = {
@@ -1215,31 +1231,9 @@ function validateBranchesNotExist(branchNames) {
1215
1231
  }
1216
1232
  }
1217
1233
 
1218
- // src/utils/validation.ts
1219
- function validateMainWorktree() {
1220
- try {
1221
- const gitCommonDir = getGitCommonDir();
1222
- if (gitCommonDir !== ".git") {
1223
- throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
1224
- }
1225
- } catch (error) {
1226
- if (error instanceof ClawtError) {
1227
- throw error;
1228
- }
1229
- throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
1230
- }
1231
- }
1232
- function validateClaudeCodeInstalled() {
1233
- try {
1234
- execCommand("claude --version");
1235
- } catch {
1236
- throw new ClawtError(MESSAGES.CLAUDE_NOT_INSTALLED);
1237
- }
1238
- }
1239
-
1240
- // src/utils/worktree.ts
1241
- import { join as join4 } from "path";
1242
- import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
1234
+ // src/utils/project-config.ts
1235
+ import { existsSync as existsSync3, readFileSync, writeFileSync } from "fs";
1236
+ import { join as join3 } from "path";
1243
1237
 
1244
1238
  // src/utils/fs.ts
1245
1239
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmdirSync, statSync } from "fs";
@@ -1280,12 +1274,7 @@ function calculateDirSize(dirPath) {
1280
1274
  return totalSize;
1281
1275
  }
1282
1276
 
1283
- // src/utils/validate-branch.ts
1284
- import Enquirer from "enquirer";
1285
-
1286
1277
  // src/utils/project-config.ts
1287
- import { existsSync as existsSync3, readFileSync, writeFileSync } from "fs";
1288
- import { join as join3 } from "path";
1289
1278
  function getProjectConfigPath(projectName) {
1290
1279
  return join3(PROJECTS_CONFIG_DIR, projectName, "config.json");
1291
1280
  }
@@ -1325,12 +1314,80 @@ function getMainWorkBranch() {
1325
1314
  const config2 = requireProjectConfig();
1326
1315
  return config2.clawtMainWorkBranch;
1327
1316
  }
1317
+ function guardMainWorkBranchExists(cwd) {
1318
+ const config2 = requireProjectConfig();
1319
+ const mainBranch = config2.clawtMainWorkBranch;
1320
+ if (!checkBranchExists(mainBranch, cwd)) {
1321
+ throw new ClawtError(MESSAGES.GUARD_BRANCH_NOT_EXISTS(mainBranch));
1322
+ }
1323
+ }
1324
+ async function guardMainWorkBranch(cwd) {
1325
+ guardMainWorkBranchExists(cwd);
1326
+ const config2 = requireProjectConfig();
1327
+ const mainBranch = config2.clawtMainWorkBranch;
1328
+ const currentBranch = getCurrentBranch(cwd);
1329
+ if (currentBranch !== mainBranch && !currentBranch.startsWith(VALIDATE_BRANCH_PREFIX)) {
1330
+ printWarning(MESSAGES.GUARD_BRANCH_MISMATCH(mainBranch, currentBranch));
1331
+ const confirmed = await confirmAction("\u662F\u5426\u7EE7\u7EED\u6267\u884C\uFF1F");
1332
+ if (!confirmed) {
1333
+ throw new ClawtError(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
1334
+ }
1335
+ }
1336
+ }
1328
1337
  function getValidateRunCommand() {
1329
1338
  const config2 = loadProjectConfig();
1330
1339
  return config2?.validateRunCommand || void 0;
1331
1340
  }
1332
1341
 
1342
+ // src/utils/validation.ts
1343
+ function validateMainWorktree() {
1344
+ try {
1345
+ const gitCommonDir = getGitCommonDir();
1346
+ if (gitCommonDir !== ".git") {
1347
+ throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
1348
+ }
1349
+ } catch (error) {
1350
+ if (error instanceof ClawtError) {
1351
+ throw error;
1352
+ }
1353
+ throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
1354
+ }
1355
+ }
1356
+ function validateClaudeCodeInstalled() {
1357
+ try {
1358
+ execCommand("claude --version");
1359
+ } catch {
1360
+ throw new ClawtError(MESSAGES.CLAUDE_NOT_INSTALLED);
1361
+ }
1362
+ }
1363
+ function validateHeadExists() {
1364
+ try {
1365
+ execCommand("git rev-parse --verify HEAD");
1366
+ } catch {
1367
+ throw new ClawtError(MESSAGES.HEAD_NOT_FOUND);
1368
+ }
1369
+ }
1370
+ function runPreChecks(options) {
1371
+ if (options.mainWorktree) {
1372
+ validateMainWorktree();
1373
+ }
1374
+ if (options.headExists) {
1375
+ validateHeadExists();
1376
+ }
1377
+ if (options.projectConfig) {
1378
+ requireProjectConfig();
1379
+ }
1380
+ if (options.branchExists) {
1381
+ guardMainWorkBranchExists();
1382
+ }
1383
+ }
1384
+
1385
+ // src/utils/worktree.ts
1386
+ import { join as join4 } from "path";
1387
+ import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
1388
+
1333
1389
  // src/utils/validate-branch.ts
1390
+ import Enquirer from "enquirer";
1334
1391
  function getValidateBranchName(branchName) {
1335
1392
  return `${VALIDATE_BRANCH_PREFIX}${branchName}`;
1336
1393
  }
@@ -3178,6 +3235,7 @@ function buildSeparatorWithHint(cols, hint) {
3178
3235
  function buildPanelFrame(statusResult, selectedIndex, scrollOffset, rows, cols, countdown) {
3179
3236
  const lines = [];
3180
3237
  lines.push(PANEL_TITLE(statusResult.main.projectName));
3238
+ lines.push(renderConfiguredBranchLine(statusResult.main));
3181
3239
  lines.push(renderSnapshotSummary(statusResult.snapshots.total, statusResult.snapshots.orphaned));
3182
3240
  const visibleRows = calculateVisibleRows(rows);
3183
3241
  if (statusResult.worktrees.length === 0) {
@@ -3301,6 +3359,18 @@ function formatChangeStatusLabel(status) {
3301
3359
  return chalk9.gray(MESSAGES.STATUS_CHANGE_CLEAN);
3302
3360
  }
3303
3361
  }
3362
+ function renderConfiguredBranchLine(main2) {
3363
+ if (main2.configuredMainBranch === null) {
3364
+ return PANEL_NOT_INITIALIZED;
3365
+ }
3366
+ if (main2.configuredBranchExists === false) {
3367
+ return PANEL_CONFIGURED_BRANCH_DELETED(main2.configuredMainBranch);
3368
+ }
3369
+ if (main2.branch !== main2.configuredMainBranch && !main2.branch.startsWith(VALIDATE_BRANCH_PREFIX)) {
3370
+ return PANEL_CONFIGURED_BRANCH_MISMATCH(main2.configuredMainBranch);
3371
+ }
3372
+ return PANEL_CONFIGURED_BRANCH(main2.configuredMainBranch);
3373
+ }
3304
3374
  function renderSnapshotSummary(total, orphaned) {
3305
3375
  return PANEL_SNAPSHOT_SUMMARY(total, orphaned);
3306
3376
  }
@@ -3748,7 +3818,7 @@ function registerListCommand(program2) {
3748
3818
  });
3749
3819
  }
3750
3820
  function handleList(options) {
3751
- validateMainWorktree();
3821
+ runPreChecks({ mainWorktree: true });
3752
3822
  const projectName = getProjectName();
3753
3823
  const worktrees = getProjectWorktrees();
3754
3824
  logger.info(`list \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}\uFF0C\u5171 ${worktrees.length} \u4E2A worktree`);
@@ -3798,7 +3868,8 @@ function registerCreateCommand(program2) {
3798
3868
  });
3799
3869
  }
3800
3870
  async function handleCreate(options) {
3801
- validateMainWorktree();
3871
+ runPreChecks({ mainWorktree: true, headExists: true });
3872
+ await guardMainWorkBranch();
3802
3873
  await ensureOnMainWorkBranch();
3803
3874
  const count = Number(options.number);
3804
3875
  if (!Number.isInteger(count) || count <= 0) {
@@ -3833,8 +3904,7 @@ function registerRemoveCommand(program2) {
3833
3904
  });
3834
3905
  }
3835
3906
  async function handleRemove(options) {
3836
- validateMainWorktree();
3837
- requireProjectConfig();
3907
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
3838
3908
  const projectName = getProjectName();
3839
3909
  logger.info(`remove \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}`);
3840
3910
  const allWorktrees = getProjectWorktrees();
@@ -3940,8 +4010,9 @@ function handleDryRunFromFile(options) {
3940
4010
  printDryRunPreview(branchNames, tasks, concurrency);
3941
4011
  }
3942
4012
  async function handleRun(options) {
3943
- validateMainWorktree();
4013
+ runPreChecks({ mainWorktree: true, headExists: true });
3944
4014
  if (!options.dryRun) {
4015
+ await guardMainWorkBranch();
3945
4016
  await ensureOnMainWorkBranch();
3946
4017
  }
3947
4018
  if (options.file && options.tasks) {
@@ -4004,7 +4075,7 @@ function registerResumeCommand(program2) {
4004
4075
  });
4005
4076
  }
4006
4077
  async function handleResume(options) {
4007
- validateMainWorktree();
4078
+ runPreChecks({ mainWorktree: true, headExists: true });
4008
4079
  validateClaudeCodeInstalled();
4009
4080
  logger.info(`resume \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F\u8FC7\u6EE4: ${options.branch ?? "(\u65E0)"}`);
4010
4081
  const worktrees = getProjectWorktrees();
@@ -4105,8 +4176,8 @@ async function executeSyncForBranch(targetWorktreePath, branch) {
4105
4176
  return { success: true, hasConflict: false };
4106
4177
  }
4107
4178
  async function handleSync(options) {
4108
- validateMainWorktree();
4109
- requireProjectConfig();
4179
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
4180
+ await guardMainWorkBranch();
4110
4181
  logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
4111
4182
  const worktrees = getProjectWorktrees();
4112
4183
  const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
@@ -4139,8 +4210,7 @@ async function handlePatchApplyFailure(targetWorktreePath, branchName) {
4139
4210
  const syncResult = await executeSyncForBranch(targetWorktreePath, branchName);
4140
4211
  }
4141
4212
  async function handleValidateClean(options) {
4142
- validateMainWorktree();
4143
- requireProjectConfig();
4213
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
4144
4214
  const projectName = getProjectName();
4145
4215
  const mainWorktreePath = getGitTopLevel();
4146
4216
  const worktrees = getProjectWorktrees();
@@ -4225,8 +4295,7 @@ async function handleValidate(options) {
4225
4295
  await handleValidateClean(options);
4226
4296
  return;
4227
4297
  }
4228
- validateMainWorktree();
4229
- requireProjectConfig();
4298
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
4230
4299
  const projectName = getProjectName();
4231
4300
  const mainWorktreePath = getGitTopLevel();
4232
4301
  const worktrees = getProjectWorktrees();
@@ -4291,8 +4360,7 @@ function computeIncrementalPatch(snapshotTreeHash, mainWorktreePath) {
4291
4360
  return { patch, currentTreeHash };
4292
4361
  }
4293
4362
  async function handleCoverValidate() {
4294
- validateMainWorktree();
4295
- requireProjectConfig();
4363
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
4296
4364
  const projectName = getProjectName();
4297
4365
  const mainWorktreePath = getGitTopLevel();
4298
4366
  const currentBranch = getCurrentBranch(mainWorktreePath);
@@ -4371,7 +4439,9 @@ function cleanupWorktreeAndBranch(worktreePath, branchName) {
4371
4439
  printSuccess(MESSAGES.WORKTREE_CLEANED(branchName));
4372
4440
  }
4373
4441
  async function handleMerge(options) {
4374
- validateMainWorktree();
4442
+ runPreChecks({ mainWorktree: true, headExists: true });
4443
+ await guardMainWorkBranch();
4444
+ await guardMainWorkBranch();
4375
4445
  const mainWorktreePath = getGitTopLevel();
4376
4446
  await ensureOnMainWorkBranch(mainWorktreePath);
4377
4447
  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)"}`);
@@ -4532,8 +4602,7 @@ function registerResetCommand(program2) {
4532
4602
  });
4533
4603
  }
4534
4604
  async function handleReset() {
4535
- validateMainWorktree();
4536
- requireProjectConfig();
4605
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
4537
4606
  const mainWorktreePath = getGitTopLevel();
4538
4607
  logger.info("reset \u547D\u4EE4\u6267\u884C");
4539
4608
  if (!isWorkingDirClean(mainWorktreePath)) {
@@ -4563,7 +4632,7 @@ function registerStatusCommand(program2) {
4563
4632
  });
4564
4633
  }
4565
4634
  async function handleStatus(options) {
4566
- validateMainWorktree();
4635
+ runPreChecks({ mainWorktree: true, headExists: true });
4567
4636
  if (options.interactive) {
4568
4637
  const panel = new InteractivePanel(collectStatus);
4569
4638
  await panel.start();
@@ -4581,10 +4650,15 @@ function collectStatus() {
4581
4650
  const projectName = getProjectName();
4582
4651
  const currentBranch = getCurrentBranch();
4583
4652
  const isClean = isWorkingDirClean();
4653
+ const projectConfig = loadProjectConfig();
4654
+ const configuredMainBranch = projectConfig?.clawtMainWorkBranch || null;
4655
+ const configuredBranchExists = configuredMainBranch ? checkBranchExists(configuredMainBranch) : null;
4584
4656
  const main2 = {
4585
4657
  branch: currentBranch,
4586
4658
  isClean,
4587
- projectName
4659
+ projectName,
4660
+ configuredMainBranch,
4661
+ configuredBranchExists
4588
4662
  };
4589
4663
  const worktrees = getProjectWorktrees();
4590
4664
  const worktreeStatuses = worktrees.map((wt) => collectWorktreeDetailedStatus(wt, projectName));
@@ -4694,6 +4768,15 @@ function printMainSection(main2) {
4694
4768
  } else {
4695
4769
  printInfo(` \u72B6\u6001: ${chalk11.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
4696
4770
  }
4771
+ if (main2.configuredMainBranch !== null) {
4772
+ if (main2.configuredBranchExists === false) {
4773
+ printInfo(` ${chalk11.red(MESSAGES.STATUS_CONFIGURED_BRANCH_DELETED(main2.configuredMainBranch))}`);
4774
+ } else if (main2.branch !== main2.configuredMainBranch && !main2.branch.startsWith(VALIDATE_BRANCH_PREFIX)) {
4775
+ printInfo(` ${chalk11.yellow(MESSAGES.STATUS_CONFIGURED_BRANCH_MISMATCH(main2.configuredMainBranch))}`);
4776
+ } else {
4777
+ printInfo(` ${chalk11.gray(MESSAGES.STATUS_CONFIGURED_BRANCH(main2.configuredMainBranch))}`);
4778
+ }
4779
+ }
4697
4780
  printInfo("");
4698
4781
  }
4699
4782
  function printWorktreesSection(worktrees, total) {
@@ -5229,7 +5312,7 @@ function registerInitCommand(program2) {
5229
5312
  );
5230
5313
  }
5231
5314
  async function handleInitShow() {
5232
- validateMainWorktree();
5315
+ runPreChecks({ mainWorktree: true, projectConfig: true });
5233
5316
  const config2 = requireProjectConfig();
5234
5317
  logger.info("init show \u547D\u4EE4\u6267\u884C\uFF0C\u8FDB\u5165\u4EA4\u4E92\u5F0F\u9879\u76EE\u914D\u7F6E");
5235
5318
  const { key, newValue } = await interactiveConfigEditor(
@@ -5242,8 +5325,11 @@ async function handleInitShow() {
5242
5325
  printSuccess(MESSAGES.INIT_SET_SUCCESS(key, String(newValue)));
5243
5326
  }
5244
5327
  async function handleInit(options) {
5245
- validateMainWorktree();
5328
+ runPreChecks({ mainWorktree: true });
5246
5329
  const existingConfig = loadProjectConfig();
5330
+ if (!options.branch) {
5331
+ validateHeadExists();
5332
+ }
5247
5333
  const branchName = options.branch || getCurrentBranch();
5248
5334
  logger.info(`init \u547D\u4EE4\u6267\u884C\uFF0C\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`);
5249
5335
  saveProjectConfig({ clawtMainWorkBranch: branchName });
@@ -5261,8 +5347,7 @@ function registerHomeCommand(program2) {
5261
5347
  });
5262
5348
  }
5263
5349
  async function handleHome() {
5264
- validateMainWorktree();
5265
- requireProjectConfig();
5350
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true, branchExists: true });
5266
5351
  const mainBranch = getMainWorkBranch();
5267
5352
  const currentBranch = getCurrentBranch();
5268
5353
  if (currentBranch === mainBranch) {
@@ -21,6 +21,8 @@ var COMMON_MESSAGES = {
21
21
  GIT_NOT_INSTALLED: "Git \u672A\u5B89\u88C5\u6216\u4E0D\u5728 PATH \u4E2D\uFF0C\u8BF7\u5148\u5B89\u88C5 Git",
22
22
  /** Claude Code CLI 未安装 */
23
23
  CLAUDE_NOT_INSTALLED: "Claude Code CLI \u672A\u5B89\u88C5\uFF0C\u8BF7\u5148\u5B89\u88C5\uFF1Anpm install -g @anthropic-ai/claude-code",
24
+ /** HEAD 不存在(仓库无任何 commit) */
25
+ HEAD_NOT_FOUND: "\u5F53\u524D\u4ED3\u5E93\u5C1A\u672A\u521B\u5EFA\u4EFB\u4F55\u63D0\u4EA4\uFF0C\u8BF7\u5148\u6267\u884C git commit \u521B\u5EFA\u9996\u6B21\u63D0\u4EA4\u540E\u518D\u4F7F\u7528 clawt",
24
26
  /** 分支已存在 */
25
27
  BRANCH_EXISTS: (name) => `\u5206\u652F ${name} \u5DF2\u5B58\u5728\uFF0C\u65E0\u6CD5\u521B\u5EFA`,
26
28
  /** 分支名清理后为空 */
@@ -50,7 +52,11 @@ var COMMON_MESSAGES = {
50
52
  /** 分隔线 */
51
53
  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",
52
54
  /** 粗分隔线 */
53
- 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"
55
+ 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",
56
+ /** 守卫检测:配置的主工作分支已不存在 */
57
+ GUARD_BRANCH_NOT_EXISTS: (branchName) => `\u914D\u7F6E\u7684\u4E3B\u5DE5\u4F5C\u5206\u652F ${branchName} \u5DF2\u4E0D\u5B58\u5728\uFF0C\u8BF7\u6267\u884C clawt init \u91CD\u65B0\u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F`,
58
+ /** 守卫检测:当前分支与配置的主工作分支不一致 */
59
+ GUARD_BRANCH_MISMATCH: (configuredBranch, currentBranch) => `\u5F53\u524D\u5206\u652F ${currentBranch} \u4E0E\u914D\u7F6E\u7684\u4E3B\u5DE5\u4F5C\u5206\u652F ${configuredBranch} \u4E0D\u4E00\u81F4\uFF0C\u5982\u9700\u66F4\u65B0\u8BF7\u6267\u884C clawt init`
54
60
  };
55
61
 
56
62
  // src/constants/messages/run.ts
@@ -361,7 +367,13 @@ var STATUS_MESSAGES = {
361
367
  /** status 上次验证时间标签 */
362
368
  STATUS_LAST_VALIDATED: (relativeTime) => `\u4E0A\u6B21\u9A8C\u8BC1: ${relativeTime}`,
363
369
  /** status 未验证警示 */
364
- STATUS_NOT_VALIDATED: "\u2717 \u672A\u9A8C\u8BC1"
370
+ STATUS_NOT_VALIDATED: "\u2717 \u672A\u9A8C\u8BC1",
371
+ /** status 配置的主工作分支(正常状态) */
372
+ STATUS_CONFIGURED_BRANCH: (branchName) => `\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`,
373
+ /** status 配置的主工作分支已不存在 */
374
+ STATUS_CONFIGURED_BRANCH_DELETED: (branchName) => `\u2717 \u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}\uFF08\u5DF2\u4E0D\u5B58\u5728\uFF0C\u8BF7\u6267\u884C clawt init \u91CD\u65B0\u8BBE\u7F6E\uFF09`,
375
+ /** status 当前分支与配置的主工作分支不一致 */
376
+ STATUS_CONFIGURED_BRANCH_MISMATCH: (branchName) => `\u26A0 \u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}\uFF08\u5F53\u524D\u5206\u652F\u4E0D\u4E00\u81F4\uFF0C\u5982\u9700\u66F4\u65B0\u8BF7\u6267\u884C clawt init\uFF09`
365
377
  };
366
378
 
367
379
  // src/constants/messages/alias.ts
@@ -513,6 +525,7 @@ var PANEL_FOOTER_SHORTCUTS = Object.entries(SHORTCUT_LABELS).map(([key, label])
513
525
  var PANEL_OVERFLOW_DOWN_HINT = chalk.gray("\u2193 \u66F4\u591A worktree...");
514
526
  var PANEL_OVERFLOW_UP_HINT = chalk.gray("\u2191 \u66F4\u591A worktree...");
515
527
  var PANEL_PRESS_ENTER_TO_RETURN = chalk.gray("\n\u6309 Enter \u8FD4\u56DE\u9762\u677F...");
528
+ var PANEL_NOT_INITIALIZED = chalk.gray("\u672A\u521D\u59CB\u5316\uFF08\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F\uFF09");
516
529
 
517
530
  // src/constants/messages/index.ts
518
531
  var MESSAGES = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "3.4.1",
3
+ "version": "3.4.3",
4
4
  "description": "本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -3,8 +3,7 @@ import { logger } from '../logger/index.js';
3
3
  import { ClawtError } from '../errors/index.js';
4
4
  import { MESSAGES, VALIDATE_BRANCH_PREFIX } from '../constants/index.js';
5
5
  import {
6
- validateMainWorktree,
7
- requireProjectConfig,
6
+ runPreChecks,
8
7
  getProjectName,
9
8
  getGitTopLevel,
10
9
  getCurrentBranch,
@@ -98,8 +97,7 @@ export function computeIncrementalPatch(snapshotTreeHash: string, mainWorktreePa
98
97
  */
99
98
  async function handleCoverValidate(): Promise<void> {
100
99
  // 步骤 1:前置校验
101
- validateMainWorktree();
102
- requireProjectConfig();
100
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
103
101
  const projectName = getProjectName();
104
102
  const mainWorktreePath = getGitTopLevel();
105
103
  const currentBranch = getCurrentBranch(mainWorktreePath);
@@ -4,13 +4,14 @@ import { ClawtError } from '../errors/index.js';
4
4
  import { logger } from '../logger/index.js';
5
5
  import type { CreateOptions } from '../types/index.js';
6
6
  import {
7
- validateMainWorktree,
7
+ runPreChecks,
8
8
  createWorktrees,
9
9
  ensureOnMainWorkBranch,
10
10
  getValidateBranchName,
11
11
  printSuccess,
12
12
  printInfo,
13
13
  printSeparator,
14
+ guardMainWorkBranch,
14
15
  } from '../utils/index.js';
15
16
 
16
17
  /**
@@ -33,7 +34,9 @@ export function registerCreateCommand(program: Command): void {
33
34
  * @param {CreateOptions} options - 命令选项
34
35
  */
35
36
  async function handleCreate(options: CreateOptions): Promise<void> {
36
- validateMainWorktree();
37
+ runPreChecks({ mainWorktree: true, headExists: true });
38
+
39
+ await guardMainWorkBranch();
37
40
 
38
41
  await ensureOnMainWorkBranch();
39
42
 
@@ -1,13 +1,13 @@
1
1
  import type { Command } from 'commander';
2
2
  import { MESSAGES } from '../constants/index.js';
3
3
  import {
4
- validateMainWorktree,
5
- requireProjectConfig,
4
+ runPreChecks,
6
5
  ensureOnMainWorkBranch,
7
6
  getCurrentBranch,
8
7
  getMainWorkBranch,
9
8
  printSuccess,
10
9
  printInfo,
10
+ guardMainWorkBranchExists,
11
11
  } from '../utils/index.js';
12
12
 
13
13
  /**
@@ -27,8 +27,7 @@ export function registerHomeCommand(program: Command): void {
27
27
  * 执行 home 命令:切换回主工作分支
28
28
  */
29
29
  async function handleHome(): Promise<void> {
30
- validateMainWorktree();
31
- requireProjectConfig();
30
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true, branchExists: true });
32
31
 
33
32
  const mainBranch = getMainWorkBranch();
34
33
  const currentBranch = getCurrentBranch();
@@ -4,7 +4,8 @@ import { logger } from '../logger/index.js';
4
4
  import { MESSAGES, PROJECT_CONFIG_DEFINITIONS } from '../constants/index.js';
5
5
  import type { InitOptions, ProjectConfig } from '../types/index.js';
6
6
  import {
7
- validateMainWorktree,
7
+ runPreChecks,
8
+ validateHeadExists,
8
9
  getCurrentBranch,
9
10
  loadProjectConfig,
10
11
  saveProjectConfig,
@@ -40,7 +41,7 @@ export function registerInitCommand(program: Command): void {
40
41
  * 处理 init show 子命令:交互式面板展示和修改项目配置
41
42
  */
42
43
  async function handleInitShow(): Promise<void> {
43
- validateMainWorktree();
44
+ runPreChecks({ mainWorktree: true, projectConfig: true });
44
45
  const config = requireProjectConfig();
45
46
 
46
47
  logger.info('init show 命令执行,进入交互式项目配置');
@@ -66,11 +67,14 @@ async function handleInitShow(): Promise<void> {
66
67
  * @param {InitOptions} options - 命令选项
67
68
  */
68
69
  async function handleInit(options: InitOptions): Promise<void> {
69
- validateMainWorktree();
70
+ runPreChecks({ mainWorktree: true });
70
71
 
71
72
  const existingConfig = loadProjectConfig();
72
73
 
73
74
  // 确定分支名:优先使用 -b 参数,否则使用当前分支
75
+ if (!options.branch) {
76
+ validateHeadExists();
77
+ }
74
78
  const branchName = options.branch || getCurrentBranch();
75
79
 
76
80
  logger.info(`init 命令执行,主工作分支: ${branchName}`);
@@ -4,7 +4,7 @@ import { MESSAGES } from '../constants/index.js';
4
4
  import { logger } from '../logger/index.js';
5
5
  import type { ListOptions } from '../types/index.js';
6
6
  import {
7
- validateMainWorktree,
7
+ runPreChecks,
8
8
  getProjectName,
9
9
  getProjectWorktrees,
10
10
  getWorktreeStatus,
@@ -33,7 +33,7 @@ export function registerListCommand(program: Command): void {
33
33
  * @param {ListOptions} options - 命令选项
34
34
  */
35
35
  function handleList(options: ListOptions): void {
36
- validateMainWorktree();
36
+ runPreChecks({ mainWorktree: true });
37
37
 
38
38
  const projectName = getProjectName();
39
39
  const worktrees = getProjectWorktrees();
@@ -4,7 +4,7 @@ import { ClawtError } from '../errors/index.js';
4
4
  import { MESSAGES, AUTO_SAVE_COMMIT_MESSAGE } from '../constants/index.js';
5
5
  import type { MergeOptions } from '../types/index.js';
6
6
  import {
7
- validateMainWorktree,
7
+ runPreChecks,
8
8
  getProjectName,
9
9
  getGitTopLevel,
10
10
  getProjectWorktrees,
@@ -33,6 +33,7 @@ import {
33
33
  resolveTargetWorktree,
34
34
  getMainWorkBranch,
35
35
  ensureOnMainWorkBranch,
36
+ guardMainWorkBranch,
36
37
  } from '../utils/index.js';
37
38
  import type { WorktreeResolveMessages } from '../utils/index.js';
38
39
 
@@ -135,7 +136,11 @@ function cleanupWorktreeAndBranch(worktreePath: string, branchName: string): voi
135
136
  * @param {MergeOptions} options - 命令选项
136
137
  */
137
138
  async function handleMerge(options: MergeOptions): Promise<void> {
138
- validateMainWorktree();
139
+ runPreChecks({ mainWorktree: true, headExists: true });
140
+
141
+ await guardMainWorkBranch();
142
+
143
+ await guardMainWorkBranch();
139
144
 
140
145
  const mainWorktreePath = getGitTopLevel();
141
146