clawt 3.4.0 → 3.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,26 +17,36 @@ npm i -g clawt
17
17
  ## 快速开始
18
18
 
19
19
  ```bash
20
- # 1. 在项目根目录(包含 .git 的目录)下执行
21
- # 2. 并行执行 3 个任务,每个任务在独立的 worktree 中运行
22
- clawt run -b <branch-1>
23
- clawt run -b <branch-2>
24
- clawt run -b <branch-3>
20
+ # 1. 在项目根目录(包含 .git 的目录)下初始化,确认主工作分支
21
+ clawt init
25
22
 
26
- # 3. 查看所有 worktree 状态
27
- clawt status
28
-
29
- # 4. 验证某个分支的变更(在主 worktree 中测试)
30
- clawt validate -b branch-1
31
-
32
- # 5. 确认无误后合并到主分支
33
- clawt merge -b branch-1 -m "feat: 实现xxx功能"
23
+ # 2. 并行执行任务,每个任务在独立的 worktree 中运行
24
+ clawt run -b feat-login
25
+ clawt run -b feat-search
26
+ clawt run -b fix-bug
34
27
 
28
+ # 3. 打开交互式面板,实时查看所有任务状态,一站式完成后续操作
29
+ clawt status -i
35
30
  ```
36
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
+
37
47
  ## 命令一览
38
48
 
39
- > 所有命令必须在**主 worktree 的仓库根目录**下执行。`-b` 参数支持模糊匹配。
49
+ > `config`、`alias`、`projects`、`completion` 外,其余命令需在**主 worktree 的仓库根目录**下执行。`-b` 参数支持模糊匹配。大部分操作命令(`run`、`create`、`validate`、`merge` 等)需要先执行 `clawt init`。
40
50
 
41
51
  ### `clawt init` — 初始化项目级配置
42
52
 
package/dist/index.js CHANGED
@@ -59,7 +59,11 @@ var COMMON_MESSAGES = {
59
59
  /** 分隔线 */
60
60
  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
61
  /** 粗分隔线 */
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"
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",
63
+ /** 守卫检测:配置的主工作分支已不存在 */
64
+ 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`,
65
+ /** 守卫检测:当前分支与配置的主工作分支不一致 */
66
+ 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
67
  };
64
68
 
65
69
  // src/constants/messages/run.ts
@@ -371,7 +375,13 @@ var STATUS_MESSAGES = {
371
375
  /** status 上次验证时间标签 */
372
376
  STATUS_LAST_VALIDATED: (relativeTime) => `\u4E0A\u6B21\u9A8C\u8BC1: ${relativeTime}`,
373
377
  /** status 未验证警示 */
374
- STATUS_NOT_VALIDATED: "\u2717 \u672A\u9A8C\u8BC1"
378
+ STATUS_NOT_VALIDATED: "\u2717 \u672A\u9A8C\u8BC1",
379
+ /** status 配置的主工作分支(正常状态) */
380
+ STATUS_CONFIGURED_BRANCH: (branchName) => `\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`,
381
+ /** status 配置的主工作分支已不存在 */
382
+ 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`,
383
+ /** status 当前分支与配置的主工作分支不一致 */
384
+ 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
385
  };
376
386
 
377
387
  // src/constants/messages/alias.ts
@@ -529,7 +539,7 @@ var PANEL_SHORTCUT_KEYS = {
529
539
  QUIT: "q"
530
540
  };
531
541
  var PANEL_DATE_SEPARATOR_PREFIX = "\u2550\u2550\u2550\u2550";
532
- var PANEL_FIXED_ROWS = 4;
542
+ var PANEL_FIXED_ROWS = 5;
533
543
 
534
544
  // src/constants/messages/interactive-panel.ts
