clawt 2.7.0 → 2.7.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.
@@ -6,10 +6,11 @@
6
6
  - 完整的软件规格说明,包含 7 大章节
7
7
  - 命令流程在 `5. 需求场景详细设计` 下,每个命令一个子章节(5.1-5.13)
8
8
  - run 命令对应 `5.2 批量创建 Worktree + 执行 Claude Code 任务`,流程按步骤编号描述
9
- - merge 命令对应 `5.6 合并验证过的分支`,流程按步骤编号描述
10
- - config 命令对应 `5.10 查看全局配置`,只读展示配置
9
+ - merge 命令对应 `5.6 合并验证过的分支`,-b 可选,支持模糊匹配(与 resume/validate 共享匹配逻辑),流程按步骤编号描述
10
+ - config 命令对应 `5.10 查看和管理全局配置`,包含查看配置和 config reset 子命令两部分(使用 `####` 子标题区分)
11
11
  - resume 命令对应 `5.11 在已有 Worktree 中恢复会话`,支持模糊匹配和交互式分支选择(-b 可选)
12
12
  - validate 命令对应 `5.4 在主 Worktree 验证其他分支`,-b 可选,支持模糊匹配(与 resume 共享匹配逻辑)
13
+ - sync 命令对应 `5.12 将主分支代码同步到目标 Worktree`,-b 可选,支持模糊匹配(与 resume/validate/merge 共享匹配逻辑)
13
14
  - 配置项说明在 `5.7 默认配置文件` 章节的表格中
14
15
  - 更新模式:新增步骤时追加编号,配置项影响范围变化时更新说明列
15
16
 
@@ -21,7 +22,7 @@
21
22
 
22
23
  ## 关键约定
23
24
  - `autoDeleteBranch` 配置项影响三处:remove 命令、merge 命令、run 中断清理
24
- - `confirmDestructiveOps` 配置项影响两处:reset 命令、validate --clean
25
+ - `confirmDestructiveOps` 配置项影响三处:reset 命令、validate --clean、config reset
25
26
  - merge 的清理确认和清理操作均在 merge 成功后执行(避免 merge 冲突时提前询问用户造成困惑)
26
27
  - merge 成功后自动清理对应的 validate 快照(hasSnapshot + removeSnapshot)
27
28
  - merge 成功消息根据 `autoPullPush` 配置动态显示推送状态
@@ -34,7 +35,7 @@
34
35
  - `launchInteractiveClaude` 是 run(交互式模式)和 resume 共用的公共函数(在 src/utils/claude.ts)
35
36
  - killAllChildProcesses 是 run 专用的子进程终止函数(在 src/utils/shell.ts)
36
37
  - validate 快照管理函数在 `src/utils/validate-snapshot.ts`,被 validate、merge 和 remove 三个命令使用
37
- - `confirmDestructiveAction` 在 `src/utils/formatter.ts`,被 resetvalidate --clean 使用
38
+ - `confirmDestructiveAction` 在 `src/utils/formatter.ts`,被 resetvalidate --clean 和 config reset 使用
38
39
  - sanitizeBranchName 清理后为空串时抛出 BRANCH_NAME_EMPTY 错误
39
40
 
40
41
  ## 配置项同步检查点
@@ -66,10 +67,10 @@ Notes:
66
67
  - resume 和 run(交互式模式)共用 `launchInteractiveClaude()`,该函数从 run.ts 提取到 src/utils/claude.ts
67
68
  - `claudeCodeCommand` 配置项同时影响 run 交互式模式和 resume 命令
68
69
  - reset 命令与 validate --clean 的区别:reset 不删除快照文件,validate --clean 会删除快照
69
- - `resolveTargetWorktree()` 是 resume 和 validate 共用的分支匹配函数(在 src/utils/worktree-matcher.ts)
70
+ - `resolveTargetWorktree()` 是 resume、validate、mergesync 共用的分支匹配函数(在 src/utils/worktree-matcher.ts)
70
71
  - `WorktreeResolveMessages` 接口实现命令间消息解耦,每个命令传入各自的提示文案
71
- - resume 的消息常量在 `MESSAGES.RESUME_*`,validate 的消息常量在 `MESSAGES.VALIDATE_*`
72
- - resume 和 validate 的 `-b` 参数均为可选,匹配策略一致:精确→模糊(子串,大小写不敏感)→交互选择
72
+ - resume 的消息常量在 `MESSAGES.RESUME_*`,validate 的消息常量在 `MESSAGES.VALIDATE_*`,merge 的消息常量在 `MESSAGES.MERGE_*`,sync 的消息常量在 `MESSAGES.SYNC_*`
73
+ - resume、validate、mergesync 的 `-b` 参数均为可选,匹配策略一致:精确→模糊(子串,大小写不敏感)→交互选择
73
74
  - validate 的交互式选择和 resume 使用同一个 `promptSelectBranch()`(Enquirer.Select)
74
75
 
75
76
  ## validate 快照机制
package/README.md CHANGED
@@ -150,39 +150,67 @@ clawt validate --clean
150
150
  ### `clawt sync` — 将主分支代码同步到目标 worktree
151
151
 
152
152
  ```bash
153
+ # 指定分支名(支持模糊匹配)
153
154
  clawt sync -b <branchName>
155
+
156
+ # 不指定分支名(列出所有分支供选择)
157
+ clawt sync
154
158
  ```
155
159
 
156
160
  | 参数 | 必填 | 说明 |
157
161
  | ---- | ---- | ---- |
158
- | `-b` | | 要同步的分支名 |
162
+ | `-b` | | 要同步的分支名(支持模糊匹配,不传则列出所有分支供选择) |
159
163
 
160
164
  将主分支最新代码合并到目标 worktree 的分支中。如果目标 worktree 有未提交的修改,会自动保存后再合并。存在冲突时会提示用户手动解决。合并成功后会自动清除该分支的 validate 快照(代码基础已变化,旧快照无效)。
161
165
 
166
+ **分支匹配策略:**
167
+ - 传 `-b` 时,优先精确匹配分支名;未精确匹配则进行模糊匹配(子串匹配,大小写不敏感);模糊匹配到多个时通过交互列表选择;无匹配时报错并列出所有可用分支
168
+ - 不传 `-b` 时,列出当前项目所有可用分支供交互式选择(仅 1 个时自动使用)
169
+
162
170
  ```bash
163
- # 将主分支最新代码同步到目标 worktree
171
+ # 精确匹配分支名
164
172
  clawt sync -b feature-scheme-1
173
+
174
+ # 模糊匹配(匹配包含 "scheme" 的分支)
175
+ clawt sync -b scheme
176
+
177
+ # 交互式选择所有分支
178
+ clawt sync
165
179
  ```
166
180
 
167
181
  ### `clawt merge` — 合并分支到主 worktree
168
182
 
169
183
  ```bash
184
+ # 指定分支名(支持模糊匹配)
170
185
  clawt merge -b <branchName> [-m <commitMessage>]
186
+
187
+ # 不指定分支名(列出所有分支供选择)
188
+ clawt merge [-m <commitMessage>]
171
189
  ```
172
190
 
173
191
  | 参数 | 必填 | 说明 |
