clawt 3.4.2 → 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.
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
  /** 分支名清理后为空 */
@@ -1229,31 +1231,9 @@ function validateBranchesNotExist(branchNames) {
1229
1231
  }
1230
1232
  }
1231
1233
 
1232
- // src/utils/validation.ts
1233
- function validateMainWorktree() {
1234
- try {
1235
- const gitCommonDir = getGitCommonDir();
1236
- if (gitCommonDir !== ".git") {
1237
- throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
1238
- }
1239
- } catch (error) {
1240
- if (error instanceof ClawtError) {
1241
- throw error;
1242
- }
1243
- throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
1244
- }
1245
- }
1246
- function validateClaudeCodeInstalled() {
1247
- try {
1248
- execCommand("claude --version");
1249
- } catch {
1250
- throw new ClawtError(MESSAGES.CLAUDE_NOT_INSTALLED);
1251
- }
1252
- }
1253
-
1254
- // src/utils/worktree.ts
1255
- import { join as join4 } from "path";
1256
- 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";
1257
1237
 
1258
1238
  // src/utils/fs.ts
1259
1239
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmdirSync, statSync } from "fs";
@@ -1294,12 +1274,7 @@ function calculateDirSize(dirPath) {
1294
1274
  return totalSize;
1295
1275
  }
1296
1276
 
1297
- // src/utils/validate-branch.ts
1298
- import Enquirer from "enquirer";
1299
-
1300
1277
  // src/utils/project-config.ts
1301
- import { existsSync as existsSync3, readFileSync, writeFileSync } from "fs";
1302
- import { join as join3 } from "path";
1303
1278
  function getProjectConfigPath(projectName) {
1304
1279
  return join3(PROJECTS_CONFIG_DIR, projectName, "config.json");
1305
1280
  }
@@ -1364,7 +1339,55 @@ function getValidateRunCommand() {
1364
1339
  return config2?.validateRunCommand || void 0;
1365
1340
  }
1366
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
+
1367
1389
  // src/utils/validate-branch.ts
1390
+ import Enquirer from "enquirer";
1368
1391
  function getValidateBranchName(branchName) {
1369
1392
  return `${VALIDATE_BRANCH_PREFIX}${branchName}`;
1370
1393
  }
@@ -3795,7 +3818,7 @@ function registerListCommand(program2) {
3795
3818
  });
3796
3819
  }
3797
3820
  function handleList(options) {
3798
- validateMainWorktree();
3821
+ runPreChecks({ mainWorktree: true });
3799
3822
  const projectName = getProjectName();
3800
3823
  const worktrees = getProjectWorktrees();
3801
3824
  logger.info(`list \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}\uFF0C\u5171 ${worktrees.length} \u4E2A worktree`);
@@ -3845,7 +3868,7 @@ function registerCreateCommand(program2) {
3845
3868
  });
3846
3869
  }
3847
3870
  async function handleCreate(options) {
3848
- validateMainWorktree();
3871
+ runPreChecks({ mainWorktree: true, headExists: true });
3849
3872
  await guardMainWorkBranch();
3850
3873
  await ensureOnMainWorkBranch();
3851
3874
  const count = Number(options.number);
@@ -3881,8 +3904,7 @@ function registerRemoveCommand(program2) {
3881
3904
  });
3882
3905
  }
