clawt 3.5.3 → 3.6.1
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 +24 -44
- package/CLAUDE.md +6 -0
- package/dist/index.js +169 -16
- package/dist/postinstall.js +16 -2
- package/package.json +1 -1
- package/src/commands/home.ts +24 -2
- package/src/commands/init.ts +23 -6
- package/src/commands/resume.ts +91 -0
- package/src/constants/messages/home.ts +2 -0
- package/src/constants/messages/resume.ts +10 -0
- package/src/types/command.ts +12 -0
- package/src/types/index.ts +1 -1
- package/src/utils/git-core.ts +40 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/task-executor.ts +34 -8
- package/src/utils/validation.ts +1 -1
- package/tests/unit/commands/resume.test.ts +241 -3
- package/docs/alias.md +0 -114
- package/docs/completion.md +0 -55
- package/docs/config-file.md +0 -45
- package/docs/config.md +0 -93
- package/docs/cover-validate.md +0 -94
- package/docs/create.md +0 -101
- package/docs/home.md +0 -58
- package/docs/init.md +0 -81
- package/docs/list.md +0 -73
- package/docs/log.md +0 -67
- package/docs/merge.md +0 -137
- package/docs/notification.md +0 -94
- package/docs/project-config.md +0 -132
- package/docs/projects.md +0 -135
- package/docs/remove.md +0 -90
- package/docs/reset.md +0 -37
- package/docs/resume.md +0 -100
- package/docs/run.md +0 -146
- package/docs/spec.md +0 -415
- package/docs/status.md +0 -343
- package/docs/sync.md +0 -128
- package/docs/update-check.md +0 -95
- package/docs/validate.md +0 -416
package/src/commands/resume.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import { logger } from '../logger/index.js';
|
|
3
|
+
import { ClawtError } from '../errors/index.js';
|
|
3
4
|
import { MESSAGES } from '../constants/index.js';
|
|
4
5
|
import type { ResumeOptions } from '../types/index.js';
|
|
5
6
|
import type { WorktreeInfo } from '../types/index.js';
|
|
@@ -12,10 +13,14 @@ import {
|
|
|
12
13
|
hasClaudeSessionHistory,
|
|
13
14
|
resolveTargetWorktrees,
|
|
14
15
|
promptGroupedMultiSelectBranches,
|
|
16
|
+
findExactMatch,
|
|
15
17
|
printInfo,
|
|
16
18
|
printSuccess,
|
|
17
19
|
confirmAction,
|
|
18
20
|
getConfigValue,
|
|
21
|
+
parseConcurrency,
|
|
22
|
+
loadTaskFile,
|
|
23
|
+
executeBatchTasks,
|
|
19
24
|
} from '../utils/index.js';
|
|
20
25
|
import type { WorktreeMultiResolveMessages } from '../utils/index.js';
|
|
21
26
|
|
|
@@ -36,6 +41,9 @@ export function registerResumeCommand(program: Command): void {
|
|
|
36
41
|
.command('resume')
|
|
37
42
|
.description('在已有 worktree 中恢复 Claude Code 会话(支持多选批量恢复)')
|
|
38
43
|
.option('-b, --branch <branchName>', '要恢复的分支名(支持模糊匹配,不传则列出所有分支)')
|
|
44
|
+
.option('--prompt <content>', '非交互式追问(需配合 -b 指定分支)')
|
|
45
|
+
.option('-f, --file <path>', '从任务文件批量追问(通过 branch 名匹配已有 worktree)')
|
|
46
|
+
.option('-c, --concurrency <n>', '批量追问最大并发数,0 表示不限制')
|
|
39
47
|
.action(async (options: ResumeOptions) => {
|
|
40
48
|
await handleResume(options);
|
|
41
49
|
});
|
|
@@ -49,6 +57,25 @@ export function registerResumeCommand(program: Command): void {
|
|
|
49
57
|
async function handleResume(options: ResumeOptions): Promise<void> {
|
|
50
58
|
await runPreChecks(PRE_CHECK_RESUME);
|
|
51
59
|
|
|
60
|
+
// 非交互式追问逻辑分支
|
|
61
|
+
if (options.prompt && options.file) {
|
|
62
|
+
throw new ClawtError(MESSAGES.RESUME_PROMPT_FILE_CONFLICT);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (options.prompt) {
|
|
66
|
+
if (!options.branch) {
|
|
67
|
+
throw new ClawtError(MESSAGES.RESUME_PROMPT_REQUIRES_BRANCH);
|
|
68
|
+
}
|
|
69
|
+
const worktrees = getProjectWorktrees();
|
|
70
|
+
const worktree = resolveWorktreeByBranch(options.branch, worktrees);
|
|
71
|
+
return handleNonInteractiveSingleResume(worktree, options.prompt);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (options.file) {
|
|
75
|
+
return handleNonInteractiveBatchResume(options.file, options);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 原有交互式逻辑
|
|
52
79
|
logger.info(`resume 命令执行,分支过滤: ${options.branch ?? '(无)'}`);
|
|
53
80
|
const worktrees = getProjectWorktrees();
|
|
54
81
|
|
|
@@ -82,6 +109,70 @@ async function handleResume(options: ResumeOptions): Promise<void> {
|
|
|
82
109
|
}
|
|
83
110
|
}
|
|
84
111
|
|
|
112
|
+
/**
|
|
113
|
+
* 精确匹配分支对应的 worktree
|
|
114
|
+
* 找不到时抛出包含可用分支列表的错误
|
|
115
|
+
* @param {string} branch - 目标分支名
|
|
116
|
+
* @param {WorktreeInfo[]} worktrees - 可用的 worktree 列表
|
|
117
|
+
* @returns {WorktreeInfo} 匹配的 worktree
|
|
118
|
+
* @throws {ClawtError} 未找到匹配分支时抛出
|
|
119
|
+
*/
|
|
120
|
+
function resolveWorktreeByBranch(branch: string, worktrees: WorktreeInfo[]): WorktreeInfo {
|
|
121
|
+
const match = findExactMatch(worktrees, branch);
|
|
122
|
+
if (!match) {
|
|
123
|
+
const available = worktrees.map((wt) => wt.branch);
|
|
124
|
+
throw new ClawtError(MESSAGES.RESUME_WORKTREE_NOT_FOUND(branch, available));
|
|
125
|
+
}
|
|
126
|
+
return match;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 非交互式单分支追问
|
|
131
|
+
* 检查 worktree 是否有历史会话,有则通过 --continue 继续最新会话
|
|
132
|
+
* @param {WorktreeInfo} worktree - 目标 worktree
|
|
133
|
+
* @param {string} prompt - 追问内容
|
|
134
|
+
*/
|
|
135
|
+
async function handleNonInteractiveSingleResume(worktree: WorktreeInfo, prompt: string): Promise<void> {
|
|
136
|
+
// 检查是否有历史会话,有则追加 --continue
|
|
137
|
+
const hasPrevious = hasClaudeSessionHistory(worktree.path);
|
|
138
|
+
await executeBatchTasks([worktree], [prompt], 0, [hasPrevious]);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 非交互式批量追问
|
|
143
|
+
* 从任务文件解析追问内容,按 branch 名精确匹配已有 worktree,批量执行
|
|
144
|
+
* @param {string} filePath - 追问任务文件路径
|
|
145
|
+
* @param {ResumeOptions} options - 命令选项(含并发数配置)
|
|
146
|
+
*/
|
|
147
|
+
async function handleNonInteractiveBatchResume(filePath: string, options: ResumeOptions): Promise<void> {
|
|
148
|
+
// 解析追问文件,branch 为必填字段
|
|
149
|
+
const entries = loadTaskFile(filePath, { branchRequired: true });
|
|
150
|
+
printSuccess(MESSAGES.RESUME_FOLLOW_UP_FILE_LOADED(entries.length, filePath));
|
|
151
|
+
|
|
152
|
+
// 获取所有已有 worktree
|
|
153
|
+
const allWorktrees = getProjectWorktrees();
|
|
154
|
+
|
|
155
|
+
// 按 branch 名精确匹配 worktree,构建执行参数
|
|
156
|
+
const worktrees: WorktreeInfo[] = [];
|
|
157
|
+
const tasks: string[] = [];
|
|
158
|
+
const continueFlags: boolean[] = [];
|
|
159
|
+
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
const worktree = resolveWorktreeByBranch(entry.branch!, allWorktrees);
|
|
162
|
+
worktrees.push(worktree);
|
|
163
|
+
tasks.push(entry.task);
|
|
164
|
+
// 按 worktree 独立检查是否有历史会话
|
|
165
|
+
continueFlags.push(hasClaudeSessionHistory(worktree.path));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 解析并发数
|
|
169
|
+
const concurrency = parseConcurrency(options.concurrency, getConfigValue('maxConcurrency'));
|
|
170
|
+
|
|
171
|
+
logger.info(`resume 命令(批量追问模式)执行,任务数: ${entries.length},并发数: ${concurrency || '不限制'}`);
|
|
172
|
+
|
|
173
|
+
await executeBatchTasks(worktrees, tasks, concurrency, continueFlags);
|
|
174
|
+
}
|
|
175
|
+
|
|
85
176
|
/**
|
|
86
177
|
* 输出即将恢复的分支列表(含会话状态:继续/新对话)
|
|
87
178
|
* @param {WorktreeInfo[]} worktrees - 待恢复的 worktree 列表
|
|
@@ -4,4 +4,6 @@ export const HOME_MESSAGES = {
|
|
|
4
4
|
HOME_ALREADY_ON_MAIN: (branch: string) => `已在主工作分支 ${branch} 上,无需切换`,
|
|
5
5
|
/** 切换成功 */
|
|
6
6
|
HOME_SWITCH_SUCCESS: (from: string, to: string) => `✓ 已从 ${from} 切换到主工作分支 ${to}`,
|
|
7
|
+
/** 当前在子 worktree,提示用户手动 cd 到主 worktree */
|
|
8
|
+
HOME_NOT_IN_MAIN_WORKTREE: (mainPath: string) => `当前不在主 worktree,请先切换到主 worktree:\n\n cd ${mainPath}`,
|
|
7
9
|
} as const;
|
|
@@ -18,4 +18,14 @@ export const RESUME_MESSAGES = {
|
|
|
18
18
|
RESUME_ALL_PLATFORM_UNSUPPORTED: '批量 resume 目前仅支持 macOS 平台(通过 AppleScript 打开终端 Tab)',
|
|
19
19
|
/** 批量 resume 无匹配分支提示 */
|
|
20
20
|
RESUME_ALL_NO_MATCH: (keyword: string) => `未找到与 "${keyword}" 匹配的分支`,
|
|
21
|
+
|
|
22
|
+
/** --prompt 必须配合 -b 指定目标分支 */
|
|
23
|
+
RESUME_PROMPT_REQUIRES_BRANCH: '--prompt 必须配合 -b 指定目标分支',
|
|
24
|
+
/** --prompt 和 -f 不能同时使用 */
|
|
25
|
+
RESUME_PROMPT_FILE_CONFLICT: '--prompt 和 -f 不能同时使用',
|
|
26
|
+
/** 未找到对应 worktree */
|
|
27
|
+
RESUME_WORKTREE_NOT_FOUND: (branch: string, available: string[]) =>
|
|
28
|
+
`未找到分支 "${branch}" 对应的 worktree\n 可用分支:\n${available.map((b) => ` - ${b}`).join('\n')}`,
|
|
29
|
+
/** 追问文件加载完成 */
|
|
30
|
+
RESUME_FOLLOW_UP_FILE_LOADED: (count: number, path: string) => `从 ${path} 加载了 ${count} 个追问任务`,
|
|
21
31
|
} as const;
|
package/src/types/command.ts
CHANGED
|
@@ -52,6 +52,12 @@ export interface RemoveOptions {
|
|
|
52
52
|
export interface ResumeOptions {
|
|
53
53
|
/** 要恢复的分支名(可选,不传则列出所有分支供选择) */
|
|
54
54
|
branch?: string;
|
|
55
|
+
/** 非交互式追问内容(需配合 -b 指定分支) */
|
|
56
|
+
prompt?: string;
|
|
57
|
+
/** 从任务文件批量追问(通过 branch 名匹配已有 worktree) */
|
|
58
|
+
file?: string;
|
|
59
|
+
/** 批量追问最大并发数,0 表示不限制(Commander 传入为字符串) */
|
|
60
|
+
concurrency?: string;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
/** sync 命令选项 */
|
|
@@ -88,6 +94,12 @@ export interface InitOptions {
|
|
|
88
94
|
branch?: string;
|
|
89
95
|
}
|
|
90
96
|
|
|
97
|
+
/** init show 子命令选项 */
|
|
98
|
+
export interface InitShowOptions {
|
|
99
|
+
/** 以 JSON 格式输出 */
|
|
100
|
+
json?: boolean;
|
|
101
|
+
}
|
|
102
|
+
|
|
91
103
|
/** tasks init 命令选项 */
|
|
92
104
|
export interface TasksInitOptions {
|
|
93
105
|
/** 输出文件路径(可选,默认 tasks.md) */
|
package/src/types/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { ClawtConfig, ConfigItemDefinition, ConfigDefinitions } from './config.js';
|
|
2
|
-
export type { CreateOptions, RunOptions, ValidateOptions, MergeOptions, RemoveOptions, ResumeOptions, SyncOptions, ListOptions, StatusOptions, ProjectsOptions, InitOptions, TasksInitOptions } from './command.js';
|
|
2
|
+
export type { CreateOptions, RunOptions, ValidateOptions, MergeOptions, RemoveOptions, ResumeOptions, SyncOptions, ListOptions, StatusOptions, ProjectsOptions, InitOptions, InitShowOptions, TasksInitOptions } 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';
|
package/src/utils/git-core.ts
CHANGED
|
@@ -12,6 +12,34 @@ export function getGitCommonDir(cwd?: string): string {
|
|
|
12
12
|
return execCommand('git rev-parse --git-common-dir', { cwd });
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* 判断当前是否在主 worktree 中
|
|
17
|
+
* 条件:git rev-parse --git-common-dir === ".git"
|
|
18
|
+
* @param {string} [cwd] - 工作目录
|
|
19
|
+
* @returns {boolean} 是否在主 worktree 中
|
|
20
|
+
*/
|
|
21
|
+
export function isMainWorktree(cwd?: string): boolean {
|
|
22
|
+
try {
|
|
23
|
+
return getGitCommonDir(cwd) === '.git';
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 判断当前是否在 git 仓库中
|
|
31
|
+
* @param {string} [cwd] - 工作目录
|
|
32
|
+
* @returns {boolean} 是否在 git 仓库中
|
|
33
|
+
*/
|
|
34
|
+
export function isInsideGitRepo(cwd?: string): boolean {
|
|
35
|
+
try {
|
|
36
|
+
execCommand('git rev-parse --is-inside-work-tree', { cwd });
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
15
43
|
/**
|
|
16
44
|
* 获取 git 仓库根目录的绝对路径
|
|
17
45
|
* @param {string} cwd - 工作目录
|
|
@@ -21,6 +49,18 @@ export function getGitTopLevel(cwd?: string): string {
|
|
|
21
49
|
return execCommand('git rev-parse --show-toplevel', { cwd });
|
|
22
50
|
}
|
|
23
51
|
|
|
52
|
+
/**
|
|
53
|
+
* 获取主 worktree 的绝对路径
|
|
54
|
+
* 通过 git worktree list --porcelain 解析第一行(始终为主 worktree)
|
|
55
|
+
* @param {string} [cwd] - 工作目录
|
|
56
|
+
* @returns {string} 主 worktree 的绝对路径
|
|
57
|
+
*/
|
|
58
|
+
export function getMainWorktreePath(cwd?: string): string {
|
|
59
|
+
const output = execCommand('git worktree list --porcelain', { cwd });
|
|
60
|
+
const firstLine = output.split('\n')[0] || '';
|
|
61
|
+
return firstLine.replace('worktree ', '');
|
|
62
|
+
}
|
|
63
|
+
|
|
24
64
|
/**
|
|
25
65
|
* 获取项目名(仓库根目录名称)
|
|
26
66
|
* @param {string} cwd - 工作目录
|
package/src/utils/index.ts
CHANGED
|
@@ -3,7 +3,10 @@ export type { ParallelCommandResult, CommandResultWithStderr, ParallelCommandRes
|
|
|
3
3
|
export { copyToClipboard } from './clipboard.js';
|
|
4
4
|
export {
|
|
5
5
|
getGitCommonDir,
|
|
6
|
+
isMainWorktree,
|
|
7
|
+
isInsideGitRepo,
|
|
6
8
|
getGitTopLevel,
|
|
9
|
+
getMainWorktreePath,
|
|
7
10
|
getProjectName,
|
|
8
11
|
checkBranchExists,
|
|
9
12
|
createWorktree,
|
|
@@ -9,6 +9,7 @@ import { printSuccess, printWarning, printInfo, printDoubleSeparator, confirmAct
|
|
|
9
9
|
import { ProgressRenderer } from './progress.js';
|
|
10
10
|
import { createLineBuffer, parseStreamLine, parseStreamEvent, truncateText } from './stream-parser.js';
|
|
11
11
|
import { RESULT_PREVIEW_MAX_LENGTH } from '../constants/index.js';
|
|
12
|
+
import { ClawtError } from '../errors/index.js';
|
|
12
13
|
|
|
13
14
|
/** executeClaudeTask 的返回结构,包含子进程引用和结果 Promise */
|
|
14
15
|
interface ClaudeTaskHandle {
|
|
@@ -29,13 +30,21 @@ type ActivityCallback = (activityText: string) => void;
|
|
|
29
30
|
* @param {WorktreeInfo} worktree - worktree 信息
|
|
30
31
|
* @param {string} task - 任务描述
|
|
31
32
|
* @param {ActivityCallback} [onActivity] - 活动更新回调(可选)
|
|
33
|
+
* @param {boolean} [continueSession] - 是否继续该 worktree 目录下最新的会话
|
|
32
34
|
* @returns {ClaudeTaskHandle} 包含子进程引用和结果 Promise
|
|
33
35
|
*/
|
|
34
|
-
function executeClaudeTask(worktree: WorktreeInfo, task: string, onActivity?: ActivityCallback): ClaudeTaskHandle {
|
|
36
|
+
function executeClaudeTask(worktree: WorktreeInfo, task: string, onActivity?: ActivityCallback, continueSession?: boolean): ClaudeTaskHandle {
|
|
35
37
|
// 旧版使用 --output-format json,现改为 stream-json --verbose 以支持实时活动信息
|
|
38
|
+
const args = ['-p', task, '--output-format', 'stream-json', '--verbose', '--permission-mode', 'bypassPermissions'];
|
|
39
|
+
|
|
40
|
+
// 追问模式:追加 --continue 继续该目录下最新会话
|
|
41
|
+
if (continueSession) {
|
|
42
|
+
args.push('--continue');
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
const child = spawnProcess(
|
|
37
46
|
'claude',
|
|
38
|
-
|
|
47
|
+
args,
|
|
39
48
|
{
|
|
40
49
|
cwd: worktree.path,
|
|
41
50
|
// stdin 必须设置为 'ignore',不能用 'pipe'
|
|
@@ -198,6 +207,7 @@ function updateRendererStatus(renderer: ProgressRenderer, index: number, result:
|
|
|
198
207
|
* @param {number} startTime - 任务批次启动时间戳
|
|
199
208
|
* @param {() => boolean} isInterrupted - 检查是否已中断的函数
|
|
200
209
|
* @param {ChildProcess[]} childProcesses - 共享子进程数组,执行过程中动态追加
|
|
210
|
+
* @param {boolean[]} [continueFlags] - 按索引对应每个任务是否继续已有会话(追问模式,追加 --continue)
|
|
201
211
|
* @returns {Promise<TaskResult[]>} 所有任务结果
|
|
202
212
|
*/
|
|
203
213
|
async function executeWithConcurrency(
|
|
@@ -208,6 +218,7 @@ async function executeWithConcurrency(
|
|
|
208
218
|
startTime: number,
|
|
209
219
|
isInterrupted: () => boolean,
|
|
210
220
|
childProcesses: ChildProcess[],
|
|
221
|
+
continueFlags?: boolean[],
|
|
211
222
|
): Promise<TaskResult[]> {
|
|
212
223
|
const total = tasks.length;
|
|
213
224
|
const results: TaskResult[] = new Array(total);
|
|
@@ -234,7 +245,7 @@ async function executeWithConcurrency(
|
|
|
234
245
|
|
|
235
246
|
const handle = executeClaudeTask(wt, task, (activityText) => {
|
|
236
247
|
renderer.updateActivityText(index, activityText);
|
|
237
|
-
});
|
|
248
|
+
}, continueFlags?.[index]);
|
|
238
249
|
childProcesses.push(handle.child);
|
|
239
250
|
|
|
240
251
|
handle.promise.then((result) => {
|
|
@@ -272,6 +283,7 @@ async function executeWithConcurrency(
|
|
|
272
283
|
* @param {number} startTime - 任务批次启动时间戳
|
|
273
284
|
* @param {() => boolean} isInterrupted - 检查是否已中断的函数
|
|
274
285
|
* @param {ChildProcess[]} childProcesses - 共享子进程数组,启动时追加
|
|
286
|
+
* @param {boolean[]} [continueFlags] - 按索引对应每个任务是否继续已有会话(追问模式,追加 --continue)
|
|
275
287
|
* @returns {Promise<TaskResult[]>} 所有任务结果
|
|
276
288
|
*/
|
|
277
289
|
async function executeAllParallel(
|
|
@@ -281,13 +293,14 @@ async function executeAllParallel(
|
|
|
281
293
|
startTime: number,
|
|
282
294
|
isInterrupted: () => boolean,
|
|
283
295
|
childProcesses: ChildProcess[],
|
|
296
|
+
continueFlags?: boolean[],
|
|
284
297
|
): Promise<TaskResult[]> {
|
|
285
298
|
const handles = worktrees.map((wt, index) => {
|
|
286
299
|
const task = tasks[index];
|
|
287
300
|
logger.info(`启动任务 ${index + 1}: ${task} (worktree: ${wt.path})`);
|
|
288
301
|
const handle = executeClaudeTask(wt, task, (activityText) => {
|
|
289
302
|
renderer.updateActivityText(index, activityText);
|
|
290
|
-
});
|
|
303
|
+
}, continueFlags?.[index]);
|
|
291
304
|
childProcesses.push(handle.child);
|
|
292
305
|
|
|
293
306
|
return handle;
|
|
@@ -314,14 +327,25 @@ async function executeAllParallel(
|
|
|
314
327
|
* @param {WorktreeInfo[]} worktrees - worktree 列表
|
|
315
328
|
* @param {string[]} tasks - 任务描述列表
|
|
316
329
|
* @param {number} concurrency - 最大并发数,0 表示不限制
|
|
330
|
+
* @param {boolean[]} [continueFlags] - 按索引对应每个任务是否继续已有会话(追问模式,追加 --continue)
|
|
331
|
+
* @returns {Promise<TaskResult[]>} 所有任务的执行结果
|
|
332
|
+
* @throws {ClawtError} continueFlags 长度与任务数不一致时抛出
|
|
317
333
|
*/
|
|
318
334
|
export async function executeBatchTasks(
|
|
319
335
|
worktrees: WorktreeInfo[],
|
|
320
336
|
tasks: string[],
|
|
321
337
|
concurrency: number,
|
|
322
|
-
|
|
338
|
+
continueFlags?: boolean[],
|
|
339
|
+
): Promise<TaskResult[]> {
|
|
323
340
|
const count = tasks.length;
|
|
324
341
|
|
|
342
|
+
// 校验 continueFlags 长度与 worktrees 一致,防止调用方传入不匹配的数组
|
|
343
|
+
if (continueFlags && continueFlags.length !== count) {
|
|
344
|
+
throw new ClawtError(
|
|
345
|
+
`continueFlags 长度 (${continueFlags.length}) 与任务数 (${count}) 不一致`,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
325
349
|
// 有并发限制时输出提示
|
|
326
350
|
if (concurrency > 0) {
|
|
327
351
|
printInfo(MESSAGES.CONCURRENCY_INFO(concurrency, count));
|
|
@@ -374,15 +398,15 @@ export async function executeBatchTasks(
|
|
|
374
398
|
|
|
375
399
|
// 根据并发限制选择执行模式
|
|
376
400
|
const results = concurrency > 0
|
|
377
|
-
? await executeWithConcurrency(worktrees, tasks, concurrency, renderer, startTime, isInterrupted, childProcesses)
|
|
378
|
-
: await executeAllParallel(worktrees, tasks, renderer, startTime, isInterrupted, childProcesses);
|
|
401
|
+
? await executeWithConcurrency(worktrees, tasks, concurrency, renderer, startTime, isInterrupted, childProcesses, continueFlags)
|
|
402
|
+
: await executeAllParallel(worktrees, tasks, renderer, startTime, isInterrupted, childProcesses, continueFlags);
|
|
379
403
|
|
|
380
404
|
// 正常完成,停止进度面板并移除 SIGINT 监听器
|
|
381
405
|
renderer.stop();
|
|
382
406
|
process.removeListener('SIGINT', sigintHandler);
|
|
383
407
|
|
|
384
408
|
// 被中断时不输出汇总(已在 sigintHandler 中处理退出)
|
|
385
|
-
if (interrupted) return;
|
|
409
|
+
if (interrupted) return [];
|
|
386
410
|
|
|
387
411
|
const totalDurationMs = Date.now() - startTime;
|
|
388
412
|
|
|
@@ -396,4 +420,6 @@ export async function executeBatchTasks(
|
|
|
396
420
|
};
|
|
397
421
|
|
|
398
422
|
printTaskSummary(summary);
|
|
423
|
+
|
|
424
|
+
return results;
|
|
399
425
|
}
|
package/src/utils/validation.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface PreCheckOptions {
|
|
|
26
26
|
/**
|
|
27
27
|
* 校验当前目录是否为主 worktree 的根目录
|
|
28
28
|
* 条件:git rev-parse --git-common-dir === ".git"
|
|
29
|
-
* @throws {ClawtError} 不在主 worktree
|
|
29
|
+
* @throws {ClawtError} 不在主 worktree 根目录时抛出(包括不在 git 仓库中的情况)
|
|
30
30
|
*/
|
|
31
31
|
export function validateMainWorktree(): void {
|
|
32
32
|
try {
|