clawt 2.3.8 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -100,7 +100,8 @@ var MESSAGES = {
100
100
  /** sync 冲突 */
101
101
  SYNC_CONFLICT: (worktreePath) => `\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u8FDB\u5165\u76EE\u6807 worktree \u624B\u52A8\u89E3\u51B3\uFF1A
102
102
  cd ${worktreePath}
103
- \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue`,
103
+ \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue
104
+ clawt validate -b <branch> \u9A8C\u8BC1\u53D8\u66F4`,
104
105
  /** validate patch apply 失败,提示用户同步主分支 */
105
106
  VALIDATE_PATCH_APPLY_FAILED: (branch) => `\u53D8\u66F4\u8FC1\u79FB\u5931\u8D25\uFF1A\u76EE\u6807\u5206\u652F\u4E0E\u4E3B\u5206\u652F\u5DEE\u5F02\u8FC7\u5927
106
107
  \u8BF7\u5148\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`,
@@ -113,6 +114,8 @@ var MESSAGES = {
113
114
  \u8BF7\u5728\u76EE\u6807 worktree \u4E2D\u63D0\u4EA4\u540E\u91CD\u65B0\u6267\u884C merge\uFF1A
114
115
  cd ${worktreePath}
115
116
  \u63D0\u4EA4\u5B8C\u6210\u540E\u6267\u884C\uFF1Aclawt merge -b ${branch}`,
117
+ /** 用户取消破坏性操作 */
118
+ DESTRUCTIVE_OP_CANCELLED: "\u5DF2\u53D6\u6D88\u64CD\u4F5C",
116
119
  /** reset 成功 */
117
120
  RESET_SUCCESS: "\u2713 \u4E3B worktree \u5DE5\u4F5C\u533A\u548C\u6682\u5B58\u533A\u5DF2\u91CD\u7F6E",
118
121
  /** reset 时工作区和暂存区已干净 */
@@ -146,6 +149,10 @@ var CONFIG_DEFINITIONS = {
146
149
  autoPullPush: {
147
150
  defaultValue: false,
148
151
  description: "merge \u6210\u529F\u540E\u662F\u5426\u81EA\u52A8\u6267\u884C git pull \u548C git push"
152
+ },
153
+ confirmDestructiveOps: {
154
+ defaultValue: true,
155
+ description: "\u6267\u884C\u7834\u574F\u6027\u64CD\u4F5C\uFF08reset\u3001validate --clean\uFF09\u524D\u662F\u5426\u63D0\u793A\u786E\u8BA4"
149
156
  }
150
157
  };
151
158
  function deriveDefaultConfig(definitions) {
@@ -420,6 +427,10 @@ function confirmAction(question) {
420
427
  });
421
428
  });
422
429
  }
430
+ function confirmDestructiveAction(dangerousCommand, description) {
431
+ printWarning(`\u5373\u5C06\u6267\u884C ${chalk.red.bold(dangerousCommand)}\uFF0C${description}`);
432
+ return confirmAction("\u662F\u5426\u7EE7\u7EED\uFF1F");
433
+ }
423
434
  function formatWorktreeStatus(status) {
424
435
  const parts = [];
425
436
  parts.push(chalk.yellow(`${status.commitCount} \u4E2A\u63D0\u4EA4`));
@@ -666,6 +677,21 @@ function removeSnapshot(projectName, branchName) {
666
677
  logger.info(`\u5DF2\u5220\u9664 validate \u5FEB\u7167: ${snapshotPath}`);
667
678
  }
668
679
  }
680
+ function removeProjectSnapshots(projectName) {
681
+ const projectDir = join3(VALIDATE_SNAPSHOTS_DIR, projectName);
682
+ if (!existsSync5(projectDir)) {
683
+ return;
684
+ }
685
+ const files = readdirSync3(projectDir);
686
+ for (const file of files) {
687
+ unlinkSync(join3(projectDir, file));
688
+ }
689
+ try {
690
+ rmdirSync2(projectDir);
691
+ } catch {
692
+ }
693
+ logger.info(`\u5DF2\u5220\u9664\u9879\u76EE ${projectName} \u7684\u6240\u6709 validate \u5FEB\u7167`);
694
+ }
669
695
 
