clawt 3.5.0 → 3.5.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.
@@ -67,7 +67,7 @@ vi.mock('../../../src/utils/index.js', () => ({
67
67
  hasCommitWithMessage: vi.fn(),
68
68
  gitMergeBase: vi.fn(),
69
69
  gitResetSoftTo: vi.fn(),
70
- getCurrentBranch: vi.fn(),
70
+ getCurrentBranch: vi.fn().mockReturnValue('main'),
71
71
  gitResetHard: vi.fn(),
72
72
  gitCleanForce: vi.fn(),
73
73
  gitCheckout: vi.fn(),
@@ -77,6 +77,7 @@ vi.mock('../../../src/utils/index.js', () => ({
77
77
  ensureOnMainWorkBranch: vi.fn(),
78
78
  guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
79
79
  guardMainWorkBranchExists: vi.fn(),
80
+ handleMergeConflict: vi.fn(),
80
81
  }));
81
82
 
82
83
  import { registerMergeCommand } from '../../../src/commands/merge.js';
@@ -101,6 +102,7 @@ import {
101
102
  cleanupWorktrees,
102
103
  hasCommitWithMessage,
103
104
  resolveTargetWorktree,
105
+ handleMergeConflict,
104
106
  } from '../../../src/utils/index.js';
105
107
 
106
108
  const mockedGetProjectName = vi.mocked(getProjectName);
@@ -123,6 +125,7 @@ const mockedConfirmAction = vi.mocked(confirmAction);
123
125
  const mockedCleanupWorktrees = vi.mocked(cleanupWorktrees);
124
126
  const mockedHasCommitWithMessage = vi.mocked(hasCommitWithMessage);
125
127
  const mockedResolveTargetWorktree = vi.mocked(resolveTargetWorktree);
128
+ const mockedHandleMergeConflict = vi.mocked(handleMergeConflict);
126
129
 
127
130
  const worktree = { path: '/path/feature', branch: 'feature' };
128
131
 
@@ -147,6 +150,8 @@ beforeEach(() => {
147
150
  mockedPrintWarning.mockReset();
148
151
  mockedRemoveSnapshot.mockReset();
149
152
  mockedCleanupWorktrees.mockReset();
153
+ mockedHandleMergeConflict.mockReset();
154
+ mockedHandleMergeConflict.mockResolvedValue(true); // 默认 AI 解决成功
150
155
  });
151
156
 
152
157
  describe('registerMergeCommand', () => {
@@ -229,11 +234,29 @@ describe('handleMerge', () => {
229
234
  expect(mockedPrintSuccess).toHaveBeenCalled();
230
235
  });
231
236
 
232
- it('合并冲突时抛出错误', async () => {
237
+ it('合并冲突时调用 handleMergeConflict', async () => {
233
238
  mockedIsWorkingDirClean.mockReturnValue(true);
234
239
  mockedHasLocalCommits.mockReturnValue(true);
235
240
  mockedGitMerge.mockImplementation(() => { throw new Error('merge conflict'); });
236
241
  mockedHasMergeConflict.mockReturnValue(true);
242
+ mockedHandleMergeConflict.mockResolvedValue(true);
243
+ mockedConfirmAction.mockResolvedValue(false);
244
+
245
+ const program = new Command();
246
+ program.exitOverride();
247
+ registerMergeCommand(program);
248
+ await program.parseAsync(['merge', '-b', 'feature'], { from: 'user' });
249
+
250
+ expect(mockedHandleMergeConflict).toHaveBeenCalledWith('main', 'feature', '/repo', undefined);
251
+ expect(mockedPrintSuccess).toHaveBeenCalled();
252
+ });
253
+
254
+ it('合并冲突且 handleMergeConflict 抛出时向上传播错误', async () => {
255
+ mockedIsWorkingDirClean.mockReturnValue(true);
256
+ mockedHasLocalCommits.mockReturnValue(true);
257
+ mockedGitMerge.mockImplementation(() => { throw new Error('merge conflict'); });
258
+ mockedHasMergeConflict.mockReturnValue(true);
259
+ mockedHandleMergeConflict.mockRejectedValue(new Error('请手动解决冲突'));
237
260
 
238
261
  const program = new Command();
239
262
  program.exitOverride();
@@ -244,6 +267,39 @@ describe('handleMerge', () => {
244
267
  ).rejects.toThrow();
245
268
  });
246
269
 
270
+ it('合并冲突且 AI 部分解决时提前返回', async () => {
271
+ mockedIsWorkingDirClean.mockReturnValue(true);
272
+ mockedHasLocalCommits.mockReturnValue(true);
273
+ mockedGitMerge.mockImplementation(() => { throw new Error('merge conflict'); });
274
+ mockedHasMergeConflict.mockReturnValue(true);
275
+ mockedHandleMergeConflict.mockResolvedValue(false);
276
+
277
+ const program = new Command();
278
+ program.exitOverride();
279
+ registerMergeCommand(program);
280
+ await program.parseAsync(['merge', '-b', 'feature'], { from: 'user' });
281
+
282
+ // AI 未完全解决,不应继续到 pull/push 和 success
283
+ expect(mockedGitPull).not.toHaveBeenCalled();
284
+ expect(mockedPrintSuccess).not.toHaveBeenCalled();
285
+ });
286
+
287
+ it('--auto 参数传递给 handleMergeConflict', async () => {
288
+ mockedIsWorkingDirClean.mockReturnValue(true);
289
+ mockedHasLocalCommits.mockReturnValue(true);
290
+ mockedGitMerge.mockImplementation(() => { throw new Error('merge conflict'); });
291
+ mockedHasMergeConflict.mockReturnValue(true);
292
+ mockedHandleMergeConflict.mockResolvedValue(true);
293
+ mockedConfirmAction.mockResolvedValue(false);
294
+
295
+ const program = new Command();
296
+ program.exitOverride();
297
+ registerMergeCommand(program);
298
+ await program.parseAsync(['merge', '-b', 'feature', '--auto'], { from: 'user' });
299
+
300
+ expect(mockedHandleMergeConflict).toHaveBeenCalledWith('main', 'feature', '/repo', true);
301
+ });
302
+
247
303
  it('autoPullPush=true 时执行 pull 和 push', async () => {
248
304
  mockedIsWorkingDirClean.mockReturnValue(true);
249
305
  mockedHasLocalCommits.mockReturnValue(true);
@@ -303,7 +359,7 @@ describe('handleMerge', () => {
303
359
  });
304
360
  mockedGitPull.mockImplementation(() => { throw new Error('pull failed'); });
305
361
  // merge 成功后的调用顺序:
306
- // 1. 步骤 6 二次确认 hasMergeConflict → false
362
+ // 1. 步骤 5.5 二次确认 hasMergeConflict → false(merge 成功无冲突)
307
363
  // 2. pull catch 中 hasMergeConflict → true(触发 printWarning 并 return)
308
364
  mockedHasMergeConflict.mockReturnValueOnce(false)
309
365
  .mockReturnValueOnce(true);
@@ -0,0 +1,250 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+
3
+ // mock logger
4
+ vi.mock('../../../src/logger/index.js', () => ({
5
+ logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
6
+ }));
7
+
8
+ // mock errors
9
+ vi.mock('../../../src/errors/index.js', () => ({
10
+ ClawtError: class ClawtError extends Error {
11
+ exitCode: number;
12
+ constructor(message: string, exitCode = 1) {
13
+ super(message);
14
+ this.exitCode = exitCode;
15
+ }
16
+ },
17
+ }));
18
+
19
+ // mock node:child_process
20
+ vi.mock('node:child_process', () => ({
21
+ execFileSync: vi.fn(),
22
+ }));
23
+
24
+ // mock constants(使用与 src/constants/messages/merge.ts 一致的消息文本)
25
+ vi.mock('../../../src/constants/index.js', async (importOriginal) => {
26
+ const actual = await importOriginal<typeof import('../../../src/constants/index.js')>();
27
+ return {
28
+ ...actual,
29
+ MESSAGES: {
30
+ ...actual.MESSAGES,
31
+ MERGE_CONFLICT_ASK_AI: '检测到合并冲突,是否使用 Claude Code 自动解决?',
32
+ MERGE_CONFLICT_AI_START: (fileCount: number) =>
33
+ `正在使用 Claude Code 分析并解决 ${fileCount} 个冲突文件...`,
34
+ MERGE_CONFLICT_AI_SUCCESS: '✓ Claude Code 已成功解决所有冲突',
35
+ MERGE_CONFLICT_AI_PARTIAL: (remaining: number) =>
36
+ `Claude Code 已处理冲突文件,但仍有 ${remaining} 个文件存在冲突\n 请手动处理剩余冲突后执行 git add . && git merge --continue`,
37
+ MERGE_CONFLICT_AI_FAILED: (errorMsg: string) =>
38
+ `Claude Code 解决冲突失败: ${errorMsg}\n 请手动处理:\n 解决冲突后执行 git add . && git merge --continue`,
39
+ MERGE_CONFLICT_MANUAL: '合并存在冲突,请手动处理:\n 解决冲突后执行 git add . && git merge --continue',
40
+ },
41
+ };
42
+ });
43
+
44
+ // mock config(determineConflictResolveMode 仍需要)
45
+ vi.mock('../../../src/utils/config.js', () => ({
46
+ getConfigValue: vi.fn(),
47
+ }));
48
+
49
+ // mock formatter
50
+ vi.mock('../../../src/utils/formatter.js', () => ({
51
+ printInfo: vi.fn(),
52
+ printSuccess: vi.fn(),
53
+ printWarning: vi.fn(),
54
+ confirmAction: vi.fn(),
55
+ }));
56
+
57
+ // mock git
58
+ vi.mock('../../../src/utils/git.js', () => ({
59
+ getConflictFiles: vi.fn(),
60
+ hasMergeConflict: vi.fn(),
61
+ gitAddFiles: vi.fn(),
62
+ gitMergeContinue: vi.fn(),
63
+ }));
64
+
65
+ import { execFileSync } from 'node:child_process';
66
+ import {
67
+ buildConflictResolvePrompt,
68
+ invokeClaudeForConflictResolve,
69
+ resolveConflictsWithAI,
70
+ determineConflictResolveMode,
71
+ handleMergeConflict,
72
+ } from '../../../src/utils/conflict-resolver.js';
73
+ import { getConfigValue } from '../../../src/utils/config.js';
74
+ import { confirmAction, printInfo, printSuccess, printWarning } from '../../../src/utils/formatter.js';
75
+ import { getConflictFiles, gitAddFiles, gitMergeContinue } from '../../../src/utils/git.js';
76
+ import { ClawtError } from '../../../src/errors/index.js';
77
+
78
+ const mockedExecFileSync = vi.mocked(execFileSync);
79
+ const mockedGetConfigValue = vi.mocked(getConfigValue);
80
+ const mockedConfirmAction = vi.mocked(confirmAction);
81
+ const mockedGetConflictFiles = vi.mocked(getConflictFiles);
82
+ const mockedGitAddFiles = vi.mocked(gitAddFiles);
83
+ const mockedGitMergeContinue = vi.mocked(gitMergeContinue);
84
+ const mockedPrintInfo = vi.mocked(printInfo);
85
+ const mockedPrintSuccess = vi.mocked(printSuccess);
86
+ const mockedPrintWarning = vi.mocked(printWarning);
87
+
88
+ beforeEach(() => {
89
+ vi.clearAllMocks();
90
+ mockedGetConfigValue.mockReturnValue('ask');
91
+ });
92
+
93
+ describe('buildConflictResolvePrompt', () => {
94
+ it('生成纯指令性 prompt(无参数)', () => {
95
+ const prompt = buildConflictResolvePrompt();
96
+
97
+ expect(prompt).toContain('Git 合并冲突解决专家');
98
+ expect(prompt).toContain('git status');
99
+ expect(prompt).toContain('git log');
100
+ expect(prompt).toContain('冲突标记');
101
+ expect(prompt).toContain('请直接开始');
102
+ });
103
+ });
104
+
105
+ describe('invokeClaudeForConflictResolve', () => {
106
+ it('成功调用 execFileSync 并返回输出', () => {
107
+ mockedExecFileSync.mockReturnValue('冲突已解决');
108
+
109
+ const result = invokeClaudeForConflictResolve('test prompt', '/repo');
110
+
111
+ expect(result).toBe('冲突已解决');
112
+ expect(mockedExecFileSync).toHaveBeenCalledWith(
113
+ 'claude',
114
+ ['-p', 'test prompt', '--permission-mode', 'bypassPermissions'],
115
+ expect.objectContaining({ cwd: '/repo' }),
116
+ );
117
+ });
118
+
119
+ it('Claude Code 执行失败时抛出 ClawtError', () => {
120
+ mockedExecFileSync.mockImplementation(() => { throw new Error('command failed'); });
121
+
122
+ expect(() => invokeClaudeForConflictResolve('test prompt', '/repo')).toThrow(ClawtError);
123
+ });
124
+ });
125
+
126
+ describe('resolveConflictsWithAI', () => {
127
+ it('无冲突文件时直接返回 true', () => {
128
+ mockedGetConflictFiles.mockReturnValue([]);
129
+
130
+ const result = resolveConflictsWithAI('main', 'feature', '/repo');
131
+
132
+ expect(result).toBe(true);
133
+ });
134
+
135
+ it('AI 成功解决所有冲突后 git add 并 merge continue', () => {
136
+ mockedGetConflictFiles
137
+ .mockReturnValueOnce(['src/a.ts']) // 初始冲突文件
138
+ .mockReturnValueOnce([]); // AI 解决后无冲突
139
+ mockedExecFileSync.mockReturnValue('resolved');
140
+
141
+ const result = resolveConflictsWithAI('main', 'feature', '/repo');
142
+
143
+ expect(result).toBe(true);
144
+ expect(mockedGitAddFiles).toHaveBeenCalledWith(['src/a.ts'], '/repo');
145
+ expect(mockedGitMergeContinue).toHaveBeenCalledWith('/repo');
146
+ expect(mockedPrintSuccess).toHaveBeenCalled();
147
+ });
148
+
149
+ it('AI 部分解决冲突时 git add 已解决的文件并返回 false', () => {
150
+ mockedGetConflictFiles
151
+ .mockReturnValueOnce(['src/a.ts', 'src/b.ts']) // 初始 2 个冲突
152
+ .mockReturnValueOnce(['src/b.ts']); // AI 后还剩 1 个
153
+ mockedExecFileSync.mockReturnValue('partial');
154
+
155
+ const result = resolveConflictsWithAI('main', 'feature', '/repo');
156
+
157
+ expect(result).toBe(false);
158
+ expect(mockedGitAddFiles).toHaveBeenCalledWith(['src/a.ts'], '/repo');
159
+ expect(mockedPrintWarning).toHaveBeenCalled();
160
+ });
161
+
162
+ it('AI 调用失败时输出警告并返回 false', () => {
163
+ mockedGetConflictFiles.mockReturnValueOnce(['src/a.ts']);
164
+ mockedExecFileSync.mockImplementation(() => { throw new Error('timeout'); });
165
+
166
+ const result = resolveConflictsWithAI('main', 'feature', '/repo');
167
+
168
+ expect(result).toBe(false);
169
+ expect(mockedPrintWarning).toHaveBeenCalled();
170
+ expect(mockedGitAddFiles).not.toHaveBeenCalled();
171
+ expect(mockedGitMergeContinue).not.toHaveBeenCalled();
172
+ });
173
+ });
174
+
175
+ describe('determineConflictResolveMode', () => {
176
+ it('--auto 参数优先返回 auto', () => {
177
+ mockedGetConfigValue.mockReturnValue('manual');
178
+
179
+ expect(determineConflictResolveMode(true)).toBe('auto');
180
+ });
181
+
182
+ it('配置为 auto 时返回 auto', () => {
183
+ mockedGetConfigValue.mockReturnValue('auto');
184
+
185
+ expect(determineConflictResolveMode()).toBe('auto');
186
+ });
187
+
188
+ it('配置为 manual 时返回 manual', () => {
189
+ mockedGetConfigValue.mockReturnValue('manual');
190
+
191
+ expect(determineConflictResolveMode()).toBe('manual');
192
+ });
193
+
194
+ it('配置为 ask 时返回 ask', () => {
195
+ mockedGetConfigValue.mockReturnValue('ask');
196
+
197
+ expect(determineConflictResolveMode()).toBe('ask');
198
+ });
199
+
200
+ it('默认返回 ask', () => {
201
+ mockedGetConfigValue.mockReturnValue('unknown');
202
+
203
+ expect(determineConflictResolveMode()).toBe('ask');
204
+ });
205
+ });
206
+
207
+ describe('handleMergeConflict', () => {
208
+ it('manual 模式抛出 ClawtError', async () => {
209
+ mockedGetConfigValue.mockReturnValue('manual');
210
+
211
+ await expect(
212
+ handleMergeConflict('main', 'feature', '/repo'),
213
+ ).rejects.toThrow(ClawtError);
214
+ });
215
+
216
+ it('auto 模式直接调用 AI 解决', async () => {
217
+ mockedGetConflictFiles
218
+ .mockReturnValueOnce(['src/a.ts'])
219
+ .mockReturnValueOnce([]);
220
+ mockedExecFileSync.mockReturnValue('resolved');
221
+
222
+ const result = await handleMergeConflict('main', 'feature', '/repo', true);
223
+
224
+ expect(result).toBe(true);
225
+ expect(mockedConfirmAction).not.toHaveBeenCalled();
226
+ });
227
+
228
+ it('ask 模式用户选择使用 AI 时调用 AI 解决', async () => {
229
+ mockedGetConfigValue.mockReturnValue('ask');
230
+ mockedConfirmAction.mockResolvedValue(true);
231
+ mockedGetConflictFiles
232
+ .mockReturnValueOnce(['src/a.ts'])
233
+ .mockReturnValueOnce([]);
234
+ mockedExecFileSync.mockReturnValue('resolved');
235
+
236
+ const result = await handleMergeConflict('main', 'feature', '/repo');
237
+
238
+ expect(result).toBe(true);
239
+ expect(mockedConfirmAction).toHaveBeenCalled();
240
+ });
241
+
242
+ it('ask 模式用户拒绝 AI 时抛出 ClawtError', async () => {
243
+ mockedGetConfigValue.mockReturnValue('ask');
244
+ mockedConfirmAction.mockResolvedValue(false);
245
+
246
+ await expect(
247
+ handleMergeConflict('main', 'feature', '/repo'),
248
+ ).rejects.toThrow(ClawtError);
249
+ });
250
+ });
@@ -1,179 +0,0 @@
1
- /** 提示消息模板 */
2
- export const MESSAGES = {
3
- /** 不在主 worktree 根目录 */
4
- NOT_MAIN_WORKTREE: '请在主 worktree 的根目录下执行 clawt',
5
- /** Git 未安装 */
6
- GIT_NOT_INSTALLED: 'Git 未安装或不在 PATH 中,请先安装 Git',
7
- /** Claude Code CLI 未安装 */
8
- CLAUDE_NOT_INSTALLED: 'Claude Code CLI 未安装,请先安装:npm install -g @anthropic-ai/claude-code',
9
- /** 分支已存在 */
10
- BRANCH_EXISTS: (name: string) => `分支 ${name} 已存在,无法创建`,
11
- /** 分支已存在时提示使用 resume */
12
- BRANCH_EXISTS_USE_RESUME: (name: string) =>
13
- `分支 ${name} 已存在,请使用 clawt resume -b ${name} 恢复会话`,
14
- /** 分支名清理后为空 */
15
- BRANCH_NAME_EMPTY: (original: string) =>
16
- `分支名 "${original}" 中不包含合法字符,无法创建分支`,
17
- /** 分支名被转换 */
18
- BRANCH_SANITIZED: (original: string, sanitized: string) =>
19
- `分支名已转换: ${original} → ${sanitized}`,
20
- /** worktree 创建成功 */
21
- WORKTREE_CREATED: (count: number) => `✓ 已创建 ${count} 个 worktree`,
22
- /** worktree 移除成功 */
23
- WORKTREE_REMOVED: (path: string) => `✓ 已移除 worktree: ${path}`,
24
- /** 没有 worktree */
25
- NO_WORKTREES: '(无 worktree)',
26
- /** 目标 worktree 不存在 */
27
- WORKTREE_NOT_FOUND: (name: string) => `worktree ${name} 不存在`,
28
- /** 主 worktree 有未提交更改 */
29
- MAIN_WORKTREE_DIRTY: '主 worktree 有未提交的更改,请先处理',
30
- /** 目标 worktree 无更改 */
31
- TARGET_WORKTREE_CLEAN: '该 worktree 的分支上没有任何更改,无需验证',
32
- /** validate 成功 */
33
- VALIDATE_SUCCESS: (branch: string) =>
34
- `✓ 已将分支 ${branch} 的变更应用到主 worktree\n 可以开始验证了`,
35
- /** merge 成功 */
36
- MERGE_SUCCESS: (branch: string, message: string, pushed: boolean) =>
37
- `✓ 分支 ${branch} 已成功合并到当前分支\n 提交信息: ${message}${pushed ? '\n 已推送到远程仓库' : ''}`,
38
- /** merge 成功(无提交信息,目标 worktree 已提交过) */
39
- MERGE_SUCCESS_NO_MESSAGE: (branch: string, pushed: boolean) =>
40
- `✓ 分支 ${branch} 已成功合并到当前分支${pushed ? '\n 已推送到远程仓库' : ''}`,
41
- /** merge 冲突 */
42
- MERGE_CONFLICT: '合并存在冲突,请手动处理:\n 解决冲突后执行 git add . && git merge --continue',
43
- /** merge 后清理 worktree 和分支成功 */
44
- WORKTREE_CLEANED: (branch: string) => `✓ 已清理 worktree 和分支: ${branch}`,
45
- /** 请提供提交信息 */
46
- COMMIT_MESSAGE_REQUIRED: '请提供提交信息(-m 参数)',
47
- /** 目标 worktree 有未提交修改但未指定 -m */
48
- TARGET_WORKTREE_DIRTY_NO_MESSAGE: (worktreePath: string) =>
49
- `${worktreePath} 有未提交的修改,请通过 -m 参数提供提交信息`,
50
- /** 目标 worktree 既干净又无本地提交 */
51
- TARGET_WORKTREE_NO_CHANGES: '目标 worktree 没有任何可合并的变更(工作区干净且无本地提交)',
52
- /** 检测到用户中断 */
53
- INTERRUPTED: '检测到退出指令,已停止 Claude Code 任务',
54
- /** 中断后自动清理完成 */
55
- INTERRUPT_AUTO_CLEANED: (count: number) => `✓ 已自动清理 ${count} 个 worktree 和对应分支`,
56
- /** 中断后手动确认清理 */
57
- INTERRUPT_CONFIRM_CLEANUP: '是否移除刚刚创建的 worktree 和对应分支?',
58
- /** 中断后清理完成 */
59
- INTERRUPT_CLEANED: (count: number) => `✓ 已清理 ${count} 个 worktree 和对应分支`,
60
- /** 中断后保留 worktree */
61
- INTERRUPT_KEPT: '已保留 worktree,可稍后使用 clawt remove 手动清理',
62
- /** 配置文件损坏,已重新生成默认配置 */
63
- CONFIG_CORRUPTED: '配置文件损坏或无法解析,已重新生成默认配置',
64
- /** 配置已恢复为默认值 */
65
- CONFIG_RESET_SUCCESS: '✓ 配置已恢复为默认值',
66
- /** 分隔线 */
67
- SEPARATOR: '────────────────────────────────────────',
68
- /** 粗分隔线 */
69
- DOUBLE_SEPARATOR: '════════════════════════════════════════',
70
- /** 创建数量参数无效 */
71
- INVALID_COUNT: (value: string) => `无效的创建数量: "${value}",请输入正整数`,
72
- /** worktree 状态获取失败 */
73
- WORKTREE_STATUS_UNAVAILABLE: '(状态不可用)',
74
- /** 增量 validate 成功提示 */
75
- INCREMENTAL_VALIDATE_SUCCESS: (branch: string) =>
76
- `✓ 已将分支 ${branch} 的最新变更应用到主 worktree(增量模式)\n 暂存区 = 上次快照,工作目录 = 最新变更`,
77
- /** 增量 validate 降级为全量模式提示 */
78
- INCREMENTAL_VALIDATE_FALLBACK: '增量对比失败,已降级为全量模式',
79
- /** validate 状态已清理 */
80
- VALIDATE_CLEANED: (branch: string) => `✓ 分支 ${branch} 的 validate 状态已清理`,
81
- /** merge 命令检测到 validate 状态的提示 */
82
- MERGE_VALIDATE_STATE_HINT: (branch: string) =>
83
- `主 worktree 可能存在 validate 残留状态,可先执行 clawt validate -b ${branch} --clean 清理`,
84
- /** sync 自动保存未提交变更 */
85
- SYNC_AUTO_COMMITTED: (branch: string) =>
86
- `已自动保存 ${branch} 分支的未提交变更`,
87
- /** sync 开始合并 */
88
- SYNC_MERGING: (targetBranch: string, mainBranch: string) =>
89
- `正在将 ${mainBranch} 合并到 ${targetBranch} ...`,
90
- /** sync 成功 */
91
- SYNC_SUCCESS: (targetBranch: string, mainBranch: string) =>
92
- `✓ 已将 ${mainBranch} 的最新代码同步到 ${targetBranch}`,
93
- /** sync 冲突 */
94
- SYNC_CONFLICT: (worktreePath: string) =>
95
- `合并存在冲突,请进入目标 worktree 手动解决:\n cd ${worktreePath}\n 解决冲突后执行 git add . && git merge --continue\n clawt validate -b <branch> 验证变更`,
96
- /** validate patch apply 失败,提示用户同步主分支 */
97
- VALIDATE_PATCH_APPLY_FAILED: (branch: string) =>
98
- `变更迁移失败:目标分支与主分支差异过大\n 请先执行 clawt sync -b ${branch} 同步主分支后重试`,
99
- /** merge 检测到 auto-save 提交,提示用户是否压缩 */
100
- MERGE_SQUASH_PROMPT: '检测到 sync 产生的临时提交,是否将所有提交压缩为一个?\n 压缩后变更将保留在目标worktree的暂存区,需要重新提交(可使用 Claude Code Cli或其他工具生成提交信息)',
101
- /** squash 完成且通过 -m 直接提交后的提示 */
102
- MERGE_SQUASH_COMMITTED: (branch: string) =>
103
- `✓ 已将分支 ${branch} 的所有提交压缩为一个`,
104
- /** squash 完成但未提供 -m,提示用户自行提交 */
105
- MERGE_SQUASH_PENDING: (worktreePath: string, branch: string) =>
106
- `✓ 已将所有提交压缩到暂存区\n 请在目标 worktree 中提交后重新执行 merge:\n cd ${worktreePath}\n 提交完成后执行:clawt merge -b ${branch}`,
107
- /** 用户取消破坏性操作 */
108
- DESTRUCTIVE_OP_CANCELLED: '已取消操作',
109
- /** reset 成功 */
110
- RESET_SUCCESS: '✓ 主 worktree 工作区和暂存区已重置',
111
- /** reset 时工作区和暂存区已干净 */
112
- RESET_ALREADY_CLEAN: '主 worktree 工作区和暂存区已是干净状态,无需重置',
113
- /** 批量移除部分失败 */
114
- REMOVE_PARTIAL_FAILURE: (failures: Array<{ path: string; error: string }>) =>
115
- `以下 worktree 移除失败:\n${failures.map((f) => ` ✗ ${f.path}: ${f.error}`).join('\n')}`,
116
- /** resume 无可用 worktree */
117
- RESUME_NO_WORKTREES: '当前项目没有可用的 worktree,请先通过 clawt run 或 clawt create 创建',
118
- /** resume 模糊匹配无结果,列出可用分支 */
119
- RESUME_NO_MATCH: (name: string, branches: string[]) =>
120
- `未找到与 "${name}" 匹配的分支\n 可用分支:\n${branches.map((b) => ` - ${b}`).join('\n')}`,
121
- /** resume 交互选择提示 */
122
- RESUME_SELECT_BRANCH: '请选择要恢复的分支',
123
- /** resume 模糊匹配到多个结果提示 */
124
- RESUME_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
125
- /** validate 无可用 worktree */
126
- VALIDATE_NO_WORKTREES: '当前项目没有可用的 worktree,请先通过 clawt run 或 clawt create 创建',
127
- /** validate 模糊匹配无结果,列出可用分支 */
128
- VALIDATE_NO_MATCH: (name: string, branches: string[]) =>
129
- `未找到与 "${name}" 匹配的分支\n 可用分支:\n${branches.map((b) => ` - ${b}`).join('\n')}`,
130
- /** validate 交互选择提示 */
131
- VALIDATE_SELECT_BRANCH: '请选择要验证的分支',
132
- /** validate 模糊匹配到多个结果提示 */
133
- VALIDATE_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
134
- /** merge 无可用 worktree */
135
- MERGE_NO_WORKTREES: '当前项目没有可用的 worktree,请先通过 clawt run 或 clawt create 创建',
136
- /** merge 模糊匹配无结果,列出可用分支 */
137
- MERGE_NO_MATCH: (name: string, branches: string[]) =>
138
- `未找到与 "${name}" 匹配的分支\n 可用分支:\n${branches.map((b) => ` - ${b}`).join('\n')}`,
139
- /** merge 交互选择提示 */
140
- MERGE_SELECT_BRANCH: '请选择要合并的分支',
141
- /** merge 模糊匹配到多个结果提示 */
142
- MERGE_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
143
- /** sync 无可用 worktree */
144
- SYNC_NO_WORKTREES: '当前项目没有可用的 worktree,请先通过 clawt run 或 clawt create 创建',
145
- /** sync 模糊匹配无结果,列出可用分支 */
146
- SYNC_NO_MATCH: (name: string, branches: string[]) =>
147
- `未找到与 "${name}" 匹配的分支\n 可用分支:\n${branches.map((b) => ` - ${b}`).join('\n')}`,
148
- /** sync 交互选择提示 */
149
- SYNC_SELECT_BRANCH: '请选择要同步的分支',
150
- /** sync 模糊匹配到多个结果提示 */
151
- SYNC_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
152
- /** status 命令标题 */
153
- STATUS_TITLE: (projectName: string) => `项目状态总览: ${projectName}`,
154
- /** status 主 worktree 区块标题 */
155
- STATUS_MAIN_SECTION: '主 Worktree',
156
- /** status worktrees 区块标题 */
157
- STATUS_WORKTREES_SECTION: 'Worktree 列表',
158
- /** status 快照区块标题 */
159
- STATUS_SNAPSHOTS_SECTION: '未清理的 Validate 快照',
160
- /** status 无 worktree */
161
- STATUS_NO_WORKTREES: '(无活跃 worktree)',
162
- /** status 无未清理快照 */
163
- STATUS_NO_SNAPSHOTS: '(无未清理的快照)',
164
- /** status 变更状态:已提交 */
165
- STATUS_CHANGE_COMMITTED: '已提交',
166
- /** status 变更状态:未提交修改 */
167
- STATUS_CHANGE_UNCOMMITTED: '未提交修改',
168
- /** status 变更状态:合并冲突 */
169
- STATUS_CHANGE_CONFLICT: '合并冲突',
170
- /** status 变更状态:无变更 */
171
- STATUS_CHANGE_CLEAN: '无变更',
172
- /** status 快照对应 worktree 已不存在 */
173
- STATUS_SNAPSHOT_ORPHANED: '(对应 worktree 已不存在)',
174
- /** merge 后 pull 冲突 */
175
- PULL_CONFLICT:
176
- '自动 pull 时发生冲突,merge 已完成但远程同步失败\n 请手动解决冲突:\n 解决冲突后执行 git add . && git commit\n 然后执行 git push 推送到远程',
177
- /** push 失败 */
178
- PUSH_FAILED: '自动 push 失败,merge 和 pull 已完成\n 请手动执行 git push',
179
- } as const;