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 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> # 将变更迁移到主 worktree 测试
100
- clawt validate -b <branch> --clean # 清理 validate 状态
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 = spawnSync(cmd, args, {
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 chalk4 from "chalk";
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 ? chalk4.green("true") : chalk4.yellow("false");
1906
+ return value ? chalk5.green("true") : chalk5.yellow("false");
1804
1907
  }
1805
- return chalk4.cyan(String(value));
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 chalk5 from "chalk";
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 ? chalk5.hex("#FF8C00")(wt.path) : wt.path;
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(` ${chalk5.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
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 parseConcurrency(optionValue, configValue) {
1999
- if (optionValue === void 0) {
2000
- return configValue;
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 parsed;
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.map((t) => t.trim()).filter(Boolean);
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 chalk6 from "chalk";
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 ? chalk6.dim(JSON.stringify(config2[k])) : formatConfigValue(config2[k])} ${chalk6.dim(`\u2014 ${CONFIG_DESCRIPTIONS[k]}`)}`,
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 chalk7 from "chalk";
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(` ${chalk7.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
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(` ${chalk7.bold("\u25C6")} ${chalk7.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
2696
- printInfo(` \u5206\u652F: ${chalk7.bold(main.branch)}`);
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: ${chalk7.green("\u2713 \u5E72\u51C0")}`);
2842
+ printInfo(` \u72B6\u6001: ${chalk8.green("\u2713 \u5E72\u51C0")}`);
2699
2843
  } else {
2700
- printInfo(` \u72B6\u6001: ${chalk7.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
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(` ${chalk7.bold("\u25C6")} ${chalk7.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
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(` ${chalk7.bold("\u25CF")} ${chalk7.bold(wt.branch)} [${statusLabel}]`);
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(`${chalk7.green(`+${wt.insertions}`)} ${chalk7.red(`-${wt.deletions}`)}`);
2864
+ parts.push(`${chalk8.green(`+${wt.insertions}`)} ${chalk8.red(`-${wt.deletions}`)}`);
2721
2865
  }
2722
2866
  if (wt.commitsAhead > 0) {
2723
- parts.push(chalk7.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`));
2867
+ parts.push(chalk8.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`));
2724
2868
  }
2725
2869
  if (wt.commitsBehind > 0) {
2726
- parts.push(chalk7.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`));
2870
+ parts.push(chalk8.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`));
2727
2871
  } else {
2728
- parts.push(chalk7.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65"));
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(` ${chalk7.blue("\u6709 validate \u5FEB\u7167")}`);
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 chalk7.green(MESSAGES.STATUS_CHANGE_COMMITTED);
2883
+ return chalk8.green(MESSAGES.STATUS_CHANGE_COMMITTED);
2740
2884
  case "uncommitted":
2741
- return chalk7.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
2885
+ return chalk8.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
2742
2886
  case "conflict":
2743
- return chalk7.red(MESSAGES.STATUS_CHANGE_CONFLICT);
2887
+ return chalk8.red(MESSAGES.STATUS_CHANGE_CONFLICT);
2744
2888
  case "clean":
2745
- return chalk7.gray(MESSAGES.STATUS_CHANGE_CLEAN);
2889
+ return chalk8.gray(MESSAGES.STATUS_CHANGE_CLEAN);
2746
2890
  }
2747
2891
  }
2748
2892
  function printSnapshotsSection(snapshots) {
2749
- printInfo(` ${chalk7.bold("\u25C6")} ${chalk7.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.length} \u4E2A)`);
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 ? "" : ` ${chalk7.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED)}`;
2758
- const icon = snap.worktreeExists ? chalk7.blue("\u25CF") : chalk7.yellow("\u26A0");
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 chalk8 from "chalk";
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(` ${chalk8.bold(alias)} \u2192 ${chalk8.cyan(command)}`);
2930
+ printInfo(` ${chalk9.bold(alias)} \u2192 ${chalk9.cyan(command)}`);
2787
2931
  }
2788
2932
  printInfo("");
2789
2933
  printSeparator();
@@ -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