3883
3906
  async function handleRemove(options) {
3884
- validateMainWorktree();
3885
- requireProjectConfig();
3907
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
3886
3908
  const projectName = getProjectName();
3887
3909
  logger.info(`remove \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}`);
3888
3910
  const allWorktrees = getProjectWorktrees();
@@ -3988,7 +4010,7 @@ function handleDryRunFromFile(options) {
3988
4010
  printDryRunPreview(branchNames, tasks, concurrency);
3989
4011
  }
3990
4012
  async function handleRun(options) {
3991
- validateMainWorktree();
4013
+ runPreChecks({ mainWorktree: true, headExists: true });
3992
4014
  if (!options.dryRun) {
3993
4015
  await guardMainWorkBranch();
3994
4016
  await ensureOnMainWorkBranch();
@@ -4053,7 +4075,7 @@ function registerResumeCommand(program2) {
4053
4075
  });
4054
4076
  }
4055
4077
  async function handleResume(options) {
4056
- validateMainWorktree();
4078
+ runPreChecks({ mainWorktree: true, headExists: true });
4057
4079
  validateClaudeCodeInstalled();
4058
4080
  logger.info(`resume \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F\u8FC7\u6EE4: ${options.branch ?? "(\u65E0)"}`);
4059
4081
  const worktrees = getProjectWorktrees();
@@ -4154,8 +4176,7 @@ async function executeSyncForBranch(targetWorktreePath, branch) {
4154
4176
  return { success: true, hasConflict: false };
4155
4177
  }
4156
4178
  async function handleSync(options) {
4157
- validateMainWorktree();
4158
- requireProjectConfig();
4179
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
4159
4180
  await guardMainWorkBranch();
4160
4181
  logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
4161
4182
  const worktrees = getProjectWorktrees();
@@ -4189,8 +4210,7 @@ async function handlePatchApplyFailure(targetWorktreePath, branchName) {
4189
4210
  const syncResult = await executeSyncForBranch(targetWorktreePath, branchName);
4190
4211
  }
4191
4212
  async function handleValidateClean(options) {
4192
- validateMainWorktree();
4193
- requireProjectConfig();
4213
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
4194
4214
  const projectName = getProjectName();
4195
4215
  const mainWorktreePath = getGitTopLevel();
4196
4216
  const worktrees = getProjectWorktrees();
@@ -4275,8 +4295,7 @@ async function handleValidate(options) {
4275
4295
  await handleValidateClean(options);
4276
4296
  return;
4277
4297
  }
4278
- validateMainWorktree();
4279
- requireProjectConfig();
4298
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
4280
4299
  const projectName = getProjectName();
4281
4300
  const mainWorktreePath = getGitTopLevel();
4282
4301
  const worktrees = getProjectWorktrees();
@@ -4341,8 +4360,7 @@ function computeIncrementalPatch(snapshotTreeHash, mainWorktreePath) {
4341
4360
  return { patch, currentTreeHash };
4342
4361
  }
4343
4362
  async function handleCoverValidate() {
4344
- validateMainWorktree();
4345
- requireProjectConfig();
4363
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
4346
4364
  const projectName = getProjectName();
4347
4365
  const mainWorktreePath = getGitTopLevel();
4348
4366
  const currentBranch = getCurrentBranch(mainWorktreePath);
@@ -4421,7 +4439,8 @@ function cleanupWorktreeAndBranch(worktreePath, branchName) {
4421
4439
  printSuccess(MESSAGES.WORKTREE_CLEANED(branchName));
4422
4440
  }
4423
4441
  async function handleMerge(options) {
4424
- validateMainWorktree();
4442
+ runPreChecks({ mainWorktree: true, headExists: true });
4443
+ await guardMainWorkBranch();
4425
4444
  await guardMainWorkBranch();
4426
4445
  const mainWorktreePath = getGitTopLevel();
4427
4446
  await ensureOnMainWorkBranch(mainWorktreePath);
@@ -4583,8 +4602,7 @@ function registerResetCommand(program2) {
4583
4602
  });
4584
4603
  }
4585
4604
  async function handleReset() {
4586
- validateMainWorktree();
4587
- requireProjectConfig();
4605
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
4588
4606
  const mainWorktreePath = getGitTopLevel();
4589
4607
  logger.info("reset \u547D\u4EE4\u6267\u884C");
4590
4608
  if (!isWorkingDirClean(mainWorktreePath)) {
@@ -4614,7 +4632,7 @@ function registerStatusCommand(program2) {
4614
4632
  });
4615
4633
  }
4616
4634
  async function handleStatus(options) {
4617
- validateMainWorktree();
4635
+ runPreChecks({ mainWorktree: true, headExists: true });
4618
4636
  if (options.interactive) {
4619
4637
  const panel = new InteractivePanel(collectStatus);
4620
4638
  await panel.start();
@@ -5294,7 +5312,7 @@ function registerInitCommand(program2) {
5294
5312
  );
5295
5313
  }
5296
5314
  async function handleInitShow() {
5297
- validateMainWorktree();
5315
+ runPreChecks({ mainWorktree: true, projectConfig: true });
5298
5316
  const config2 = requireProjectConfig();
5299
5317
  logger.info("init show \u547D\u4EE4\u6267\u884C\uFF0C\u8FDB\u5165\u4EA4\u4E92\u5F0F\u9879\u76EE\u914D\u7F6E");
5300
5318
  const { key, newValue } = await interactiveConfigEditor(
@@ -5307,8 +5325,11 @@ async function handleInitShow() {
5307
5325
  printSuccess(MESSAGES.INIT_SET_SUCCESS(key, String(newValue)));
5308
5326
  }
5309
5327
  async function handleInit(options) {
5310
- validateMainWorktree();
5328
+ runPreChecks({ mainWorktree: true });
5311
5329
  const existingConfig = loadProjectConfig();
5330
+ if (!options.branch) {
5331
+ validateHeadExists();
5332
+ }
5312
5333
  const branchName = options.branch || getCurrentBranch();
5313
5334
  logger.info(`init \u547D\u4EE4\u6267\u884C\uFF0C\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`);
5314
5335
  saveProjectConfig({ clawtMainWorkBranch: branchName });
@@ -5326,9 +5347,7 @@ function registerHomeCommand(program2) {
5326
5347
  });
5327
5348
  }
5328
5349
  async function handleHome() {
5329
- validateMainWorktree();
5330
- requireProjectConfig();
5331
- guardMainWorkBranchExists();
5350
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true, branchExists: true });
5332
5351
  const mainBranch = getMainWorkBranch();
5333
5352
  const currentBranch = getCurrentBranch();
5334
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
  /** 分支名清理后为空 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "3.4.2",
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,7 +4,7 @@ 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,
@@ -34,7 +34,7 @@ export function registerCreateCommand(program: Command): void {
34
34
  * @param {CreateOptions} options - 命令选项
35
35
  */
36
36
  async function handleCreate(options: CreateOptions): Promise<void> {
37
- validateMainWorktree();
37
+ runPreChecks({ mainWorktree: true, headExists: true });
38
38
 
39
39
  await guardMainWorkBranch();
40
40
 
@@ -1,8 +1,7 @@
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,
@@ -28,9 +27,7 @@ export function registerHomeCommand(program: Command): void {
28
27
  * 执行 home 命令:切换回主工作分支
29
28
  */
30
29
  async function handleHome(): Promise<void> {
31
- validateMainWorktree();
32
- requireProjectConfig();
33
- guardMainWorkBranchExists();
30
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true, branchExists: true });
34
31
 
35
32
  const mainBranch = getMainWorkBranch();
36
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,
@@ -136,7 +136,9 @@ function cleanupWorktreeAndBranch(worktreePath: string, branchName: string): voi
136
136
  * @param {MergeOptions} options - 命令选项
137
137
  */
138
138
  async function handleMerge(options: MergeOptions): Promise<void> {
139
- validateMainWorktree();
139
+ runPreChecks({ mainWorktree: true, headExists: true });
140
+
141
+ await guardMainWorkBranch();
140
142
 
141
143
  await guardMainWorkBranch();
142
144
 
@@ -4,7 +4,7 @@ import { ClawtError } from '../errors/index.js';
4
4
  import { MESSAGES } from '../constants/index.js';
5
5
  import type { RemoveOptions } from '../types/index.js';
6
6
  import {
7
- validateMainWorktree,
7
+ runPreChecks,
8
8
  getProjectName,
9
9
  getProjectWorktreeDir,
10
10
  getProjectWorktrees,
@@ -23,7 +23,6 @@ import {
23
23
  resolveTargetWorktrees,
24
24
  getValidateBranchName,
25
25
  deleteValidateBranch,
26
- requireProjectConfig,
27
26
  getCurrentBranch,
28
27
  } from '../utils/index.js';
29
28
  import type { WorktreeMultiResolveMessages } from '../utils/index.js';
@@ -56,8 +55,7 @@ export function registerRemoveCommand(program: Command): void {
56
55
  * @param {RemoveOptions} options - 命令选项
57
56
  */
58
57
  async function handleRemove(options: RemoveOptions): Promise<void> {
59
- validateMainWorktree();
60
- requireProjectConfig();
58
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
61
59
 
62
60
  const projectName = getProjectName();
63
61
  logger.info(`remove 命令执行,项目: ${projectName}`);
@@ -2,7 +2,7 @@ import type { Command } from 'commander';
2
2
  import { logger } from '../logger/index.js';
3
3
  import { MESSAGES } from '../constants/index.js';
4
4
  import {
5
- validateMainWorktree,
5
+ runPreChecks,
6
6
  getGitTopLevel,
7
7
  getConfigValue,
8
8
  isWorkingDirClean,
@@ -11,7 +11,6 @@ import {
11
11
  confirmDestructiveAction,
12
12
  printSuccess,
13
13
  printInfo,
14
- requireProjectConfig,
15
14
  } from '../utils/index.js';
16
15
 
17
16
  /**
@@ -31,8 +30,7 @@ export function registerResetCommand(program: Command): void {
31
30
  * 执行 reset 命令:重置主 worktree 工作区和暂存区
32
31
  */
33
32
  async function handleReset(): Promise<void> {
34
- validateMainWorktree();
35
- requireProjectConfig();
33
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
36
34
 
37
35
  const mainWorktreePath = getGitTopLevel();
38
36
  logger.info('reset 命令执行');
@@ -4,7 +4,7 @@ import { MESSAGES } from '../constants/index.js';
4
4
  import type { ResumeOptions } from '../types/index.js';
5
5
  import type { WorktreeInfo } from '../types/index.js';
6
6
  import {
7
- validateMainWorktree,
7
+ runPreChecks,
8
8
  validateClaudeCodeInstalled,
9
9
  getProjectWorktrees,
10
10
  launchInteractiveClaude,
@@ -47,7 +47,7 @@ export function registerResumeCommand(program: Command): void {
47
47
  * @param {ResumeOptions} options - 命令选项
48
48
  */
49
49
  async function handleResume(options: ResumeOptions): Promise<void> {
50
- validateMainWorktree();
50
+ runPreChecks({ mainWorktree: true, headExists: true });
51
51
  validateClaudeCodeInstalled();
52
52
 
53
53
  logger.info(`resume 命令执行,分支过滤: ${options.branch ?? '(无)'}`);
@@ -4,7 +4,7 @@ import { ClawtError } from '../errors/index.js';
4
4
  import { MESSAGES } from '../constants/index.js';
5
5
  import type { RunOptions, WorktreeInfo } from '../types/index.js';
6
6
  import {
7
- validateMainWorktree,
7
+ runPreChecks,
8
8
  validateClaudeCodeInstalled,
9
9
  createWorktrees,
10
10
  createWorktreesByBranches,
@@ -120,7 +120,7 @@ function handleDryRunFromFile(options: RunOptions): void {
120
120
  * @param {RunOptions} options - 命令选项
121
121
  */
122
122
  async function handleRun(options: RunOptions): Promise<void> {
123
- validateMainWorktree();
123
+ runPreChecks({ mainWorktree: true, headExists: true });
124
124
 
125
125
  // dry-run 模式跳过项目配置前置校验
126
126
  if (!options.dryRun) {
@@ -4,7 +4,7 @@ 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 {
7
- validateMainWorktree,
7
+ runPreChecks,
8
8
  getProjectName,
9
9
  getCurrentBranch,
10
10
  isWorkingDirClean,
@@ -46,7 +46,7 @@ export function registerStatusCommand(program: Command): void {
46
46
  * @param {StatusOptions} options - 命令选项
47
47
  */
48
48
  async function handleStatus(options: StatusOptions): Promise<void> {
49
- validateMainWorktree();
49
+ runPreChecks({ mainWorktree: true, headExists: true });
50
50
 
51
51
  // 交互式面板模式
52
52
  if (options.interactive) {
@@ -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 { SyncOptions } from '../types/index.js';
6
6
  import {
7
- validateMainWorktree,
7
+ runPreChecks,
8
8
  getGitTopLevel,
9
9
  getProjectWorktrees,
10
10
  isWorkingDirClean,
@@ -16,7 +16,6 @@ import {
16
16
  printInfo,
17
17
  printWarning,
18
18
  resolveTargetWorktree,
19
- requireProjectConfig,
20
19
  getMainWorkBranch,
21
20
  rebuildValidateBranch,
22
21
  getValidateBranchName,
@@ -128,8 +127,7 @@ export async function executeSyncForBranch(targetWorktreePath: string, branch: s
128
127
  * @param {SyncOptions} options - 命令选项
129
128
  */
130
129
  async function handleSync(options: SyncOptions): Promise<void> {
131
- validateMainWorktree();
132
- requireProjectConfig();
130
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
133
131
  await guardMainWorkBranch();
134
132
 
135
133
  logger.info(`sync 命令执行,分支: ${options.branch ?? '(未指定)'}`);
@@ -4,7 +4,7 @@ import { MESSAGES } from '../constants/index.js';
4
4
  import type { ValidateOptions } from '../types/index.js';
5
5
  import { executeSyncForBranch } from './sync.js';
6
6
  import {
7
- validateMainWorktree,
7
+ runPreChecks,
8
8
  getProjectName,
9
9
  getGitTopLevel,
10
10
  getProjectWorktrees,
@@ -25,7 +25,6 @@ import {
25
25
  printWarning,
26
26
  printInfo,
27
27
  resolveTargetWorktree,
28
- requireProjectConfig,
29
28
  ensureOnMainWorkBranch,
30
29
  handleDirtyWorkingDir,
31
30
  getValidateRunCommand,
@@ -98,9 +97,7 @@ async function handlePatchApplyFailure(targetWorktreePath: string, branchName: s
98
97
  * @param {ValidateOptions} options - 命令选项
99
98
  */
100
99
  async function handleValidateClean(options: ValidateOptions): Promise<void> {
101
- validateMainWorktree();
102
- // 显式前置校验:确保项目已初始化
103
- requireProjectConfig();
100
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
104
101
 
105
102
  const projectName = getProjectName();
106
103
  const mainWorktreePath = getGitTopLevel();
@@ -264,8 +261,7 @@ async function handleValidate(options: ValidateOptions): Promise<void> {
264
261
  return;
265
262
  }
266
263
 
267
- validateMainWorktree();
268
- requireProjectConfig();
264
+ runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true });
269
265
 
270
266
  const projectName = getProjectName();
271
267
  const mainWorktreePath = getGitTopLevel();
@@ -6,6 +6,8 @@ export const COMMON_MESSAGES = {
6
6
  GIT_NOT_INSTALLED: 'Git 未安装或不在 PATH 中,请先安装 Git',
7
7
  /** Claude Code CLI 未安装 */
8
8
  CLAUDE_NOT_INSTALLED: 'Claude Code CLI 未安装,请先安装:npm install -g @anthropic-ai/claude-code',
9
+ /** HEAD 不存在(仓库无任何 commit) */
10
+ HEAD_NOT_FOUND: '当前仓库尚未创建任何提交,请先执行 git commit 创建首次提交后再使用 clawt',
9
11
  /** 分支已存在 */
10
12
  BRANCH_EXISTS: (name: string) => `分支 ${name} 已存在,无法创建`,
11
13
  /** 分支名清理后为空 */
@@ -50,7 +50,7 @@ export {
50
50
  createBranch,
51
51
  } from './git.js';
52
52
  export { sanitizeBranchName, generateBranchNames, validateBranchesNotExist } from './branch.js';
53
- export { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled } from './validation.js';
53
+ export { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled, validateHeadExists, runPreChecks } from './validation.js';
54
54
  export { createWorktrees, getProjectWorktrees, getProjectWorktreeDir, cleanupWorktrees, getWorktreeStatus, createWorktreesByBranches } from './worktree.js';
55
55
  export { loadConfig, writeDefaultConfig, writeConfig, saveConfig, getConfigValue, ensureClawtDirs, parseConcurrency } from './config.js';
56
56
  export { printSuccess, printError, printWarning, printInfo, printHint, printSeparator, printDoubleSeparator, confirmAction, confirmDestructiveAction, formatWorktreeStatus, isWorktreeIdle, formatDuration, formatRelativeTime, formatDiskSize, formatLocalISOString } from './formatter.js';
@@ -2,6 +2,19 @@ import { MESSAGES } from '../constants/index.js';
2
2
  import { ClawtError } from '../errors/index.js';
3
3
  import { execCommand } from './shell.js';
4
4
  import { getGitCommonDir } from './git.js';
5
+ import { requireProjectConfig, guardMainWorkBranchExists } from './project-config.js';
6
+
7
+ /** 统一前置校验选项 */
8
+ interface PreCheckOptions {
9
+ /** 校验是否在主 worktree 根目录 */
10
+ mainWorktree?: boolean;
11
+ /** 校验 HEAD 是否存在(仓库有至少一次 commit) */
12
+ headExists?: boolean;
13
+ /** 校验项目是否已初始化(配置文件存在) */
14
+ projectConfig?: boolean;
15
+ /** 校验配置的主工作分支是否存在 */
16
+ branchExists?: boolean;
17
+ }
5
18
 
6
19
  /**
7
20
  * 校验当前目录是否为主 worktree 的根目录
@@ -46,3 +59,40 @@ export function validateClaudeCodeInstalled(): void {
46
59
  throw new ClawtError(MESSAGES.CLAUDE_NOT_INSTALLED);
47
60
  }
48
61
  }
62
+
63
+ /**
64
+ * 校验 HEAD 是否存在(仓库是否有至少一次 commit)
65
+ * git init 后未做任何 commit 时,HEAD 不指向有效引用
66
+ * @throws {ClawtError} HEAD 不存在时抛出
67
+ */
68
+ export function validateHeadExists(): void {
69
+ try {
70
+ execCommand('git rev-parse --verify HEAD');
71
+ } catch {
72
+ throw new ClawtError(MESSAGES.HEAD_NOT_FOUND);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * 统一前置校验入口,按需执行各项校验
78
+ * @param {PreCheckOptions} options - 校验选项
79
+ * @param {boolean} [options.mainWorktree] - 校验是否在主 worktree 根目录
80
+ * @param {boolean} [options.headExists] - 校验 HEAD 是否存在
81
+ * @param {boolean} [options.projectConfig] - 校验项目是否已初始化
82
+ * @param {boolean} [options.branchExists] - 校验配置的主工作分支是否存在
83
+ * @throws {ClawtError} 任一校验未通过时抛出
84
+ */
85
+ export function runPreChecks(options: PreCheckOptions): void {
86
+ if (options.mainWorktree) {
87
+ validateMainWorktree();
88
+ }
89
+ if (options.headExists) {
90
+ validateHeadExists();
91
+ }
92
+ if (options.projectConfig) {
93
+ requireProjectConfig();
94
+ }
95
+ if (options.branchExists) {
96
+ guardMainWorkBranchExists();
97
+ }
98
+ }
@@ -29,7 +29,7 @@ vi.mock('../../../src/constants/index.js', () => ({
29
29
  }));
30
30
 
31
31
  vi.mock('../../../src/utils/index.js', () => ({
32
- validateMainWorktree: vi.fn(),
32
+ runPreChecks: vi.fn(),
33
33
  requireProjectConfig: vi.fn(),
34
34
  getProjectName: vi.fn().mockReturnValue('test-project'),
35
35
  getGitTopLevel: vi.fn().mockReturnValue('/repo'),
@@ -24,7 +24,7 @@ vi.mock('../../../src/constants/index.js', () => ({
24
24
  }));
25
25
 
26
26
  vi.mock('../../../src/utils/index.js', () => ({
27
- validateMainWorktree: vi.fn(),
27
+ runPreChecks: vi.fn(),
28
28
  createWorktrees: vi.fn(),
29
29
  getConfigValue: vi.fn().mockReturnValue(true),
30
30
  requireProjectConfig: vi.fn().mockReturnValue({ clawtMainWorkBranch: 'main' }),
@@ -38,14 +38,14 @@ vi.mock('../../../src/utils/index.js', () => ({
38
38
  }));
39
39
 
40
40
  import { registerCreateCommand } from '../../../src/commands/create.js';
41
- import { validateMainWorktree, createWorktrees, printSuccess } from '../../../src/utils/index.js';
41
+ import { runPreChecks, createWorktrees, printSuccess } from '../../../src/utils/index.js';
42
42
 
43
- const mockedValidateMainWorktree = vi.mocked(validateMainWorktree);
43
+ const mockedRunPreChecks = vi.mocked(runPreChecks);
44
44
  const mockedCreateWorktrees = vi.mocked(createWorktrees);
45
45
  const mockedPrintSuccess = vi.mocked(printSuccess);
46
46
 
47
47
  beforeEach(() => {
48
- mockedValidateMainWorktree.mockReset();
48
+ mockedRunPreChecks.mockReset();
49
49
  mockedCreateWorktrees.mockReset();
50
50
  mockedPrintSuccess.mockReset();
51
51
  });
@@ -70,7 +70,7 @@ describe('handleCreate', () => {
70
70
  registerCreateCommand(program);
71
71
  await program.parseAsync(['create', '-b', 'feature'], { from: 'user' });
72
72
 
73
- expect(mockedValidateMainWorktree).toHaveBeenCalled();
73
+ expect(mockedRunPreChecks).toHaveBeenCalled();
74
74
  expect(mockedCreateWorktrees).toHaveBeenCalledWith('feature', 1);
75
75
  expect(mockedPrintSuccess).toHaveBeenCalled();
76
76
  });
@@ -22,7 +22,8 @@ vi.mock('../../../src/constants/index.js', () => ({
22
22
  }));
23
23
 
24
24
  vi.mock('../../../src/utils/index.js', () => ({
25
- validateMainWorktree: vi.fn(),
25
+ runPreChecks: vi.fn(),
26
+ validateHeadExists: vi.fn(),
26
27
  getCurrentBranch: vi.fn().mockReturnValue('main'),
27
28
  loadProjectConfig: vi.fn(),
28
29
  saveProjectConfig: vi.fn(),
@@ -13,7 +13,7 @@ vi.mock('../../../src/constants/index.js', () => ({
13
13
  }));
14
14
 
15
15
  vi.mock('../../../src/utils/index.js', () => ({
16
- validateMainWorktree: vi.fn(),
16
+ runPreChecks: vi.fn(),
17
17
  getProjectName: vi.fn(),
18
18
  getProjectWorktrees: vi.fn(),
19
19
  getWorktreeStatus: vi.fn(),
@@ -23,16 +23,16 @@ vi.mock('../../../src/utils/index.js', () => ({
23
23
  }));
24
24
 
25
25
  import { registerListCommand } from '../../../src/commands/list.js';
26
- import { validateMainWorktree, getProjectName, getProjectWorktrees, getWorktreeStatus, printInfo } from '../../../src/utils/index.js';
26
+ import { runPreChecks, getProjectName, getProjectWorktrees, getWorktreeStatus, printInfo } from '../../../src/utils/index.js';
27
27
 
28
- const mockedValidateMainWorktree = vi.mocked(validateMainWorktree);
28
+ const mockedRunPreChecks = vi.mocked(runPreChecks);
29
29
  const mockedGetProjectName = vi.mocked(getProjectName);
30
30
  const mockedGetProjectWorktrees = vi.mocked(getProjectWorktrees);
31
31
  const mockedGetWorktreeStatus = vi.mocked(getWorktreeStatus);
32
32
  const mockedPrintInfo = vi.mocked(printInfo);
33
33
 
34
34
  beforeEach(() => {
35
- mockedValidateMainWorktree.mockReset();
35
+ mockedRunPreChecks.mockReset();
36
36
  mockedGetProjectName.mockReset();
37
37
  mockedGetProjectWorktrees.mockReset();
38
38
  mockedGetWorktreeStatus.mockReset();
@@ -58,7 +58,7 @@ describe('handleList', () => {
58
58
  registerListCommand(program);
59
59
  program.parse(['list'], { from: 'user' });
60
60
 
61
- expect(mockedValidateMainWorktree).toHaveBeenCalled();
61
+ expect(mockedRunPreChecks).toHaveBeenCalled();
62
62
  expect(mockedPrintInfo).toHaveBeenCalled();
63
63
  });
64
64
 
@@ -40,7 +40,7 @@ vi.mock('../../../src/constants/index.js', () => ({
40
40
  }));
41
41
 
42
42
  vi.mock('../../../src/utils/index.js', () => ({
43
- validateMainWorktree: vi.fn(),
43
+ runPreChecks: vi.fn(),
44
44
  getProjectName: vi.fn(),
45
45
  getGitTopLevel: vi.fn(),
46
46
  getProjectWorktrees: vi.fn(),
@@ -33,7 +33,7 @@ vi.mock('../../../src/constants/index.js', () => ({
33
33
  }));
34
34
 
35
35
  vi.mock('../../../src/utils/index.js', () => ({
36
- validateMainWorktree: vi.fn(),
36
+ runPreChecks: vi.fn(),
37
37
  getProjectName: vi.fn(),
38
38
  getProjectWorktreeDir: vi.fn(),
39
39
  getProjectWorktrees: vi.fn(),
@@ -60,7 +60,7 @@ vi.mock('../../../src/utils/index.js', () => ({
60
60
 
61
61
  import { registerRemoveCommand } from '../../../src/commands/remove.js';
62
62
  import {
63
- validateMainWorktree,
63
+ runPreChecks,
64
64
  getProjectName,
65
65
  getProjectWorktrees,
66
66
  removeWorktreeByPath,
@@ -91,7 +91,7 @@ const mockedResolveTargetWorktrees = vi.mocked(resolveTargetWorktrees);
91
91
  const mockedGetCurrentBranch = vi.mocked(getCurrentBranch);
92
92
 
93
93
  beforeEach(() => {
94
- vi.mocked(validateMainWorktree).mockReset();
94
+ vi.mocked(runPreChecks).mockReset();
95
95
  mockedGetProjectName.mockReturnValue('test-project');
96
96
  mockedGetProjectWorktrees.mockReset();
97
97
  mockedRemoveWorktreeByPath.mockReset();
@@ -14,7 +14,7 @@ vi.mock('../../../src/constants/index.js', () => ({
14
14
  }));
15
15
 
16
16
  vi.mock('../../../src/utils/index.js', () => ({
17
- validateMainWorktree: vi.fn(),
17
+ runPreChecks: vi.fn(),
18
18
  getGitTopLevel: vi.fn(),
19
19
  getConfigValue: vi.fn(),
20
20
  isWorkingDirClean: vi.fn(),
@@ -17,7 +17,7 @@ vi.mock('../../../src/constants/index.js', () => ({
17
17
  }));
18
18
 
19
19
  vi.mock('../../../src/utils/index.js', () => ({
20
- validateMainWorktree: vi.fn(),
20
+ runPreChecks: vi.fn(),
21
21
  validateClaudeCodeInstalled: vi.fn(),
22
22
  getProjectWorktrees: vi.fn(),
23
23
  launchInteractiveClaude: vi.fn(),
@@ -33,7 +33,7 @@ vi.mock('../../../src/utils/index.js', () => ({
33
33
 
34
34
  import { registerResumeCommand } from '../../../src/commands/resume.js';
35
35
  import {
36
- validateMainWorktree,
36
+ runPreChecks,
37
37
  validateClaudeCodeInstalled,
38
38
  getProjectWorktrees,
39
39
  launchInteractiveClaude,
@@ -45,7 +45,7 @@ import {
45
45
  getConfigValue,
46
46
  } from '../../../src/utils/index.js';
47
47
 
48
- const mockedValidateMainWorktree = vi.mocked(validateMainWorktree);
48
+ const mockedRunPreChecks = vi.mocked(runPreChecks);
49
49
  const mockedValidateClaudeCodeInstalled = vi.mocked(validateClaudeCodeInstalled);
50
50
  const mockedGetProjectWorktrees = vi.mocked(getProjectWorktrees);
51
51
  const mockedLaunchInteractiveClaude = vi.mocked(launchInteractiveClaude);
@@ -57,7 +57,7 @@ const mockedConfirmAction = vi.mocked(confirmAction);
57
57
  const mockedGetConfigValue = vi.mocked(getConfigValue);
58
58
 
59
59
  beforeEach(() => {
60
- mockedValidateMainWorktree.mockReset();
60
+ mockedRunPreChecks.mockReset();
61
61
  mockedValidateClaudeCodeInstalled.mockReset();
62
62
  mockedGetProjectWorktrees.mockReset();
63
63
  mockedLaunchInteractiveClaude.mockReset();
@@ -90,7 +90,7 @@ describe('handleResume', () => {
90
90
  registerResumeCommand(program);
91
91
  await program.parseAsync(['resume', '-b', 'feature'], { from: 'user' });
92
92
 
93
- expect(mockedValidateMainWorktree).toHaveBeenCalled();
93
+ expect(mockedRunPreChecks).toHaveBeenCalled();
94
94
  expect(mockedValidateClaudeCodeInstalled).toHaveBeenCalled();
95
95
  expect(mockedResolveTargetWorktrees).toHaveBeenCalled();
96
96
  expect(mockedPromptGroupedMultiSelectBranches).not.toHaveBeenCalled();
@@ -50,7 +50,7 @@ vi.mock('../../../src/utils/index.js', async (importOriginal) => {
50
50
  const actual = await importOriginal<typeof import('../../../src/utils/index.js')>();
51
51
  return {
52
52
  ...actual,
53
- validateMainWorktree: vi.fn(),
53
+ runPreChecks: vi.fn(),
54
54
  validateClaudeCodeInstalled: vi.fn(),
55
55
  createWorktrees: vi.fn(),
56
56
  sanitizeBranchName: vi.fn(),
@@ -30,7 +30,7 @@ vi.mock('../../../src/constants/index.js', () => ({
30
30
  }));
31
31
 
32
32
  vi.mock('../../../src/utils/index.js', () => ({
33
- validateMainWorktree: vi.fn(),
33
+ runPreChecks: vi.fn(),
34
34
  getProjectName: vi.fn(),
35
35
  getCurrentBranch: vi.fn(),
36
36
  isWorkingDirClean: vi.fn(),
@@ -31,7 +31,7 @@ vi.mock('../../../src/constants/index.js', () => ({
31
31
  }));
32
32
 
33
33
  vi.mock('../../../src/utils/index.js', () => ({
34
- validateMainWorktree: vi.fn(),
34
+ runPreChecks: vi.fn(),
35
35
  getGitTopLevel: vi.fn(),
36
36
  getProjectWorktrees: vi.fn(),
37
37
  isWorkingDirClean: vi.fn(),
@@ -57,7 +57,7 @@ vi.mock('enquirer', () => ({
57
57
  }));
58
58
 
59
59
  vi.mock('../../../src/utils/index.js', () => ({
60
- validateMainWorktree: vi.fn(),
60
+ runPreChecks: vi.fn(),
61
61
  getProjectName: vi.fn(),
62
62
  getGitTopLevel: vi.fn(),
63
63
  getProjectWorktrees: vi.fn(),
@@ -10,6 +10,12 @@ vi.mock('../../../src/utils/git.js', () => ({
10
10
  getGitCommonDir: vi.fn(),
11
11
  }));
12
12
 
13
+ // mock project-config(runPreChecks 依赖 requireProjectConfig 和 guardMainWorkBranchExists)
14
+ vi.mock('../../../src/utils/project-config.js', () => ({
15
+ requireProjectConfig: vi.fn(),
16
+ guardMainWorkBranchExists: vi.fn(),
17
+ }));
18
+
13
19
  // mock logger
14
20
  vi.mock('../../../src/logger/index.js', () => ({
15
21
  logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
@@ -17,11 +23,14 @@ vi.mock('../../../src/logger/index.js', () => ({
17
23
 
18
24
  import { execCommand } from '../../../src/utils/shell.js';
19
25
  import { getGitCommonDir } from '../../../src/utils/git.js';
20
- import { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled } from '../../../src/utils/validation.js';
26
+ import { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled, validateHeadExists, runPreChecks } from '../../../src/utils/validation.js';
27
+ import { requireProjectConfig, guardMainWorkBranchExists } from '../../../src/utils/project-config.js';
21
28
  import { ClawtError } from '../../../src/errors/index.js';
22
29
 
23
30
  const mockedExecCommand = vi.mocked(execCommand);
24
31
  const mockedGetGitCommonDir = vi.mocked(getGitCommonDir);
32
+ const mockedRequireProjectConfig = vi.mocked(requireProjectConfig);
33
+ const mockedGuardMainWorkBranchExists = vi.mocked(guardMainWorkBranchExists);
25
34
 
26
35
  describe('validateMainWorktree', () => {
27
36
  it('.git 返回时正常通过', () => {
@@ -63,3 +72,36 @@ describe('validateClaudeCodeInstalled', () => {
63
72
  expect(() => validateClaudeCodeInstalled()).toThrow(ClawtError);
64
73
  });
65
74
  });
75
+
76
+ describe('validateHeadExists', () => {
77
+ it('HEAD 存在时正常通过', () => {
78
+ mockedExecCommand.mockReturnValue('abc1234');
79
+ expect(() => validateHeadExists()).not.toThrow();
80
+ });
81
+
82
+ it('HEAD 不存在时抛出 ClawtError', () => {
83
+ mockedExecCommand.mockImplementation(() => { throw new Error('fatal: ambiguous argument HEAD'); });
84
+ expect(() => validateHeadExists()).toThrow(ClawtError);
85
+ });
86
+ });
87
+
88
+ describe('runPreChecks', () => {
89
+ it('按选项组合调用对应校验', () => {
90
+ mockedGetGitCommonDir.mockReturnValue('.git');
91
+ mockedExecCommand.mockReturnValue('abc1234');
92
+ mockedRequireProjectConfig.mockReturnValue({ clawtMainWorkBranch: 'main' });
93
+ mockedGuardMainWorkBranchExists.mockReturnValue(undefined);
94
+
95
+ expect(() => runPreChecks({ mainWorktree: true, headExists: true, projectConfig: true, branchExists: true })).not.toThrow();
96
+ });
97
+
98
+ it('未设置的选项不触发校验', () => {
99
+ // 不设置任何选项,不应该调用任何校验函数
100
+ mockedGetGitCommonDir.mockImplementation(() => { throw new Error('should not be called'); });
101
+ mockedExecCommand.mockImplementation(() => { throw new Error('should not be called'); });
102
+ mockedRequireProjectConfig.mockImplementation(() => { throw new Error('should not be called'); });
103
+ mockedGuardMainWorkBranchExists.mockImplementation(() => { throw new Error('should not be called'); });
104
+
105
+ expect(() => runPreChecks({})).not.toThrow();
106
+ });
107
+ });