clawt 2.14.0 → 2.15.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 +8 -3
- package/README.md +43 -3
- package/dist/index.js +162 -49
- package/dist/postinstall.js +18 -2
- package/docs/spec.md +109 -23
- package/package.json +1 -1
- package/src/commands/config.ts +10 -5
- package/src/commands/run.ts +68 -21
- package/src/constants/config.ts +1 -1
- package/src/constants/index.ts +1 -0
- package/src/constants/messages/config.ts +3 -0
- package/src/constants/messages/index.ts +3 -1
- package/src/constants/messages/run.ts +16 -0
- package/src/types/command.ts +2 -0
- package/src/utils/config.ts +20 -0
- package/src/utils/dry-run.ts +89 -0
- package/src/utils/index.ts +3 -2
- package/src/utils/task-file.ts +18 -0
- package/tests/unit/commands/config.test.ts +34 -3
- package/tests/unit/commands/resume.test.ts +9 -9
- package/tests/unit/commands/run.test.ts +160 -6
- package/tests/unit/constants/messages.test.ts +5 -1
- package/tests/unit/utils/config.test.ts +23 -1
- package/tests/unit/utils/dry-run.test.ts +124 -0
- package/tests/unit/utils/task-file.test.ts +26 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
- 命令流程在 `5. 需求场景详细设计` 下,每个命令一个子章节(5.1-5.14)
|
|
8
8
|
- run 命令对应 `5.2 批量创建 Worktree + 执行 Claude Code 任务`,流程按步骤编号描述
|
|
9
9
|
- merge 命令对应 `5.6 合并验证过的分支`,-b 可选,支持模糊匹配(与 resume/validate 共享匹配逻辑),流程按步骤编号描述
|
|
10
|
-
- config 命令对应 `5.10
|
|
10
|
+
- config 命令对应 `5.10 交互式查看和修改全局配置`,包含四个子章节:交互式修改(config / config set 无参数)、直接设置(config set key value)、获取(config get)、恢复默认(config reset),使用 `####` 子标题区分
|
|
11
11
|
- resume 命令对应 `5.11 在已有 Worktree 中恢复会话`,统一使用多选交互(resolveTargetWorktrees),选 1 个当前终端恢复,选多个在独立终端 Tab 批量恢复(-b 可选)
|
|
12
12
|
- validate 命令对应 `5.4 在主 Worktree 验证其他分支`,-b 可选,支持模糊匹配(与 resume 共享匹配逻辑)
|
|
13
13
|
- sync 命令对应 `5.12 将主分支代码同步到目标 Worktree`,-b 可选,支持模糊匹配(与 resume/validate/merge 共享匹配逻辑)
|
|
@@ -72,9 +72,9 @@ run 命令有两种模式(自 claudeCodeCommand 特性后):
|
|
|
72
72
|
- `formatDuration` 从 `src/utils/progress.ts` 移至 `src/utils/formatter.ts`
|
|
73
73
|
- 进度面板每个任务行末尾显示 worktree 路径(终端可点击跳转)
|
|
74
74
|
|
|
75
|
-
## 命令清单(
|
|
75
|
+
## 命令清单(12 个)
|
|
76
76
|
|
|
77
|
-
`create`、`run`、`resume`、`list`、`remove`、`validate`、`merge`、`config`、`sync`、`reset`、`status`
|
|
77
|
+
`create`、`run`、`resume`、`list`、`remove`、`validate`、`merge`、`config`、`sync`、`reset`、`status`、`alias`
|
|
78
78
|
|
|
79
79
|
Notes:
|
|
80
80
|
- resume 和 run(交互式模式)共用 `launchInteractiveClaude()`,该函数从 run.ts 提取到 src/utils/claude.ts
|
|
@@ -91,6 +91,11 @@ Notes:
|
|
|
91
91
|
- `promptMultiSelectBranches` 支持「全选」选项(顶部 [select-all]),通过扩展 MultiSelect 覆写 space() 实现全选 toggle
|
|
92
92
|
- `SELECT_ALL_NAME` 和 `SELECT_ALL_LABEL` 常量定义在 `src/constants/prompt.ts`
|
|
93
93
|
- `VALID_TERMINAL_APPS` 和 `ITERM2_APP_PATH` 常量定义在 `src/constants/terminal.ts`
|
|
94
|
+
- config 交互式修改:对象类型配置项(如 `aliases`)在 Enquirer.Select 列表中标灰不可选(`disabled: CONFIG_ALIAS_DISABLED_HINT`),提示用户通过专用命令管理
|
|
95
|
+
- `CONFIG_ALIAS_DISABLED_HINT` 常量定义在 `src/constants/messages/config.ts`,通过 `src/constants/messages/index.ts` 和 `src/constants/index.ts` 导出
|
|
96
|
+
- `parseConfigValue()` 和 `promptConfigValue()` 在 `src/utils/config-strategy.ts`,基于配置项类型和 `allowedValues` 自动分发值解析/提示策略
|
|
97
|
+
- `saveConfig()` 在 `src/utils/config.ts`,通用配置写入函数
|
|
98
|
+
- `ConfigItemDefinition` 支持可选 `allowedValues` 字段(`readonly string[]`),用于 string 类型枚举校验
|
|
94
99
|
|
|
95
100
|
## validate 快照机制
|
|
96
101
|
|
package/README.md
CHANGED
|
@@ -52,6 +52,29 @@ clawt run -f tasks.md
|
|
|
52
52
|
|
|
53
53
|
# 从任务文件读取任务,但用 -b 自动编号分支(文件中分支名可省略)
|
|
54
54
|
clawt run -f tasks.md -b feat
|
|
55
|
+
|
|
56
|
+
# 试运行:仅预览将要创建的 worktree 和任务,不实际执行
|
|
57
|
+
clawt run -b <branch> --tasks "任务1" --tasks "任务2" --dry-run
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**`--dry-run` 预览示例:**
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
════════════════════════════════════════
|
|
64
|
+
Dry Run 预览
|
|
65
|
+
════════════════════════════════════════
|
|
66
|
+
任务数: 2 │ 并发数: 不限制 │ Worktree: ~/.clawt/worktrees/project
|
|
67
|
+
────────────────────────────────────────
|
|
68
|
+
✓ [1/2] feat-1
|
|
69
|
+
路径: ~/.clawt/worktrees/project/feat-1
|
|
70
|
+
任务: 任务1
|
|
71
|
+
|
|
72
|
+
✓ [2/2] feat-2
|
|
73
|
+
路径: ~/.clawt/worktrees/project/feat-2
|
|
74
|
+
任务: 任务2
|
|
75
|
+
|
|
76
|
+
════════════════════════════════════════
|
|
77
|
+
✓ 预览完成,无冲突。移除 --dry-run 即可正式执行。
|
|
55
78
|
```
|
|
56
79
|
|
|
57
80
|
**任务文件格式:**
|
|
@@ -143,11 +166,28 @@ clawt status --json # JSON 格式
|
|
|
143
166
|
clawt reset
|
|
144
167
|
```
|
|
145
168
|
|
|
146
|
-
### `clawt config` —
|
|
169
|
+
### `clawt config` — 交互式查看和修改配置
|
|
147
170
|
|
|
148
171
|
```bash
|
|
149
|
-
clawt config
|
|
150
|
-
clawt config
|
|
172
|
+
clawt config # 交互式修改配置(选择配置项并修改值)
|
|
173
|
+
clawt config set <key> <value> # 直接设置某个配置项
|
|
174
|
+
clawt config get <key> # 获取某个配置项的值
|
|
175
|
+
clawt config reset # 恢复默认配置
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**使用示例:**
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# 交互式修改(列出所有配置项,方向键选择,根据类型自动提示)
|
|
182
|
+
clawt config
|
|
183
|
+
|
|
184
|
+
# 直接设置
|
|
185
|
+
clawt config set autoDeleteBranch true
|
|
186
|
+
clawt config set maxConcurrency 4
|
|
187
|
+
clawt config set terminalApp iterm2
|
|
188
|
+
|
|
189
|
+
# 查看某项配置
|
|
190
|
+
clawt config get maxConcurrency
|
|
151
191
|
```
|
|
152
192
|
|
|
153
193
|
### `clawt alias` — 管理命令别名
|
package/dist/index.js
CHANGED
|
@@ -98,7 +98,23 @@ var RUN_MESSAGES = {
|
|
|
98
98
|
/** 任务文件加载成功 */
|
|
99
99
|
TASK_FILE_LOADED: (count, path) => `\u2713 \u4ECE ${path} \u52A0\u8F7D\u4E86 ${count} \u4E2A\u4EFB\u52A1`,
|
|
100
100
|
/** 未指定 -b 或 -f */
|
|
101
|
-
BRANCH_OR_FILE_REQUIRED: "\u8BF7\u6307\u5B9A -b \u5206\u652F\u540D\u6216 -f \u4EFB\u52A1\u6587\u4EF6"
|
|
101
|
+
BRANCH_OR_FILE_REQUIRED: "\u8BF7\u6307\u5B9A -b \u5206\u652F\u540D\u6216 -f \u4EFB\u52A1\u6587\u4EF6",
|
|
102
|
+
/** dry-run 预览标题 */
|
|
103
|
+
DRY_RUN_TITLE: "Dry Run \u9884\u89C8",
|
|
104
|
+
/** dry-run 任务数量 */
|
|
105
|
+
DRY_RUN_TASK_COUNT: (count) => `\u4EFB\u52A1\u6570: ${count}`,
|
|
106
|
+
/** dry-run 并发数 */
|
|
107
|
+
DRY_RUN_CONCURRENCY: (concurrency) => `\u5E76\u53D1\u6570: ${concurrency === 0 ? "\u4E0D\u9650\u5236" : concurrency}`,
|
|
108
|
+
/** dry-run worktree 目录 */
|
|
109
|
+
DRY_RUN_WORKTREE_DIR: (dir) => `Worktree: ${dir}`,
|
|
110
|
+
/** dry-run 分支已存在警告 */
|
|
111
|
+
DRY_RUN_BRANCH_EXISTS_WARNING: (name) => `\u5206\u652F ${name} \u5DF2\u5B58\u5728`,
|
|
112
|
+
/** dry-run 交互式模式提示(无任务描述) */
|
|
113
|
+
DRY_RUN_INTERACTIVE_MODE: "\u6A21\u5F0F: \u4EA4\u4E92\u5F0F\uFF08\u65E0\u9884\u8BBE\u4EFB\u52A1\uFF09",
|
|
114
|
+
/** dry-run 预览完成且无冲突 */
|
|
115
|
+
DRY_RUN_READY: "\u9884\u89C8\u5B8C\u6210\uFF0C\u65E0\u51B2\u7A81\u3002\u79FB\u9664 --dry-run \u5373\u53EF\u6B63\u5F0F\u6267\u884C\u3002",
|
|
116
|
+
/** dry-run 存在分支冲突 */
|
|
117
|
+
DRY_RUN_HAS_CONFLICT: "\u5B58\u5728\u5206\u652F\u51B2\u7A81\uFF0C\u5B9E\u9645\u6267\u884C\u65F6\u5C06\u4F1A\u62A5\u9519\u3002\u8BF7\u5148\u5904\u7406\u51B2\u7A81\u7684\u5206\u652F\u3002"
|
|
102
118
|
};
|
|
103
119
|
|
|
104
120
|
// src/constants/messages/create.ts
|
|
@@ -249,6 +265,7 @@ var RESET_MESSAGES = {
|
|
|
249
265
|
};
|
|
250
266
|
|
|
251
267
|
// src/constants/messages/config.ts
|
|
268
|
+
var CONFIG_ALIAS_DISABLED_HINT = "(\u901A\u8FC7 clawt alias \u547D\u4EE4\u7BA1\u7406)";
|
|
252
269
|
var CONFIG_CMD_MESSAGES = {
|
|
253
270
|
/** 配置已恢复为默认值 */
|
|
254
271
|
CONFIG_RESET_SUCCESS: "\u2713 \u914D\u7F6E\u5DF2\u6062\u590D\u4E3A\u9ED8\u8BA4\u503C",
|
|
@@ -377,7 +394,7 @@ var CONFIG_DEFINITIONS = {
|
|
|
377
394
|
},
|
|
378
395
|
aliases: {
|
|
379
396
|
defaultValue: {},
|
|
380
|
-
description: "\u547D\u4EE4\u522B\u540D\u6620\u5C04
|
|
397
|
+
description: "\u547D\u4EE4\u522B\u540D\u6620\u5C04"
|
|
381
398
|
}
|
|
382
399
|
};
|
|
383
400
|
function deriveDefaultConfig(definitions) {
|
|
@@ -968,6 +985,16 @@ function ensureClawtDirs() {
|
|
|
968
985
|
ensureDir(LOGS_DIR);
|
|
969
986
|
ensureDir(WORKTREES_DIR);
|
|
970
987
|
}
|
|
988
|
+
function parseConcurrency(optionValue, configValue) {
|
|
989
|
+
if (optionValue === void 0) {
|
|
990
|
+
return configValue;
|
|
991
|
+
}
|
|
992
|
+
const parsed = parseInt(optionValue, 10);
|
|
993
|
+
if (Number.isNaN(parsed) || parsed < 0) {
|
|
994
|
+
throw new ClawtError(MESSAGES.CONCURRENCY_INVALID);
|
|
995
|
+
}
|
|
996
|
+
return parsed;
|
|
997
|
+
}
|
|
971
998
|
|
|
972
999
|
// src/utils/prompt.ts
|
|
973
1000
|
import Enquirer from "enquirer";
|
|
@@ -1491,6 +1518,14 @@ import { resolve } from "path";
|
|
|
1491
1518
|
import { existsSync as existsSync8, readFileSync as readFileSync3 } from "fs";
|
|
1492
1519
|
var TASK_BLOCK_REGEX = /<!-- CLAWT-TASKS:START -->([\s\S]*?)<!-- CLAWT-TASKS:END -->/g;
|
|
1493
1520
|
var BRANCH_LINE_REGEX = /^#\s*branch:\s*(.+)$/;
|
|
1521
|
+
var EMPTY_TASKS_MESSAGE = "\u4EFB\u52A1\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A";
|
|
1522
|
+
function parseTasksFromOptions(rawTasks) {
|
|
1523
|
+
const tasks = rawTasks.map((t) => t.trim()).filter(Boolean);
|
|
1524
|
+
if (tasks.length === 0) {
|
|
1525
|
+
throw new ClawtError(EMPTY_TASKS_MESSAGE);
|
|
1526
|
+
}
|
|
1527
|
+
return tasks;
|
|
1528
|
+
}
|
|
1494
1529
|
function parseTaskFile(content, options) {
|
|
1495
1530
|
const branchRequired = options?.branchRequired ?? true;
|
|
1496
1531
|
const entries = [];
|
|
@@ -1741,6 +1776,59 @@ async function executeBatchTasks(worktrees, tasks, concurrency) {
|
|
|
1741
1776
|
printTaskSummary(summary);
|
|
1742
1777
|
}
|
|
1743
1778
|
|
|
1779
|
+
// src/utils/dry-run.ts
|
|
1780
|
+
import chalk4 from "chalk";
|
|
1781
|
+
import { join as join5 } from "path";
|
|
1782
|
+
var DRY_RUN_TASK_DESC_MAX_LENGTH = 70;
|
|
1783
|
+
function truncateTaskDesc(task) {
|
|
1784
|
+
const oneLine = task.replace(/\n/g, " ").trim();
|
|
1785
|
+
if (oneLine.length <= DRY_RUN_TASK_DESC_MAX_LENGTH) {
|
|
1786
|
+
return oneLine;
|
|
1787
|
+
}
|
|
1788
|
+
return oneLine.slice(0, DRY_RUN_TASK_DESC_MAX_LENGTH) + "...";
|
|
1789
|
+
}
|
|
1790
|
+
function printDryRunPreview(branchNames, tasks, concurrency) {
|
|
1791
|
+
const projectDir = getProjectWorktreeDir();
|
|
1792
|
+
const isInteractive = tasks.length === 0;
|
|
1793
|
+
printDoubleSeparator();
|
|
1794
|
+
printInfo(` ${chalk4.bold(MESSAGES.DRY_RUN_TITLE)}`);
|
|
1795
|
+
printDoubleSeparator();
|
|
1796
|
+
const summaryParts = [
|
|
1797
|
+
MESSAGES.DRY_RUN_TASK_COUNT(branchNames.length),
|
|
1798
|
+
MESSAGES.DRY_RUN_CONCURRENCY(concurrency),
|
|
1799
|
+
MESSAGES.DRY_RUN_WORKTREE_DIR(projectDir)
|
|
1800
|
+
];
|
|
1801
|
+
if (isInteractive) {
|
|
1802
|
+
summaryParts.push(MESSAGES.DRY_RUN_INTERACTIVE_MODE);
|
|
1803
|
+
}
|
|
1804
|
+
printInfo(summaryParts.join(chalk4.gray(" \u2502 ")));
|
|
1805
|
+
printSeparator();
|
|
1806
|
+
let hasConflict = false;
|
|
1807
|
+
for (let i = 0; i < branchNames.length; i++) {
|
|
1808
|
+
const branch = branchNames[i];
|
|
1809
|
+
const worktreePath = join5(projectDir, branch);
|
|
1810
|
+
const exists = checkBranchExists(branch);
|
|
1811
|
+
if (exists) hasConflict = true;
|
|
1812
|
+
const indexLabel = `[${i + 1}/${branchNames.length}]`;
|
|
1813
|
+
if (exists) {
|
|
1814
|
+
printInfo(`${chalk4.yellow("\u26A0")} ${indexLabel} ${chalk4.yellow(branch)} ${chalk4.gray("\u2014")} ${chalk4.yellow(MESSAGES.DRY_RUN_BRANCH_EXISTS_WARNING(branch))}`);
|
|
1815
|
+
} else {
|
|
1816
|
+
printInfo(`${chalk4.green("\u2713")} ${indexLabel} ${chalk4.cyan(branch)}`);
|
|
1817
|
+
}
|
|
1818
|
+
printInfo(` ${chalk4.gray("\u8DEF\u5F84:")} ${worktreePath}`);
|
|
1819
|
+
if (!isInteractive) {
|
|
1820
|
+
printInfo(` ${chalk4.gray("\u4EFB\u52A1:")} ${truncateTaskDesc(tasks[i])}`);
|
|
1821
|
+
}
|
|
1822
|
+
printInfo("");
|
|
1823
|
+
}
|
|
1824
|
+
printDoubleSeparator();
|
|
1825
|
+
if (hasConflict) {
|
|
1826
|
+
printInfo(chalk4.yellow(`\u26A0 ${MESSAGES.DRY_RUN_HAS_CONFLICT}`));
|
|
1827
|
+
} else {
|
|
1828
|
+
printInfo(chalk4.green(`\u2713 ${MESSAGES.DRY_RUN_READY}`));
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1744
1832
|
// src/utils/alias.ts
|
|
1745
1833
|
function applyAliases(program2, aliases) {
|
|
1746
1834
|
for (const [alias, commandName] of Object.entries(aliases)) {
|
|
@@ -1755,7 +1843,7 @@ function applyAliases(program2, aliases) {
|
|
|
1755
1843
|
}
|
|
1756
1844
|
|
|
1757
1845
|
// src/utils/config-strategy.ts
|
|
1758
|
-
import
|
|
1846
|
+
import chalk5 from "chalk";
|
|
1759
1847
|
import Enquirer3 from "enquirer";
|
|
1760
1848
|
function isValidConfigKey(key) {
|
|
1761
1849
|
return key in DEFAULT_CONFIG;
|
|
@@ -1799,9 +1887,9 @@ async function promptConfigValue(key, currentValue) {
|
|
|
1799
1887
|
}
|
|
1800
1888
|
function formatConfigValue(value) {
|
|
1801
1889
|
if (typeof value === "boolean") {
|
|
1802
|
-
return value ?
|
|
1890
|
+
return value ? chalk5.green("true") : chalk5.yellow("false");
|
|
1803
1891
|
}
|
|
1804
|
-
return
|
|
1892
|
+
return chalk5.cyan(String(value));
|
|
1805
1893
|
}
|
|
1806
1894
|
async function promptBooleanValue(key, currentValue) {
|
|
1807
1895
|
const choices = [
|
|
@@ -1845,7 +1933,7 @@ async function promptStringValue(key, currentValue) {
|
|
|
1845
1933
|
}
|
|
1846
1934
|
|
|
1847
1935
|
// src/commands/list.ts
|
|
1848
|
-
import
|
|
1936
|
+
import chalk6 from "chalk";
|
|
1849
1937
|
function registerListCommand(program2) {
|
|
1850
1938
|
program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
1851
1939
|
handleList(options);
|
|
@@ -1882,12 +1970,12 @@ function printListAsText(projectName, worktrees) {
|
|
|
1882
1970
|
for (const wt of worktrees) {
|
|
1883
1971
|
const status = getWorktreeStatus(wt);
|
|
1884
1972
|
const isIdle = status ? isWorktreeIdle(status) : false;
|
|
1885
|
-
const pathDisplay = isIdle ?
|
|
1973
|
+
const pathDisplay = isIdle ? chalk6.hex("#FF8C00")(wt.path) : wt.path;
|
|
1886
1974
|
printInfo(` ${pathDisplay} [${wt.branch}]`);
|
|
1887
1975
|
if (status) {
|
|
1888
1976
|
printInfo(` ${formatWorktreeStatus(status)}`);
|
|
1889
1977
|
} else {
|
|
1890
|
-
printInfo(` ${
|
|
1978
|
+
printInfo(` ${chalk6.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
|
|
1891
1979
|
}
|
|
1892
1980
|
printInfo("");
|
|
1893
1981
|
}
|
|
@@ -1990,19 +2078,16 @@ async function handleRemove(options) {
|
|
|
1990
2078
|
|
|
1991
2079
|
// src/commands/run.ts
|
|
1992
2080
|
function registerRunCommand(program2) {
|
|
1993
|
-
program2.command("run").description("\u6279\u91CF\u521B\u5EFA worktree \u5E76\u542F\u52A8 Claude Code \u6267\u884C\u4EFB\u52A1").option("-b, --branch <branchName>", "\u5206\u652F\u540D").option("--tasks <task...>", "\u4EFB\u52A1\u5217\u8868\uFF08\u53EF\u591A\u6B21\u6307\u5B9A\uFF09\uFF0C\u4E0D\u4F20\u5219\u5728 worktree \u4E2D\u6253\u5F00 Claude Code \u4EA4\u4E92\u5F0F\u754C\u9762").option("-c, --concurrency <n>", "\u6700\u5927\u5E76\u53D1\u6570\uFF0C0 \u8868\u793A\u4E0D\u9650\u5236").option("-f, --file <path>", "\u4ECE\u4EFB\u52A1\u6587\u4EF6\u8BFB\u53D6\u4EFB\u52A1\u5217\u8868\uFF08\u4E0E --tasks \u4E92\u65A5\uFF09").action(async (options) => {
|
|
2081
|
+
program2.command("run").description("\u6279\u91CF\u521B\u5EFA worktree \u5E76\u542F\u52A8 Claude Code \u6267\u884C\u4EFB\u52A1").option("-b, --branch <branchName>", "\u5206\u652F\u540D").option("--tasks <task...>", "\u4EFB\u52A1\u5217\u8868\uFF08\u53EF\u591A\u6B21\u6307\u5B9A\uFF09\uFF0C\u4E0D\u4F20\u5219\u5728 worktree \u4E2D\u6253\u5F00 Claude Code \u4EA4\u4E92\u5F0F\u754C\u9762").option("-c, --concurrency <n>", "\u6700\u5927\u5E76\u53D1\u6570\uFF0C0 \u8868\u793A\u4E0D\u9650\u5236").option("-f, --file <path>", "\u4ECE\u4EFB\u52A1\u6587\u4EF6\u8BFB\u53D6\u4EFB\u52A1\u5217\u8868\uFF08\u4E0E --tasks \u4E92\u65A5\uFF09").option("-d, --dry-run", "\u9884\u89C8\u6A21\u5F0F\uFF0C\u4EC5\u5C55\u793A\u4EFB\u52A1\u8BA1\u5212\u4E0D\u5B9E\u9645\u6267\u884C").action(async (options) => {
|
|
1994
2082
|
await handleRun(options);
|
|
1995
2083
|
});
|
|
1996
2084
|
}
|
|
1997
|
-
function
|
|
1998
|
-
if (
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
const parsed = parseInt(optionValue, 10);
|
|
2002
|
-
if (Number.isNaN(parsed) || parsed < 0) {
|
|
2003
|
-
throw new ClawtError(MESSAGES.CONCURRENCY_INVALID);
|
|
2085
|
+
function resolveBranchNamesFromFile(options, entryCount, entries) {
|
|
2086
|
+
if (options.branch) {
|
|
2087
|
+
const sanitized = sanitizeBranchName(options.branch);
|
|
2088
|
+
return generateBranchNames(sanitized, entryCount);
|
|
2004
2089
|
}
|
|
2005
|
-
return
|
|
2090
|
+
return entries.map((e) => sanitizeBranchName(e.branch));
|
|
2006
2091
|
}
|
|
2007
2092
|
async function handleRunFromFile(options) {
|
|
2008
2093
|
const branchRequired = !options.branch;
|
|
@@ -2020,12 +2105,39 @@ async function handleRunFromFile(options) {
|
|
|
2020
2105
|
logger.info(`run \u547D\u4EE4\uFF08\u6587\u4EF6\u6A21\u5F0F\uFF09\u6267\u884C\uFF0C\u4EFB\u52A1\u6570: ${entries.length}\uFF0C\u5E76\u53D1\u6570: ${concurrency || "\u4E0D\u9650\u5236"}`);
|
|
2021
2106
|
await executeBatchTasks(worktrees, tasks, concurrency);
|
|
2022
2107
|
}
|
|
2108
|
+
function handleDryRunFromFile(options) {
|
|
2109
|
+
const branchRequired = !options.branch;
|
|
2110
|
+
const entries = loadTaskFile(options.file, { branchRequired });
|
|
2111
|
+
printSuccess(MESSAGES.TASK_FILE_LOADED(entries.length, options.file));
|
|
2112
|
+
const tasks = entries.map((e) => e.task);
|
|
2113
|
+
const branchNames = resolveBranchNamesFromFile(options, entries.length, entries);
|
|
2114
|
+
const concurrency = parseConcurrency(options.concurrency, getConfigValue("maxConcurrency"));
|
|
2115
|
+
printDryRunPreview(branchNames, tasks, concurrency);
|
|
2116
|
+
}
|
|
2023
2117
|
async function handleRun(options) {
|
|
2024
2118
|
validateMainWorktree();
|
|
2025
|
-
validateClaudeCodeInstalled();
|
|
2026
2119
|
if (options.file && options.tasks) {
|
|
2027
2120
|
throw new ClawtError(MESSAGES.FILE_AND_TASKS_CONFLICT);
|
|
2028
2121
|
}
|
|
2122
|
+
if (options.dryRun) {
|
|
2123
|
+
if (options.file) {
|
|
2124
|
+
return handleDryRunFromFile(options);
|
|
2125
|
+
}
|
|
2126
|
+
if (!options.branch) {
|
|
2127
|
+
throw new ClawtError(MESSAGES.BRANCH_OR_FILE_REQUIRED);
|
|
2128
|
+
}
|
|
2129
|
+
const sanitized = sanitizeBranchName(options.branch);
|
|
2130
|
+
if (!options.tasks || options.tasks.length === 0) {
|
|
2131
|
+
printDryRunPreview([sanitized], [], 0);
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
const tasks2 = parseTasksFromOptions(options.tasks);
|
|
2135
|
+
const branchNames = generateBranchNames(sanitized, tasks2.length);
|
|
2136
|
+
const concurrency2 = parseConcurrency(options.concurrency, getConfigValue("maxConcurrency"));
|
|
2137
|
+
printDryRunPreview(branchNames, tasks2, concurrency2);
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
validateClaudeCodeInstalled();
|
|
2029
2141
|
if (options.file) {
|
|
2030
2142
|
return handleRunFromFile(options);
|
|
2031
2143
|
}
|
|
@@ -2043,10 +2155,7 @@ async function handleRun(options) {
|
|
|
2043
2155
|
launchInteractiveClaude(worktree);
|
|
2044
2156
|
return;
|
|
2045
2157
|
}
|
|
2046
|
-
const tasks = options.tasks
|
|
2047
|
-
if (tasks.length === 0) {
|
|
2048
|
-
throw new ClawtError("\u4EFB\u52A1\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A");
|
|
2049
|
-
}
|
|
2158
|
+
const tasks = parseTasksFromOptions(options.tasks);
|
|
2050
2159
|
const count = tasks.length;
|
|
2051
2160
|
const concurrency = parseConcurrency(options.concurrency, getConfigValue("maxConcurrency"));
|
|
2052
2161
|
logger.info(`run \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch}\uFF0C\u4EFB\u52A1\u6570: ${count}\uFF0C\u5E76\u53D1\u6570: ${concurrency || "\u4E0D\u9650\u5236"}`);
|
|
@@ -2412,7 +2521,7 @@ async function handleMerge(options) {
|
|
|
2412
2521
|
}
|
|
2413
2522
|
|
|
2414
2523
|
// src/commands/config.ts
|
|
2415
|
-
import
|
|
2524
|
+
import chalk7 from "chalk";
|
|
2416
2525
|
import Enquirer5 from "enquirer";
|
|
2417
2526
|
function registerConfigCommand(program2) {
|
|
2418
2527
|
const configCmd = program2.command("config").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u5168\u5C40\u914D\u7F6E").action(async () => {
|
|
@@ -2469,10 +2578,14 @@ async function handleInteractiveConfigSet() {
|
|
|
2469
2578
|
const config2 = loadConfig();
|
|
2470
2579
|
const keys = Object.keys(DEFAULT_CONFIG);
|
|
2471
2580
|
logger.info("config set \u547D\u4EE4\u6267\u884C\uFF0C\u8FDB\u5165\u4EA4\u4E92\u5F0F\u914D\u7F6E");
|
|
2472
|
-
const choices = keys.map((k) =>
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2581
|
+
const choices = keys.map((k) => {
|
|
2582
|
+
const isObject = typeof DEFAULT_CONFIG[k] === "object";
|
|
2583
|
+
return {
|
|
2584
|
+
name: k,
|
|
2585
|
+
message: `${k}: ${isObject ? chalk7.dim(JSON.stringify(config2[k])) : formatConfigValue(config2[k])} ${chalk7.dim(`\u2014 ${CONFIG_DESCRIPTIONS[k]}`)}`,
|
|
2586
|
+
...isObject && { disabled: CONFIG_ALIAS_DISABLED_HINT }
|
|
2587
|
+
};
|
|
2588
|
+
});
|
|
2476
2589
|
const selectedKey = await new Enquirer5.Select({
|
|
2477
2590
|
message: MESSAGES.CONFIG_SELECT_PROMPT,
|
|
2478
2591
|
choices
|
|
@@ -2578,7 +2691,7 @@ async function handleReset() {
|
|
|
2578
2691
|
}
|
|
2579
2692
|
|
|
2580
2693
|
// src/commands/status.ts
|
|
2581
|
-
import
|
|
2694
|
+
import chalk8 from "chalk";
|
|
2582
2695
|
function registerStatusCommand(program2) {
|
|
2583
2696
|
program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
2584
2697
|
handleStatus(options);
|
|
@@ -2674,7 +2787,7 @@ function printStatusAsJson(result) {
|
|
|
2674
2787
|
}
|
|
2675
2788
|
function printStatusAsText(result) {
|
|
2676
2789
|
printDoubleSeparator();
|
|
2677
|
-
printInfo(` ${
|
|
2790
|
+
printInfo(` ${chalk8.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
|
|
2678
2791
|
printDoubleSeparator();
|
|
2679
2792
|
printInfo("");
|
|
2680
2793
|
printMainSection(result.main);
|
|
@@ -2687,17 +2800,17 @@ function printStatusAsText(result) {
|
|
|
2687
2800
|
printDoubleSeparator();
|
|
2688
2801
|
}
|
|
2689
2802
|
function printMainSection(main) {
|
|
2690
|
-
printInfo(` ${
|
|
2691
|
-
printInfo(` \u5206\u652F: ${
|
|
2803
|
+
printInfo(` ${chalk8.bold("\u25C6")} ${chalk8.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
|
|
2804
|
+
printInfo(` \u5206\u652F: ${chalk8.bold(main.branch)}`);
|
|
2692
2805
|
if (main.isClean) {
|
|
2693
|
-
printInfo(` \u72B6\u6001: ${
|
|
2806
|
+
printInfo(` \u72B6\u6001: ${chalk8.green("\u2713 \u5E72\u51C0")}`);
|
|
2694
2807
|
} else {
|
|
2695
|
-
printInfo(` \u72B6\u6001: ${
|
|
2808
|
+
printInfo(` \u72B6\u6001: ${chalk8.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
|
|
2696
2809
|
}
|
|
2697
2810
|
printInfo("");
|
|
2698
2811
|
}
|
|
2699
2812
|
function printWorktreesSection(worktrees, total) {
|
|
2700
|
-
printInfo(` ${
|
|
2813
|
+
printInfo(` ${chalk8.bold("\u25C6")} ${chalk8.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
|
|
2701
2814
|
printInfo("");
|
|
2702
2815
|
if (worktrees.length === 0) {
|
|
2703
2816
|
printInfo(` ${MESSAGES.STATUS_NO_WORKTREES}`);
|
|
@@ -2709,39 +2822,39 @@ function printWorktreesSection(worktrees, total) {
|
|
|
2709
2822
|
}
|
|
2710
2823
|
function printWorktreeItem(wt) {
|
|
2711
2824
|
const statusLabel = formatChangeStatusLabel(wt.changeStatus);
|
|
2712
|
-
printInfo(` ${
|
|
2825
|
+
printInfo(` ${chalk8.bold("\u25CF")} ${chalk8.bold(wt.branch)} [${statusLabel}]`);
|
|
2713
2826
|
const parts = [];
|
|
2714
2827
|
if (wt.insertions > 0 || wt.deletions > 0) {
|
|
2715
|
-
parts.push(`${
|
|
2828
|
+
parts.push(`${chalk8.green(`+${wt.insertions}`)} ${chalk8.red(`-${wt.deletions}`)}`);
|
|
2716
2829
|
}
|
|
2717
2830
|
if (wt.commitsAhead > 0) {
|
|
2718
|
-
parts.push(
|
|
2831
|
+
parts.push(chalk8.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`));
|
|
2719
2832
|
}
|
|
2720
2833
|
if (wt.commitsBehind > 0) {
|
|
2721
|
-
parts.push(
|
|
2834
|
+
parts.push(chalk8.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`));
|
|
2722
2835
|
} else {
|
|
2723
|
-
parts.push(
|
|
2836
|
+
parts.push(chalk8.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65"));
|
|
2724
2837
|
}
|
|
2725
2838
|
printInfo(` ${parts.join(" ")}`);
|
|
2726
2839
|
if (wt.hasSnapshot) {
|
|
2727
|
-
printInfo(` ${
|
|
2840
|
+
printInfo(` ${chalk8.blue("\u6709 validate \u5FEB\u7167")}`);
|
|
2728
2841
|
}
|
|
2729
2842
|
printInfo("");
|
|
2730
2843
|
}
|
|
2731
2844
|
function formatChangeStatusLabel(status) {
|
|
2732
2845
|
switch (status) {
|
|
2733
2846
|
case "committed":
|
|
2734
|
-
return
|
|
2847
|
+
return chalk8.green(MESSAGES.STATUS_CHANGE_COMMITTED);
|
|
2735
2848
|
case "uncommitted":
|
|
2736
|
-
return
|
|
2849
|
+
return chalk8.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
|
|
2737
2850
|
case "conflict":
|
|
2738
|
-
return
|
|
2851
|
+
return chalk8.red(MESSAGES.STATUS_CHANGE_CONFLICT);
|
|
2739
2852
|
case "clean":
|
|
2740
|
-
return
|
|
2853
|
+
return chalk8.gray(MESSAGES.STATUS_CHANGE_CLEAN);
|
|
2741
2854
|
}
|
|
2742
2855
|
}
|
|
2743
2856
|
function printSnapshotsSection(snapshots) {
|
|
2744
|
-
printInfo(` ${
|
|
2857
|
+
printInfo(` ${chalk8.bold("\u25C6")} ${chalk8.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.length} \u4E2A)`);
|
|
2745
2858
|
printInfo("");
|
|
2746
2859
|
if (snapshots.length === 0) {
|
|
2747
2860
|
printInfo(` ${MESSAGES.STATUS_NO_SNAPSHOTS}`);
|
|
@@ -2749,15 +2862,15 @@ function printSnapshotsSection(snapshots) {
|
|
|
2749
2862
|
return;
|
|
2750
2863
|
}
|
|
2751
2864
|
for (const snap of snapshots) {
|
|
2752
|
-
const orphanLabel = snap.worktreeExists ? "" : ` ${
|
|
2753
|
-
const icon = snap.worktreeExists ?
|
|
2865
|
+
const orphanLabel = snap.worktreeExists ? "" : ` ${chalk8.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED)}`;
|
|
2866
|
+
const icon = snap.worktreeExists ? chalk8.blue("\u25CF") : chalk8.yellow("\u26A0");
|
|
2754
2867
|
printInfo(` ${icon} ${snap.branch}${orphanLabel}`);
|
|
2755
2868
|
}
|
|
2756
2869
|
printInfo("");
|
|
2757
2870
|
}
|
|
2758
2871
|
|
|
2759
2872
|
// src/commands/alias.ts
|
|
2760
|
-
import
|
|
2873
|
+
import chalk9 from "chalk";
|
|
2761
2874
|
function getRegisteredCommandNames(program2) {
|
|
2762
2875
|
return program2.commands.map((cmd) => cmd.name());
|
|
2763
2876
|
}
|
|
@@ -2778,7 +2891,7 @@ ${MESSAGES.ALIAS_LIST_TITLE}
|
|
|
2778
2891
|
`);
|
|
2779
2892
|
printSeparator();
|
|
2780
2893
|
for (const [alias, command] of entries) {
|
|
2781
|
-
printInfo(` ${
|
|
2894
|
+
printInfo(` ${chalk9.bold(alias)} \u2192 ${chalk9.cyan(command)}`);
|
|
2782
2895
|
}
|
|
2783
2896
|
printInfo("");
|
|
2784
2897
|
printSeparator();
|
package/dist/postinstall.js
CHANGED
|
@@ -90,7 +90,23 @@ var RUN_MESSAGES = {
|
|
|
90
90
|
/** 任务文件加载成功 */
|
|
91
91
|
TASK_FILE_LOADED: (count, path) => `\u2713 \u4ECE ${path} \u52A0\u8F7D\u4E86 ${count} \u4E2A\u4EFB\u52A1`,
|
|
92
92
|
/** 未指定 -b 或 -f */
|
|
93
|
-
BRANCH_OR_FILE_REQUIRED: "\u8BF7\u6307\u5B9A -b \u5206\u652F\u540D\u6216 -f \u4EFB\u52A1\u6587\u4EF6"
|
|
93
|
+
BRANCH_OR_FILE_REQUIRED: "\u8BF7\u6307\u5B9A -b \u5206\u652F\u540D\u6216 -f \u4EFB\u52A1\u6587\u4EF6",
|
|
94
|
+
/** dry-run 预览标题 */
|
|
95
|
+
DRY_RUN_TITLE: "Dry Run \u9884\u89C8",
|
|
96
|
+
/** dry-run 任务数量 */
|
|
97
|
+
DRY_RUN_TASK_COUNT: (count) => `\u4EFB\u52A1\u6570: ${count}`,
|
|
98
|
+
/** dry-run 并发数 */
|
|
99
|
+
DRY_RUN_CONCURRENCY: (concurrency) => `\u5E76\u53D1\u6570: ${concurrency === 0 ? "\u4E0D\u9650\u5236" : concurrency}`,
|
|
100
|
+
/** dry-run worktree 目录 */
|
|
101
|
+
DRY_RUN_WORKTREE_DIR: (dir) => `Worktree: ${dir}`,
|
|
102
|
+
/** dry-run 分支已存在警告 */
|
|
103
|
+
DRY_RUN_BRANCH_EXISTS_WARNING: (name) => `\u5206\u652F ${name} \u5DF2\u5B58\u5728`,
|
|
104
|
+
/** dry-run 交互式模式提示(无任务描述) */
|
|
105
|
+
DRY_RUN_INTERACTIVE_MODE: "\u6A21\u5F0F: \u4EA4\u4E92\u5F0F\uFF08\u65E0\u9884\u8BBE\u4EFB\u52A1\uFF09",
|
|
106
|
+
/** dry-run 预览完成且无冲突 */
|
|
107
|
+
DRY_RUN_READY: "\u9884\u89C8\u5B8C\u6210\uFF0C\u65E0\u51B2\u7A81\u3002\u79FB\u9664 --dry-run \u5373\u53EF\u6B63\u5F0F\u6267\u884C\u3002",
|
|
108
|
+
/** dry-run 存在分支冲突 */
|
|
109
|
+
DRY_RUN_HAS_CONFLICT: "\u5B58\u5728\u5206\u652F\u51B2\u7A81\uFF0C\u5B9E\u9645\u6267\u884C\u65F6\u5C06\u4F1A\u62A5\u9519\u3002\u8BF7\u5148\u5904\u7406\u51B2\u7A81\u7684\u5206\u652F\u3002"
|
|
94
110
|
};
|
|
95
111
|
|
|
96
112
|
// src/constants/messages/create.ts
|
|
@@ -357,7 +373,7 @@ var CONFIG_DEFINITIONS = {
|
|
|
357
373
|
},
|
|
358
374
|
aliases: {
|
|
359
375
|
defaultValue: {},
|
|
360
|
-
description: "\u547D\u4EE4\u522B\u540D\u6620\u5C04
|
|
376
|
+
description: "\u547D\u4EE4\u522B\u540D\u6620\u5C04"
|
|
361
377
|
}
|
|
362
378
|
};
|
|
363
379
|
function deriveDefaultConfig(definitions) {
|