clawt 3.9.13 → 3.10.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/README.md +3 -0
- package/README.zh-CN.md +3 -0
- package/dist/index.js +1935 -592
- package/dist/postinstall.js +1626 -283
- package/docs/config-file.md +2 -0
- package/docs/config.md +2 -1
- package/docs/init.md +3 -2
- package/docs/project-config.md +10 -1
- package/docs/spec.md +69 -2
- package/package.json +1 -1
- package/src/commands/alias.ts +5 -4
- package/src/commands/completion.ts +2 -1
- package/src/commands/config.ts +25 -7
- package/src/commands/cover-validate.ts +3 -2
- package/src/commands/create.ts +8 -7
- package/src/commands/home.ts +2 -1
- package/src/commands/init.ts +13 -6
- package/src/commands/list.ts +6 -4
- package/src/commands/merge.ts +8 -7
- package/src/commands/projects.ts +5 -3
- package/src/commands/remove.ts +7 -6
- package/src/commands/reset.ts +3 -2
- package/src/commands/resume.ts +10 -7
- package/src/commands/run.ts +8 -7
- package/src/commands/status.ts +16 -11
- package/src/commands/sync.ts +4 -3
- package/src/commands/tasks.ts +8 -6
- package/src/commands/validate.ts +7 -6
- package/src/constants/ai-prompts.ts +11 -11
- package/src/constants/config.ts +30 -0
- package/src/constants/index.ts +3 -2
- package/src/constants/messages/alias.ts +44 -14
- package/src/constants/messages/cli-descriptions.ts +91 -0
- package/src/constants/messages/common.ts +221 -36
- package/src/constants/messages/completion.ts +43 -14
- package/src/constants/messages/config.ts +61 -18
- package/src/constants/messages/cover-validate.ts +43 -14
- package/src/constants/messages/create.ts +16 -5
- package/src/constants/messages/home.ts +19 -6
- package/src/constants/messages/index.ts +2 -0
- package/src/constants/messages/init.ts +45 -14
- package/src/constants/messages/interactive-panel.ts +183 -29
- package/src/constants/messages/merge.ts +140 -38
- package/src/constants/messages/post-create.ts +59 -19
- package/src/constants/messages/projects.ts +51 -14
- package/src/constants/messages/remove.ts +50 -15
- package/src/constants/messages/reset.ts +14 -4
- package/src/constants/messages/resume.ts +116 -19
- package/src/constants/messages/run.ts +165 -35
- package/src/constants/messages/status.ts +84 -23
- package/src/constants/messages/sync.ts +54 -17
- package/src/constants/messages/tasks.ts +21 -7
- package/src/constants/messages/update.ts +35 -11
- package/src/constants/messages/validate.ts +218 -57
- package/src/constants/progress.ts +17 -6
- package/src/constants/project-config.ts +17 -0
- package/src/constants/prompt.ts +18 -2
- package/src/constants/tasks-template.ts +56 -2
- package/src/hooks/post-create.ts +5 -2
- package/src/index.ts +6 -5
- package/src/types/config.ts +2 -0
- package/src/utils/alias.ts +2 -1
- package/src/utils/claude.ts +10 -9
- package/src/utils/config-strategy.ts +3 -3
- package/src/utils/dry-run.ts +2 -2
- package/src/utils/formatter.ts +18 -11
- package/src/utils/i18n.ts +63 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/interactive-panel-render.ts +6 -3
- package/src/utils/progress-render.ts +11 -9
- package/src/utils/prompt.ts +2 -1
- package/src/utils/task-executor.ts +10 -7
- package/src/utils/task-file.ts +2 -1
- package/src/utils/terminal.ts +9 -9
- package/src/utils/ui-prompts.ts +4 -3
- package/src/utils/update-checker.ts +1 -1
- package/src/utils/validate-branch.ts +16 -9
- package/src/utils/validate-core.ts +2 -1
- package/src/utils/validate-runner.ts +2 -2
- package/src/utils/worktree-matcher.ts +9 -7
- package/tests/unit/commands/alias.test.ts +4 -0
- package/tests/unit/commands/completion.test.ts +14 -0
- package/tests/unit/commands/config.test.ts +61 -28
- package/tests/unit/commands/cover-validate.test.ts +13 -2
- package/tests/unit/commands/init.test.ts +6 -2
- package/tests/unit/commands/merge.test.ts +14 -0
- package/tests/unit/commands/run.test.ts +17 -0
- package/tests/unit/commands/tasks.test.ts +39 -9
- package/tests/unit/constants/config.test.ts +16 -1
- package/tests/unit/constants/messages-post-create.test.ts +93 -1
- package/tests/unit/constants/messages.test.ts +85 -1
- package/tests/unit/hooks/post-create.test.ts +32 -0
- package/tests/unit/utils/alias.test.ts +14 -0
- package/tests/unit/utils/claude.test.ts +24 -4
- package/tests/unit/utils/config-strategy.test.ts +21 -0
- package/tests/unit/utils/conflict-resolver.test.ts +24 -4
- package/tests/unit/utils/formatter.test.ts +21 -0
- package/tests/unit/utils/i18n.test.ts +91 -0
- package/tests/unit/utils/progress.test.ts +39 -18
- package/tests/unit/utils/prompt.test.ts +25 -2
- package/tests/unit/utils/task-file.test.ts +73 -10
- package/tests/unit/utils/terminal-cmux.test.ts +19 -4
- package/tests/unit/utils/update-checker.test.ts +2 -0
- package/tests/unit/utils/validate-branch.test.ts +26 -1
- package/tests/unit/utils/validation.test.ts +2 -2
- package/tests/unit/utils/worktree-matcher.test.ts +2 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { parseTaskFile, loadTaskFile, parseTasksFromOptions } from '../../../src/utils/task-file.js';
|
|
3
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
4
3
|
|
|
5
4
|
vi.mock('node:fs', () => ({
|
|
@@ -7,6 +6,25 @@ vi.mock('node:fs', () => ({
|
|
|
7
6
|
readFileSync: vi.fn(),
|
|
8
7
|
}));
|
|
9
8
|
|
|
9
|
+
vi.mock('../../../src/logger/index.js', () => ({
|
|
10
|
+
logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
// mock i18n 模块,避免循环依赖导致 currentLanguage 未初始化
|
|
14
|
+
// 返回 'zh-CN' 以匹配测试中的中文断言
|
|
15
|
+
vi.mock('../../../src/utils/i18n.js', () => ({
|
|
16
|
+
getCurrentLanguage: vi.fn().mockReturnValue('zh-CN'),
|
|
17
|
+
resetLanguageCache: vi.fn(),
|
|
18
|
+
setCurrentLanguage: vi.fn(),
|
|
19
|
+
createMessages: vi.fn((i18nMap: Record<string, { en: any; 'zh-CN': any }>) => {
|
|
20
|
+
const result: any = {};
|
|
21
|
+
for (const key of Object.keys(i18nMap)) {
|
|
22
|
+
result[key] = i18nMap[key]['zh-CN'];
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
10
28
|
vi.mock('../../../src/errors/index.js', () => ({
|
|
11
29
|
ClawtError: class ClawtError extends Error {
|
|
12
30
|
exitCode: number;
|
|
@@ -17,15 +35,60 @@ vi.mock('../../../src/errors/index.js', () => ({
|
|
|
17
35
|
},
|
|
18
36
|
}));
|
|
19
37
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
38
|
+
// mock constants/index.js,使用固定的中文 mock 来匹配测试断言中的中文错误消息
|
|
39
|
+
// 同时保留必要的路径常量导出(避免 logger 等模块加载时报错)
|
|
40
|
+
vi.mock('../../../src/constants/index.js', async (importOriginal) => {
|
|
41
|
+
// 仅从 paths.js 获取路径常量,不触发 messages 模块加载
|
|
42
|
+
const { CLAWT_HOME, CONFIG_PATH, LOGS_DIR, WORKTREES_DIR, VALIDATE_SNAPSHOTS_DIR, CLAUDE_PROJECTS_DIR, UPDATE_CHECK_PATH, PROJECTS_CONFIG_DIR } = await import('../../../src/constants/paths.js');
|
|
43
|
+
const { INVALID_BRANCH_CHARS, VALIDATE_BRANCH_PREFIX } = await import('../../../src/constants/branch.js');
|
|
44
|
+
const { EXIT_CODES } = await import('../../../src/constants/exitCodes.js');
|
|
45
|
+
const { VALID_TERMINAL_APPS, ITERM2_APP_PATH, ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, PASTE_THRESHOLD_MS } = await import('../../../src/constants/terminal.js');
|
|
46
|
+
return {
|
|
47
|
+
CLAWT_HOME,
|
|
48
|
+
CONFIG_PATH,
|
|
49
|
+
LOGS_DIR,
|
|
50
|
+
WORKTREES_DIR,
|
|
51
|
+
VALIDATE_SNAPSHOTS_DIR,
|
|
52
|
+
CLAUDE_PROJECTS_DIR,
|
|
53
|
+
UPDATE_CHECK_PATH,
|
|
54
|
+
PROJECTS_CONFIG_DIR,
|
|
55
|
+
INVALID_BRANCH_CHARS,
|
|
56
|
+
VALIDATE_BRANCH_PREFIX,
|
|
57
|
+
EXIT_CODES,
|
|
58
|
+
VALID_TERMINAL_APPS,
|
|
59
|
+
ITERM2_APP_PATH,
|
|
60
|
+
ENABLE_BRACKETED_PASTE,
|
|
61
|
+
DISABLE_BRACKETED_PASTE,
|
|
62
|
+
PASTE_THRESHOLD_MS,
|
|
63
|
+
// 使用固定的中文 mock MESSAGES
|
|
64
|
+
MESSAGES: {
|
|
65
|
+
TASK_FILE_NOT_FOUND: (path: string) => `任务文件不存在: ${path}`,
|
|
66
|
+
TASK_FILE_EMPTY: '任务文件中没有解析到有效任务',
|
|
67
|
+
TASK_FILE_MISSING_BRANCH: (blockIndex: number) => `第 ${blockIndex} 个任务块缺少分支名`,
|
|
68
|
+
TASK_FILE_MISSING_TASK: (branch: string) => `分支 ${branch} 缺少任务描述`,
|
|
69
|
+
TASK_FILE_MISSING_TASK_BY_INDEX: (blockIndex: number) => `第 ${blockIndex} 个任务块缺少任务描述`,
|
|
70
|
+
},
|
|
71
|
+
// 其他测试不需要的导出设为空对象或空字符串
|
|
72
|
+
DEFAULT_CONFIG: {},
|
|
73
|
+
CONFIG_DESCRIPTIONS: {},
|
|
74
|
+
CONFIG_DEFINITIONS: {},
|
|
75
|
+
CLAUDE_CODE_ENTRYPOINT_VALUE: 'cli',
|
|
76
|
+
getI18nConfigDescriptions: vi.fn(),
|
|
77
|
+
AUTO_SAVE_COMMIT_MESSAGE_PREFIX: '[clawt-auto-save]',
|
|
78
|
+
DEBUG_LOG_PREFIX: '[clawt]',
|
|
79
|
+
DEBUG_TIMESTAMP_FORMAT: 'YYYY-MM-DD HH:mm:ss',
|
|
80
|
+
UPDATE_CHECK_INTERVAL_MS: 86400000,
|
|
81
|
+
NPM_REGISTRY_URL: 'https://registry.npmjs.org',
|
|
82
|
+
NPM_REGISTRY_TIMEOUT_MS: 5000,
|
|
83
|
+
PACKAGE_NAME: 'clawt',
|
|
84
|
+
CONFLICT_RESOLVE_PROMPT: '',
|
|
85
|
+
CONFIG_ALIAS_DISABLED_HINT: '',
|
|
86
|
+
UPDATE_MESSAGES: {},
|
|
87
|
+
UPDATE_COMMANDS: {},
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
import { parseTaskFile, loadTaskFile, parseTasksFromOptions } from '../../../src/utils/task-file.js';
|
|
29
92
|
|
|
30
93
|
const mockedExistsSync = vi.mocked(existsSync);
|
|
31
94
|
const mockedReadFileSync = vi.mocked(readFileSync);
|
|
@@ -20,6 +20,21 @@ vi.mock('../../../src/utils/config.js', () => ({
|
|
|
20
20
|
getConfigValue: vi.fn(),
|
|
21
21
|
}));
|
|
22
22
|
|
|
23
|
+
// mock i18n 模块,避免循环依赖导致 currentLanguage 未初始化
|
|
24
|
+
// 返回 'en' 以匹配测试中的英文断言
|
|
25
|
+
vi.mock('../../../src/utils/i18n.js', () => ({
|
|
26
|
+
getCurrentLanguage: vi.fn().mockReturnValue('en'),
|
|
27
|
+
resetLanguageCache: vi.fn(),
|
|
28
|
+
setCurrentLanguage: vi.fn(),
|
|
29
|
+
createMessages: vi.fn((i18nMap: Record<string, { en: any; 'zh-CN': any }>) => {
|
|
30
|
+
const result: any = {};
|
|
31
|
+
for (const key of Object.keys(i18nMap)) {
|
|
32
|
+
result[key] = i18nMap[key]['en'];
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}),
|
|
36
|
+
}));
|
|
37
|
+
|
|
23
38
|
import { execFileSync } from 'node:child_process';
|
|
24
39
|
import { existsSync } from 'node:fs';
|
|
25
40
|
import {
|
|
@@ -180,7 +195,7 @@ describe('cmux surface 创建', () => {
|
|
|
180
195
|
mockedGetConfigValue.mockReturnValue('cmux');
|
|
181
196
|
|
|
182
197
|
expect(() => openCommandInNewTerminalTab('claude', 'test-title')).toThrow(
|
|
183
|
-
|
|
198
|
+
/Not currently in a cmux environment/
|
|
184
199
|
);
|
|
185
200
|
});
|
|
186
201
|
|
|
@@ -192,7 +207,7 @@ describe('cmux surface 创建', () => {
|
|
|
192
207
|
mockedExecFileSync.mockReturnValueOnce('invalid output format');
|
|
193
208
|
|
|
194
209
|
expect(() => openCommandInNewTerminalTab('claude', 'test-title')).toThrow(
|
|
195
|
-
|
|
210
|
+
/Failed to parse cmux new-split output/
|
|
196
211
|
);
|
|
197
212
|
});
|
|
198
213
|
|
|
@@ -207,7 +222,7 @@ describe('cmux surface 创建', () => {
|
|
|
207
222
|
});
|
|
208
223
|
|
|
209
224
|
expect(() => openCommandInNewTerminalTab('claude', 'test-title')).toThrow(
|
|
210
|
-
|
|
225
|
+
/Failed to create surface in cmux/
|
|
211
226
|
);
|
|
212
227
|
});
|
|
213
228
|
|
|
@@ -215,7 +230,7 @@ describe('cmux surface 创建', () => {
|
|
|
215
230
|
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
216
231
|
|
|
217
232
|
expect(() => openCommandInNewTerminalTab('claude', 'test-title')).toThrow(
|
|
218
|
-
|
|
233
|
+
/Batch resume is only supported on macOS/
|
|
219
234
|
);
|
|
220
235
|
});
|
|
221
236
|
});
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
|
|
3
|
+
// mock i18n 模块,使 getCurrentLanguage 返回 'zh-CN' 以匹配中文断言
|
|
4
|
+
// 同时重写 createMessages 使其直接选择 'zh-CN' 分支,确保 constants 模块加载时生成中文消息
|
|
5
|
+
vi.mock('../../../src/utils/i18n.js', async (importOriginal) => {
|
|
6
|
+
const actual = await importOriginal<typeof import('../../../src/utils/i18n.js')>();
|
|
7
|
+
return {
|
|
8
|
+
...actual,
|
|
9
|
+
getCurrentLanguage: vi.fn().mockReturnValue('zh-CN'),
|
|
10
|
+
resetLanguageCache: vi.fn(),
|
|
11
|
+
createMessages: <T extends Record<string, { en: any; 'zh-CN': any }>>(
|
|
12
|
+
i18nMap: T,
|
|
13
|
+
) => {
|
|
14
|
+
const result: any = {};
|
|
15
|
+
for (const key of Object.keys(i18nMap)) {
|
|
16
|
+
result[key] = i18nMap[key]['zh-CN'];
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
3
23
|
// mock logger
|
|
4
24
|
vi.mock('../../../src/logger/index.js', () => ({
|
|
5
25
|
logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
@@ -16,13 +36,18 @@ vi.mock('../../../src/errors/index.js', () => ({
|
|
|
16
36
|
},
|
|
17
37
|
}));
|
|
18
38
|
|
|
19
|
-
// mock constants
|
|
39
|
+
// mock constants(补充 handleDirtyWorkingDir 和 ensureOnMainWorkBranch 使用的消息键)
|
|
20
40
|
vi.mock('../../../src/constants/index.js', () => ({
|
|
21
41
|
VALIDATE_BRANCH_PREFIX: 'clawt-validate-',
|
|
22
42
|
MESSAGES: {
|
|
23
43
|
GUARD_BRANCH_MISMATCH: (mainBranch: string, currentBranch: string) =>
|
|
24
44
|
`当前分支 ${currentBranch} 与配置的主工作分支 ${mainBranch} 不一致`,
|
|
25
45
|
DESTRUCTIVE_OP_CANCELLED: '已取消操作',
|
|
46
|
+
WORKSPACE_STILL_DIRTY: '工作区仍然不干净,请手动处理',
|
|
47
|
+
USER_CHOSE_EXIT: '用户选择退出,请手动处理工作区更改后重试',
|
|
48
|
+
UNCOMMITTED_CHANGES_ON_BRANCH: '当前分支有未提交的更改,请选择处理方式:\n',
|
|
49
|
+
SELECT_ACTION: '选择处理方式',
|
|
50
|
+
CONFIRM_CONTINUE_VALIDATE: '是否继续执行?',
|
|
26
51
|
},
|
|
27
52
|
}));
|
|
28
53
|
|
|
@@ -45,13 +45,13 @@ describe('validateMainWorktree', () => {
|
|
|
45
45
|
mockedIsInsideGitRepo.mockReturnValue(true);
|
|
46
46
|
mockedGetGitCommonDir.mockReturnValue('/path/to/.git');
|
|
47
47
|
expect(() => validateMainWorktree()).toThrow(ClawtError);
|
|
48
|
-
expect(() => validateMainWorktree()).toThrow('
|
|
48
|
+
expect(() => validateMainWorktree()).toThrow('Please run clawt in the root directory of the main worktree');
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
it('不在 git 仓库中时抛出 NOT_GIT_REPO 错误', () => {
|
|
52
52
|
mockedIsInsideGitRepo.mockReturnValue(false);
|
|
53
53
|
expect(() => validateMainWorktree()).toThrow(ClawtError);
|
|
54
|
-
expect(() => validateMainWorktree()).toThrow('
|
|
54
|
+
expect(() => validateMainWorktree()).toThrow('Current directory is not a git repository');
|
|
55
55
|
});
|
|
56
56
|
});
|
|
57
57
|
|
|
@@ -25,6 +25,8 @@ vi.mock('../../../src/utils/git.js', () => ({
|
|
|
25
25
|
// mock node:fs 的 statSync,分组测试中按需控制返回值
|
|
26
26
|
vi.mock('node:fs', () => ({
|
|
27
27
|
statSync: vi.fn().mockReturnValue({ birthtime: new Date('2026-02-27T10:00:00') }),
|
|
28
|
+
existsSync: vi.fn().mockReturnValue(false),
|
|
29
|
+
mkdirSync: vi.fn(),
|
|
28
30
|
}));
|
|
29
31
|
|
|
30
32
|
import { statSync } from 'node:fs';
|