174
192
  | ---- | ---- | ---- |
175
- | `-b` | | 要合并的分支名 |
193
+ | `-b` | | 要合并的分支名(支持模糊匹配,不传则列出所有分支供选择) |
176
194
  | `-m` | 否 | 提交信息(目标 worktree 工作区有修改时必填) |
177
195
 
178
196
  将目标 worktree 的变更合并到主 worktree 的当前分支。如果配置了 `autoPullPush: true`,合并后会自动推送到远程仓库。如果目标 worktree 工作区有未提交的修改,需要通过 `-m` 提供提交信息;如果目标 worktree 已经提交过(工作区干净但有本地提交),可以省略 `-m` 直接合并。merge 成功后会询问是否清理对应的 worktree 和分支(如果配置了 `autoDeleteBranch: true` 则自动清理)。
179
197
 
198
+ **分支匹配策略:**
199
+ - 传 `-b` 时,优先精确匹配分支名;未精确匹配则进行模糊匹配(子串匹配,大小写不敏感);模糊匹配到多个时通过交互列表选择;无匹配时报错并列出所有可用分支
200
+ - 不传 `-b` 时,列出当前项目所有可用分支供交互式选择(仅 1 个时自动使用)
201
+
180
202
  如果检测到目标分支存在 `clawt sync` 产生的临时提交(auto-save commit),会自动提示是否将所有提交压缩(squash)为一个。用户选择压缩后,所有 commit 会被 reset 到暂存区:如果提供了 `-m` 则直接提交并继续合并流程;如果未提供 `-m` 则提示用户前往目标 worktree 自行提交后重新执行 merge。
181
203
 
182
204
  ```bash
183
- # 目标 worktree 有未提交修改,需提供 -m
205
+ # 精确匹配,目标 worktree 有未提交修改,需提供 -m
184
206
  clawt merge -b feature-scheme-1 -m "feat: 实现用户登录功能"
185
207
 
208
+ # 模糊匹配(匹配包含 "scheme" 的分支)
209
+ clawt merge -b scheme
210
+
211
+ # 交互式选择所有分支
212
+ clawt merge
213
+
186
214
  # 目标 worktree 已提交过,可省略 -m
187
215
  clawt merge -b feature-scheme-1
188
216
  ```
@@ -241,14 +269,20 @@ clawt reset
241
269
  clawt reset
242
270
  ```
243
271
 
244
- ### `clawt config` — 查看全局配置
272
+ ### `clawt config` — 查看和管理全局配置
245
273
 
246
274
  ```bash
275
+ # 查看全局配置
247
276
  clawt config
277
+
278
+ # 将配置恢复为默认值
279
+ clawt config reset
248
280
  ```
249
281
 
250
282
  读取并展示全局配置文件 `~/.clawt/config.json` 中的所有配置项,包括每项的当前值和描述说明。编辑配置需直接修改配置文件。
251
283
 
284
+ `config reset` 子命令可将配置文件恢复为默认值,执行前会弹出确认提示(受 `confirmDestructiveOps` 配置项控制)。
285
+
252
286
  ## 配置文件
253
287
 
254
288
  安装后会自动在 `~/.clawt/config.json` 生成全局配置文件:
@@ -267,7 +301,7 @@ clawt config
267
301
  | `autoDeleteBranch` | `boolean` | `false` | 移除 worktree 时自动删除对应本地分支;merge 成功后自动清理 worktree 和分支;run 中断后自动清理本次创建的 worktree 和分支 |
268
302
  | `claudeCodeCommand` | `string` | `"claude"` | Claude Code CLI 启动指令,用于 `clawt run` 不传 `--tasks` 时和 `clawt resume` 在 worktree 中打开交互式界面 |
269
303
  | `autoPullPush` | `boolean` | `false` | merge 成功后是否自动执行 git pull 和 git push |
270
- | `confirmDestructiveOps` | `boolean` | `true` | 执行破坏性操作(reset、validate --clean)前是否提示确认 |
304
+ | `confirmDestructiveOps` | `boolean` | `true` | 执行破坏性操作(reset、validate --clean、config reset)前是否提示确认 |
271
305
 
272
306
  ## 分支名规则
273
307
 
package/dist/index.js CHANGED
@@ -76,6 +76,8 @@ var MESSAGES = {
76
76
  INTERRUPT_KEPT: "\u5DF2\u4FDD\u7559 worktree\uFF0C\u53EF\u7A0D\u540E\u4F7F\u7528 clawt remove \u624B\u52A8\u6E05\u7406",
77
77
  /** 配置文件损坏,已重新生成默认配置 */
78
78
  CONFIG_CORRUPTED: "\u914D\u7F6E\u6587\u4EF6\u635F\u574F\u6216\u65E0\u6CD5\u89E3\u6790\uFF0C\u5DF2\u91CD\u65B0\u751F\u6210\u9ED8\u8BA4\u914D\u7F6E",
79
+ /** 配置已恢复为默认值 */
80
+ CONFIG_RESET_SUCCESS: "\u2713 \u914D\u7F6E\u5DF2\u6062\u590D\u4E3A\u9ED8\u8BA4\u503C",
79
81
  /** 分隔线 */
80
82
  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",
81
83
  /** 粗分隔线 */
@@ -144,7 +146,27 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
144
146
  /** validate 交互选择提示 */
145
147
  VALIDATE_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u9A8C\u8BC1\u7684\u5206\u652F",
146
148
  /** validate 模糊匹配到多个结果提示 */
147
- VALIDATE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`
149
+ VALIDATE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
150
+ /** merge 无可用 worktree */
151
+ MERGE_NO_WORKTREES: "\u5F53\u524D\u9879\u76EE\u6CA1\u6709\u53EF\u7528\u7684 worktree\uFF0C\u8BF7\u5148\u901A\u8FC7 clawt run \u6216 clawt create \u521B\u5EFA",
152
+ /** merge 模糊匹配无结果,列出可用分支 */
153
+ MERGE_NO_MATCH: (name, branches) => `\u672A\u627E\u5230\u4E0E "${name}" \u5339\u914D\u7684\u5206\u652F
154
+ \u53EF\u7528\u5206\u652F\uFF1A
155
+ ${branches.map((b) => ` - ${b}`).join("\n")}`,
156
+ /** merge 交互选择提示 */
157
+ MERGE_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u5408\u5E76\u7684\u5206\u652F",
158
+ /** merge 模糊匹配到多个结果提示 */
159
+ MERGE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
160
+ /** sync 无可用 worktree */
161
+ SYNC_NO_WORKTREES: "\u5F53\u524D\u9879\u76EE\u6CA1\u6709\u53EF\u7528\u7684 worktree\uFF0C\u8BF7\u5148\u901A\u8FC7 clawt run \u6216 clawt create \u521B\u5EFA",
162
+ /** sync 模糊匹配无结果,列出可用分支 */
163
+ SYNC_NO_MATCH: (name, branches) => `\u672A\u627E\u5230\u4E0E "${name}" \u5339\u914D\u7684\u5206\u652F
164
+ \u53EF\u7528\u5206\u652F\uFF1A
165
+ ${branches.map((b) => ` - ${b}`).join("\n")}`,
166
+ /** sync 交互选择提示 */
167
+ SYNC_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u540C\u6B65\u7684\u5206\u652F",
168
+ /** sync 模糊匹配到多个结果提示 */
169
+ SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`
148
170
  };
149
171
 
150
172
  // src/constants/exitCodes.ts
@@ -1317,13 +1339,17 @@ async function handleValidate(options) {
1317
1339
  }
1318
1340
 
1319
1341
  // src/commands/merge.ts
1320
- import { join as join4 } from "path";
1321
- import { existsSync as existsSync6 } from "fs";
1322
1342
  function registerMergeCommand(program2) {
1323
- 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) => {
1343
+ program2.command("merge").description("\u5408\u5E76\u67D0\u4E2A\u5DF2\u9A8C\u8BC1\u7684 worktree \u5206\u652F\u5230\u4E3B worktree").option("-b, --branch <branchName>", "\u8981\u5408\u5E76\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").option("-m, --message <message>", "\u63D0\u4EA4\u4FE1\u606F\uFF08\u5DE5\u4F5C\u533A\u6709\u4FEE\u6539\u65F6\u5FC5\u586B\uFF09").action(async (options) => {
1324
1344
  await handleMerge(options);
1325
1345
  });
1326
1346
  }
1347
+ var MERGE_RESOLVE_MESSAGES = {
1348
+ noWorktrees: MESSAGES.MERGE_NO_WORKTREES,
1349
+ selectBranch: MESSAGES.MERGE_SELECT_BRANCH,
1350
+ multipleMatches: MESSAGES.MERGE_MULTIPLE_MATCHES,
1351
+ noMatch: MESSAGES.MERGE_NO_MATCH
1352
+ };
1327
1353
  async function handleSquashIfNeeded(targetWorktreePath, mainWorktreePath, branchName, commitMessage) {
1328
1354
  if (!hasCommitWithMessage(branchName, AUTO_SAVE_COMMIT_MESSAGE, mainWorktreePath)) {
1329
1355
  return false;
@@ -1359,20 +1385,18 @@ function cleanupWorktreeAndBranch(worktreePath, branchName) {
1359
1385
  async function handleMerge(options) {
1360
1386
  validateMainWorktree();
1361
1387
  const mainWorktreePath = getGitTopLevel();
1362
- const projectDir = getProjectWorktreeDir();
1363
- const targetWorktreePath = join4(projectDir, options.branch);
1364
- logger.info(`merge \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}\uFF0C\u63D0\u4EA4\u4FE1\u606F: ${options.message ?? "(\u672A\u63D0\u4F9B)"}`);
1365
- if (!existsSync6(targetWorktreePath)) {
1366
- throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch));
1367
- }
1388
+ 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)"}`);
1389
+ const worktrees = getProjectWorktrees();
1390
+ const worktree = await resolveTargetWorktree(worktrees, MERGE_RESOLVE_MESSAGES, options.branch);
1391
+ const { path: targetWorktreePath, branch } = worktree;
1368
1392
  const projectName = getProjectName();
