clawt 3.5.0 → 3.5.2

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/dist/index.js CHANGED
@@ -173,7 +173,22 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
173
173
  /** merge 交互选择提示 */
174
174
  MERGE_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u5408\u5E76\u7684\u5206\u652F",
175
175
  /** merge 模糊匹配到多个结果提示 */
176
- MERGE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`
176
+ MERGE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
177
+ /** 询问是否使用 AI 辅助解决冲突 */
178
+ MERGE_CONFLICT_ASK_AI: "\u68C0\u6D4B\u5230\u5408\u5E76\u51B2\u7A81\uFF0C\u662F\u5426\u4F7F\u7528 Claude Code \u81EA\u52A8\u89E3\u51B3\uFF1F",
179
+ /** AI 冲突解决开始 */
180
+ MERGE_CONFLICT_AI_START: (fileCount) => `\u6B63\u5728\u4F7F\u7528 Claude Code \u5206\u6790\u5E76\u89E3\u51B3 ${fileCount} \u4E2A\u51B2\u7A81\u6587\u4EF6...`,
181
+ /** AI 冲突解决成功 */
182
+ MERGE_CONFLICT_AI_SUCCESS: "\u2713 Claude Code \u5DF2\u6210\u529F\u89E3\u51B3\u6240\u6709\u51B2\u7A81",
183
+ /** AI 冲突解决后仍有未解决的冲突 */
184
+ MERGE_CONFLICT_AI_PARTIAL: (remaining) => `Claude Code \u5DF2\u5904\u7406\u51B2\u7A81\u6587\u4EF6\uFF0C\u4F46\u4ECD\u6709 ${remaining} \u4E2A\u6587\u4EF6\u5B58\u5728\u51B2\u7A81
185
+ \u8BF7\u624B\u52A8\u5904\u7406\u5269\u4F59\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue`,
186
+ /** AI 冲突解决失败 */
187
+ MERGE_CONFLICT_AI_FAILED: (errorMsg) => `Claude Code \u89E3\u51B3\u51B2\u7A81\u5931\u8D25: ${errorMsg}
188
+ \u8BF7\u624B\u52A8\u5904\u7406\uFF1A
189
+ \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue`,
190
+ /** --auto 模式下的冲突手动解决(配置为 manual) */
191
+ MERGE_CONFLICT_MANUAL: "\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u5904\u7406\uFF1A\n \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue"
177
192
  };
178
193
 
179
194
  // src/constants/messages/validate.ts
@@ -235,7 +250,19 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
235
250
  VALIDATE_BRANCH_NOT_FOUND: (validateBranch, branch) => `\u9A8C\u8BC1\u5206\u652F ${validateBranch} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C clawt create \u6216 clawt run \u521B\u5EFA\u5206\u652F ${branch}`,
236
251
  /** validate 成功(含验证分支信息) */
237
252
  VALIDATE_SUCCESS_WITH_BRANCH: (branch, validateBranch) => `\u2713 \u5DF2\u5207\u6362\u5230\u9A8C\u8BC1\u5206\u652F ${validateBranch} \u5E76\u5E94\u7528\u5206\u652F ${branch} \u7684\u53D8\u66F4
238
- \u53EF\u4EE5\u5F00\u59CB\u9A8C\u8BC1\u4E86`
253
+ \u53EF\u4EE5\u5F00\u59CB\u9A8C\u8BC1\u4E86`,
254
+ /** 错误信息已复制到剪贴板提示 */
255
+ VALIDATE_RUN_ERROR_COPIED: "\u2702 \u9519\u8BEF\u4FE1\u606F\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F",
256
+ /** 剪贴板复制失败提示 */
257
+ VALIDATE_RUN_ERROR_COPY_FAILED: "\u26A0 \u9519\u8BEF\u4FE1\u606F\u590D\u5236\u5230\u526A\u8D34\u677F\u5931\u8D25",
258
+ /** 单命令(含 && 链)剪贴板错误格式 */
259
+ VALIDATE_CLIPBOARD_SINGLE_ERROR: (command, stderr) => `${command} \u6307\u4EE4\u6267\u884C\u51FA\u9519\uFF0C\u9519\u8BEF\u4FE1\u606F\uFF1A
260
+ ${stderr}`,
261
+ /** 并行命令中单个命令的剪贴板错误格式 */
262
+ VALIDATE_CLIPBOARD_PARALLEL_ERROR: (command, stderr) => `${command} \u6307\u4EE4\u6267\u884C\u51FA\u9519\uFF0C\u9519\u8BEF\u4FE1\u606F\uFF1A
263
+ ${stderr}`,
264
+ /** 多个错误之间的分隔符 */
265
+ VALIDATE_CLIPBOARD_SEPARATOR: "\n\n---\n\n"
239
266
  };
240
267
 
241
268
  // src/constants/messages/sync.ts
@@ -667,6 +694,15 @@ var CONFIG_DEFINITIONS = {
667
694
  autoUpdate: {
668
695
  defaultValue: true,
669
696
  description: "\u662F\u5426\u542F\u7528\u81EA\u52A8\u66F4\u65B0\u68C0\u67E5\uFF08\u6BCF 24 \u5C0F\u65F6\u68C0\u67E5\u4E00\u6B21 npm registry\uFF09"
697
+ },
698
+ conflictResolveMode: {
699
+ defaultValue: "ask",
700
+ description: "merge \u51B2\u7A81\u65F6\u7684\u89E3\u51B3\u6A21\u5F0F\uFF1Aask\uFF08\u8BE2\u95EE\u662F\u5426\u4F7F\u7528 AI\uFF09\u3001auto\uFF08\u81EA\u52A8 AI \u89E3\u51B3\uFF09\u3001manual\uFF08\u624B\u52A8\u89E3\u51B3\uFF09",
701
+ allowedValues: ["ask", "auto", "manual"]
702
+ },
703
+ conflictResolveTimeoutMs: {
704
+ defaultValue: 3e5,
705
+ description: "Claude Code \u51B2\u7A81\u89E3\u51B3\u8D85\u65F6\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09\uFF0C\u9ED8\u8BA4 300000\uFF085 \u5206\u949F\uFF09"
670
706
  }
671
707
  };
