clawt 3.7.0 → 3.7.1

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.
@@ -0,0 +1,416 @@
1
+ ### 5.4 在主 Worktree 验证其他分支
2
+
3
+ **命令:**
4
+
5
+ ```bash
6
+ # 指定分支名(支持模糊匹配)
7
+ clawt validate -b <branchName> [--clean] [-r <command>]
8
+
9
+ # 不指定分支名(列出所有分支供选择)
10
+ clawt validate [--clean] [-r <command>]
11
+ ```
12
+
13
+ **参数:**
14
+
15
+ | 参数 | 必填 | 说明 |
16
+ | ------------- | ---- | ------------------------------------------------------------------------ |
17
+ | `-b` | 否 | 要验证的 worktree 分支名(支持模糊匹配,不传则列出所有分支供选择) |
18
+ | `--clean` | 否 | 清理 validate 状态(重置主 worktree 并删除快照) |
19
+ | `-r, --run` | 否 | validate 成功后在主 worktree 中执行的命令(如测试、构建等)。不传时自动从项目配置的 `validateRunCommand` 读取 |
20
+
21
+ > **限制:** 单次只能验证一个分支,不支持批量验证。
22
+
23
+ **背景说明:**
24
+
25
+ Git worktree 不会包含 `node_modules`、`.venv` 等依赖文件,每次安装依赖耗时较长。利用 `git diff HEAD...branch --binary`(三点 diff)可以获取目标分支自分叉点以来的全量变更(包含已提交和未提交的修改),将其作为 patch 应用到主 worktree 中进行测试,无需重新安装依赖。
26
+
27
+ **验证分支机制:**
28
+
29
+ validate 不再在主工作分支上直接 apply patch,而是先切换到目标分支对应的**验证分支**(`clawt-validate-<branchName>`),再 apply patch。验证分支的 HEAD 不会随主工作分支推进,因此 patch apply 永远不会冲突。详见 [2.5 验证分支](#25-验证分支)。
30
+
31
+ **快照机制:**
32
+
33
+ validate 命令引入了**快照(snapshot)机制**来支持增量对比。每次 validate 执行成功后,会将当前全量变更通过 `git write-tree` 保存为 git tree 对象,并将 tree hash 记录到文件(`~/.clawt/validate-snapshots/<project>/<branchName>.tree`),同时将验证分支的 HEAD commit hash 记录到文件(`~/.clawt/validate-snapshots/<project>/<branchName>.head`),以及 validate 结束时暂存区对应的 tree hash 记录到文件(`~/.clawt/validate-snapshots/<project>/<branchName>.staged`),用于增量 validate 时对齐基准和无变更恢复。当再次执行 validate 时,先计算当前变更的 tree hash 与旧快照对比:如果没有新变更(tree hash 和 HEAD 均未变化),直接通过 `git read-tree` 恢复上次 validate 结束时的暂存区状态,跳过后续步骤;如果有新变更,则继续执行暂存区载入流程——如果验证分支 HEAD 未变化(正常情况),通过 `git read-tree` 将上次快照的 tree 对象载入暂存区;如果验证分支 HEAD 已变化(sync 后重建了验证分支),则将旧变更 patch(旧 tree 相对于旧 HEAD 的差异)重放到当前 HEAD 暂存区上,避免新旧 tree 基准不同导致 diff 混入 HEAD 变化的内容。最终用户可通过 `git diff` 查看两次 validate 之间的增量差异。
34
+
35
+ **运行流程:**
36
+
37
+ #### `--clean` 模式
38
+
39
+ 当指定 `--clean` 选项时,执行清理逻辑后直接返回,不进入常规 validate 流程:
40
+
41
+ 1. **前置校验**:主 worktree 校验 + HEAD 存在性校验 + 项目配置校验(`runPreChecks`)
42
+ 2. **解析目标 worktree**:通过模糊匹配解析目标分支(匹配策略同下文常规 validate 流程中的描述)
43
+ 3. 如果配置项 `confirmDestructiveOps` 为 `true`,提示确认(显示即将执行的危险指令和操作后果),用户取消则退出
44
+ 4. 如果主 worktree 有未提交更改,执行 `git reset --hard` + `git clean -fd` 清空
45
+ 5. 确保当前处于主工作分支上(`ensureOnMainWorkBranch`):如果在验证分支上,清理后切回;如果在其他分支上,交互处理脏工作区后切回
46
+ 6. 删除对应分支的快照文件
47
+ 7. 输出清理成功提示
48
+
49
+ #### 首次 validate(无历史快照)
50
+
51
+ > 常规 validate(首次和增量)执行前均会先进行前置校验:主 worktree 校验 + HEAD 存在性校验 + 项目配置校验(`runPreChecks`)。
52
+
53
+ ##### 步骤 0:解析目标 worktree
54
+
55
+ 根据 `-b` 参数解析目标 worktree,匹配策略如下:
56
+
57
+ - **未传 `-b` 参数**:
58
+ - 获取当前项目所有 worktree
59
+ - 无可用 worktree → 报错退出
60
+ - 仅 1 个 worktree → 直接使用,无需选择
61
+ - 多个 worktree → 通过交互式列表(Enquirer.Select)让用户选择
62
+ - **传了 `-b` 参数**:
63
+ 1. **精确匹配优先**:在 worktree 列表中查找分支名完全相同的 worktree,找到则直接使用
64
+ 2. **模糊匹配**(子串匹配,大小写不敏感):
65
+ - 唯一匹配 → 直接使用
66
+ - 多个匹配 → 通过交互式列表让用户从匹配结果中选择
67
+ 3. **无匹配** → 报错退出,并列出所有可用分支名
68
+
69
+ ##### 步骤 1:检测目标分支变更
70
+
71
+ 统一检测目标 worktree 的未提交修改和已提交 commit:
72
+
73
+ ```bash
74
+ # 检测未提交修改
75
+ cd ~/.clawt/worktrees/<project>/<branchName>
76
+ git status --porcelain
77
+
78
+ # 检测已提交 commit(在主 worktree 中执行)
79
+ cd <主 worktree 路径>
80
+ git log HEAD..<branchName> --oneline
81
+ ```
82
+
83
+ - **两者均无** → 输出提示 `该 worktree 的分支上没有任何更改,无需验证`,退出
84
+ - **至少有一项** → 继续
85
+
86
+ ##### 步骤 2:检测主 worktree 工作区状态
87
+
88
+ 执行 `git status --porcelain`,判断主 worktree 是否有未提交的更改。
89
+
90
+ - **无更改** → 进入步骤 3
91
+ - **有更改** → 提示用户选择处理方式,使用交互式选择(方向键切换,回车确认):
92
+
93
+ ```
94
+ ⚠ 当前分支有未提交的更改,请选择处理方式:
95
+
96
+ ❯ reset - 丢弃所有更改 (git reset --hard HEAD && git clean -fd)
97
+ stash - 暂存更改 (git add . && git stash)
98
+ exit - 退出,手动处理
99
+ ```
100
+
101
+ | 选项 | 执行命令 | 默认 |
102
+ | ------- | ----------------------------------------- | ---- |
103
+ | `reset` | `git reset --hard HEAD && git clean -fd` | 是 |
104
+ | `stash` | `git add . && git stash push -m "clawt:auto-stash"` | 否 |
105
+ | `exit` | 退出程序 | 否 |
106
+
107
+ 执行完毕后,通过 `git status --porcelain` 再次检测状态,确保工作区干净。如果仍然不干净,报错退出。
108
+
109
+ ##### 步骤 3:切换到验证分支
110
+
111
+ ```bash
112
+ cd <主 worktree 路径>
113
+ git checkout clawt-validate-<branchName>
114
+ ```
115
+
116
+ 如果验证分支不存在,直接报错退出:
117
+
118
+ ```
119
+ 验证分支 clawt-validate-<branchName> 不存在,请先执行 clawt create 或 clawt run 创建分支 <branchName>
120
+ ```
121
+
122
+ ##### 步骤 4:通过 patch 迁移目标分支全量变更
123
+
124
+ 使用三点 diff(`git diff HEAD...branchName --binary`)获取目标分支自分叉点以来的全量变更。如果目标 worktree 有未提交修改,先做临时 commit 以便 diff 能捕获全部变更,diff 完成后撤销临时 commit 恢复原状。
125
+
126
+ ```bash
127
+ # 如果有未提交修改,先临时提交
128
+ cd ~/.clawt/worktrees/<project>/<branchName>
129
+ git add .
130
+ git commit -m "clawt:temp-commit-for-validate"
131
+
132
+ # 在主 worktree(已切换到验证分支)中执行三点 diff
133
+ cd <主 worktree 路径>
134
+ git diff HEAD...<branchName> --binary | git apply
135
+
136
+ # 撤销临时 commit,恢复目标 worktree 原状
137
+ cd ~/.clawt/worktrees/<project>/<branchName>
138
+ git reset --soft HEAD~1
139
+ git restore --staged .
140
+ ```
141
+
142
+ > 由于验证分支的 HEAD 与目标分支的创建基点一致,patch apply **永远不会冲突**。
143
+ > 此步骤结束后,目标 worktree 的代码保持原样,主 worktree 工作目录包含目标分支的全量变更。
144
+ > 如果 patch apply 失败(兜底场景),`migrateChangesViaPatch` 返回 `{ success: false }`,进入自动 sync 交互流程(见下文 [patch apply 失败后的自动 sync 流程](#patch-apply-失败后的自动-sync-流程))。
145
+
146
+ ##### patch apply 失败后的自动 sync 流程
147
+
148
+ 当 patch apply 失败时,validate 不再直接退出,而是先通过 `ensureOnMainWorkBranch()` 确保主 worktree 切回主工作分支,然后通过 `handlePatchApplyFailure()` 函数进入交互流程:
149
+
150
+ 1. **询问用户**:提示 `是否立即执行 sync 同步主分支到 <branchName>?`
151
+ 2. **用户拒绝** → 输出提示 `请手动执行 clawt sync -b <branchName> 同步主分支后重试`,退出
152
+ 3. **用户确认** → 调用 `executeSyncForBranch(targetWorktreePath, branchName)` 自动执行 sync,sync 的结果(成功/冲突)由 `executeSyncForBranch` 内部输出,`handlePatchApplyFailure` 不做额外判断,validate 流程结束(用户需重新执行 validate)
153
+
154
+ > `executeSyncForBranch` 为 sync 命令抽取的核心操作函数(见 [5.12](#512-将主分支代码同步到目标-worktree)),供 validate 等命令复用。
155
+
156
+ **实现要点:**
157
+
158
+ - `migrateChangesViaPatch()`(`src/utils/validate-core.ts`)返回 `{ success: boolean }`,patch apply 失败时返回 `{ success: false }` 而非抛出异常
159
+ - `handleFirstValidate()` 和 `handleIncrementalValidate()` 为 `async` 函数,支持交互式确认
160
+ - `handlePatchApplyFailure()`(`src/commands/validate.ts`)为异步函数,负责 patch 失败后的交互逻辑
161
+ - 消息常量:`MESSAGES.VALIDATE_CONFIRM_AUTO_SYNC`、`MESSAGES.VALIDATE_AUTO_SYNC_START`、`MESSAGES.VALIDATE_AUTO_SYNC_DECLINED`(`src/constants/messages/validate.ts`)
162
+
163
+ ##### 步骤 5:保存快照为 git tree 对象
164
+
165
+ 将主 worktree 工作目录的全量变更保存为 git tree 对象,同时记录验证分支的 HEAD commit hash:
166
+
167
+ ```bash
168
+ git add .
169
+ git write-tree # → 返回 tree hash,写入 ~/.clawt/validate-snapshots/<project>/<branchName>.tree
170
+ git rev-parse HEAD # → 返回验证分支的 HEAD commit hash,写入 ~/.clawt/validate-snapshots/<project>/<branchName>.head
171
+ # 同时写入 ~/.clawt/validate-snapshots/<project>/<branchName>.staged(首次 validate 为空字符串)
172
+ git restore --staged .
173
+ ```
174
+
175
+ > 此处保存的 HEAD commit hash 是验证分支的 HEAD(即创建时的基点),而非主工作分支的 HEAD。
176
+ > 结果:暂存区=空,工作目录=全量变更。
177
+
178
+ ##### 步骤 6:输出成功提示
179
+
180
+ ```
181
+ ✓ 已切换到验证分支 clawt-validate-feature-scheme-1 并应用分支 feature-scheme-1 的变更
182
+ 可以开始验证了
183
+ ```
184
+
185
+ ##### 步骤 7:执行 `--run` 命令(可选)
186
+
187
+ 如果用户传入了 `-r, --run` 选项,在 validate 成功后自动在主 worktree 中执行指定命令。**如果未传 `-r`,则自动从项目配置的 `validateRunCommand` 字段读取默认命令**(通过 `resolveRunCommand` 解析优先级:`-r` 参数 > 项目配置 > 不执行)。
188
+
189
+ ```bash
190
+ # 示例:单命令
191
+ clawt validate -b feature-scheme-1 -r "npm test"
192
+
193
+ # 示例:并行执行多个命令(& 为并行分隔符)
194
+ clawt validate -b feature-scheme-1 -r "pnpm test & pnpm build"
195
+ ```
196
+
197
+ **执行说明:**
198
+
199
+ - 命令执行失败(退出码非 0 或进程启动失败)**不影响** validate 本身的结果,仅输出提示信息
200
+ - `--clean` 模式下传入 `--run` 会被忽略(只执行 clean 逻辑)
201
+
202
+ **命令解析规则:**
203
+
204
+ `-r` 选项支持通过 `&` 将多个命令并行执行。解析由 `parseParallelCommands()`(`src/utils/shell.ts`)负责:
205
+
206
+ 1. 先将命令字符串中的 `&&` 临时替换为占位符,避免被误拆
207
+ 2. 按单个 `&` 分割为多个独立命令
208
+ 3. 还原占位符为 `&&`,去除首尾空白,过滤空串
209
+
210
+ | 输入示例 | 解析结果 | 执行方式 |
211
+ | -------- | -------- | -------- |
212
+ | `"npm test"` | `["npm test"]` | 单命令,同步执行(`spawnSync` + `inherit`) |
213
+ | `"npm lint && npm test"` | `["npm lint && npm test"]` | 单命令(`&&` 不拆分),同步执行 |
214
+ | `"npm test & npm build"` | `["npm test", "npm build"]` | 并行执行(`spawn` + `Promise.all`) |
215
+ | `"npm lint && npm test & npm build"` | `["npm lint && npm test", "npm build"]` | 并行执行 2 个命令 |
216
+
217
+ **单命令执行:**
218
+
219
+ 当解析后只有 1 个命令时,通过 `spawnSync` + `inherit` stdio 模式同步执行,输出实时显示在终端。
220
+
221
+ **并行命令执行:**
222
+
223
+ 当解析后有多个命令时,通过 `runParallelCommands()`(`src/utils/shell.ts`)执行:
224
+
225
+ - 每个命令通过 Node.js `spawn` 以 shell 模式启动,`stdio: 'inherit'`
226
+ - 使用 `Promise.all` 等待全部命令完成
227
+ - 完成后汇总输出各命令的执行结果
228
+
229
+ **向后兼容性:**
230
+
231
+ - `-r "npm test"` — 单命令,走原有同步路径,行为无变化
232
+ - `-r "npm lint && npm test"` — `&&` 不拆分,走原有同步路径,行为无变化
233
+ - `-r "npm test & npm build"` — **新行为**:并行执行,等全部完成后汇总
234
+
235
+ **输出格式:**
236
+
237
+ ```
238
+ # 单命令执行成功
239
+ 正在主 worktree 中执行命令: npm test
240
+ ────────────────────────────────────────
241
+ ... 命令的实时输出 ...
242
+ ────────────────────────────────────────
243
+ ✓ 命令执行完成: npm test,退出码: 0
244
+
245
+ # 单命令执行失败(退出码非 0)
246
+ 正在主 worktree 中执行命令: npm test
247
+ ────────────────────────────────────────
248
+ ... 命令的实时输出 ...
249
+ ────────────────────────────────────────
250
+ ✗ 命令执行完成: npm test,退出码: 1
251
+
252
+ # 单命令执行出错(进程启动失败)
253
+ 正在主 worktree 中执行命令: nonexistent
254
+ ────────────────────────────────────────
255
+ ────────────────────────────────────────
256
+ ✗ 命令执行出错: spawn ENOENT
257
+
258
+ # 并行命令执行(全部成功)
259
+ 正在并行执行 2 个命令...
260
+ [1/2] pnpm test
261
+ [2/2] pnpm build
262
+ ────────────────────────────────────────
263
+ ... 各命令的实时输出(交错显示) ...
264
+ ────────────────────────────────────────
265
+ ✓ pnpm test
266
+ ✓ pnpm build
267
+ ✓ 全部 2 个命令执行成功
268
+
269
+ # 并行命令执行(部分失败)
270
+ 正在并行执行 2 个命令...
271
+ [1/2] pnpm test
272
+ [2/2] pnpm build
273
+ ────────────────────────────────────────
274
+ ... 各命令的实时输出(交错显示) ...
275
+ ────────────────────────────────────────
276
+ ✗ pnpm test(退出码: 1)
277
+ ✓ pnpm build
278
+ 共 2 个命令,1 个成功,1 个失败
279
+ ```
280
+
281
+ **实现要点:**
282
+
283
+ - 命令解析:`parseParallelCommands()`(`src/utils/shell.ts`)
284
+ - 并行执行:`runParallelCommands()`(`src/utils/shell.ts`),返回 `ParallelCommandResult[]`
285
+ - 结果汇总:`reportParallelResults()`(`src/utils/validate-runner.ts`)
286
+ - 消息常量:`MESSAGES.VALIDATE_PARALLEL_*` 系列(`src/constants/messages/validate.ts`)
287
+ - 命令解析优先级:`resolveRunCommand()`(`src/commands/validate.ts`)负责解析最终要执行的命令,优先使用 `-r` 参数,否则从项目配置读取 `validateRunCommand`(通过 `getValidateRunCommand()`,`src/utils/project-config.ts`)
288
+
289
+ #### 增量 validate(存在历史快照)
290
+
291
+ 当 `~/.clawt/validate-snapshots/<project>/<branchName>.tree` 存在时,自动进入增量模式:
292
+
293
+ ##### 步骤 1:读取旧快照
294
+
295
+ 在清空主 worktree 之前,读取上次保存的快照 tree hash、当时的 HEAD commit hash 和暂存区 tree hash(`stagedTreeHash`)。
296
+
297
+ ##### 步骤 2:确保主 worktree 干净
298
+
299
+ 如果主 worktree 有残留状态,直接执行 `git reset --hard` + `git clean -fd` 兜底清理(无交互,用户交互已在 `handleValidate` 主函数中通过 `handleDirtyMainWorktree` 完成)。
300
+
301
+ ##### 步骤 3:切换到验证分支
302
+
303
+ 如果当前已在该验证分支上(上次 validate 后未切回),跳过。如果当前在另一个验证分支上(验证了分支 A,现在要验证分支 B),直接切换:
304
+
305
+ ```bash
306
+ git checkout clawt-validate-<branchName>
307
+ ```
308
+
309
+ ##### 步骤 4:从目标分支获取最新全量变更
310
+
311
+ 通过 patch 方式从目标分支获取最新全量变更(流程同首次 validate 的步骤 4)。如果 patch apply 失败,同样进入自动 sync 交互流程(见首次 validate 的 [patch apply 失败后的自动 sync 流程](#patch-apply-失败后的自动-sync-流程)),validate 流程提前结束。
312
+
313
+ ##### 步骤 5:检测是否有新变更
314
+
315
+ 计算当前工作目录变更的 tree hash(`git add . → git write-tree → git restore --staged .`),并与旧快照的 tree hash 及 HEAD commit hash 对比:
316
+
317
+ ```bash
318
+ # 计算当前变更的 tree hash
319
+ git add .
320
+ git write-tree # → newTreeHash
321
+ git restore --staged .
322
+
323
+ # 获取当前 HEAD
324
+ git rev-parse HEAD # → currentHeadCommitHash
325
+
326
+ # 判断是否有新变更
327
+ hasNewChanges = (newTreeHash !== oldTreeHash) || (oldHeadCommitHash !== currentHeadCommitHash)
328
+ ```
329
+
330
+ - **无新变更**(tree hash 和 HEAD 均未变化)→ 不更新快照;如果旧快照记录了 `oldStagedTreeHash`(非空),通过 `git read-tree <oldStagedTreeHash>` 恢复上次 validate 结束时的暂存区状态(恢复失败仅输出 warn 日志,不影响流程);输出提示后返回
331
+ - **有新变更** → 继续步骤 6
332
+
333
+ > 无变更检测避免了重复 validate 时不必要的快照更新和暂存区重载操作。恢复上次暂存区状态后,用户看到的 `git diff` 结果与上次 validate 结束时完全一致。
334
+
335
+ ##### 步骤 6:将旧变更状态载入暂存区
336
+
337
+ 由于验证分支的 HEAD 不会变化,`oldHeadCommitHash` 与 `currentHeadCommitHash` 始终一致(除非执行了 sync 重建验证分支),因此:
338
+
339
+ **正常情况(HEAD 未变化):**
340
+
341
+ 直接通过 `git read-tree` 将旧 tree 对象载入暂存区:
342
+
343
+ ```bash
344
+ git read-tree <旧 tree hash>
345
+ ```
346
+
347
+ - **读取成功** → 记录 `newStagedTreeHash = oldTreeHash`,结果:暂存区=上次快照,工作目录=最新全量变更(用户可通过 `git diff` 查看增量差异)
348
+ - **读取失败**(tree 对象可能被 git gc 回收)→ 降级为全量模式,写入快照(`stagedTreeHash` 为空)后返回,暂存区保持为空,等同于首次 validate 的结果
349
+
350
+ > 这是最常见的路径。相比重构前,正常情况不再需要处理 HEAD 变化的复杂逻辑,代码路径更简单、更可靠。
351
+
352
+ **sync 后(HEAD 变化,验证分支已重建):**
353
+
354
+ 此时旧 tree 对象基于旧 HEAD,直接 read-tree 会导致 diff 混入 HEAD 变化的内容。需要将旧变更 patch(旧 tree 相对于旧 HEAD 的差异)重放到当前 HEAD 暂存区上:
355
+
356
+ ```bash
357
+ # 获取旧 HEAD 对应的 tree hash
358
+ git rev-parse <旧 HEAD commit hash>^{tree} # → 旧 HEAD tree hash
359
+
360
+ # 提取旧变更 patch(旧 HEAD tree → 旧快照 tree 的差异)
361
+ git diff-tree -p --binary <旧 HEAD tree hash> <旧快照 tree hash>
362
+
363
+ # 检测 patch 能否无冲突地应用到暂存区
364
+ git apply --cached --check < patch
365
+
366
+ # 无冲突:apply --cached 到当前 HEAD 暂存区
367
+ git apply --cached < patch
368
+
369
+ # 记录暂存区的 tree hash
370
+ git write-tree # → newStagedTreeHash
371
+ ```
372
+
373
+ - **patch 为空**(旧变更为空)→ 暂存区保持干净
374
+ - **无冲突** → apply --cached 到当前 HEAD 暂存区,通过 `git write-tree` 记录 `newStagedTreeHash`,结果与正常情况一致
375
+ - **有冲突** → 降级为全量模式(暂存区保持为空),写入快照(`stagedTreeHash` 为空)后返回
376
+
377
+ ##### 步骤 7:写入新快照
378
+
379
+ 将步骤 5 计算的 `newTreeHash`、当前 HEAD commit hash 和步骤 6 记录的 `newStagedTreeHash` 写入快照文件,供下次 validate 使用:
380
+
381
+ ```bash
382
+ # 写入 ~/.clawt/validate-snapshots/<project>/<branchName>.tree
383
+ echo <newTreeHash>
384
+
385
+ # 写入 ~/.clawt/validate-snapshots/<project>/<branchName>.head
386
+ echo <currentHeadCommitHash>
387
+
388
+ # 写入 ~/.clawt/validate-snapshots/<project>/<branchName>.staged
389
+ echo <newStagedTreeHash>
390
+ ```
391
+
392
+ > `stagedTreeHash` 记录了 validate 结束时暂存区的完整状态。下次 validate 如果检测到无新变更,可直接通过此值恢复暂存区,避免重复执行 read-tree 或 apply --cached 流程。
393
+
394
+ ##### 步骤 8:输出成功提示
395
+
396
+ ```
397
+ # 增量模式成功
398
+ ✓ 已将分支 feature-scheme-1 的最新变更应用到主 worktree(增量模式)
399
+ 暂存区 = 上次快照,工作目录 = 最新变更
400
+
401
+ # 增量无变更
402
+ 分支 feature-scheme-1 自上次 validate 以来没有新的变更,已恢复到上次验证状态
403
+ ✓ 已切换到验证分支 clawt-validate-feature-scheme-1 并应用分支 feature-scheme-1 的变更
404
+ 可以开始验证了
405
+
406
+ # 增量降级为全量
407
+ 增量对比失败,已降级为全量模式
408
+ ✓ 已切换到验证分支 clawt-validate-feature-scheme-1 并应用分支 feature-scheme-1 的变更
409
+ 可以开始验证了
410
+ ```
411
+
412
+ ##### 步骤 9:执行 `--run` 命令(可选)
413
+
414
+ 与首次 validate 的步骤 7 相同,增量 validate 成功后也会执行 `-r, --run` 指定的命令(或从项目配置 `validateRunCommand` 读取的默认命令)。
415
+
416
+ ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "3.7.0",
3
+ "version": "3.7.1",
4
4
  "description": "本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,2 +1,5 @@
1
1
  /** sync 自动保存的 commit message 前缀,用于检测 auto-save 提交 */
2
2
  export const AUTO_SAVE_COMMIT_MESSAGE = 'chore: auto-save before sync';
3
+
4
+ /** execSync 最大缓冲区大小(200MB),防止大分支 diff 时触发 ENOBUFS 错误 */
5
+ export const EXEC_MAX_BUFFER = 200 * 1024 * 1024;
@@ -1,4 +1,4 @@
1
- /** 项目级配置(存储在 ~/.clawt/projects/<projectName>.json) */
1
+ /** 项目级配置(存储在 ~/.clawt/projects/<projectName>/config.json) */
2
2
  export interface ProjectConfig {
3
3
  /** 主 worktree 的工作分支名 */
4
4
  clawtMainWorkBranch: string;
@@ -2,6 +2,7 @@ import { basename } from 'node:path';
2
2
  import { execSync, execFileSync } from 'node:child_process';
3
3
  import { execCommand, execCommandWithInput } from './shell.js';
4
4
  import { logger } from '../logger/index.js';
5
+ import { EXEC_MAX_BUFFER } from '../constants/git.js';
5
6
 
6
7
  /**
7
8
  * 获取 git common dir(用于判断是否为主 worktree)
@@ -258,6 +259,7 @@ export function gitDiffCachedBinary(cwd?: string): Buffer {
258
259
  return execSync('git diff --cached --binary', {
259
260
  cwd,
260
261
  stdio: ['pipe', 'pipe', 'pipe'],
262
+ maxBuffer: EXEC_MAX_BUFFER,
261
263
  });
262
264
  }
263
265
 
@@ -291,6 +293,7 @@ export function gitDiffBinaryAgainstBranch(branchName: string, cwd?: string): Bu
291
293
  return execSync(`git diff HEAD...${branchName} --binary`, {
292
294
  cwd,
293
295
  stdio: ['pipe', 'pipe', 'pipe'],
296
+ maxBuffer: EXEC_MAX_BUFFER,
294
297
  });
295
298
  }
296
299
 
@@ -390,6 +393,7 @@ export function gitDiffTree(baseTreeHash: string, targetTreeHash: string, cwd?:
390
393
  return execSync(`git diff-tree -p --binary ${baseTreeHash} ${targetTreeHash}`, {
391
394
  cwd,
392
395
  stdio: ['pipe', 'pipe', 'pipe'],
396
+ maxBuffer: EXEC_MAX_BUFFER,
393
397
  });
394
398
  }
395
399
 
@@ -1,5 +1,6 @@
1
1
  import { execSync, execFileSync, spawn, spawnSync, type ChildProcess, type SpawnSyncReturns, type StdioOptions } from 'node:child_process';
2
2
  import { logger } from '../logger/index.js';
3
+ import { EXEC_MAX_BUFFER } from '../constants/git.js';
3
4
 
4
5
  /**
5
6
  * 获取移除了 CLAUDECODE 嵌套会话标记的环境变量副本
@@ -52,6 +53,7 @@ export function execCommand(command: string, options?: { cwd?: string }): string
52
53
  cwd: options?.cwd,
53
54
  encoding: 'utf-8',
54
55
  stdio: ['pipe', 'pipe', 'pipe'],
56
+ maxBuffer: EXEC_MAX_BUFFER,
55
57
  });
56
58
  return result.trim();
57
59
  }
@@ -107,6 +109,7 @@ export function execCommandWithInput(command: string, args: string[], options: {
107
109
  input: options.input,
108
110
  encoding: 'utf-8',
109
111
  stdio: ['pipe', 'pipe', 'pipe'],
112
+ maxBuffer: EXEC_MAX_BUFFER,
110
113
  });
111
114
  return result.trim();
112
115
  }