clawt 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agent-memory/docs-sync-updater/MEMORY.md +16 -0
- package/CLAUDE.md +22 -6
- package/README.md +31 -2
- package/dist/index.js +279 -43
- package/dist/postinstall.js +1 -0
- package/docs/spec.md +162 -26
- package/package.json +1 -1
- package/src/commands/merge.ts +15 -0
- package/src/commands/sync.ts +116 -0
- package/src/commands/validate.ts +194 -26
- package/src/constants/index.ts +1 -1
- package/src/constants/messages.ts +25 -2
- package/src/constants/paths.ts +3 -0
- package/src/index.ts +2 -0
- package/src/types/command.ts +8 -0
- package/src/types/index.ts +1 -1
- package/src/utils/git.ts +85 -1
- package/src/utils/index.ts +10 -1
- package/src/utils/shell.ts +22 -1
- package/src/utils/validate-snapshot.ts +123 -0
|
@@ -29,11 +29,13 @@
|
|
|
29
29
|
## 关键约定
|
|
30
30
|
- `autoDeleteBranch` 配置项影响三处:remove 命令、merge 命令、run 中断清理
|
|
31
31
|
- merge 的清理确认在 merge 操作之前询问(避免交互中断),但清理在 merge 成功后执行
|
|
32
|
+
- merge 成功后自动清理对应的 validate 快照(hasSnapshot + removeSnapshot)
|
|
32
33
|
- run 的中断清理在所有子进程退出后执行
|
|
33
34
|
- 文档中文风格,技术术语保留英文(worktree, merge, branch, SIGINT 等)
|
|
34
35
|
- cleanupWorktrees 是 merge 和 run 共用的公共清理函数(在 src/utils/worktree.ts)
|
|
35
36
|
- `launchInteractiveClaude` 是 run(交互式模式)和 resume 共用的公共函数(在 src/utils/claude.ts)
|
|
36
37
|
- killAllChildProcesses 是 run 专用的子进程终止函数(在 src/utils/shell.ts)
|
|
38
|
+
- validate 快照管理函数在 `src/utils/validate-snapshot.ts`,被 validate 和 merge 两个命令使用
|
|
37
39
|
|
|
38
40
|
## 配置项同步检查点
|
|
39
41
|
|
|
@@ -65,3 +67,17 @@ run 命令有两种模式(自 claudeCodeCommand 特性后):
|
|
|
65
67
|
Notes:
|
|
66
68
|
- resume 和 run(交互式模式)共用 `launchInteractiveClaude()`,该函数从 run.ts 提取到 src/utils/claude.ts
|
|
67
69
|
- `claudeCodeCommand` 配置项同时影响 run 交互式模式和 resume 命令
|
|
70
|
+
|
|
71
|
+
## validate 快照机制
|
|
72
|
+
|
|
73
|
+
- validate 命令支持首次/增量两种模式,通过 `hasSnapshot()` 判断
|
|
74
|
+
- 快照路径:`~/.clawt/validate-snapshots/<projectName>/<branchName>.patch`
|
|
75
|
+
- 常量 `VALIDATE_SNAPSHOTS_DIR` 定义在 `src/constants/paths.ts`
|
|
76
|
+
- validate 新增 `--clean` 选项(`ValidateOptions.clean?: boolean`)
|
|
77
|
+
- 增量模式核心:旧 patch 应用到暂存区 + 新全量变更在工作目录 → `git diff` 可查看增量差异
|
|
78
|
+
- 增量 apply 失败时自动降级为全量模式
|
|
79
|
+
- shell 层新增 `execCommandWithInput()`(`execFileSync` + stdin),用于 `gitApplyCachedFromStdin()`
|
|
80
|
+
- git 层新增 `gitDiffCachedBinary()`(返回 Buffer)和 `gitApplyCachedFromStdin()`
|
|
81
|
+
- merge 成功后自动清理对应快照;merge 时主 worktree 脏 + 存在快照会输出警告提示
|
|
82
|
+
- docs/spec.md 中 validate 章节(5.4)按 `--clean 模式`、`首次 validate`、`增量 validate` 三段描述
|
|
83
|
+
- CLAUDE.md 中在 validate + merge 工作流章节用缩进列表描述两种模式
|
package/CLAUDE.md
CHANGED
|
@@ -24,7 +24,7 @@ npm i -g . # 本地全局安装进行测试
|
|
|
24
24
|
|
|
25
25
|
每个命令为独立文件 `src/commands/<name>.ts`,导出 `registerXxxCommand(program)` 函数,在 `src/index.ts` 中统一注册到 Commander。命令内部逻辑封装在对应的 `handleXxx` 函数中。
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
九个命令:`create`、`run`、`resume`、`list`、`remove`、`validate`、`merge`、`config`、`sync`。
|
|
28
28
|
|
|
29
29
|
### 核心流程(run 命令)
|
|
30
30
|
|
|
@@ -55,15 +55,31 @@ run 命令有两种模式:
|
|
|
55
55
|
|
|
56
56
|
### validate + merge 工作流
|
|
57
57
|
|
|
58
|
-
- `validate
|
|
59
|
-
-
|
|
58
|
+
- `validate`:将目标分支的全量变更(已提交 + 未提交)通过 `git diff HEAD...branch --binary` 的 patch 方式迁移到主 worktree,便于在主 worktree 中测试。支持两种模式:
|
|
59
|
+
- **首次 validate**(无历史快照):patch 迁移全量变更 → 保存纯净快照 patch + 主分支 HEAD hash → 结果:暂存区=空,工作目录=全量变更
|
|
60
|
+
- **增量 validate**(存在历史快照):校验主分支 HEAD 一致性(不一致则清除旧快照降级为首次模式)→ 读取旧 patch → 确保主 worktree 干净 → patch 迁移最新变更 → 保存新快照 → 旧 patch 应用到暂存区 → 结果:暂存区=上次快照,工作目录=最新变更(可通过 `git diff` 查看增量差异)
|
|
61
|
+
- `--clean` 选项:重置主 worktree + 删除对应快照文件
|
|
62
|
+
- 快照存储路径:`~/.clawt/validate-snapshots/<projectName>/<branchName>.patch`(patch 文件)+ `<branchName>.head`(主分支 HEAD hash)
|
|
63
|
+
- 变更检测:同时检测目标 worktree 的未提交修改和已提交 commit,两者均无则提示无需验证
|
|
64
|
+
- 未提交修改处理:有未提交修改时先做临时 commit,diff 完成后通过 `git reset --soft` 撤销恢复原状
|
|
65
|
+
- `merge`:检测目标 worktree 状态(有修改则需 `-m` 提交,已提交则跳过,无变更则报错)→ 合并到主 worktree → pull → push → 可选清理 worktree 和分支(受 `autoDeleteBranch` 配置或交互式确认控制)→ 清理对应的 validate 快照
|
|
60
66
|
- `run` 中断清理:Ctrl+C 终止所有子进程后,根据 `autoDeleteBranch` 配置自动清理或交互式确认清理本次创建的 worktree 和分支
|
|
61
67
|
|
|
68
|
+
### sync 命令流程
|
|
69
|
+
|
|
70
|
+
1. `validateMainWorktree()` 确认在主 worktree 根目录
|
|
71
|
+
2. 检查目标 worktree 是否存在
|
|
72
|
+
3. 获取主分支名(`getCurrentBranch()`,不硬编码 main/master)
|
|
73
|
+
4. 如果目标 worktree 有未提交变更,自动 `git add . && git commit` 保存
|
|
74
|
+
5. 在目标 worktree 中执行 `git merge <mainBranch>` 合并主分支
|
|
75
|
+
6. 冲突处理:有冲突时提示用户手动解决,无冲突则输出成功
|
|
76
|
+
7. 合并成功后清除该分支的 validate 快照(代码基础已变化,旧快照无效)
|
|
77
|
+
|
|
62
78
|
### 目录层级
|
|
63
79
|
|
|
64
80
|
- `src/commands/` — 各命令的注册与处理逻辑
|
|
65
|
-
- `src/utils/` — 工具函数(git
|
|
66
|
-
- `src/constants/` —
|
|
81
|
+
- `src/utils/` — 工具函数(git 操作(含三点 diff、分支合并、冲突检测等)、shell 执行与子进程管理、分支名处理、worktree 管理与批量清理、配置、格式化输出、交互式输入、Claude Code 交互式启动、validate 快照管理(含 HEAD hash 一致性校验))
|
|
82
|
+
- `src/constants/` — 常量定义(路径、退出码、消息模板、分支规则、配置默认值、终端控制序列、validate 快照目录、sync 相关消息)
|
|
67
83
|
- `src/types/` — TypeScript 类型定义
|
|
68
84
|
- `src/errors/` — 自定义 `ClawtError` 错误类(携带退出码)
|
|
69
85
|
- `src/logger/` — winston 日志(按日期滚动,写入 `~/.clawt/logs/`)
|
|
@@ -73,6 +89,6 @@ run 命令有两种模式:
|
|
|
73
89
|
- 所有命令执行前都会调用 `validateMainWorktree()` 确保在主 worktree 根目录(`git rev-parse --git-common-dir === ".git"`)
|
|
74
90
|
- Worktree 统一存放在 `~/.clawt/worktrees/<projectName>/` 下
|
|
75
91
|
- 全局配置文件 `~/.clawt/config.json`,postinstall 时自动创建/合并,包含 `autoDeleteBranch`(是否自动删除分支)、`claudeCodeCommand`(Claude Code CLI 启动指令,用于 `run` 和 `resume` 的交互式界面)、`autoPullPush`(merge 后是否自动 pull/push)三个配置项。配置项以 `CONFIG_DEFINITIONS` 为单一数据源,`DEFAULT_CONFIG` 和 `CONFIG_DESCRIPTIONS` 均从中派生
|
|
76
|
-
- shell 命令执行有同步(`execCommand` → `execSync
|
|
92
|
+
- shell 命令执行有同步(`execCommand` → `execSync`)、异步(`spawnProcess` → `spawn`)和同步带 stdin(`execCommandWithInput` → `execFileSync`)三种方式
|
|
77
93
|
- 项目为纯 ESM(`"type": "module"`),模块导入需带 `.js` 后缀
|
|
78
94
|
- 分支名特殊字符会被 `sanitizeBranchName()` 自动清理
|
package/README.md
CHANGED
|
@@ -93,17 +93,46 @@ clawt resume -b feature-login
|
|
|
93
93
|
### `clawt validate` — 在主 worktree 验证分支变更
|
|
94
94
|
|
|
95
95
|
```bash
|
|
96
|
-
clawt validate -b <branchName>
|
|
96
|
+
clawt validate -b <branchName> [--clean]
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
| 参数 | 必填 | 说明 |
|
|
100
100
|
| ---- | ---- | ---- |
|
|
101
101
|
| `-b` | 是 | 要验证的分支名 |
|
|
102
|
+
| `--clean` | 否 | 清理 validate 状态(重置主 worktree 并删除快照) |
|
|
102
103
|
|
|
103
|
-
将目标 worktree 的变更通过 `git
|
|
104
|
+
将目标 worktree 的变更通过 `git diff`(三点 diff)迁移到主 worktree,方便在主 worktree 中直接测试,无需重新安装依赖。同时检测未提交修改和已提交 commit,确保所有变更都能被捕获。
|
|
105
|
+
|
|
106
|
+
支持增量模式:首次 validate 后会自动保存快照(patch + 主分支 HEAD hash),再次 validate 同一分支时会先校验主分支 HEAD 一致性(不一致则降级为首次模式),然后将上次快照应用到暂存区、最新变更保留在工作目录,用户可通过 `git diff` 查看两次 validate 之间的增量差异。使用 `--clean` 可清理 validate 状态(重置主 worktree 并删除快照文件)。
|
|
107
|
+
|
|
108
|
+
> **提示:** 如果 validate 时 patch apply 失败(目标分支与主分支差异过大),可先执行 `clawt sync -b <branchName>` 同步主分支后重试。
|
|
104
109
|
|
|
105
110
|
```bash
|
|
111
|
+
# 首次验证
|
|
112
|
+
clawt validate -b feature-scheme-1
|
|
113
|
+
|
|
114
|
+
# 再次验证(增量模式,可通过 git diff 查看增量差异)
|
|
106
115
|
clawt validate -b feature-scheme-1
|
|
116
|
+
|
|
117
|
+
# 清理 validate 状态
|
|
118
|
+
clawt validate -b feature-scheme-1 --clean
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `clawt sync` — 将主分支代码同步到目标 worktree
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
clawt sync -b <branchName>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
| 参数 | 必填 | 说明 |
|
|
128
|
+
| ---- | ---- | ---- |
|
|
129
|
+
| `-b` | 是 | 要同步的分支名 |
|
|
130
|
+
|
|
131
|
+
将主分支最新代码合并到目标 worktree 的分支中。如果目标 worktree 有未提交的修改,会自动保存后再合并。存在冲突时会提示用户手动解决。合并成功后会自动清除该分支的 validate 快照(代码基础已变化,旧快照无效)。
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# 将主分支最新代码同步到目标 worktree
|
|
135
|
+
clawt sync -b feature-scheme-1
|
|
107
136
|
```
|
|
108
137
|
|
|
109
138
|
### `clawt merge` — 合并分支到主 worktree
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ var CLAWT_HOME = join(homedir(), ".clawt");
|
|
|
13
13
|
var CONFIG_PATH = join(CLAWT_HOME, "config.json");
|
|
14
14
|
var LOGS_DIR = join(CLAWT_HOME, "logs");
|
|
15
15
|
var WORKTREES_DIR = join(CLAWT_HOME, "worktrees");
|
|
16
|
+
var VALIDATE_SNAPSHOTS_DIR = join(CLAWT_HOME, "validate-snapshots");
|
|
16
17
|
|
|
17
18
|
// src/constants/branch.ts
|
|
18
19
|
var INVALID_BRANCH_CHARS = /[\/\\.\s~:*?[\]^]+/g;
|
|
@@ -41,8 +42,6 @@ var MESSAGES = {
|
|
|
41
42
|
MAIN_WORKTREE_DIRTY: "\u4E3B worktree \u6709\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF0C\u8BF7\u5148\u5904\u7406",
|
|
42
43
|
/** 目标 worktree 无更改 */
|
|
43
44
|
TARGET_WORKTREE_CLEAN: "\u8BE5 worktree \u7684\u5206\u652F\u4E0A\u6CA1\u6709\u4EFB\u4F55\u66F4\u6539\uFF0C\u65E0\u9700\u9A8C\u8BC1",
|
|
44
|
-
/** stash 已变更 */
|
|
45
|
-
STASH_CHANGED: "git stash list \u5DF2\u53D8\u66F4\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C",
|
|
46
45
|
/** validate 成功 */
|
|
47
46
|
VALIDATE_SUCCESS: (branch) => `\u2713 \u5DF2\u5C06\u5206\u652F ${branch} \u7684\u53D8\u66F4\u5E94\u7528\u5230\u4E3B worktree
|
|
48
47
|
\u53EF\u4EE5\u5F00\u59CB\u9A8C\u8BC1\u4E86`,
|
|
@@ -81,7 +80,29 @@ var MESSAGES = {
|
|
|
81
80
|
/** 创建数量参数无效 */
|
|
82
81
|
INVALID_COUNT: (value) => `\u65E0\u6548\u7684\u521B\u5EFA\u6570\u91CF: "${value}"\uFF0C\u8BF7\u8F93\u5165\u6B63\u6574\u6570`,
|
|
83
82
|
/** worktree 状态获取失败 */
|
|
84
|
-
WORKTREE_STATUS_UNAVAILABLE: "(\u72B6\u6001\u4E0D\u53EF\u7528)"
|
|
83
|
+
WORKTREE_STATUS_UNAVAILABLE: "(\u72B6\u6001\u4E0D\u53EF\u7528)",
|
|
84
|
+
/** 增量 validate 成功提示 */
|
|
85
|
+
INCREMENTAL_VALIDATE_SUCCESS: (branch) => `\u2713 \u5DF2\u5C06\u5206\u652F ${branch} \u7684\u6700\u65B0\u53D8\u66F4\u5E94\u7528\u5230\u4E3B worktree\uFF08\u589E\u91CF\u6A21\u5F0F\uFF09
|
|
86
|
+
\u6682\u5B58\u533A = \u4E0A\u6B21\u5FEB\u7167\uFF0C\u5DE5\u4F5C\u76EE\u5F55 = \u6700\u65B0\u53D8\u66F4`,
|
|
87
|
+
/** 增量 validate 降级为全量模式提示 */
|
|
88
|
+
INCREMENTAL_VALIDATE_FALLBACK: "\u589E\u91CF\u5BF9\u6BD4\u5931\u8D25\uFF0C\u5DF2\u964D\u7EA7\u4E3A\u5168\u91CF\u6A21\u5F0F",
|
|
89
|
+
/** validate 状态已清理 */
|
|
90
|
+
VALIDATE_CLEANED: (branch) => `\u2713 \u5206\u652F ${branch} \u7684 validate \u72B6\u6001\u5DF2\u6E05\u7406`,
|
|
91
|
+
/** merge 命令检测到 validate 状态的提示 */
|
|
92
|
+
MERGE_VALIDATE_STATE_HINT: (branch) => `\u4E3B worktree \u53EF\u80FD\u5B58\u5728 validate \u6B8B\u7559\u72B6\u6001\uFF0C\u53EF\u5148\u6267\u884C clawt validate -b ${branch} --clean \u6E05\u7406`,
|
|
93
|
+
/** sync 自动保存未提交变更 */
|
|
94
|
+
SYNC_AUTO_COMMITTED: (branch) => `\u5DF2\u81EA\u52A8\u4FDD\u5B58 ${branch} \u5206\u652F\u7684\u672A\u63D0\u4EA4\u53D8\u66F4`,
|
|
95
|
+
/** sync 开始合并 */
|
|
96
|
+
SYNC_MERGING: (targetBranch, mainBranch) => `\u6B63\u5728\u5C06 ${mainBranch} \u5408\u5E76\u5230 ${targetBranch} ...`,
|
|
97
|
+
/** sync 成功 */
|
|
98
|
+
SYNC_SUCCESS: (targetBranch, mainBranch) => `\u2713 \u5DF2\u5C06 ${mainBranch} \u7684\u6700\u65B0\u4EE3\u7801\u540C\u6B65\u5230 ${targetBranch}`,
|
|
99
|
+
/** sync 冲突 */
|
|
100
|
+
SYNC_CONFLICT: (worktreePath) => `\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u8FDB\u5165\u76EE\u6807 worktree \u624B\u52A8\u89E3\u51B3\uFF1A
|
|
101
|
+
cd ${worktreePath}
|
|
102
|
+
\u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue`,
|
|
103
|
+
/** validate patch apply 失败,提示用户同步主分支 */
|
|
104
|
+
VALIDATE_PATCH_APPLY_FAILED: (branch) => `\u53D8\u66F4\u8FC1\u79FB\u5931\u8D25\uFF1A\u76EE\u6807\u5206\u652F\u4E0E\u4E3B\u5206\u652F\u5DEE\u5F02\u8FC7\u5927
|
|
105
|
+
\u8BF7\u5148\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`
|
|
85
106
|
};
|
|
86
107
|
|
|
87
108
|
// src/constants/exitCodes.ts
|
|
@@ -168,7 +189,7 @@ var logger = winston.createLogger({
|
|
|
168
189
|
});
|
|
169
190
|
|
|
170
191
|
// src/utils/shell.ts
|
|
171
|
-
import { execSync, spawn } from "child_process";
|
|
192
|
+
import { execSync, execFileSync, spawn } from "child_process";
|
|
172
193
|
function execCommand(command, options) {
|
|
173
194
|
logger.debug(`\u6267\u884C\u547D\u4EE4: ${command}${options?.cwd ? ` (cwd: ${options.cwd})` : ""}`);
|
|
174
195
|
const result = execSync(command, {
|
|
@@ -192,9 +213,20 @@ function killAllChildProcesses(children) {
|
|
|
192
213
|
}
|
|
193
214
|
}
|
|
194
215
|
}
|
|
216
|
+
function execCommandWithInput(command, args, options) {
|
|
217
|
+
logger.debug(`\u6267\u884C\u547D\u4EE4(stdin): ${command} ${args.join(" ")}${options.cwd ? ` (cwd: ${options.cwd})` : ""}`);
|
|
218
|
+
const result = execFileSync(command, args, {
|
|
219
|
+
cwd: options.cwd,
|
|
220
|
+
input: options.input,
|
|
221
|
+
encoding: "utf-8",
|
|
222
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
223
|
+
});
|
|
224
|
+
return result.trim();
|
|
225
|
+
}
|
|
195
226
|
|
|
196
227
|
// src/utils/git.ts
|
|
197
228
|
import { basename } from "path";
|
|
229
|
+
import { execSync as execSync2 } from "child_process";
|
|
198
230
|
function getGitCommonDir(cwd) {
|
|
199
231
|
return execCommand("git rev-parse --git-common-dir", { cwd });
|
|
200
232
|
}
|
|
@@ -259,19 +291,6 @@ function gitCleanForce(cwd) {
|
|
|
259
291
|
function gitStashPush(message, cwd) {
|
|
260
292
|
execCommand(`git stash push -m "${message}"`, { cwd });
|
|
261
293
|
}
|
|
262
|
-
function gitStashApply(cwd) {
|
|
263
|
-
execCommand("git stash apply", { cwd });
|
|
264
|
-
}
|
|
265
|
-
function gitStashPop(index = 0, cwd) {
|
|
266
|
-
execCommand(`git stash pop stash@{${index}}`, { cwd });
|
|
267
|
-
}
|
|
268
|
-
function gitStashList(cwd) {
|
|
269
|
-
try {
|
|
270
|
-
return execCommand("git stash list", { cwd });
|
|
271
|
-
} catch {
|
|
272
|
-
return "";
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
294
|
function gitRestoreStaged(cwd) {
|
|
276
295
|
execCommand("git restore --staged .", { cwd });
|
|
277
296
|
}
|
|
@@ -316,6 +335,35 @@ function getDiffStat(branchName, worktreePath, cwd) {
|
|
|
316
335
|
deletions: committed.deletions + uncommitted.deletions
|
|
317
336
|
};
|
|
318
337
|
}
|
|
338
|
+
function gitDiffCachedBinary(cwd) {
|
|
339
|
+
logger.debug(`\u6267\u884C\u547D\u4EE4: git diff --cached --binary${cwd ? ` (cwd: ${cwd})` : ""}`);
|
|
340
|
+
return execSync2("git diff --cached --binary", {
|
|
341
|
+
cwd,
|
|
342
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
function gitApplyCachedFromStdin(patchContent, cwd) {
|
|
346
|
+
execCommandWithInput("git", ["apply", "--cached"], { input: patchContent, cwd });
|
|
347
|
+
}
|
|
348
|
+
function getCurrentBranch(cwd) {
|
|
349
|
+
return execCommand("git rev-parse --abbrev-ref HEAD", { cwd });
|
|
350
|
+
}
|
|
351
|
+
function getHeadCommitHash(cwd) {
|
|
352
|
+
return execCommand("git rev-parse HEAD", { cwd });
|
|
353
|
+
}
|
|
354
|
+
function gitDiffBinaryAgainstBranch(branchName, cwd) {
|
|
355
|
+
logger.debug(`\u6267\u884C\u547D\u4EE4: git diff HEAD...${branchName} --binary${cwd ? ` (cwd: ${cwd})` : ""}`);
|
|
356
|
+
return execSync2(`git diff HEAD...${branchName} --binary`, {
|
|
357
|
+
cwd,
|
|
358
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function gitApplyFromStdin(patchContent, cwd) {
|
|
362
|
+
execCommandWithInput("git", ["apply"], { input: patchContent, cwd });
|
|
363
|
+
}
|
|
364
|
+
function gitResetSoft(count = 1, cwd) {
|
|
365
|
+
execCommand(`git reset --soft HEAD~${count}`, { cwd });
|
|
366
|
+
}
|
|
319
367
|
|
|
320
368
|
// src/utils/formatter.ts
|
|
321
369
|
import chalk from "chalk";
|
|
@@ -565,6 +613,52 @@ function launchInteractiveClaude(worktree) {
|
|
|
565
613
|
}
|
|
566
614
|
}
|
|
567
615
|
|
|
616
|
+
// src/utils/validate-snapshot.ts
|
|
617
|
+
import { join as join3 } from "path";
|
|
618
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, readdirSync as readdirSync3, rmdirSync as rmdirSync2 } from "fs";
|
|
619
|
+
function getSnapshotPath(projectName, branchName) {
|
|
620
|
+
return join3(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.patch`);
|
|
621
|
+
}
|
|
622
|
+
function getSnapshotHeadPath(projectName, branchName) {
|
|
623
|
+
return join3(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.head`);
|
|
624
|
+
}
|
|
625
|
+
function hasSnapshot(projectName, branchName) {
|
|
626
|
+
return existsSync5(getSnapshotPath(projectName, branchName));
|
|
627
|
+
}
|
|
628
|
+
function readSnapshot(projectName, branchName) {
|
|
629
|
+
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
630
|
+
logger.debug(`\u8BFB\u53D6 validate \u5FEB\u7167: ${snapshotPath}`);
|
|
631
|
+
return readFileSync2(snapshotPath);
|
|
632
|
+
}
|
|
633
|
+
function writeSnapshot(projectName, branchName, patch, headHash) {
|
|
634
|
+
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
635
|
+
const snapshotDir = join3(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
636
|
+
ensureDir(snapshotDir);
|
|
637
|
+
writeFileSync2(snapshotPath, patch);
|
|
638
|
+
if (headHash) {
|
|
639
|
+
writeFileSync2(getSnapshotHeadPath(projectName, branchName), headHash, "utf-8");
|
|
640
|
+
}
|
|
641
|
+
logger.info(`\u5DF2\u4FDD\u5B58 validate \u5FEB\u7167: ${snapshotPath}`);
|
|
642
|
+
}
|
|
643
|
+
function removeSnapshot(projectName, branchName) {
|
|
644
|
+
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
645
|
+
if (existsSync5(snapshotPath)) {
|
|
646
|
+
unlinkSync(snapshotPath);
|
|
647
|
+
logger.info(`\u5DF2\u5220\u9664 validate \u5FEB\u7167: ${snapshotPath}`);
|
|
648
|
+
}
|
|
649
|
+
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
650
|
+
if (existsSync5(headPath)) {
|
|
651
|
+
unlinkSync(headPath);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function readSnapshotHead(projectName, branchName) {
|
|
655
|
+
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
656
|
+
if (!existsSync5(headPath)) {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
return readFileSync2(headPath, "utf-8").trim();
|
|
660
|
+
}
|
|
661
|
+
|
|
568
662
|
// src/commands/list.ts
|
|
569
663
|
import chalk2 from "chalk";
|
|
570
664
|
function registerListCommand(program2) {
|
|
@@ -624,7 +718,7 @@ function handleCreate(options) {
|
|
|
624
718
|
}
|
|
625
719
|
|
|
626
720
|
// src/commands/remove.ts
|
|
627
|
-
import { join as
|
|
721
|
+
import { join as join4 } from "path";
|
|
628
722
|
function registerRemoveCommand(program2) {
|
|
629
723
|
program2.command("remove").description("\u79FB\u9664 worktree\uFF08\u652F\u6301\u5355\u4E2A/\u6279\u91CF/\u5168\u90E8\uFF09").option("--all", "\u79FB\u9664\u5F53\u524D\u9879\u76EE\u4E0B\u6240\u6709 worktree").option("-b, --branch <branchName>", "\u6307\u5B9A\u5206\u652F\u540D").option("-i, --index <index>", "\u6307\u5B9A\u7D22\u5F15\uFF08\u914D\u5408 -b \u4F7F\u7528\uFF09").action(async (options) => {
|
|
630
724
|
await handleRemove(options);
|
|
@@ -641,7 +735,7 @@ function resolveWorktreesToRemove(options) {
|
|
|
641
735
|
}
|
|
642
736
|
if (options.index !== void 0) {
|
|
643
737
|
const targetName = `${options.branch}-${options.index}`;
|
|
644
|
-
const targetPath =
|
|
738
|
+
const targetPath = join4(projectDir, targetName);
|
|
645
739
|
const found = allWorktrees.find((wt) => wt.path === targetPath);
|
|
646
740
|
if (!found) {
|
|
647
741
|
throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(targetName));
|
|
@@ -881,11 +975,11 @@ async function handleResume(options) {
|
|
|
881
975
|
}
|
|
882
976
|
|
|
883
977
|
// src/commands/validate.ts
|
|
884
|
-
import { join as
|
|
885
|
-
import { existsSync as
|
|
978
|
+
import { join as join5 } from "path";
|
|
979
|
+
import { existsSync as existsSync6 } from "fs";
|
|
886
980
|
import Enquirer2 from "enquirer";
|
|
887
981
|
function registerValidateCommand(program2) {
|
|
888
|
-
program2.command("validate").description("\u5728\u4E3B worktree \u9A8C\u8BC1\u67D0\u4E2A worktree \u5206\u652F\u7684\u53D8\u66F4").requiredOption("-b, --branch <branchName>", "\u8981\u9A8C\u8BC1\u7684\u5206\u652F\u540D").action(async (options) => {
|
|
982
|
+
program2.command("validate").description("\u5728\u4E3B worktree \u9A8C\u8BC1\u67D0\u4E2A worktree \u5206\u652F\u7684\u53D8\u66F4").requiredOption("-b, --branch <branchName>", "\u8981\u9A8C\u8BC1\u7684\u5206\u652F\u540D").option("--clean", "\u6E05\u7406 validate \u72B6\u6001\uFF08\u91CD\u7F6E\u4E3B worktree \u5E76\u5220\u9664\u5FEB\u7167\uFF09").action(async (options) => {
|
|
889
983
|
await handleValidate(options);
|
|
890
984
|
});
|
|
891
985
|
}
|
|
@@ -923,40 +1017,122 @@ async function handleDirtyMainWorktree(mainWorktreePath) {
|
|
|
923
1017
|
throw new ClawtError("\u5DE5\u4F5C\u533A\u4ECD\u7136\u4E0D\u5E72\u51C0\uFF0C\u8BF7\u624B\u52A8\u5904\u7406");
|
|
924
1018
|
}
|
|
925
1019
|
}
|
|
1020
|
+
function migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted) {
|
|
1021
|
+
let didTempCommit = false;
|
|
1022
|
+
try {
|
|
1023
|
+
if (hasUncommitted) {
|
|
1024
|
+
gitAddAll(targetWorktreePath);
|
|
1025
|
+
gitCommit("clawt:temp-commit-for-validate", targetWorktreePath);
|
|
1026
|
+
didTempCommit = true;
|
|
1027
|
+
}
|
|
1028
|
+
const patch = gitDiffBinaryAgainstBranch(branchName, mainWorktreePath);
|
|
1029
|
+
if (patch.length > 0) {
|
|
1030
|
+
try {
|
|
1031
|
+
gitApplyFromStdin(patch, mainWorktreePath);
|
|
1032
|
+
} catch (error) {
|
|
1033
|
+
logger.warn(`patch apply \u5931\u8D25: ${error}`);
|
|
1034
|
+
printWarning(MESSAGES.VALIDATE_PATCH_APPLY_FAILED(branchName));
|
|
1035
|
+
throw error;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
} finally {
|
|
1039
|
+
if (didTempCommit) {
|
|
1040
|
+
gitResetSoft(1, targetWorktreePath);
|
|
1041
|
+
gitRestoreStaged(targetWorktreePath);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
function saveCurrentSnapshotPatch(mainWorktreePath, projectName, branchName) {
|
|
1046
|
+
gitAddAll(mainWorktreePath);
|
|
1047
|
+
const patch = gitDiffCachedBinary(mainWorktreePath);
|
|
1048
|
+
gitRestoreStaged(mainWorktreePath);
|
|
1049
|
+
const headHash = getHeadCommitHash(mainWorktreePath);
|
|
1050
|
+
writeSnapshot(projectName, branchName, patch, headHash);
|
|
1051
|
+
return patch;
|
|
1052
|
+
}
|
|
1053
|
+
function handleValidateClean(options) {
|
|
1054
|
+
validateMainWorktree();
|
|
1055
|
+
const projectName = getProjectName();
|
|
1056
|
+
const mainWorktreePath = getGitTopLevel();
|
|
1057
|
+
logger.info(`validate --clean \u6267\u884C\uFF0C\u5206\u652F: ${options.branch}`);
|
|
1058
|
+
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
1059
|
+
gitResetHard(mainWorktreePath);
|
|
1060
|
+
gitCleanForce(mainWorktreePath);
|
|
1061
|
+
}
|
|
1062
|
+
removeSnapshot(projectName, options.branch);
|
|
1063
|
+
printSuccess(MESSAGES.VALIDATE_CLEANED(options.branch));
|
|
1064
|
+
}
|
|
1065
|
+
function handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
|
|
1066
|
+
migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
1067
|
+
saveCurrentSnapshotPatch(mainWorktreePath, projectName, branchName);
|
|
1068
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS(branchName));
|
|
1069
|
+
}
|
|
1070
|
+
function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
|
|
1071
|
+
const oldPatch = readSnapshot(projectName, branchName);
|
|
1072
|
+
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
1073
|
+
gitResetHard(mainWorktreePath);
|
|
1074
|
+
gitCleanForce(mainWorktreePath);
|
|
1075
|
+
}
|
|
1076
|
+
migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
1077
|
+
saveCurrentSnapshotPatch(mainWorktreePath, projectName, branchName);
|
|
1078
|
+
if (oldPatch.length > 0) {
|
|
1079
|
+
try {
|
|
1080
|
+
gitApplyCachedFromStdin(oldPatch, mainWorktreePath);
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
logger.warn(`\u589E\u91CF apply \u5931\u8D25: ${error}`);
|
|
1083
|
+
printWarning(MESSAGES.INCREMENTAL_VALIDATE_FALLBACK);
|
|
1084
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS(branchName));
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
printSuccess(MESSAGES.INCREMENTAL_VALIDATE_SUCCESS(branchName));
|
|
1089
|
+
}
|
|
926
1090
|
async function handleValidate(options) {
|
|
1091
|
+
if (options.clean) {
|
|
1092
|
+
handleValidateClean(options);
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
927
1095
|
validateMainWorktree();
|
|
928
1096
|
const projectName = getProjectName();
|
|
929
1097
|
const mainWorktreePath = getGitTopLevel();
|
|
930
1098
|
const projectDir = getProjectWorktreeDir();
|
|
931
|
-
const targetWorktreePath =
|
|
1099
|
+
const targetWorktreePath = join5(projectDir, options.branch);
|
|
932
1100
|
logger.info(`validate \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}`);
|
|
933
|
-
if (!
|
|
1101
|
+
if (!existsSync6(targetWorktreePath)) {
|
|
934
1102
|
throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch));
|
|
935
1103
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
if (isWorkingDirClean(targetWorktreePath)) {
|
|
1104
|
+
const hasUncommitted = !isWorkingDirClean(targetWorktreePath);
|
|
1105
|
+
const hasCommitted = hasLocalCommits(options.branch, mainWorktreePath);
|
|
1106
|
+
if (!hasUncommitted && !hasCommitted) {
|
|
940
1107
|
printInfo(MESSAGES.TARGET_WORKTREE_CLEAN);
|
|
941
1108
|
return;
|
|
942
1109
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1110
|
+
let isIncremental = hasSnapshot(projectName, options.branch);
|
|
1111
|
+
if (isIncremental) {
|
|
1112
|
+
const savedHead = readSnapshotHead(projectName, options.branch);
|
|
1113
|
+
const currentHead = getHeadCommitHash(mainWorktreePath);
|
|
1114
|
+
if (!savedHead || savedHead !== currentHead) {
|
|
1115
|
+
logger.info(`\u4E3B\u5206\u652F HEAD \u4E0D\u5339\u914D (${savedHead ?? "null"} \u2192 ${currentHead})\uFF0C\u6E05\u9664\u65E7\u5FEB\u7167`);
|
|
1116
|
+
removeSnapshot(projectName, options.branch);
|
|
1117
|
+
isIncremental = false;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
if (isIncremental) {
|
|
1121
|
+
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
1122
|
+
await handleDirtyMainWorktree(mainWorktreePath);
|
|
1123
|
+
}
|
|
1124
|
+
handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, options.branch, hasUncommitted);
|
|
1125
|
+
} else {
|
|
1126
|
+
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
1127
|
+
await handleDirtyMainWorktree(mainWorktreePath);
|
|
1128
|
+
}
|
|
1129
|
+
handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, options.branch, hasUncommitted);
|
|
952
1130
|
}
|
|
953
|
-
gitStashPop(0, mainWorktreePath);
|
|
954
|
-
printSuccess(MESSAGES.VALIDATE_SUCCESS(options.branch));
|
|
955
1131
|
}
|
|
956
1132
|
|
|
957
1133
|
// src/commands/merge.ts
|
|
958
|
-
import { join as
|
|
959
|
-
import { existsSync as
|
|
1134
|
+
import { join as join6 } from "path";
|
|
1135
|
+
import { existsSync as existsSync7 } from "fs";
|
|
960
1136
|
function registerMergeCommand(program2) {
|
|
961
1137
|
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) => {
|
|
962
1138
|
await handleMerge(options);
|
|
@@ -978,12 +1154,16 @@ async function handleMerge(options) {
|
|
|
978
1154
|
validateMainWorktree();
|
|
979
1155
|
const mainWorktreePath = getGitTopLevel();
|
|
980
1156
|
const projectDir = getProjectWorktreeDir();
|
|
981
|
-
const targetWorktreePath =
|
|
1157
|
+
const targetWorktreePath = join6(projectDir, options.branch);
|
|
982
1158
|
logger.info(`merge \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}\uFF0C\u63D0\u4EA4\u4FE1\u606F: ${options.message ?? "(\u672A\u63D0\u4F9B)"}`);
|
|
983
|
-
if (!
|
|
1159
|
+
if (!existsSync7(targetWorktreePath)) {
|
|
984
1160
|
throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(options.branch));
|
|
985
1161
|
}
|
|
1162
|
+
const projectName = getProjectName();
|
|
986
1163
|
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
1164
|
+
if (hasSnapshot(projectName, options.branch)) {
|
|
1165
|
+
printWarning(MESSAGES.MERGE_VALIDATE_STATE_HINT(options.branch));
|
|
1166
|
+
}
|
|
987
1167
|
throw new ClawtError(MESSAGES.MAIN_WORKTREE_DIRTY);
|
|
988
1168
|
}
|
|
989
1169
|
const shouldCleanup = await shouldCleanupAfterMerge(options.branch);
|
|
@@ -1024,6 +1204,9 @@ async function handleMerge(options) {
|
|
|
1024
1204
|
if (shouldCleanup) {
|
|
1025
1205
|
cleanupWorktreeAndBranch(targetWorktreePath, options.branch);
|
|
1026
1206
|
}
|
|
1207
|
+
if (hasSnapshot(projectName, options.branch)) {
|
|
1208
|
+
removeSnapshot(projectName, options.branch);
|
|
1209
|
+
}
|
|
1027
1210
|
}
|
|
1028
1211
|
|
|
1029
1212
|
// src/commands/config.ts
|
|
@@ -1060,6 +1243,58 @@ function formatConfigValue(value) {
|
|
|
1060
1243
|
return chalk3.cyan(String(value));
|
|
1061
1244
|
}
|
|
1062
1245
|
|
|
1246
|
+
// src/commands/sync.ts
|
|
1247
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1248
|
+
function registerSyncCommand(program2) {
|
|
1249
|
+
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) => {
|
|
1250
|
+
await handleSync(options);
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
function autoSaveChanges(worktreePath, branch) {
|
|
1254
|
+
gitAddAll(worktreePath);
|
|
1255
|
+
gitCommit("chore: auto-save before sync", worktreePath);
|
|
1256
|
+
printInfo(MESSAGES.SYNC_AUTO_COMMITTED(branch));
|
|
1257
|
+
logger.info(`\u5DF2\u81EA\u52A8\u4FDD\u5B58 ${branch} \u5206\u652F\u7684\u672A\u63D0\u4EA4\u53D8\u66F4`);
|
|
1258
|
+
}
|
|
1259
|
+
function mergeMainBranch(worktreePath, mainBranch) {
|
|
1260
|
+
try {
|
|
1261
|
+
gitMerge(mainBranch, worktreePath);
|
|
1262
|
+
return false;
|
|
1263
|
+
} catch {
|
|
1264
|
+
if (hasMergeConflict(worktreePath)) {
|
|
1265
|
+
return true;
|
|
1266
|
+
}
|
|
1267
|
+
throw new ClawtError(`\u5408\u5E76 ${mainBranch} \u5931\u8D25`);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
async function handleSync(options) {
|
|
1271
|
+
validateMainWorktree();
|
|
1272
|
+
const { branch } = options;
|
|
1273
|
+
logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${branch}`);
|
|
1274
|
+
const projectWorktreeDir = getProjectWorktreeDir();
|
|
1275
|
+
const targetWorktreePath = `${projectWorktreeDir}/${branch}`;
|
|
1276
|
+
if (!existsSync8(targetWorktreePath)) {
|
|
1277
|
+
throw new ClawtError(MESSAGES.WORKTREE_NOT_FOUND(branch));
|
|
1278
|
+
}
|
|
1279
|
+
const mainWorktreePath = getGitTopLevel();
|
|
1280
|
+
const mainBranch = getCurrentBranch(mainWorktreePath);
|
|
1281
|
+
if (!isWorkingDirClean(targetWorktreePath)) {
|
|
1282
|
+
autoSaveChanges(targetWorktreePath, branch);
|
|
1283
|
+
}
|
|
1284
|
+
printInfo(MESSAGES.SYNC_MERGING(branch, mainBranch));
|
|
1285
|
+
const hasConflict = mergeMainBranch(targetWorktreePath, mainBranch);
|
|
1286
|
+
if (hasConflict) {
|
|
1287
|
+
printWarning(MESSAGES.SYNC_CONFLICT(targetWorktreePath));
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
const projectName = getProjectName();
|
|
1291
|
+
if (hasSnapshot(projectName, branch)) {
|
|
1292
|
+
removeSnapshot(projectName, branch);
|
|
1293
|
+
logger.info(`\u5DF2\u6E05\u9664\u5206\u652F ${branch} \u7684 validate \u5FEB\u7167`);
|
|
1294
|
+
}
|
|
1295
|
+
printSuccess(MESSAGES.SYNC_SUCCESS(branch, mainBranch));
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1063
1298
|
// src/index.ts
|
|
1064
1299
|
var require2 = createRequire(import.meta.url);
|
|
1065
1300
|
var { version } = require2("../package.json");
|
|
@@ -1074,6 +1309,7 @@ registerResumeCommand(program);
|
|
|
1074
1309
|
registerValidateCommand(program);
|
|
1075
1310
|
registerMergeCommand(program);
|
|
1076
1311
|
registerConfigCommand(program);
|
|
1312
|
+
registerSyncCommand(program);
|
|
1077
1313
|
process.on("uncaughtException", (error) => {
|
|
1078
1314
|
if (error instanceof ClawtError) {
|
|
1079
1315
|
printError(error.message);
|
package/dist/postinstall.js
CHANGED
|
@@ -10,6 +10,7 @@ var CLAWT_HOME = join(homedir(), ".clawt");
|
|
|
10
10
|
var CONFIG_PATH = join(CLAWT_HOME, "config.json");
|
|
11
11
|
var LOGS_DIR = join(CLAWT_HOME, "logs");
|
|
12
12
|
var WORKTREES_DIR = join(CLAWT_HOME, "worktrees");
|
|
13
|
+
var VALIDATE_SNAPSHOTS_DIR = join(CLAWT_HOME, "validate-snapshots");
|
|
13
14
|
|
|
14
15
|
// src/constants/config.ts
|
|
15
16
|
var CONFIG_DEFINITIONS = {
|