672
708
  function deriveDefaultConfig(definitions) {
@@ -768,6 +804,21 @@ var GROUP_SEPARATOR_LABEL = (dateLabel, relativeTime) => `\u2550\u2550\u2550\u25
768
804
  var UNKNOWN_DATE_GROUP = "\u672A\u77E5\u65E5\u671F";
769
805
  var UNKNOWN_DATE_SEPARATOR_LABEL = `\u2550\u2550\u2550\u2550 ${chalk2.bold.hex("#FF8C00")("\u672A\u77E5\u65E5\u671F")} \u2550\u2550\u2550\u2550`;
770
806
 
807
+ // src/constants/ai-prompts.ts
808
+ var CONFLICT_RESOLVE_PROMPT = `\u4F60\u662F\u4E00\u4E2A Git \u5408\u5E76\u51B2\u7A81\u89E3\u51B3\u4E13\u5BB6\u3002\u5F53\u524D\u4ED3\u5E93\u5904\u4E8E\u5408\u5E76\u51B2\u7A81\u72B6\u6001\u3002
809
+
810
+ ## \u4EFB\u52A1
811
+
812
+ 1. \u901A\u8FC7 git status \u548C git diff \u7B49\u547D\u4EE4\uFF0C\u81EA\u884C\u67E5\u770B\u5F53\u524D\u4ED3\u5E93\u7684\u51B2\u7A81\u6587\u4EF6\u5217\u8868\u53CA\u51B2\u7A81\u5185\u5BB9
813
+ 2. \u901A\u8FC7 git log \u7B49\u547D\u4EE4\uFF0C\u5206\u6790\u4E24\u4E2A\u5206\u652F\u5404\u81EA\u7684\u53D8\u66F4\u610F\u56FE
814
+ 3. \u76F4\u63A5\u7F16\u8F91\u6BCF\u4E2A\u51B2\u7A81\u6587\u4EF6\uFF0C\u79FB\u9664\u6240\u6709\u51B2\u7A81\u6807\u8BB0\uFF08<<<<<<<\u3001=======\u3001>>>>>>>\uFF09
815
+ 4. \u4FDD\u7559\u53CC\u65B9\u6709\u610F\u4E49\u7684\u53D8\u66F4\uFF0C\u5408\u7406\u5408\u5E76\u4EE3\u7801\u903B\u8F91
816
+ 5. \u5982\u679C\u4E24\u4E2A\u5206\u652F\u4FEE\u6539\u4E86\u540C\u4E00\u6BB5\u4EE3\u7801\u4F46\u610F\u56FE\u4E0D\u540C\uFF0C\u4F18\u5148\u4FDD\u8BC1\u4EE3\u7801\u7684\u6B63\u786E\u6027\u548C\u5B8C\u6574\u6027
817
+ 6. \u89E3\u51B3\u51B2\u7A81\u540E\uFF0C\u786E\u4FDD\u4EE3\u7801\u8BED\u6CD5\u6B63\u786E\u3001\u903B\u8F91\u5B8C\u6574
818
+ 7. \u4E0D\u8981\u6DFB\u52A0\u4EFB\u4F55\u6CE8\u91CA\u8BF4\u660E\u4F60\u505A\u4E86\u4EC0\u4E48\u4FEE\u6539\uFF0C\u53EA\u9700\u8981\u4FEE\u6539\u6587\u4EF6\u5185\u5BB9
819
+
820
+ \u8BF7\u76F4\u63A5\u5F00\u59CB\u3002`;
821
+
771
822
  // src/constants/pre-checks.ts
772
823
  var PRE_CHECK_CREATE = {
773
824
  requireMainWorktree: true,
@@ -940,29 +991,86 @@ function parseParallelCommands(commandString) {
940
991
  const parts = escaped.split("&");
941
992
  return parts.map((part) => part.replace(new RegExp(placeholder, "g"), "&&").trim()).filter((part) => part.length > 0);
942
993
  }
943
- function runParallelCommands(commands, options) {
944
- const promises = commands.map((command) => {
945
- return new Promise((resolve4) => {
946
- logger.debug(`\u5E76\u884C\u542F\u52A8\u547D\u4EE4: ${command}${options?.cwd ? ` (cwd: ${options.cwd})` : ""}`);
947
- const child = spawn(command, {
948
- cwd: options?.cwd,
949
- stdio: "inherit",
950
- shell: true
951
- });
952
- child.on("error", (err) => {
953
- resolve4({ command, exitCode: 1, error: err.message });
994
+ function spawnWithStderrCapture(command, options) {
995
+ return new Promise((resolve4) => {
996
+ const child = spawn(command, {
997
+ cwd: options?.cwd,
998
+ stdio: ["inherit", "inherit", "pipe"],
999
+ shell: true
1000
+ });
1001
+ const stderrChunks = [];
1002
+ child.stderr?.on("data", (chunk) => {
1003
+ process.stderr.write(chunk);
1004
+ stderrChunks.push(chunk);
1005
+ });
1006
+ child.on("error", (err) => {
1007
+ resolve4({
1008
+ exitCode: 1,
1009
+ error: err.message,
1010
+ stderr: Buffer.concat(stderrChunks).toString("utf-8")
954
1011
  });
955
- child.on("close", (code) => {
956
- resolve4({ command, exitCode: code ?? 1 });
1012
+ });
1013
+ child.on("close", (code) => {
1014
+ resolve4({
1015
+ exitCode: code ?? 1,
1016
+ stderr: Buffer.concat(stderrChunks).toString("utf-8")
957
1017
  });
958
1018
  });
959
1019
  });
1020
+ }
1021
+ function runCommandWithStderrCapture(command, options) {
1022
+ logger.debug(`\u6267\u884C\u547D\u4EE4(stderr\u6355\u83B7): ${command}${options?.cwd ? ` (cwd: ${options.cwd})` : ""}`);
1023
+ return spawnWithStderrCapture(command, options);
1024
+ }
1025
+ function runParallelCommandsWithStderrCapture(commands, options) {
1026
+ const promises = commands.map(async (command) => {
1027
+ logger.debug(`\u5E76\u884C\u542F\u52A8\u547D\u4EE4(stderr\u6355\u83B7): ${command}${options?.cwd ? ` (cwd: ${options.cwd})` : ""}`);
1028
+ const result = await spawnWithStderrCapture(command, options);
1029
+ return { command, ...result };
1030
+ });
960
1031
  return Promise.all(promises);
961
1032
  }
962
1033
 
1034
+ // src/utils/clipboard.ts
1035
+ import { spawnSync as spawnSync2 } from "child_process";
1036
+ function getClipboardCommand() {
1037
+ switch (process.platform) {
1038
+ case "darwin":
1039
+ return { command: "pbcopy", args: [] };
1040
+ case "linux":
1041
+ return { command: "xclip", args: ["-selection", "clipboard"] };
1042
+ case "win32":
1043
+ return { command: "clip", args: [] };
1044
+ default:
1045
+ return null;
1046
+ }
1047
+ }
1048
+ function copyToClipboard(text) {
1049
+ try {
1050
+ const clipboardCmd = getClipboardCommand();
1051
+ if (!clipboardCmd) {
1052
+ logger.debug(`\u4E0D\u652F\u6301\u7684\u5E73\u53F0: ${process.platform}\uFF0C\u8DF3\u8FC7\u526A\u8D34\u677F\u590D\u5236`);
1053
+ return false;
1054
+ }
1055
+ const result = spawnSync2(clipboardCmd.command, clipboardCmd.args, {
1056
+ input: text,
1057
+ encoding: "utf-8",
1058
+ stdio: ["pipe", "pipe", "pipe"]
1059
+ });
1060
+ if (result.status !== 0) {
1061
+ logger.debug(`\u526A\u8D34\u677F\u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF0C\u9000\u51FA\u7801: ${result.status}`);
1062
+ return false;
1063
+ }
1064
+ return true;
1065
+ } catch (error) {
1066
+ logger.debug(`\u526A\u8D34\u677F\u590D\u5236\u5F02\u5E38: ${error.message}`);
1067
+ return false;
1068
+ }
1069
+ }
1070
+
963
1071
  // src/utils/git-core.ts
964
1072
  import { basename } from "path";
965
- import { execSync as execSync2 } from "child_process";
1073
+ import { execSync as execSync2, execFileSync as execFileSync2 } from "child_process";
966
1074
  function getGitCommonDir(cwd) {
967
1075
  return execCommand("git rev-parse --git-common-dir", { cwd });
968
1076
  }
@@ -1085,6 +1193,24 @@ function gitApplyCachedCheck(patchContent, cwd) {
1085
1193
  return false;
1086
1194
  }
1087
1195
  }
1196
+ function getConflictFiles(cwd) {
1197
+ const status = getStatusPorcelain(cwd);
1198
+ if (!status) return [];
1199
+ return status.split("\n").filter((line) => /^(UU|AA|DD|DU|UD|AU|UA)/.test(line)).map((line) => line.slice(3));
1200
+ }
1201
+ function gitAddFiles(files, cwd) {
1202
+ if (files.length === 0) return;
1203
+ const args = ["add", "--", ...files];
1204
+ logger.debug(`\u6267\u884C\u547D\u4EE4: git ${args.join(" ")}${cwd ? ` (cwd: ${cwd})` : ""}`);
1205
+ execFileSync2("git", args, {
1206
+ cwd,
1207
+ encoding: "utf-8",
1208
+ stdio: ["pipe", "pipe", "pipe"]
1209
+ });
1210
+ }
1211
+ function gitMergeContinue(cwd) {
1212
+ execCommand("GIT_EDITOR=true git merge --continue", { cwd });
1213
+ }
1088
1214
 
1089
1215
  // src/utils/git-branch.ts
1090
1216
  function checkBranchExists(branchName, cwd) {
@@ -1700,12 +1826,12 @@ function parseConcurrency(optionValue, configValue) {
1700
1826
  import Enquirer2 from "enquirer";
1701
1827
 
1702
1828
  // src/utils/claude.ts
1703
- import { spawnSync as spawnSync2 } from "child_process";
1829
+ import { spawnSync as spawnSync3 } from "child_process";
1704
1830
  import { existsSync as existsSync7, readdirSync as readdirSync3 } from "fs";
1705
1831
  import { join as join5 } from "path";
1706
1832
 
1707
1833
  // src/utils/terminal.ts
1708
- import { execFileSync as execFileSync2 } from "child_process";
1834
+ import { execFileSync as execFileSync3 } from "child_process";
1709
1835
  import { existsSync as existsSync6 } from "fs";
1710
1836
  function isITerm2Installed() {
1711
1837
  return existsSync6(ITERM2_APP_PATH);
@@ -1764,7 +1890,7 @@ function openCommandInNewTerminalTab(command, tabTitle) {
1764
1890
  logger.debug(`\u6253\u5F00\u7EC8\u7AEF Tab [${terminalApp}]: ${tabTitle}`);
1765
1891
  logger.debug(`\u6267\u884C\u547D\u4EE4: ${command}`);
1766
1892
  try {
1767
- execFileSync2("osascript", ["-e", script], {
1893
+ execFileSync3("osascript", ["-e", script], {
1768
1894
  encoding: "utf-8",
1769
1895
  stdio: ["pipe", "pipe", "pipe"]
1770
1896
  });
@@ -1809,7 +1935,7 @@ function launchInteractiveClaude(worktree, options = {}) {
1809
1935
  printInfo(` \u6A21\u5F0F: ${hasPreviousSession ? "\u7EE7\u7EED\u4E0A\u6B21\u5BF9\u8BDD" : "\u65B0\u5BF9\u8BDD"}`);
1810
1936
  }
1811
1937
  printInfo("");
1812
- const result = spawnSync2(cmd, args, {
1938
+ const result = spawnSync3(cmd, args, {
1813
1939
  cwd: worktree.path,
1814
1940
  stdio: "inherit"
1815
1941
  });
@@ -3170,39 +3296,66 @@ async function checkForUpdates(currentVersion) {
3170
3296
  }
3171
3297
 
3172
3298
  // src/utils/validate-runner.ts
3173
- function executeSingleCommand(command, mainWorktreePath) {
3299
+ function handleErrorClipboard(clipboardContent) {
3300
+ const success = copyToClipboard(clipboardContent);
3301
+ if (success) {
3302
+ printInfo(MESSAGES.VALIDATE_RUN_ERROR_COPIED);
3303
+ } else {
3304
+ printWarning(MESSAGES.VALIDATE_RUN_ERROR_COPY_FAILED);
3305
+ }
3306
+ }
3307
+ function buildSingleErrorClipboard(command, stderr, exitCode) {
3308
+ if (stderr.trim()) {
3309
+ return MESSAGES.VALIDATE_CLIPBOARD_SINGLE_ERROR(command, stderr.trim());
3310
+ }
3311
+ return `${command} \u6307\u4EE4\u6267\u884C\u51FA\u9519\uFF0C\u9000\u51FA\u7801: ${exitCode}`;
3312
+ }
3313
+ async function executeSingleCommand(command, mainWorktreePath) {
3174
3314
  printInfo(MESSAGES.VALIDATE_RUN_START(command));
3175
3315
  printSeparator();
3176
- const result = runCommandInherited(command, { cwd: mainWorktreePath });
3316
+ const result = await runCommandWithStderrCapture(command, { cwd: mainWorktreePath });
3177
3317
  printSeparator();
3178
3318
  if (result.error) {
3179
- printError(MESSAGES.VALIDATE_RUN_ERROR(command, result.error.message));
3319
+ printError(MESSAGES.VALIDATE_RUN_ERROR(command, result.error));
3320
+ const clipboardContent = MESSAGES.VALIDATE_CLIPBOARD_SINGLE_ERROR(command, result.error);
3321
+ handleErrorClipboard(clipboardContent);
3180
3322
  return;
3181
3323
  }
3182
- const exitCode = result.status ?? 1;
3183
- if (exitCode === 0) {
3324
+ if (result.exitCode === 0) {
3184
3325
  printSuccess(MESSAGES.VALIDATE_RUN_SUCCESS(command));
3185
3326
  } else {
3186
- printError(MESSAGES.VALIDATE_RUN_FAILED(command, exitCode));
3327
+ printError(MESSAGES.VALIDATE_RUN_FAILED(command, result.exitCode));
3328
+ const clipboardContent = buildSingleErrorClipboard(command, result.stderr, result.exitCode);
3329
+ handleErrorClipboard(clipboardContent);
3187
3330
  }
3188
3331
  }
3189
3332
  function reportParallelResults(results) {
3190
3333
  printSeparator();
3191
3334
  const successCount = results.filter((r) => r.exitCode === 0 && !r.error).length;
3192
3335
  const failedCount = results.length - successCount;
3336
+ const errorClipboardParts = [];
3193
3337
  for (const result of results) {
3194
3338
  if (result.error) {
3195
3339
  printError(MESSAGES.VALIDATE_PARALLEL_CMD_ERROR(result.command, result.error));
3340
+ errorClipboardParts.push(
3341
+ MESSAGES.VALIDATE_CLIPBOARD_PARALLEL_ERROR(result.command, result.error)
3342
+ );
3196
3343
  } else if (result.exitCode === 0) {
3197
3344
  printSuccess(MESSAGES.VALIDATE_PARALLEL_CMD_SUCCESS(result.command));
3198
3345
  } else {
3199
3346
  printError(MESSAGES.VALIDATE_PARALLEL_CMD_FAILED(result.command, result.exitCode));
3347
+ const errorContent = result.stderr.trim() ? result.stderr.trim() : `\u9000\u51FA\u7801: ${result.exitCode}`;
3348
+ errorClipboardParts.push(
3349
+ MESSAGES.VALIDATE_CLIPBOARD_PARALLEL_ERROR(result.command, errorContent)
3350
+ );
3200
3351
  }
3201
3352
  }
3202
3353
  if (failedCount === 0) {
3203
3354
  printSuccess(MESSAGES.VALIDATE_PARALLEL_RUN_ALL_SUCCESS(results.length));
3204
3355
  } else {
3205
3356
  printError(MESSAGES.VALIDATE_PARALLEL_RUN_SUMMARY(successCount, failedCount));
3357
+ const clipboardContent = errorClipboardParts.join(MESSAGES.VALIDATE_CLIPBOARD_SEPARATOR);
3358
+ handleErrorClipboard(clipboardContent);
3206
3359
  }
3207
3360
  }
3208
3361
  async function executeParallelCommands(commands, mainWorktreePath) {
@@ -3211,14 +3364,14 @@ async function executeParallelCommands(commands, mainWorktreePath) {
3211
3364
  printInfo(MESSAGES.VALIDATE_PARALLEL_CMD_START(i + 1, commands.length, commands[i]));
3212
3365
  }
3213
3366
  printSeparator();
3214
- const results = await runParallelCommands(commands, { cwd: mainWorktreePath });
3367
+ const results = await runParallelCommandsWithStderrCapture(commands, { cwd: mainWorktreePath });
3215
3368
  reportParallelResults(results);
3216
3369
  }
3217
3370
  async function executeRunCommand(command, mainWorktreePath) {
3218
3371
  printInfo("");
3219
3372
  const commands = parseParallelCommands(command);
3220
3373
  if (commands.length <= 1) {
3221
- executeSingleCommand(commands[0] || command, mainWorktreePath);
3374
+ await executeSingleCommand(commands[0] || command, mainWorktreePath);
3222
3375
  } else {
3223
3376
  await executeParallelCommands(commands, mainWorktreePath);
3224
3377
  }
@@ -3948,6 +4101,89 @@ var InteractivePanel = class {
3948
4101
  }
3949
4102
  };
3950
4103
 
4104
+ // src/utils/conflict-resolver.ts
4105
+ import { execFileSync as execFileSync4 } from "child_process";
4106
+ var DEFAULT_CONFLICT_RESOLVE_TIMEOUT_MS = 3e5;
4107
+ function buildConflictResolvePrompt() {
4108
+ return CONFLICT_RESOLVE_PROMPT;
4109
+ }
4110
+ function getConflictResolveTimeout() {
4111
+ const configValue = getConfigValue("conflictResolveTimeoutMs");
4112
+ if (typeof configValue === "number" && configValue > 0) {
4113
+ return configValue;
4114
+ }
4115
+ return DEFAULT_CONFLICT_RESOLVE_TIMEOUT_MS;
4116
+ }
4117
+ function invokeClaudeForConflictResolve(prompt, cwd) {
4118
+ const args = ["-p", prompt, "--permission-mode", "bypassPermissions"];
4119
+ logger.info(`\u8C03\u7528 Claude Code \u89E3\u51B3\u51B2\u7A81\uFF0C\u547D\u4EE4: claude -p "..." --permission-mode bypassPermissions`);
4120
+ try {
4121
+ const output = execFileSync4("claude", args, {
4122
+ cwd,
4123
+ encoding: "utf-8",
4124
+ stdio: ["pipe", "pipe", "pipe"],
4125
+ timeout: getConflictResolveTimeout()
4126
+ });
4127
+ return output;
4128
+ } catch (error) {
4129
+ const errMsg = error instanceof Error ? error.message : String(error);
4130
+ logger.error(`Claude Code \u51B2\u7A81\u89E3\u51B3\u5931\u8D25: ${errMsg}`);
4131
+ throw new ClawtError(MESSAGES.MERGE_CONFLICT_AI_FAILED(errMsg));
4132
+ }
4133
+ }
4134
+ function resolveConflictsWithAI(currentBranch, incomingBranch, cwd) {
4135
+ const conflictFiles = getConflictFiles(cwd);
4136
+ if (conflictFiles.length === 0) {
4137
+ return true;
4138
+ }
4139
+ printInfo(MESSAGES.MERGE_CONFLICT_AI_START(conflictFiles.length));
4140
+ const prompt = buildConflictResolvePrompt();
4141
+ try {
4142
+ invokeClaudeForConflictResolve(prompt, cwd);
4143
+ } catch (error) {
4144
+ const errMsg = error instanceof ClawtError ? error.message : String(error);
4145
+ printWarning(errMsg);
4146
+ return false;
4147
+ }
4148
+ const remainingConflicts = getConflictFiles(cwd);
4149
+ if (remainingConflicts.length === 0) {
4150
+ gitAddFiles(conflictFiles, cwd);
4151
+ gitMergeContinue(cwd);
4152
+ printSuccess(MESSAGES.MERGE_CONFLICT_AI_SUCCESS);
4153
+ return true;
4154
+ }
4155
+ const resolvedFiles = conflictFiles.filter((f) => !remainingConflicts.includes(f));
4156
+ if (resolvedFiles.length > 0) {
4157
+ gitAddFiles(resolvedFiles, cwd);
4158
+ }
4159
+ printWarning(MESSAGES.MERGE_CONFLICT_AI_PARTIAL(remainingConflicts.length));
4160
+ return false;
4161
+ }
4162
+ function determineConflictResolveMode(autoFlag) {
4163
+ if (autoFlag === true) {
4164
+ return "auto";
4165
+ }
4166
+ const configMode = getConfigValue("conflictResolveMode");
4167
+ if (configMode === "auto" || configMode === "manual") {
4168
+ return configMode;
4169
+ }
4170
+ return "ask";
4171
+ }
4172
+ async function handleMergeConflict(currentBranch, incomingBranch, cwd, autoFlag) {
4173
+ const mode = determineConflictResolveMode(autoFlag);
4174
+ if (mode === "manual") {
4175
+ throw new ClawtError(MESSAGES.MERGE_CONFLICT_MANUAL);
4176
+ }
4177
+ if (mode === "auto") {
4178
+ return resolveConflictsWithAI(currentBranch, incomingBranch, cwd);
4179
+ }
4180
+ const shouldUseAI = await confirmAction(MESSAGES.MERGE_CONFLICT_ASK_AI);
4181
+ if (!shouldUseAI) {
4182
+ throw new ClawtError(MESSAGES.MERGE_CONFLICT_MANUAL);
4183
+ }
4184
+ return resolveConflictsWithAI(currentBranch, incomingBranch, cwd);
4185
+ }
4186
+
3951
4187
  // src/commands/list.ts
3952
4188
  import chalk10 from "chalk";
3953
4189
  function registerListCommand(program2) {
@@ -4527,7 +4763,7 @@ async function handleCoverValidate() {
4527
4763
 
4528
4764
  // src/commands/merge.ts
4529
4765
  function registerMergeCommand(program2) {
4530
- program2.command("merge").description("\u5408\u5E76\u67D0\u4E2A\u5DF2\u9A8C\u8BC1\u7684 worktree \u5206\u652F\u5230\u4E3B worktree").option("-b, --branch <branchName>", "\u8981\u5408\u5E76\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\u4F9B\u9009\u62E9\uFF09").option("-m, --message <commitMessage>", "\u63D0\u4EA4\u4FE1\u606F\uFF08\u76EE\u6807 worktree \u5DE5\u4F5C\u533A\u6709\u4FEE\u6539\u65F6\u5FC5\u586B\uFF09").action(async (options) => {
4766
+ program2.command("merge").description("\u5408\u5E76\u67D0\u4E2A\u5DF2\u9A8C\u8BC1\u7684 worktree \u5206\u652F\u5230\u4E3B worktree").option("-b, --branch <branchName>", "\u8981\u5408\u5E76\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\u4F9B\u9009\u62E9\uFF09").option("-m, --message <commitMessage>", "\u63D0\u4EA4\u4FE1\u606F\uFF08\u76EE\u6807 worktree \u5DE5\u4F5C\u533A\u6709\u4FEE\u6539\u65F6\u5FC5\u586B\uFF09").option("--auto", "\u9047\u5230\u51B2\u7A81\u76F4\u63A5\u8C03\u7528 AI \u89E3\u51B3\uFF0C\u4E0D\u518D\u8BE2\u95EE").action(async (options) => {
4531
4767
  await handleMerge(options);
4532
4768
  });
4533
4769
  }
@@ -4599,16 +4835,25 @@ async function handleMerge(options) {
4599
4835
  throw new ClawtError(MESSAGES.TARGET_WORKTREE_NO_CHANGES);
4600
4836
  }
4601
4837
  }
4838
+ let mergeHadConflict = false;
4602
4839
  try {
4603
4840
  gitMerge(branch, mainWorktreePath);
4604
4841
  } catch (error) {
4605
4842
  if (hasMergeConflict(mainWorktreePath)) {
4606
- throw new ClawtError(MESSAGES.MERGE_CONFLICT);
4843
+ mergeHadConflict = true;
4844
+ } else {
4845
+ throw error;
4607
4846
  }
4608
- throw error;
4609
4847
  }
4610
- if (hasMergeConflict(mainWorktreePath)) {
4611
- throw new ClawtError(MESSAGES.MERGE_CONFLICT);
4848
+ if (!mergeHadConflict && hasMergeConflict(mainWorktreePath)) {
4849
+ mergeHadConflict = true;
4850
+ }
4851
+ if (mergeHadConflict) {
4852
+ const currentBranch = getCurrentBranch(mainWorktreePath);
4853
+ const resolved = await handleMergeConflict(currentBranch, branch, mainWorktreePath, options.auto);
4854
+ if (!resolved) {
4855
+ return;
4856
+ }
4612
4857
  }
4613
4858
  const autoPullPush = getConfigValue("autoPullPush");
4614
4859
  if (autoPullPush) {
@@ -164,7 +164,22 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
164
164
  /** merge 交互选择提示 */
165
165
  MERGE_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u5408\u5E76\u7684\u5206\u652F",
166
166
  /** merge 模糊匹配到多个结果提示 */
167
- MERGE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`
167
+ MERGE_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
168
+ /** 询问是否使用 AI 辅助解决冲突 */
169
+ MERGE_CONFLICT_ASK_AI: "\u68C0\u6D4B\u5230\u5408\u5E76\u51B2\u7A81\uFF0C\u662F\u5426\u4F7F\u7528 Claude Code \u81EA\u52A8\u89E3\u51B3\uFF1F",
170
+ /** AI 冲突解决开始 */
171
+ MERGE_CONFLICT_AI_START: (fileCount) => `\u6B63\u5728\u4F7F\u7528 Claude Code \u5206\u6790\u5E76\u89E3\u51B3 ${fileCount} \u4E2A\u51B2\u7A81\u6587\u4EF6...`,
172
+ /** AI 冲突解决成功 */
173
+ MERGE_CONFLICT_AI_SUCCESS: "\u2713 Claude Code \u5DF2\u6210\u529F\u89E3\u51B3\u6240\u6709\u51B2\u7A81",
174
+ /** AI 冲突解决后仍有未解决的冲突 */
175
+ MERGE_CONFLICT_AI_PARTIAL: (remaining) => `Claude Code \u5DF2\u5904\u7406\u51B2\u7A81\u6587\u4EF6\uFF0C\u4F46\u4ECD\u6709 ${remaining} \u4E2A\u6587\u4EF6\u5B58\u5728\u51B2\u7A81
176
+ \u8BF7\u624B\u52A8\u5904\u7406\u5269\u4F59\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue`,
177
+ /** AI 冲突解决失败 */
178
+ MERGE_CONFLICT_AI_FAILED: (errorMsg) => `Claude Code \u89E3\u51B3\u51B2\u7A81\u5931\u8D25: ${errorMsg}
179
+ \u8BF7\u624B\u52A8\u5904\u7406\uFF1A
180
+ \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue`,
181
+ /** --auto 模式下的冲突手动解决(配置为 manual) */
182
+ MERGE_CONFLICT_MANUAL: "\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u5904\u7406\uFF1A\n \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue"
168
183
  };