670
696
  // src/commands/list.ts
671
697
  import chalk2 from "chalk";
@@ -773,6 +799,7 @@ async function handleRemove(options) {
773
799
  if (shouldDeleteBranch) {
774
800
  deleteBranch(wt.branch);
775
801
  }
802
+ removeSnapshot(projectName, wt.branch);
776
803
  printSuccess(MESSAGES.WORKTREE_REMOVED(wt.path));
777
804
  } catch (error) {
778
805
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -780,6 +807,9 @@ async function handleRemove(options) {
780
807
  failures.push({ path: wt.path, error: errorMessage });
781
808
  }
782
809
  }
810
+ if (options.all) {
811
+ removeProjectSnapshots(projectName);
812
+ }
783
813
  gitWorktreePrune();
784
814
  const projectDir = getProjectWorktreeDir();
785
815
  removeEmptyDir(projectDir);
@@ -1062,11 +1092,21 @@ function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName) {
1062
1092
  writeSnapshot(projectName, branchName, treeHash);
1063
1093
  return treeHash;
1064
1094
  }
1065
- function handleValidateClean(options) {
1095
+ async function handleValidateClean(options) {
1066
1096
  validateMainWorktree();
1067
1097
  const projectName = getProjectName();
1068
1098
  const mainWorktreePath = getGitTopLevel();
1069
1099
  logger.info(`validate --clean \u6267\u884C\uFF0C\u5206\u652F: ${options.branch}`);
1100
+ if (getConfigValue("confirmDestructiveOps")) {
1101
+ const confirmed = await confirmDestructiveAction(
1102
+ "git reset --hard + git clean -fd",
1103
+ `\u91CD\u7F6E\u4E3B worktree \u5E76\u5220\u9664\u5206\u652F ${options.branch} \u7684 validate \u5FEB\u7167`
1104
+ );
1105
+ if (!confirmed) {
1106
+ printInfo(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
1107
+ return;
1108
+ }
1109
+ }
1070
1110
  if (!isWorkingDirClean(mainWorktreePath)) {
1071
1111
  gitResetHard(mainWorktreePath);
1072
1112
  gitCleanForce(mainWorktreePath);
@@ -1099,7 +1139,7 @@ function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, project
1099
1139
  }
1100
1140
  async function handleValidate(options) {
1101
1141
  if (options.clean) {
1102
- handleValidateClean(options);
1142
+ await handleValidateClean(options);
1103
1143
  return;
1104
1144
  }
1105
1145
  validateMainWorktree();
@@ -1324,15 +1364,25 @@ async function handleSync(options) {
1324
1364
 
1325
1365
  // src/commands/reset.ts
1326
1366
  function registerResetCommand(program2) {
1327
- program2.command("reset").description("\u91CD\u7F6E\u4E3B worktree \u5DE5\u4F5C\u533A\u548C\u6682\u5B58\u533A\uFF08\u4FDD\u7559 validate \u5FEB\u7167\uFF09").action(() => {
1328
- handleReset();
1367
+ program2.command("reset").description("\u91CD\u7F6E\u4E3B worktree \u5DE5\u4F5C\u533A\u548C\u6682\u5B58\u533A\uFF08\u4FDD\u7559 validate \u5FEB\u7167\uFF09").action(async () => {
1368
+ await handleReset();
1329
1369
  });
1330
1370
  }
1331
- function handleReset() {
1371
+ async function handleReset() {
1332
1372
  validateMainWorktree();
1333
1373
  const mainWorktreePath = getGitTopLevel();
1334
1374
  logger.info("reset \u547D\u4EE4\u6267\u884C");
1335
1375
  if (!isWorkingDirClean(mainWorktreePath)) {
1376
+ if (getConfigValue("confirmDestructiveOps")) {
1377
+ const confirmed = await confirmDestructiveAction(
1378
+ "git reset --hard + git clean -fd",
1379
+ "\u4E22\u5F03\u6240\u6709\u672A\u63D0\u4EA4\u7684\u66F4\u6539"
1380
+ );
1381
+ if (!confirmed) {
1382
+ printInfo(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
1383
+ return;
1384
+ }
1385
+ }
1336
1386
  gitResetHard(mainWorktreePath);
1337
1387
  gitCleanForce(mainWorktreePath);
1338
1388
  printSuccess(MESSAGES.RESET_SUCCESS);
@@ -23,6 +23,10 @@ var CONFIG_DEFINITIONS = {
23
23
  autoPullPush: {
24
24
  defaultValue: false,
25
25
  description: "merge \u6210\u529F\u540E\u662F\u5426\u81EA\u52A8\u6267\u884C git pull \u548C git push"
26
+ },
27
+ confirmDestructiveOps: {
28
+ defaultValue: true,
29
+ description: "\u6267\u884C\u7834\u574F\u6027\u64CD\u4F5C\uFF08reset\u3001validate --clean\uFF09\u524D\u662F\u5426\u63D0\u793A\u786E\u8BA4"
26
30
  }
27
31
  };
28
32
  function deriveDefaultConfig(definitions) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "2.3.8",
3
+ "version": "2.4.0",
4
4
  "description": "本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -17,6 +17,8 @@ import {
17
17
  printSuccess,
18
18
  printError,
19
19
  confirmAction,
20
+ removeSnapshot,
21
+ removeProjectSnapshots,
20
22
  } from '../utils/index.js';
21
23
 
22
24
  /**
@@ -100,6 +102,8 @@ async function handleRemove(options: RemoveOptions): Promise<void> {
100
102
  if (shouldDeleteBranch) {
101
103
  deleteBranch(wt.branch);
102
104
  }
105
+ // 清理该分支对应的 validate 快照
106
+ removeSnapshot(projectName, wt.branch);
103
107
  printSuccess(MESSAGES.WORKTREE_REMOVED(wt.path));
104
108
  } catch (error) {
105
109
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -108,6 +112,11 @@ async function handleRemove(options: RemoveOptions): Promise<void> {
108
112
  }
109
113
  }
110
114
 
115
+ // --all 模式下清理整个项目的 validate 快照目录
116
+ if (options.all) {
117
+ removeProjectSnapshots(projectName);
118
+ }
119
+
111
120
  // 清理 worktree 并清除空目录
112
121
  gitWorktreePrune();
113
122
  const projectDir = getProjectWorktreeDir();
@@ -4,9 +4,11 @@ import { MESSAGES } from '../constants/index.js';
4
4
  import {
5
5
  validateMainWorktree,
6
6
  getGitTopLevel,
7
+ getConfigValue,
7
8
  isWorkingDirClean,
8
9
  gitResetHard,
9
10
  gitCleanForce,
11
+ confirmDestructiveAction,
10
12
  printSuccess,
11
13
  printInfo,
12
14
  } from '../utils/index.js';
@@ -19,20 +21,32 @@ export function registerResetCommand(program: Command): void {
19
21
  program
20
22
  .command('reset')
21
23
  .description('重置主 worktree 工作区和暂存区(保留 validate 快照)')
22
- .action(() => {
23
- handleReset();
24
+ .action(async () => {
25
+ await handleReset();
24
26
  });
25
27
  }
26
28
 
27
29
  /**
28
30
  * 执行 reset 命令:重置主 worktree 工作区和暂存区
29
31
  */
30
- function handleReset(): void {
32
+ async function handleReset(): Promise<void> {
31
33
  validateMainWorktree();
32
34
  const mainWorktreePath = getGitTopLevel();
33
35
  logger.info('reset 命令执行');
34
36
 
35
37
  if (!isWorkingDirClean(mainWorktreePath)) {
38
+ // 根据配置决定是否需要确认
39
+ if (getConfigValue('confirmDestructiveOps')) {
40
+ const confirmed = await confirmDestructiveAction(
41
+ 'git reset --hard + git clean -fd',
42
+ '丢弃所有未提交的更改',
43
+ );
44
+ if (!confirmed) {
45
+ printInfo(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
46
+ return;
47
+ }
48
+ }
49
+
36
50
  gitResetHard(mainWorktreePath);
37
51
  gitCleanForce(mainWorktreePath);
38
52
  printSuccess(MESSAGES.RESET_SUCCESS);
@@ -46,7 +46,7 @@ interface ClaudeTaskHandle {
46
46
  }
47
47
 
48
48
  /**
49
- * 在指定 worktree 中执行 Claude Code 任务
49
+ * 在指定 worktree 中执行 Claude Code 任务,由于是--output-format json形式,所以这里固定claude code cli的启动命令
50
50
  * @param {WorktreeInfo} worktree - worktree 信息
51
51
  * @param {string} task - 任务描述
52
52
  * @returns {ClaudeTaskHandle} 包含子进程引用和结果 Promise
@@ -11,6 +11,7 @@ import {
11
11
  getProjectName,
12
12
  getGitTopLevel,
13
13
  getProjectWorktreeDir,
14
+ getConfigValue,
14
15
  isWorkingDirClean,
15
16
  gitAddAll,
16
17
  gitCommit,
@@ -28,6 +29,7 @@ import {
28
29
  readSnapshotTreeHash,
29
30
  writeSnapshot,
30
31
  removeSnapshot,
32
+ confirmDestructiveAction,
31
33
  printSuccess,
32
34
  printWarning,
33
35
  printInfo,
@@ -163,7 +165,7 @@ function saveCurrentSnapshotTree(mainWorktreePath: string, projectName: string,
163
165
  * 处理 --clean 选项:清理 validate 状态
164
166
  * @param {ValidateOptions} options - 命令选项
165
167
  */
166
- function handleValidateClean(options: ValidateOptions): void {
168
+ async function handleValidateClean(options: ValidateOptions): Promise<void> {
167
169
  validateMainWorktree();
168
170
 
169
171
  const projectName = getProjectName();
@@ -171,6 +173,18 @@ function handleValidateClean(options: ValidateOptions): void {
171
173
 
172
174
  logger.info(`validate --clean 执行,分支: ${options.branch}`);
173
175
 
176
+ // 根据配置决定是否需要确认
177
+ if (getConfigValue('confirmDestructiveOps')) {
178
+ const confirmed = await confirmDestructiveAction(
179
+ 'git reset --hard + git clean -fd',
180
+ `重置主 worktree 并删除分支 ${options.branch} 的 validate 快照`,
181
+ );
182
+ if (!confirmed) {
183
+ printInfo(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
184
+ return;
185
+ }
186
+ }
187
+
174
188
  // 清空主 worktree
175
189
  if (!isWorkingDirClean(mainWorktreePath)) {
176
190
  gitResetHard(mainWorktreePath);
@@ -250,7 +264,7 @@ function handleIncrementalValidate(targetWorktreePath: string, mainWorktreePath:
250
264
  async function handleValidate(options: ValidateOptions): Promise<void> {
251
265
  // 处理 --clean 选项
252
266
  if (options.clean) {
253
- handleValidateClean(options);
267
+ await handleValidateClean(options);
254
268
  return;
255
269
  }
256
270
 
@@ -21,6 +21,10 @@ export const CONFIG_DEFINITIONS: ConfigDefinitions = {
21
21
  defaultValue: false,
22
22
  description: 'merge 成功后是否自动执行 git pull 和 git push',
23
23
  },
24
+ confirmDestructiveOps: {
25
+ defaultValue: true,
26
+ description: '执行破坏性操作(reset、validate --clean)前是否提示确认',
27
+ },
24
28
  };
25
29
 
26
30
  /**
@@ -86,7 +86,7 @@ export const MESSAGES = {
86
86
  `✓ 已将 ${mainBranch} 的最新代码同步到 ${targetBranch}`,
87
87
  /** sync 冲突 */
88
88
  SYNC_CONFLICT: (worktreePath: string) =>
89
- `合并存在冲突,请进入目标 worktree 手动解决:\n cd ${worktreePath}\n 解决冲突后执行 git add . && git merge --continue`,
89
+ `合并存在冲突,请进入目标 worktree 手动解决:\n cd ${worktreePath}\n 解决冲突后执行 git add . && git merge --continue\n clawt validate -b <branch> 验证变更`,
90
90
  /** validate patch apply 失败,提示用户同步主分支 */
91
91
  VALIDATE_PATCH_APPLY_FAILED: (branch: string) =>
92
92
  `变更迁移失败:目标分支与主分支差异过大\n 请先执行 clawt sync -b ${branch} 同步主分支后重试`,
@@ -98,6 +98,8 @@ export const MESSAGES = {
98
98
  /** squash 完成但未提供 -m,提示用户自行提交 */
99
99
  MERGE_SQUASH_PENDING: (worktreePath: string, branch: string) =>
100
100
  `✓ 已将所有提交压缩到暂存区\n 请在目标 worktree 中提交后重新执行 merge:\n cd ${worktreePath}\n 提交完成后执行:clawt merge -b ${branch}`,
101
+ /** 用户取消破坏性操作 */
102
+ DESTRUCTIVE_OP_CANCELLED: '已取消操作',
101
103
  /** reset 成功 */
102
104
  RESET_SUCCESS: '✓ 主 worktree 工作区和暂存区已重置',
103
105
  /** reset 时工作区和暂存区已干净 */
@@ -6,6 +6,8 @@ export interface ClawtConfig {
6
6
  claudeCodeCommand: string;
7
7
  /** merge 成功后是否自动执行 git pull 和 git push */
8
8
  autoPullPush: boolean;
9
+ /** 执行破坏性操作(reset、validate --clean)前是否提示确认 */
10
+ confirmDestructiveOps: boolean;
9
11
  }
10
12
 
11
13
  /** 单个配置项的完整定义(默认值 + 描述) */
@@ -67,6 +67,17 @@ export function confirmAction(question: string): Promise<boolean> {
67
67
  });
68
68
  }
69
69
 
70
+ /**
71
+ * 破坏性操作确认:先输出带高亮危险指令的警告,再等待用户确认
72
+ * @param {string} dangerousCommand - 即将执行的危险指令(会用红色加粗高亮)
73
+ * @param {string} description - 操作后果描述
74
+ * @returns {Promise<boolean>} 用户是否确认
75
+ */
76
+ export function confirmDestructiveAction(dangerousCommand: string, description: string): Promise<boolean> {
77
+ printWarning(`即将执行 ${chalk.red.bold(dangerousCommand)},${description}`);
78
+ return confirmAction('是否继续?');
79
+ }
80
+
70
81
  /**
71
82
  * 将 WorktreeStatus 格式化为带颜色的字符串
72
83
  * @param {WorktreeStatus} status - worktree 变更统计信息
@@ -45,7 +45,7 @@ export { sanitizeBranchName, generateBranchNames, validateBranchesNotExist } fro
45
45
  export { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled } from './validation.js';
46
46
  export { createWorktrees, getProjectWorktrees, getProjectWorktreeDir, cleanupWorktrees, getWorktreeStatus } from './worktree.js';
47
47
  export { loadConfig, getConfigValue, ensureClawtDirs } from './config.js';
48
- export { printSuccess, printError, printWarning, printInfo, printSeparator, printDoubleSeparator, confirmAction, formatWorktreeStatus } from './formatter.js';
48
+ export { printSuccess, printError, printWarning, printInfo, printSeparator, printDoubleSeparator, confirmAction, confirmDestructiveAction, formatWorktreeStatus } from './formatter.js';
49
49
  export { ensureDir, removeEmptyDir } from './fs.js';
50
50
  export { multilineInput } from './prompt.js';
51
51
  export { launchInteractiveClaude } from './claude.js';