clawt 2.20.0 → 3.1.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/agents/docs-sync-updater.md +29 -11
- package/README.md +19 -30
- package/dist/index.js +1127 -222
- package/dist/postinstall.js +73 -8
- 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 +157 -1906
- package/docs/status.md +298 -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 +1 -1
- package/src/commands/run.ts +9 -3
- package/src/commands/status.ts +14 -5
- 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 +14 -2
- package/src/constants/interactive-panel.ts +44 -0
- package/src/constants/messages/completion.ts +1 -1
- package/src/constants/messages/create.ts +3 -0
- package/src/constants/messages/index.ts +4 -0
- package/src/constants/messages/init.ts +18 -0
- package/src/constants/messages/interactive-panel.ts +61 -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/index.ts +2 -0
- package/src/types/command.ts +9 -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 +9 -1
- package/src/utils/interactive-panel-render.ts +315 -0
- package/src/utils/interactive-panel.ts +590 -0
- 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 +2 -2
- 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/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.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
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
printInfo,
|
|
22
22
|
printDoubleSeparator,
|
|
23
23
|
printSeparator,
|
|
24
|
+
InteractivePanel,
|
|
24
25
|
} from '../utils/index.js';
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -30,10 +31,11 @@ import {
|
|
|
30
31
|
export function registerStatusCommand(program: Command): void {
|
|
31
32
|
program
|
|
32
33
|
.command('status')
|
|
33
|
-
.description('
|
|
34
|
+
.description('显示项目全局状态总览(支持 --json 格式输出)')
|
|
34
35
|
.option('--json', '以 JSON 格式输出')
|
|
35
|
-
.
|
|
36
|
-
|
|
36
|
+
.option('-i, --interactive', '交互式面板模式')
|
|
37
|
+
.action(async (options: StatusOptions) => {
|
|
38
|
+
await handleStatus(options);
|
|
37
39
|
});
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -41,9 +43,16 @@ export function registerStatusCommand(program: Command): void {
|
|
|
41
43
|
* 执行 status 命令的核心逻辑
|
|
42
44
|
* @param {StatusOptions} options - 命令选项
|
|
43
45
|
*/
|
|
44
|
-
function handleStatus(options: StatusOptions): void {
|
|
46
|
+
async function handleStatus(options: StatusOptions): Promise<void> {
|
|
45
47
|
validateMainWorktree();
|
|
46
48
|
|
|
49
|
+
// 交互式面板模式
|
|
50
|
+
if (options.interactive) {
|
|
51
|
+
const panel = new InteractivePanel(collectStatus);
|
|
52
|
+
await panel.start();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
47
56
|
const statusResult = collectStatus();
|
|
48
57
|
|
|
49
58
|
logger.info(`status 命令执行,项目: ${statusResult.main.projectName},共 ${statusResult.totalWorktrees} 个 worktree`);
|
|
@@ -60,7 +69,7 @@ function handleStatus(options: StatusOptions): void {
|
|
|
60
69
|
* 收集项目全局状态信息
|
|
61
70
|
* @returns {StatusResult} 完整的状态数据
|
|
62
71
|
*/
|
|
63
|
-
function collectStatus(): StatusResult {
|
|
72
|
+
export function collectStatus(): StatusResult {
|
|
64
73
|
const projectName = getProjectName();
|
|
65
74
|
const currentBranch = getCurrentBranch();
|
|
66
75
|
const isClean = isWorkingDirClean();
|
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';
|
|
@@ -30,3 +30,15 @@ export {
|
|
|
30
30
|
CURSOR_HOME,
|
|
31
31
|
} from './progress.js';
|
|
32
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';
|
|
33
|
+
export {
|
|
34
|
+
PANEL_REFRESH_INTERVAL_MS,
|
|
35
|
+
PANEL_COUNTDOWN_INTERVAL_MS,
|
|
36
|
+
SELECTED_INDICATOR,
|
|
37
|
+
UNSELECTED_INDICATOR,
|
|
38
|
+
KEY_ARROW_UP,
|
|
39
|
+
KEY_ARROW_DOWN,
|
|
40
|
+
KEY_CTRL_C,
|
|
41
|
+
PANEL_SHORTCUT_KEYS,
|
|
42
|
+
PANEL_DATE_SEPARATOR_PREFIX,
|
|
43
|
+
PANEL_FIXED_ROWS,
|
|
44
|
+
} from './interactive-panel.js';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** 自动刷新间隔(毫秒) */
|
|
2
|
+
export const PANEL_REFRESH_INTERVAL_MS = 5000;
|
|
3
|
+
|
|
4
|
+
/** 倒计时更新间隔(毫秒) */
|
|
5
|
+
export const PANEL_COUNTDOWN_INTERVAL_MS = 1000;
|
|
6
|
+
|
|
7
|
+
/** 选中指示器 */
|
|
8
|
+
export const SELECTED_INDICATOR = '▶';
|
|
9
|
+
|
|
10
|
+
/** 未选中占位(与选中指示器等宽) */
|
|
11
|
+
export const UNSELECTED_INDICATOR = ' ';
|
|
12
|
+
|
|
13
|
+
/** 方向键上的字节序列 */
|
|
14
|
+
export const KEY_ARROW_UP = '\x1b[A';
|
|
15
|
+
|
|
16
|
+
/** 方向键下的字节序列 */
|
|
17
|
+
export const KEY_ARROW_DOWN = '\x1b[B';
|
|
18
|
+
|
|
19
|
+
/** Ctrl+C 的字节值 */
|
|
20
|
+
export const KEY_CTRL_C = 0x03;
|
|
21
|
+
|
|
22
|
+
/** 面板快捷键映射 */
|
|
23
|
+
export const PANEL_SHORTCUT_KEYS = {
|
|
24
|
+
/** 验证 */
|
|
25
|
+
VALIDATE: 'v',
|
|
26
|
+
/** 合并 */
|
|
27
|
+
MERGE: 'm',
|
|
28
|
+
/** 删除 */
|
|
29
|
+
DELETE: 'd',
|
|
30
|
+
/** 恢复 */
|
|
31
|
+
RESUME: 'r',
|
|
32
|
+
/** 同步 */
|
|
33
|
+
SYNC: 's',
|
|
34
|
+
/** 手动刷新 */
|
|
35
|
+
REFRESH: 'f',
|
|
36
|
+
/** 退出 */
|
|
37
|
+
QUIT: 'q',
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
/** 日期分隔线前缀 */
|
|
41
|
+
export const PANEL_DATE_SEPARATOR_PREFIX = '════';
|
|
42
|
+
|
|
43
|
+
/** 固定占用行数(顶部分隔线 + 快照摘要 + 底部分隔线 + 底栏) */
|
|
44
|
+
export const PANEL_FIXED_ROWS = 4;
|
|
@@ -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,9 +13,12 @@ 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';
|
|
17
|
+
import { PANEL_FOOTER_SHORTCUTS, PANEL_FOOTER_COUNTDOWN, PANEL_OVERFLOW_DOWN_HINT, PANEL_OVERFLOW_UP_HINT, PANEL_SNAPSHOT_SUMMARY, PANEL_NO_WORKTREES as PANEL_NO_WORKTREES_MSG, PANEL_PRESS_ENTER_TO_RETURN, PANEL_NOT_TTY, PANEL_TITLE } from './interactive-panel.js';
|
|
16
18
|
|
|
17
19
|
export { CONFIG_ALIAS_DISABLED_HINT };
|
|
18
20
|
export { UPDATE_MESSAGES, UPDATE_COMMANDS };
|
|
21
|
+
export { PANEL_FOOTER_SHORTCUTS, PANEL_FOOTER_COUNTDOWN, PANEL_OVERFLOW_DOWN_HINT, PANEL_OVERFLOW_UP_HINT, PANEL_SNAPSHOT_SUMMARY, PANEL_NO_WORKTREES_MSG, PANEL_PRESS_ENTER_TO_RETURN, PANEL_NOT_TTY, PANEL_TITLE };
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
24
|
* 提示消息模板
|
|
@@ -36,4 +39,5 @@ export const MESSAGES = {
|
|
|
36
39
|
...ALIAS_MESSAGES,
|
|
37
40
|
...PROJECTS_MESSAGES,
|
|
38
41
|
...COMPLETION_MESSAGES,
|
|
42
|
+
...INIT_MESSAGES,
|
|
39
43
|
} 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;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { PANEL_SHORTCUT_KEYS } from '../interactive-panel.js';
|
|
3
|
+
|
|
4
|
+
/** 快捷键标签映射(键名 → 中文标签) */
|
|
5
|
+
const SHORTCUT_LABELS: Record<keyof typeof PANEL_SHORTCUT_KEYS, string> = {
|
|
6
|
+
VALIDATE: '验证',
|
|
7
|
+
MERGE: '合并',
|
|
8
|
+
DELETE: '删除',
|
|
9
|
+
RESUME: '恢复',
|
|
10
|
+
SYNC: '同步',
|
|
11
|
+
REFRESH: '刷新',
|
|
12
|
+
QUIT: '退出',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** 底栏快捷键提示文本(从 PANEL_SHORTCUT_KEYS 自动生成) */
|
|
16
|
+
export const PANEL_FOOTER_SHORTCUTS = Object.entries(SHORTCUT_LABELS)
|
|
17
|
+
.map(([key, label]) => `[${chalk.cyan(PANEL_SHORTCUT_KEYS[key as keyof typeof PANEL_SHORTCUT_KEYS])}]${label}`)
|
|
18
|
+
.join(' ');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 底栏倒计时文本
|
|
22
|
+
* @param {number} seconds - 剩余秒数
|
|
23
|
+
* @returns {string} 格式化的倒计时文本
|
|
24
|
+
*/
|
|
25
|
+
export const PANEL_FOOTER_COUNTDOWN = (seconds: number): string => chalk.gray(`(${seconds}s 后刷新)`);
|
|
26
|
+
|
|
27
|
+
/** 向下溢出提示 */
|
|
28
|
+
export const PANEL_OVERFLOW_DOWN_HINT = chalk.gray('↓ 更多 worktree...');
|
|
29
|
+
|
|
30
|
+
/** 向上溢出提示 */
|
|
31
|
+
export const PANEL_OVERFLOW_UP_HINT = chalk.gray('↑ 更多 worktree...');
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 快照摘要文本
|
|
35
|
+
* @param {number} total - 快照总数
|
|
36
|
+
* @param {number} orphaned - 孤立快照数
|
|
37
|
+
* @returns {string} 格式化的快照摘要
|
|
38
|
+
*/
|
|
39
|
+
export const PANEL_SNAPSHOT_SUMMARY = (total: number, orphaned: number): string => {
|
|
40
|
+
const base = `快照: ${total} 个`;
|
|
41
|
+
if (orphaned > 0) {
|
|
42
|
+
return `${base}(${chalk.yellow(`${orphaned} 个孤立`)})`;
|
|
43
|
+
}
|
|
44
|
+
return base;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** 无 worktree 提示 */
|
|
48
|
+
export const PANEL_NO_WORKTREES = '(无活跃 worktree)';
|
|
49
|
+
|
|
50
|
+
/** 操作后返回提示 */
|
|
51
|
+
export const PANEL_PRESS_ENTER_TO_RETURN = chalk.gray('\n按 Enter 返回面板...');
|
|
52
|
+
|
|
53
|
+
/** 非 TTY 降级提示 */
|
|
54
|
+
export const PANEL_NOT_TTY = '交互式面板需要 TTY 终端环境,请直接在终端中运行 clawt status -i';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 面板标题
|
|
58
|
+
* @param {string} projectName - 项目名
|
|
59
|
+
* @returns {string} 格式化的标题
|
|
60
|
+
*/
|
|
61
|
+
export const PANEL_TITLE = (projectName: string): string => chalk.bold.cyan(`项目状态总览: ${projectName}`);
|
|
@@ -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/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
|
|
|
@@ -68,6 +68,8 @@ export interface ListOptions {
|
|
|
68
68
|
export interface StatusOptions {
|
|
69
69
|
/** 以 JSON 格式输出 */
|
|
70
70
|
json?: boolean;
|
|
71
|
+
/** 交互式面板模式 */
|
|
72
|
+
interactive?: boolean;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
/** projects 命令选项 */
|
|
@@ -77,3 +79,9 @@ export interface ProjectsOptions {
|
|
|
77
79
|
/** 以 JSON 格式输出 */
|
|
78
80
|
json?: boolean;
|
|
79
81
|
}
|
|
82
|
+
|
|
83
|
+
/** init 命令选项 */
|
|
84
|
+
export interface InitOptions {
|
|
85
|
+
/** 指定主工作分支名(可选,默认使用当前分支) */
|
|
86
|
+
branch?: string;
|
|
87
|
+
}
|