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
|
@@ -5,6 +5,16 @@ vi.mock('../../../src/logger/index.js', () => ({
|
|
|
5
5
|
logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
6
6
|
}));
|
|
7
7
|
|
|
8
|
+
vi.mock('../../../src/errors/index.js', () => ({
|
|
9
|
+
ClawtError: class ClawtError extends Error {
|
|
10
|
+
exitCode: number;
|
|
11
|
+
constructor(message: string, exitCode = 1) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.exitCode = exitCode;
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
|
|
8
18
|
vi.mock('../../../src/constants/index.js', async (importOriginal) => {
|
|
9
19
|
const actual = await importOriginal<typeof import('../../../src/constants/index.js')>();
|
|
10
20
|
return {
|
|
@@ -16,6 +26,11 @@ vi.mock('../../../src/constants/index.js', async (importOriginal) => {
|
|
|
16
26
|
RESUME_NO_MATCH: (keyword: string, branches: string[]) => `未找到匹配 "${keyword}" 的分支`,
|
|
17
27
|
RESUME_ALL_CONFIRM: (count: number) => `确认恢复 ${count} 个分支?`,
|
|
18
28
|
RESUME_ALL_SUCCESS: (count: number) => `已恢复 ${count} 个分支`,
|
|
29
|
+
RESUME_PROMPT_REQUIRES_BRANCH: '--prompt 必须配合 -b 指定目标分支',
|
|
30
|
+
RESUME_PROMPT_FILE_CONFLICT: '--prompt 和 -f 不能同时使用',
|
|
31
|
+
RESUME_WORKTREE_NOT_FOUND: (branch: string, available: string[]) => `未找到分支 "${branch}" 对应的 worktree`,
|
|
32
|
+
RESUME_FOLLOW_UP_FILE_LOADED: (count: number, path: string) => `从 ${path} 加载了 ${count} 个追问任务`,
|
|
33
|
+
CONCURRENCY_INFO: (concurrency: number, total: number) => `并发限制: ${concurrency},共 ${total} 个任务`,
|
|
19
34
|
},
|
|
20
35
|
};
|
|
21
36
|
});
|
|
@@ -29,48 +44,64 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
29
44
|
hasClaudeSessionHistory: vi.fn(),
|
|
30
45
|
resolveTargetWorktrees: vi.fn(),
|
|
31
46
|
promptGroupedMultiSelectBranches: vi.fn(),
|
|
47
|
+
findExactMatch: vi.fn(),
|
|
32
48
|
printInfo: vi.fn(),
|
|
33
49
|
printSuccess: vi.fn(),
|
|
50
|
+
printWarning: vi.fn(),
|
|
34
51
|
confirmAction: vi.fn(),
|
|
35
52
|
getConfigValue: vi.fn(),
|
|
53
|
+
parseConcurrency: vi.fn().mockReturnValue(0),
|
|
54
|
+
loadTaskFile: vi.fn(),
|
|
55
|
+
executeBatchTasks: vi.fn().mockResolvedValue([]),
|
|
36
56
|
}));
|
|
37
57
|
|
|
38
58
|
import { registerResumeCommand } from '../../../src/commands/resume.js';
|
|
39
59
|
import {
|
|
40
60
|
runPreChecks,
|
|
41
|
-
validateClaudeCodeInstalled,
|
|
42
61
|
getProjectWorktrees,
|
|
43
62
|
launchInteractiveClaude,
|
|
44
63
|
launchInteractiveClaudeInNewTerminal,
|
|
45
64
|
hasClaudeSessionHistory,
|
|
46
65
|
resolveTargetWorktrees,
|
|
47
66
|
promptGroupedMultiSelectBranches,
|
|
67
|
+
findExactMatch,
|
|
48
68
|
confirmAction,
|
|
49
69
|
getConfigValue,
|
|
70
|
+
parseConcurrency,
|
|
71
|
+
loadTaskFile,
|
|
72
|
+
executeBatchTasks,
|
|
50
73
|
} from '../../../src/utils/index.js';
|
|
51
74
|
|
|
52
75
|
const mockedRunPreChecks = vi.mocked(runPreChecks);
|
|
53
|
-
const mockedValidateClaudeCodeInstalled = vi.mocked(validateClaudeCodeInstalled);
|
|
54
76
|
const mockedGetProjectWorktrees = vi.mocked(getProjectWorktrees);
|
|
55
77
|
const mockedLaunchInteractiveClaude = vi.mocked(launchInteractiveClaude);
|
|
56
78
|
const mockedLaunchInteractiveClaudeInNewTerminal = vi.mocked(launchInteractiveClaudeInNewTerminal);
|
|
57
79
|
const mockedHasClaudeSessionHistory = vi.mocked(hasClaudeSessionHistory);
|
|
58
80
|
const mockedResolveTargetWorktrees = vi.mocked(resolveTargetWorktrees);
|
|
59
81
|
const mockedPromptGroupedMultiSelectBranches = vi.mocked(promptGroupedMultiSelectBranches);
|
|
82
|
+
const mockedFindExactMatch = vi.mocked(findExactMatch);
|
|
60
83
|
const mockedConfirmAction = vi.mocked(confirmAction);
|
|
61
84
|
const mockedGetConfigValue = vi.mocked(getConfigValue);
|
|
85
|
+
const mockedParseConcurrency = vi.mocked(parseConcurrency);
|
|
86
|
+
const mockedLoadTaskFile = vi.mocked(loadTaskFile);
|
|
87
|
+
const mockedExecuteBatchTasks = vi.mocked(executeBatchTasks);
|
|
62
88
|
|
|
63
89
|
beforeEach(() => {
|
|
64
90
|
mockedRunPreChecks.mockReset();
|
|
65
|
-
mockedValidateClaudeCodeInstalled.mockReset();
|
|
66
91
|
mockedGetProjectWorktrees.mockReset();
|
|
67
92
|
mockedLaunchInteractiveClaude.mockReset();
|
|
68
93
|
mockedLaunchInteractiveClaudeInNewTerminal.mockReset();
|
|
69
94
|
mockedHasClaudeSessionHistory.mockReset();
|
|
70
95
|
mockedResolveTargetWorktrees.mockReset();
|
|
71
96
|
mockedPromptGroupedMultiSelectBranches.mockReset();
|
|
97
|
+
mockedFindExactMatch.mockReset();
|
|
72
98
|
mockedConfirmAction.mockReset();
|
|
73
99
|
mockedGetConfigValue.mockReset();
|
|
100
|
+
mockedParseConcurrency.mockReset();
|
|
101
|
+
mockedParseConcurrency.mockReturnValue(0);
|
|
102
|
+
mockedLoadTaskFile.mockReset();
|
|
103
|
+
mockedExecuteBatchTasks.mockReset();
|
|
104
|
+
mockedExecuteBatchTasks.mockResolvedValue([]);
|
|
74
105
|
});
|
|
75
106
|
|
|
76
107
|
describe('registerResumeCommand', () => {
|
|
@@ -80,6 +111,16 @@ describe('registerResumeCommand', () => {
|
|
|
80
111
|
const cmd = program.commands.find((c) => c.name() === 'resume');
|
|
81
112
|
expect(cmd).toBeDefined();
|
|
82
113
|
});
|
|
114
|
+
|
|
115
|
+
it('注册 --prompt、-f、-c 选项', () => {
|
|
116
|
+
const program = new Command();
|
|
117
|
+
registerResumeCommand(program);
|
|
118
|
+
const cmd = program.commands.find((c) => c.name() === 'resume');
|
|
119
|
+
const options = cmd!.options.map((o) => o.long);
|
|
120
|
+
expect(options).toContain('--prompt');
|
|
121
|
+
expect(options).toContain('--file');
|
|
122
|
+
expect(options).toContain('--concurrency');
|
|
123
|
+
});
|
|
83
124
|
});
|
|
84
125
|
|
|
85
126
|
describe('handleResume', () => {
|
|
@@ -226,3 +267,200 @@ describe('handleResume — resumeInPlace 配置', () => {
|
|
|
226
267
|
expect(mockedGetConfigValue).not.toHaveBeenCalled();
|
|
227
268
|
});
|
|
228
269
|
});
|
|
270
|
+
|
|
271
|
+
describe('handleResume — 非交互式追问', () => {
|
|
272
|
+
it('--prompt + -b 有历史会话时传 [true]', async () => {
|
|
273
|
+
const worktree = { path: '/path/feature', branch: 'feature' };
|
|
274
|
+
mockedGetProjectWorktrees.mockReturnValue([worktree]);
|
|
275
|
+
mockedFindExactMatch.mockReturnValue(worktree);
|
|
276
|
+
mockedHasClaudeSessionHistory.mockReturnValue(true);
|
|
277
|
+
mockedExecuteBatchTasks.mockResolvedValue([]);
|
|
278
|
+
|
|
279
|
+
const program = new Command();
|
|
280
|
+
program.exitOverride();
|
|
281
|
+
registerResumeCommand(program);
|
|
282
|
+
await program.parseAsync(['resume', '-b', 'feature', '--prompt', '加上单元测试'], { from: 'user' });
|
|
283
|
+
|
|
284
|
+
expect(mockedFindExactMatch).toHaveBeenCalled();
|
|
285
|
+
expect(mockedHasClaudeSessionHistory).toHaveBeenCalledWith(worktree.path);
|
|
286
|
+
// 有历史会话时使用 --continue 模式
|
|
287
|
+
expect(mockedExecuteBatchTasks).toHaveBeenCalledWith(
|
|
288
|
+
[worktree],
|
|
289
|
+
['加上单元测试'],
|
|
290
|
+
0,
|
|
291
|
+
[true],
|
|
292
|
+
);
|
|
293
|
+
// 不应走交互式流程
|
|
294
|
+
expect(mockedResolveTargetWorktrees).not.toHaveBeenCalled();
|
|
295
|
+
expect(mockedLaunchInteractiveClaude).not.toHaveBeenCalled();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('--prompt + -b 无历史会话时传 [false]', async () => {
|
|
299
|
+
const worktree = { path: '/path/feature', branch: 'feature' };
|
|
300
|
+
mockedGetProjectWorktrees.mockReturnValue([worktree]);
|
|
301
|
+
mockedFindExactMatch.mockReturnValue(worktree);
|
|
302
|
+
mockedHasClaudeSessionHistory.mockReturnValue(false);
|
|
303
|
+
mockedExecuteBatchTasks.mockResolvedValue([]);
|
|
304
|
+
|
|
305
|
+
const program = new Command();
|
|
306
|
+
program.exitOverride();
|
|
307
|
+
registerResumeCommand(program);
|
|
308
|
+
await program.parseAsync(['resume', '-b', 'feature', '--prompt', '加上单元测试'], { from: 'user' });
|
|
309
|
+
|
|
310
|
+
expect(mockedHasClaudeSessionHistory).toHaveBeenCalledWith(worktree.path);
|
|
311
|
+
// 无历史会话时不传 --continue
|
|
312
|
+
expect(mockedExecuteBatchTasks).toHaveBeenCalledWith(
|
|
313
|
+
[worktree],
|
|
314
|
+
['加上单元测试'],
|
|
315
|
+
0,
|
|
316
|
+
[false],
|
|
317
|
+
);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('--prompt 无 -b 时报错', async () => {
|
|
321
|
+
const program = new Command();
|
|
322
|
+
program.exitOverride();
|
|
323
|
+
registerResumeCommand(program);
|
|
324
|
+
|
|
325
|
+
await expect(
|
|
326
|
+
program.parseAsync(['resume', '--prompt', '加上单元测试'], { from: 'user' }),
|
|
327
|
+
).rejects.toThrow('--prompt 必须配合 -b 指定目标分支');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('--prompt 和 -f 同时使用时报错', async () => {
|
|
331
|
+
const program = new Command();
|
|
332
|
+
program.exitOverride();
|
|
333
|
+
registerResumeCommand(program);
|
|
334
|
+
|
|
335
|
+
await expect(
|
|
336
|
+
program.parseAsync(['resume', '-b', 'feature', '--prompt', '追问', '-f', 'tasks.md'], { from: 'user' }),
|
|
337
|
+
).rejects.toThrow('--prompt 和 -f 不能同时使用');
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('--prompt 指定的分支不存在时报错', async () => {
|
|
341
|
+
mockedGetProjectWorktrees.mockReturnValue([
|
|
342
|
+
{ path: '/path/other', branch: 'other' },
|
|
343
|
+
]);
|
|
344
|
+
mockedFindExactMatch.mockReturnValue(undefined);
|
|
345
|
+
|
|
346
|
+
const program = new Command();
|
|
347
|
+
program.exitOverride();
|
|
348
|
+
registerResumeCommand(program);
|
|
349
|
+
|
|
350
|
+
await expect(
|
|
351
|
+
program.parseAsync(['resume', '-b', 'nonexistent', '--prompt', '追问'], { from: 'user' }),
|
|
352
|
+
).rejects.toThrow('未找到分支');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('-f 批量追问模式', async () => {
|
|
356
|
+
const worktrees = [
|
|
357
|
+
{ path: '/path/feat-a', branch: 'feat-a' },
|
|
358
|
+
{ path: '/path/feat-b', branch: 'feat-b' },
|
|
359
|
+
];
|
|
360
|
+
mockedLoadTaskFile.mockReturnValue([
|
|
361
|
+
{ branch: 'feat-a', task: '追问任务A' },
|
|
362
|
+
{ branch: 'feat-b', task: '追问任务B' },
|
|
363
|
+
]);
|
|
364
|
+
mockedGetProjectWorktrees.mockReturnValue(worktrees);
|
|
365
|
+
mockedFindExactMatch
|
|
366
|
+
.mockReturnValueOnce(worktrees[0])
|
|
367
|
+
.mockReturnValueOnce(worktrees[1]);
|
|
368
|
+
mockedHasClaudeSessionHistory.mockReturnValue(true);
|
|
369
|
+
mockedExecuteBatchTasks.mockResolvedValue([]);
|
|
370
|
+
|
|
371
|
+
const program = new Command();
|
|
372
|
+
program.exitOverride();
|
|
373
|
+
registerResumeCommand(program);
|
|
374
|
+
await program.parseAsync(['resume', '-f', 'follow-up.md'], { from: 'user' });
|
|
375
|
+
|
|
376
|
+
expect(mockedLoadTaskFile).toHaveBeenCalledWith('follow-up.md', { branchRequired: true });
|
|
377
|
+
// 按 worktree 独立检查会话历史
|
|
378
|
+
expect(mockedHasClaudeSessionHistory).toHaveBeenCalledWith('/path/feat-a');
|
|
379
|
+
expect(mockedHasClaudeSessionHistory).toHaveBeenCalledWith('/path/feat-b');
|
|
380
|
+
expect(mockedExecuteBatchTasks).toHaveBeenCalledWith(
|
|
381
|
+
worktrees,
|
|
382
|
+
['追问任务A', '追问任务B'],
|
|
383
|
+
0,
|
|
384
|
+
[true, true],
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('-f 批量追问分支不存在时报错', async () => {
|
|
389
|
+
mockedLoadTaskFile.mockReturnValue([
|
|
390
|
+
{ branch: 'nonexistent', task: '追问任务' },
|
|
391
|
+
]);
|
|
392
|
+
mockedGetProjectWorktrees.mockReturnValue([
|
|
393
|
+
{ path: '/path/feat-a', branch: 'feat-a' },
|
|
394
|
+
]);
|
|
395
|
+
mockedFindExactMatch.mockReturnValue(undefined);
|
|
396
|
+
|
|
397
|
+
const program = new Command();
|
|
398
|
+
program.exitOverride();
|
|
399
|
+
registerResumeCommand(program);
|
|
400
|
+
|
|
401
|
+
await expect(
|
|
402
|
+
program.parseAsync(['resume', '-f', 'follow-up.md'], { from: 'user' }),
|
|
403
|
+
).rejects.toThrow('未找到分支');
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('-f + -c 传递并发数', async () => {
|
|
407
|
+
const worktree = { path: '/path/feat-a', branch: 'feat-a' };
|
|
408
|
+
mockedLoadTaskFile.mockReturnValue([
|
|
409
|
+
{ branch: 'feat-a', task: '追问' },
|
|
410
|
+
]);
|
|
411
|
+
mockedGetProjectWorktrees.mockReturnValue([worktree]);
|
|
412
|
+
mockedFindExactMatch.mockReturnValue(worktree);
|
|
413
|
+
mockedHasClaudeSessionHistory.mockReturnValue(true);
|
|
414
|
+
mockedParseConcurrency.mockReturnValue(2);
|
|
415
|
+
mockedExecuteBatchTasks.mockResolvedValue([]);
|
|
416
|
+
|
|
417
|
+
const program = new Command();
|
|
418
|
+
program.exitOverride();
|
|
419
|
+
registerResumeCommand(program);
|
|
420
|
+
await program.parseAsync(['resume', '-f', 'follow-up.md', '-c', '2'], { from: 'user' });
|
|
421
|
+
|
|
422
|
+
expect(mockedExecuteBatchTasks).toHaveBeenCalledWith(
|
|
423
|
+
[worktree],
|
|
424
|
+
['追问'],
|
|
425
|
+
2,
|
|
426
|
+
[true],
|
|
427
|
+
);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('-f 批量追问按 worktree 独立检查会话历史', async () => {
|
|
431
|
+
const worktrees = [
|
|
432
|
+
{ path: '/path/feat-a', branch: 'feat-a' },
|
|
433
|
+
{ path: '/path/feat-b', branch: 'feat-b' },
|
|
434
|
+
{ path: '/path/feat-c', branch: 'feat-c' },
|
|
435
|
+
];
|
|
436
|
+
mockedLoadTaskFile.mockReturnValue([
|
|
437
|
+
{ branch: 'feat-a', task: '任务A' },
|
|
438
|
+
{ branch: 'feat-b', task: '任务B' },
|
|
439
|
+
{ branch: 'feat-c', task: '任务C' },
|
|
440
|
+
]);
|
|
441
|
+
mockedGetProjectWorktrees.mockReturnValue(worktrees);
|
|
442
|
+
mockedFindExactMatch
|
|
443
|
+
.mockReturnValueOnce(worktrees[0])
|
|
444
|
+
.mockReturnValueOnce(worktrees[1])
|
|
445
|
+
.mockReturnValueOnce(worktrees[2]);
|
|
446
|
+
// feat-a 有历史会话,feat-b 无,feat-c 有
|
|
447
|
+
mockedHasClaudeSessionHistory
|
|
448
|
+
.mockReturnValueOnce(true)
|
|
449
|
+
.mockReturnValueOnce(false)
|
|
450
|
+
.mockReturnValueOnce(true);
|
|
451
|
+
mockedExecuteBatchTasks.mockResolvedValue([]);
|
|
452
|
+
|
|
453
|
+
const program = new Command();
|
|
454
|
+
program.exitOverride();
|
|
455
|
+
registerResumeCommand(program);
|
|
456
|
+
await program.parseAsync(['resume', '-f', 'follow-up.md'], { from: 'user' });
|
|
457
|
+
|
|
458
|
+
// continueFlags 应按 worktree 独立反映各自的会话历史状态
|
|
459
|
+
expect(mockedExecuteBatchTasks).toHaveBeenCalledWith(
|
|
460
|
+
worktrees,
|
|
461
|
+
['任务A', '任务B', '任务C'],
|
|
462
|
+
0,
|
|
463
|
+
[true, false, true],
|
|
464
|
+
);
|
|
465
|
+
});
|
|
466
|
+
});
|
package/docs/alias.md
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
### 5.15 命令别名管理
|
|
2
|
-
|
|
3
|
-
**命令:**
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
# 列出所有命令别名
|
|
7
|
-
clawt alias
|
|
8
|
-
clawt alias list
|
|
9
|
-
|
|
10
|
-
# 设置命令别名
|
|
11
|
-
clawt alias set <alias> <command>
|
|
12
|
-
|
|
13
|
-
# 移除命令别名
|
|
14
|
-
clawt alias remove <alias>
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
**子命令:**
|
|
18
|
-
|
|
19
|
-
| 子命令 | 说明 |
|
|
20
|
-
| ------ | ---- |
|
|
21
|
-
| `clawt alias` / `clawt alias list` | 列出所有已配置的命令别名 |
|
|
22
|
-
| `clawt alias set <alias> <command>` | 设置命令别名,将 `<alias>` 映射到 `<command>` |
|
|
23
|
-
| `clawt alias remove <alias>` | 移除指定的命令别名 |
|
|
24
|
-
|
|
25
|
-
**参数:**
|
|
26
|
-
|
|
27
|
-
| 参数 | 必填 | 说明 |
|
|
28
|
-
| ---- | ---- | ---- |
|
|
29
|
-
| `<alias>` | 是(set / remove) | 别名名称 |
|
|
30
|
-
| `<command>` | 是(set) | 目标内置命令名 |
|
|
31
|
-
|
|
32
|
-
**约束规则:**
|
|
33
|
-
|
|
34
|
-
1. **别名不能覆盖内置命令名**:别名不能与任何已注册的内置命令同名(动态检测,当前包括 `list`、`create`、`remove`、`run`、`resume`、`validate`、`cover`、`merge`、`config`、`sync`、`reset`、`status`、`alias`、`projects`、`completion`、`init`、`home`)。如果用户尝试设置与内置命令同名的别名,输出错误提示并返回
|
|
35
|
-
2. **目标必须是内置命令**:别名的目标(`<command>`)必须是已注册的内置命令名。如果指定了不存在的目标命令,输出错误提示并返回
|
|
36
|
-
3. **参数透传**:通过别名调用时,所有选项和参数会完全透传给目标命令,行为与直接调用目标命令完全一致
|
|
37
|
-
|
|
38
|
-
**持久化:**
|
|
39
|
-
|
|
40
|
-
别名配置存储在 `~/.clawt/config.json` 的 `aliases` 字段中(类型 `Record<string, string>`,默认 `{}`)。
|
|
41
|
-
|
|
42
|
-
**运行流程:**
|
|
43
|
-
|
|
44
|
-
#### `alias list`(默认)
|
|
45
|
-
|
|
46
|
-
1. 读取配置文件中的 `aliases` 字段
|
|
47
|
-
2. 如果没有配置任何别名,输出提示 `(无别名)`
|
|
48
|
-
3. 如果有别名,逐行输出所有别名映射
|
|
49
|
-
|
|
50
|
-
**输出格式:**
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
当前别名列表:
|
|
54
|
-
────────────────────────────────────────
|
|
55
|
-
|
|
56
|
-
l → list
|
|
57
|
-
r → run
|
|
58
|
-
v → validate
|
|
59
|
-
|
|
60
|
-
────────────────────────────────────────
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
#### `alias set <alias> <command>`
|
|
64
|
-
|
|
65
|
-
1. **校验别名不与内置命令冲突**:检查 `<alias>` 是否为内置命令名,是则输出错误提示并返回
|
|
66
|
-
2. **校验目标命令存在**:检查 `<command>` 是否为已注册的内置命令名,不是则输出错误提示并返回
|
|
67
|
-
3. 将别名写入配置文件的 `aliases` 字段(如果别名已存在,覆盖旧值)
|
|
68
|
-
4. 输出成功提示
|
|
69
|
-
|
|
70
|
-
**输出格式:**
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
✓ 已设置别名: l → list
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
#### `alias remove <alias>`
|
|
77
|
-
|
|
78
|
-
1. 读取配置文件中的 `aliases` 字段
|
|
79
|
-
2. 检查指定的别名是否存在,不存在则输出错误提示并返回
|
|
80
|
-
3. 从 `aliases` 中删除该别名并写入配置文件
|
|
81
|
-
4. 输出成功提示
|
|
82
|
-
|
|
83
|
-
**输出格式:**
|
|
84
|
-
|
|
85
|
-
```
|
|
86
|
-
✓ 已移除别名: l
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
**别名使用示例:**
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
# 设置别名
|
|
93
|
-
clawt alias set l list
|
|
94
|
-
clawt alias set r run
|
|
95
|
-
clawt alias set v validate
|
|
96
|
-
|
|
97
|
-
# 使用别名(等同于对应的完整命令)
|
|
98
|
-
clawt l # 等同于 clawt list
|
|
99
|
-
clawt r task.md # 等同于 clawt run task.md
|
|
100
|
-
|
|
101
|
-
# 查看所有别名
|
|
102
|
-
clawt alias list
|
|
103
|
-
|
|
104
|
-
# 移除别名
|
|
105
|
-
clawt alias remove l
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
**实现要点:**
|
|
109
|
-
|
|
110
|
-
- 消息常量定义在 `src/constants/messages/alias.ts`(`ALIAS_MESSAGES`),包括列表为空提示、设置/移除成功、别名不存在、与内置命令冲突、目标命令不存在等消息
|
|
111
|
-
- 别名应用逻辑位于 `src/utils/alias.ts` 的 `applyAliases` 函数:在主入口 `src/index.ts` 中,所有命令注册完成后调用,遍历配置中的 `aliases` 映射,通过 Commander.js 的 `.alias()` 方法为对应命令注册别名。如果目标命令不存在则跳过并输出 warn 级别日志
|
|
112
|
-
- 内置命令冲突检测通过 `getRegisteredCommandNames(program)` 动态获取所有已注册命令名,而非硬编码命令列表
|
|
113
|
-
|
|
114
|
-
---
|
package/docs/completion.md
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
### 5.16 `clawt completion` 命令
|
|
2
|
-
|
|
3
|
-
为终端环境(bash/zsh)生成并安装 `clawt` 的命令、选项及参数的自动补全脚本。
|
|
4
|
-
|
|
5
|
-
#### 语法
|
|
6
|
-
```bash
|
|
7
|
-
clawt completion bash
|
|
8
|
-
clawt completion zsh
|
|
9
|
-
clawt completion install
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
#### 子命令说明
|
|
13
|
-
|
|
14
|
-
| 子命令 | 说明 |
|
|
15
|
-
| --------- | ----------------------------------------------------------------------------------- |
|
|
16
|
-
| `bash` | 输出适用于 bash 的补全脚本(用户可重定向到 `~/.bashrc`) |
|
|
17
|
-
| `zsh` | 输出适用于 zsh 的补全脚本(用户可重定向到 `~/.zshrc`) |
|
|
18
|
-
| `install` | 自动检测当前 shell 类型,将补全脚本追加到对应的配置文件中 |
|
|
19
|
-
|
|
20
|
-
#### `install` 子命令流程
|
|
21
|
-
|
|
22
|
-
1. 通过 `process.env.SHELL` 检测当前 shell 类型
|
|
23
|
-
2. 根据 shell 类型确定目标配置文件:
|
|
24
|
-
- zsh → `~/.zshrc`(追加 `source <(clawt completion zsh)`)
|
|
25
|
-
- bash → `~/.bashrc`(追加 `eval "$(clawt completion bash)"`)
|
|
26
|
-
3. 检查目标文件中是否已包含 `clawt completion`,已存在则跳过并提示
|
|
27
|
-
4. 追加成功后提示用户重启终端或 source 配置文件
|
|
28
|
-
5. 未知 shell 类型时输出警告,提示手动配置
|
|
29
|
-
|
|
30
|
-
#### 动态补全特性
|
|
31
|
-
|
|
32
|
-
补全脚本通过内部子命令 `_complete` 实现动态补全,不对外公开。补全引擎基于 Commander.js 的命令树结构遍历,支持以下场景:
|
|
33
|
-
|
|
34
|
-
| 场景 | 补全行为 |
|
|
35
|
-
| ---------------------------- | ---------------------------------------------------------- |
|
|
36
|
-
| `-b` / `--branch` 参数之后 | 动态列出当前项目所有 worktree 分支名(通过 `getProjectWorktrees`) |
|
|
37
|
-
| `-f` / `--file` 参数之后 | 动态列出匹配的文件和子目录(不限制文件类型,支持子目录递归浏览) |
|
|
38
|
-
| `config set` / `config get` 之后 | 动态列出所有配置项键名(从 `CONFIG_DEFINITIONS` 获取) |
|
|
39
|
-
| 输入以 `-` 开头 | 列出当前命令层级的可用选项(short/long) |
|
|
40
|
-
| 其他情况 | 列出当前命令层级的可用子命令及别名;若输入为空,同时列出可用选项 |
|
|
41
|
-
|
|
42
|
-
**文件路径补全细节:**
|
|
43
|
-
- 支持子目录递归浏览(如 `tasks/` 后继续 Tab 可深入子目录)
|
|
44
|
-
- 目录候选项以 `/` 结尾,补全时不自动追加空格
|
|
45
|
-
- 不限制文件类型,列出所有非隐藏文件
|
|
46
|
-
- 跳过隐藏文件和目录(以 `.` 开头)
|
|
47
|
-
|
|
48
|
-
#### 实现说明
|
|
49
|
-
|
|
50
|
-
- 补全命令注册函数:`registerCompletionCommand()`(在 `src/commands/completion.ts`)
|
|
51
|
-
- 消息常量:`COMPLETION_MESSAGES`(在 `src/constants/messages/completion.ts`)
|
|
52
|
-
- 核心函数:`generateCompletions()` 解析当前输入上下文并输出候选项,`completeFilePath()` 处理文件路径补全,`tryCompleteSpecialArg()` 处理特殊参数(分支名、文件路径、配置键)的动态补全,`completeFromCommandTree()` 基于命令树遍历生成子命令和选项候选项
|
|
53
|
-
- shell 脚本生成:`getBashScript()`、`getZshScript()` 分别生成对应 shell 的补全脚本
|
|
54
|
-
|
|
55
|
-
---
|
package/docs/config-file.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
### 5.7 默认配置文件
|
|
2
|
-
|
|
3
|
-
**路径:** `~/.clawt/config.json`
|
|
4
|
-
|
|
5
|
-
**生成时机:** 全局安装后自动生成(通过 `postinstall` 脚本)。
|
|
6
|
-
|
|
7
|
-
**升级策略:** 配置文件已存在时,执行增量合并而非简单跳过:
|
|
8
|
-
|
|
9
|
-
- **新版本新增的配置项** → 使用默认值补充到用户配置中
|
|
10
|
-
- **用户已有的配置项** → 保留用户的值,不覆盖
|
|
11
|
-
- **新版本已移除的配置项** → 从用户配置中删除
|
|
12
|
-
|
|
13
|
-
仅在合并后配置发生变化时才写入文件。配置文件损坏或无法解析时,视为不存在,重新生成默认配置。
|
|
14
|
-
|
|
15
|
-
**默认内容:**
|
|
16
|
-
|
|
17
|
-
```json
|
|
18
|
-
{
|
|
19
|
-
"autoDeleteBranch": false,
|
|
20
|
-
"claudeCodeCommand": "claude",
|
|
21
|
-
"autoPullPush": false,
|
|
22
|
-
"confirmDestructiveOps": true,
|
|
23
|
-
"maxConcurrency": 0,
|
|
24
|
-
"terminalApp": "auto",
|
|
25
|
-
"resumeInPlace": false,
|
|
26
|
-
"aliases": {},
|
|
27
|
-
"autoUpdate": true
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
**配置项说明:**
|
|
32
|
-
|
|
33
|
-
| 配置项 | 类型 | 默认值 | 说明 |
|
|
34
|
-
| ------------------ | --------- | --------- | -------------------------------------------------- |
|
|
35
|
-
| `autoDeleteBranch` | `boolean` | `false` | 移除 worktree 时是否自动删除对应本地分支(无需每次确认);merge 成功后是否自动清理 worktree 和分支;run 任务被中断(Ctrl+C)后是否自动清理本次创建的 worktree 和分支 |
|
|
36
|
-
| `claudeCodeCommand` | `string` | `"claude"` | Claude Code CLI 启动指令,用于 `clawt run` 不传 `--tasks` 时和 `clawt resume` 在 worktree 中打开交互式界面 |
|
|
37
|
-
| `autoPullPush` | `boolean` | `false` | merge 成功后是否自动执行 git pull 和 git push |
|
|
38
|
-
| `confirmDestructiveOps` | `boolean` | `true` | 执行破坏性操作(reset、validate --clean)前是否提示确认 |
|
|
39
|
-
| `maxConcurrency` | `number` | `0` | run 命令默认最大并发数,`0` 表示不限制 |
|
|
40
|
-
| `terminalApp` | `string` | `"auto"` | 批量 resume 使用的终端应用:`auto`(自动检测)、`iterm2`、`terminal`(macOS) |
|
|
41
|
-
| `resumeInPlace` | `boolean` | `false` | resume 单选时是否在当前终端就地打开,`false` 则通过 `terminalApp` 在新 Tab 中打开 |
|
|
42
|
-
| `aliases` | `Record<string, string>` | `{}` | 命令别名映射,键为别名,值为目标内置命令名 |
|
|
43
|
-
| `autoUpdate` | `boolean` | `true` | 是否启用自动更新检查(每 24 小时通过 npm registry 检查一次新版本) |
|
|
44
|
-
|
|
45
|
-
---
|
package/docs/config.md
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
### 5.10 交互式查看和修改全局配置
|
|
2
|
-
|
|
3
|
-
**命令:**
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
# 交互式修改配置(等同于 config set 无参数)
|
|
7
|
-
clawt config
|
|
8
|
-
|
|
9
|
-
# 修改配置项(无参数进入交互式,有参数直接设置)
|
|
10
|
-
clawt config set [key] [value]
|
|
11
|
-
|
|
12
|
-
# 获取单个配置项的值
|
|
13
|
-
clawt config get <key>
|
|
14
|
-
|
|
15
|
-
# 将配置恢复为默认值
|
|
16
|
-
clawt config reset
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
#### 交互式修改配置(`config` / `config set`)
|
|
20
|
-
|
|
21
|
-
直接执行 `clawt config` 或 `clawt config set`(不带参数)进入交互式配置修改模式。
|
|
22
|
-
|
|
23
|
-
**运行流程:**
|
|
24
|
-
|
|
25
|
-
1. 读取全局配置文件 `~/.clawt/config.json`
|
|
26
|
-
2. 列出所有配置项供用户选择(`Enquirer.Select`),每项显示:
|
|
27
|
-
- 配置项名称
|
|
28
|
-
- 当前值(布尔值绿色/黄色,字符串和数字青色)
|
|
29
|
-
- 配置项描述(暗淡色 dim)
|
|
30
|
-
- 对象类型配置项(如 `aliases`)标灰不可选,提示用户通过专用命令管理
|
|
31
|
-
3. 用户选择某个配置项后,根据值类型自动选择提示策略:
|
|
32
|
-
- **boolean 类型** → `Select`(true / false)
|
|
33
|
-
- **number 类型** → `Input`(带数字校验)
|
|
34
|
-
- **string 类型 + 有 `allowedValues`** → `Select`(枚举列表)
|
|
35
|
-
- **string 类型 + 无 `allowedValues`** → `Input`(自由输入)
|
|
36
|
-
4. 将修改后的配置持久化到配置文件
|
|
37
|
-
5. 输出成功提示:`✓ <key> 已设置为 <value>`
|
|
38
|
-
|
|
39
|
-
#### 直接设置配置项(`config set <key> <value>`)
|
|
40
|
-
|
|
41
|
-
当带参数执行 `clawt config set <key> <value>` 时,直接修改指定配置项。
|
|
42
|
-
|
|
43
|
-
**参数:**
|
|
44
|
-
|
|
45
|
-
| 参数 | 必填 | 说明 |
|
|
46
|
-
| ---- | ---- | ---- |
|
|
47
|
-
| `key` | 否 | 配置项名称(不传则进入交互式模式) |
|
|
48
|
-
| `value` | 否 | 配置值(传了 `key` 时必填) |
|
|
49
|
-
|
|
50
|
-
**运行流程:**
|
|
51
|
-
|
|
52
|
-
1. 校验 `key` 是否为有效的配置项名称(基于 `DEFAULT_CONFIG` 的键列表),无效则输出错误及可用配置项列表
|
|
53
|
-
2. 校验 `value` 是否缺失,缺失则提示:`缺少配置值,用法: clawt config set <key> <value>`
|
|
54
|
-
3. 根据目标配置项的类型解析并校验值:
|
|
55
|
-
- **boolean** → 仅接受 `true` 或 `false`
|
|
56
|
-
- **number** → `Number()` 解析,`NaN` 报错
|
|
57
|
-
- **string + 有 `allowedValues`** → 校验值是否在枚举列表中
|
|
58
|
-
- **string + 无 `allowedValues`** → 无额外校验
|
|
59
|
-
4. 加载配置、修改目标项、持久化
|
|
60
|
-
5. 输出成功提示:`✓ <key> 已设置为 <value>`
|
|
61
|
-
|
|
62
|
-
#### 获取单个配置项(`config get <key>`)
|
|
63
|
-
|
|
64
|
-
**参数:**
|
|
65
|
-
|
|
66
|
-
| 参数 | 必填 | 说明 |
|
|
67
|
-
| ---- | ---- | ---- |
|
|
68
|
-
| `key` | 是 | 配置项名称 |
|
|
69
|
-
|
|
70
|
-
**运行流程:**
|
|
71
|
-
|
|
72
|
-
1. 校验 `key` 是否为有效的配置项名称,无效则输出错误及可用配置项列表
|
|
73
|
-
2. 读取配置文件,获取目标配置项的值
|
|
74
|
-
3. 输出:`<key> = <value>`
|
|
75
|
-
|
|
76
|
-
#### 恢复默认配置(`config reset`)
|
|
77
|
-
|
|
78
|
-
**运行流程:**
|
|
79
|
-
|
|
80
|
-
1. 始终提示确认(显示即将执行的操作和后果:当前配置将被覆盖为默认值),不受 `confirmDestructiveOps` 配置控制。用户取消则退出
|
|
81
|
-
2. 将默认配置写入 `~/.clawt/config.json`(覆盖现有配置文件)
|
|
82
|
-
3. 输出成功提示:`✓ 配置已恢复为默认值`
|
|
83
|
-
|
|
84
|
-
**实现要点:**
|
|
85
|
-
|
|
86
|
-
- 配置项类型定义:`ConfigItemDefinition` 新增可选字段 `allowedValues`(`readonly string[]`),仅对 string 类型有效,用于枚举值校验和交互式 Select 提示
|
|
87
|
-
- 值解析与提示策略:`src/utils/config-strategy.ts` 中的 `parseConfigValue()`(CLI 字符串解析)和 `promptConfigValue()`(交互式提示),基于类型和 `allowedValues` 自动分发
|
|
88
|
-
- 交互式配置编辑:`handleInteractiveConfigSet` 调用通用的 `interactiveConfigEditor`(`src/utils/config-strategy.ts`),传入 `CONFIG_DEFINITIONS` 和 `disabledKeys`(对象类型配置项禁用映射),不再在 config 命令中直接构建选择列表和调用 `promptConfigValue`
|
|
89
|
-
- `saveConfig(config)`:`src/utils/config.ts` 中的通用配置写入函数,将完整配置对象持久化到文件
|
|
90
|
-
- `formatConfigValue(value)`:支持 boolean(绿色/黄色)和 string/number(青色)的格式化显示。`undefined` / `null` 值显示为暗淡色的 `(未设置)`
|
|
91
|
-
- 对象类型配置项(如 `aliases`)的显示逻辑在 `interactiveConfigEditor` 的列表构建中处理:通过 `JSON.stringify` 以暗淡色显示值,并标记为不可选(disabled),提示用户通过 `clawt alias` 命令管理
|
|
92
|
-
|
|
93
|
-
---
|