clawt 2.16.3 → 2.16.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agent-memory/docs-sync-updater/MEMORY.md +13 -1
- package/README.md +4 -0
- package/dist/index.js +379 -119
- package/dist/postinstall.js +12 -3
- package/docs/spec.md +97 -12
- package/package.json +2 -1
- package/src/commands/sync.ts +34 -14
- package/src/commands/validate.ts +50 -8
- package/src/constants/index.ts +12 -2
- package/src/constants/messages/run.ts +4 -4
- package/src/constants/messages/validate.ts +12 -0
- package/src/constants/progress.ts +36 -6
- package/src/utils/index.ts +2 -0
- package/src/utils/progress-render.ts +77 -13
- package/src/utils/progress.ts +110 -24
- package/src/utils/stream-parser.ts +251 -0
- package/src/utils/task-executor.ts +61 -27
- package/tests/unit/utils/progress-render.test.ts +96 -0
- package/tests/unit/utils/progress.test.ts +391 -10
- package/tests/unit/utils/stream-parser.test.ts +375 -0
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ var COMMON_MESSAGES = {
|
|
|
36
36
|
/** worktree 创建成功 */
|
|
37
37
|
WORKTREE_CREATED: (count) => `\u2713 \u5DF2\u521B\u5EFA ${count} \u4E2A worktree`,
|
|
38
38
|
/** worktree 移除成功 */
|
|
39
|
-
WORKTREE_REMOVED: (
|
|
39
|
+
WORKTREE_REMOVED: (path2) => `\u2713 \u5DF2\u79FB\u9664 worktree: ${path2}`,
|
|
40
40
|
/** 没有 worktree */
|
|
41
41
|
NO_WORKTREES: "(\u65E0 worktree)",
|
|
42
42
|
/** 目标 worktree 不存在 */
|
|
@@ -74,17 +74,17 @@ var RUN_MESSAGES = {
|
|
|
74
74
|
/** 中断后保留 worktree */
|
|
75
75
|
INTERRUPT_KEPT: "\u5DF2\u4FDD\u7559 worktree\uFF0C\u53EF\u7A0D\u540E\u4F7F\u7528 clawt remove \u624B\u52A8\u6E05\u7406",
|
|
76
76
|
/** 非 TTY 环境降级输出:任务启动 */
|
|
77
|
-
PROGRESS_TASK_STARTED: (index, total, branch,
|
|
77
|
+
PROGRESS_TASK_STARTED: (index, total, branch, path2) => `[${index}/${total}] ${branch} \u542F\u52A8 ${path2}`,
|
|
78
78
|
/** 非 TTY 环境降级输出:任务完成 */
|
|
79
|
-
PROGRESS_TASK_DONE: (index, total, branch, duration, cost,
|
|
79
|
+
PROGRESS_TASK_DONE: (index, total, branch, duration, cost, detail) => `[${index}/${total}] ${branch} \u2713 \u5B8C\u6210 ${duration} ${cost} ${detail}`,
|
|
80
80
|
/** 非 TTY 环境降级输出:任务失败 */
|
|
81
|
-
PROGRESS_TASK_FAILED: (index, total, branch, duration,
|
|
81
|
+
PROGRESS_TASK_FAILED: (index, total, branch, duration, detail) => `[${index}/${total}] ${branch} \u2717 \u5931\u8D25 ${duration} ${detail}`,
|
|
82
82
|
/** 并发限制提示 */
|
|
83
83
|
CONCURRENCY_INFO: (concurrency, total) => `\u5E76\u53D1\u9650\u5236: ${concurrency}\uFF0C\u5171 ${total} \u4E2A\u4EFB\u52A1`,
|
|
84
84
|
/** 并发数无效提示 */
|
|
85
85
|
CONCURRENCY_INVALID: "\u5E76\u53D1\u6570\u5FC5\u987B\u4E3A\u6B63\u6574\u6570",
|
|
86
86
|
/** 任务文件不存在 */
|
|
87
|
-
TASK_FILE_NOT_FOUND: (
|
|
87
|
+
TASK_FILE_NOT_FOUND: (path2) => `\u4EFB\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728: ${path2}`,
|
|
88
88
|
/** 任务文件中没有解析到有效任务 */
|
|
89
89
|
TASK_FILE_EMPTY: "\u4EFB\u52A1\u6587\u4EF6\u4E2D\u6CA1\u6709\u89E3\u6790\u5230\u6709\u6548\u4EFB\u52A1",
|
|
90
90
|
/** 任务文件中某个任务块缺少分支名 */
|
|
@@ -96,7 +96,7 @@ var RUN_MESSAGES = {
|
|
|
96
96
|
/** --file 和 --tasks 不能同时使用 */
|
|
97
97
|
FILE_AND_TASKS_CONFLICT: "--file \u548C --tasks \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528",
|
|
98
98
|
/** 任务文件加载成功 */
|
|
99
|
-
TASK_FILE_LOADED: (count,
|
|
99
|
+
TASK_FILE_LOADED: (count, path2) => `\u2713 \u4ECE ${path2} \u52A0\u8F7D\u4E86 ${count} \u4E2A\u4EFB\u52A1`,
|
|
100
100
|
/** 未指定 -b 或 -f */
|
|
101
101
|
BRANCH_OR_FILE_REQUIRED: "\u8BF7\u6307\u5B9A -b \u5206\u652F\u540D\u6216 -f \u4EFB\u52A1\u6587\u4EF6",
|
|
102
102
|
/** dry-run 预览标题 */
|
|
@@ -211,7 +211,16 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
|
211
211
|
/** 并行执行中单个命令失败 */
|
|
212
212
|
VALIDATE_PARALLEL_CMD_FAILED: (command, exitCode) => ` \u2717 ${command}\uFF08\u9000\u51FA\u7801: ${exitCode}\uFF09`,
|
|
213
213
|
/** 并行执行中单个命令启动失败 */
|
|
214
|
-
VALIDATE_PARALLEL_CMD_ERROR: (command, errorMessage) => ` \u2717 ${command}\uFF08\u9519\u8BEF: ${errorMessage}\uFF09
|
|
214
|
+
VALIDATE_PARALLEL_CMD_ERROR: (command, errorMessage) => ` \u2717 ${command}\uFF08\u9519\u8BEF: ${errorMessage}\uFF09`,
|
|
215
|
+
/** patch apply 失败后询问用户是否执行 sync */
|
|
216
|
+
VALIDATE_CONFIRM_AUTO_SYNC: (branch) => `\u662F\u5426\u7ACB\u5373\u6267\u884C sync \u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch}\uFF1F`,
|
|
217
|
+
/** 自动 sync 开始提示 */
|
|
218
|
+
VALIDATE_AUTO_SYNC_START: (branch) => `\u6B63\u5728\u81EA\u52A8\u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch} ...`,
|
|
219
|
+
/** 自动 sync 存在冲突,无法重试 */
|
|
220
|
+
VALIDATE_AUTO_SYNC_CONFLICT: (worktreePath) => `\u540C\u6B65\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u8FDB\u5165\u76EE\u6807 worktree \u624B\u52A8\u89E3\u51B3\u51B2\u7A81\u540E\u91CD\u8BD5
|
|
221
|
+
cd ${worktreePath}`,
|
|
222
|
+
/** 用户拒绝自动 sync */
|
|
223
|
+
VALIDATE_AUTO_SYNC_DECLINED: (branch) => `\u8BF7\u624B\u52A8\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`
|
|
215
224
|
};
|
|
216
225
|
|
|
217
226
|
// src/constants/messages/sync.ts
|
|
@@ -453,8 +462,6 @@ var DEBUG_TIMESTAMP_FORMAT = "HH:mm:ss.SSS";
|
|
|
453
462
|
// src/constants/progress.ts
|
|
454
463
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
455
464
|
var SPINNER_INTERVAL_MS = 100;
|
|
456
|
-
var CURSOR_UP = (n) => `\x1B[${n}A`;
|
|
457
|
-
var CLEAR_LINE = "\x1B[0K";
|
|
458
465
|
var CURSOR_HIDE = "\x1B[?25l";
|
|
459
466
|
var CURSOR_SHOW = "\x1B[?25h";
|
|
460
467
|
var TASK_STATUS_ICONS = {
|
|
@@ -475,6 +482,18 @@ var TASK_STATUS_LABELS = {
|
|
|
475
482
|
/** 失败 */
|
|
476
483
|
FAILED: "\u5931\u8D25"
|
|
477
484
|
};
|
|
485
|
+
var ACTIVITY_TEXT_MAX_LENGTH = 30;
|
|
486
|
+
var TEXT_ACTIVITY_PREFIX = "\u601D\u8003\u4E2D";
|
|
487
|
+
var RESULT_PREVIEW_MAX_LENGTH = 40;
|
|
488
|
+
var DEFAULT_TERMINAL_COLUMNS = 80;
|
|
489
|
+
var LINE_WRAP_DISABLE = "\x1B[?7l";
|
|
490
|
+
var LINE_WRAP_ENABLE = "\x1B[?7h";
|
|
491
|
+
var SYNC_OUTPUT_START = "\x1B[?2026h";
|
|
492
|
+
var SYNC_OUTPUT_END = "\x1B[?2026l";
|
|
493
|
+
var ALT_SCREEN_ENTER = "\x1B[?1049h";
|
|
494
|
+
var ALT_SCREEN_LEAVE = "\x1B[?1049l";
|
|
495
|
+
var CLEAR_SCREEN = "\x1B[2J";
|
|
496
|
+
var CURSOR_HOME = "\x1B[H";
|
|
478
497
|
|
|
479
498
|
// src/constants/prompt.ts
|
|
480
499
|
var SELECT_ALL_NAME = "__select_all__";
|
|
@@ -1425,28 +1444,59 @@ async function resolveTargetWorktree(worktrees, messages, branchName) {
|
|
|
1425
1444
|
|
|
1426
1445
|
// src/utils/progress-render.ts
|
|
1427
1446
|
import chalk3 from "chalk";
|
|
1428
|
-
|
|
1429
|
-
|
|
1447
|
+
import stringWidth from "string-width";
|
|
1448
|
+
var ANSI_RESET = "\x1B[0m";
|
|
1449
|
+
function truncateToTerminalWidth(text, maxWidth) {
|
|
1450
|
+
if (stringWidth(text) <= maxWidth) {
|
|
1451
|
+
return text;
|
|
1452
|
+
}
|
|
1453
|
+
let visibleWidth = 0;
|
|
1454
|
+
let result = "";
|
|
1455
|
+
let i = 0;
|
|
1456
|
+
while (i < text.length) {
|
|
1457
|
+
if (text[i] === "\x1B" && text[i + 1] === "[") {
|
|
1458
|
+
const match = text.slice(i).match(/^\x1B\[[0-9;]*m/);
|
|
1459
|
+
if (match) {
|
|
1460
|
+
result += match[0];
|
|
1461
|
+
i += match[0].length;
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
const charWidth = stringWidth(text[i]);
|
|
1466
|
+
if (visibleWidth + charWidth > maxWidth) {
|
|
1467
|
+
break;
|
|
1468
|
+
}
|
|
1469
|
+
result += text[i];
|
|
1470
|
+
visibleWidth += charWidth;
|
|
1471
|
+
i++;
|
|
1472
|
+
}
|
|
1473
|
+
return result + ANSI_RESET;
|
|
1474
|
+
}
|
|
1475
|
+
function getMaxPathWidth(tasks) {
|
|
1476
|
+
return Math.max(...tasks.map((t) => t.path.length));
|
|
1430
1477
|
}
|
|
1431
|
-
function renderTaskLine(task, total,
|
|
1478
|
+
function renderTaskLine(task, total, maxPathWidth, spinnerChar) {
|
|
1432
1479
|
const indexStr = `[${task.index}/${total}]`;
|
|
1433
|
-
const
|
|
1480
|
+
const pathStr = task.path.padEnd(maxPathWidth);
|
|
1434
1481
|
switch (task.status) {
|
|
1435
1482
|
case "pending": {
|
|
1436
|
-
return `${indexStr} ${
|
|
1483
|
+
return `${indexStr} ${pathStr} ${chalk3.gray(TASK_STATUS_ICONS.PENDING)} ${chalk3.gray(TASK_STATUS_LABELS.PENDING)}`;
|
|
1437
1484
|
}
|
|
1438
1485
|
case "running": {
|
|
1439
1486
|
const elapsed = formatDuration(Date.now() - task.startedAt);
|
|
1440
|
-
|
|
1487
|
+
const detail = task.activity ? ` ${chalk3.dim(task.activity)}` : "";
|
|
1488
|
+
return `${indexStr} ${pathStr} ${chalk3.cyan(spinnerChar)} ${chalk3.cyan(TASK_STATUS_LABELS.RUNNING)} ${chalk3.gray(elapsed)}${detail}`;
|
|
1441
1489
|
}
|
|
1442
1490
|
case "done": {
|
|
1443
1491
|
const duration = task.durationMs != null ? formatDuration(task.durationMs) : "N/A";
|
|
1444
1492
|
const cost = task.costUsd != null ? `$${task.costUsd.toFixed(2)}` : "";
|
|
1445
|
-
|
|
1493
|
+
const preview = task.resultPreview ? ` ${chalk3.dim(task.resultPreview)}` : "";
|
|
1494
|
+
return `${indexStr} ${pathStr} ${chalk3.green(TASK_STATUS_ICONS.DONE)} ${chalk3.green(TASK_STATUS_LABELS.DONE)} ${chalk3.gray(duration)} ${chalk3.yellow(cost)}${preview}`;
|
|
1446
1495
|
}
|
|
1447
1496
|
case "failed": {
|
|
1448
1497
|
const duration = task.durationMs != null ? formatDuration(task.durationMs) : "N/A";
|
|
1449
|
-
|
|
1498
|
+
const preview = task.resultPreview ? ` ${chalk3.dim(task.resultPreview)}` : "";
|
|
1499
|
+
return `${indexStr} ${pathStr} ${chalk3.red(TASK_STATUS_ICONS.FAILED)} ${chalk3.red(TASK_STATUS_LABELS.FAILED)} ${chalk3.gray(duration)}${preview}`;
|
|
1450
1500
|
}
|
|
1451
1501
|
}
|
|
1452
1502
|
}
|
|
@@ -1475,10 +1525,12 @@ var ProgressRenderer = class {
|
|
|
1475
1525
|
timer;
|
|
1476
1526
|
/** 是否为 TTY 环境 */
|
|
1477
1527
|
isTTY;
|
|
1478
|
-
/**
|
|
1479
|
-
|
|
1528
|
+
/** resize 事件处理器引用(用于 stop 时移除监听) */
|
|
1529
|
+
resizeHandler;
|
|
1480
1530
|
/** 是否已停止 */
|
|
1481
1531
|
stopped;
|
|
1532
|
+
/** exit 兜底处理器(确保异常退出时终端状态被恢复) */
|
|
1533
|
+
exitHandler;
|
|
1482
1534
|
/** 是否存在排队任务(启用汇总行渲染) */
|
|
1483
1535
|
hasPendingTasks;
|
|
1484
1536
|
/**
|
|
@@ -1493,8 +1545,9 @@ var ProgressRenderer = class {
|
|
|
1493
1545
|
this.frameIndex = 0;
|
|
1494
1546
|
this.timer = null;
|
|
1495
1547
|
this.isTTY = !!process.stdout.isTTY;
|
|
1496
|
-
this.
|
|
1548
|
+
this.resizeHandler = null;
|
|
1497
1549
|
this.stopped = false;
|
|
1550
|
+
this.exitHandler = null;
|
|
1498
1551
|
this.hasPendingTasks = !allRunning;
|
|
1499
1552
|
this.tasks = branches.map((branch, i) => ({
|
|
1500
1553
|
index: i + 1,
|
|
@@ -1505,7 +1558,9 @@ var ProgressRenderer = class {
|
|
|
1505
1558
|
finishedAt: null,
|
|
1506
1559
|
lastActiveAt: allRunning ? now : 0,
|
|
1507
1560
|
durationMs: null,
|
|
1508
|
-
costUsd: null
|
|
1561
|
+
costUsd: null,
|
|
1562
|
+
activity: null,
|
|
1563
|
+
resultPreview: null
|
|
1509
1564
|
}));
|
|
1510
1565
|
}
|
|
1511
1566
|
/**
|
|
@@ -1523,7 +1578,9 @@ var ProgressRenderer = class {
|
|
|
1523
1578
|
}
|
|
1524
1579
|
return;
|
|
1525
1580
|
}
|
|
1581
|
+
process.stdout.write(ALT_SCREEN_ENTER);
|
|
1526
1582
|
process.stdout.write(CURSOR_HIDE);
|
|
1583
|
+
process.stdout.write(LINE_WRAP_DISABLE);
|
|
1527
1584
|
this.render();
|
|
1528
1585
|
this.timer = setInterval(() => {
|
|
1529
1586
|
this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
|
|
@@ -1532,6 +1589,18 @@ var ProgressRenderer = class {
|
|
|
1532
1589
|
if (this.timer.unref) {
|
|
1533
1590
|
this.timer.unref();
|
|
1534
1591
|
}
|
|
1592
|
+
this.resizeHandler = () => {
|
|
1593
|
+
if (!this.stopped) {
|
|
1594
|
+
this.render();
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
process.stdout.on("resize", this.resizeHandler);
|
|
1598
|
+
this.exitHandler = () => {
|
|
1599
|
+
process.stdout.write(LINE_WRAP_ENABLE);
|
|
1600
|
+
process.stdout.write(CURSOR_SHOW);
|
|
1601
|
+
process.stdout.write(ALT_SCREEN_LEAVE);
|
|
1602
|
+
};
|
|
1603
|
+
process.on("exit", this.exitHandler);
|
|
1535
1604
|
}
|
|
1536
1605
|
/**
|
|
1537
1606
|
* 更新指定任务的最后活动时间戳
|
|
@@ -1541,6 +1610,17 @@ var ProgressRenderer = class {
|
|
|
1541
1610
|
updateActivity(index) {
|
|
1542
1611
|
this.tasks[index].lastActiveAt = Date.now();
|
|
1543
1612
|
}
|
|
1613
|
+
/**
|
|
1614
|
+
* 更新指定任务的活动描述文本
|
|
1615
|
+
* 同时更新 lastActiveAt 时间戳
|
|
1616
|
+
* @param {number} index - 任务索引(从 0 开始)
|
|
1617
|
+
* @param {string} text - 活动描述文本
|
|
1618
|
+
*/
|
|
1619
|
+
updateActivityText(index, text) {
|
|
1620
|
+
const task = this.tasks[index];
|
|
1621
|
+
task.activity = text;
|
|
1622
|
+
task.lastActiveAt = Date.now();
|
|
1623
|
+
}
|
|
1544
1624
|
/**
|
|
1545
1625
|
* 标记指定任务为运行中状态
|
|
1546
1626
|
* 将 pending 任务标记为 running 并设置启动时间戳
|
|
@@ -1561,32 +1641,36 @@ var ProgressRenderer = class {
|
|
|
1561
1641
|
* @param {number} index - 任务索引(从 0 开始)
|
|
1562
1642
|
* @param {number} durationMs - 耗时(毫秒)
|
|
1563
1643
|
* @param {number} costUsd - 费用(美元)
|
|
1644
|
+
* @param {string} [resultPreview] - 结果预览文本(可选)
|
|
1564
1645
|
*/
|
|
1565
|
-
markDone(index, durationMs, costUsd) {
|
|
1646
|
+
markDone(index, durationMs, costUsd, resultPreview) {
|
|
1566
1647
|
const task = this.tasks[index];
|
|
1567
1648
|
task.status = "done";
|
|
1568
1649
|
task.finishedAt = Date.now();
|
|
1569
1650
|
task.durationMs = durationMs;
|
|
1570
1651
|
task.costUsd = costUsd;
|
|
1652
|
+
task.resultPreview = resultPreview ?? null;
|
|
1571
1653
|
if (!this.isTTY) {
|
|
1572
1654
|
const duration = formatDuration(durationMs);
|
|
1573
1655
|
const cost = `$${costUsd.toFixed(2)}`;
|
|
1574
|
-
console.log(MESSAGES.PROGRESS_TASK_DONE(task.index, this.total, task.branch, duration, cost, task.path));
|
|
1656
|
+
console.log(MESSAGES.PROGRESS_TASK_DONE(task.index, this.total, task.branch, duration, cost, task.resultPreview ?? task.path));
|
|
1575
1657
|
}
|
|
1576
1658
|
}
|
|
1577
1659
|
/**
|
|
1578
1660
|
* 标记指定任务为失败状态
|
|
1579
1661
|
* @param {number} index - 任务索引(从 0 开始)
|
|
1580
1662
|
* @param {number} durationMs - 耗时(毫秒)
|
|
1663
|
+
* @param {string} [resultPreview] - 结果预览文本(可选)
|
|
1581
1664
|
*/
|
|
1582
|
-
markFailed(index, durationMs) {
|
|
1665
|
+
markFailed(index, durationMs, resultPreview) {
|
|
1583
1666
|
const task = this.tasks[index];
|
|
1584
1667
|
task.status = "failed";
|
|
1585
1668
|
task.finishedAt = Date.now();
|
|
1586
1669
|
task.durationMs = durationMs;
|
|
1670
|
+
task.resultPreview = resultPreview ?? null;
|
|
1587
1671
|
if (!this.isTTY) {
|
|
1588
1672
|
const duration = formatDuration(durationMs);
|
|
1589
|
-
console.log(MESSAGES.PROGRESS_TASK_FAILED(task.index, this.total, task.branch, duration, task.path));
|
|
1673
|
+
console.log(MESSAGES.PROGRESS_TASK_FAILED(task.index, this.total, task.branch, duration, task.resultPreview ?? task.path));
|
|
1590
1674
|
}
|
|
1591
1675
|
}
|
|
1592
1676
|
/**
|
|
@@ -1600,30 +1684,58 @@ var ProgressRenderer = class {
|
|
|
1600
1684
|
clearInterval(this.timer);
|
|
1601
1685
|
this.timer = null;
|
|
1602
1686
|
}
|
|
1687
|
+
if (this.resizeHandler) {
|
|
1688
|
+
process.stdout.removeListener("resize", this.resizeHandler);
|
|
1689
|
+
this.resizeHandler = null;
|
|
1690
|
+
}
|
|
1603
1691
|
if (this.isTTY) {
|
|
1604
1692
|
this.render();
|
|
1693
|
+
process.stdout.write(LINE_WRAP_ENABLE);
|
|
1605
1694
|
process.stdout.write(CURSOR_SHOW);
|
|
1695
|
+
process.stdout.write(ALT_SCREEN_LEAVE);
|
|
1696
|
+
const cols = process.stdout.columns || DEFAULT_TERMINAL_COLUMNS;
|
|
1697
|
+
const finalLines = this.buildLines(cols);
|
|
1698
|
+
for (const line of finalLines) {
|
|
1699
|
+
process.stdout.write(`${line}
|
|
1700
|
+
`);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
if (this.exitHandler) {
|
|
1704
|
+
process.removeListener("exit", this.exitHandler);
|
|
1705
|
+
this.exitHandler = null;
|
|
1606
1706
|
}
|
|
1607
1707
|
}
|
|
1608
1708
|
/**
|
|
1609
|
-
*
|
|
1610
|
-
*
|
|
1709
|
+
* 构建当前帧的面板行内容
|
|
1710
|
+
* @param {number} cols - 终端列数
|
|
1711
|
+
* @returns {string[]} 截断后的面板行数组
|
|
1611
1712
|
*/
|
|
1612
|
-
|
|
1613
|
-
const
|
|
1713
|
+
buildLines(cols) {
|
|
1714
|
+
const maxPathWidth = getMaxPathWidth(this.tasks);
|
|
1614
1715
|
const spinnerChar = SPINNER_FRAMES[this.frameIndex];
|
|
1615
|
-
const lines = this.tasks.map((task) => renderTaskLine(task, this.total,
|
|
1716
|
+
const lines = this.tasks.map((task) => renderTaskLine(task, this.total, maxPathWidth, spinnerChar));
|
|
1616
1717
|
if (this.hasPendingTasks) {
|
|
1617
1718
|
lines.push(renderSummaryLine(this.tasks, this.total));
|
|
1618
1719
|
}
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1720
|
+
return lines.map((line) => truncateToTerminalWidth(line, cols));
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* 执行一次完整的面板渲染
|
|
1724
|
+
* 在备选屏幕缓冲区中,每次清屏+归位后完全重绘
|
|
1725
|
+
* 无需计算 CURSOR_UP 回退量,不受终端 reflow 影响
|
|
1726
|
+
* 使用 Synchronized Output 防止多行写入时的闪烁
|
|
1727
|
+
*/
|
|
1728
|
+
render() {
|
|
1729
|
+
const cols = process.stdout.columns || DEFAULT_TERMINAL_COLUMNS;
|
|
1730
|
+
const lines = this.buildLines(cols);
|
|
1731
|
+
process.stdout.write(SYNC_OUTPUT_START);
|
|
1732
|
+
process.stdout.write(CLEAR_SCREEN);
|
|
1733
|
+
process.stdout.write(CURSOR_HOME);
|
|
1622
1734
|
for (const line of lines) {
|
|
1623
|
-
process.stdout.write(`${line}
|
|
1735
|
+
process.stdout.write(`${line}
|
|
1624
1736
|
`);
|
|
1625
1737
|
}
|
|
1626
|
-
|
|
1738
|
+
process.stdout.write(SYNC_OUTPUT_END);
|
|
1627
1739
|
}
|
|
1628
1740
|
};
|
|
1629
1741
|
|
|
@@ -1686,11 +1798,114 @@ function loadTaskFile(filePath, options) {
|
|
|
1686
1798
|
return entries;
|
|
1687
1799
|
}
|
|
1688
1800
|
|
|
1801
|
+
// src/utils/stream-parser.ts
|
|
1802
|
+
import path from "path";
|
|
1803
|
+
function createLineBuffer() {
|
|
1804
|
+
let buffer = "";
|
|
1805
|
+
return {
|
|
1806
|
+
push(chunk) {
|
|
1807
|
+
buffer += chunk;
|
|
1808
|
+
const lines = buffer.split("\n");
|
|
1809
|
+
buffer = lines.pop() ?? "";
|
|
1810
|
+
return lines;
|
|
1811
|
+
},
|
|
1812
|
+
flush() {
|
|
1813
|
+
const remaining = buffer;
|
|
1814
|
+
buffer = "";
|
|
1815
|
+
return remaining || null;
|
|
1816
|
+
}
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
function parseStreamLine(line) {
|
|
1820
|
+
const trimmed = line.trim();
|
|
1821
|
+
if (!trimmed) return null;
|
|
1822
|
+
try {
|
|
1823
|
+
return JSON.parse(trimmed);
|
|
1824
|
+
} catch {
|
|
1825
|
+
return null;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
function truncateText(text, maxLength) {
|
|
1829
|
+
if (text.length <= maxLength) return text;
|
|
1830
|
+
return text.substring(0, maxLength - 1) + "\u2026";
|
|
1831
|
+
}
|
|
1832
|
+
function formatActivityText(kind, toolName, input, text) {
|
|
1833
|
+
let raw = "";
|
|
1834
|
+
if (kind === "tool_use" && toolName) {
|
|
1835
|
+
const filePath = input?.file_path;
|
|
1836
|
+
const command = input?.command;
|
|
1837
|
+
if (filePath) {
|
|
1838
|
+
const basename2 = path.basename(filePath);
|
|
1839
|
+
raw = `${toolName} ${basename2}`;
|
|
1840
|
+
} else if (command) {
|
|
1841
|
+
const cleaned = command.replace(/[\n\r\t]+/g, " ").trim();
|
|
1842
|
+
raw = `${toolName} ${cleaned}`;
|
|
1843
|
+
} else {
|
|
1844
|
+
raw = toolName;
|
|
1845
|
+
}
|
|
1846
|
+
} else if (kind === "text" && text) {
|
|
1847
|
+
const cleaned = text.replace(/[\n\r\t]/g, " ").trim();
|
|
1848
|
+
if (!cleaned) return "";
|
|
1849
|
+
raw = `${TEXT_ACTIVITY_PREFIX}: ${cleaned}`;
|
|
1850
|
+
}
|
|
1851
|
+
if (!raw) return "";
|
|
1852
|
+
return truncateText(raw, ACTIVITY_TEXT_MAX_LENGTH);
|
|
1853
|
+
}
|
|
1854
|
+
function parseStreamEvent(event) {
|
|
1855
|
+
if (event.type === "result") {
|
|
1856
|
+
const result = {
|
|
1857
|
+
type: event.type,
|
|
1858
|
+
subtype: event.subtype ?? "",
|
|
1859
|
+
is_error: event.is_error ?? false,
|
|
1860
|
+
duration_ms: event.duration_ms ?? 0,
|
|
1861
|
+
duration_api_ms: event.duration_api_ms ?? 0,
|
|
1862
|
+
num_turns: event.num_turns ?? 0,
|
|
1863
|
+
result: event.result ?? "",
|
|
1864
|
+
stop_reason: event.stop_reason ?? "",
|
|
1865
|
+
session_id: event.session_id ?? "",
|
|
1866
|
+
total_cost_usd: event.total_cost_usd ?? 0,
|
|
1867
|
+
usage: event.usage ?? {}
|
|
1868
|
+
};
|
|
1869
|
+
return { kind: "result", activityText: "", result };
|
|
1870
|
+
}
|
|
1871
|
+
if (event.type === "assistant") {
|
|
1872
|
+
const content = event.message?.content;
|
|
1873
|
+
if (!content || content.length === 0) return null;
|
|
1874
|
+
let lastToolUse = null;
|
|
1875
|
+
let lastText = null;
|
|
1876
|
+
for (const block of content) {
|
|
1877
|
+
if (block.type === "tool_use") {
|
|
1878
|
+
lastToolUse = block;
|
|
1879
|
+
} else if (block.type === "text") {
|
|
1880
|
+
lastText = block;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
if (lastToolUse) {
|
|
1884
|
+
const activityText = formatActivityText(
|
|
1885
|
+
"tool_use",
|
|
1886
|
+
lastToolUse.name,
|
|
1887
|
+
lastToolUse.input
|
|
1888
|
+
);
|
|
1889
|
+
if (activityText) {
|
|
1890
|
+
return { kind: "tool_use", activityText };
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
if (lastText?.text) {
|
|
1894
|
+
const activityText = formatActivityText("text", void 0, void 0, lastText.text);
|
|
1895
|
+
if (activityText) {
|
|
1896
|
+
return { kind: "text", activityText };
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
return null;
|
|
1900
|
+
}
|
|
1901
|
+
return null;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1689
1904
|
// src/utils/task-executor.ts
|
|
1690
|
-
function executeClaudeTask(worktree, task) {
|
|
1905
|
+
function executeClaudeTask(worktree, task, onActivity) {
|
|
1691
1906
|
const child = spawnProcess(
|
|
1692
1907
|
"claude",
|
|
1693
|
-
["-p", task, "--output-format", "json", "--permission-mode", "bypassPermissions"],
|
|
1908
|
+
["-p", task, "--output-format", "stream-json", "--verbose", "--permission-mode", "bypassPermissions"],
|
|
1694
1909
|
{
|
|
1695
1910
|
cwd: worktree.path,
|
|
1696
1911
|
// stdin 必须设置为 'ignore',不能用 'pipe'
|
|
@@ -1701,31 +1916,47 @@ function executeClaudeTask(worktree, task) {
|
|
|
1701
1916
|
}
|
|
1702
1917
|
);
|
|
1703
1918
|
const promise = new Promise((resolve2) => {
|
|
1704
|
-
let stdout = "";
|
|
1705
1919
|
let stderr = "";
|
|
1920
|
+
let finalResult = null;
|
|
1921
|
+
const lineBuffer = createLineBuffer();
|
|
1706
1922
|
child.stdout?.on("data", (data) => {
|
|
1707
|
-
|
|
1923
|
+
const lines = lineBuffer.push(data.toString());
|
|
1924
|
+
for (const line of lines) {
|
|
1925
|
+
const event = parseStreamLine(line);
|
|
1926
|
+
if (!event) continue;
|
|
1927
|
+
const parsed = parseStreamEvent(event);
|
|
1928
|
+
if (!parsed) continue;
|
|
1929
|
+
if (parsed.kind === "result") {
|
|
1930
|
+
finalResult = parsed.result ?? null;
|
|
1931
|
+
} else if (parsed.activityText && onActivity) {
|
|
1932
|
+
onActivity(parsed.activityText);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1708
1935
|
});
|
|
1709
1936
|
child.stderr?.on("data", (data) => {
|
|
1710
1937
|
stderr += data.toString();
|
|
1711
1938
|
});
|
|
1712
1939
|
child.on("close", (code) => {
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
if (
|
|
1717
|
-
|
|
1718
|
-
|
|
1940
|
+
const remaining = lineBuffer.flush();
|
|
1941
|
+
if (remaining) {
|
|
1942
|
+
const event = parseStreamLine(remaining);
|
|
1943
|
+
if (event) {
|
|
1944
|
+
const parsed = parseStreamEvent(event);
|
|
1945
|
+
if (parsed?.kind === "result") {
|
|
1946
|
+
finalResult = parsed.result ?? null;
|
|
1947
|
+
}
|
|
1719
1948
|
}
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1949
|
+
}
|
|
1950
|
+
let success = code === 0;
|
|
1951
|
+
if (finalResult) {
|
|
1952
|
+
success = !finalResult.is_error;
|
|
1722
1953
|
}
|
|
1723
1954
|
resolve2({
|
|
1724
1955
|
task,
|
|
1725
1956
|
branch: worktree.branch,
|
|
1726
1957
|
worktreePath: worktree.path,
|
|
1727
1958
|
success,
|
|
1728
|
-
result,
|
|
1959
|
+
result: finalResult,
|
|
1729
1960
|
error: success ? void 0 : stderr || "\u4EFB\u52A1\u6267\u884C\u5931\u8D25"
|
|
1730
1961
|
});
|
|
1731
1962
|
});
|
|
@@ -1767,16 +1998,20 @@ async function handleInterruptCleanup(worktrees) {
|
|
|
1767
1998
|
}
|
|
1768
1999
|
}
|
|
1769
2000
|
function updateRendererStatus(renderer, index, result, startTime) {
|
|
2001
|
+
const rawResultText = result.result?.result;
|
|
2002
|
+
const resultPreview = rawResultText ? truncateText(rawResultText.replace(/[\n\r\t]+/g, " ").trim(), RESULT_PREVIEW_MAX_LENGTH) : void 0;
|
|
1770
2003
|
if (result.success) {
|
|
1771
2004
|
renderer.markDone(
|
|
1772
2005
|
index,
|
|
1773
2006
|
result.result?.duration_ms ?? Date.now() - startTime,
|
|
1774
|
-
result.result?.total_cost_usd ?? 0
|
|
2007
|
+
result.result?.total_cost_usd ?? 0,
|
|
2008
|
+
resultPreview
|
|
1775
2009
|
);
|
|
1776
2010
|
} else {
|
|
1777
2011
|
renderer.markFailed(
|
|
1778
2012
|
index,
|
|
1779
|
-
result.result?.duration_ms ?? Date.now() - startTime
|
|
2013
|
+
result.result?.duration_ms ?? Date.now() - startTime,
|
|
2014
|
+
resultPreview
|
|
1780
2015
|
);
|
|
1781
2016
|
}
|
|
1782
2017
|
}
|
|
@@ -1794,11 +2029,10 @@ async function executeWithConcurrency(worktrees, tasks, concurrency, renderer, s
|
|
|
1794
2029
|
const task = tasks[index];
|
|
1795
2030
|
logger.info(`\u542F\u52A8\u4EFB\u52A1 ${index + 1}: ${task} (worktree: ${wt.path})`);
|
|
1796
2031
|
renderer.markRunning(index);
|
|
1797
|
-
const handle = executeClaudeTask(wt, task)
|
|
1798
|
-
|
|
1799
|
-
handle.child.stderr?.on("data", () => {
|
|
1800
|
-
renderer.updateActivity(index);
|
|
2032
|
+
const handle = executeClaudeTask(wt, task, (activityText) => {
|
|
2033
|
+
renderer.updateActivityText(index, activityText);
|
|
1801
2034
|
});
|
|
2035
|
+
childProcesses.push(handle.child);
|
|
1802
2036
|
handle.promise.then((result) => {
|
|
1803
2037
|
results[index] = result;
|
|
1804
2038
|
completedCount++;
|
|
@@ -1821,11 +2055,10 @@ async function executeAllParallel(worktrees, tasks, renderer, startTime, isInter
|
|
|
1821
2055
|
const handles = worktrees.map((wt, index) => {
|
|
1822
2056
|
const task = tasks[index];
|
|
1823
2057
|
logger.info(`\u542F\u52A8\u4EFB\u52A1 ${index + 1}: ${task} (worktree: ${wt.path})`);
|
|
1824
|
-
const handle = executeClaudeTask(wt, task)
|
|
1825
|
-
|
|
1826
|
-
handle.child.stderr?.on("data", () => {
|
|
1827
|
-
renderer.updateActivity(index);
|
|
2058
|
+
const handle = executeClaudeTask(wt, task, (activityText) => {
|
|
2059
|
+
renderer.updateActivityText(index, activityText);
|
|
1828
2060
|
});
|
|
2061
|
+
childProcesses.push(handle.child);
|
|
1829
2062
|
return handle;
|
|
1830
2063
|
});
|
|
1831
2064
|
const results = await Promise.all(
|
|
@@ -2337,6 +2570,66 @@ async function handleBatchResume(worktrees) {
|
|
|
2337
2570
|
|
|
2338
2571
|
// src/commands/validate.ts
|
|
2339
2572
|
import Enquirer4 from "enquirer";
|
|
2573
|
+
|
|
2574
|
+
// src/commands/sync.ts
|
|
2575
|
+
function registerSyncCommand(program2) {
|
|
2576
|
+
program2.command("sync").description("\u5C06\u4E3B\u5206\u652F\u6700\u65B0\u4EE3\u7801\u540C\u6B65\u5230\u76EE\u6807 worktree").option("-b, --branch <branchName>", "\u8981\u540C\u6B65\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").action(async (options) => {
|
|
2577
|
+
await handleSync(options);
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
var SYNC_RESOLVE_MESSAGES = {
|
|
2581
|
+
noWorktrees: MESSAGES.SYNC_NO_WORKTREES,
|
|
2582
|
+
selectBranch: MESSAGES.SYNC_SELECT_BRANCH,
|
|
2583
|
+
multipleMatches: MESSAGES.SYNC_MULTIPLE_MATCHES,
|
|
2584
|
+
noMatch: MESSAGES.SYNC_NO_MATCH
|
|
2585
|
+
};
|
|
2586
|
+
function autoSaveChanges(worktreePath, branch) {
|
|
2587
|
+
gitAddAll(worktreePath);
|
|
2588
|
+
gitCommit(AUTO_SAVE_COMMIT_MESSAGE, worktreePath);
|
|
2589
|
+
printInfo(MESSAGES.SYNC_AUTO_COMMITTED(branch));
|
|
2590
|
+
logger.info(`\u5DF2\u81EA\u52A8\u4FDD\u5B58 ${branch} \u5206\u652F\u7684\u672A\u63D0\u4EA4\u53D8\u66F4`);
|
|
2591
|
+
}
|
|
2592
|
+
function mergeMainBranch(worktreePath, mainBranch) {
|
|
2593
|
+
try {
|
|
2594
|
+
gitMerge(mainBranch, worktreePath);
|
|
2595
|
+
return false;
|
|
2596
|
+
} catch {
|
|
2597
|
+
if (hasMergeConflict(worktreePath)) {
|
|
2598
|
+
return true;
|
|
2599
|
+
}
|
|
2600
|
+
throw new ClawtError(`\u5408\u5E76 ${mainBranch} \u5931\u8D25`);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
function executeSyncForBranch(targetWorktreePath, branch) {
|
|
2604
|
+
const mainWorktreePath = getGitTopLevel();
|
|
2605
|
+
const mainBranch = getCurrentBranch(mainWorktreePath);
|
|
2606
|
+
if (!isWorkingDirClean(targetWorktreePath)) {
|
|
2607
|
+
autoSaveChanges(targetWorktreePath, branch);
|
|
2608
|
+
}
|
|
2609
|
+
printInfo(MESSAGES.SYNC_MERGING(branch, mainBranch));
|
|
2610
|
+
const hasConflict = mergeMainBranch(targetWorktreePath, mainBranch);
|
|
2611
|
+
if (hasConflict) {
|
|
2612
|
+
printWarning(MESSAGES.SYNC_CONFLICT(targetWorktreePath));
|
|
2613
|
+
return { success: false, hasConflict: true };
|
|
2614
|
+
}
|
|
2615
|
+
const projectName = getProjectName();
|
|
2616
|
+
if (hasSnapshot(projectName, branch)) {
|
|
2617
|
+
removeSnapshot(projectName, branch);
|
|
2618
|
+
logger.info(`\u5DF2\u6E05\u9664\u5206\u652F ${branch} \u7684 validate \u5FEB\u7167`);
|
|
2619
|
+
}
|
|
2620
|
+
printSuccess(MESSAGES.SYNC_SUCCESS(branch, mainBranch));
|
|
2621
|
+
return { success: true, hasConflict: false };
|
|
2622
|
+
}
|
|
2623
|
+
async function handleSync(options) {
|
|
2624
|
+
validateMainWorktree();
|
|
2625
|
+
logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
|
|
2626
|
+
const worktrees = getProjectWorktrees();
|
|
2627
|
+
const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
|
|
2628
|
+
const { path: targetWorktreePath, branch } = worktree;
|
|
2629
|
+
executeSyncForBranch(targetWorktreePath, branch);
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
// src/commands/validate.ts
|
|
2340
2633
|
var VALIDATE_RESOLVE_MESSAGES = {
|
|
2341
2634
|
noWorktrees: MESSAGES.VALIDATE_NO_WORKTREES,
|
|
2342
2635
|
selectBranch: MESSAGES.VALIDATE_SELECT_BRANCH,
|
|
@@ -2397,9 +2690,10 @@ function migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName
|
|
|
2397
2690
|
} catch (error) {
|
|
2398
2691
|
logger.warn(`patch apply \u5931\u8D25: ${error}`);
|
|
2399
2692
|
printWarning(MESSAGES.VALIDATE_PATCH_APPLY_FAILED(branchName));
|
|
2400
|
-
|
|
2693
|
+
return { success: false };
|
|
2401
2694
|
}
|
|
2402
2695
|
}
|
|
2696
|
+
return { success: true };
|
|
2403
2697
|
} finally {
|
|
2404
2698
|
if (didTempCommit) {
|
|
2405
2699
|
try {
|
|
@@ -2415,6 +2709,18 @@ function migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName
|
|
|
2415
2709
|
}
|
|
2416
2710
|
}
|
|
2417
2711
|
}
|
|
2712
|
+
async function handlePatchApplyFailure(targetWorktreePath, branchName) {
|
|
2713
|
+
const confirmed = await confirmAction(MESSAGES.VALIDATE_CONFIRM_AUTO_SYNC(branchName));
|
|
2714
|
+
if (!confirmed) {
|
|
2715
|
+
printWarning(MESSAGES.VALIDATE_AUTO_SYNC_DECLINED(branchName));
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2718
|
+
printInfo(MESSAGES.VALIDATE_AUTO_SYNC_START(branchName));
|
|
2719
|
+
const syncResult = executeSyncForBranch(targetWorktreePath, branchName);
|
|
2720
|
+
if (syncResult.hasConflict) {
|
|
2721
|
+
printWarning(MESSAGES.VALIDATE_AUTO_SYNC_CONFLICT(targetWorktreePath));
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2418
2724
|
function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName) {
|
|
2419
2725
|
gitAddAll(mainWorktreePath);
|
|
2420
2726
|
const treeHash = gitWriteTree(mainWorktreePath);
|
|
@@ -2448,18 +2754,26 @@ async function handleValidateClean(options) {
|
|
|
2448
2754
|
removeSnapshot(projectName, branchName);
|
|
2449
2755
|
printSuccess(MESSAGES.VALIDATE_CLEANED(branchName));
|
|
2450
2756
|
}
|
|
2451
|
-
function handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
|
|
2452
|
-
migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
2757
|
+
async function handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
|
|
2758
|
+
const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
2759
|
+
if (!result.success) {
|
|
2760
|
+
await handlePatchApplyFailure(targetWorktreePath, branchName);
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2453
2763
|
saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName);
|
|
2454
2764
|
printSuccess(MESSAGES.VALIDATE_SUCCESS(branchName));
|
|
2455
2765
|
}
|
|
2456
|
-
function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
|
|
2766
|
+
async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
|
|
2457
2767
|
const { treeHash: oldTreeHash, headCommitHash: oldHeadCommitHash } = readSnapshot(projectName, branchName);
|
|
2458
2768
|
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
2459
2769
|
gitResetHard(mainWorktreePath);
|
|
2460
2770
|
gitCleanForce(mainWorktreePath);
|
|
2461
2771
|
}
|
|
2462
|
-
migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
2772
|
+
const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
2773
|
+
if (!result.success) {
|
|
2774
|
+
await handlePatchApplyFailure(targetWorktreePath, branchName);
|
|
2775
|
+
return;
|
|
2776
|
+
}
|
|
2463
2777
|
saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName);
|
|
2464
2778
|
try {
|
|
2465
2779
|
const currentHeadCommitHash = getHeadCommitHash(mainWorktreePath);
|
|
@@ -2562,12 +2876,12 @@ async function handleValidate(options) {
|
|
|
2562
2876
|
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
2563
2877
|
await handleDirtyMainWorktree(mainWorktreePath);
|
|
2564
2878
|
}
|
|
2565
|
-
handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted);
|
|
2879
|
+
await handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted);
|
|
2566
2880
|
} else {
|
|
2567
2881
|
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
2568
2882
|
await handleDirtyMainWorktree(mainWorktreePath);
|
|
2569
2883
|
}
|
|
2570
|
-
handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted);
|
|
2884
|
+
await handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted);
|
|
2571
2885
|
}
|
|
2572
2886
|
if (options.run) {
|
|
2573
2887
|
await executeRunCommand(options.run, mainWorktreePath);
|
|
@@ -2780,60 +3094,6 @@ function handleConfigGet(key) {
|
|
|
2780
3094
|
printInfo(MESSAGES.CONFIG_GET_VALUE(key, String(value)));
|
|
2781
3095
|
}
|
|
2782
3096
|
|
|
2783
|
-
// src/commands/sync.ts
|
|
2784
|
-
function registerSyncCommand(program2) {
|
|
2785
|
-
program2.command("sync").description("\u5C06\u4E3B\u5206\u652F\u6700\u65B0\u4EE3\u7801\u540C\u6B65\u5230\u76EE\u6807 worktree").option("-b, --branch <branchName>", "\u8981\u540C\u6B65\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").action(async (options) => {
|
|
2786
|
-
await handleSync(options);
|
|
2787
|
-
});
|
|
2788
|
-
}
|
|
2789
|
-
var SYNC_RESOLVE_MESSAGES = {
|
|
2790
|
-
noWorktrees: MESSAGES.SYNC_NO_WORKTREES,
|
|
2791
|
-
selectBranch: MESSAGES.SYNC_SELECT_BRANCH,
|
|
2792
|
-
multipleMatches: MESSAGES.SYNC_MULTIPLE_MATCHES,
|
|
2793
|
-
noMatch: MESSAGES.SYNC_NO_MATCH
|
|
2794
|
-
};
|
|
2795
|
-
function autoSaveChanges(worktreePath, branch) {
|
|
2796
|
-
gitAddAll(worktreePath);
|
|
2797
|
-
gitCommit(AUTO_SAVE_COMMIT_MESSAGE, worktreePath);
|
|
2798
|
-
printInfo(MESSAGES.SYNC_AUTO_COMMITTED(branch));
|
|
2799
|
-
logger.info(`\u5DF2\u81EA\u52A8\u4FDD\u5B58 ${branch} \u5206\u652F\u7684\u672A\u63D0\u4EA4\u53D8\u66F4`);
|
|
2800
|
-
}
|
|
2801
|
-
function mergeMainBranch(worktreePath, mainBranch) {
|
|
2802
|
-
try {
|
|
2803
|
-
gitMerge(mainBranch, worktreePath);
|
|
2804
|
-
return false;
|
|
2805
|
-
} catch {
|
|
2806
|
-
if (hasMergeConflict(worktreePath)) {
|
|
2807
|
-
return true;
|
|
2808
|
-
}
|
|
2809
|
-
throw new ClawtError(`\u5408\u5E76 ${mainBranch} \u5931\u8D25`);
|
|
2810
|
-
}
|
|
2811
|
-
}
|
|
2812
|
-
async function handleSync(options) {
|
|
2813
|
-
validateMainWorktree();
|
|
2814
|
-
logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
|
|
2815
|
-
const worktrees = getProjectWorktrees();
|
|
2816
|
-
const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
|
|
2817
|
-
const { path: targetWorktreePath, branch } = worktree;
|
|
2818
|
-
const mainWorktreePath = getGitTopLevel();
|
|
2819
|
-
const mainBranch = getCurrentBranch(mainWorktreePath);
|
|
2820
|
-
if (!isWorkingDirClean(targetWorktreePath)) {
|
|
2821
|
-
autoSaveChanges(targetWorktreePath, branch);
|
|
2822
|
-
}
|
|
2823
|
-
printInfo(MESSAGES.SYNC_MERGING(branch, mainBranch));
|
|
2824
|
-
const hasConflict = mergeMainBranch(targetWorktreePath, mainBranch);
|
|
2825
|
-
if (hasConflict) {
|
|
2826
|
-
printWarning(MESSAGES.SYNC_CONFLICT(targetWorktreePath));
|
|
2827
|
-
return;
|
|
2828
|
-
}
|
|
2829
|
-
const projectName = getProjectName();
|
|
2830
|
-
if (hasSnapshot(projectName, branch)) {
|
|
2831
|
-
removeSnapshot(projectName, branch);
|
|
2832
|
-
logger.info(`\u5DF2\u6E05\u9664\u5206\u652F ${branch} \u7684 validate \u5FEB\u7167`);
|
|
2833
|
-
}
|
|
2834
|
-
printSuccess(MESSAGES.SYNC_SUCCESS(branch, mainBranch));
|
|
2835
|
-
}
|
|
2836
|
-
|
|
2837
3097
|
// src/commands/reset.ts
|
|
2838
3098
|
function registerResetCommand(program2) {
|
|
2839
3099
|
program2.command("reset").description("\u91CD\u7F6E\u4E3B worktree \u5DE5\u4F5C\u533A\u548C\u6682\u5B58\u533A\uFF08\u4FDD\u7559 validate \u5FEB\u7167\uFF09").action(async () => {
|