535
545
  var SHORTCUT_LABELS = {
@@ -556,6 +566,10 @@ var PANEL_NO_WORKTREES = "(\u65E0\u6D3B\u8DC3 worktree)";
556
566
  var PANEL_PRESS_ENTER_TO_RETURN = chalk.gray("\n\u6309 Enter \u8FD4\u56DE\u9762\u677F...");
557
567
  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
568
  var PANEL_TITLE = (projectName) => chalk.bold.cyan(`\u9879\u76EE\u72B6\u6001\u603B\u89C8: ${projectName}`);
569
+ var PANEL_CONFIGURED_BRANCH = (branchName) => chalk.gray(`\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`);
570
+ var PANEL_CONFIGURED_BRANCH_DELETED = (branchName) => chalk.red(`\u2717 \u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}\uFF08\u5DF2\u4E0D\u5B58\u5728\uFF09`);
571
+ var PANEL_CONFIGURED_BRANCH_MISMATCH = (branchName) => chalk.yellow(`\u26A0 \u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}\uFF08\u4E0D\u4E00\u81F4\uFF09`);
572
+ var PANEL_NOT_INITIALIZED = chalk.gray("\u672A\u521D\u59CB\u5316\uFF08\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F\uFF09");
559
573
 
560
574
  // src/constants/messages/index.ts
561
575
  var MESSAGES = {
@@ -593,7 +607,7 @@ var VALID_TERMINAL_APPS = ["auto", "iterm2", "terminal"];
593
607
  var ITERM2_APP_PATH = "/Applications/iTerm.app";
594
608
 
595
609
  // src/constants/config.ts
596
- var APPEND_SYSTEM_PROMPT = "After the code execution is completed, it is prohibited to build the project for verification.";
610
+ var APPEND_SYSTEM_PROMPT = "Currently, you are in the git worktree directory. There are no dependencies. To save disk space and improve the speed of task completion, installing dependencies is prohibited.";
597
611
  var CONFIG_DEFINITIONS = {
598
612
  autoDeleteBranch: {
599
613
  defaultValue: false,
@@ -1325,6 +1339,26 @@ function getMainWorkBranch() {
1325
1339
  const config2 = requireProjectConfig();
1326
1340
  return config2.clawtMainWorkBranch;
1327
1341
  }
1342
+ function guardMainWorkBranchExists(cwd) {
1343
+ const config2 = requireProjectConfig();
1344
+ const mainBranch = config2.clawtMainWorkBranch;
1345
+ if (!checkBranchExists(mainBranch, cwd)) {
1346
+ throw new ClawtError(MESSAGES.GUARD_BRANCH_NOT_EXISTS(mainBranch));
1347
+ }
1348
+ }
1349
+ async function guardMainWorkBranch(cwd) {
1350
+ guardMainWorkBranchExists(cwd);
1351
+ const config2 = requireProjectConfig();
1352
+ const mainBranch = config2.clawtMainWorkBranch;
1353
+ const currentBranch = getCurrentBranch(cwd);
1354
+ if (currentBranch !== mainBranch && !currentBranch.startsWith(VALIDATE_BRANCH_PREFIX)) {
1355
+ printWarning(MESSAGES.GUARD_BRANCH_MISMATCH(mainBranch, currentBranch));
1356
+ const confirmed = await confirmAction("\u662F\u5426\u7EE7\u7EED\u6267\u884C\uFF1F");
1357
+ if (!confirmed) {
1358
+ throw new ClawtError(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
1359
+ }
1360
+ }
1361
+ }
1328
1362
  function getValidateRunCommand() {
1329
1363
  const config2 = loadProjectConfig();
1330
1364
  return config2?.validateRunCommand || void 0;
@@ -3178,6 +3212,7 @@ function buildSeparatorWithHint(cols, hint) {
3178
3212
  function buildPanelFrame(statusResult, selectedIndex, scrollOffset, rows, cols, countdown) {
3179
3213
  const lines = [];
3180
3214
  lines.push(PANEL_TITLE(statusResult.main.projectName));
3215
+ lines.push(renderConfiguredBranchLine(statusResult.main));
3181
3216
  lines.push(renderSnapshotSummary(statusResult.snapshots.total, statusResult.snapshots.orphaned));
3182
3217
  const visibleRows = calculateVisibleRows(rows);
3183
3218
  if (statusResult.worktrees.length === 0) {
@@ -3301,6 +3336,18 @@ function formatChangeStatusLabel(status) {
3301
3336
  return chalk9.gray(MESSAGES.STATUS_CHANGE_CLEAN);
3302
3337
  }
3303
3338
  }
3339
+ function renderConfiguredBranchLine(main2) {
3340
+ if (main2.configuredMainBranch === null) {
3341
+ return PANEL_NOT_INITIALIZED;
3342
+ }
3343
+ if (main2.configuredBranchExists === false) {
3344
+ return PANEL_CONFIGURED_BRANCH_DELETED(main2.configuredMainBranch);
3345
+ }
3346
+ if (main2.branch !== main2.configuredMainBranch && !main2.branch.startsWith(VALIDATE_BRANCH_PREFIX)) {
3347
+ return PANEL_CONFIGURED_BRANCH_MISMATCH(main2.configuredMainBranch);
3348
+ }
3349
+ return PANEL_CONFIGURED_BRANCH(main2.configuredMainBranch);
3350
+ }
3304
3351
  function renderSnapshotSummary(total, orphaned) {
3305
3352
  return PANEL_SNAPSHOT_SUMMARY(total, orphaned);
3306
3353
  }
@@ -3799,6 +3846,7 @@ function registerCreateCommand(program2) {
3799
3846
  }
3800
3847
  async function handleCreate(options) {
3801
3848
  validateMainWorktree();
3849
+ await guardMainWorkBranch();
3802
3850
  await ensureOnMainWorkBranch();
3803
3851
  const count = Number(options.number);
3804
3852
  if (!Number.isInteger(count) || count <= 0) {
@@ -3942,6 +3990,7 @@ function handleDryRunFromFile(options) {
3942
3990
  async function handleRun(options) {
3943
3991
  validateMainWorktree();
3944
3992
  if (!options.dryRun) {
3993
+ await guardMainWorkBranch();
3945
3994
  await ensureOnMainWorkBranch();
3946
3995
  }
3947
3996
  if (options.file && options.tasks) {
@@ -4107,6 +4156,7 @@ async function executeSyncForBranch(targetWorktreePath, branch) {
4107
4156
  async function handleSync(options) {
4108
4157
  validateMainWorktree();
4109
4158
  requireProjectConfig();
4159
+ await guardMainWorkBranch();
4110
4160
  logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
4111
4161
  const worktrees = getProjectWorktrees();
4112
4162
  const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
@@ -4372,6 +4422,7 @@ function cleanupWorktreeAndBranch(worktreePath, branchName) {
4372
4422
  }
4373
4423
  async function handleMerge(options) {
4374
4424
  validateMainWorktree();
4425
+ await guardMainWorkBranch();
4375
4426
  const mainWorktreePath = getGitTopLevel();
4376
4427
  await ensureOnMainWorkBranch(mainWorktreePath);
4377
4428
  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)"}`);
@@ -4581,10 +4632,15 @@ function collectStatus() {
4581
4632
  const projectName = getProjectName();
4582
4633
  const currentBranch = getCurrentBranch();
4583
4634
  const isClean = isWorkingDirClean();
4635
+ const projectConfig = loadProjectConfig();
4636
+ const configuredMainBranch = projectConfig?.clawtMainWorkBranch || null;
4637
+ const configuredBranchExists = configuredMainBranch ? checkBranchExists(configuredMainBranch) : null;
4584
4638
  const main2 = {
4585
4639
  branch: currentBranch,
4586
4640
  isClean,
4587
- projectName
4641
+ projectName,
4642
+ configuredMainBranch,
4643
+ configuredBranchExists
4588
4644
  };
4589
4645
  const worktrees = getProjectWorktrees();
4590
4646
  const worktreeStatuses = worktrees.map((wt) => collectWorktreeDetailedStatus(wt, projectName));
@@ -4694,6 +4750,15 @@ function printMainSection(main2) {
4694
4750
  } else {
4695
4751
  printInfo(` \u72B6\u6001: ${chalk11.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
4696
4752
  }
4753
+ if (main2.configuredMainBranch !== null) {
4754
+ if (main2.configuredBranchExists === false) {
4755
+ printInfo(` ${chalk11.red(MESSAGES.STATUS_CONFIGURED_BRANCH_DELETED(main2.configuredMainBranch))}`);
4756
+ } else if (main2.branch !== main2.configuredMainBranch && !main2.branch.startsWith(VALIDATE_BRANCH_PREFIX)) {
4757
+ printInfo(` ${chalk11.yellow(MESSAGES.STATUS_CONFIGURED_BRANCH_MISMATCH(main2.configuredMainBranch))}`);
4758
+ } else {
4759
+ printInfo(` ${chalk11.gray(MESSAGES.STATUS_CONFIGURED_BRANCH(main2.configuredMainBranch))}`);
4760
+ }
4761
+ }
4697
4762
  printInfo("");
4698
4763
  }
4699
4764
  function printWorktreesSection(worktrees, total) {
@@ -5263,6 +5328,7 @@ function registerHomeCommand(program2) {
5263
5328
  async function handleHome() {
5264
5329
  validateMainWorktree();
5265
5330
  requireProjectConfig();
5331
+ guardMainWorkBranchExists();
5266
5332
  const mainBranch = getMainWorkBranch();
5267
5333
  const currentBranch = getCurrentBranch();
5268
5334
  if (currentBranch === mainBranch) {
@@ -50,7 +50,11 @@ var COMMON_MESSAGES = {
50
50
  /** 分隔线 */
51
51
  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
52
  /** 粗分隔线 */
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"
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",
54
+ /** 守卫检测:配置的主工作分支已不存在 */
55
+ 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`,
56
+ /** 守卫检测:当前分支与配置的主工作分支不一致 */
57
+ 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
58
  };
55
59
 
56
60
  // src/constants/messages/run.ts
@@ -361,7 +365,13 @@ var STATUS_MESSAGES = {
361
365
  /** status 上次验证时间标签 */
362
366
  STATUS_LAST_VALIDATED: (relativeTime) => `\u4E0A\u6B21\u9A8C\u8BC1: ${relativeTime}`,
363
367
  /** status 未验证警示 */
364
- STATUS_NOT_VALIDATED: "\u2717 \u672A\u9A8C\u8BC1"
368
+ STATUS_NOT_VALIDATED: "\u2717 \u672A\u9A8C\u8BC1",
369
+ /** status 配置的主工作分支(正常状态) */
370
+ STATUS_CONFIGURED_BRANCH: (branchName) => `\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`,
371
+ /** status 配置的主工作分支已不存在 */
372
+ 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`,
373
+ /** status 当前分支与配置的主工作分支不一致 */
374
+ 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
375
  };
366
376
 
367
377
  // src/constants/messages/alias.ts
@@ -513,6 +523,7 @@ var PANEL_FOOTER_SHORTCUTS = Object.entries(SHORTCUT_LABELS).map(([key, label])
513
523
  var PANEL_OVERFLOW_DOWN_HINT = chalk.gray("\u2193 \u66F4\u591A worktree...");
514
524
  var PANEL_OVERFLOW_UP_HINT = chalk.gray("\u2191 \u66F4\u591A worktree...");
515
525
  var PANEL_PRESS_ENTER_TO_RETURN = chalk.gray("\n\u6309 Enter \u8FD4\u56DE\u9762\u677F...");
526
+ var PANEL_NOT_INITIALIZED = chalk.gray("\u672A\u521D\u59CB\u5316\uFF08\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F\uFF09");
516
527
 
517
528
  // src/constants/messages/index.ts
518
529
  var MESSAGES = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "3.4.0",
3
+ "version": "3.4.2",
4
4
  "description": "本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,6 +11,7 @@ import {
11
11
  printSuccess,
12
12
  printInfo,
13
13
  printSeparator,
14
+ guardMainWorkBranch,
14
15
  } from '../utils/index.js';
15
16
 
16
17
  /**
@@ -35,6 +36,8 @@ export function registerCreateCommand(program: Command): void {
35
36
  async function handleCreate(options: CreateOptions): Promise<void> {
36
37
  validateMainWorktree();
37
38
 
39
+ await guardMainWorkBranch();
40
+
38
41
  await ensureOnMainWorkBranch();
39
42
 
40
43
  const count = Number(options.number);
@@ -8,6 +8,7 @@ import {
8
8
  getMainWorkBranch,
9
9
  printSuccess,
10
10
  printInfo,
11
+ guardMainWorkBranchExists,
11
12
  } from '../utils/index.js';
12
13
 
13
14
  /**
@@ -29,6 +30,7 @@ export function registerHomeCommand(program: Command): void {
29
30
  async function handleHome(): Promise<void> {
30
31
  validateMainWorktree();
31
32
  requireProjectConfig();
33
+ guardMainWorkBranchExists();
32
34
 
33
35
  const mainBranch = getMainWorkBranch();
34
36
  const currentBranch = getCurrentBranch();
@@ -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
 
@@ -137,6 +138,8 @@ function cleanupWorktreeAndBranch(worktreePath: string, branchName: string): voi
137
138
  async function handleMerge(options: MergeOptions): Promise<void> {
138
139
  validateMainWorktree();
139
140
 
141
+ await guardMainWorkBranch();
142
+
140
143
  const mainWorktreePath = getGitTopLevel();
141
144
 
142
145
  // 确保当前在主工作分支上
@@ -20,6 +20,7 @@ import {
20
20
  executeBatchTasks,
21
21
  printDryRunPreview,
22
22
  ensureOnMainWorkBranch,
23
+ guardMainWorkBranch,
23
24
  } from '../utils/index.js';
24
25
 
25
26
  /**
@@ -123,6 +124,7 @@ async function handleRun(options: RunOptions): Promise<void> {
123
124
 
124
125
  // dry-run 模式跳过项目配置前置校验
125
126
  if (!options.dryRun) {
127
+ await guardMainWorkBranch();
126
128
  await ensureOnMainWorkBranch();
127
129
  }
128
130
 
@@ -1,6 +1,6 @@
1
1
  import type { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import { MESSAGES } from '../constants/index.js';
3
+ import { MESSAGES, VALIDATE_BRANCH_PREFIX } from '../constants/index.js';
4
4
  import { logger } from '../logger/index.js';
5
5
  import type { StatusOptions, WorktreeDetailedStatus, MainWorktreeStatus, SnapshotSummary, StatusResult, WorktreeInfo } from '../types/index.js';
6
6
  import {
@@ -22,6 +22,8 @@ import {
22
22
  printDoubleSeparator,
23
23
  printSeparator,
24
24
  InteractivePanel,
25
+ loadProjectConfig,
26
+ checkBranchExists,
25
27
  } from '../utils/index.js';
26
28
 
27
29
  /**
@@ -74,11 +76,18 @@ export function collectStatus(): StatusResult {
74
76
  const currentBranch = getCurrentBranch();
75
77
  const isClean = isWorkingDirClean();
76
78
 
79
+ // 配置分支信息收集
80
+ const projectConfig = loadProjectConfig();
81
+ const configuredMainBranch = projectConfig?.clawtMainWorkBranch || null;
82
+ const configuredBranchExists = configuredMainBranch ? checkBranchExists(configuredMainBranch) : null;
83
+
77
84
  // 主 worktree 状态
78
85
  const main: MainWorktreeStatus = {
79
86
  branch: currentBranch,
80
87
  isClean,
81
88
  projectName,
89
+ configuredMainBranch,
90
+ configuredBranchExists,
82
91
  };
83
92
 
84
93
  // 各 worktree 详细状态
@@ -265,6 +274,18 @@ function printMainSection(main: MainWorktreeStatus): void {
265
274
  } else {
266
275
  printInfo(` 状态: ${chalk.yellow('✗ 有未提交修改')}`);
267
276
  }
277
+
278
+ // 配置分支信息展示
279
+ if (main.configuredMainBranch !== null) {
280
+ if (main.configuredBranchExists === false) {
281
+ printInfo(` ${chalk.red(MESSAGES.STATUS_CONFIGURED_BRANCH_DELETED(main.configuredMainBranch))}`);
282
+ } else if (main.branch !== main.configuredMainBranch && !main.branch.startsWith(VALIDATE_BRANCH_PREFIX)) {
283
+ printInfo(` ${chalk.yellow(MESSAGES.STATUS_CONFIGURED_BRANCH_MISMATCH(main.configuredMainBranch))}`);
284
+ } else {
285
+ printInfo(` ${chalk.gray(MESSAGES.STATUS_CONFIGURED_BRANCH(main.configuredMainBranch))}`);
286
+ }
287
+ }
288
+
268
289
  printInfo('');
269
290
  }
270
291
 
@@ -20,6 +20,7 @@ import {
20
20
  getMainWorkBranch,
21
21
  rebuildValidateBranch,
22
22
  getValidateBranchName,
23
+ guardMainWorkBranch,
23
24
  } from '../utils/index.js';
24
25
  import type { WorktreeResolveMessages } from '../utils/index.js';
25
26
 
@@ -129,6 +130,7 @@ export async function executeSyncForBranch(targetWorktreePath: string, branch: s
129
130
  async function handleSync(options: SyncOptions): Promise<void> {
130
131
  validateMainWorktree();
131
132
  requireProjectConfig();
133
+ await guardMainWorkBranch();
132
134
 
133
135
  logger.info(`sync 命令执行,分支: ${options.branch ?? '(未指定)'}`);
134
136
 
@@ -3,7 +3,7 @@ import { VALID_TERMINAL_APPS } from './terminal.js';
3
3
 
4
4
  /** Claude Code 系统约束提示 */
5
5
  export const APPEND_SYSTEM_PROMPT =
6
- 'After the code execution is completed, it is prohibited to build the project for verification.';
6
+ 'Currently, you are in the git worktree directory. There are no dependencies. To save disk space and improve the speed of task completion, installing dependencies is prohibited.';
7
7
 
8
8
  /**
9
9
  * 配置项完整定义(单一数据源)
@@ -40,5 +40,5 @@ export const PANEL_SHORTCUT_KEYS = {
40
40
  /** 日期分隔线前缀 */
41
41
  export const PANEL_DATE_SEPARATOR_PREFIX = '════';
42
42
 
43
- /** 固定占用行数(顶部分隔线 + 快照摘要 + 底部分隔线 + 底栏) */
44
- export const PANEL_FIXED_ROWS = 4;
43
+ /** 固定占用行数(配置分支信息 + 快照摘要 + 顶部分隔线 + 底部分隔线 + 底栏) */
44
+ export const PANEL_FIXED_ROWS = 5;
@@ -38,4 +38,10 @@ export const COMMON_MESSAGES = {
38
38
  SEPARATOR: '────────────────────────────────────────',
39
39
  /** 粗分隔线 */
40
40
  DOUBLE_SEPARATOR: '════════════════════════════════════════',
41
+ /** 守卫检测:配置的主工作分支已不存在 */
42
+ GUARD_BRANCH_NOT_EXISTS: (branchName: string) =>
43
+ `配置的主工作分支 ${branchName} 已不存在,请执行 clawt init 重新设置主工作分支`,
44
+ /** 守卫检测:当前分支与配置的主工作分支不一致 */
45
+ GUARD_BRANCH_MISMATCH: (configuredBranch: string, currentBranch: string) =>
46
+ `当前分支 ${currentBranch} 与配置的主工作分支 ${configuredBranch} 不一致,如需更新请执行 clawt init`,
41
47
  } as const;
@@ -16,11 +16,11 @@ import { UPDATE_MESSAGES, UPDATE_COMMANDS } from './update.js';
16
16
  import { INIT_MESSAGES } from './init.js';
17
17
  import { COVER_VALIDATE_MESSAGES } from './cover-validate.js';
18
18
  import { HOME_MESSAGES } from './home.js';
19
- import { PANEL_FOOTER_SHORTCUTS, PANEL_FOOTER_COUNTDOWN, PANEL_OVERFLOW_DOWN_HINT, PANEL_OVERFLOW_UP_HINT, PANEL_SNAPSHOT_SUMMARY, PANEL_NO_WORKTREES as PANEL_NO_WORKTREES_MSG, PANEL_PRESS_ENTER_TO_RETURN, PANEL_NOT_TTY, PANEL_TITLE } from './interactive-panel.js';
19
+ import { PANEL_FOOTER_SHORTCUTS, PANEL_FOOTER_COUNTDOWN, PANEL_OVERFLOW_DOWN_HINT, PANEL_OVERFLOW_UP_HINT, PANEL_SNAPSHOT_SUMMARY, PANEL_NO_WORKTREES as PANEL_NO_WORKTREES_MSG, PANEL_PRESS_ENTER_TO_RETURN, PANEL_NOT_TTY, PANEL_TITLE, PANEL_CONFIGURED_BRANCH, PANEL_CONFIGURED_BRANCH_DELETED, PANEL_CONFIGURED_BRANCH_MISMATCH, PANEL_NOT_INITIALIZED } from './interactive-panel.js';
20
20
 
21
21
  export { CONFIG_ALIAS_DISABLED_HINT };
22
22
  export { UPDATE_MESSAGES, UPDATE_COMMANDS };
23
- export { PANEL_FOOTER_SHORTCUTS, PANEL_FOOTER_COUNTDOWN, PANEL_OVERFLOW_DOWN_HINT, PANEL_OVERFLOW_UP_HINT, PANEL_SNAPSHOT_SUMMARY, PANEL_NO_WORKTREES_MSG, PANEL_PRESS_ENTER_TO_RETURN, PANEL_NOT_TTY, PANEL_TITLE };
23
+ export { PANEL_FOOTER_SHORTCUTS, PANEL_FOOTER_COUNTDOWN, PANEL_OVERFLOW_DOWN_HINT, PANEL_OVERFLOW_UP_HINT, PANEL_SNAPSHOT_SUMMARY, PANEL_NO_WORKTREES_MSG, PANEL_PRESS_ENTER_TO_RETURN, PANEL_NOT_TTY, PANEL_TITLE, PANEL_CONFIGURED_BRANCH, PANEL_CONFIGURED_BRANCH_DELETED, PANEL_CONFIGURED_BRANCH_MISMATCH, PANEL_NOT_INITIALIZED };
24
24
 
25
25
  /**
26
26
  * 提示消息模板
@@ -59,3 +59,30 @@ export const PANEL_NOT_TTY = '交互式面板需要 TTY 终端环境,请直接
59
59
  * @returns {string} 格式化的标题
60
60
  */
61
61
  export const PANEL_TITLE = (projectName: string): string => chalk.bold.cyan(`项目状态总览: ${projectName}`);
62
+
63
+ /**
64
+ * 面板配置分支信息(正常)
65
+ * @param {string} branchName - 分支名
66
+ * @returns {string} 格式化的分支信息
67
+ */
68
+ export const PANEL_CONFIGURED_BRANCH = (branchName: string): string =>
69
+ chalk.gray(`主工作分支: ${branchName}`);
70
+
71
+ /**
72
+ * 面板配置分支信息(分支已删除)
73
+ * @param {string} branchName - 分支名
74
+ * @returns {string} 格式化的分支信息
75
+ */
76
+ export const PANEL_CONFIGURED_BRANCH_DELETED = (branchName: string): string =>
77
+ chalk.red(`✗ 主工作分支: ${branchName}(已不存在)`);
78
+
79
+ /**
80
+ * 面板配置分支信息(分支不一致)
81
+ * @param {string} branchName - 分支名
82
+ * @returns {string} 格式化的分支信息
83
+ */
84
+ export const PANEL_CONFIGURED_BRANCH_MISMATCH = (branchName: string): string =>
85
+ chalk.yellow(`⚠ 主工作分支: ${branchName}(不一致)`);
86
+
87
+ /** 面板配置分支信息(未初始化) */
88
+ export const PANEL_NOT_INITIALIZED = chalk.gray('未初始化(执行 clawt init 设置主工作分支)');
@@ -30,4 +30,13 @@ export const STATUS_MESSAGES = {
30
30
  STATUS_LAST_VALIDATED: (relativeTime: string) => `上次验证: ${relativeTime}`,
31
31
  /** status 未验证警示 */
32
32
  STATUS_NOT_VALIDATED: '✗ 未验证',
33
+ /** status 配置的主工作分支(正常状态) */
34
+ STATUS_CONFIGURED_BRANCH: (branchName: string) =>
35
+ `主工作分支: ${branchName}`,
36
+ /** status 配置的主工作分支已不存在 */
37
+ STATUS_CONFIGURED_BRANCH_DELETED: (branchName: string) =>
38
+ `✗ 主工作分支: ${branchName}(已不存在,请执行 clawt init 重新设置)`,
39
+ /** status 当前分支与配置的主工作分支不一致 */
40
+ STATUS_CONFIGURED_BRANCH_MISMATCH: (branchName: string) =>
41
+ `⚠ 主工作分支: ${branchName}(当前分支不一致,如需更新请执行 clawt init)`,
33
42
  } as const;
@@ -28,6 +28,10 @@ export interface MainWorktreeStatus {
28
28
  isClean: boolean;
29
29
  /** 项目名 */
30
30
  projectName: string;
31
+ /** 配置的主工作分支名(项目未初始化时为 null) */
32
+ configuredMainBranch: string | null;
33
+ /** 配置的主工作分支是否存在(项目未初始化时为 null) */
34
+ configuredBranchExists: boolean | null;
31
35
  }
32
36
 
33
37
  /** validate 快照信息 */
@@ -70,7 +70,7 @@ export { truncateTaskDesc, printDryRunPreview } from './dry-run.js';
70
70
  export { applyAliases } from './alias.js';
71
71
  export { isValidConfigKey, getValidConfigKeys, parseConfigValue, promptConfigValue, formatConfigValue, interactiveConfigEditor } from './config-strategy.js';
72
72
  export { checkForUpdates } from './update-checker.js';
73
- export { getProjectConfigPath, loadProjectConfig, saveProjectConfig, requireProjectConfig, getMainWorkBranch, getValidateRunCommand } from './project-config.js';
73
+ export { getProjectConfigPath, loadProjectConfig, saveProjectConfig, requireProjectConfig, getMainWorkBranch, guardMainWorkBranchExists, guardMainWorkBranch, getValidateRunCommand } from './project-config.js';
74
74
  export { getValidateBranchName, createValidateBranch, deleteValidateBranch, rebuildValidateBranch, ensureOnMainWorkBranch, handleDirtyWorkingDir } from './validate-branch.js';
75
75
  export { safeStringify } from './json.js';
76
76
  export { executeRunCommand } from './validate-runner.js';
@@ -7,6 +7,7 @@ import {
7
7
  PANEL_DATE_SEPARATOR_PREFIX,
8
8
  PANEL_FIXED_ROWS,
9
9
  UNKNOWN_DATE_GROUP,
10
+ VALIDATE_BRANCH_PREFIX,
10
11
  } from '../constants/index.js';
11
12
  import {
12
13
  PANEL_FOOTER_SHORTCUTS,
@@ -16,8 +17,12 @@ import {
16
17
  PANEL_SNAPSHOT_SUMMARY,
17
18
  PANEL_NO_WORKTREES_MSG,
18
19
  PANEL_TITLE,
20
+ PANEL_CONFIGURED_BRANCH,
21
+ PANEL_CONFIGURED_BRANCH_DELETED,
22
+ PANEL_CONFIGURED_BRANCH_MISMATCH,
23
+ PANEL_NOT_INITIALIZED,
19
24
  } from '../constants/messages/index.js';
20
- import type { StatusResult, WorktreeDetailedStatus } from '../types/index.js';
25
+ import type { StatusResult, WorktreeDetailedStatus, MainWorktreeStatus } from '../types/index.js';
21
26
  import { formatRelativeTime, groupWorktreesByDate, formatRelativeDate } from './index.js';
22
27
 
23
28
  /** 面板行类型 */
@@ -73,6 +78,9 @@ export function buildPanelFrame(
73
78
  // 标题行
74
79
  lines.push(PANEL_TITLE(statusResult.main.projectName));
75
80
 
81
+ // 配置分支信息行
82
+ lines.push(renderConfiguredBranchLine(statusResult.main));
83
+
76
84
  // 快照摘要行
77
85
  lines.push(renderSnapshotSummary(statusResult.snapshots.total, statusResult.snapshots.orphaned));
78
86
 
@@ -283,6 +291,24 @@ function formatChangeStatusLabel(status: WorktreeDetailedStatus['changeStatus'])
283
291
  }
284
292
  }
285
293
 
294
+ /**
295
+ * 渲染配置的主工作分支信息行(面板模式)
296
+ * @param {MainWorktreeStatus} main - 主 worktree 状态
297
+ * @returns {string} 格式化的配置分支信息
298
+ */
299
+ function renderConfiguredBranchLine(main: MainWorktreeStatus): string {
300
+ if (main.configuredMainBranch === null) {
301
+ return PANEL_NOT_INITIALIZED;
302
+ }
303
+ if (main.configuredBranchExists === false) {
304
+ return PANEL_CONFIGURED_BRANCH_DELETED(main.configuredMainBranch);
305
+ }
306
+ if (main.branch !== main.configuredMainBranch && !main.branch.startsWith(VALIDATE_BRANCH_PREFIX)) {
307
+ return PANEL_CONFIGURED_BRANCH_MISMATCH(main.configuredMainBranch);
308
+ }
309
+ return PANEL_CONFIGURED_BRANCH(main.configuredMainBranch);
310
+ }
311
+
286
312
  /**
287
313
  * 渲染快照摘要行
288
314
  * @param {number} total - 快照总数
@@ -309,7 +335,7 @@ export function renderFooter(countdown: number): string {
309
335
  * @returns {number} 滚动区域总行数
310
336
  */
311
337
  export function calculateVisibleRows(terminalRows: number): number {
312
- // 固定行:标题(1) + 快照摘要 + 顶部分隔线 + 底部分隔线 + 底栏(后四项 = PANEL_FIXED_ROWS)
338
+ // 固定行:标题(1) + 配置分支信息 + 快照摘要 + 顶部分隔线 + 底部分隔线 + 底栏(后五项 = PANEL_FIXED_ROWS)
313
339
  const fixedRows = PANEL_FIXED_ROWS + 1;
314
340
  return Math.max(terminalRows - fixedRows, 3);
315
341
  }
@@ -1,10 +1,11 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { PROJECTS_CONFIG_DIR, MESSAGES } from '../constants/index.js';
3
+ import { PROJECTS_CONFIG_DIR, MESSAGES, VALIDATE_BRANCH_PREFIX } from '../constants/index.js';
4
4
  import { ClawtError } from '../errors/index.js';
5
5
  import { logger } from '../logger/index.js';
6
- import { getProjectName } from './git.js';
6
+ import { getProjectName, checkBranchExists, getCurrentBranch } from './git.js';
7
7
  import { ensureDir } from './fs.js';
8
+ import { printWarning, confirmAction } from './formatter.js';
8
9
  import type { ProjectConfig } from '../types/index.js';
9
10
 
10
11
  /**
@@ -76,6 +77,42 @@ export function getMainWorkBranch(): string {
76
77
  return config.clawtMainWorkBranch;
77
78
  }
78
79
 
80
+ /**
81
+ * 守卫检测:仅验证配置中的主工作分支是否存在
82
+ * 分支不存在 → 抛出 ClawtError(致命错误)
83
+ * 适用于不需要分支一致性检测的场景(如 home 命令)
84
+ * @param {string} [cwd] - 工作目录
85
+ */
86
+ export function guardMainWorkBranchExists(cwd?: string): void {
87
+ const config = requireProjectConfig();
88
+ const mainBranch = config.clawtMainWorkBranch;
89
+
90
+ if (!checkBranchExists(mainBranch, cwd)) {
91
+ throw new ClawtError(MESSAGES.GUARD_BRANCH_NOT_EXISTS(mainBranch));
92
+ }
93
+ }
94
+
95
+ /**
96
+ * 守卫检测:验证配置中的主工作分支是否有效
97
+ * 分支不存在 → 抛出 ClawtError(致命错误)
98
+ * 当前分支与配置分支不一致且非验证分支 → 警告并交互确认是否继续
99
+ * @param {string} [cwd] - 工作目录
100
+ */
101
+ export async function guardMainWorkBranch(cwd?: string): Promise<void> {
102
+ guardMainWorkBranchExists(cwd);
103
+
104
+ const config = requireProjectConfig();
105
+ const mainBranch = config.clawtMainWorkBranch;
106
+ const currentBranch = getCurrentBranch(cwd);
107
+ if (currentBranch !== mainBranch && !currentBranch.startsWith(VALIDATE_BRANCH_PREFIX)) {
108
+ printWarning(MESSAGES.GUARD_BRANCH_MISMATCH(mainBranch, currentBranch));
109
+ const confirmed = await confirmAction('是否继续执行?');
110
+ if (!confirmed) {
111
+ throw new ClawtError(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
112
+ }
113
+ }
114
+ }
115
+
79
116
  /**
80
117
  * 从项目配置中获取 validate 成功后自动执行的命令
81
118
  * @returns {string | undefined} 配置的命令字符串,未配置时返回 undefined
@@ -48,6 +48,8 @@ vi.mock('../../../src/utils/index.js', () => ({
48
48
  printInfo: vi.fn(),
49
49
  isWorkingDirClean: vi.fn().mockReturnValue(false),
50
50
  confirmAction: vi.fn().mockResolvedValue(true),
51
+ guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
52
+ guardMainWorkBranchExists: vi.fn(),
51
53
  }));
52
54
 
53
55
  import { registerCoverValidateCommand, extractTargetBranchName, findTargetWorktreePath, computeIncrementalPatch } from '../../../src/commands/cover-validate.js';
@@ -33,6 +33,8 @@ vi.mock('../../../src/utils/index.js', () => ({
33
33
  printSuccess: vi.fn(),
34
34
  printInfo: vi.fn(),
35
35
  printSeparator: vi.fn(),
36
+ guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
37
+ guardMainWorkBranchExists: vi.fn(),
36
38
  }));
37
39
 
38
40
  import { registerCreateCommand } from '../../../src/commands/create.js';
@@ -31,6 +31,8 @@ vi.mock('../../../src/utils/index.js', () => ({
31
31
  printInfo: vi.fn(),
32
32
  safeStringify: vi.fn((value: unknown, indent: number = 2) => JSON.stringify(value, null, indent)),
33
33
  interactiveConfigEditor: vi.fn(),
34
+ guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
35
+ guardMainWorkBranchExists: vi.fn(),
34
36
  }));
35
37
 
36
38
  import { registerInitCommand } from '../../../src/commands/init.js';
@@ -71,6 +71,8 @@ vi.mock('../../../src/utils/index.js', () => ({
71
71
  requireProjectConfig: vi.fn().mockReturnValue({ clawtMainWorkBranch: 'main' }),
72
72
  getMainWorkBranch: vi.fn().mockReturnValue('main'),
73
73
  ensureOnMainWorkBranch: vi.fn(),
74
+ guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
75
+ guardMainWorkBranchExists: vi.fn(),
74
76
  }));
75
77
 
76
78
  import { registerMergeCommand } from '../../../src/commands/merge.js';
@@ -54,6 +54,8 @@ vi.mock('../../../src/utils/index.js', () => ({
54
54
  getValidateBranchName: vi.fn((name: string) => `clawt-validate-${name}`),
55
55
  deleteValidateBranch: vi.fn(),
56
56
  getCurrentBranch: vi.fn(),
57
+ guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
58
+ guardMainWorkBranchExists: vi.fn(),
57
59
  }));
58
60
 
59
61
  import { registerRemoveCommand } from '../../../src/commands/remove.js';
@@ -25,6 +25,8 @@ vi.mock('../../../src/utils/index.js', () => ({
25
25
  printInfo: vi.fn(),
26
26
  requireProjectConfig: vi.fn().mockReturnValue({ clawtMainWorkBranch: 'main' }),
27
27
  switchBackIfOnValidateBranch: vi.fn(),
28
+ guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
29
+ guardMainWorkBranchExists: vi.fn(),
28
30
  }));
29
31
 
30
32
  import { registerResetCommand } from '../../../src/commands/reset.js';
@@ -73,6 +73,8 @@ vi.mock('../../../src/utils/index.js', async (importOriginal) => {
73
73
  printDryRunPreview: vi.fn(),
74
74
  requireProjectConfig: vi.fn().mockReturnValue({ clawtMainWorkBranch: 'main' }),
75
75
  ensureOnMainWorkBranch: vi.fn().mockResolvedValue(undefined),
76
+ guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
77
+ guardMainWorkBranchExists: vi.fn(),
76
78
  };
77
79
  });
78
80
 
@@ -22,7 +22,11 @@ vi.mock('../../../src/constants/index.js', () => ({
22
22
  STATUS_NO_DIVERGED_COMMITS: '尚无分叉提交',
23
23
  STATUS_LAST_VALIDATED: (relativeTime: string) => `上次验证: ${relativeTime}`,
24
24
  STATUS_NOT_VALIDATED: '✗ 未验证',
25
+ STATUS_CONFIGURED_BRANCH: (branchName: string) => `主工作分支: ${branchName}`,
26
+ STATUS_CONFIGURED_BRANCH_DELETED: (branchName: string) => `✗ 主工作分支: ${branchName}(已不存在)`,
27
+ STATUS_CONFIGURED_BRANCH_MISMATCH: (branchName: string) => `⚠ 主工作分支: ${branchName}(当前分支不一致)`,
25
28
  },
29
+ VALIDATE_BRANCH_PREFIX: 'clawt-validate-',
26
30
  }));
27
31
 
28
32
  vi.mock('../../../src/utils/index.js', () => ({
@@ -43,6 +47,8 @@ vi.mock('../../../src/utils/index.js', () => ({
43
47
  printInfo: vi.fn(),
44
48
  printDoubleSeparator: vi.fn(),
45
49
  printSeparator: vi.fn(),
50
+ loadProjectConfig: vi.fn().mockReturnValue(null),
51
+ checkBranchExists: vi.fn().mockReturnValue(true),
46
52
  }));
47
53
 
48
54
  import { registerStatusCommand } from '../../../src/commands/status.js';
@@ -48,6 +48,8 @@ vi.mock('../../../src/utils/index.js', () => ({
48
48
  getMainWorkBranch: vi.fn().mockReturnValue('main'),
49
49
  rebuildValidateBranch: vi.fn(),
50
50
  getValidateBranchName: vi.fn((name: string) => `clawt-validate-${name}`),
51
+ guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
52
+ guardMainWorkBranchExists: vi.fn(),
51
53
  }));
52
54
 
53
55
  import { registerSyncCommand } from '../../../src/commands/sync.js';
@@ -92,6 +92,8 @@ vi.mock('../../../src/utils/index.js', () => ({
92
92
  switchToValidateBranch: vi.fn((name: string) => `clawt-validate-${name}`),
93
93
  // validate-runner.ts 抽离的函数
94
94
  executeRunCommand: vi.fn(),
95
+ guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
96
+ guardMainWorkBranchExists: vi.fn(),
95
97
  }));
96
98
 
97
99
  import { registerValidateCommand } from '../../../src/commands/validate.js';