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.
- package/.claude/agent-memory/docs-sync-updater/MEMORY.md +8 -7
- package/README.md +40 -6
- package/dist/index.js +73 -32
- package/docs/spec.md +69 -24
- package/package.json +1 -1
- package/src/commands/config.ts +32 -5
- package/src/commands/merge.ts +28 -22
- package/src/commands/sync.ts +17 -13
- package/src/constants/messages.ts +20 -0
- package/src/types/command.ts +4 -4
- package/src/utils/config.ts +1 -1
- package/src/utils/index.ts +1 -1
|
@@ -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`
|
|
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`,被 reset
|
|
38
|
+
- `confirmDestructiveAction` 在 `src/utils/formatter.ts`,被 reset、validate --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 和
|
|
70
|
+
- `resolveTargetWorktree()` 是 resume、validate、merge 和 sync 共用的分支匹配函数(在 src/utils/worktree-matcher.ts)
|
|
70
71
|
- `WorktreeResolveMessages` 接口实现命令间消息解耦,每个命令传入各自的提示文案
|
|
71
|
-
- resume 的消息常量在 `MESSAGES.RESUME_*`,validate 的消息常量在 `MESSAGES.VALIDATE_*`
|
|
72
|
-
- resume 和
|
|
72
|
+
- resume 的消息常量在 `MESSAGES.RESUME_*`,validate 的消息常量在 `MESSAGES.VALIDATE_*`,merge 的消息常量在 `MESSAGES.MERGE_*`,sync 的消息常量在 `MESSAGES.SYNC_*`
|
|
73
|
+
- resume、validate、merge 和 sync 的 `-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
|
-
#
|
|
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
|
-
#
|
|
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").
|
|
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
|
-
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1365
|
-
|
|
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,
|
|
1371
|
-
printWarning(MESSAGES.MERGE_VALIDATE_STATE_HINT(
|
|
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,
|
|
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(
|
|
1411
|
+
if (!hasLocalCommits(branch, mainWorktreePath)) {
|
|
1388
1412
|
throw new ClawtError(MESSAGES.TARGET_WORKTREE_NO_CHANGES);
|
|
1389
1413
|
}
|
|
1390
1414
|
}
|
|
1391
1415
|
try {
|
|
1392
|
-
gitMerge(
|
|
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(
|
|
1434
|
+
printSuccess(MESSAGES.MERGE_SUCCESS(branch, options.message, autoPullPush));
|
|
1411
1435
|
} else {
|
|
1412
|
-
printSuccess(MESSAGES.MERGE_SUCCESS_NO_MESSAGE(
|
|
1436
|
+
printSuccess(MESSAGES.MERGE_SUCCESS_NO_MESSAGE(branch, autoPullPush));
|
|
1413
1437
|
}
|
|
1414
|
-
const shouldCleanup = await shouldCleanupAfterMerge(
|
|
1438
|
+
const shouldCleanup = await shouldCleanupAfterMerge(branch);
|
|
1415
1439
|
if (shouldCleanup) {
|
|
1416
|
-
cleanupWorktreeAndBranch(targetWorktreePath,
|
|
1440
|
+
cleanupWorktreeAndBranch(targetWorktreePath, branch);
|
|
1417
1441
|
}
|
|
1418
|
-
if (hasSnapshot(projectName,
|
|
1419
|
-
removeSnapshot(projectName,
|
|
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").
|
|
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
|
-
|
|
1485
|
-
|
|
1486
|
-
const
|
|
1487
|
-
const
|
|
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
|
|
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.
|
|
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
|
-
|
|
661
|
+
4. **Squash 检测与执行(auto-save 临时提交压缩)**
|
|
645
662
|
- 通过 `git log HEAD..<branchName> --format=%s` 检查目标分支是否存在以 `AUTO_SAVE_COMMIT_MESSAGE`(`chore: auto-save before sync`)为前缀的 commit
|
|
646
|
-
- **不存在** → 跳过,进入步骤
|
|
663
|
+
- **不存在** → 跳过,进入步骤 5
|
|
647
664
|
- **存在** → 提示用户是否将所有提交压缩为一个:
|
|
648
665
|
```
|
|
649
666
|
检测到 sync 产生的临时提交,是否将所有提交压缩为一个?
|
|
650
667
|
压缩后变更将保留在目标worktree的暂存区,需要重新提交
|
|
651
668
|
```
|
|
652
|
-
- **用户选择不压缩** → 跳过,进入步骤
|
|
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>'`,输出成功提示,继续步骤
|
|
674
|
+
4. 如果用户提供了 `-m` → 直接在目标 worktree 执行 `git commit -m '<commitMessage>'`,输出成功提示,继续步骤 5
|
|
658
675
|
5. 如果用户未提供 `-m` → 提示用户前往目标 worktree 自行提交后重新执行 `clawt merge`,**退出流程**
|
|
659
|
-
|
|
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
|
-
|
|
690
|
+
6. **回到主 worktree 进行合并**
|
|
674
691
|
```bash
|
|
675
692
|
cd <主 worktree 路径>
|
|
676
693
|
git merge <branchName>
|
|
677
694
|
```
|
|
678
|
-
|
|
695
|
+
7. **冲突检测**
|
|
679
696
|
- 检查 merge 退出码及 `git status` 是否存在冲突
|
|
680
697
|
- **有冲突** → 提示 `合并存在冲突,请手动处理`,退出
|
|
681
698
|
- **无冲突** → 继续
|
|
682
|
-
|
|
699
|
+
8. **推送(受 `autoPullPush` 配置控制)**
|
|
683
700
|
```bash
|
|
684
701
|
# 仅当 autoPullPush 为 true 时执行
|
|
685
702
|
git pull
|
|
686
703
|
git push
|
|
687
704
|
```
|
|
688
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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` |
|
|
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.
|
|
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
package/src/commands/config.ts
CHANGED
|
@@ -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 - 配置值
|
package/src/commands/merge.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
.
|
|
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
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
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,
|
|
145
|
-
printWarning(MESSAGES.MERGE_VALIDATE_STATE_HINT(
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
207
|
+
printSuccess(MESSAGES.MERGE_SUCCESS(branch, options.message, autoPullPush));
|
|
202
208
|
} else {
|
|
203
|
-
printSuccess(MESSAGES.MERGE_SUCCESS_NO_MESSAGE(
|
|
209
|
+
printSuccess(MESSAGES.MERGE_SUCCESS_NO_MESSAGE(branch, autoPullPush));
|
|
204
210
|
}
|
|
205
211
|
|
|
206
212
|
// 步骤 9:merge 成功后确认并清理 worktree 和分支
|
|
207
|
-
const shouldCleanup = await shouldCleanupAfterMerge(
|
|
213
|
+
const shouldCleanup = await shouldCleanupAfterMerge(branch);
|
|
208
214
|
if (shouldCleanup) {
|
|
209
|
-
cleanupWorktreeAndBranch(targetWorktreePath,
|
|
215
|
+
cleanupWorktreeAndBranch(targetWorktreePath, branch);
|
|
210
216
|
}
|
|
211
217
|
|
|
212
218
|
// 步骤 10:清理 validate 快照(merge 成功后快照已无意义)
|
|
213
|
-
if (hasSnapshot(projectName,
|
|
214
|
-
removeSnapshot(projectName,
|
|
219
|
+
if (hasSnapshot(projectName, branch)) {
|
|
220
|
+
removeSnapshot(projectName, branch);
|
|
215
221
|
}
|
|
216
222
|
}
|
package/src/commands/sync.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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;
|
package/src/types/command.ts
CHANGED
|
@@ -24,8 +24,8 @@ export interface ValidateOptions {
|
|
|
24
24
|
|
|
25
25
|
/** merge 命令选项 */
|
|
26
26
|
export interface MergeOptions {
|
|
27
|
-
/**
|
|
28
|
-
branch
|
|
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
|
|
49
|
+
/** 要同步的分支名(可选,支持模糊匹配,不传则列出所有分支供选择) */
|
|
50
|
+
branch?: string;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/** list 命令选项 */
|
package/src/utils/config.ts
CHANGED
package/src/utils/index.ts
CHANGED
|
@@ -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';
|