clawt 2.10.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agent-memory/docs-sync-updater/MEMORY.md +14 -7
- package/.claude/agents/docs-sync-updater.md +11 -0
- package/README.md +80 -284
- package/dist/index.js +839 -307
- package/dist/postinstall.js +272 -0
- package/docs/spec.md +84 -22
- package/package.json +1 -1
- package/src/commands/remove.ts +21 -28
- package/src/commands/run.ts +68 -206
- package/src/constants/config.ts +4 -0
- package/src/constants/index.ts +11 -1
- package/src/constants/messages/common.ts +41 -0
- package/src/constants/messages/config.ts +5 -0
- package/src/constants/messages/create.ts +5 -0
- package/src/constants/messages/index.ts +29 -0
- package/src/constants/messages/merge.ts +42 -0
- package/src/constants/messages/remove.ts +15 -0
- package/src/constants/messages/reset.ts +7 -0
- package/src/constants/messages/resume.ts +12 -0
- package/src/constants/messages/run.ts +46 -0
- package/src/constants/messages/status.ts +25 -0
- package/src/constants/messages/sync.ts +24 -0
- package/src/constants/messages/validate.ts +25 -0
- package/src/constants/progress.ts +39 -0
- package/src/types/command.ts +4 -0
- package/src/types/config.ts +2 -0
- package/src/types/index.ts +1 -0
- package/src/types/taskFile.ts +13 -0
- package/src/utils/formatter.ts +16 -0
- package/src/utils/index.ts +8 -4
- package/src/utils/progress-render.ts +90 -0
- package/src/utils/progress.ts +213 -0
- package/src/utils/task-executor.ts +365 -0
- package/src/utils/task-file.ts +87 -0
- package/src/utils/worktree-matcher.ts +92 -0
- package/src/utils/worktree.ts +27 -0
- package/tests/unit/commands/config.test.ts +110 -0
- package/tests/unit/commands/create.test.ts +115 -0
- package/tests/unit/commands/list.test.ts +118 -0
- package/tests/unit/commands/merge.test.ts +323 -0
- package/tests/unit/commands/remove.test.ts +240 -0
- package/tests/unit/commands/reset.test.ts +124 -0
- package/tests/unit/commands/resume.test.ts +91 -0
- package/tests/unit/commands/run.test.ts +456 -0
- package/tests/unit/commands/status.test.ts +214 -0
- package/tests/unit/commands/sync.test.ts +208 -0
- package/tests/unit/commands/validate.test.ts +382 -0
- package/tests/unit/constants/config.test.ts +1 -0
- package/tests/unit/constants/messages.test.ts +1 -1
- package/tests/unit/utils/config.test.ts +21 -1
- package/tests/unit/utils/formatter.test.ts +70 -1
- package/tests/unit/utils/git.test.ts +44 -0
- package/tests/unit/utils/progress.test.ts +255 -0
- package/tests/unit/utils/task-file.test.ts +236 -0
- package/tests/unit/utils/validate-snapshot.test.ts +25 -0
- package/tests/unit/utils/worktree-matcher.test.ts +81 -5
- package/tests/unit/utils/worktree.test.ts +26 -1
package/src/commands/run.ts
CHANGED
|
@@ -1,27 +1,20 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
-
import type { ChildProcess } from 'node:child_process';
|
|
3
2
|
import { logger } from '../logger/index.js';
|
|
4
3
|
import { ClawtError } from '../errors/index.js';
|
|
5
4
|
import { MESSAGES } from '../constants/index.js';
|
|
6
|
-
import type { RunOptions,
|
|
5
|
+
import type { RunOptions, WorktreeInfo } from '../types/index.js';
|
|
7
6
|
import {
|
|
8
7
|
validateMainWorktree,
|
|
9
8
|
validateClaudeCodeInstalled,
|
|
10
9
|
createWorktrees,
|
|
10
|
+
createWorktreesByBranches,
|
|
11
11
|
sanitizeBranchName,
|
|
12
12
|
checkBranchExists,
|
|
13
|
-
spawnProcess,
|
|
14
|
-
killAllChildProcesses,
|
|
15
|
-
cleanupWorktrees,
|
|
16
13
|
getConfigValue,
|
|
17
14
|
printSuccess,
|
|
18
|
-
printError,
|
|
19
|
-
printWarning,
|
|
20
|
-
printInfo,
|
|
21
|
-
printSeparator,
|
|
22
|
-
printDoubleSeparator,
|
|
23
|
-
confirmAction,
|
|
24
15
|
launchInteractiveClaude,
|
|
16
|
+
loadTaskFile,
|
|
17
|
+
executeBatchTasks,
|
|
25
18
|
} from '../utils/index.js';
|
|
26
19
|
|
|
27
20
|
/**
|
|
@@ -32,165 +25,93 @@ export function registerRunCommand(program: Command): void {
|
|
|
32
25
|
program
|
|
33
26
|
.command('run')
|
|
34
27
|
.description('批量创建 worktree 并启动 Claude Code 执行任务')
|
|
35
|
-
.
|
|
28
|
+
.option('-b, --branch <branchName>', '分支名')
|
|
36
29
|
.option('--tasks <task...>', '任务列表(可多次指定),不传则在 worktree 中打开 Claude Code 交互式界面')
|
|
30
|
+
.option('-c, --concurrency <n>', '最大并发数,0 表示不限制')
|
|
31
|
+
.option('-f, --file <path>', '从任务文件读取任务列表(与 --tasks 互斥)')
|
|
37
32
|
.action(async (options: RunOptions) => {
|
|
38
33
|
await handleRun(options);
|
|
39
34
|
});
|
|
40
35
|
}
|
|
41
36
|
|
|
42
|
-
/** executeClaudeTask 的返回结构,包含子进程引用和结果 Promise */
|
|
43
|
-
interface ClaudeTaskHandle {
|
|
44
|
-
/** 子进程实例,用于在中断时终止 */
|
|
45
|
-
child: ChildProcess;
|
|
46
|
-
/** 任务结果 Promise */
|
|
47
|
-
promise: Promise<TaskResult>;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
37
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* @param {string}
|
|
54
|
-
* @
|
|
38
|
+
* 解析并发数参数
|
|
39
|
+
* 优先级:命令行参数 > 全局配置 > 默认值 0
|
|
40
|
+
* @param {string | undefined} optionValue - 命令行传入的并发数字符串
|
|
41
|
+
* @param {number} configValue - 全局配置中的默认并发数
|
|
42
|
+
* @returns {number} 解析后的并发数,0 表示不限制
|
|
55
43
|
*/
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
['-p', task, '--output-format', 'json', '--permission-mode', 'bypassPermissions'],
|
|
60
|
-
{
|
|
61
|
-
cwd: worktree.path,
|
|
62
|
-
// stdin 必须设置为 'ignore',不能用 'pipe'
|
|
63
|
-
// 原因:claude -p 是非交互模式,不需要 stdin 输入。如果 stdin 为 'pipe',
|
|
64
|
-
// 父进程会创建一个可写流连接到子进程但从不写入也不关闭,
|
|
65
|
-
// claude 检测到 stdin 是管道后会尝试读取输入,导致进程永远卡住
|
|
66
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
67
|
-
},
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
const promise = new Promise<TaskResult>((resolve) => {
|
|
71
|
-
let stdout = '';
|
|
72
|
-
let stderr = '';
|
|
73
|
-
|
|
74
|
-
child.stdout?.on('data', (data: Buffer) => {
|
|
75
|
-
stdout += data.toString();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
child.stderr?.on('data', (data: Buffer) => {
|
|
79
|
-
stderr += data.toString();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
child.on('close', (code) => {
|
|
83
|
-
let result: ClaudeCodeResult | null = null;
|
|
84
|
-
let success = code === 0;
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
if (stdout.trim()) {
|
|
88
|
-
result = JSON.parse(stdout.trim()) as ClaudeCodeResult;
|
|
89
|
-
success = !result.is_error;
|
|
90
|
-
}
|
|
91
|
-
} catch {
|
|
92
|
-
logger.warn(`解析 Claude Code 输出失败: ${stdout.substring(0, 200)}`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
resolve({
|
|
96
|
-
task,
|
|
97
|
-
branch: worktree.branch,
|
|
98
|
-
worktreePath: worktree.path,
|
|
99
|
-
success,
|
|
100
|
-
result,
|
|
101
|
-
error: success ? undefined : stderr || '任务执行失败',
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
child.on('error', (err) => {
|
|
106
|
-
resolve({
|
|
107
|
-
task,
|
|
108
|
-
branch: worktree.branch,
|
|
109
|
-
worktreePath: worktree.path,
|
|
110
|
-
success: false,
|
|
111
|
-
result: null,
|
|
112
|
-
error: err.message,
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
return { child, promise };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* 输出单个任务完成通知
|
|
122
|
-
* @param {TaskResult} taskResult - 任务结果
|
|
123
|
-
*/
|
|
124
|
-
function printTaskNotification(taskResult: TaskResult): void {
|
|
125
|
-
const { success, worktreePath, branch, result } = taskResult;
|
|
126
|
-
const status = success ? '完成' : '失败';
|
|
127
|
-
const icon = success ? '✓' : '✗';
|
|
128
|
-
const durationStr = result ? `${(result.duration_ms / 1000).toFixed(1)}s` : 'N/A';
|
|
129
|
-
const costStr = result ? `$${result.total_cost_usd.toFixed(2)}` : 'N/A';
|
|
130
|
-
const resultStr = success ? 'success' : 'failed';
|
|
131
|
-
|
|
132
|
-
if (success) {
|
|
133
|
-
printSuccess(`${icon} [${status}] worktree: ${worktreePath}`);
|
|
134
|
-
} else {
|
|
135
|
-
printError(`${icon} [${status}] worktree: ${worktreePath}`);
|
|
44
|
+
function parseConcurrency(optionValue: string | undefined, configValue: number): number {
|
|
45
|
+
if (optionValue === undefined) {
|
|
46
|
+
return configValue;
|
|
136
47
|
}
|
|
137
|
-
printInfo(` 分支: ${branch}`);
|
|
138
|
-
printInfo(` 耗时: ${durationStr}`);
|
|
139
|
-
printInfo(` 花费: ${costStr}`);
|
|
140
|
-
printInfo(` 结果: ${resultStr}`);
|
|
141
|
-
printSeparator();
|
|
142
|
-
}
|
|
143
48
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
printDoubleSeparator();
|
|
150
|
-
printInfo(`全部任务已完成 (${summary.total}/${summary.total})`);
|
|
151
|
-
printInfo(` 成功: ${summary.succeeded}`);
|
|
152
|
-
printInfo(` 失败: ${summary.failed}`);
|
|
153
|
-
printInfo(` 总耗时: ${(summary.totalDurationMs / 1000).toFixed(1)}s`);
|
|
154
|
-
printInfo(` 总花费: $${summary.totalCostUsd.toFixed(2)}`);
|
|
155
|
-
printDoubleSeparator();
|
|
49
|
+
const parsed = parseInt(optionValue, 10);
|
|
50
|
+
if (Number.isNaN(parsed) || parsed < 0) {
|
|
51
|
+
throw new ClawtError(MESSAGES.CONCURRENCY_INVALID);
|
|
52
|
+
}
|
|
53
|
+
return parsed;
|
|
156
54
|
}
|
|
157
55
|
|
|
158
56
|
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* @param {WorktreeInfo[]} worktrees - 本次创建的 worktree 列表
|
|
57
|
+
* 处理从任务文件执行的逻辑
|
|
58
|
+
* @param {RunOptions} options - 命令选项(包含 file 字段)
|
|
162
59
|
*/
|
|
163
|
-
async function
|
|
164
|
-
|
|
60
|
+
async function handleRunFromFile(options: RunOptions): Promise<void> {
|
|
61
|
+
// 有 -b 参数时,文件中的分支名为可选
|
|
62
|
+
const branchRequired = !options.branch;
|
|
63
|
+
// 加载并解析任务文件
|
|
64
|
+
const entries = loadTaskFile(options.file!, { branchRequired });
|
|
65
|
+
printSuccess(MESSAGES.TASK_FILE_LOADED(entries.length, options.file!));
|
|
165
66
|
|
|
166
|
-
|
|
167
|
-
// 全局配置了自动删除,直接清理
|
|
168
|
-
cleanupWorktrees(worktrees);
|
|
169
|
-
printSuccess(MESSAGES.INTERRUPT_AUTO_CLEANED(worktrees.length));
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
67
|
+
const tasks = entries.map((e) => e.task);
|
|
172
68
|
|
|
173
|
-
|
|
174
|
-
const shouldClean = await confirmAction(MESSAGES.INTERRUPT_CONFIRM_CLEANUP);
|
|
69
|
+
let worktrees: WorktreeInfo[];
|
|
175
70
|
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
|
|
71
|
+
if (options.branch) {
|
|
72
|
+
// 有 -b 参数:忽略文件中的分支名,用 -b 自动编号
|
|
73
|
+
worktrees = createWorktrees(options.branch, entries.length);
|
|
179
74
|
} else {
|
|
180
|
-
|
|
75
|
+
// 无 -b 参数:使用文件中每个任务的独立分支名
|
|
76
|
+
const branches = entries.map((e) => sanitizeBranchName(e.branch!));
|
|
77
|
+
worktrees = createWorktreesByBranches(branches);
|
|
181
78
|
}
|
|
79
|
+
|
|
80
|
+
// 解析并发数
|
|
81
|
+
const concurrency = parseConcurrency(options.concurrency, getConfigValue('maxConcurrency'));
|
|
82
|
+
|
|
83
|
+
logger.info(`run 命令(文件模式)执行,任务数: ${entries.length},并发数: ${concurrency || '不限制'}`);
|
|
84
|
+
|
|
85
|
+
await executeBatchTasks(worktrees, tasks, concurrency);
|
|
182
86
|
}
|
|
183
87
|
|
|
184
88
|
/**
|
|
185
89
|
* 执行 run 命令的核心逻辑
|
|
186
|
-
*
|
|
187
|
-
*
|
|
90
|
+
* 支持三种模式:
|
|
91
|
+
* 1. -f 任务文件模式
|
|
92
|
+
* 2. --tasks 命令行任务模式
|
|
93
|
+
* 3. 无任务参数时打开交互式界面
|
|
188
94
|
* @param {RunOptions} options - 命令选项
|
|
189
95
|
*/
|
|
190
96
|
async function handleRun(options: RunOptions): Promise<void> {
|
|
191
97
|
validateMainWorktree();
|
|
192
98
|
validateClaudeCodeInstalled();
|
|
193
99
|
|
|
100
|
+
// 互斥校验:--file 和 --tasks 不能同时使用
|
|
101
|
+
if (options.file && options.tasks) {
|
|
102
|
+
throw new ClawtError(MESSAGES.FILE_AND_TASKS_CONFLICT);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// --file 模式
|
|
106
|
+
if (options.file) {
|
|
107
|
+
return handleRunFromFile(options);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 非 --file 模式必须指定 -b
|
|
111
|
+
if (!options.branch) {
|
|
112
|
+
throw new ClawtError(MESSAGES.BRANCH_OR_FILE_REQUIRED);
|
|
113
|
+
}
|
|
114
|
+
|
|
194
115
|
// 未传 --tasks 时,创建单个 worktree 并打开 Claude Code 交互式界面
|
|
195
116
|
if (!options.tasks || options.tasks.length === 0) {
|
|
196
117
|
// 分支已存在时,提示用户使用 resume 恢复会话
|
|
@@ -214,73 +135,14 @@ async function handleRun(options: RunOptions): Promise<void> {
|
|
|
214
135
|
}
|
|
215
136
|
|
|
216
137
|
const count = tasks.length;
|
|
217
|
-
logger.info(`run 命令执行,分支: ${options.branch},任务数: ${count}`);
|
|
218
138
|
|
|
219
|
-
//
|
|
220
|
-
const
|
|
221
|
-
printSuccess(MESSAGES.WORKTREE_CREATED(worktrees.length));
|
|
222
|
-
for (const wt of worktrees) {
|
|
223
|
-
printInfo(` 分支: ${wt.branch} 路径: ${wt.path}`);
|
|
224
|
-
}
|
|
225
|
-
printInfo('');
|
|
139
|
+
// 解析并发数:命令行参数 > 全局配置 > 默认值 0
|
|
140
|
+
const concurrency = parseConcurrency(options.concurrency, getConfigValue('maxConcurrency'));
|
|
226
141
|
|
|
227
|
-
|
|
228
|
-
const startTime = Date.now();
|
|
229
|
-
const handles = worktrees.map((wt, index) => {
|
|
230
|
-
const task = tasks[index];
|
|
231
|
-
logger.info(`启动任务 ${index + 1}: ${task} (worktree: ${wt.path})`);
|
|
232
|
-
return executeClaudeTask(wt, task);
|
|
233
|
-
});
|
|
142
|
+
logger.info(`run 命令执行,分支: ${options.branch},任务数: ${count},并发数: ${concurrency || '不限制'}`);
|
|
234
143
|
|
|
235
|
-
//
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
// 监听 SIGINT(Ctrl+C),终止所有子进程并触发清理流程
|
|
239
|
-
let interrupted = false;
|
|
240
|
-
const sigintHandler = async () => {
|
|
241
|
-
if (interrupted) return;
|
|
242
|
-
interrupted = true;
|
|
243
|
-
|
|
244
|
-
printInfo('');
|
|
245
|
-
printWarning(MESSAGES.INTERRUPTED);
|
|
246
|
-
killAllChildProcesses(childProcesses);
|
|
247
|
-
|
|
248
|
-
// 等待所有子进程退出后再执行清理
|
|
249
|
-
await Promise.allSettled(handles.map((h) => h.promise));
|
|
250
|
-
|
|
251
|
-
await handleInterruptCleanup(worktrees);
|
|
252
|
-
process.exit(1);
|
|
253
|
-
};
|
|
254
|
-
process.on('SIGINT', sigintHandler);
|
|
255
|
-
|
|
256
|
-
const taskPromises = handles.map((handle) =>
|
|
257
|
-
handle.promise.then((result) => {
|
|
258
|
-
// 被中断时不再输出通知
|
|
259
|
-
if (!interrupted) {
|
|
260
|
-
printTaskNotification(result);
|
|
261
|
-
}
|
|
262
|
-
return result;
|
|
263
|
-
}),
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
const results = await Promise.all(taskPromises);
|
|
267
|
-
|
|
268
|
-
// 正常完成,移除 SIGINT 监听器
|
|
269
|
-
process.removeListener('SIGINT', sigintHandler);
|
|
270
|
-
|
|
271
|
-
// 被中断时不输出汇总(已在 sigintHandler 中处理退出)
|
|
272
|
-
if (interrupted) return;
|
|
273
|
-
|
|
274
|
-
const totalDurationMs = Date.now() - startTime;
|
|
275
|
-
|
|
276
|
-
// 汇总
|
|
277
|
-
const summary: TaskSummary = {
|
|
278
|
-
total: results.length,
|
|
279
|
-
succeeded: results.filter((r) => r.success).length,
|
|
280
|
-
failed: results.filter((r) => !r.success).length,
|
|
281
|
-
totalDurationMs,
|
|
282
|
-
totalCostUsd: results.reduce((sum, r) => sum + (r.result?.total_cost_usd ?? 0), 0),
|
|
283
|
-
};
|
|
144
|
+
// 创建 worktree
|
|
145
|
+
const worktrees = createWorktrees(options.branch, count);
|
|
284
146
|
|
|
285
|
-
|
|
147
|
+
await executeBatchTasks(worktrees, tasks, concurrency);
|
|
286
148
|
}
|
package/src/constants/config.ts
CHANGED
package/src/constants/index.ts
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
export { CLAWT_HOME, CONFIG_PATH, LOGS_DIR, WORKTREES_DIR, VALIDATE_SNAPSHOTS_DIR } from './paths.js';
|
|
2
2
|
export { INVALID_BRANCH_CHARS } from './branch.js';
|
|
3
|
-
export { MESSAGES } from './messages.js';
|
|
3
|
+
export { MESSAGES } from './messages/index.js';
|
|
4
4
|
export { EXIT_CODES } from './exitCodes.js';
|
|
5
5
|
export { ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, PASTE_THRESHOLD_MS } from './terminal.js';
|
|
6
6
|
export { DEFAULT_CONFIG, CONFIG_DESCRIPTIONS, APPEND_SYSTEM_PROMPT } from './config.js';
|
|
7
7
|
export { AUTO_SAVE_COMMIT_MESSAGE } from './git.js';
|
|
8
8
|
export { DEBUG_LOG_PREFIX, DEBUG_TIMESTAMP_FORMAT } from './logger.js';
|
|
9
|
+
export {
|
|
10
|
+
SPINNER_FRAMES,
|
|
11
|
+
SPINNER_INTERVAL_MS,
|
|
12
|
+
CURSOR_UP,
|
|
13
|
+
CLEAR_LINE,
|
|
14
|
+
CURSOR_HIDE,
|
|
15
|
+
CURSOR_SHOW,
|
|
16
|
+
TASK_STATUS_ICONS,
|
|
17
|
+
TASK_STATUS_LABELS,
|
|
18
|
+
} from './progress.js';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** 通用/共享提示消息 */
|
|
2
|
+
export const COMMON_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
|
+
/** 分支名清理后为空 */
|
|
12
|
+
BRANCH_NAME_EMPTY: (original: string) =>
|
|
13
|
+
`分支名 "${original}" 中不包含合法字符,无法创建分支`,
|
|
14
|
+
/** 分支名被转换 */
|
|
15
|
+
BRANCH_SANITIZED: (original: string, sanitized: string) =>
|
|
16
|
+
`分支名已转换: ${original} → ${sanitized}`,
|
|
17
|
+
/** worktree 创建成功 */
|
|
18
|
+
WORKTREE_CREATED: (count: number) => `✓ 已创建 ${count} 个 worktree`,
|
|
19
|
+
/** worktree 移除成功 */
|
|
20
|
+
WORKTREE_REMOVED: (path: string) => `✓ 已移除 worktree: ${path}`,
|
|
21
|
+
/** 没有 worktree */
|
|
22
|
+
NO_WORKTREES: '(无 worktree)',
|
|
23
|
+
/** 目标 worktree 不存在 */
|
|
24
|
+
WORKTREE_NOT_FOUND: (name: string) => `worktree ${name} 不存在`,
|
|
25
|
+
/** 主 worktree 有未提交更改 */
|
|
26
|
+
MAIN_WORKTREE_DIRTY: '主 worktree 有未提交的更改,请先处理',
|
|
27
|
+
/** 目标 worktree 无更改 */
|
|
28
|
+
TARGET_WORKTREE_CLEAN: '该 worktree 的分支上没有任何更改,无需验证',
|
|
29
|
+
/** 用户取消破坏性操作 */
|
|
30
|
+
DESTRUCTIVE_OP_CANCELLED: '已取消操作',
|
|
31
|
+
/** 请提供提交信息 */
|
|
32
|
+
COMMIT_MESSAGE_REQUIRED: '请提供提交信息(-m 参数)',
|
|
33
|
+
/** 配置文件损坏,已重新生成默认配置 */
|
|
34
|
+
CONFIG_CORRUPTED: '配置文件损坏或无法解析,已重新生成默认配置',
|
|
35
|
+
/** worktree 状态获取失败 */
|
|
36
|
+
WORKTREE_STATUS_UNAVAILABLE: '(状态不可用)',
|
|
37
|
+
/** 分隔线 */
|
|
38
|
+
SEPARATOR: '────────────────────────────────────────',
|
|
39
|
+
/** 粗分隔线 */
|
|
40
|
+
DOUBLE_SEPARATOR: '════════════════════════════════════════',
|
|
41
|
+
} as const;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { COMMON_MESSAGES } from './common.js';
|
|
2
|
+
import { RUN_MESSAGES } from './run.js';
|
|
3
|
+
import { CREATE_MESSAGES } from './create.js';
|
|
4
|
+
import { MERGE_MESSAGES } from './merge.js';
|
|
5
|
+
import { VALIDATE_MESSAGES } from './validate.js';
|
|
6
|
+
import { SYNC_MESSAGES } from './sync.js';
|
|
7
|
+
import { RESUME_MESSAGES } from './resume.js';
|
|
8
|
+
import { REMOVE_MESSAGES } from './remove.js';
|
|
9
|
+
import { RESET_MESSAGES } from './reset.js';
|
|
10
|
+
import { CONFIG_CMD_MESSAGES } from './config.js';
|
|
11
|
+
import { STATUS_MESSAGES } from './status.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 提示消息模板
|
|
15
|
+
* 合并所有子模块的消息,保持扁平结构以兼容现有的 MESSAGES.XXX 访问方式
|
|
16
|
+
*/
|
|
17
|
+
export const MESSAGES = {
|
|
18
|
+
...COMMON_MESSAGES,
|
|
19
|
+
...RUN_MESSAGES,
|
|
20
|
+
...CREATE_MESSAGES,
|
|
21
|
+
...MERGE_MESSAGES,
|
|
22
|
+
...VALIDATE_MESSAGES,
|
|
23
|
+
...SYNC_MESSAGES,
|
|
24
|
+
...RESUME_MESSAGES,
|
|
25
|
+
...REMOVE_MESSAGES,
|
|
26
|
+
...RESET_MESSAGES,
|
|
27
|
+
...CONFIG_CMD_MESSAGES,
|
|
28
|
+
...STATUS_MESSAGES,
|
|
29
|
+
} as const;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** merge 命令专属提示消息 */
|
|
2
|
+
export const MERGE_MESSAGES = {
|
|
3
|
+
/** merge 成功 */
|
|
4
|
+
MERGE_SUCCESS: (branch: string, message: string, pushed: boolean) =>
|
|
5
|
+
`✓ 分支 ${branch} 已成功合并到当前分支\n 提交信息: ${message}${pushed ? '\n 已推送到远程仓库' : ''}`,
|
|
6
|
+
/** merge 成功(无提交信息,目标 worktree 已提交过) */
|
|
7
|
+
MERGE_SUCCESS_NO_MESSAGE: (branch: string, pushed: boolean) =>
|
|
8
|
+
`✓ 分支 ${branch} 已成功合并到当前分支${pushed ? '\n 已推送到远程仓库' : ''}`,
|
|
9
|
+
/** merge 冲突 */
|
|
10
|
+
MERGE_CONFLICT: '合并存在冲突,请手动处理:\n 解决冲突后执行 git add . && git merge --continue',
|
|
11
|
+
/** merge 后清理 worktree 和分支成功 */
|
|
12
|
+
WORKTREE_CLEANED: (branch: string) => `✓ 已清理 worktree 和分支: ${branch}`,
|
|
13
|
+
/** 目标 worktree 有未提交修改但未指定 -m */
|
|
14
|
+
TARGET_WORKTREE_DIRTY_NO_MESSAGE: '目标 worktree 有未提交的修改,请通过 -m 参数提供提交信息',
|
|
15
|
+
/** 目标 worktree 既干净又无本地提交 */
|
|
16
|
+
TARGET_WORKTREE_NO_CHANGES: '目标 worktree 没有任何可合并的变更(工作区干净且无本地提交)',
|
|
17
|
+
/** merge 命令检测到 validate 状态的提示 */
|
|
18
|
+
MERGE_VALIDATE_STATE_HINT: (branch: string) =>
|
|
19
|
+
`主 worktree 可能存在 validate 残留状态,可先执行 clawt validate -b ${branch} --clean 清理`,
|
|
20
|
+
/** merge 检测到 auto-save 提交,提示用户是否压缩 */
|
|
21
|
+
MERGE_SQUASH_PROMPT: '检测到 sync 产生的临时提交,是否将所有提交压缩为一个?\n 压缩后变更将保留在目标worktree的暂存区,需要重新提交(可使用 Claude Code Cli或其他工具生成提交信息)',
|
|
22
|
+
/** squash 完成且通过 -m 直接提交后的提示 */
|
|
23
|
+
MERGE_SQUASH_COMMITTED: (branch: string) =>
|
|
24
|
+
`✓ 已将分支 ${branch} 的所有提交压缩为一个`,
|
|
25
|
+
/** squash 完成但未提供 -m,提示用户自行提交 */
|
|
26
|
+
MERGE_SQUASH_PENDING: (worktreePath: string, branch: string) =>
|
|
27
|
+
`✓ 已将所有提交压缩到暂存区\n 请在目标 worktree 中提交后重新执行 merge:\n cd ${worktreePath}\n 提交完成后执行:clawt merge -b ${branch}`,
|
|
28
|
+
/** merge 后 pull 冲突 */
|
|
29
|
+
PULL_CONFLICT:
|
|
30
|
+
'自动 pull 时发生冲突,merge 已完成但远程同步失败\n 请手动解决冲突:\n 解决冲突后执行 git add . && git commit\n 然后执行 git push 推送到远程',
|
|
31
|
+
/** push 失败 */
|
|
32
|
+
PUSH_FAILED: '自动 push 失败,merge 和 pull 已完成\n 请手动执行 git push',
|
|
33
|
+
/** merge 无可用 worktree */
|
|
34
|
+
MERGE_NO_WORKTREES: '当前项目没有可用的 worktree,请先通过 clawt run 或 clawt create 创建',
|
|
35
|
+
/** merge 模糊匹配无结果,列出可用分支 */
|
|
36
|
+
MERGE_NO_MATCH: (name: string, branches: string[]) =>
|
|
37
|
+
`未找到与 "${name}" 匹配的分支\n 可用分支:\n${branches.map((b) => ` - ${b}`).join('\n')}`,
|
|
38
|
+
/** merge 交互选择提示 */
|
|
39
|
+
MERGE_SELECT_BRANCH: '请选择要合并的分支',
|
|
40
|
+
/** merge 模糊匹配到多个结果提示 */
|
|
41
|
+
MERGE_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
|
|
42
|
+
} as const;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** remove 命令专属提示消息 */
|
|
2
|
+
export const REMOVE_MESSAGES = {
|
|
3
|
+
/** remove 无可用 worktree */
|
|
4
|
+
REMOVE_NO_WORKTREES: '当前项目没有可用的 worktree,无需移除',
|
|
5
|
+
/** remove 多选交互提示 */
|
|
6
|
+
REMOVE_SELECT_BRANCH: '请选择要移除的分支(空格选择,回车确认)',
|
|
7
|
+
/** remove 模糊匹配到多个结果提示 */
|
|
8
|
+
REMOVE_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择要移除的(空格选择,回车确认):`,
|
|
9
|
+
/** remove 模糊匹配无结果,列出可用分支 */
|
|
10
|
+
REMOVE_NO_MATCH: (name: string, branches: string[]) =>
|
|
11
|
+
`未找到与 "${name}" 匹配的分支\n 可用分支:\n${branches.map((b) => ` - ${b}`).join('\n')}`,
|
|
12
|
+
/** 批量移除部分失败 */
|
|
13
|
+
REMOVE_PARTIAL_FAILURE: (failures: Array<{ path: string; error: string }>) =>
|
|
14
|
+
`以下 worktree 移除失败:\n${failures.map((f) => ` ✗ ${f.path}: ${f.error}`).join('\n')}`,
|
|
15
|
+
} as const;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** resume 命令专属提示消息 */
|
|
2
|
+
export const RESUME_MESSAGES = {
|
|
3
|
+
/** resume 无可用 worktree */
|
|
4
|
+
RESUME_NO_WORKTREES: '当前项目没有可用的 worktree,请先通过 clawt run 或 clawt create 创建',
|
|
5
|
+
/** resume 模糊匹配无结果,列出可用分支 */
|
|
6
|
+
RESUME_NO_MATCH: (name: string, branches: string[]) =>
|
|
7
|
+
`未找到与 "${name}" 匹配的分支\n 可用分支:\n${branches.map((b) => ` - ${b}`).join('\n')}`,
|
|
8
|
+
/** resume 交互选择提示 */
|
|
9
|
+
RESUME_SELECT_BRANCH: '请选择要恢复的分支',
|
|
10
|
+
/** resume 模糊匹配到多个结果提示 */
|
|
11
|
+
RESUME_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
|
|
12
|
+
} as const;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** run 命令专属提示消息 */
|
|
2
|
+
export const RUN_MESSAGES = {
|
|
3
|
+
/** 分支已存在时提示使用 resume */
|
|
4
|
+
BRANCH_EXISTS_USE_RESUME: (name: string) =>
|
|
5
|
+
`分支 ${name} 已存在,请使用 clawt resume -b ${name} 恢复会话`,
|
|
6
|
+
/** 检测到用户中断 */
|
|
7
|
+
INTERRUPTED: '检测到退出指令,已停止 Claude Code 任务',
|
|
8
|
+
/** 中断后自动清理完成 */
|
|
9
|
+
INTERRUPT_AUTO_CLEANED: (count: number) => `✓ 已自动清理 ${count} 个 worktree 和对应分支`,
|
|
10
|
+
/** 中断后手动确认清理 */
|
|
11
|
+
INTERRUPT_CONFIRM_CLEANUP: '是否移除刚刚创建的 worktree 和对应分支?',
|
|
12
|
+
/** 中断后清理完成 */
|
|
13
|
+
INTERRUPT_CLEANED: (count: number) => `✓ 已清理 ${count} 个 worktree 和对应分支`,
|
|
14
|
+
/** 中断后保留 worktree */
|
|
15
|
+
INTERRUPT_KEPT: '已保留 worktree,可稍后使用 clawt remove 手动清理',
|
|
16
|
+
/** 非 TTY 环境降级输出:任务启动 */
|
|
17
|
+
PROGRESS_TASK_STARTED: (index: number, total: number, branch: string, path: string) =>
|
|
18
|
+
`[${index}/${total}] ${branch} 启动 ${path}`,
|
|
19
|
+
/** 非 TTY 环境降级输出:任务完成 */
|
|
20
|
+
PROGRESS_TASK_DONE: (index: number, total: number, branch: string, duration: string, cost: string, path: string) =>
|
|
21
|
+
`[${index}/${total}] ${branch} ✓ 完成 ${duration} ${cost} ${path}`,
|
|
22
|
+
/** 非 TTY 环境降级输出:任务失败 */
|
|
23
|
+
PROGRESS_TASK_FAILED: (index: number, total: number, branch: string, duration: string, path: string) =>
|
|
24
|
+
`[${index}/${total}] ${branch} ✗ 失败 ${duration} ${path}`,
|
|
25
|
+
/** 并发限制提示 */
|
|
26
|
+
CONCURRENCY_INFO: (concurrency: number, total: number) =>
|
|
27
|
+
`并发限制: ${concurrency},共 ${total} 个任务`,
|
|
28
|
+
/** 并发数无效提示 */
|
|
29
|
+
CONCURRENCY_INVALID: '并发数必须为正整数',
|
|
30
|
+
/** 任务文件不存在 */
|
|
31
|
+
TASK_FILE_NOT_FOUND: (path: string) => `任务文件不存在: ${path}`,
|
|
32
|
+
/** 任务文件中没有解析到有效任务 */
|
|
33
|
+
TASK_FILE_EMPTY: '任务文件中没有解析到有效任务',
|
|
34
|
+
/** 任务文件中某个任务块缺少分支名 */
|
|
35
|
+
TASK_FILE_MISSING_BRANCH: (blockIndex: number) => `任务文件第 ${blockIndex} 个任务块缺少分支名(# branch: ...)`,
|
|
36
|
+
/** 任务文件中某个任务块缺少任务描述 */
|
|
37
|
+
TASK_FILE_MISSING_TASK: (branch: string) => `任务文件中分支 ${branch} 缺少任务描述`,
|
|
38
|
+
/** 任务文件中某个任务块缺少任务描述(无分支名时按索引定位) */
|
|
39
|
+
TASK_FILE_MISSING_TASK_BY_INDEX: (blockIndex: number) => `任务文件第 ${blockIndex} 个任务块缺少任务描述`,
|
|
40
|
+
/** --file 和 --tasks 不能同时使用 */
|
|
41
|
+
FILE_AND_TASKS_CONFLICT: '--file 和 --tasks 不能同时使用',
|
|
42
|
+
/** 任务文件加载成功 */
|
|
43
|
+
TASK_FILE_LOADED: (count: number, path: string) => `✓ 从 ${path} 加载了 ${count} 个任务`,
|
|
44
|
+
/** 未指定 -b 或 -f */
|
|
45
|
+
BRANCH_OR_FILE_REQUIRED: '请指定 -b 分支名或 -f 任务文件',
|
|
46
|
+
} as const;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** status 命令专属提示消息 */
|
|
2
|
+
export const STATUS_MESSAGES = {
|
|
3
|
+
/** status 命令标题 */
|
|
4
|
+
STATUS_TITLE: (projectName: string) => `项目状态总览: ${projectName}`,
|
|
5
|
+
/** status 主 worktree 区块标题 */
|
|
6
|
+
STATUS_MAIN_SECTION: '主 Worktree',
|
|
7
|
+
/** status worktrees 区块标题 */
|
|
8
|
+
STATUS_WORKTREES_SECTION: 'Worktree 列表',
|
|
9
|
+
/** status 快照区块标题 */
|
|
10
|
+
STATUS_SNAPSHOTS_SECTION: '未清理的 Validate 快照',
|
|
11
|
+
/** status 无 worktree */
|
|
12
|
+
STATUS_NO_WORKTREES: '(无活跃 worktree)',
|
|
13
|
+
/** status 无未清理快照 */
|
|
14
|
+
STATUS_NO_SNAPSHOTS: '(无未清理的快照)',
|
|
15
|
+
/** status 变更状态:已提交 */
|
|
16
|
+
STATUS_CHANGE_COMMITTED: '已提交',
|
|
17
|
+
/** status 变更状态:未提交修改 */
|
|
18
|
+
STATUS_CHANGE_UNCOMMITTED: '未提交修改',
|
|
19
|
+
/** status 变更状态:合并冲突 */
|
|
20
|
+
STATUS_CHANGE_CONFLICT: '合并冲突',
|
|
21
|
+
/** status 变更状态:无变更 */
|
|
22
|
+
STATUS_CHANGE_CLEAN: '无变更',
|
|
23
|
+
/** status 快照对应 worktree 已不存在 */
|
|
24
|
+
STATUS_SNAPSHOT_ORPHANED: '(对应 worktree 已不存在)',
|
|
25
|
+
} as const;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** sync 命令专属提示消息 */
|
|
2
|
+
export const SYNC_MESSAGES = {
|
|
3
|
+
/** sync 自动保存未提交变更 */
|
|
4
|
+
SYNC_AUTO_COMMITTED: (branch: string) =>
|
|
5
|
+
`已自动保存 ${branch} 分支的未提交变更`,
|
|
6
|
+
/** sync 开始合并 */
|
|
7
|
+
SYNC_MERGING: (targetBranch: string, mainBranch: string) =>
|
|
8
|
+
`正在将 ${mainBranch} 合并到 ${targetBranch} ...`,
|
|
9
|
+
/** sync 成功 */
|
|
10
|
+
SYNC_SUCCESS: (targetBranch: string, mainBranch: string) =>
|
|
11
|
+
`✓ 已将 ${mainBranch} 的最新代码同步到 ${targetBranch}`,
|
|
12
|
+
/** sync 冲突 */
|
|
13
|
+
SYNC_CONFLICT: (worktreePath: string) =>
|
|
14
|
+
`合并存在冲突,请进入目标 worktree 手动解决:\n cd ${worktreePath}\n 解决冲突后执行 git add . && git merge --continue\n clawt validate -b <branch> 验证变更`,
|
|
15
|
+
/** sync 无可用 worktree */
|
|
16
|
+
SYNC_NO_WORKTREES: '当前项目没有可用的 worktree,请先通过 clawt run 或 clawt create 创建',
|
|
17
|
+
/** sync 模糊匹配无结果,列出可用分支 */
|
|
18
|
+
SYNC_NO_MATCH: (name: string, branches: string[]) =>
|
|
19
|
+
`未找到与 "${name}" 匹配的分支\n 可用分支:\n${branches.map((b) => ` - ${b}`).join('\n')}`,
|
|
20
|
+
/** sync 交互选择提示 */
|
|
21
|
+
SYNC_SELECT_BRANCH: '请选择要同步的分支',
|
|
22
|
+
/** sync 模糊匹配到多个结果提示 */
|
|
23
|
+
SYNC_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
|
|
24
|
+
} as const;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** validate 命令专属提示消息 */
|
|
2
|
+
export const VALIDATE_MESSAGES = {
|
|
3
|
+
/** validate 成功 */
|
|
4
|
+
VALIDATE_SUCCESS: (branch: string) =>
|
|
5
|
+
`✓ 已将分支 ${branch} 的变更应用到主 worktree\n 可以开始验证了`,
|
|
6
|
+
/** 增量 validate 成功提示 */
|
|
7
|
+
INCREMENTAL_VALIDATE_SUCCESS: (branch: string) =>
|
|
8
|
+
`✓ 已将分支 ${branch} 的最新变更应用到主 worktree(增量模式)\n 暂存区 = 上次快照,工作目录 = 最新变更`,
|
|
9
|
+
/** 增量 validate 降级为全量模式提示 */
|
|
10
|
+
INCREMENTAL_VALIDATE_FALLBACK: '增量对比失败,已降级为全量模式',
|
|
11
|
+
/** validate 状态已清理 */
|
|
12
|
+
VALIDATE_CLEANED: (branch: string) => `✓ 分支 ${branch} 的 validate 状态已清理`,
|
|
13
|
+
/** validate patch apply 失败,提示用户同步主分支 */
|
|
14
|
+
VALIDATE_PATCH_APPLY_FAILED: (branch: string) =>
|
|
15
|
+
`变更迁移失败:目标分支与主分支差异过大\n 请先执行 clawt sync -b ${branch} 同步主分支后重试`,
|
|
16
|
+
/** validate 无可用 worktree */
|
|
17
|
+
VALIDATE_NO_WORKTREES: '当前项目没有可用的 worktree,请先通过 clawt run 或 clawt create 创建',
|
|
18
|
+
/** validate 模糊匹配无结果,列出可用分支 */
|
|
19
|
+
VALIDATE_NO_MATCH: (name: string, branches: string[]) =>
|
|
20
|
+
`未找到与 "${name}" 匹配的分支\n 可用分支:\n${branches.map((b) => ` - ${b}`).join('\n')}`,
|
|
21
|
+
/** validate 交互选择提示 */
|
|
22
|
+
VALIDATE_SELECT_BRANCH: '请选择要验证的分支',
|
|
23
|
+
/** validate 模糊匹配到多个结果提示 */
|
|
24
|
+
VALIDATE_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
|
|
25
|
+
} as const;
|