clawt 1.4.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -168,11 +168,11 @@ clawt remove [options]
168
168
  # 移除当前项目下所有 worktree
169
169
  clawt remove --all
170
170
 
171
- # 移除指定分支名下的所有 worktree
171
+ # 移除指定分支名下的所有 worktree(匹配 feature-scheme 和 feature-scheme-*)
172
172
  clawt remove -b feature-scheme
173
173
 
174
- # 移除指定分支名的某一个 worktree
175
- clawt remove -b feature-scheme -i 2
174
+ # 移除单个 worktree(直接写完整分支名)
175
+ clawt remove -b feature-scheme-2
176
176
  ```
177
177
 
178
178
  移除时会询问是否同时删除对应的本地分支。
package/dist/index.js CHANGED
@@ -325,15 +325,9 @@ function parseShortStat(output) {
325
325
  }
326
326
  return { insertions, deletions };
327
327
  }
328
- function getDiffStat(branchName, worktreePath, cwd) {
329
- const committedOutput = execCommand(`git diff --shortstat HEAD...${branchName}`, { cwd });
330
- const committed = parseShortStat(committedOutput);
331
- const uncommittedOutput = execCommand("git diff --shortstat HEAD", { cwd: worktreePath });
332
- const uncommitted = parseShortStat(uncommittedOutput);
333
- return {
334
- insertions: committed.insertions + uncommitted.insertions,
335
- deletions: committed.deletions + uncommitted.deletions
336
- };
328
+ function getDiffStat(worktreePath) {
329
+ const output = execCommand("git diff --shortstat HEAD", { cwd: worktreePath });
330
+ return parseShortStat(output);
337
331
  }
338
332
  function gitDiffCachedBinary(cwd) {
339
333
  logger.debug(`\u6267\u884C\u547D\u4EE4: git diff --cached --binary${cwd ? ` (cwd: ${cwd})` : ""}`);
@@ -545,7 +539,7 @@ function cleanupWorktrees(worktrees) {
545
539
  function getWorktreeStatus(worktree) {
546
540
  try {
547
541
  const commitCount = getCommitCountAhead(worktree.branch);
548
- const { insertions, deletions } = getDiffStat(worktree.branch, worktree.path);
542
+ const { insertions, deletions } = getDiffStat(worktree.path);
549
543
  const hasDirtyFiles = !isWorkingDirClean(worktree.path);
550
544
  return { commitCount, insertions, deletions, hasDirtyFiles };
551
545
  } catch (error) {
@@ -718,14 +712,12 @@ function handleCreate(options) {
718
712
  }
719
713
 
720
714
  // src/commands/remove.ts
721
- import { join as join4 } from "path";
722
715
  function registerRemoveCommand(program2) {
723
- program2.command("remove").description("\u79FB\u9664 worktree\uFF08\u652F\u6301\u5355\u4E2A/\u6279\u91CF/\u5168\u90E8\uFF09").option("--all", "\u79FB\u9664\u5F53\u524D\u9879\u76EE\u4E0B\u6240\u6709 worktree").option("-b, --branch <branchName>", "\u6307\u5B9A\u5206\u652F\u540D").option("-i, --index <index>", "\u6307\u5B9A\u7D22\u5F15\uFF08\u914D\u5408 -b \u4F7F\u7528\uFF09").action(async (options) => {
716
+ program2.command("remove").description("\u79FB\u9664 worktree\uFF08\u652F\u6301\u5355\u4E2A/\u6279\u91CF/\u5168\u90E8\uFF09").option("--all", "\u79FB\u9664\u5F53\u524D\u9879\u76EE\u4E0B\u6240\u6709 worktree").option("-b, --branch <branchName>", "\u6307\u5B9A\u5206\u652F\u540D\uFF08\u5B8C\u6574\u5206\u652F\u540D\u7CBE\u786E\u5339\u914D\uFF09").action(async (options) => {
724
717
  await handleRemove(options);
725
718
  });
726
719
  }
727
720
  function resolveWorktreesToRemove(options) {
728
- const projectDir = getProjectWorktreeDir();
729
721
  const allWorktrees = getProjectWorktrees();
730
722
  if (options.all) {
731
723
  return allWorktrees;
@@ -733,15 +725,6 @@ function resolveWorktreesToRemove(options) {
733
725
  if (!options.branch) {
734
726
  throw new ClawtError("\u8BF7\u6307\u5B9A --all \u6216 -b <branchName> \u53C2\u6570");
735
727
  }
736
- if (options.index !== void 0) {
737
- const targetName = `${options.branch}-${options.index}`;
738
- const targetPath = join4(projectDir, targetName);
739
- const found = allWorktrees.find((wt) => wt.path === targetPath);
740
- if (!found) {
741
- throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(targetName));
742
- }
743
- return [found];
744
- }
745
728
  const matched = allWorktrees.filter(
746
729
  (wt) => wt.branch === options.branch || wt.branch.startsWith(`${options.branch}-`)
747
730
  );
@@ -975,7 +958,7 @@ async function handleResume(options) {
975
958
  }
976
959
 
977
960
  // src/commands/validate.ts
978
- import { join as join5 } from "path";
961
+ import { join as join4 } from "path";
979
962
  import { existsSync as existsSync6 } from "fs";
980
963
  import Enquirer2 from "enquirer";
981
964
  function registerValidateCommand(program2) {
@@ -1096,7 +1079,7 @@ async function handleValidate(options) {
1096
1079
  const projectName = getProjectName();
1097
1080
  const mainWorktreePath = getGitTopLevel();
1098
1081
  const projectDir = getProjectWorktreeDir();
1099
- const targetWorktreePath = join5(projectDir, options.branch);
1082
+ const targetWorktreePath = join4(projectDir, options.branch);
1100
1083
  logger.info(`validate \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}`);
1101
1084
  if (!existsSync6(targetWorktreePath)) {
1102
1085
  throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch));
@@ -1131,7 +1114,7 @@ async function handleValidate(options) {
1131
1114
  }
1132
1115
 
1133
1116
  // src/commands/merge.ts
1134
- import { join as join6 } from "path";
1117
+ import { join as join5 } from "path";
1135
1118
  import { existsSync as existsSync7 } from "fs";
1136
1119
  function registerMergeCommand(program2) {
1137
1120
  program2.command("merge").description("\u5408\u5E76\u67D0\u4E2A\u5DF2\u9A8C\u8BC1\u7684 worktree \u5206\u652F\u5230\u4E3B worktree").requiredOption("-b, --branch <branchName>", "\u8981\u5408\u5E76\u7684\u5206\u652F\u540D").option("-m, --message <message>", "\u63D0\u4EA4\u4FE1\u606F\uFF08\u5DE5\u4F5C\u533A\u6709\u4FEE\u6539\u65F6\u5FC5\u586B\uFF09").action(async (options) => {
@@ -1154,7 +1137,7 @@ async function handleMerge(options) {
1154
1137
  validateMainWorktree();
1155
1138
  const mainWorktreePath = getGitTopLevel();
1156
1139
  const projectDir = getProjectWorktreeDir();
1157
- const targetWorktreePath = join6(projectDir, options.branch);
1140
+ const targetWorktreePath = join5(projectDir, options.branch);
1158
1141
  logger.info(`merge \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}\uFF0C\u63D0\u4EA4\u4FE1\u606F: ${options.message ?? "(\u672A\u63D0\u4F9B)"}`);
1159
1142
  if (!existsSync7(targetWorktreePath)) {
1160
1143
  throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch));
package/docs/spec.md CHANGED
@@ -522,8 +522,7 @@ clawt remove [options]
522
522
  | 参数 | 说明 |
523
523
  | --------- | ---------------------------------------------------------- |
524
524
  | `--all` | 移除当前项目 (`~/.clawt/worktrees/<project>/`) 下所有 worktree |
525
- | `-b <branchName>` | 移除指定 branchName 下的所有 worktree |
526
- | `-b <branchName> -i <index>` | 移除指定 branchName 的某一个 worktree (如 `branchName-2`) |
525
+ | `-b <branchName>` | 移除匹配 branchName branchName-* 的 worktree |
527
526
 
528
527
  **三种移除粒度:**
529
528
 
@@ -531,7 +530,7 @@ clawt remove [options]
531
530
  | ---- | ---------------------------------------- | ------------------------------------------------------------- |
532
531
  | 全部 | `clawt remove --all` | `~/.clawt/worktrees/<project>/` 下所有 worktree |
533
532
  | 分支 | `clawt remove -b feature-scheme` | `~/.clawt/worktrees/<project>/feature-scheme-*` 的所有 worktree |
534
- | 单个 | `clawt remove -b feature-scheme -i 2` | 仅移除 `~/.clawt/worktrees/<project>/feature-scheme-2` |
533
+ | 单个 | `clawt remove -b feature-scheme-2` | 仅移除 `feature-scheme-2` 对应的 worktree(完整分支名精确匹配) |
535
534
 
536
535
  **运行流程:**
537
536
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "1.4.0",
3
+ "version": "2.0.0",
4
4
  "description": "本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,4 @@
1
1
  import type { Command } from 'commander';
2
- import { join } from 'node:path';
3
- import { existsSync, readdirSync } from 'node:fs';
4
2
  import { logger } from '../logger/index.js';
5
3
  import { ClawtError } from '../errors/index.js';
6
4
  import { MESSAGES } from '../constants/index.js';
@@ -29,8 +27,7 @@ export function registerRemoveCommand(program: Command): void {
29
27
  .command('remove')
30
28
  .description('移除 worktree(支持单个/批量/全部)')
31
29
  .option('--all', '移除当前项目下所有 worktree')
32
- .option('-b, --branch <branchName>', '指定分支名')
33
- .option('-i, --index <index>', '指定索引(配合 -b 使用)')
30
+ .option('-b, --branch <branchName>', '指定分支名(完整分支名精确匹配)')
34
31
  .action(async (options: RemoveOptions) => {
35
32
  await handleRemove(options);
36
33
  });
@@ -42,7 +39,6 @@ export function registerRemoveCommand(program: Command): void {
42
39
  * @returns {Array<{path: string, branch: string}>} 待移除的 worktree 列表
43
40
  */
44
41
  function resolveWorktreesToRemove(options: RemoveOptions): Array<{ path: string; branch: string }> {
45
- const projectDir = getProjectWorktreeDir();
46
42
  const allWorktrees = getProjectWorktrees();
47
43
 
48
44
  if (options.all) {
@@ -53,23 +49,12 @@ function resolveWorktreesToRemove(options: RemoveOptions): Array<{ path: string;
53
49
  throw new ClawtError('请指定 --all 或 -b <branchName> 参数');
54
50
  }
55
51
 
56
- if (options.index !== undefined) {
57
- // 单个移除:branchName-<index>
58
- const targetName = `${options.branch}-${options.index}`;
59
- const targetPath = join(projectDir, targetName);
60
- const found = allWorktrees.find((wt) => wt.path === targetPath);
61
- if (!found) {
62
- throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(targetName));
63
- }
64
- return [found];
65
- }
66
-
67
52
  // 分支级移除:匹配 branchName 或 branchName-*
68
53
  const matched = allWorktrees.filter(
69
54
  (wt) => wt.branch === options.branch || wt.branch.startsWith(`${options.branch}-`),
70
55
  );
71
56
  if (matched.length === 0) {
72
- throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch!));
57
+ throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch));
73
58
  }
74
59
  return matched;
75
60
  }
@@ -36,8 +36,6 @@ export interface RemoveOptions {
36
36
  all?: boolean;
37
37
  /** 分支名 */
38
38
  branch?: string;
39
- /** 指定索引 */
40
- index?: number;
41
39
  }
42
40
 
43
41
  /** resume 命令选项 */
@@ -10,9 +10,9 @@ export interface WorktreeInfo {
10
10
  export interface WorktreeStatus {
11
11
  /** 相对于主分支的新增提交数 */
12
12
  commitCount: number;
13
- /** 新增行数 */
13
+ /** 工作区和暂存区的新增行数 */
14
14
  insertions: number;
15
- /** 删除行数 */
15
+ /** 工作区和暂存区的删除行数 */
16
16
  deletions: number;
17
17
  /** 工作区是否有未提交修改 */
18
18
  hasDirtyFiles: boolean;
package/src/utils/git.ts CHANGED
@@ -286,25 +286,14 @@ function parseShortStat(output: string): { insertions: number; deletions: number
286
286
  }
287
287
 
288
288
  /**
289
- * 获取目标分支的变更统计(已提交 + 未提交)
290
- * @param {string} branchName - 目标分支名
289
+ * 获取 worktree 中工作区和暂存区的变更统计
291
290
  * @param {string} worktreePath - worktree 目录路径
292
- * @param {string} [cwd] - 执行 git diff HEAD...branch 的工作目录
293
- * @returns {{ insertions: number; deletions: number }} 聚合后的新增和删除行数
294
- */
295
- export function getDiffStat(branchName: string, worktreePath: string, cwd?: string): { insertions: number; deletions: number } {
296
- // 已提交的变更(当前分支与目标分支的差异)
297
- const committedOutput = execCommand(`git diff --shortstat HEAD...${branchName}`, { cwd });
298
- const committed = parseShortStat(committedOutput);
299
-
300
- // 未提交的变更(在 worktree 内执行)
301
- const uncommittedOutput = execCommand('git diff --shortstat HEAD', { cwd: worktreePath });
302
- const uncommitted = parseShortStat(uncommittedOutput);
303
-
304
- return {
305
- insertions: committed.insertions + uncommitted.insertions,
306
- deletions: committed.deletions + uncommitted.deletions,
307
- };
291
+ * @returns {{ insertions: number; deletions: number }} 新增和删除行数
292
+ */
293
+ export function getDiffStat(worktreePath: string): { insertions: number; deletions: number } {
294
+ // 工作区和暂存区相对于 HEAD 的变更
295
+ const output = execCommand('git diff --shortstat HEAD', { cwd: worktreePath });
296
+ return parseShortStat(output);
308
297
  }
309
298
 
310
299
  /**
@@ -115,7 +115,7 @@ export function cleanupWorktrees(worktrees: WorktreeInfo[]): void {
115
115
  export function getWorktreeStatus(worktree: WorktreeInfo): WorktreeStatus | null {
116
116
  try {
117
117
  const commitCount = getCommitCountAhead(worktree.branch);
118
- const { insertions, deletions } = getDiffStat(worktree.branch, worktree.path);
118
+ const { insertions, deletions } = getDiffStat(worktree.path);
119
119
  const hasDirtyFiles = !isWorkingDirClean(worktree.path);
120
120
 
121
121
  return { commitCount, insertions, deletions, hasDirtyFiles };