clawt 2.14.1 → 2.16.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/README.md +29 -2
- package/dist/index.js +194 -50
- package/dist/postinstall.js +26 -2
- package/docs/spec.md +96 -6
- package/package.json +1 -1
- package/src/commands/run.ts +68 -21
- package/src/commands/validate.ts +38 -0
- package/src/constants/messages/run.ts +16 -0
- package/src/constants/messages/validate.ts +10 -0
- package/src/types/command.ts +4 -0
- package/src/utils/config.ts +20 -0
- package/src/utils/dry-run.ts +89 -0
- package/src/utils/index.ts +4 -3
- package/src/utils/shell.ts +21 -1
- package/src/utils/task-file.ts +18 -0
- package/tests/unit/commands/resume.test.ts +9 -9
- package/tests/unit/commands/run.test.ts +160 -6
- package/tests/unit/commands/validate.test.ts +136 -0
- 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
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
|
**任务文件格式:**
|
|
@@ -96,12 +119,16 @@ clawt create -b <branch> -n 3 # 批量创建 3 个
|
|
|
96
119
|
### `clawt validate` — 在主 worktree 中验证分支变更
|
|
97
120
|
|
|
98
121
|
```bash
|
|
99
|
-
clawt validate -b <branch>
|
|
100
|
-
clawt validate -b <branch> --clean
|
|
122
|
+
clawt validate -b <branch> # 将变更迁移到主 worktree 测试
|
|
123
|
+
clawt validate -b <branch> --clean # 清理 validate 状态
|
|
124
|
+
clawt validate -b <branch> -r "npm test" # validate 成功后自动运行测试
|
|
125
|
+
clawt validate -b <branch> -r "npm run build" # validate 成功后自动构建
|
|
101
126
|
```
|
|
102
127
|
|
|
103
128
|
支持增量模式:再次 validate 同一分支时,可通过 `git diff` 查看两次之间的增量差异。
|
|
104
129
|
|
|
130
|
+
`-r, --run` 选项可在 validate 成功后自动在主 worktree 中执行指定命令(如测试、构建等),命令执行失败不影响 validate 结果。
|
|
131
|
+
|
|
105
132
|
### `clawt sync` — 同步主分支代码到目标 worktree
|
|
106
133
|
|
|
107
134
|
```bash
|
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
|
|
@@ -173,7 +189,15 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
|
173
189
|
/** validate 交互选择提示 */
|
|
174
190
|
VALIDATE_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u9A8C\u8BC1\u7684\u5206\u652F",
|
|
175
191
|
/** validate 模糊匹配到多个结果提示 */
|
|
176
|
-
VALIDATE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A
|
|
192
|
+
VALIDATE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
|
|
193
|
+
/** --run 命令开始执行提示 */
|
|
194
|
+
VALIDATE_RUN_START: (command) => `\u6B63\u5728\u4E3B worktree \u4E2D\u6267\u884C\u547D\u4EE4: ${command}`,
|
|
195
|
+
/** --run 命令执行成功(退出码 0) */
|
|
196
|
+
VALIDATE_RUN_SUCCESS: (command) => `\u2713 \u547D\u4EE4\u6267\u884C\u5B8C\u6210: ${command}\uFF0C\u9000\u51FA\u7801: 0`,
|
|
197
|
+
/** --run 命令执行失败(退出码非 0) */
|
|
198
|
+
VALIDATE_RUN_FAILED: (command, exitCode) => `\u2717 \u547D\u4EE4\u6267\u884C\u5B8C\u6210: ${command}\uFF0C\u9000\u51FA\u7801: ${exitCode}`,
|
|
199
|
+
/** --run 命令执行异常(进程启动失败等) */
|
|
200
|
+
VALIDATE_RUN_ERROR: (command, errorMessage) => `\u2717 \u547D\u4EE4\u6267\u884C\u51FA\u9519: ${errorMessage}`
|
|
177
201
|
};
|
|
178
202
|
|
|
179
203
|
// src/constants/messages/sync.ts
|
|
@@ -505,7 +529,7 @@ function enableConsoleTransport() {
|
|
|
505
529
|
}
|
|
506
530
|
|
|
507
531
|
// src/utils/shell.ts
|
|
508
|
-
import { execSync, execFileSync, spawn } from "child_process";
|
|
532
|
+
import { execSync, execFileSync, spawn, spawnSync } from "child_process";
|
|
509
533
|
function execCommand(command, options) {
|
|
510
534
|
logger.debug(`\u6267\u884C\u547D\u4EE4: ${command}${options?.cwd ? ` (cwd: ${options.cwd})` : ""}`);
|
|
511
535
|
const result = execSync(command, {
|
|
@@ -539,6 +563,14 @@ function execCommandWithInput(command, args, options) {
|
|
|
539
563
|
});
|
|
540
564
|
return result.trim();
|
|
541
565
|
}
|
|
566
|
+
function runCommandInherited(command, options) {
|
|
567
|
+
logger.debug(`\u6267\u884C\u547D\u4EE4(inherit): ${command}${options?.cwd ? ` (cwd: ${options.cwd})` : ""}`);
|
|
568
|
+
return spawnSync(command, {
|
|
569
|
+
cwd: options?.cwd,
|
|
570
|
+
stdio: "inherit",
|
|
571
|
+
shell: true
|
|
572
|
+
});
|
|
573
|
+
}
|
|
542
574
|
|
|
543
575
|
// src/utils/git.ts
|
|
544
576
|
import { basename } from "path";
|
|
@@ -969,12 +1001,22 @@ function ensureClawtDirs() {
|
|
|
969
1001
|
ensureDir(LOGS_DIR);
|
|
970
1002
|
ensureDir(WORKTREES_DIR);
|
|
971
1003
|
}
|
|
1004
|
+
function parseConcurrency(optionValue, configValue) {
|
|
1005
|
+
if (optionValue === void 0) {
|
|
1006
|
+
return configValue;
|
|
1007
|
+
}
|
|
1008
|
+
const parsed = parseInt(optionValue, 10);
|
|
1009
|
+
if (Number.isNaN(parsed) || parsed < 0) {
|
|
1010
|
+
throw new ClawtError(MESSAGES.CONCURRENCY_INVALID);
|
|
1011
|
+
}
|
|
1012
|
+
return parsed;
|
|
1013
|
+
}
|
|
972
1014
|
|
|
973
1015
|
// src/utils/prompt.ts
|
|
974
1016
|
import Enquirer from "enquirer";
|
|
975
1017
|
|
|
976
1018
|
// src/utils/claude.ts
|
|
977
|
-
import { spawnSync } from "child_process";
|
|
1019
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
978
1020
|
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
979
1021
|
import { join as join3 } from "path";
|
|
980
1022
|
|
|
@@ -1083,7 +1125,7 @@ function launchInteractiveClaude(worktree, options = {}) {
|
|
|
1083
1125
|
printInfo(` \u6A21\u5F0F: ${hasPreviousSession ? "\u7EE7\u7EED\u4E0A\u6B21\u5BF9\u8BDD" : "\u65B0\u5BF9\u8BDD"}`);
|
|
1084
1126
|
}
|
|
1085
1127
|
printInfo("");
|
|
1086
|
-
const result =
|
|
1128
|
+
const result = spawnSync2(cmd, args, {
|
|
1087
1129
|
cwd: worktree.path,
|
|
1088
1130
|
stdio: "inherit"
|
|
1089
1131
|
});
|
|
@@ -1492,6 +1534,14 @@ import { resolve } from "path";
|
|
|
1492
1534
|
import { existsSync as existsSync8, readFileSync as readFileSync3 } from "fs";
|
|
1493
1535
|
var TASK_BLOCK_REGEX = /<!-- CLAWT-TASKS:START -->([\s\S]*?)<!-- CLAWT-TASKS:END -->/g;
|
|
1494
1536
|
var BRANCH_LINE_REGEX = /^#\s*branch:\s*(.+)$/;
|
|
1537
|
+
var EMPTY_TASKS_MESSAGE = "\u4EFB\u52A1\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A";
|
|
1538
|
+
function parseTasksFromOptions(rawTasks) {
|
|
1539
|
+
const tasks = rawTasks.map((t) => t.trim()).filter(Boolean);
|
|
1540
|
+
if (tasks.length === 0) {
|
|
1541
|
+
throw new ClawtError(EMPTY_TASKS_MESSAGE);
|
|
1542
|
+
}
|
|
1543
|
+
return tasks;
|
|
1544
|
+
}
|
|
1495
1545
|
function parseTaskFile(content, options) {
|
|
1496
1546
|
const branchRequired = options?.branchRequired ?? true;
|
|
1497
1547
|
const entries = [];
|
|
@@ -1742,6 +1792,59 @@ async function executeBatchTasks(worktrees, tasks, concurrency) {
|
|
|
1742
1792
|
printTaskSummary(summary);
|
|
1743
1793
|
}
|
|
1744
1794
|
|
|
1795
|
+
// src/utils/dry-run.ts
|
|
1796
|
+
import chalk4 from "chalk";
|
|
1797
|
+
import { join as join5 } from "path";
|
|
1798
|
+
var DRY_RUN_TASK_DESC_MAX_LENGTH = 80;
|
|
1799
|
+
function truncateTaskDesc(task) {
|
|
1800
|
+
const oneLine = task.replace(/\n/g, " ").trim();
|
|
1801
|
+
if (oneLine.length <= DRY_RUN_TASK_DESC_MAX_LENGTH) {
|
|
1802
|
+
return oneLine;
|
|
1803
|
+
}
|
|
1804
|
+
return oneLine.slice(0, DRY_RUN_TASK_DESC_MAX_LENGTH) + "...";
|
|
1805
|
+
}
|
|
1806
|
+
function printDryRunPreview(branchNames, tasks, concurrency) {
|
|
1807
|
+
const projectDir = getProjectWorktreeDir();
|
|
1808
|
+
const isInteractive = tasks.length === 0;
|
|
1809
|
+
printDoubleSeparator();
|
|
1810
|
+
printInfo(` ${chalk4.bold(MESSAGES.DRY_RUN_TITLE)}`);
|
|
1811
|
+
printDoubleSeparator();
|
|
1812
|
+
const summaryParts = [
|
|
1813
|
+
MESSAGES.DRY_RUN_TASK_COUNT(branchNames.length),
|
|
1814
|
+
MESSAGES.DRY_RUN_CONCURRENCY(concurrency),
|
|
1815
|
+
MESSAGES.DRY_RUN_WORKTREE_DIR(projectDir)
|
|
1816
|
+
];
|
|
1817
|
+
if (isInteractive) {
|
|
1818
|
+
summaryParts.push(MESSAGES.DRY_RUN_INTERACTIVE_MODE);
|
|
1819
|
+
}
|
|
1820
|
+
printInfo(summaryParts.join(chalk4.gray(" \u2502 ")));
|
|
1821
|
+
printSeparator();
|
|
1822
|
+
let hasConflict = false;
|
|
1823
|
+
for (let i = 0; i < branchNames.length; i++) {
|
|
1824
|
+
const branch = branchNames[i];
|
|
1825
|
+
const worktreePath = join5(projectDir, branch);
|
|
1826
|
+
const exists = checkBranchExists(branch);
|
|
1827
|
+
if (exists) hasConflict = true;
|
|
1828
|
+
const indexLabel = `[${i + 1}/${branchNames.length}]`;
|
|
1829
|
+
if (exists) {
|
|
1830
|
+
printInfo(`${chalk4.yellow("\u26A0")} ${indexLabel} ${chalk4.yellow(branch)} ${chalk4.gray("\u2014")} ${chalk4.yellow(MESSAGES.DRY_RUN_BRANCH_EXISTS_WARNING(branch))}`);
|
|
1831
|
+
} else {
|
|
1832
|
+
printInfo(`${chalk4.green("\u2713")} ${indexLabel} ${chalk4.cyan(branch)}`);
|
|
1833
|
+
}
|
|
1834
|
+
printInfo(` ${chalk4.gray("\u8DEF\u5F84:")} ${worktreePath}`);
|
|
1835
|
+
if (!isInteractive) {
|
|
1836
|
+
printInfo(` ${chalk4.gray("\u4EFB\u52A1:")} ${truncateTaskDesc(tasks[i])}`);
|
|
1837
|
+
}
|
|
1838
|
+
printInfo("");
|
|
1839
|
+
}
|
|
1840
|
+
printDoubleSeparator();
|
|
1841
|
+
if (hasConflict) {
|
|
1842
|
+
printInfo(chalk4.yellow(`\u26A0 ${MESSAGES.DRY_RUN_HAS_CONFLICT}`));
|
|
1843
|
+
} else {
|
|
1844
|
+
printInfo(chalk4.green(`\u2713 ${MESSAGES.DRY_RUN_READY}`));
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1745
1848
|
// src/utils/alias.ts
|
|
1746
1849
|
function applyAliases(program2, aliases) {
|
|
1747
1850
|
for (const [alias, commandName] of Object.entries(aliases)) {
|
|
@@ -1756,7 +1859,7 @@ function applyAliases(program2, aliases) {
|
|
|
1756
1859
|
}
|
|
1757
1860
|
|
|
1758
1861
|
// src/utils/config-strategy.ts
|
|
1759
|
-
import
|
|
1862
|
+
import chalk5 from "chalk";
|
|
1760
1863
|
import Enquirer3 from "enquirer";
|
|
1761
1864
|
function isValidConfigKey(key) {
|
|
1762
1865
|
return key in DEFAULT_CONFIG;
|
|
@@ -1800,9 +1903,9 @@ async function promptConfigValue(key, currentValue) {
|
|
|
1800
1903
|
}
|
|
1801
1904
|
function formatConfigValue(value) {
|
|
1802
1905
|
if (typeof value === "boolean") {
|
|
1803
|
-
return value ?
|
|
1906
|
+
return value ? chalk5.green("true") : chalk5.yellow("false");
|
|
1804
1907
|
}
|
|
1805
|
-
return
|
|
1908
|
+
return chalk5.cyan(String(value));
|
|
1806
1909
|
}
|
|
1807
1910
|
async function promptBooleanValue(key, currentValue) {
|
|
1808
1911
|
const choices = [
|
|
@@ -1846,7 +1949,7 @@ async function promptStringValue(key, currentValue) {
|
|
|
1846
1949
|
}
|
|
1847
1950
|
|
|
1848
1951
|
// src/commands/list.ts
|
|
1849
|
-
import
|
|
1952
|
+
import chalk6 from "chalk";
|
|
1850
1953
|
function registerListCommand(program2) {
|
|
1851
1954
|
program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
1852
1955
|
handleList(options);
|
|
@@ -1883,12 +1986,12 @@ function printListAsText(projectName, worktrees) {
|
|
|
1883
1986
|
for (const wt of worktrees) {
|
|
1884
1987
|
const status = getWorktreeStatus(wt);
|
|
1885
1988
|
const isIdle = status ? isWorktreeIdle(status) : false;
|
|
1886
|
-
const pathDisplay = isIdle ?
|
|
1989
|
+
const pathDisplay = isIdle ? chalk6.hex("#FF8C00")(wt.path) : wt.path;
|
|
1887
1990
|
printInfo(` ${pathDisplay} [${wt.branch}]`);
|
|
1888
1991
|
if (status) {
|
|
1889
1992
|
printInfo(` ${formatWorktreeStatus(status)}`);
|
|
1890
1993
|
} else {
|
|
1891
|
-
printInfo(` ${
|
|
1994
|
+
printInfo(` ${chalk6.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
|
|
1892
1995
|
}
|
|
1893
1996
|
printInfo("");
|
|
1894
1997
|
}
|
|
@@ -1991,19 +2094,16 @@ async function handleRemove(options) {
|
|
|
1991
2094
|
|
|
1992
2095
|
// src/commands/run.ts
|
|
1993
2096
|
function registerRunCommand(program2) {
|
|
1994
|
-
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) => {
|
|
2097
|
+
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) => {
|
|
1995
2098
|
await handleRun(options);
|
|
1996
2099
|
});
|
|
1997
2100
|
}
|
|
1998
|
-
function
|
|
1999
|
-
if (
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
const parsed = parseInt(optionValue, 10);
|
|
2003
|
-
if (Number.isNaN(parsed) || parsed < 0) {
|
|
2004
|
-
throw new ClawtError(MESSAGES.CONCURRENCY_INVALID);
|
|
2101
|
+
function resolveBranchNamesFromFile(options, entryCount, entries) {
|
|
2102
|
+
if (options.branch) {
|
|
2103
|
+
const sanitized = sanitizeBranchName(options.branch);
|
|
2104
|
+
return generateBranchNames(sanitized, entryCount);
|
|
2005
2105
|
}
|
|
2006
|
-
return
|
|
2106
|
+
return entries.map((e) => sanitizeBranchName(e.branch));
|
|
2007
2107
|
}
|
|
2008
2108
|
async function handleRunFromFile(options) {
|
|
2009
2109
|
const branchRequired = !options.branch;
|
|
@@ -2021,12 +2121,39 @@ async function handleRunFromFile(options) {
|
|
|
2021
2121
|
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"}`);
|
|
2022
2122
|
await executeBatchTasks(worktrees, tasks, concurrency);
|
|
2023
2123
|
}
|
|
2124
|
+
function handleDryRunFromFile(options) {
|
|
2125
|
+
const branchRequired = !options.branch;
|
|
2126
|
+
const entries = loadTaskFile(options.file, { branchRequired });
|
|
2127
|
+
printSuccess(MESSAGES.TASK_FILE_LOADED(entries.length, options.file));
|
|
2128
|
+
const tasks = entries.map((e) => e.task);
|
|
2129
|
+
const branchNames = resolveBranchNamesFromFile(options, entries.length, entries);
|
|
2130
|
+
const concurrency = parseConcurrency(options.concurrency, getConfigValue("maxConcurrency"));
|
|
2131
|
+
printDryRunPreview(branchNames, tasks, concurrency);
|
|
2132
|
+
}
|
|
2024
2133
|
async function handleRun(options) {
|
|
2025
2134
|
validateMainWorktree();
|
|
2026
|
-
validateClaudeCodeInstalled();
|
|
2027
2135
|
if (options.file && options.tasks) {
|
|
2028
2136
|
throw new ClawtError(MESSAGES.FILE_AND_TASKS_CONFLICT);
|
|
2029
2137
|
}
|
|
2138
|
+
if (options.dryRun) {
|
|
2139
|
+
if (options.file) {
|
|
2140
|
+
return handleDryRunFromFile(options);
|
|
2141
|
+
}
|
|
2142
|
+
if (!options.branch) {
|
|
2143
|
+
throw new ClawtError(MESSAGES.BRANCH_OR_FILE_REQUIRED);
|
|
2144
|
+
}
|
|
2145
|
+
const sanitized = sanitizeBranchName(options.branch);
|
|
2146
|
+
if (!options.tasks || options.tasks.length === 0) {
|
|
2147
|
+
printDryRunPreview([sanitized], [], 0);
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
const tasks2 = parseTasksFromOptions(options.tasks);
|
|
2151
|
+
const branchNames = generateBranchNames(sanitized, tasks2.length);
|
|
2152
|
+
const concurrency2 = parseConcurrency(options.concurrency, getConfigValue("maxConcurrency"));
|
|
2153
|
+
printDryRunPreview(branchNames, tasks2, concurrency2);
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
validateClaudeCodeInstalled();
|
|
2030
2157
|
if (options.file) {
|
|
2031
2158
|
return handleRunFromFile(options);
|
|
2032
2159
|
}
|
|
@@ -2044,10 +2171,7 @@ async function handleRun(options) {
|
|
|
2044
2171
|
launchInteractiveClaude(worktree);
|
|
2045
2172
|
return;
|
|
2046
2173
|
}
|
|
2047
|
-
const tasks = options.tasks
|
|
2048
|
-
if (tasks.length === 0) {
|
|
2049
|
-
throw new ClawtError("\u4EFB\u52A1\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A");
|
|
2050
|
-
}
|
|
2174
|
+
const tasks = parseTasksFromOptions(options.tasks);
|
|
2051
2175
|
const count = tasks.length;
|
|
2052
2176
|
const concurrency = parseConcurrency(options.concurrency, getConfigValue("maxConcurrency"));
|
|
2053
2177
|
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"}`);
|
|
@@ -2119,7 +2243,7 @@ var VALIDATE_RESOLVE_MESSAGES = {
|
|
|
2119
2243
|
noMatch: MESSAGES.VALIDATE_NO_MATCH
|
|
2120
2244
|
};
|
|
2121
2245
|
function registerValidateCommand(program2) {
|
|
2122
|
-
program2.command("validate").description("\u5728\u4E3B worktree \u9A8C\u8BC1\u67D0\u4E2A worktree \u5206\u652F\u7684\u53D8\u66F4").option("-b, --branch <branchName>", "\u8981\u9A8C\u8BC1\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").option("--clean", "\u6E05\u7406 validate \u72B6\u6001\uFF08\u91CD\u7F6E\u4E3B worktree \u5E76\u5220\u9664\u5FEB\u7167\uFF09").action(async (options) => {
|
|
2246
|
+
program2.command("validate").description("\u5728\u4E3B worktree \u9A8C\u8BC1\u67D0\u4E2A worktree \u5206\u652F\u7684\u53D8\u66F4").option("-b, --branch <branchName>", "\u8981\u9A8C\u8BC1\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").option("--clean", "\u6E05\u7406 validate \u72B6\u6001\uFF08\u91CD\u7F6E\u4E3B worktree \u5E76\u5220\u9664\u5FEB\u7167\uFF09").option("-r, --run <command>", "validate \u6210\u529F\u540E\u5728\u4E3B worktree \u4E2D\u6267\u884C\u7684\u547D\u4EE4").action(async (options) => {
|
|
2123
2247
|
await handleValidate(options);
|
|
2124
2248
|
});
|
|
2125
2249
|
}
|
|
@@ -2260,6 +2384,23 @@ function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, project
|
|
|
2260
2384
|
}
|
|
2261
2385
|
printSuccess(MESSAGES.INCREMENTAL_VALIDATE_SUCCESS(branchName));
|
|
2262
2386
|
}
|
|
2387
|
+
function executeRunCommand(command, mainWorktreePath) {
|
|
2388
|
+
printInfo("");
|
|
2389
|
+
printInfo(MESSAGES.VALIDATE_RUN_START(command));
|
|
2390
|
+
printSeparator();
|
|
2391
|
+
const result = runCommandInherited(command, { cwd: mainWorktreePath });
|
|
2392
|
+
printSeparator();
|
|
2393
|
+
if (result.error) {
|
|
2394
|
+
printError(MESSAGES.VALIDATE_RUN_ERROR(command, result.error.message));
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2397
|
+
const exitCode = result.status ?? 1;
|
|
2398
|
+
if (exitCode === 0) {
|
|
2399
|
+
printSuccess(MESSAGES.VALIDATE_RUN_SUCCESS(command));
|
|
2400
|
+
} else {
|
|
2401
|
+
printError(MESSAGES.VALIDATE_RUN_FAILED(command, exitCode));
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2263
2404
|
async function handleValidate(options) {
|
|
2264
2405
|
if (options.clean) {
|
|
2265
2406
|
await handleValidateClean(options);
|
|
@@ -2291,6 +2432,9 @@ async function handleValidate(options) {
|
|
|
2291
2432
|
}
|
|
2292
2433
|
handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted);
|
|
2293
2434
|
}
|
|
2435
|
+
if (options.run) {
|
|
2436
|
+
executeRunCommand(options.run, mainWorktreePath);
|
|
2437
|
+
}
|
|
2294
2438
|
}
|
|
2295
2439
|
|
|
2296
2440
|
// src/commands/merge.ts
|
|
@@ -2413,7 +2557,7 @@ async function handleMerge(options) {
|
|
|
2413
2557
|
}
|
|
2414
2558
|
|
|
2415
2559
|
// src/commands/config.ts
|
|
2416
|
-
import
|
|
2560
|
+
import chalk7 from "chalk";
|
|
2417
2561
|
import Enquirer5 from "enquirer";
|
|
2418
2562
|
function registerConfigCommand(program2) {
|
|
2419
2563
|
const configCmd = program2.command("config").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u5168\u5C40\u914D\u7F6E").action(async () => {
|
|
@@ -2474,7 +2618,7 @@ async function handleInteractiveConfigSet() {
|
|
|
2474
2618
|
const isObject = typeof DEFAULT_CONFIG[k] === "object";
|
|
2475
2619
|
return {
|
|
2476
2620
|
name: k,
|
|
2477
|
-
message: `${k}: ${isObject ?
|
|
2621
|
+
message: `${k}: ${isObject ? chalk7.dim(JSON.stringify(config2[k])) : formatConfigValue(config2[k])} ${chalk7.dim(`\u2014 ${CONFIG_DESCRIPTIONS[k]}`)}`,
|
|
2478
2622
|
...isObject && { disabled: CONFIG_ALIAS_DISABLED_HINT }
|
|
2479
2623
|
};
|
|
2480
2624
|
});
|
|
@@ -2583,7 +2727,7 @@ async function handleReset() {
|
|
|
2583
2727
|
}
|
|
2584
2728
|
|
|
2585
2729
|
// src/commands/status.ts
|
|
2586
|
-
import
|
|
2730
|
+
import chalk8 from "chalk";
|
|
2587
2731
|
function registerStatusCommand(program2) {
|
|
2588
2732
|
program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
2589
2733
|
handleStatus(options);
|
|
@@ -2679,7 +2823,7 @@ function printStatusAsJson(result) {
|
|
|
2679
2823
|
}
|
|
2680
2824
|
function printStatusAsText(result) {
|
|
2681
2825
|
printDoubleSeparator();
|
|
2682
|
-
printInfo(` ${
|
|
2826
|
+
printInfo(` ${chalk8.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
|
|
2683
2827
|
printDoubleSeparator();
|
|
2684
2828
|
printInfo("");
|
|
2685
2829
|
printMainSection(result.main);
|
|
@@ -2692,17 +2836,17 @@ function printStatusAsText(result) {
|
|
|
2692
2836
|
printDoubleSeparator();
|
|
2693
2837
|
}
|
|
2694
2838
|
function printMainSection(main) {
|
|
2695
|
-
printInfo(` ${
|
|
2696
|
-
printInfo(` \u5206\u652F: ${
|
|
2839
|
+
printInfo(` ${chalk8.bold("\u25C6")} ${chalk8.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
|
|
2840
|
+
printInfo(` \u5206\u652F: ${chalk8.bold(main.branch)}`);
|
|
2697
2841
|
if (main.isClean) {
|
|
2698
|
-
printInfo(` \u72B6\u6001: ${
|
|
2842
|
+
printInfo(` \u72B6\u6001: ${chalk8.green("\u2713 \u5E72\u51C0")}`);
|
|
2699
2843
|
} else {
|
|
2700
|
-
printInfo(` \u72B6\u6001: ${
|
|
2844
|
+
printInfo(` \u72B6\u6001: ${chalk8.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
|
|
2701
2845
|
}
|
|
2702
2846
|
printInfo("");
|
|
2703
2847
|
}
|
|
2704
2848
|
function printWorktreesSection(worktrees, total) {
|
|
2705
|
-
printInfo(` ${
|
|
2849
|
+
printInfo(` ${chalk8.bold("\u25C6")} ${chalk8.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
|
|
2706
2850
|
printInfo("");
|
|
2707
2851
|
if (worktrees.length === 0) {
|
|
2708
2852
|
printInfo(` ${MESSAGES.STATUS_NO_WORKTREES}`);
|
|
@@ -2714,39 +2858,39 @@ function printWorktreesSection(worktrees, total) {
|
|
|
2714
2858
|
}
|
|
2715
2859
|
function printWorktreeItem(wt) {
|
|
2716
2860
|
const statusLabel = formatChangeStatusLabel(wt.changeStatus);
|
|
2717
|
-
printInfo(` ${
|
|
2861
|
+
printInfo(` ${chalk8.bold("\u25CF")} ${chalk8.bold(wt.branch)} [${statusLabel}]`);
|
|
2718
2862
|
const parts = [];
|
|
2719
2863
|
if (wt.insertions > 0 || wt.deletions > 0) {
|
|
2720
|
-
parts.push(`${
|
|
2864
|
+
parts.push(`${chalk8.green(`+${wt.insertions}`)} ${chalk8.red(`-${wt.deletions}`)}`);
|
|
2721
2865
|
}
|
|
2722
2866
|
if (wt.commitsAhead > 0) {
|
|
2723
|
-
parts.push(
|
|
2867
|
+
parts.push(chalk8.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`));
|
|
2724
2868
|
}
|
|
2725
2869
|
if (wt.commitsBehind > 0) {
|
|
2726
|
-
parts.push(
|
|
2870
|
+
parts.push(chalk8.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`));
|
|
2727
2871
|
} else {
|
|
2728
|
-
parts.push(
|
|
2872
|
+
parts.push(chalk8.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65"));
|
|
2729
2873
|
}
|
|
2730
2874
|
printInfo(` ${parts.join(" ")}`);
|
|
2731
2875
|
if (wt.hasSnapshot) {
|
|
2732
|
-
printInfo(` ${
|
|
2876
|
+
printInfo(` ${chalk8.blue("\u6709 validate \u5FEB\u7167")}`);
|
|
2733
2877
|
}
|
|
2734
2878
|
printInfo("");
|
|
2735
2879
|
}
|
|
2736
2880
|
function formatChangeStatusLabel(status) {
|
|
2737
2881
|
switch (status) {
|
|
2738
2882
|
case "committed":
|
|
2739
|
-
return
|
|
2883
|
+
return chalk8.green(MESSAGES.STATUS_CHANGE_COMMITTED);
|
|
2740
2884
|
case "uncommitted":
|
|
2741
|
-
return
|
|
2885
|
+
return chalk8.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
|
|
2742
2886
|
case "conflict":
|
|
2743
|
-
return
|
|
2887
|
+
return chalk8.red(MESSAGES.STATUS_CHANGE_CONFLICT);
|
|
2744
2888
|
case "clean":
|
|
2745
|
-
return
|
|
2889
|
+
return chalk8.gray(MESSAGES.STATUS_CHANGE_CLEAN);
|
|
2746
2890
|
}
|
|
2747
2891
|
}
|
|
2748
2892
|
function printSnapshotsSection(snapshots) {
|
|
2749
|
-
printInfo(` ${
|
|
2893
|
+
printInfo(` ${chalk8.bold("\u25C6")} ${chalk8.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.length} \u4E2A)`);
|
|
2750
2894
|
printInfo("");
|
|
2751
2895
|
if (snapshots.length === 0) {
|
|
2752
2896
|
printInfo(` ${MESSAGES.STATUS_NO_SNAPSHOTS}`);
|
|
@@ -2754,15 +2898,15 @@ function printSnapshotsSection(snapshots) {
|
|
|
2754
2898
|
return;
|
|
2755
2899
|
}
|
|
2756
2900
|
for (const snap of snapshots) {
|
|
2757
|
-
const orphanLabel = snap.worktreeExists ? "" : ` ${
|
|
2758
|
-
const icon = snap.worktreeExists ?
|
|
2901
|
+
const orphanLabel = snap.worktreeExists ? "" : ` ${chalk8.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED)}`;
|
|
2902
|
+
const icon = snap.worktreeExists ? chalk8.blue("\u25CF") : chalk8.yellow("\u26A0");
|
|
2759
2903
|
printInfo(` ${icon} ${snap.branch}${orphanLabel}`);
|
|
2760
2904
|
}
|
|
2761
2905
|
printInfo("");
|
|
2762
2906
|
}
|
|
2763
2907
|
|
|
2764
2908
|
// src/commands/alias.ts
|
|
2765
|
-
import
|
|
2909
|
+
import chalk9 from "chalk";
|
|
2766
2910
|
function getRegisteredCommandNames(program2) {
|
|
2767
2911
|
return program2.commands.map((cmd) => cmd.name());
|
|
2768
2912
|
}
|
|
@@ -2783,7 +2927,7 @@ ${MESSAGES.ALIAS_LIST_TITLE}
|
|
|
2783
2927
|
`);
|
|
2784
2928
|
printSeparator();
|
|
2785
2929
|
for (const [alias, command] of entries) {
|
|
2786
|
-
printInfo(` ${
|
|
2930
|
+
printInfo(` ${chalk9.bold(alias)} \u2192 ${chalk9.cyan(command)}`);
|
|
2787
2931
|
}
|
|
2788
2932
|
printInfo("");
|
|
2789
2933
|
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
|
|
@@ -165,7 +181,15 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
|
165
181
|
/** validate 交互选择提示 */
|
|
166
182
|
VALIDATE_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u9A8C\u8BC1\u7684\u5206\u652F",
|
|
167
183
|
/** validate 模糊匹配到多个结果提示 */
|
|
168
|
-
VALIDATE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A
|
|
184
|
+
VALIDATE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
|
|
185
|
+
/** --run 命令开始执行提示 */
|
|
186
|
+
VALIDATE_RUN_START: (command) => `\u6B63\u5728\u4E3B worktree \u4E2D\u6267\u884C\u547D\u4EE4: ${command}`,
|
|
187
|
+
/** --run 命令执行成功(退出码 0) */
|
|
188
|
+
VALIDATE_RUN_SUCCESS: (command) => `\u2713 \u547D\u4EE4\u6267\u884C\u5B8C\u6210: ${command}\uFF0C\u9000\u51FA\u7801: 0`,
|
|
189
|
+
/** --run 命令执行失败(退出码非 0) */
|
|
190
|
+
VALIDATE_RUN_FAILED: (command, exitCode) => `\u2717 \u547D\u4EE4\u6267\u884C\u5B8C\u6210: ${command}\uFF0C\u9000\u51FA\u7801: ${exitCode}`,
|
|
191
|
+
/** --run 命令执行异常(进程启动失败等) */
|
|
192
|
+
VALIDATE_RUN_ERROR: (command, errorMessage) => `\u2717 \u547D\u4EE4\u6267\u884C\u51FA\u9519: ${errorMessage}`
|
|
169
193
|
};
|
|
170
194
|
|
|
171
195
|
// src/constants/messages/sync.ts
|