clawt 2.19.0 → 3.0.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 +12 -2
- package/dist/index.js +626 -219
- package/dist/postinstall.js +39 -6
- package/docs/alias.md +108 -0
- package/docs/completion.md +55 -0
- package/docs/config-file.md +43 -0
- package/docs/config.md +91 -0
- package/docs/create.md +85 -0
- package/docs/init.md +65 -0
- package/docs/list.md +67 -0
- package/docs/log.md +67 -0
- package/docs/merge.md +137 -0
- package/docs/notification.md +94 -0
- package/docs/projects.md +135 -0
- package/docs/remove.md +79 -0
- package/docs/reset.md +35 -0
- package/docs/resume.md +99 -0
- package/docs/run.md +146 -0
- package/docs/spec.md +156 -1879
- package/docs/status.md +155 -0
- package/docs/sync.md +114 -0
- package/docs/update-check.md +95 -0
- package/docs/validate.md +368 -0
- package/package.json +1 -1
- package/src/commands/alias.ts +1 -1
- package/src/commands/create.ts +10 -5
- package/src/commands/init.ts +75 -0
- package/src/commands/list.ts +1 -1
- package/src/commands/merge.ts +11 -4
- package/src/commands/remove.ts +10 -3
- package/src/commands/reset.ts +3 -0
- package/src/commands/resume.ts +9 -3
- package/src/commands/run.ts +9 -3
- package/src/commands/status.ts +1 -1
- package/src/commands/sync.ts +18 -6
- package/src/commands/validate.ts +46 -52
- package/src/constants/branch.ts +3 -0
- package/src/constants/config.ts +1 -1
- package/src/constants/index.ts +3 -3
- package/src/constants/messages/completion.ts +1 -1
- package/src/constants/messages/create.ts +3 -0
- package/src/constants/messages/index.ts +2 -0
- package/src/constants/messages/init.ts +18 -0
- package/src/constants/messages/remove.ts +2 -0
- package/src/constants/messages/sync.ts +3 -0
- package/src/constants/messages/validate.ts +6 -0
- package/src/constants/paths.ts +3 -0
- package/src/constants/prompt.ts +28 -0
- package/src/index.ts +2 -0
- package/src/types/command.ts +7 -1
- package/src/types/index.ts +2 -1
- package/src/types/projectConfig.ts +5 -0
- package/src/utils/config.ts +2 -1
- package/src/utils/git.ts +18 -0
- package/src/utils/index.ts +6 -1
- package/src/utils/json.ts +67 -0
- package/src/utils/project-config.ts +77 -0
- package/src/utils/validate-branch.ts +166 -0
- package/src/utils/worktree-matcher.ts +268 -1
- package/src/utils/worktree.ts +6 -2
- package/tests/unit/commands/create.test.ts +20 -16
- package/tests/unit/commands/init.test.ts +146 -0
- package/tests/unit/commands/merge.test.ts +7 -1
- package/tests/unit/commands/remove.test.ts +4 -0
- package/tests/unit/commands/reset.test.ts +2 -0
- package/tests/unit/commands/resume.test.ts +29 -8
- package/tests/unit/commands/run.test.ts +2 -0
- package/tests/unit/commands/sync.test.ts +6 -0
- package/tests/unit/commands/validate.test.ts +13 -0
- package/tests/unit/utils/config.test.ts +2 -2
- package/tests/unit/utils/project-config.test.ts +136 -0
- package/tests/unit/utils/update-checker.test.ts +28 -7
- package/tests/unit/utils/validate-branch.test.ts +272 -0
- package/tests/unit/utils/worktree-matcher.test.ts +142 -1
- package/tests/unit/utils/worktree.test.ts +6 -0
package/src/commands/run.ts
CHANGED
|
@@ -19,21 +19,22 @@ import {
|
|
|
19
19
|
parseTasksFromOptions,
|
|
20
20
|
executeBatchTasks,
|
|
21
21
|
printDryRunPreview,
|
|
22
|
+
ensureOnMainWorkBranch,
|
|
22
23
|
} from '../utils/index.js';
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
|
-
* 注册 run 命令:批量创建 worktree
|
|
26
|
+
* 注册 run 命令:批量创建 worktree + 启动 Claude Code 执行任务(支持任务文件)
|
|
26
27
|
* @param {Command} program - Commander 实例
|
|
27
28
|
*/
|
|
28
29
|
export function registerRunCommand(program: Command): void {
|
|
29
30
|
program
|
|
30
31
|
.command('run')
|
|
31
|
-
.description('批量创建 worktree
|
|
32
|
+
.description('批量创建 worktree + 启动 Claude Code 执行任务(支持任务文件)')
|
|
32
33
|
.option('-b, --branch <branchName>', '分支名')
|
|
33
34
|
.option('--tasks <task...>', '任务列表(可多次指定),不传则在 worktree 中打开 Claude Code 交互式界面')
|
|
34
35
|
.option('-c, --concurrency <n>', '最大并发数,0 表示不限制')
|
|
35
36
|
.option('-f, --file <path>', '从任务文件读取任务列表(与 --tasks 互斥)')
|
|
36
|
-
.option('
|
|
37
|
+
.option('--dry-run', '预览模式,仅展示任务计划不实际执行')
|
|
37
38
|
.action(async (options: RunOptions) => {
|
|
38
39
|
await handleRun(options);
|
|
39
40
|
});
|
|
@@ -120,6 +121,11 @@ function handleDryRunFromFile(options: RunOptions): void {
|
|
|
120
121
|
async function handleRun(options: RunOptions): Promise<void> {
|
|
121
122
|
validateMainWorktree();
|
|
122
123
|
|
|
124
|
+
// dry-run 模式跳过项目配置前置校验
|
|
125
|
+
if (!options.dryRun) {
|
|
126
|
+
await ensureOnMainWorkBranch();
|
|
127
|
+
}
|
|
128
|
+
|
|
123
129
|
// 互斥校验:--file 和 --tasks 不能同时使用
|
|
124
130
|
if (options.file && options.tasks) {
|
|
125
131
|
throw new ClawtError(MESSAGES.FILE_AND_TASKS_CONFLICT);
|
package/src/commands/status.ts
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
export function registerStatusCommand(program: Command): void {
|
|
31
31
|
program
|
|
32
32
|
.command('status')
|
|
33
|
-
.description('
|
|
33
|
+
.description('显示项目全局状态总览(支持 --json 格式输出)')
|
|
34
34
|
.option('--json', '以 JSON 格式输出')
|
|
35
35
|
.action((options: StatusOptions) => {
|
|
36
36
|
handleStatus(options);
|
package/src/commands/sync.ts
CHANGED
|
@@ -13,18 +13,22 @@ import {
|
|
|
13
13
|
gitCommit,
|
|
14
14
|
gitMerge,
|
|
15
15
|
hasMergeConflict,
|
|
16
|
-
getCurrentBranch,
|
|
17
16
|
hasSnapshot,
|
|
18
17
|
removeSnapshot,
|
|
19
18
|
printSuccess,
|
|
20
19
|
printInfo,
|
|
21
20
|
printWarning,
|
|
22
21
|
resolveTargetWorktree,
|
|
22
|
+
requireProjectConfig,
|
|
23
|
+
getMainWorkBranch,
|
|
24
|
+
rebuildValidateBranch,
|
|
25
|
+
getValidateBranchName,
|
|
26
|
+
ensureOnMainWorkBranch,
|
|
23
27
|
} from '../utils/index.js';
|
|
24
28
|
import type { WorktreeResolveMessages } from '../utils/index.js';
|
|
25
29
|
|
|
26
30
|
/**
|
|
27
|
-
* 注册 sync 命令:将主分支最新代码同步到目标 worktree
|
|
31
|
+
* 注册 sync 命令:将主分支最新代码同步到目标 worktree(含验证分支重建)
|
|
28
32
|
* @param {Command} program - Commander 实例
|
|
29
33
|
*/
|
|
30
34
|
export function registerSyncCommand(program: Command): void {
|
|
@@ -92,10 +96,10 @@ export interface SyncResult {
|
|
|
92
96
|
* @param {string} branch - 分支名
|
|
93
97
|
* @returns {SyncResult} sync 执行结果
|
|
94
98
|
*/
|
|
95
|
-
export function executeSyncForBranch(targetWorktreePath: string, branch: string): SyncResult {
|
|
96
|
-
//
|
|
99
|
+
export async function executeSyncForBranch(targetWorktreePath: string, branch: string): Promise<SyncResult> {
|
|
100
|
+
// 从项目配置获取主工作分支名
|
|
97
101
|
const mainWorktreePath = getGitTopLevel();
|
|
98
|
-
const mainBranch =
|
|
102
|
+
const mainBranch = getMainWorkBranch();
|
|
99
103
|
|
|
100
104
|
// 检查目标 worktree 是否有未提交变更,有则自动保存
|
|
101
105
|
if (!isWorkingDirClean(targetWorktreePath)) {
|
|
@@ -119,6 +123,12 @@ export function executeSyncForBranch(targetWorktreePath: string, branch: string)
|
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
printSuccess(MESSAGES.SYNC_SUCCESS(branch, mainBranch));
|
|
126
|
+
|
|
127
|
+
// 合并成功后重建验证分支(基于最新的主分支 HEAD)
|
|
128
|
+
await rebuildValidateBranch(branch, mainWorktreePath);
|
|
129
|
+
const validateBranchName = getValidateBranchName(branch);
|
|
130
|
+
printInfo(MESSAGES.SYNC_VALIDATE_BRANCH_REBUILT(validateBranchName));
|
|
131
|
+
|
|
122
132
|
return { success: true, hasConflict: false };
|
|
123
133
|
}
|
|
124
134
|
|
|
@@ -129,6 +139,8 @@ export function executeSyncForBranch(targetWorktreePath: string, branch: string)
|
|
|
129
139
|
*/
|
|
130
140
|
async function handleSync(options: SyncOptions): Promise<void> {
|
|
131
141
|
validateMainWorktree();
|
|
142
|
+
requireProjectConfig();
|
|
143
|
+
await ensureOnMainWorkBranch();
|
|
132
144
|
|
|
133
145
|
logger.info(`sync 命令执行,分支: ${options.branch ?? '(未指定)'}`);
|
|
134
146
|
|
|
@@ -137,5 +149,5 @@ async function handleSync(options: SyncOptions): Promise<void> {
|
|
|
137
149
|
const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
|
|
138
150
|
const { path: targetWorktreePath, branch } = worktree;
|
|
139
151
|
|
|
140
|
-
executeSyncForBranch(targetWorktreePath, branch);
|
|
152
|
+
await executeSyncForBranch(targetWorktreePath, branch);
|
|
141
153
|
}
|
package/src/commands/validate.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
-
import Enquirer from 'enquirer';
|
|
3
2
|
import { logger } from '../logger/index.js';
|
|
4
3
|
import { ClawtError } from '../errors/index.js';
|
|
5
4
|
import { MESSAGES } from '../constants/index.js';
|
|
@@ -14,7 +13,6 @@ import {
|
|
|
14
13
|
isWorkingDirClean,
|
|
15
14
|
gitAddAll,
|
|
16
15
|
gitCommit,
|
|
17
|
-
gitStashPush,
|
|
18
16
|
gitRestoreStaged,
|
|
19
17
|
gitResetHard,
|
|
20
18
|
gitCleanForce,
|
|
@@ -44,6 +42,13 @@ import {
|
|
|
44
42
|
runCommandInherited,
|
|
45
43
|
parseParallelCommands,
|
|
46
44
|
runParallelCommands,
|
|
45
|
+
requireProjectConfig,
|
|
46
|
+
getValidateBranchName,
|
|
47
|
+
gitCheckout,
|
|
48
|
+
ensureOnMainWorkBranch,
|
|
49
|
+
checkBranchExists,
|
|
50
|
+
getCurrentBranch,
|
|
51
|
+
handleDirtyWorkingDir,
|
|
47
52
|
} from '../utils/index.js';
|
|
48
53
|
import type { WorktreeResolveMessages, ParallelCommandResult } from '../utils/index.js';
|
|
49
54
|
|
|
@@ -56,13 +61,13 @@ const VALIDATE_RESOLVE_MESSAGES: WorktreeResolveMessages = {
|
|
|
56
61
|
};
|
|
57
62
|
|
|
58
63
|
/**
|
|
59
|
-
* 注册 validate 命令:在主 worktree
|
|
64
|
+
* 注册 validate 命令:在主 worktree 验证其他分支的变更(通过验证分支)
|
|
60
65
|
* @param {Command} program - Commander 实例
|
|
61
66
|
*/
|
|
62
67
|
export function registerValidateCommand(program: Command): void {
|
|
63
68
|
program
|
|
64
69
|
.command('validate')
|
|
65
|
-
.description('在主 worktree 验证某个 worktree
|
|
70
|
+
.description('在主 worktree 验证某个 worktree 分支的变更(通过验证分支)')
|
|
66
71
|
.option('-b, --branch <branchName>', '要验证的分支名(支持模糊匹配,不传则列出所有分支)')
|
|
67
72
|
.option('--clean', '清理 validate 状态(重置主 worktree 并删除快照)')
|
|
68
73
|
.option('-r, --run <command>', 'validate 成功后在主 worktree 中执行的命令')
|
|
@@ -73,47 +78,11 @@ export function registerValidateCommand(program: Command): void {
|
|
|
73
78
|
|
|
74
79
|
/**
|
|
75
80
|
* 处理主 worktree 工作区有未提交更改的情况(首次 validate 时使用)
|
|
81
|
+
* 委托给通用的 handleDirtyWorkingDir 函数处理
|
|
76
82
|
* @param {string} mainWorktreePath - 主 worktree 路径
|
|
77
83
|
*/
|
|
78
84
|
async function handleDirtyMainWorktree(mainWorktreePath: string): Promise<void> {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// @ts-expect-error enquirer 类型声明未导出 Select 类,但运行时存在
|
|
82
|
-
const choice = await new Enquirer.Select({
|
|
83
|
-
message: '选择处理方式',
|
|
84
|
-
choices: [
|
|
85
|
-
{
|
|
86
|
-
name: 'reset',
|
|
87
|
-
message: 'reset (推荐) - 丢弃所有更改 (git reset --hard HEAD && git clean -fd)',
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
name: 'stash',
|
|
91
|
-
message: 'stash - 暂存更改 (git add . && git stash)',
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
name: 'exit',
|
|
95
|
-
message: 'exit - 退出,手动处理',
|
|
96
|
-
},
|
|
97
|
-
],
|
|
98
|
-
initial: 0,
|
|
99
|
-
}).run();
|
|
100
|
-
|
|
101
|
-
if (choice === 'exit') {
|
|
102
|
-
throw new ClawtError('用户选择退出');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (choice === 'reset') {
|
|
106
|
-
gitResetHard(mainWorktreePath);
|
|
107
|
-
gitCleanForce(mainWorktreePath);
|
|
108
|
-
} else if (choice === 'stash') {
|
|
109
|
-
gitAddAll(mainWorktreePath);
|
|
110
|
-
gitStashPush('clawt:auto-stash', mainWorktreePath);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// 再次检查是否干净
|
|
114
|
-
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
115
|
-
throw new ClawtError('工作区仍然不干净,请手动处理');
|
|
116
|
-
}
|
|
85
|
+
await handleDirtyWorkingDir(mainWorktreePath);
|
|
117
86
|
}
|
|
118
87
|
|
|
119
88
|
/**
|
|
@@ -186,7 +155,7 @@ async function handlePatchApplyFailure(targetWorktreePath: string, branchName: s
|
|
|
186
155
|
|
|
187
156
|
// 用户确认,执行 sync
|
|
188
157
|
printInfo(MESSAGES.VALIDATE_AUTO_SYNC_START(branchName));
|
|
189
|
-
const syncResult = executeSyncForBranch(targetWorktreePath, branchName);
|
|
158
|
+
const syncResult = await executeSyncForBranch(targetWorktreePath, branchName);
|
|
190
159
|
|
|
191
160
|
// sync 冲突提示已在 executeSyncForBranch 内部输出(SYNC_CONFLICT),此处无需重复提示
|
|
192
161
|
}
|
|
@@ -215,6 +184,8 @@ function saveCurrentSnapshotTree(mainWorktreePath: string, projectName: string,
|
|
|
215
184
|
*/
|
|
216
185
|
async function handleValidateClean(options: ValidateOptions): Promise<void> {
|
|
217
186
|
validateMainWorktree();
|
|
187
|
+
// 显式前置校验:确保项目已初始化
|
|
188
|
+
requireProjectConfig();
|
|
218
189
|
|
|
219
190
|
const projectName = getProjectName();
|
|
220
191
|
const mainWorktreePath = getGitTopLevel();
|
|
@@ -244,6 +215,9 @@ async function handleValidateClean(options: ValidateOptions): Promise<void> {
|
|
|
244
215
|
gitCleanForce(mainWorktreePath);
|
|
245
216
|
}
|
|
246
217
|
|
|
218
|
+
// 确保当前在主工作分支上
|
|
219
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
220
|
+
|
|
247
221
|
// 删除对应的快照文件
|
|
248
222
|
removeSnapshot(projectName, branchName);
|
|
249
223
|
|
|
@@ -259,11 +233,19 @@ async function handleValidateClean(options: ValidateOptions): Promise<void> {
|
|
|
259
233
|
* @param {boolean} hasUncommitted - 目标 worktree 是否有未提交修改
|
|
260
234
|
*/
|
|
261
235
|
async function handleFirstValidate(targetWorktreePath: string, mainWorktreePath: string, projectName: string, branchName: string, hasUncommitted: boolean): Promise<void> {
|
|
236
|
+
// 切换主 worktree 到验证分支
|
|
237
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
238
|
+
if (!checkBranchExists(validateBranchName)) {
|
|
239
|
+
throw new ClawtError(MESSAGES.VALIDATE_BRANCH_NOT_FOUND(validateBranchName, branchName));
|
|
240
|
+
}
|
|
241
|
+
gitCheckout(validateBranchName, mainWorktreePath);
|
|
242
|
+
|
|
262
243
|
// 通过 patch 迁移目标分支全量变更到主 worktree
|
|
263
244
|
const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
264
245
|
|
|
265
246
|
if (!result.success) {
|
|
266
|
-
// patch
|
|
247
|
+
// patch 失败,确保在主工作分支上后询问用户是否自动 sync
|
|
248
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
267
249
|
await handlePatchApplyFailure(targetWorktreePath, branchName);
|
|
268
250
|
return;
|
|
269
251
|
}
|
|
@@ -272,7 +254,7 @@ async function handleFirstValidate(targetWorktreePath: string, mainWorktreePath:
|
|
|
272
254
|
saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName);
|
|
273
255
|
|
|
274
256
|
// 结果:暂存区=空,工作目录=全量变更
|
|
275
|
-
printSuccess(MESSAGES.
|
|
257
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
276
258
|
}
|
|
277
259
|
|
|
278
260
|
/**
|
|
@@ -294,24 +276,35 @@ async function handleIncrementalValidate(targetWorktreePath: string, mainWorktre
|
|
|
294
276
|
gitCleanForce(mainWorktreePath);
|
|
295
277
|
}
|
|
296
278
|
|
|
297
|
-
// 步骤 3
|
|
279
|
+
// 步骤 3:切换到验证分支(如果已在该分支上则跳过)
|
|
280
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
281
|
+
if (!checkBranchExists(validateBranchName)) {
|
|
282
|
+
throw new ClawtError(MESSAGES.VALIDATE_BRANCH_NOT_FOUND(validateBranchName, branchName));
|
|
283
|
+
}
|
|
284
|
+
const currentBranch = getCurrentBranch(mainWorktreePath);
|
|
285
|
+
if (currentBranch !== validateBranchName) {
|
|
286
|
+
gitCheckout(validateBranchName, mainWorktreePath);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 步骤 4:通过 patch 从目标分支获取最新全量变更
|
|
298
290
|
const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
299
291
|
|
|
300
292
|
if (!result.success) {
|
|
301
|
-
// patch
|
|
293
|
+
// patch 失败,确保在主工作分支上后询问用户是否自动 sync
|
|
294
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
302
295
|
await handlePatchApplyFailure(targetWorktreePath, branchName);
|
|
303
296
|
return;
|
|
304
297
|
}
|
|
305
298
|
|
|
306
|
-
// 步骤
|
|
299
|
+
// 步骤 5:保存最新快照为 git tree 对象(同时记录当前 HEAD)
|
|
307
300
|
saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName);
|
|
308
301
|
|
|
309
|
-
// 步骤
|
|
302
|
+
// 步骤 6:将旧变更状态载入暂存区
|
|
310
303
|
try {
|
|
311
304
|
const currentHeadCommitHash = getHeadCommitHash(mainWorktreePath);
|
|
312
305
|
|
|
313
306
|
if (oldHeadCommitHash && oldHeadCommitHash !== currentHeadCommitHash) {
|
|
314
|
-
// HEAD
|
|
307
|
+
// HEAD 发生了变化:
|
|
315
308
|
// 将旧变更 patch(旧 tree 相对于旧 HEAD 的差异)重放到当前 HEAD 暂存区上,
|
|
316
309
|
// 避免新旧 tree 基准不同导致 diff 混入 HEAD 变化的内容
|
|
317
310
|
const oldHeadTreeHash = getCommitTreeHash(oldHeadCommitHash, mainWorktreePath);
|
|
@@ -324,7 +317,7 @@ async function handleIncrementalValidate(targetWorktreePath: string, mainWorktre
|
|
|
324
317
|
// 有冲突:降级为全量模式(暂存区保持为空)
|
|
325
318
|
logger.warn('旧变更 patch 与当前 HEAD 冲突,降级为全量模式');
|
|
326
319
|
printWarning(MESSAGES.INCREMENTAL_VALIDATE_FALLBACK);
|
|
327
|
-
printSuccess(MESSAGES.
|
|
320
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
328
321
|
return;
|
|
329
322
|
}
|
|
330
323
|
// oldChangePatch 为空表示旧变更为空,暂存区保持干净即可
|
|
@@ -337,7 +330,7 @@ async function handleIncrementalValidate(targetWorktreePath: string, mainWorktre
|
|
|
337
330
|
logger.warn(`增量 read-tree 失败: ${error}`);
|
|
338
331
|
printWarning(MESSAGES.INCREMENTAL_VALIDATE_FALLBACK);
|
|
339
332
|
// 降级后暂存区保持为空,工作目录为最新全量变更,与首次 validate 一致
|
|
340
|
-
printSuccess(MESSAGES.
|
|
333
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
341
334
|
return;
|
|
342
335
|
}
|
|
343
336
|
|
|
@@ -451,6 +444,7 @@ async function handleValidate(options: ValidateOptions): Promise<void> {
|
|
|
451
444
|
}
|
|
452
445
|
|
|
453
446
|
validateMainWorktree();
|
|
447
|
+
requireProjectConfig();
|
|
454
448
|
|
|
455
449
|
const projectName = getProjectName();
|
|
456
450
|
const mainWorktreePath = getGitTopLevel();
|
package/src/constants/branch.ts
CHANGED
package/src/constants/config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ClawtConfig, ConfigDefinitions } from '../types/index.js';
|
|
2
2
|
import { VALID_TERMINAL_APPS } from './terminal.js';
|
|
3
3
|
|
|
4
|
-
/** Claude Code
|
|
4
|
+
/** Claude Code 系统约束提示 */
|
|
5
5
|
export const APPEND_SYSTEM_PROMPT =
|
|
6
6
|
'After the code execution is completed, it is prohibited to build the project for verification.';
|
|
7
7
|
|
package/src/constants/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { CLAWT_HOME, CONFIG_PATH, LOGS_DIR, WORKTREES_DIR, VALIDATE_SNAPSHOTS_DIR, CLAUDE_PROJECTS_DIR, UPDATE_CHECK_PATH } from './paths.js';
|
|
2
|
-
export { INVALID_BRANCH_CHARS } from './branch.js';
|
|
1
|
+
export { CLAWT_HOME, CONFIG_PATH, LOGS_DIR, WORKTREES_DIR, VALIDATE_SNAPSHOTS_DIR, CLAUDE_PROJECTS_DIR, UPDATE_CHECK_PATH, PROJECTS_CONFIG_DIR } from './paths.js';
|
|
2
|
+
export { INVALID_BRANCH_CHARS, VALIDATE_BRANCH_PREFIX } from './branch.js';
|
|
3
3
|
export { MESSAGES } from './messages/index.js';
|
|
4
4
|
export { CONFIG_ALIAS_DISABLED_HINT } from './messages/index.js';
|
|
5
5
|
export { UPDATE_MESSAGES, UPDATE_COMMANDS } from './messages/update.js';
|
|
@@ -29,4 +29,4 @@ export {
|
|
|
29
29
|
CLEAR_SCREEN,
|
|
30
30
|
CURSOR_HOME,
|
|
31
31
|
} from './progress.js';
|
|
32
|
-
export { SELECT_ALL_NAME, SELECT_ALL_LABEL } from './prompt.js';
|
|
32
|
+
export { SELECT_ALL_NAME, SELECT_ALL_LABEL, GROUP_SELECT_ALL_PREFIX, GROUP_SELECT_ALL_LABEL, GROUP_SEPARATOR_LABEL, UNKNOWN_DATE_GROUP, UNKNOWN_DATE_SEPARATOR_LABEL } from './prompt.js';
|
|
@@ -2,4 +2,7 @@
|
|
|
2
2
|
export const CREATE_MESSAGES = {
|
|
3
3
|
/** 创建数量参数无效 */
|
|
4
4
|
INVALID_COUNT: (value: string) => `无效的创建数量: "${value}",请输入正整数`,
|
|
5
|
+
/** 当前不在主工作分支上的警告 */
|
|
6
|
+
CREATE_WARN_NOT_ON_MAIN_BRANCH: (mainBranch: string, currentBranch: string) =>
|
|
7
|
+
`当前不在主工作分支 ${mainBranch} 上(当前: ${currentBranch})`,
|
|
5
8
|
} as const;
|
|
@@ -13,6 +13,7 @@ import { ALIAS_MESSAGES } from './alias.js';
|
|
|
13
13
|
import { PROJECTS_MESSAGES } from './projects.js';
|
|
14
14
|
import { COMPLETION_MESSAGES } from './completion.js';
|
|
15
15
|
import { UPDATE_MESSAGES, UPDATE_COMMANDS } from './update.js';
|
|
16
|
+
import { INIT_MESSAGES } from './init.js';
|
|
16
17
|
|
|
17
18
|
export { CONFIG_ALIAS_DISABLED_HINT };
|
|
18
19
|
export { UPDATE_MESSAGES, UPDATE_COMMANDS };
|
|
@@ -36,4 +37,5 @@ export const MESSAGES = {
|
|
|
36
37
|
...ALIAS_MESSAGES,
|
|
37
38
|
...PROJECTS_MESSAGES,
|
|
38
39
|
...COMPLETION_MESSAGES,
|
|
40
|
+
...INIT_MESSAGES,
|
|
39
41
|
} as const;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** init 命令专属提示消息 */
|
|
2
|
+
export const INIT_MESSAGES = {
|
|
3
|
+
/** init 成功 */
|
|
4
|
+
INIT_SUCCESS: (branch: string) =>
|
|
5
|
+
`✓ 项目初始化成功,主工作分支设置为: ${branch}`,
|
|
6
|
+
/** init 更新 */
|
|
7
|
+
INIT_UPDATED: (oldBranch: string, newBranch: string) =>
|
|
8
|
+
`✓ 已将主工作分支从 ${oldBranch} 更新为 ${newBranch}`,
|
|
9
|
+
/** init show 查看当前项目完整配置(JSON 格式) */
|
|
10
|
+
INIT_SHOW: (configJson: string) =>
|
|
11
|
+
`${configJson}`,
|
|
12
|
+
/** init 未初始化 */
|
|
13
|
+
INIT_NOT_INITIALIZED: '项目尚未初始化,请先执行 clawt init 设置主工作分支',
|
|
14
|
+
/** 项目未初始化(requireProjectConfig 使用) */
|
|
15
|
+
PROJECT_NOT_INITIALIZED: '项目尚未初始化,请先执行 clawt init 设置主工作分支',
|
|
16
|
+
/** 项目配置缺少 clawtMainWorkBranch 字段 */
|
|
17
|
+
PROJECT_CONFIG_MISSING_BRANCH: '项目配置缺少主工作分支信息,请重新执行 clawt init 设置主工作分支',
|
|
18
|
+
} as const;
|
|
@@ -14,4 +14,6 @@ export const REMOVE_MESSAGES = {
|
|
|
14
14
|
`以下 worktree 移除失败:\n${failures.map((f) => ` ✗ ${f.path}: ${f.error}`).join('\n')}`,
|
|
15
15
|
/** 用户选择保留本地分支 */
|
|
16
16
|
REMOVE_BRANCHES_KEPT: '已保留本地分支,可稍后使用 git branch -D <分支名> 手动删除',
|
|
17
|
+
/** 确认删除本地分支和验证分支 */
|
|
18
|
+
REMOVE_CONFIRM_DELETE_BRANCHES: '是否同时删除对应的本地分支和验证分支?',
|
|
17
19
|
} as const;
|
|
@@ -21,4 +21,7 @@ export const SYNC_MESSAGES = {
|
|
|
21
21
|
SYNC_SELECT_BRANCH: '请选择要同步的分支',
|
|
22
22
|
/** sync 模糊匹配到多个结果提示 */
|
|
23
23
|
SYNC_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
|
|
24
|
+
/** sync 后验证分支已重建提示 */
|
|
25
|
+
SYNC_VALIDATE_BRANCH_REBUILT: (validateBranch: string) =>
|
|
26
|
+
`验证分支 ${validateBranch} 已重建`,
|
|
24
27
|
} as const;
|
|
@@ -62,4 +62,10 @@ export const VALIDATE_MESSAGES = {
|
|
|
62
62
|
/** 用户拒绝自动 sync */
|
|
63
63
|
VALIDATE_AUTO_SYNC_DECLINED: (branch: string) =>
|
|
64
64
|
`请手动执行 clawt sync -b ${branch} 同步主分支后重试`,
|
|
65
|
+
/** 验证分支不存在 */
|
|
66
|
+
VALIDATE_BRANCH_NOT_FOUND: (validateBranch: string, branch: string) =>
|
|
67
|
+
`验证分支 ${validateBranch} 不存在,请先执行 clawt create 或 clawt run 创建分支 ${branch}`,
|
|
68
|
+
/** validate 成功(含验证分支信息) */
|
|
69
|
+
VALIDATE_SUCCESS_WITH_BRANCH: (branch: string, validateBranch: string) =>
|
|
70
|
+
`✓ 已切换到验证分支 ${validateBranch} 并应用分支 ${branch} 的变更\n 可以开始验证了`,
|
|
65
71
|
} as const;
|
package/src/constants/paths.ts
CHANGED
|
@@ -21,3 +21,6 @@ export const CLAUDE_PROJECTS_DIR = join(homedir(), '.claude', 'projects');
|
|
|
21
21
|
|
|
22
22
|
/** 更新检查缓存文件路径 ~/.clawt/update-check.json */
|
|
23
23
|
export const UPDATE_CHECK_PATH = join(CLAWT_HOME, 'update-check.json');
|
|
24
|
+
|
|
25
|
+
/** 项目配置目录 ~/.clawt/projects/ */
|
|
26
|
+
export const PROJECTS_CONFIG_DIR = join(CLAWT_HOME, 'projects');
|
package/src/constants/prompt.ts
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
1
3
|
/** 多选列表中全选选项的标识名称 */
|
|
2
4
|
export const SELECT_ALL_NAME = '__select_all__';
|
|
3
5
|
|
|
4
6
|
/** 多选列表中全选选项的显示文本 */
|
|
5
7
|
export const SELECT_ALL_LABEL = '[select-all]';
|
|
8
|
+
|
|
9
|
+
/** 组级全选选项的 name 前缀 */
|
|
10
|
+
export const GROUP_SELECT_ALL_PREFIX = '__group_select_all_';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 生成组级全选选项的显示文本
|
|
14
|
+
* @param {string} dateLabel - 日期标签
|
|
15
|
+
* @returns {string} 格式化的组全选显示文本
|
|
16
|
+
*/
|
|
17
|
+
export const GROUP_SELECT_ALL_LABEL = (dateLabel: string): string => `[select-all: ${dateLabel}]`;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 生成日期分组分隔线的显示文本
|
|
21
|
+
* 日期部分高亮显示,两侧使用 === 分隔线增强视觉区分
|
|
22
|
+
* @param {string} dateLabel - 日期标签
|
|
23
|
+
* @param {string} relativeTime - 相对时间描述
|
|
24
|
+
* @returns {string} 格式化的分隔线文本
|
|
25
|
+
*/
|
|
26
|
+
export const GROUP_SEPARATOR_LABEL = (dateLabel: string, relativeTime: string): string =>
|
|
27
|
+
`════ ${chalk.bold.hex('#FF8C00')(dateLabel)}(${chalk.hex('#FF8C00')(relativeTime)}) ════`;
|
|
28
|
+
|
|
29
|
+
/** 无法获取创建日期时的默认分组名称 */
|
|
30
|
+
export const UNKNOWN_DATE_GROUP = '未知日期';
|
|
31
|
+
|
|
32
|
+
/** 未知日期分组的分隔线显示文本 */
|
|
33
|
+
export const UNKNOWN_DATE_SEPARATOR_LABEL = `════ ${chalk.bold.hex('#FF8C00')('未知日期')} ════`;
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { registerStatusCommand } from './commands/status.js';
|
|
|
18
18
|
import { registerAliasCommand } from './commands/alias.js';
|
|
19
19
|
import { registerProjectsCommand } from './commands/projects.js';
|
|
20
20
|
import { registerCompletionCommand } from './commands/completion.js';
|
|
21
|
+
import { registerInitCommand } from './commands/init.js';
|
|
21
22
|
|
|
22
23
|
// 从 package.json 读取版本号,避免硬编码
|
|
23
24
|
const require = createRequire(import.meta.url);
|
|
@@ -56,6 +57,7 @@ registerStatusCommand(program);
|
|
|
56
57
|
registerAliasCommand(program);
|
|
57
58
|
registerProjectsCommand(program);
|
|
58
59
|
registerCompletionCommand(program);
|
|
60
|
+
registerInitCommand(program);
|
|
59
61
|
|
|
60
62
|
// 加载配置并应用命令别名
|
|
61
63
|
const config = loadConfig();
|
package/src/types/command.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface ValidateOptions {
|
|
|
34
34
|
export interface MergeOptions {
|
|
35
35
|
/** 要合并的分支名(可选,支持模糊匹配,不传则列出所有分支供选择) */
|
|
36
36
|
branch?: string;
|
|
37
|
-
/**
|
|
37
|
+
/** 提交信息(目标 worktree 工作区有修改时必填) */
|
|
38
38
|
message?: string;
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -77,3 +77,9 @@ export interface ProjectsOptions {
|
|
|
77
77
|
/** 以 JSON 格式输出 */
|
|
78
78
|
json?: boolean;
|
|
79
79
|
}
|
|
80
|
+
|
|
81
|
+
/** init 命令选项 */
|
|
82
|
+
export interface InitOptions {
|
|
83
|
+
/** 指定主工作分支名(可选,默认使用当前分支) */
|
|
84
|
+
branch?: string;
|
|
85
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export type { ClawtConfig, ConfigItemDefinition, ConfigDefinitions } from './config.js';
|
|
2
|
-
export type { CreateOptions, RunOptions, ValidateOptions, MergeOptions, RemoveOptions, ResumeOptions, SyncOptions, ListOptions, StatusOptions, ProjectsOptions } from './command.js';
|
|
2
|
+
export type { CreateOptions, RunOptions, ValidateOptions, MergeOptions, RemoveOptions, ResumeOptions, SyncOptions, ListOptions, StatusOptions, ProjectsOptions, InitOptions } from './command.js';
|
|
3
3
|
export type { WorktreeInfo, WorktreeStatus } from './worktree.js';
|
|
4
4
|
export type { ClaudeCodeResult } from './claudeCode.js';
|
|
5
5
|
export type { TaskResult, TaskSummary } from './taskResult.js';
|
|
6
6
|
export type { WorktreeDetailedStatus, MainWorktreeStatus, SnapshotInfo, SnapshotSummary, StatusResult } from './status.js';
|
|
7
7
|
export type { TaskFileEntry, ParseTaskFileOptions } from './taskFile.js';
|
|
8
8
|
export type { ProjectOverview, ProjectWorktreeDetail, ProjectDetailResult, ProjectsOverviewResult } from './project.js';
|
|
9
|
+
export type { ProjectConfig } from './projectConfig.js';
|
package/src/utils/config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { CONFIG_PATH, CLAWT_HOME, LOGS_DIR, WORKTREES_DIR, DEFAULT_CONFIG, MESSAGES } from '../constants/index.js';
|
|
2
|
+
import { CONFIG_PATH, CLAWT_HOME, LOGS_DIR, WORKTREES_DIR, DEFAULT_CONFIG, MESSAGES, PROJECTS_CONFIG_DIR } from '../constants/index.js';
|
|
3
3
|
import { ClawtError } from '../errors/index.js';
|
|
4
4
|
import { ensureDir } from './fs.js';
|
|
5
5
|
import { logger } from '../logger/index.js';
|
|
@@ -65,6 +65,7 @@ export function ensureClawtDirs(): void {
|
|
|
65
65
|
ensureDir(CLAWT_HOME);
|
|
66
66
|
ensureDir(LOGS_DIR);
|
|
67
67
|
ensureDir(WORKTREES_DIR);
|
|
68
|
+
ensureDir(PROJECTS_CONFIG_DIR);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/**
|
package/src/utils/git.ts
CHANGED
|
@@ -501,3 +501,21 @@ export function getBranchCreatedAt(branchName: string, cwd?: string): string | n
|
|
|
501
501
|
return null;
|
|
502
502
|
}
|
|
503
503
|
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* 切换到指定分支
|
|
507
|
+
* @param {string} branchName - 目标分支名
|
|
508
|
+
* @param {string} [cwd] - 工作目录
|
|
509
|
+
*/
|
|
510
|
+
export function gitCheckout(branchName: string, cwd?: string): void {
|
|
511
|
+
execCommand(`git checkout ${branchName}`, { cwd });
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* 创建本地分支(不切换)
|
|
516
|
+
* @param {string} branchName - 新分支名
|
|
517
|
+
* @param {string} [cwd] - 工作目录
|
|
518
|
+
*/
|
|
519
|
+
export function createBranch(branchName: string, cwd?: string): void {
|
|
520
|
+
execCommand(`git branch ${branchName}`, { cwd });
|
|
521
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -46,6 +46,8 @@ export {
|
|
|
46
46
|
gitDiffTree,
|
|
47
47
|
gitApplyCachedCheck,
|
|
48
48
|
getBranchCreatedAt,
|
|
49
|
+
gitCheckout,
|
|
50
|
+
createBranch,
|
|
49
51
|
} from './git.js';
|
|
50
52
|
export { sanitizeBranchName, generateBranchNames, validateBranchesNotExist } from './branch.js';
|
|
51
53
|
export { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled } from './validation.js';
|
|
@@ -56,7 +58,7 @@ export { ensureDir, removeEmptyDir, calculateDirSize } from './fs.js';
|
|
|
56
58
|
export { multilineInput } from './prompt.js';
|
|
57
59
|
export { launchInteractiveClaude, hasClaudeSessionHistory, launchInteractiveClaudeInNewTerminal } from './claude.js';
|
|
58
60
|
export { getSnapshotPath, hasSnapshot, getSnapshotModifiedTime, readSnapshotTreeHash, readSnapshot, writeSnapshot, removeSnapshot, removeProjectSnapshots, getProjectSnapshotBranches } from './validate-snapshot.js';
|
|
59
|
-
export { findExactMatch, findFuzzyMatches, promptSelectBranch, promptMultiSelectBranches, resolveTargetWorktree, resolveTargetWorktrees } from './worktree-matcher.js';
|
|
61
|
+
export { findExactMatch, findFuzzyMatches, promptSelectBranch, promptMultiSelectBranches, promptGroupedMultiSelectBranches, resolveTargetWorktree, resolveTargetWorktrees, groupWorktreesByDate, buildGroupedChoices, buildGroupMembershipMap } from './worktree-matcher.js';
|
|
60
62
|
export type { WorktreeResolveMessages, WorktreeMultiResolveMessages } from './worktree-matcher.js';
|
|
61
63
|
export { ProgressRenderer } from './progress.js';
|
|
62
64
|
export { parseTaskFile, loadTaskFile, parseTasksFromOptions } from './task-file.js';
|
|
@@ -68,4 +70,7 @@ export { truncateTaskDesc, printDryRunPreview } from './dry-run.js';
|
|
|
68
70
|
export { applyAliases } from './alias.js';
|
|
69
71
|
export { isValidConfigKey, getValidConfigKeys, parseConfigValue, promptConfigValue, formatConfigValue } from './config-strategy.js';
|
|
70
72
|
export { checkForUpdates } from './update-checker.js';
|
|
73
|
+
export { getProjectConfigPath, loadProjectConfig, saveProjectConfig, requireProjectConfig, getMainWorkBranch } from './project-config.js';
|
|
74
|
+
export { getValidateBranchName, createValidateBranch, deleteValidateBranch, rebuildValidateBranch, ensureOnMainWorkBranch, handleDirtyWorkingDir } from './validate-branch.js';
|
|
75
|
+
export { safeStringify } from './json.js';
|
|
71
76
|
|