clawt 2.14.1 → 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/README.md +23 -0
- package/dist/index.js +153 -45
- package/dist/postinstall.js +17 -1
- package/docs/spec.md +46 -0
- package/package.json +1 -1
- package/src/commands/run.ts +68 -21
- 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/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
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
|
**任务文件格式:**
|
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
|
|
@@ -969,6 +985,16 @@ function ensureClawtDirs() {
|
|
|
969
985
|
ensureDir(LOGS_DIR);
|
|
970
986
|
ensureDir(WORKTREES_DIR);
|
|
971
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
|
+
}
|
|
972
998
|
|
|
973
999
|
// src/utils/prompt.ts
|
|
974
1000
|
import Enquirer from "enquirer";
|
|
@@ -1492,6 +1518,14 @@ import { resolve } from "path";
|
|
|
1492
1518
|
import { existsSync as existsSync8, readFileSync as readFileSync3 } from "fs";
|
|
1493
1519
|
var TASK_BLOCK_REGEX = /<!-- CLAWT-TASKS:START -->([\s\S]*?)<!-- CLAWT-TASKS:END -->/g;
|
|
1494
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
|
+
}
|
|
1495
1529
|
function parseTaskFile(content, options) {
|
|
1496
1530
|
const branchRequired = options?.branchRequired ?? true;
|
|
1497
1531
|
const entries = [];
|
|
@@ -1742,6 +1776,59 @@ async function executeBatchTasks(worktrees, tasks, concurrency) {
|
|
|
1742
1776
|
printTaskSummary(summary);
|
|
1743
1777
|
}
|
|
1744
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
|
+
|
|
1745
1832
|
// src/utils/alias.ts
|
|
1746
1833
|
function applyAliases(program2, aliases) {
|
|
1747
1834
|
for (const [alias, commandName] of Object.entries(aliases)) {
|
|
@@ -1756,7 +1843,7 @@ function applyAliases(program2, aliases) {
|
|
|
1756
1843
|
}
|
|
1757
1844
|
|
|
1758
1845
|
// src/utils/config-strategy.ts
|
|
1759
|
-
import
|
|
1846
|
+
import chalk5 from "chalk";
|
|
1760
1847
|
import Enquirer3 from "enquirer";
|
|
1761
1848
|
function isValidConfigKey(key) {
|
|
1762
1849
|
return key in DEFAULT_CONFIG;
|
|
@@ -1800,9 +1887,9 @@ async function promptConfigValue(key, currentValue) {
|
|
|
1800
1887
|
}
|
|
1801
1888
|
function formatConfigValue(value) {
|
|
1802
1889
|
if (typeof value === "boolean") {
|
|
1803
|
-
return value ?
|
|
1890
|
+
return value ? chalk5.green("true") : chalk5.yellow("false");
|
|
1804
1891
|
}
|
|
1805
|
-
return
|
|
1892
|
+
return chalk5.cyan(String(value));
|
|
1806
1893
|
}
|
|
1807
1894
|
async function promptBooleanValue(key, currentValue) {
|
|
1808
1895
|
const choices = [
|
|
@@ -1846,7 +1933,7 @@ async function promptStringValue(key, currentValue) {
|
|
|
1846
1933
|
}
|
|
1847
1934
|
|
|
1848
1935
|
// src/commands/list.ts
|
|
1849
|
-
import
|
|
1936
|
+
import chalk6 from "chalk";
|
|
1850
1937
|
function registerListCommand(program2) {
|
|
1851
1938
|
program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
1852
1939
|
handleList(options);
|
|
@@ -1883,12 +1970,12 @@ function printListAsText(projectName, worktrees) {
|
|
|
1883
1970
|
for (const wt of worktrees) {
|
|
1884
1971
|
const status = getWorktreeStatus(wt);
|
|
1885
1972
|
const isIdle = status ? isWorktreeIdle(status) : false;
|
|
1886
|
-
const pathDisplay = isIdle ?
|
|
1973
|
+
const pathDisplay = isIdle ? chalk6.hex("#FF8C00")(wt.path) : wt.path;
|
|
1887
1974
|
printInfo(` ${pathDisplay} [${wt.branch}]`);
|
|
1888
1975
|
if (status) {
|
|
1889
1976
|
printInfo(` ${formatWorktreeStatus(status)}`);
|
|
1890
1977
|
} else {
|
|
1891
|
-
printInfo(` ${
|
|
1978
|
+
printInfo(` ${chalk6.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
|
|
1892
1979
|
}
|
|
1893
1980
|
printInfo("");
|
|
1894
1981
|
}
|
|
@@ -1991,19 +2078,16 @@ async function handleRemove(options) {
|
|
|
1991
2078
|
|
|
1992
2079
|
// src/commands/run.ts
|
|
1993
2080
|
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) => {
|
|
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) => {
|
|
1995
2082
|
await handleRun(options);
|
|
1996
2083
|
});
|
|
1997
2084
|
}
|
|
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);
|
|
2085
|
+
function resolveBranchNamesFromFile(options, entryCount, entries) {
|
|
2086
|
+
if (options.branch) {
|
|
2087
|
+
const sanitized = sanitizeBranchName(options.branch);
|
|
2088
|
+
return generateBranchNames(sanitized, entryCount);
|
|
2005
2089
|
}
|
|
2006
|
-
return
|
|
2090
|
+
return entries.map((e) => sanitizeBranchName(e.branch));
|
|
2007
2091
|
}
|
|
2008
2092
|
async function handleRunFromFile(options) {
|
|
2009
2093
|
const branchRequired = !options.branch;
|
|
@@ -2021,12 +2105,39 @@ async function handleRunFromFile(options) {
|
|
|
2021
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"}`);
|
|
2022
2106
|
await executeBatchTasks(worktrees, tasks, concurrency);
|
|
2023
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
|
+
}
|
|
2024
2117
|
async function handleRun(options) {
|
|
2025
2118
|
validateMainWorktree();
|
|
2026
|
-
validateClaudeCodeInstalled();
|
|
2027
2119
|
if (options.file && options.tasks) {
|
|
2028
2120
|
throw new ClawtError(MESSAGES.FILE_AND_TASKS_CONFLICT);
|
|
2029
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();
|
|
2030
2141
|
if (options.file) {
|
|
2031
2142
|
return handleRunFromFile(options);
|
|
2032
2143
|
}
|
|
@@ -2044,10 +2155,7 @@ async function handleRun(options) {
|
|
|
2044
2155
|
launchInteractiveClaude(worktree);
|
|
2045
2156
|
return;
|
|
2046
2157
|
}
|
|
2047
|
-
const tasks = options.tasks
|
|
2048
|
-
if (tasks.length === 0) {
|
|
2049
|
-
throw new ClawtError("\u4EFB\u52A1\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A");
|
|
2050
|
-
}
|
|
2158
|
+
const tasks = parseTasksFromOptions(options.tasks);
|
|
2051
2159
|
const count = tasks.length;
|
|
2052
2160
|
const concurrency = parseConcurrency(options.concurrency, getConfigValue("maxConcurrency"));
|
|
2053
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"}`);
|
|
@@ -2413,7 +2521,7 @@ async function handleMerge(options) {
|
|
|
2413
2521
|
}
|
|
2414
2522
|
|
|
2415
2523
|
// src/commands/config.ts
|
|
2416
|
-
import
|
|
2524
|
+
import chalk7 from "chalk";
|
|
2417
2525
|
import Enquirer5 from "enquirer";
|
|
2418
2526
|
function registerConfigCommand(program2) {
|
|
2419
2527
|
const configCmd = program2.command("config").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u5168\u5C40\u914D\u7F6E").action(async () => {
|
|
@@ -2474,7 +2582,7 @@ async function handleInteractiveConfigSet() {
|
|
|
2474
2582
|
const isObject = typeof DEFAULT_CONFIG[k] === "object";
|
|
2475
2583
|
return {
|
|
2476
2584
|
name: k,
|
|
2477
|
-
message: `${k}: ${isObject ?
|
|
2585
|
+
message: `${k}: ${isObject ? chalk7.dim(JSON.stringify(config2[k])) : formatConfigValue(config2[k])} ${chalk7.dim(`\u2014 ${CONFIG_DESCRIPTIONS[k]}`)}`,
|
|
2478
2586
|
...isObject && { disabled: CONFIG_ALIAS_DISABLED_HINT }
|
|
2479
2587
|
};
|
|
2480
2588
|
});
|
|
@@ -2583,7 +2691,7 @@ async function handleReset() {
|
|
|
2583
2691
|
}
|
|
2584
2692
|
|
|
2585
2693
|
// src/commands/status.ts
|
|
2586
|
-
import
|
|
2694
|
+
import chalk8 from "chalk";
|
|
2587
2695
|
function registerStatusCommand(program2) {
|
|
2588
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) => {
|
|
2589
2697
|
handleStatus(options);
|
|
@@ -2679,7 +2787,7 @@ function printStatusAsJson(result) {
|
|
|
2679
2787
|
}
|
|
2680
2788
|
function printStatusAsText(result) {
|
|
2681
2789
|
printDoubleSeparator();
|
|
2682
|
-
printInfo(` ${
|
|
2790
|
+
printInfo(` ${chalk8.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
|
|
2683
2791
|
printDoubleSeparator();
|
|
2684
2792
|
printInfo("");
|
|
2685
2793
|
printMainSection(result.main);
|
|
@@ -2692,17 +2800,17 @@ function printStatusAsText(result) {
|
|
|
2692
2800
|
printDoubleSeparator();
|
|
2693
2801
|
}
|
|
2694
2802
|
function printMainSection(main) {
|
|
2695
|
-
printInfo(` ${
|
|
2696
|
-
printInfo(` \u5206\u652F: ${
|
|
2803
|
+
printInfo(` ${chalk8.bold("\u25C6")} ${chalk8.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
|
|
2804
|
+
printInfo(` \u5206\u652F: ${chalk8.bold(main.branch)}`);
|
|
2697
2805
|
if (main.isClean) {
|
|
2698
|
-
printInfo(` \u72B6\u6001: ${
|
|
2806
|
+
printInfo(` \u72B6\u6001: ${chalk8.green("\u2713 \u5E72\u51C0")}`);
|
|
2699
2807
|
} else {
|
|
2700
|
-
printInfo(` \u72B6\u6001: ${
|
|
2808
|
+
printInfo(` \u72B6\u6001: ${chalk8.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
|
|
2701
2809
|
}
|
|
2702
2810
|
printInfo("");
|
|
2703
2811
|
}
|
|
2704
2812
|
function printWorktreesSection(worktrees, total) {
|
|
2705
|
-
printInfo(` ${
|
|
2813
|
+
printInfo(` ${chalk8.bold("\u25C6")} ${chalk8.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
|
|
2706
2814
|
printInfo("");
|
|
2707
2815
|
if (worktrees.length === 0) {
|
|
2708
2816
|
printInfo(` ${MESSAGES.STATUS_NO_WORKTREES}`);
|
|
@@ -2714,39 +2822,39 @@ function printWorktreesSection(worktrees, total) {
|
|
|
2714
2822
|
}
|
|
2715
2823
|
function printWorktreeItem(wt) {
|
|
2716
2824
|
const statusLabel = formatChangeStatusLabel(wt.changeStatus);
|
|
2717
|
-
printInfo(` ${
|
|
2825
|
+
printInfo(` ${chalk8.bold("\u25CF")} ${chalk8.bold(wt.branch)} [${statusLabel}]`);
|
|
2718
2826
|
const parts = [];
|
|
2719
2827
|
if (wt.insertions > 0 || wt.deletions > 0) {
|
|
2720
|
-
parts.push(`${
|
|
2828
|
+
parts.push(`${chalk8.green(`+${wt.insertions}`)} ${chalk8.red(`-${wt.deletions}`)}`);
|
|
2721
2829
|
}
|
|
2722
2830
|
if (wt.commitsAhead > 0) {
|
|
2723
|
-
parts.push(
|
|
2831
|
+
parts.push(chalk8.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`));
|
|
2724
2832
|
}
|
|
2725
2833
|
if (wt.commitsBehind > 0) {
|
|
2726
|
-
parts.push(
|
|
2834
|
+
parts.push(chalk8.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`));
|
|
2727
2835
|
} else {
|
|
2728
|
-
parts.push(
|
|
2836
|
+
parts.push(chalk8.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65"));
|
|
2729
2837
|
}
|
|
2730
2838
|
printInfo(` ${parts.join(" ")}`);
|
|
2731
2839
|
if (wt.hasSnapshot) {
|
|
2732
|
-
printInfo(` ${
|
|
2840
|
+
printInfo(` ${chalk8.blue("\u6709 validate \u5FEB\u7167")}`);
|
|
2733
2841
|
}
|
|
2734
2842
|
printInfo("");
|
|
2735
2843
|
}
|
|
2736
2844
|
function formatChangeStatusLabel(status) {
|
|
2737
2845
|
switch (status) {
|
|
2738
2846
|
case "committed":
|
|
2739
|
-
return
|
|
2847
|
+
return chalk8.green(MESSAGES.STATUS_CHANGE_COMMITTED);
|
|
2740
2848
|
case "uncommitted":
|
|
2741
|
-
return
|
|
2849
|
+
return chalk8.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
|
|
2742
2850
|
case "conflict":
|
|
2743
|
-
return
|
|
2851
|
+
return chalk8.red(MESSAGES.STATUS_CHANGE_CONFLICT);
|
|
2744
2852
|
case "clean":
|
|
2745
|
-
return
|
|
2853
|
+
return chalk8.gray(MESSAGES.STATUS_CHANGE_CLEAN);
|
|
2746
2854
|
}
|
|
2747
2855
|
}
|
|
2748
2856
|
function printSnapshotsSection(snapshots) {
|
|
2749
|
-
printInfo(` ${
|
|
2857
|
+
printInfo(` ${chalk8.bold("\u25C6")} ${chalk8.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.length} \u4E2A)`);
|
|
2750
2858
|
printInfo("");
|
|
2751
2859
|
if (snapshots.length === 0) {
|
|
2752
2860
|
printInfo(` ${MESSAGES.STATUS_NO_SNAPSHOTS}`);
|
|
@@ -2754,15 +2862,15 @@ function printSnapshotsSection(snapshots) {
|
|
|
2754
2862
|
return;
|
|
2755
2863
|
}
|
|
2756
2864
|
for (const snap of snapshots) {
|
|
2757
|
-
const orphanLabel = snap.worktreeExists ? "" : ` ${
|
|
2758
|
-
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");
|
|
2759
2867
|
printInfo(` ${icon} ${snap.branch}${orphanLabel}`);
|
|
2760
2868
|
}
|
|
2761
2869
|
printInfo("");
|
|
2762
2870
|
}
|
|
2763
2871
|
|
|
2764
2872
|
// src/commands/alias.ts
|
|
2765
|
-
import
|
|
2873
|
+
import chalk9 from "chalk";
|
|
2766
2874
|
function getRegisteredCommandNames(program2) {
|
|
2767
2875
|
return program2.commands.map((cmd) => cmd.name());
|
|
2768
2876
|
}
|
|
@@ -2783,7 +2891,7 @@ ${MESSAGES.ALIAS_LIST_TITLE}
|
|
|
2783
2891
|
`);
|
|
2784
2892
|
printSeparator();
|
|
2785
2893
|
for (const [alias, command] of entries) {
|
|
2786
|
-
printInfo(` ${
|
|
2894
|
+
printInfo(` ${chalk9.bold(alias)} \u2192 ${chalk9.cyan(command)}`);
|
|
2787
2895
|
}
|
|
2788
2896
|
printInfo("");
|
|
2789
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
|
package/docs/spec.md
CHANGED
|
@@ -278,6 +278,7 @@ clawt run -b <branchName>
|
|
|
278
278
|
| `--tasks` | 否 | 任务描述(可多次指定,每个 --tasks 对应一个任务,任务数量即 worktree 数量)。不传则在 worktree 中打开 Claude Code 交互式界面 |
|
|
279
279
|
| `-f` | 否 | 从任务文件读取任务列表(与 `--tasks` 互斥) |
|
|
280
280
|
| `-c` | 否 | 最大并发数,`0` 表示不限制 |
|
|
281
|
+
| `--dry-run` | 否 | 试运行模式,仅输出预览信息不实际执行 |
|
|
281
282
|
|
|
282
283
|
**互斥约束:**
|
|
283
284
|
|
|
@@ -351,6 +352,51 @@ clawt run -b <branchName>
|
|
|
351
352
|
|
|
352
353
|
**注意:** 当 `n = 1` 时(只有一个任务),worktree 目录命名规则同 **5.1**(不加 `-1` 后缀)。
|
|
353
354
|
|
|
355
|
+
#### `--dry-run` 预览模式
|
|
356
|
+
|
|
357
|
+
传入 `--dry-run` 时不实际创建 worktree 和执行任务,仅输出预览信息供用户确认。预览由 `printDryRunPreview()`(`src/utils/dry-run.ts`)负责渲染。
|
|
358
|
+
|
|
359
|
+
**输出格式:**
|
|
360
|
+
|
|
361
|
+
```
|
|
362
|
+
════════════════════════════════════════
|
|
363
|
+
Dry Run 预览
|
|
364
|
+
════════════════════════════════════════
|
|
365
|
+
任务数: 3 │ 并发数: 不限制 │ Worktree: ~/.clawt/worktrees/project
|
|
366
|
+
────────────────────────────────────────
|
|
367
|
+
✓ [1/3] feat-login
|
|
368
|
+
路径: ~/.clawt/worktrees/project/feat-login
|
|
369
|
+
任务: 实现登录功能
|
|
370
|
+
|
|
371
|
+
⚠ [2/3] feat-signup — 分支 feat-signup 已存在
|
|
372
|
+
路径: ~/.clawt/worktrees/project/feat-signup
|
|
373
|
+
任务: 实现注册功能
|
|
374
|
+
|
|
375
|
+
✓ [3/3] fix-bug
|
|
376
|
+
路径: ~/.clawt/worktrees/project/fix-bug
|
|
377
|
+
任务: 修复内存泄漏
|
|
378
|
+
|
|
379
|
+
════════════════════════════════════════
|
|
380
|
+
✓ 预览完成,无冲突。移除 --dry-run 即可正式执行。
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**格式规则:**
|
|
384
|
+
|
|
385
|
+
1. **标题区**:双线分隔符包裹标题 `Dry Run 预览`
|
|
386
|
+
2. **摘要行**:任务数、并发数、Worktree 目录路径合并为一行,用灰色 `│` 分隔;交互式模式(无 `--tasks`)会额外追加模式信息
|
|
387
|
+
3. **分支列表**:
|
|
388
|
+
- 正常分支:行首绿色 `✓` + 序号 + 青色分支名
|
|
389
|
+
- 冲突分支:行首黄色 `⚠` + 序号 + 黄色分支名 + 灰色 `—` + 黄色警告文本(如 `分支 xxx 已存在`),警告合并在序号行
|
|
390
|
+
4. **路径/任务行**:2 空格缩进,灰色标签前缀(`路径:` / `任务:`)
|
|
391
|
+
5. **任务描述截断**:超过 70 字符时末尾加 `...`,多行合并为单行
|
|
392
|
+
6. **结尾**:双线分隔符后根据冲突情况输出结论——无冲突时绿色 `✓` 提示,有冲突时黄色 `⚠` 警告
|
|
393
|
+
|
|
394
|
+
**实现要点:**
|
|
395
|
+
|
|
396
|
+
- 常量定义在 `src/constants/messages/run.ts`(`DRY_RUN_*` 系列)
|
|
397
|
+
- `DRY_RUN_WORKTREE_DIR` 前缀为 `Worktree:`(简短形式)
|
|
398
|
+
- `truncateTaskDesc()` 负责截断任务描述(最大长度 70 字符)
|
|
399
|
+
|
|
354
400
|
---
|
|
355
401
|
|
|
356
402
|
### 5.3 任务完成通知机制
|
package/package.json
CHANGED
package/src/commands/run.ts
CHANGED
|
@@ -9,12 +9,16 @@ import {
|
|
|
9
9
|
createWorktrees,
|
|
10
10
|
createWorktreesByBranches,
|
|
11
11
|
sanitizeBranchName,
|
|
12
|
+
generateBranchNames,
|
|
12
13
|
checkBranchExists,
|
|
13
14
|
getConfigValue,
|
|
15
|
+
parseConcurrency,
|
|
14
16
|
printSuccess,
|
|
15
17
|
launchInteractiveClaude,
|
|
16
18
|
loadTaskFile,
|
|
19
|
+
parseTasksFromOptions,
|
|
17
20
|
executeBatchTasks,
|
|
21
|
+
printDryRunPreview,
|
|
18
22
|
} from '../utils/index.js';
|
|
19
23
|
|
|
20
24
|
/**
|
|
@@ -29,28 +33,32 @@ export function registerRunCommand(program: Command): void {
|
|
|
29
33
|
.option('--tasks <task...>', '任务列表(可多次指定),不传则在 worktree 中打开 Claude Code 交互式界面')
|
|
30
34
|
.option('-c, --concurrency <n>', '最大并发数,0 表示不限制')
|
|
31
35
|
.option('-f, --file <path>', '从任务文件读取任务列表(与 --tasks 互斥)')
|
|
36
|
+
.option('-d, --dry-run', '预览模式,仅展示任务计划不实际执行')
|
|
32
37
|
.action(async (options: RunOptions) => {
|
|
33
38
|
await handleRun(options);
|
|
34
39
|
});
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* @param {
|
|
41
|
-
* @param {number}
|
|
42
|
-
* @
|
|
43
|
+
* 从任务文件解析出分支名列表
|
|
44
|
+
* 有 -b 参数时使用自动编号,否则使用文件中每个任务块的独立分支名
|
|
45
|
+
* @param {RunOptions} options - 命令选项
|
|
46
|
+
* @param {number} entryCount - 任务条目数量
|
|
47
|
+
* @param {Array<{branch?: string}>} entries - 解析出的任务条目(含可选分支名)
|
|
48
|
+
* @returns {string[]} 分支名列表
|
|
43
49
|
*/
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
function resolveBranchNamesFromFile(
|
|
51
|
+
options: RunOptions,
|
|
52
|
+
entryCount: number,
|
|
53
|
+
entries: Array<{ branch?: string }>,
|
|
54
|
+
): string[] {
|
|
55
|
+
if (options.branch) {
|
|
56
|
+
// 有 -b 参数:忽略文件中的分支名,用 -b 自动编号
|
|
57
|
+
const sanitized = sanitizeBranchName(options.branch);
|
|
58
|
+
return generateBranchNames(sanitized, entryCount);
|
|
52
59
|
}
|
|
53
|
-
|
|
60
|
+
// 无 -b 参数:使用文件中每个任务的独立分支名
|
|
61
|
+
return entries.map((e) => sanitizeBranchName(e.branch!));
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
/**
|
|
@@ -85,6 +93,22 @@ async function handleRunFromFile(options: RunOptions): Promise<void> {
|
|
|
85
93
|
await executeBatchTasks(worktrees, tasks, concurrency);
|
|
86
94
|
}
|
|
87
95
|
|
|
96
|
+
/**
|
|
97
|
+
* 处理 dry-run 模式下从任务文件读取的逻辑
|
|
98
|
+
* @param {RunOptions} options - 命令选项(包含 file 字段)
|
|
99
|
+
*/
|
|
100
|
+
function handleDryRunFromFile(options: RunOptions): void {
|
|
101
|
+
const branchRequired = !options.branch;
|
|
102
|
+
const entries = loadTaskFile(options.file!, { branchRequired });
|
|
103
|
+
printSuccess(MESSAGES.TASK_FILE_LOADED(entries.length, options.file!));
|
|
104
|
+
|
|
105
|
+
const tasks = entries.map((e) => e.task);
|
|
106
|
+
const branchNames = resolveBranchNamesFromFile(options, entries.length, entries);
|
|
107
|
+
const concurrency = parseConcurrency(options.concurrency, getConfigValue('maxConcurrency'));
|
|
108
|
+
|
|
109
|
+
printDryRunPreview(branchNames, tasks, concurrency);
|
|
110
|
+
}
|
|
111
|
+
|
|
88
112
|
/**
|
|
89
113
|
* 执行 run 命令的核心逻辑
|
|
90
114
|
* 支持三种模式:
|
|
@@ -95,13 +119,41 @@ async function handleRunFromFile(options: RunOptions): Promise<void> {
|
|
|
95
119
|
*/
|
|
96
120
|
async function handleRun(options: RunOptions): Promise<void> {
|
|
97
121
|
validateMainWorktree();
|
|
98
|
-
validateClaudeCodeInstalled();
|
|
99
122
|
|
|
100
123
|
// 互斥校验:--file 和 --tasks 不能同时使用
|
|
101
124
|
if (options.file && options.tasks) {
|
|
102
125
|
throw new ClawtError(MESSAGES.FILE_AND_TASKS_CONFLICT);
|
|
103
126
|
}
|
|
104
127
|
|
|
128
|
+
// dry-run 模式:仅解析和展示任务计划,不实际创建 worktree 或启动 Claude Code
|
|
129
|
+
if (options.dryRun) {
|
|
130
|
+
// dry-run 不需要校验 Claude Code 是否安装
|
|
131
|
+
if (options.file) {
|
|
132
|
+
return handleDryRunFromFile(options);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!options.branch) {
|
|
136
|
+
throw new ClawtError(MESSAGES.BRANCH_OR_FILE_REQUIRED);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const sanitized = sanitizeBranchName(options.branch);
|
|
140
|
+
|
|
141
|
+
if (!options.tasks || options.tasks.length === 0) {
|
|
142
|
+
// 交互式模式 dry-run:展示单个 worktree 信息
|
|
143
|
+
printDryRunPreview([sanitized], [], 0);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const tasks = parseTasksFromOptions(options.tasks);
|
|
148
|
+
const branchNames = generateBranchNames(sanitized, tasks.length);
|
|
149
|
+
const concurrency = parseConcurrency(options.concurrency, getConfigValue('maxConcurrency'));
|
|
150
|
+
printDryRunPreview(branchNames, tasks, concurrency);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 正常执行模式需要校验 Claude Code
|
|
155
|
+
validateClaudeCodeInstalled();
|
|
156
|
+
|
|
105
157
|
// --file 模式
|
|
106
158
|
if (options.file) {
|
|
107
159
|
return handleRunFromFile(options);
|
|
@@ -128,12 +180,7 @@ async function handleRun(options: RunOptions): Promise<void> {
|
|
|
128
180
|
return;
|
|
129
181
|
}
|
|
130
182
|
|
|
131
|
-
const tasks = options.tasks
|
|
132
|
-
|
|
133
|
-
if (tasks.length === 0) {
|
|
134
|
-
throw new ClawtError('任务列表不能为空');
|
|
135
|
-
}
|
|
136
|
-
|
|
183
|
+
const tasks = parseTasksFromOptions(options.tasks);
|
|
137
184
|
const count = tasks.length;
|
|
138
185
|
|
|
139
186
|
// 解析并发数:命令行参数 > 全局配置 > 默认值 0
|
|
@@ -43,4 +43,20 @@ export const RUN_MESSAGES = {
|
|
|
43
43
|
TASK_FILE_LOADED: (count: number, path: string) => `✓ 从 ${path} 加载了 ${count} 个任务`,
|
|
44
44
|
/** 未指定 -b 或 -f */
|
|
45
45
|
BRANCH_OR_FILE_REQUIRED: '请指定 -b 分支名或 -f 任务文件',
|
|
46
|
+
/** dry-run 预览标题 */
|
|
47
|
+
DRY_RUN_TITLE: 'Dry Run 预览',
|
|
48
|
+
/** dry-run 任务数量 */
|
|
49
|
+
DRY_RUN_TASK_COUNT: (count: number) => `任务数: ${count}`,
|
|
50
|
+
/** dry-run 并发数 */
|
|
51
|
+
DRY_RUN_CONCURRENCY: (concurrency: number) => `并发数: ${concurrency === 0 ? '不限制' : concurrency}`,
|
|
52
|
+
/** dry-run worktree 目录 */
|
|
53
|
+
DRY_RUN_WORKTREE_DIR: (dir: string) => `Worktree: ${dir}`,
|
|
54
|
+
/** dry-run 分支已存在警告 */
|
|
55
|
+
DRY_RUN_BRANCH_EXISTS_WARNING: (name: string) => `分支 ${name} 已存在`,
|
|
56
|
+
/** dry-run 交互式模式提示(无任务描述) */
|
|
57
|
+
DRY_RUN_INTERACTIVE_MODE: '模式: 交互式(无预设任务)',
|
|
58
|
+
/** dry-run 预览完成且无冲突 */
|
|
59
|
+
DRY_RUN_READY: '预览完成,无冲突。移除 --dry-run 即可正式执行。',
|
|
60
|
+
/** dry-run 存在分支冲突 */
|
|
61
|
+
DRY_RUN_HAS_CONFLICT: '存在分支冲突,实际执行时将会报错。请先处理冲突的分支。',
|
|
46
62
|
} as const;
|