169
184
 
170
185
  // src/constants/messages/validate.ts
@@ -226,7 +241,19 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
226
241
  VALIDATE_BRANCH_NOT_FOUND: (validateBranch, branch) => `\u9A8C\u8BC1\u5206\u652F ${validateBranch} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C clawt create \u6216 clawt run \u521B\u5EFA\u5206\u652F ${branch}`,
227
242
  /** validate 成功(含验证分支信息) */
228
243
  VALIDATE_SUCCESS_WITH_BRANCH: (branch, validateBranch) => `\u2713 \u5DF2\u5207\u6362\u5230\u9A8C\u8BC1\u5206\u652F ${validateBranch} \u5E76\u5E94\u7528\u5206\u652F ${branch} \u7684\u53D8\u66F4
229
- \u53EF\u4EE5\u5F00\u59CB\u9A8C\u8BC1\u4E86`
244
+ \u53EF\u4EE5\u5F00\u59CB\u9A8C\u8BC1\u4E86`,
245
+ /** 错误信息已复制到剪贴板提示 */
246
+ VALIDATE_RUN_ERROR_COPIED: "\u2702 \u9519\u8BEF\u4FE1\u606F\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F",
247
+ /** 剪贴板复制失败提示 */
248
+ VALIDATE_RUN_ERROR_COPY_FAILED: "\u26A0 \u9519\u8BEF\u4FE1\u606F\u590D\u5236\u5230\u526A\u8D34\u677F\u5931\u8D25",
249
+ /** 单命令(含 && 链)剪贴板错误格式 */
250
+ VALIDATE_CLIPBOARD_SINGLE_ERROR: (command, stderr) => `${command} \u6307\u4EE4\u6267\u884C\u51FA\u9519\uFF0C\u9519\u8BEF\u4FE1\u606F\uFF1A
251
+ ${stderr}`,
252
+ /** 并行命令中单个命令的剪贴板错误格式 */
253
+ VALIDATE_CLIPBOARD_PARALLEL_ERROR: (command, stderr) => `${command} \u6307\u4EE4\u6267\u884C\u51FA\u9519\uFF0C\u9519\u8BEF\u4FE1\u606F\uFF1A
254
+ ${stderr}`,
255
+ /** 多个错误之间的分隔符 */
256
+ VALIDATE_CLIPBOARD_SEPARATOR: "\n\n---\n\n"
230
257
  };
231
258
 
232
259
  // src/constants/messages/sync.ts
@@ -603,6 +630,15 @@ var CONFIG_DEFINITIONS = {
603
630
  autoUpdate: {
604
631
  defaultValue: true,
605
632
  description: "\u662F\u5426\u542F\u7528\u81EA\u52A8\u66F4\u65B0\u68C0\u67E5\uFF08\u6BCF 24 \u5C0F\u65F6\u68C0\u67E5\u4E00\u6B21 npm registry\uFF09"
633
+ },
634
+ conflictResolveMode: {
635
+ defaultValue: "ask",
636
+ description: "merge \u51B2\u7A81\u65F6\u7684\u89E3\u51B3\u6A21\u5F0F\uFF1Aask\uFF08\u8BE2\u95EE\u662F\u5426\u4F7F\u7528 AI\uFF09\u3001auto\uFF08\u81EA\u52A8 AI \u89E3\u51B3\uFF09\u3001manual\uFF08\u624B\u52A8\u89E3\u51B3\uFF09",
637
+ allowedValues: ["ask", "auto", "manual"]
638
+ },
639
+ conflictResolveTimeoutMs: {
640
+ defaultValue: 3e5,
641
+ description: "Claude Code \u51B2\u7A81\u89E3\u51B3\u8D85\u65F6\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09\uFF0C\u9ED8\u8BA4 300000\uFF085 \u5206\u949F\uFF09"
606
642
  }
607
643
  };
608
644
  function deriveDefaultConfig(definitions) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "3.5.0",
3
+ "version": "3.5.2",
4
4
  "description": "本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -33,6 +33,8 @@ import {
33
33
  gitCheckout,
34
34
  resolveTargetWorktree,
35
35
  getMainWorkBranch,
36
+ getCurrentBranch,
37
+ handleMergeConflict,
36
38
  } from '../utils/index.js';
37
39
  import type { WorktreeResolveMessages } from '../utils/index.js';
38
40
 
@@ -46,6 +48,7 @@ export function registerMergeCommand(program: Command): void {
46
48
  .description('合并某个已验证的 worktree 分支到主 worktree')
47
49
  .option('-b, --branch <branchName>', '要合并的分支名(支持模糊匹配,不传则列出所有分支供选择)')
48
50
  .option('-m, --message <commitMessage>', '提交信息(目标 worktree 工作区有修改时必填)')
51
+ .option('--auto', '遇到冲突直接调用 AI 解决,不再询问')
49
52
  .action(async (options: MergeOptions) => {
50
53
  await handleMerge(options);
51
54
  });
@@ -182,19 +185,31 @@ async function handleMerge(options: MergeOptions): Promise<void> {
182
185
  }
183
186
 
184
187
  // 步骤 5:回到主 worktree 进行合并
188
+ let mergeHadConflict = false;
185
189
  try {
186
190
  gitMerge(branch, mainWorktreePath);
187
191
  } catch (error) {
188
192
  // 检查是否有冲突
189
193
  if (hasMergeConflict(mainWorktreePath)) {
190
- throw new ClawtError(MESSAGES.MERGE_CONFLICT);
194
+ mergeHadConflict = true;
195
+ } else {
196
+ throw error;
191
197
  }
192
- throw error;
193
198
  }
194
199
 
195
- // 步骤 6:冲突检测(二次确认)
196
- if (hasMergeConflict(mainWorktreePath)) {
197
- throw new ClawtError(MESSAGES.MERGE_CONFLICT);
200
+ // 步骤 5.5:冲突检测(二次确认)
201
+ if (!mergeHadConflict && hasMergeConflict(mainWorktreePath)) {
202
+ mergeHadConflict = true;
203
+ }
204
+
205
+ // 步骤 5.6:如果有冲突,尝试 AI 辅助解决
206
+ if (mergeHadConflict) {
207
+ const currentBranch = getCurrentBranch(mainWorktreePath);
208
+ const resolved = await handleMergeConflict(currentBranch, branch, mainWorktreePath, options.auto);
209
+ if (!resolved) {
210
+ // AI 未能完全解决冲突,流程中止(handleMergeConflict 内部已输出提示)
211
+ return;
212
+ }
198
213
  }
199
214
 
200
215
  // 步骤 7:根据配置决定是否自动 pull 和 push
@@ -0,0 +1,14 @@
1
+ /** Claude Code 冲突解决指令性 prompt */
2
+ export const CONFLICT_RESOLVE_PROMPT = `你是一个 Git 合并冲突解决专家。当前仓库处于合并冲突状态。
3
+
4
+ ## 任务
5
+
6
+ 1. 通过 git status 和 git diff 等命令,自行查看当前仓库的冲突文件列表及冲突内容
7
+ 2. 通过 git log 等命令,分析两个分支各自的变更意图
8
+ 3. 直接编辑每个冲突文件,移除所有冲突标记(<<<<<<<、=======、>>>>>>>)
9
+ 4. 保留双方有意义的变更,合理合并代码逻辑
10
+ 5. 如果两个分支修改了同一段代码但意图不同,优先保证代码的正确性和完整性
11
+ 6. 解决冲突后,确保代码语法正确、逻辑完整
12
+ 7. 不要添加任何注释说明你做了什么修改,只需要修改文件内容
13
+
14
+ 请直接开始。`;
@@ -47,6 +47,15 @@ export const CONFIG_DEFINITIONS: ConfigDefinitions = {
47
47
  defaultValue: true,
48
48
  description: '是否启用自动更新检查(每 24 小时检查一次 npm registry)',
49
49
  },
50
+ conflictResolveMode: {
51
+ defaultValue: 'ask',
52
+ description: 'merge 冲突时的解决模式:ask(询问是否使用 AI)、auto(自动 AI 解决)、manual(手动解决)',
53
+ allowedValues: ['ask', 'auto', 'manual'] as const,
54
+ },
55
+ conflictResolveTimeoutMs: {
56
+ defaultValue: 300000,
57
+ description: 'Claude Code 冲突解决超时时间(毫秒),默认 300000(5 分钟)',
58
+ },
50
59
  };
51
60
 
52
61
  /**
@@ -31,6 +31,7 @@ export {
31
31
  CURSOR_HOME,
32
32
  } from './progress.js';
33
33
  export { SELECT_ALL_NAME, SELECT_ALL_LABEL, GROUP_SELECT_ALL_PREFIX, GROUP_SELECT_ALL_LABEL, GROUP_SEPARATOR_LABEL, UNKNOWN_DATE_GROUP, UNKNOWN_DATE_SEPARATOR_LABEL } from './prompt.js';
34
+ export { CONFLICT_RESOLVE_PROMPT } from './ai-prompts.js';
34
35
  export {
35
36
  PANEL_REFRESH_INTERVAL_MS,
36
37
  PANEL_COUNTDOWN_INTERVAL_MS,
@@ -40,4 +40,19 @@ export const MERGE_MESSAGES = {
40
40
  MERGE_SELECT_BRANCH: '请选择要合并的分支',
41
41
  /** merge 模糊匹配到多个结果提示 */
42
42
  MERGE_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
43
+ /** 询问是否使用 AI 辅助解决冲突 */
44
+ MERGE_CONFLICT_ASK_AI: '检测到合并冲突,是否使用 Claude Code 自动解决?',
45
+ /** AI 冲突解决开始 */
46
+ MERGE_CONFLICT_AI_START: (fileCount: number) =>
47
+ `正在使用 Claude Code 分析并解决 ${fileCount} 个冲突文件...`,
48
+ /** AI 冲突解决成功 */
49
+ MERGE_CONFLICT_AI_SUCCESS: '✓ Claude Code 已成功解决所有冲突',
50
+ /** AI 冲突解决后仍有未解决的冲突 */
51
+ MERGE_CONFLICT_AI_PARTIAL: (remaining: number) =>
52
+ `Claude Code 已处理冲突文件,但仍有 ${remaining} 个文件存在冲突\n 请手动处理剩余冲突后执行 git add . && git merge --continue`,
53
+ /** AI 冲突解决失败 */
54
+ MERGE_CONFLICT_AI_FAILED: (errorMsg: string) =>
55
+ `Claude Code 解决冲突失败: ${errorMsg}\n 请手动处理:\n 解决冲突后执行 git add . && git merge --continue`,
56
+ /** --auto 模式下的冲突手动解决(配置为 manual) */
57
+ MERGE_CONFLICT_MANUAL: '合并存在冲突,请手动处理:\n 解决冲突后执行 git add . && git merge --continue',
43
58
  } as const;
@@ -71,4 +71,16 @@ export const VALIDATE_MESSAGES = {
71
71
  /** validate 成功(含验证分支信息) */
72
72
  VALIDATE_SUCCESS_WITH_BRANCH: (branch: string, validateBranch: string) =>
73
73
  `✓ 已切换到验证分支 ${validateBranch} 并应用分支 ${branch} 的变更\n 可以开始验证了`,
74
+ /** 错误信息已复制到剪贴板提示 */
75
+ VALIDATE_RUN_ERROR_COPIED: '✂ 错误信息已复制到剪贴板',
76
+ /** 剪贴板复制失败提示 */
77
+ VALIDATE_RUN_ERROR_COPY_FAILED: '⚠ 错误信息复制到剪贴板失败',
78
+ /** 单命令(含 && 链)剪贴板错误格式 */
79
+ VALIDATE_CLIPBOARD_SINGLE_ERROR: (command: string, stderr: string) =>
80
+ `${command} 指令执行出错,错误信息:\n${stderr}`,
81
+ /** 并行命令中单个命令的剪贴板错误格式 */
82
+ VALIDATE_CLIPBOARD_PARALLEL_ERROR: (command: string, stderr: string) =>
83
+ `${command} 指令执行出错,错误信息:\n${stderr}`,
84
+ /** 多个错误之间的分隔符 */
85
+ VALIDATE_CLIPBOARD_SEPARATOR: '\n\n---\n\n',
74
86
  } as const;