1369
1393
  if (!isWorkingDirClean(mainWorktreePath)) {
1370
- if (hasSnapshot(projectName, options.branch)) {
1371
- printWarning(MESSAGES.MERGE_VALIDATE_STATE_HINT(options.branch));
1394
+ if (hasSnapshot(projectName, branch)) {
1395
+ printWarning(MESSAGES.MERGE_VALIDATE_STATE_HINT(branch));
1372
1396
  }
1373
1397
  throw new ClawtError(MESSAGES.MAIN_WORKTREE_DIRTY);
1374
1398
  }
1375
- const shouldExit = await handleSquashIfNeeded(targetWorktreePath, mainWorktreePath, options.branch, options.message);
1399
+ const shouldExit = await handleSquashIfNeeded(targetWorktreePath, mainWorktreePath, branch, options.message);
1376
1400
  if (shouldExit) {
1377
1401
  return;
1378
1402
  }
@@ -1384,12 +1408,12 @@ async function handleMerge(options) {
1384
1408
  gitAddAll(targetWorktreePath);
1385
1409
  gitCommit(options.message, targetWorktreePath);
1386
1410
  } else {
1387
- if (!hasLocalCommits(options.branch, mainWorktreePath)) {
1411
+ if (!hasLocalCommits(branch, mainWorktreePath)) {
1388
1412
  throw new ClawtError(MESSAGES.TARGET_WORKTREE_NO_CHANGES);
1389
1413
  }
1390
1414
  }
1391
1415
  try {
1392
- gitMerge(options.branch, mainWorktreePath);
1416
+ gitMerge(branch, mainWorktreePath);
1393
1417
  } catch (error) {
1394
1418
  if (hasMergeConflict(mainWorktreePath)) {
1395
1419
  throw new ClawtError(MESSAGES.MERGE_CONFLICT);
@@ -1407,25 +1431,28 @@ async function handleMerge(options) {
1407
1431
  printInfo("\u5DF2\u8DF3\u8FC7\u81EA\u52A8 pull/push\uFF0C\u8BF7\u624B\u52A8\u6267\u884C git pull && git push");
1408
1432
  }
1409
1433
  if (options.message) {
1410
- printSuccess(MESSAGES.MERGE_SUCCESS(options.branch, options.message, autoPullPush));
1434
+ printSuccess(MESSAGES.MERGE_SUCCESS(branch, options.message, autoPullPush));
1411
1435
  } else {
1412
- printSuccess(MESSAGES.MERGE_SUCCESS_NO_MESSAGE(options.branch, autoPullPush));
1436
+ printSuccess(MESSAGES.MERGE_SUCCESS_NO_MESSAGE(branch, autoPullPush));
1413
1437
  }
1414
- const shouldCleanup = await shouldCleanupAfterMerge(options.branch);
1438
+ const shouldCleanup = await shouldCleanupAfterMerge(branch);
1415
1439
  if (shouldCleanup) {
1416
- cleanupWorktreeAndBranch(targetWorktreePath, options.branch);
1440
+ cleanupWorktreeAndBranch(targetWorktreePath, branch);
1417
1441
  }
1418
- if (hasSnapshot(projectName, options.branch)) {
1419
- removeSnapshot(projectName, options.branch);
1442
+ if (hasSnapshot(projectName, branch)) {
1443
+ removeSnapshot(projectName, branch);
1420
1444
  }
1421
1445
  }
1422
1446
 
1423
1447
  // src/commands/config.ts
1424
1448
  import chalk3 from "chalk";
1425
1449
  function registerConfigCommand(program2) {
1426
- program2.command("config").description("\u67E5\u770B\u5168\u5C40\u914D\u7F6E").action(() => {
1450
+ const configCmd = program2.command("config").description("\u67E5\u770B\u548C\u7BA1\u7406\u5168\u5C40\u914D\u7F6E").action(() => {
1427
1451
  handleConfig();
1428
1452
  });
1453
+ configCmd.command("reset").description("\u5C06\u914D\u7F6E\u6062\u590D\u4E3A\u9ED8\u8BA4\u503C").action(async () => {
1454
+ await handleConfigReset();
1455
+ });
1429
1456
  }
1430
1457
  function handleConfig() {
1431
1458
  const config = loadConfig();
@@ -1447,6 +1474,19 @@ ${chalk3.dim("\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84:")} ${CONFIG_PATH}
1447
1474
  }
1448
1475
  printSeparator();
1449
1476
  }
1477
+ async function handleConfigReset() {
1478
+ logger.info("config reset \u547D\u4EE4\u6267\u884C\uFF0C\u6062\u590D\u9ED8\u8BA4\u914D\u7F6E");
1479
+ const confirmed = await confirmDestructiveAction(
1480
+ "config reset",
1481
+ "\u5F53\u524D\u914D\u7F6E\u5C06\u88AB\u8986\u76D6\u4E3A\u9ED8\u8BA4\u503C"
1482
+ );
1483
+ if (!confirmed) {
1484
+ printInfo(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
1485
+ return;
1486
+ }
1487
+ writeDefaultConfig();
1488
+ printSuccess(MESSAGES.CONFIG_RESET_SUCCESS);
1489
+ }
1450
1490
  function formatConfigValue(value) {
1451
1491
  if (typeof value === "boolean") {
1452
1492
  return value ? chalk3.green("true") : chalk3.yellow("false");
@@ -1455,13 +1495,17 @@ function formatConfigValue(value) {
1455
1495
  }
1456
1496
 
1457
1497
  // src/commands/sync.ts
1458
- import { existsSync as existsSync7 } from "fs";
1459
- import { join as join5 } from "path";
1460
1498
  function registerSyncCommand(program2) {
1461
- program2.command("sync").description("\u5C06\u4E3B\u5206\u652F\u6700\u65B0\u4EE3\u7801\u540C\u6B65\u5230\u76EE\u6807 worktree").requiredOption("-b, --branch <branchName>", "\u8981\u540C\u6B65\u7684\u5206\u652F\u540D").action(async (options) => {
1499
+ program2.command("sync").description("\u5C06\u4E3B\u5206\u652F\u6700\u65B0\u4EE3\u7801\u540C\u6B65\u5230\u76EE\u6807 worktree").option("-b, --branch <branchName>", "\u8981\u540C\u6B65\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").action(async (options) => {
1462
1500
  await handleSync(options);
1463
1501
  });
1464
1502
  }
1503
+ var SYNC_RESOLVE_MESSAGES = {
1504
+ noWorktrees: MESSAGES.SYNC_NO_WORKTREES,
1505
+ selectBranch: MESSAGES.SYNC_SELECT_BRANCH,
1506
+ multipleMatches: MESSAGES.SYNC_MULTIPLE_MATCHES,
1507
+ noMatch: MESSAGES.SYNC_NO_MATCH
1508
+ };
1465
1509
  function autoSaveChanges(worktreePath, branch) {
1466
1510
  gitAddAll(worktreePath);
1467
1511
  gitCommit(AUTO_SAVE_COMMIT_MESSAGE, worktreePath);
@@ -1481,13 +1525,10 @@ function mergeMainBranch(worktreePath, mainBranch) {
1481
1525
  }
1482
1526
  async function handleSync(options) {
1483
1527
  validateMainWorktree();
1484
- const { branch } = options;
1485
- logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${branch}`);
1486
- const projectWorktreeDir = getProjectWorktreeDir();
1487
- const targetWorktreePath = join5(projectWorktreeDir, branch);
1488
- if (!existsSync7(targetWorktreePath)) {
1489
- throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(branch));
1490
- }
1528
+ logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
1529
+ const worktrees = getProjectWorktrees();
1530
+ const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
1531
+ const { path: targetWorktreePath, branch } = worktree;
1491
1532
  const mainWorktreePath = getGitTopLevel();
1492
1533
  const mainBranch = getCurrentBranch(mainWorktreePath);
1493
1534
  if (!isWorkingDirClean(targetWorktreePath)) {
package/docs/spec.md CHANGED
@@ -21,7 +21,7 @@
21
21
  - [5.7 默认配置文件](#57-默认配置文件)
22
22
  - [5.8 获取当前项目所有 Worktree](#58-获取当前项目所有-worktree)
23
23
  - [5.9 日志系统](#59-日志系统)
24
- - [5.10 查看全局配置](#510-查看全局配置)
24
+ - [5.10 查看和管理全局配置](#510-查看和管理全局配置)
25
25
  - [5.11 在已有 Worktree 中恢复会话](#511-在已有-worktree-中恢复会话)
26
26
  - [5.12 将主分支代码同步到目标 Worktree](#512-将主分支代码同步到目标-worktree)
27
27
  - [5.13 重置主 Worktree 工作区和暂存区](#513-重置主-worktree-工作区和暂存区)
@@ -168,6 +168,7 @@ git show-ref --verify refs/heads/<branchName> 2>/dev/null
168
168
  | `clawt remove` | 移除 worktree(支持单个/批量/全部) | 5.5 |
169
169
  | `clawt list` | 列出当前项目所有 worktree(支持 `--json` 格式输出) | 5.8 |
170
170
  | `clawt config` | 查看全局配置 | 5.10 |
171
+ | `clawt config reset` | 将配置恢复为默认值 | 5.10 |
171
172
  | `clawt resume` | 在已有 worktree 中恢复 Claude Code 交互式会话 | 5.11 |
172
173
  | `clawt sync` | 将主分支最新代码同步到目标 worktree | 5.12 |
173
174
  | `clawt reset` | 重置主 worktree 工作区和暂存区 | 5.13 |
@@ -622,41 +623,57 @@ git branch -D <branchName>
622
623
  **命令:**
623
624
 
624
625
  ```bash
626
+ # 指定分支名(支持模糊匹配)
625
627
  clawt merge -b <branchName> [-m <commitMessage>]
628
+
629
+ # 不指定分支名(列出所有分支供选择)
630
+ clawt merge [-m <commitMessage>]
626
631
  ```
627
632
 
628
633
  **参数:**
629
634
 
630
- | 参数 | 必填 | 说明 |
631
- | ---- | ---- | ---------------------------------------- |
632
- | `-b` | | 要合并的分支名 |
633
- | `-m` | 否 | 提交信息(目标 worktree 工作区有修改时必填) |
635
+ | 参数 | 必填 | 说明 |
636
+ | ---- | ---- | ------------------------------------------------------------------------ |
637
+ | `-b` | | 要合并的分支名(支持模糊匹配,不传则列出所有分支供选择) |
638
+ | `-m` | 否 | 提交信息(目标 worktree 工作区有修改时必填) |
634
639
 
635
640
  **运行流程:**
636
641
 
637
642
  1. **主 worktree 校验** (2.1)
638
- 2. **主 worktree 状态检测**
643
+ 2. **解析目标 worktree**:根据 `-b` 参数解析目标 worktree,匹配策略如下:
644
+ - **未传 `-b` 参数**:
645
+ - 获取当前项目所有 worktree
646
+ - 无可用 worktree → 报错退出
647
+ - 仅 1 个 worktree → 直接使用,无需选择
648
+ - 多个 worktree → 通过交互式列表(Enquirer.Select)让用户选择
649
+ - **传了 `-b` 参数**:
650
+ 1. **精确匹配优先**:在 worktree 列表中查找分支名完全相同的 worktree,找到则直接使用
651
+ 2. **模糊匹配**(子串匹配,大小写不敏感):
652
+ - 唯一匹配 → 直接使用
653
+ - 多个匹配 → 通过交互式列表让用户从匹配结果中选择
654
+ 3. **无匹配** → 报错退出,并列出所有可用分支名
655
+ 3. **主 worktree 状态检测**
639
656
  - 执行 `git status --porcelain`
640
657
  - 如果有更改:
641
658
  - 如果存在该分支的 validate 快照(`~/.clawt/validate-snapshots/<project>/<branchName>.tree`),额外输出警告提示用户可先执行 `clawt validate -b <branchName> --clean` 清理
642
659
  - 提示 `主 worktree 有未提交的更改,请先处理`,退出
643
660
  - 无更改 → 继续
644
- 3. **Squash 检测与执行(auto-save 临时提交压缩)**
661
+ 4. **Squash 检测与执行(auto-save 临时提交压缩)**
645
662
  - 通过 `git log HEAD..<branchName> --format=%s` 检查目标分支是否存在以 `AUTO_SAVE_COMMIT_MESSAGE`(`chore: auto-save before sync`)为前缀的 commit
646
- - **不存在** → 跳过,进入步骤 4
663
+ - **不存在** → 跳过,进入步骤 5
647
664
  - **存在** → 提示用户是否将所有提交压缩为一个:
648
665
  ```
649
666
  检测到 sync 产生的临时提交,是否将所有提交压缩为一个?
650
667
  压缩后变更将保留在目标worktree的暂存区,需要重新提交
651
668
  ```
652
- - **用户选择不压缩** → 跳过,进入步骤 4
669
+ - **用户选择不压缩** → 跳过,进入步骤 5
653
670
  - **用户选择压缩** →
654
671
  1. 获取主分支名(`git rev-parse --abbrev-ref HEAD`)
655
672
  2. 计算分叉点:`git merge-base <mainBranch> <branchName>`
656
673
  3. 在目标 worktree 中执行 `git reset --soft <merge-base>`,将所有 commit 撤销到暂存区
657
- 4. 如果用户提供了 `-m` → 直接在目标 worktree 执行 `git commit -m '<commitMessage>'`,输出成功提示,继续步骤 4
674
+ 4. 如果用户提供了 `-m` → 直接在目标 worktree 执行 `git commit -m '<commitMessage>'`,输出成功提示,继续步骤 5
658
675
  5. 如果用户未提供 `-m` → 提示用户前往目标 worktree 自行提交后重新执行 `clawt merge`,**退出流程**
659
- 4. **根据目标 worktree 状态决定是否需要提交**
676
+ 5. **根据目标 worktree 状态决定是否需要提交**
660
677
  - 检测目标 worktree 工作区是否干净(`git status --porcelain`)
661
678
  - **工作区有未提交修改**:
662
679
  - 如果用户未提供 `-m`,提示 `目标 worktree 有未提交的修改,请通过 -m 参数提供提交信息`,退出
@@ -670,22 +687,22 @@ clawt merge -b <branchName> [-m <commitMessage>]
670
687
  - 检查目标分支相对于主分支是否有本地提交(`git log HEAD..<branchName> --oneline`)
671
688
  - 有本地提交 → 跳过提交步骤,直接进入合并
672
689
  - 无本地提交 → 提示 `目标 worktree 没有任何可合并的变更(工作区干净且无本地提交)`,退出
673
- 5. **回到主 worktree 进行合并**
690
+ 6. **回到主 worktree 进行合并**
674
691
  ```bash
675
692
  cd <主 worktree 路径>
676
693
  git merge <branchName>
677
694
  ```
678
- 6. **冲突检测**
695
+ 7. **冲突检测**
679
696
  - 检查 merge 退出码及 `git status` 是否存在冲突
680
697
  - **有冲突** → 提示 `合并存在冲突,请手动处理`,退出
681
698
  - **无冲突** → 继续
682
- 7. **推送(受 `autoPullPush` 配置控制)**
699
+ 8. **推送(受 `autoPullPush` 配置控制)**
683
700
  ```bash
684
701
  # 仅当 autoPullPush 为 true 时执行
685
702
  git pull
686
703
  git push
687
704
  ```
688
- 8. **输出成功提示**
705
+ 9. **输出成功提示**
689
706
 
690
707
  ```
691
708
  # 提供了 -m 且已推送时
@@ -705,7 +722,7 @@ clawt merge -b <branchName> [-m <commitMessage>]
705
722
  ✓ 分支 feature-scheme-1 已成功合并到当前分支
706
723
  ```
707
724
 
708
- 9. **merge 成功后确认并清理 worktree 和分支(可选)**
725
+ 10. **merge 成功后确认并清理 worktree 和分支(可选)**
709
726
  - 如果配置文件中 `autoDeleteBranch` 为 `true`,自动执行清理
710
727
  - 否则交互式询问用户是否清理
711
728
  - 用户确认后,依次执行:
@@ -720,7 +737,7 @@ clawt merge -b <branchName> [-m <commitMessage>]
720
737
  ```
721
738
  - 输出清理成功提示:`✓ 已清理 worktree 和分支: <branchName>`
722
739
 
723
- 10. **清理 validate 快照**
740
+ 11. **清理 validate 快照**
724
741
  - merge 成功后,如果存在该分支的 validate 快照(`~/.clawt/validate-snapshots/<project>/<branchName>.tree` 和 `<branchName>.head`),自动删除这些快照文件(merge 成功后快照已无意义)
725
742
 
726
743
  > **注意:** 清理确认和清理操作均在 merge 成功后执行。只有 merge 成功才会询问用户是否清理 worktree 和分支,避免 merge 冲突时用户被提前询问造成困惑。
@@ -759,7 +776,7 @@ clawt merge -b <branchName> [-m <commitMessage>]
759
776
  | `autoDeleteBranch` | `boolean` | `false` | 移除 worktree 时是否自动删除对应本地分支(无需每次确认);merge 成功后是否自动清理 worktree 和分支;run 任务被中断(Ctrl+C)后是否自动清理本次创建的 worktree 和分支 |
760
777
  | `claudeCodeCommand` | `string` | `"claude"` | Claude Code CLI 启动指令,用于 `clawt run` 不传 `--tasks` 时和 `clawt resume` 在 worktree 中打开交互式界面 |
761
778
  | `autoPullPush` | `boolean` | `false` | merge 成功后是否自动执行 git pull 和 git push |
762
- | `confirmDestructiveOps` | `boolean` | `true` | 执行破坏性操作(reset、validate --clean)前是否提示确认 |
779
+ | `confirmDestructiveOps` | `boolean` | `true` | 执行破坏性操作(reset、validate --clean、config reset)前是否提示确认 |
763
780
 
764
781
  ---
765
782
 
@@ -862,14 +879,20 @@ clawt list [--json]
862
879
 
863
880
  ---
864
881
 
865
- ### 5.10 查看全局配置
882
+ ### 5.10 查看和管理全局配置
866
883
 
867
884
  **命令:**
868
885
 
869
886
  ```bash
887
+ # 查看全局配置
870
888
  clawt config
889
+
890
+ # 将配置恢复为默认值
891
+ clawt config reset
871
892
  ```
872
893
 
894
+ #### 查看配置
895
+
873
896
  **运行流程:**
874
897
 
875
898
  1. 读取全局配置文件 `~/.clawt/config.json`
@@ -900,6 +923,14 @@ clawt config
900
923
 
901
924
  ```
902
925
 
926
+ #### 恢复默认配置
927
+
928
+ **运行流程:**
929
+
930
+ 1. 如果配置项 `confirmDestructiveOps` 为 `true`,提示确认(显示即将执行的操作和后果:当前配置将被覆盖为默认值),用户取消则退出
931
+ 2. 将默认配置写入 `~/.clawt/config.json`(覆盖现有配置文件)
932
+ 3. 输出成功提示:`✓ 配置已恢复为默认值`
933
+
903
934
  ---
904
935
 
905
936
  ### 5.11 在已有 Worktree 中恢复会话
@@ -951,14 +982,18 @@ clawt resume
951
982
  **命令:**
952
983
 
953
984
  ```bash
985
+ # 指定分支名(支持模糊匹配)
954
986
  clawt sync -b <branchName>
987
+
988
+ # 不指定分支名(列出所有分支供选择)
989
+ clawt sync
955
990
  ```
956
991
 
957
992
  **参数:**
958
993
 
959
- | 参数 | 必填 | 说明 |
960
- | ---- | ---- | ----------------------------------------------------- |
961
- | `-b` | | 要同步的分支名(对应已有 worktree 的分支) |
994
+ | 参数 | 必填 | 说明 |
995
+ | ---- | ---- | ------------------------------------------------------------------------ |
996
+ | `-b` | | 要同步的分支名(支持模糊匹配,不传则列出所有分支供选择) |
962
997
 
963
998
  **使用场景:**
964
999
 
@@ -967,8 +1002,18 @@ clawt sync -b <branchName>
967
1002
  **运行流程:**
968
1003
 
969
1004
  1. **主 worktree 校验** (2.1)
970
- 2. **检查目标 worktree 是否存在**:确认 `~/.clawt/worktrees/<project>/<branchName>` 目录存在
971
- - 不存在 报错退出
1005
+ 2. **解析目标 worktree**:根据 `-b` 参数解析目标 worktree,匹配策略如下:
1006
+ - **未传 `-b` 参数**:
1007
+ - 获取当前项目所有 worktree
1008
+ - 无可用 worktree → 报错退出
1009
+ - 仅 1 个 worktree → 直接使用,无需选择
1010
+ - 多个 worktree → 通过交互式列表(Enquirer.Select)让用户选择
1011
+ - **传了 `-b` 参数**:
1012
+ 1. **精确匹配优先**:在 worktree 列表中查找分支名完全相同的 worktree,找到则直接使用
1013
+ 2. **模糊匹配**(子串匹配,大小写不敏感):
1014
+ - 唯一匹配 → 直接使用
1015
+ - 多个匹配 → 通过交互式列表让用户从匹配结果中选择
1016
+ 3. **无匹配** → 报错退出,并列出所有可用分支名
972
1017
  3. **获取主分支名**:通过 `git rev-parse --abbrev-ref HEAD` 获取主 worktree 当前分支名(不硬编码 main/master)
973
1018
  4. **自动保存未提交变更**:检查目标 worktree 是否有未提交修改
974
1019
  - 有修改 → 自动执行 `git add . && git commit -m "<AUTO_SAVE_COMMIT_MESSAGE>"` 保存变更(commit message 由常量 `AUTO_SAVE_COMMIT_MESSAGE` 定义,值为 `chore: auto-save before sync`,同时用于 merge 命令的 squash 检测)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "2.7.0",
3
+ "version": "2.7.2",
4
4
  "description": "本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,21 +1,28 @@
1
1
  import type { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import { CONFIG_PATH, DEFAULT_CONFIG, CONFIG_DESCRIPTIONS } from '../constants/index.js';
3
+ import { CONFIG_PATH, DEFAULT_CONFIG, CONFIG_DESCRIPTIONS, MESSAGES } from '../constants/index.js';
4
4
  import { logger } from '../logger/index.js';
5
- import { loadConfig, printInfo, printSeparator } from '../utils/index.js';
5
+ import { loadConfig, writeDefaultConfig, printInfo, printSuccess, printSeparator, confirmDestructiveAction } from '../utils/index.js';
6
6
  import type { ClawtConfig } from '../types/index.js';
7
7
 
8
8
  /**
9
- * 注册 config 命令:查看全局配置
9
+ * 注册 config 命令组:查看和管理全局配置
10
10
  * @param {Command} program - Commander 实例
11
11
  */
12
12
  export function registerConfigCommand(program: Command): void {
13
- program
13
+ const configCmd = program
14
14
  .command('config')
15
- .description('查看全局配置')
15
+ .description('查看和管理全局配置')
16
16
  .action(() => {
17
17
  handleConfig();
18
18
  });
19
+
20
+ configCmd
21
+ .command('reset')
22
+ .description('将配置恢复为默认值')
23
+ .action(async () => {
24
+ await handleConfigReset();
25
+ });
19
26
  }
20
27
 
21
28
  /**
@@ -48,6 +55,26 @@ function handleConfig(): void {
48
55
  printSeparator();
49
56
  }
50
57
 
58
+ /**
59
+ * 执行 config reset 子命令的核心逻辑,将配置恢复为默认值
60
+ */
61
+ async function handleConfigReset(): Promise<void> {
62
+ logger.info('config reset 命令执行,恢复默认配置');
63
+
64
+ const confirmed = await confirmDestructiveAction(
65
+ 'config reset',
66
+ '当前配置将被覆盖为默认值',
67
+ );
68
+
69
+ if (!confirmed) {
70
+ printInfo(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
71
+ return;
72
+ }
73
+
74
+ writeDefaultConfig();
75
+ printSuccess(MESSAGES.CONFIG_RESET_SUCCESS);
76
+ }
77
+
51
78
  /**
52
79
  * 格式化配置值的显示样式
53
80
  * @param {ClawtConfig[keyof ClawtConfig]} value - 配置值
@@ -1,6 +1,4 @@
1
1
  import type { Command } from 'commander';
2
- import { join } from 'node:path';
3
- import { existsSync } from 'node:fs';
4
2
  import { logger } from '../logger/index.js';
5
3
  import { ClawtError } from '../errors/index.js';
6
4
  import { MESSAGES, AUTO_SAVE_COMMIT_MESSAGE } from '../constants/index.js';
@@ -9,7 +7,7 @@ import {
9
7
  validateMainWorktree,
10
8
  getProjectName,
11
9
  getGitTopLevel,
12
- getProjectWorktreeDir,
10
+ getProjectWorktrees,
13
11
  isWorkingDirClean,
14
12
  gitAddAll,
15
13
  gitCommit,
@@ -30,7 +28,9 @@ import {
30
28
  gitMergeBase,
31
29
  gitResetSoftTo,
32
30
  getCurrentBranch,
31
+ resolveTargetWorktree,
33
32
  } from '../utils/index.js';
33
+ import type { WorktreeResolveMessages } from '../utils/index.js';
34
34
 
35
35
  /**
36
36
  * 注册 merge 命令:合并验证过的分支到主 worktree
@@ -40,13 +40,21 @@ export function registerMergeCommand(program: Command): void {
40
40
  program
41
41
  .command('merge')
42
42
  .description('合并某个已验证的 worktree 分支到主 worktree')
43
- .requiredOption('-b, --branch <branchName>', '要合并的分支名')
43
+ .option('-b, --branch <branchName>', '要合并的分支名(支持模糊匹配,不传则列出所有分支)')
44
44
  .option('-m, --message <message>', '提交信息(工作区有修改时必填)')
45
45
  .action(async (options: MergeOptions) => {
46
46
  await handleMerge(options);
47
47
  });
48
48
  }
49
49
 
50
+ /** merge 命令的分支解析消息配置 */
51
+ const MERGE_RESOLVE_MESSAGES: WorktreeResolveMessages = {
52
+ noWorktrees: MESSAGES.MERGE_NO_WORKTREES,
53
+ selectBranch: MESSAGES.MERGE_SELECT_BRANCH,
54
+ multipleMatches: MESSAGES.MERGE_MULTIPLE_MATCHES,
55
+ noMatch: MESSAGES.MERGE_NO_MATCH,
56
+ };
57
+
50
58
  /**
51
59
  * 检测并处理目标分支的 auto-save 提交压缩
52
60
  * 如果检测到 sync 产生的临时提交,提示用户是否将所有提交压缩为一个
@@ -126,29 +134,27 @@ async function handleMerge(options: MergeOptions): Promise<void> {
126
134
  validateMainWorktree();
127
135
 
128
136
  const mainWorktreePath = getGitTopLevel();
129
- const projectDir = getProjectWorktreeDir();
130
- const targetWorktreePath = join(projectDir, options.branch);
131
137
 
132
- logger.info(`merge 命令执行,分支: ${options.branch},提交信息: ${options.message ?? '(未提供)'}`);
138
+ logger.info(`merge 命令执行,分支: ${options.branch ?? '(未指定)'},提交信息: ${options.message ?? '(未提供)'}`);
133
139
 
134
- // 检查目标 worktree 是否存在
135
- if (!existsSync(targetWorktreePath)) {
136
- throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch));
137
- }
140
+ // 解析目标 worktree(精确匹配 / 模糊匹配 / 交互选择)
141
+ const worktrees = getProjectWorktrees();
142
+ const worktree = await resolveTargetWorktree(worktrees, MERGE_RESOLVE_MESSAGES, options.branch);
143
+ const { path: targetWorktreePath, branch } = worktree;
138
144
 
139
145
  const projectName = getProjectName();
140
146
 
141
147
  // 步骤 3:主 worktree 状态检测
142
148
  if (!isWorkingDirClean(mainWorktreePath)) {
143
149
  // 如果存在 validate 快照状态,提示用户先清理
144
- if (hasSnapshot(projectName, options.branch)) {
145
- printWarning(MESSAGES.MERGE_VALIDATE_STATE_HINT(options.branch));
150
+ if (hasSnapshot(projectName, branch)) {
151
+ printWarning(MESSAGES.MERGE_VALIDATE_STATE_HINT(branch));
146
152
  }
147
153
  throw new ClawtError(MESSAGES.MAIN_WORKTREE_DIRTY);
148
154
  }
149
155
 
150
156
  // 步骤 3.5:检测是否需要 squash(sync 临时提交压缩)
151
- const shouldExit = await handleSquashIfNeeded(targetWorktreePath, mainWorktreePath, options.branch, options.message);
157
+ const shouldExit = await handleSquashIfNeeded(targetWorktreePath, mainWorktreePath, branch, options.message);
152
158
  if (shouldExit) {
153
159
  return;
154
160
  }
@@ -165,7 +171,7 @@ async function handleMerge(options: MergeOptions): Promise<void> {
165
171
  gitCommit(options.message, targetWorktreePath);
166
172
  } else {
167
173
  // 目标 worktree 干净,检查是否有本地提交
168
- if (!hasLocalCommits(options.branch, mainWorktreePath)) {
174
+ if (!hasLocalCommits(branch, mainWorktreePath)) {
169
175
  throw new ClawtError(MESSAGES.TARGET_WORKTREE_NO_CHANGES);
170
176
  }
171
177
  // 有本地提交,跳过提交步骤,直接合并
@@ -173,7 +179,7 @@ async function handleMerge(options: MergeOptions): Promise<void> {
173
179
 
174
180
  // 步骤 5:回到主 worktree 进行合并
175
181
  try {
176
- gitMerge(options.branch, mainWorktreePath);
182
+ gitMerge(branch, mainWorktreePath);
177
183
  } catch (error) {
178
184
  // 检查是否有冲突
179
185
  if (hasMergeConflict(mainWorktreePath)) {
@@ -198,19 +204,19 @@ async function handleMerge(options: MergeOptions): Promise<void> {
198
204
 
199
205
  // 步骤 8:输出成功提示(根据是否有 message 选择对应模板)
200
206
  if (options.message) {
201
- printSuccess(MESSAGES.MERGE_SUCCESS(options.branch, options.message, autoPullPush));
207
+ printSuccess(MESSAGES.MERGE_SUCCESS(branch, options.message, autoPullPush));
202
208
  } else {
203
- printSuccess(MESSAGES.MERGE_SUCCESS_NO_MESSAGE(options.branch, autoPullPush));
209
+ printSuccess(MESSAGES.MERGE_SUCCESS_NO_MESSAGE(branch, autoPullPush));
204
210
  }
205
211
 
206
212
  // 步骤 9:merge 成功后确认并清理 worktree 和分支
207
- const shouldCleanup = await shouldCleanupAfterMerge(options.branch);
213
+ const shouldCleanup = await shouldCleanupAfterMerge(branch);
208
214
  if (shouldCleanup) {
209
- cleanupWorktreeAndBranch(targetWorktreePath, options.branch);
215
+ cleanupWorktreeAndBranch(targetWorktreePath, branch);
210
216
  }
211
217
 
212
218
  // 步骤 10:清理 validate 快照(merge 成功后快照已无意义)
213
- if (hasSnapshot(projectName, options.branch)) {
214
- removeSnapshot(projectName, options.branch);
219
+ if (hasSnapshot(projectName, branch)) {
220
+ removeSnapshot(projectName, branch);
215
221
  }
216
222
  }
@@ -1,5 +1,3 @@
1
- import { existsSync } from 'node:fs';
2
- import { join } from 'node:path';
3
1
  import type { Command } from 'commander';
4
2
  import { logger } from '../logger/index.js';
5
3
  import { ClawtError } from '../errors/index.js';
@@ -9,7 +7,7 @@ import {
9
7
  validateMainWorktree,
10
8
  getGitTopLevel,
11
9
  getProjectName,
12
- getProjectWorktreeDir,
10
+ getProjectWorktrees,
13
11
  isWorkingDirClean,
14
12
  gitAddAll,
15
13
  gitCommit,
@@ -21,7 +19,9 @@ import {
21
19
  printSuccess,
22
20
  printInfo,
23
21
  printWarning,
22
+ resolveTargetWorktree,
24
23
  } from '../utils/index.js';
24
+ import type { WorktreeResolveMessages } from '../utils/index.js';
25
25
 
26
26
  /**
27
27
  * 注册 sync 命令:将主分支最新代码同步到目标 worktree
@@ -31,12 +31,20 @@ export function registerSyncCommand(program: Command): void {
31
31
  program
32
32
  .command('sync')
33
33
  .description('将主分支最新代码同步到目标 worktree')
34
- .requiredOption('-b, --branch <branchName>', '要同步的分支名')
34
+ .option('-b, --branch <branchName>', '要同步的分支名(支持模糊匹配,不传则列出所有分支)')
35
35
  .action(async (options: SyncOptions) => {
36
36
  await handleSync(options);
37
37
  });
38
38
  }
39
39
 
40
+ /** sync 命令的分支解析消息配置 */
41
+ const SYNC_RESOLVE_MESSAGES: WorktreeResolveMessages = {
42
+ noWorktrees: MESSAGES.SYNC_NO_WORKTREES,
43
+ selectBranch: MESSAGES.SYNC_SELECT_BRANCH,
44
+ multipleMatches: MESSAGES.SYNC_MULTIPLE_MATCHES,
45
+ noMatch: MESSAGES.SYNC_NO_MATCH,
46
+ };
47
+
40
48
  /**
41
49
  * 自动保存目标 worktree 中的未提交变更
42
50
  * @param {string} worktreePath - 目标 worktree 路径
@@ -77,16 +85,12 @@ function mergeMainBranch(worktreePath: string, mainBranch: string): boolean {
77
85
  async function handleSync(options: SyncOptions): Promise<void> {
78
86
  validateMainWorktree();
79
87
 
80
- const { branch } = options;
81
- logger.info(`sync 命令执行,分支: ${branch}`);
82
-
83
- // 检查目标 worktree 是否存在
84
- const projectWorktreeDir = getProjectWorktreeDir();
85
- const targetWorktreePath = join(projectWorktreeDir, branch);
88
+ logger.info(`sync 命令执行,分支: ${options.branch ?? '(未指定)'}`);
86
89
 
87
- if (!existsSync(targetWorktreePath)) {
88
- throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(branch));
89
- }
90
+ // 解析目标 worktree(精确匹配 / 模糊匹配 / 交互选择)
91
+ const worktrees = getProjectWorktrees();
92
+ const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
93
+ const { path: targetWorktreePath, branch } = worktree;
90
94
 
91
95
  // 获取主分支名(不硬编码 main/master)
92
96
  const mainWorktreePath = getGitTopLevel();
@@ -60,6 +60,8 @@ export const MESSAGES = {
60
60
  INTERRUPT_KEPT: '已保留 worktree,可稍后使用 clawt remove 手动清理',
61
61
  /** 配置文件损坏,已重新生成默认配置 */
62
62
  CONFIG_CORRUPTED: '配置文件损坏或无法解析,已重新生成默认配置',
63
+ /** 配置已恢复为默认值 */
64
+ CONFIG_RESET_SUCCESS: '✓ 配置已恢复为默认值',
63
65
  /** 分隔线 */
64
66
  SEPARATOR: '────────────────────────────────────────',
65
67
  /** 粗分隔线 */
@@ -128,4 +130,22 @@ export const MESSAGES = {
128
130
  VALIDATE_SELECT_BRANCH: '请选择要验证的分支',
129
131
  /** validate 模糊匹配到多个结果提示 */
130
132
  VALIDATE_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
133
+ /** merge 无可用 worktree */
134
+ MERGE_NO_WORKTREES: '当前项目没有可用的 worktree,请先通过 clawt run 或 clawt create 创建',
135
+ /** merge 模糊匹配无结果,列出可用分支 */
136
+ MERGE_NO_MATCH: (name: string, branches: string[]) =>
137
+ `未找到与 "${name}" 匹配的分支\n 可用分支:\n${branches.map((b) => ` - ${b}`).join('\n')}`,
138
+ /** merge 交互选择提示 */
139
+ MERGE_SELECT_BRANCH: '请选择要合并的分支',
140
+ /** merge 模糊匹配到多个结果提示 */
141
+ MERGE_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
142
+ /** sync 无可用 worktree */
143
+ SYNC_NO_WORKTREES: '当前项目没有可用的 worktree,请先通过 clawt run 或 clawt create 创建',
144
+ /** sync 模糊匹配无结果,列出可用分支 */
145
+ SYNC_NO_MATCH: (name: string, branches: string[]) =>
146
+ `未找到与 "${name}" 匹配的分支\n 可用分支:\n${branches.map((b) => ` - ${b}`).join('\n')}`,
147
+ /** sync 交互选择提示 */
148
+ SYNC_SELECT_BRANCH: '请选择要同步的分支',
149
+ /** sync 模糊匹配到多个结果提示 */
150
+ SYNC_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
131
151
  } as const;
@@ -24,8 +24,8 @@ export interface ValidateOptions {
24
24
 
25
25
  /** merge 命令选项 */
26
26
  export interface MergeOptions {
27
- /** 要合并的分支名 */
28
- branch: string;
27
+ /** 要合并的分支名(可选,支持模糊匹配,不传则列出所有分支供选择) */
28
+ branch?: string;
29
29
  /** 提交信息(工作区有修改时必填) */
30
30
  message?: string;
31
31
  }
@@ -46,8 +46,8 @@ export interface ResumeOptions {
46
46
 
47
47
  /** sync 命令选项 */
48
48
  export interface SyncOptions {
49
- /** 要同步的分支名 */
50
- branch: string;
49
+ /** 要同步的分支名(可选,支持模糊匹配,不传则列出所有分支供选择) */
50
+ branch?: string;
51
51
  }
52
52
 
53
53
  /** list 命令选项 */
@@ -27,7 +27,7 @@ export function loadConfig(): ClawtConfig {
27
27
  /**
28
28
  * 将默认配置写入配置文件
29
29
  */
30
- function writeDefaultConfig(): void {
30
+ export function writeDefaultConfig(): void {
31
31
  writeFileSync(CONFIG_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2), 'utf-8');
32
32
  }
33
33
 
@@ -47,7 +47,7 @@ export {
47
47
  export { sanitizeBranchName, generateBranchNames, validateBranchesNotExist } from './branch.js';
48
48
  export { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled } from './validation.js';
49
49
  export { createWorktrees, getProjectWorktrees, getProjectWorktreeDir, cleanupWorktrees, getWorktreeStatus } from './worktree.js';
50
- export { loadConfig, getConfigValue, ensureClawtDirs } from './config.js';
50
+ export { loadConfig, getConfigValue, ensureClawtDirs, writeDefaultConfig } from './config.js';
51
51
  export { printSuccess, printError, printWarning, printInfo, printSeparator, printDoubleSeparator, confirmAction, confirmDestructiveAction, formatWorktreeStatus } from './formatter.js';
52
52
  export { ensureDir, removeEmptyDir } from './fs.js';
53
53
  export { multilineInput } from './prompt.js';