clawt 2.20.0 → 3.1.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/.claude/agents/docs-sync-updater.md +29 -11
- package/README.md +19 -30
- package/dist/index.js +1127 -222
- package/dist/postinstall.js +73 -8
- package/docs/alias.md +108 -0
- package/docs/completion.md +55 -0
- package/docs/config-file.md +43 -0
- package/docs/config.md +91 -0
- package/docs/create.md +85 -0
- package/docs/init.md +65 -0
- package/docs/list.md +67 -0
- package/docs/log.md +67 -0
- package/docs/merge.md +137 -0
- package/docs/notification.md +94 -0
- package/docs/projects.md +135 -0
- package/docs/remove.md +79 -0
- package/docs/reset.md +35 -0
- package/docs/resume.md +99 -0
- package/docs/run.md +146 -0
- package/docs/spec.md +157 -1906
- package/docs/status.md +298 -0
- package/docs/sync.md +114 -0
- package/docs/update-check.md +95 -0
- package/docs/validate.md +368 -0
- package/package.json +1 -1
- package/src/commands/alias.ts +1 -1
- package/src/commands/create.ts +10 -5
- package/src/commands/init.ts +75 -0
- package/src/commands/list.ts +1 -1
- package/src/commands/merge.ts +11 -4
- package/src/commands/remove.ts +10 -3
- package/src/commands/reset.ts +3 -0
- package/src/commands/resume.ts +1 -1
- package/src/commands/run.ts +9 -3
- package/src/commands/status.ts +14 -5
- package/src/commands/sync.ts +18 -6
- package/src/commands/validate.ts +46 -52
- package/src/constants/branch.ts +3 -0
- package/src/constants/config.ts +1 -1
- package/src/constants/index.ts +14 -2
- package/src/constants/interactive-panel.ts +44 -0
- package/src/constants/messages/completion.ts +1 -1
- package/src/constants/messages/create.ts +3 -0
- package/src/constants/messages/index.ts +4 -0
- package/src/constants/messages/init.ts +18 -0
- package/src/constants/messages/interactive-panel.ts +61 -0
- package/src/constants/messages/remove.ts +2 -0
- package/src/constants/messages/sync.ts +3 -0
- package/src/constants/messages/validate.ts +6 -0
- package/src/constants/paths.ts +3 -0
- package/src/index.ts +2 -0
- package/src/types/command.ts +9 -1
- package/src/types/index.ts +2 -1
- package/src/types/projectConfig.ts +5 -0
- package/src/utils/config.ts +2 -1
- package/src/utils/git.ts +18 -0
- package/src/utils/index.ts +9 -1
- package/src/utils/interactive-panel-render.ts +315 -0
- package/src/utils/interactive-panel.ts +590 -0
- package/src/utils/json.ts +67 -0
- package/src/utils/project-config.ts +77 -0
- package/src/utils/validate-branch.ts +166 -0
- package/src/utils/worktree-matcher.ts +2 -2
- package/src/utils/worktree.ts +6 -2
- package/tests/unit/commands/create.test.ts +20 -16
- package/tests/unit/commands/init.test.ts +146 -0
- package/tests/unit/commands/merge.test.ts +7 -1
- package/tests/unit/commands/remove.test.ts +4 -0
- package/tests/unit/commands/reset.test.ts +2 -0
- package/tests/unit/commands/run.test.ts +2 -0
- package/tests/unit/commands/sync.test.ts +6 -0
- package/tests/unit/commands/validate.test.ts +13 -0
- package/tests/unit/utils/config.test.ts +2 -2
- package/tests/unit/utils/project-config.test.ts +136 -0
- package/tests/unit/utils/update-checker.test.ts +28 -7
- package/tests/unit/utils/validate-branch.test.ts +272 -0
- package/tests/unit/utils/worktree.test.ts +6 -0
package/dist/index.js
CHANGED
|
@@ -16,9 +16,11 @@ var WORKTREES_DIR = join(CLAWT_HOME, "worktrees");
|
|
|
16
16
|
var VALIDATE_SNAPSHOTS_DIR = join(CLAWT_HOME, "validate-snapshots");
|
|
17
17
|
var CLAUDE_PROJECTS_DIR = join(homedir(), ".claude", "projects");
|
|
18
18
|
var UPDATE_CHECK_PATH = join(CLAWT_HOME, "update-check.json");
|
|
19
|
+
var PROJECTS_CONFIG_DIR = join(CLAWT_HOME, "projects");
|
|
19
20
|
|
|
20
21
|
// src/constants/branch.ts
|
|
21
22
|
var INVALID_BRANCH_CHARS = /[\/\\.\s~:*?[\]^]+/g;
|
|
23
|
+
var VALIDATE_BRANCH_PREFIX = "clawt-validate-";
|
|
22
24
|
|
|
23
25
|
// src/constants/messages/common.ts
|
|
24
26
|
var COMMON_MESSAGES = {
|
|
@@ -121,7 +123,9 @@ var RUN_MESSAGES = {
|
|
|
121
123
|
// src/constants/messages/create.ts
|
|
122
124
|
var CREATE_MESSAGES = {
|
|
123
125
|
/** 创建数量参数无效 */
|
|
124
|
-
INVALID_COUNT: (value) => `\u65E0\u6548\u7684\u521B\u5EFA\u6570\u91CF: "${value}"\uFF0C\u8BF7\u8F93\u5165\u6B63\u6574\u6570
|
|
126
|
+
INVALID_COUNT: (value) => `\u65E0\u6548\u7684\u521B\u5EFA\u6570\u91CF: "${value}"\uFF0C\u8BF7\u8F93\u5165\u6B63\u6574\u6570`,
|
|
127
|
+
/** 当前不在主工作分支上的警告 */
|
|
128
|
+
CREATE_WARN_NOT_ON_MAIN_BRANCH: (mainBranch, currentBranch) => `\u5F53\u524D\u4E0D\u5728\u4E3B\u5DE5\u4F5C\u5206\u652F ${mainBranch} \u4E0A\uFF08\u5F53\u524D: ${currentBranch}\uFF09`
|
|
125
129
|
};
|
|
126
130
|
|
|
127
131
|
// src/constants/messages/merge.ts
|
|
@@ -218,7 +222,12 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
|
218
222
|
/** 自动 sync 开始提示 */
|
|
219
223
|
VALIDATE_AUTO_SYNC_START: (branch) => `\u6B63\u5728\u81EA\u52A8\u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch} ...`,
|
|
220
224
|
/** 用户拒绝自动 sync */
|
|
221
|
-
VALIDATE_AUTO_SYNC_DECLINED: (branch) => `\u8BF7\u624B\u52A8\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5
|
|
225
|
+
VALIDATE_AUTO_SYNC_DECLINED: (branch) => `\u8BF7\u624B\u52A8\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`,
|
|
226
|
+
/** 验证分支不存在 */
|
|
227
|
+
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}`,
|
|
228
|
+
/** validate 成功(含验证分支信息) */
|
|
229
|
+
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
|
|
230
|
+
\u53EF\u4EE5\u5F00\u59CB\u9A8C\u8BC1\u4E86`
|
|
222
231
|
};
|
|
223
232
|
|
|
224
233
|
// src/constants/messages/sync.ts
|
|
@@ -243,7 +252,9 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
|
243
252
|
/** sync 交互选择提示 */
|
|
244
253
|
SYNC_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u540C\u6B65\u7684\u5206\u652F",
|
|
245
254
|
/** sync 模糊匹配到多个结果提示 */
|
|
246
|
-
SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A
|
|
255
|
+
SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
|
|
256
|
+
/** sync 后验证分支已重建提示 */
|
|
257
|
+
SYNC_VALIDATE_BRANCH_REBUILT: (validateBranch) => `\u9A8C\u8BC1\u5206\u652F ${validateBranch} \u5DF2\u91CD\u5EFA`
|
|
247
258
|
};
|
|
248
259
|
|
|
249
260
|
// src/constants/messages/resume.ts
|
|
@@ -284,7 +295,9 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
|
284
295
|
REMOVE_PARTIAL_FAILURE: (failures) => `\u4EE5\u4E0B worktree \u79FB\u9664\u5931\u8D25\uFF1A
|
|
285
296
|
${failures.map((f) => ` \u2717 ${f.path}: ${f.error}`).join("\n")}`,
|
|
286
297
|
/** 用户选择保留本地分支 */
|
|
287
|
-
REMOVE_BRANCHES_KEPT: "\u5DF2\u4FDD\u7559\u672C\u5730\u5206\u652F\uFF0C\u53EF\u7A0D\u540E\u4F7F\u7528 git branch -D <\u5206\u652F\u540D> \u624B\u52A8\u5220\u9664"
|
|
298
|
+
REMOVE_BRANCHES_KEPT: "\u5DF2\u4FDD\u7559\u672C\u5730\u5206\u652F\uFF0C\u53EF\u7A0D\u540E\u4F7F\u7528 git branch -D <\u5206\u652F\u540D> \u624B\u52A8\u5220\u9664",
|
|
299
|
+
/** 确认删除本地分支和验证分支 */
|
|
300
|
+
REMOVE_CONFIRM_DELETE_BRANCHES: "\u662F\u5426\u540C\u65F6\u5220\u9664\u5BF9\u5E94\u7684\u672C\u5730\u5206\u652F\u548C\u9A8C\u8BC1\u5206\u652F\uFF1F"
|
|
288
301
|
};
|
|
289
302
|
|
|
290
303
|
// src/constants/messages/reset.ts
|
|
@@ -402,7 +415,7 @@ var PROJECTS_MESSAGES = {
|
|
|
402
415
|
// src/constants/messages/completion.ts
|
|
403
416
|
var COMPLETION_MESSAGES = {
|
|
404
417
|
/** completion 命令的主描述 */
|
|
405
|
-
COMPLETION_COMMAND_DESC: "\
|
|
418
|
+
COMPLETION_COMMAND_DESC: "\u4E3A\u7EC8\u7AEF\u63D0\u4F9B shell \u81EA\u52A8\u8865\u5168\u529F\u80FD\uFF08bash/zsh\uFF09",
|
|
406
419
|
/** bash 子命令描述 */
|
|
407
420
|
COMPLETION_BASH_DESC: "\u8F93\u51FA bash \u81EA\u52A8\u8865\u5168\u811A\u672C",
|
|
408
421
|
/** zsh 子命令描述 */
|
|
@@ -434,6 +447,78 @@ var UPDATE_MESSAGES = {
|
|
|
434
447
|
UPDATE_HINT: (command) => `\u6267\u884C ${command} \u8FDB\u884C\u66F4\u65B0`
|
|
435
448
|
};
|
|
436
449
|
|
|
450
|
+
// src/constants/messages/init.ts
|
|
451
|
+
var INIT_MESSAGES = {
|
|
452
|
+
/** init 成功 */
|
|
453
|
+
INIT_SUCCESS: (branch) => `\u2713 \u9879\u76EE\u521D\u59CB\u5316\u6210\u529F\uFF0C\u4E3B\u5DE5\u4F5C\u5206\u652F\u8BBE\u7F6E\u4E3A: ${branch}`,
|
|
454
|
+
/** init 更新 */
|
|
455
|
+
INIT_UPDATED: (oldBranch, newBranch) => `\u2713 \u5DF2\u5C06\u4E3B\u5DE5\u4F5C\u5206\u652F\u4ECE ${oldBranch} \u66F4\u65B0\u4E3A ${newBranch}`,
|
|
456
|
+
/** init show 查看当前项目完整配置(JSON 格式) */
|
|
457
|
+
INIT_SHOW: (configJson) => `${configJson}`,
|
|
458
|
+
/** init 未初始化 */
|
|
459
|
+
INIT_NOT_INITIALIZED: "\u9879\u76EE\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F",
|
|
460
|
+
/** 项目未初始化(requireProjectConfig 使用) */
|
|
461
|
+
PROJECT_NOT_INITIALIZED: "\u9879\u76EE\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F",
|
|
462
|
+
/** 项目配置缺少 clawtMainWorkBranch 字段 */
|
|
463
|
+
PROJECT_CONFIG_MISSING_BRANCH: "\u9879\u76EE\u914D\u7F6E\u7F3A\u5C11\u4E3B\u5DE5\u4F5C\u5206\u652F\u4FE1\u606F\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F"
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// src/constants/messages/interactive-panel.ts
|
|
467
|
+
import chalk from "chalk";
|
|
468
|
+
|
|
469
|
+
// src/constants/interactive-panel.ts
|
|
470
|
+
var PANEL_REFRESH_INTERVAL_MS = 5e3;
|
|
471
|
+
var PANEL_COUNTDOWN_INTERVAL_MS = 1e3;
|
|
472
|
+
var SELECTED_INDICATOR = "\u25B6";
|
|
473
|
+
var UNSELECTED_INDICATOR = " ";
|
|
474
|
+
var KEY_ARROW_UP = "\x1B[A";
|
|
475
|
+
var KEY_ARROW_DOWN = "\x1B[B";
|
|
476
|
+
var KEY_CTRL_C = 3;
|
|
477
|
+
var PANEL_SHORTCUT_KEYS = {
|
|
478
|
+
/** 验证 */
|
|
479
|
+
VALIDATE: "v",
|
|
480
|
+
/** 合并 */
|
|
481
|
+
MERGE: "m",
|
|
482
|
+
/** 删除 */
|
|
483
|
+
DELETE: "d",
|
|
484
|
+
/** 恢复 */
|
|
485
|
+
RESUME: "r",
|
|
486
|
+
/** 同步 */
|
|
487
|
+
SYNC: "s",
|
|
488
|
+
/** 手动刷新 */
|
|
489
|
+
REFRESH: "f",
|
|
490
|
+
/** 退出 */
|
|
491
|
+
QUIT: "q"
|
|
492
|
+
};
|
|
493
|
+
var PANEL_DATE_SEPARATOR_PREFIX = "\u2550\u2550\u2550\u2550";
|
|
494
|
+
var PANEL_FIXED_ROWS = 4;
|
|
495
|
+
|
|
496
|
+
// src/constants/messages/interactive-panel.ts
|
|
497
|
+
var SHORTCUT_LABELS = {
|
|
498
|
+
VALIDATE: "\u9A8C\u8BC1",
|
|
499
|
+
MERGE: "\u5408\u5E76",
|
|
500
|
+
DELETE: "\u5220\u9664",
|
|
501
|
+
RESUME: "\u6062\u590D",
|
|
502
|
+
SYNC: "\u540C\u6B65",
|
|
503
|
+
REFRESH: "\u5237\u65B0",
|
|
504
|
+
QUIT: "\u9000\u51FA"
|
|
505
|
+
};
|
|
506
|
+
var PANEL_FOOTER_SHORTCUTS = Object.entries(SHORTCUT_LABELS).map(([key, label]) => `[${chalk.cyan(PANEL_SHORTCUT_KEYS[key])}]${label}`).join(" ");
|
|
507
|
+
var PANEL_FOOTER_COUNTDOWN = (seconds) => chalk.gray(`(${seconds}s \u540E\u5237\u65B0)`);
|
|
508
|
+
var PANEL_OVERFLOW_DOWN_HINT = chalk.gray("\u2193 \u66F4\u591A worktree...");
|
|
509
|
+
var PANEL_OVERFLOW_UP_HINT = chalk.gray("\u2191 \u66F4\u591A worktree...");
|
|
510
|
+
var PANEL_SNAPSHOT_SUMMARY = (total, orphaned) => {
|
|
511
|
+
const base = `\u5FEB\u7167: ${total} \u4E2A`;
|
|
512
|
+
if (orphaned > 0) {
|
|
513
|
+
return `${base}\uFF08${chalk.yellow(`${orphaned} \u4E2A\u5B64\u7ACB`)}\uFF09`;
|
|
514
|
+
}
|
|
515
|
+
return base;
|
|
516
|
+
};
|
|
517
|
+
var PANEL_NO_WORKTREES = "(\u65E0\u6D3B\u8DC3 worktree)";
|
|
518
|
+
var PANEL_PRESS_ENTER_TO_RETURN = chalk.gray("\n\u6309 Enter \u8FD4\u56DE\u9762\u677F...");
|
|
519
|
+
var PANEL_NOT_TTY = "\u4EA4\u4E92\u5F0F\u9762\u677F\u9700\u8981 TTY \u7EC8\u7AEF\u73AF\u5883\uFF0C\u8BF7\u76F4\u63A5\u5728\u7EC8\u7AEF\u4E2D\u8FD0\u884C clawt status -i";
|
|
520
|
+
var PANEL_TITLE = (projectName) => chalk.bold.cyan(`\u9879\u76EE\u72B6\u6001\u603B\u89C8: ${projectName}`);
|
|
521
|
+
|
|
437
522
|
// src/constants/messages/index.ts
|
|
438
523
|
var MESSAGES = {
|
|
439
524
|
...COMMON_MESSAGES,
|
|
@@ -449,7 +534,8 @@ var MESSAGES = {
|
|
|
449
534
|
...STATUS_MESSAGES,
|
|
450
535
|
...ALIAS_MESSAGES,
|
|
451
536
|
...PROJECTS_MESSAGES,
|
|
452
|
-
...COMPLETION_MESSAGES
|
|
537
|
+
...COMPLETION_MESSAGES,
|
|
538
|
+
...INIT_MESSAGES
|
|
453
539
|
};
|
|
454
540
|
|
|
455
541
|
// src/constants/exitCodes.ts
|
|
@@ -567,14 +653,14 @@ var CLEAR_SCREEN = "\x1B[2J";
|
|
|
567
653
|
var CURSOR_HOME = "\x1B[H";
|
|
568
654
|
|
|
569
655
|
// src/constants/prompt.ts
|
|
570
|
-
import
|
|
656
|
+
import chalk2 from "chalk";
|
|
571
657
|
var SELECT_ALL_NAME = "__select_all__";
|
|
572
658
|
var SELECT_ALL_LABEL = "[select-all]";
|
|
573
659
|
var GROUP_SELECT_ALL_PREFIX = "__group_select_all_";
|
|
574
660
|
var GROUP_SELECT_ALL_LABEL = (dateLabel) => `[select-all: ${dateLabel}]`;
|
|
575
|
-
var GROUP_SEPARATOR_LABEL = (dateLabel, relativeTime) => `\u2550\u2550\u2550\u2550 ${
|
|
661
|
+
var GROUP_SEPARATOR_LABEL = (dateLabel, relativeTime) => `\u2550\u2550\u2550\u2550 ${chalk2.bold.hex("#FF8C00")(dateLabel)}\uFF08${chalk2.hex("#FF8C00")(relativeTime)}\uFF09 \u2550\u2550\u2550\u2550`;
|
|
576
662
|
var UNKNOWN_DATE_GROUP = "\u672A\u77E5\u65E5\u671F";
|
|
577
|
-
var UNKNOWN_DATE_SEPARATOR_LABEL = `\u2550\u2550\u2550\u2550 ${
|
|
663
|
+
var UNKNOWN_DATE_SEPARATOR_LABEL = `\u2550\u2550\u2550\u2550 ${chalk2.bold.hex("#FF8C00")("\u672A\u77E5\u65E5\u671F")} \u2550\u2550\u2550\u2550`;
|
|
578
664
|
|
|
579
665
|
// src/errors/index.ts
|
|
580
666
|
var ClawtError = class extends Error {
|
|
@@ -594,7 +680,7 @@ var ClawtError = class extends Error {
|
|
|
594
680
|
// src/logger/index.ts
|
|
595
681
|
import winston from "winston";
|
|
596
682
|
import DailyRotateFile from "winston-daily-rotate-file";
|
|
597
|
-
import
|
|
683
|
+
import chalk3 from "chalk";
|
|
598
684
|
import { existsSync, mkdirSync } from "fs";
|
|
599
685
|
if (!existsSync(LOGS_DIR)) {
|
|
600
686
|
mkdirSync(LOGS_DIR, { recursive: true });
|
|
@@ -619,13 +705,13 @@ var logger = winston.createLogger({
|
|
|
619
705
|
transports: [dailyRotateTransport]
|
|
620
706
|
});
|
|
621
707
|
var LEVEL_COLORS = {
|
|
622
|
-
error:
|
|
623
|
-
warn:
|
|
624
|
-
info:
|
|
625
|
-
debug:
|
|
708
|
+
error: chalk3.red,
|
|
709
|
+
warn: chalk3.yellow,
|
|
710
|
+
info: chalk3.cyan,
|
|
711
|
+
debug: chalk3.gray
|
|
626
712
|
};
|
|
627
713
|
function colorizeLevel(level) {
|
|
628
|
-
const colorFn = LEVEL_COLORS[level] ||
|
|
714
|
+
const colorFn = LEVEL_COLORS[level] || chalk3.white;
|
|
629
715
|
return colorFn(level.toUpperCase().padEnd(5));
|
|
630
716
|
}
|
|
631
717
|
function enableConsoleTransport() {
|
|
@@ -636,7 +722,7 @@ function enableConsoleTransport() {
|
|
|
636
722
|
return;
|
|
637
723
|
}
|
|
638
724
|
const consoleFormat = winston.format.printf(({ level, message, timestamp }) => {
|
|
639
|
-
return `${
|
|
725
|
+
return `${chalk3.gray(timestamp)} ${colorizeLevel(level)} ${message}`;
|
|
640
726
|
});
|
|
641
727
|
const consoleTransport = new winston.transports.Console({
|
|
642
728
|
level: "debug",
|
|
@@ -902,24 +988,30 @@ function getBranchCreatedAt(branchName, cwd) {
|
|
|
902
988
|
return null;
|
|
903
989
|
}
|
|
904
990
|
}
|
|
991
|
+
function gitCheckout(branchName, cwd) {
|
|
992
|
+
execCommand(`git checkout ${branchName}`, { cwd });
|
|
993
|
+
}
|
|
994
|
+
function createBranch(branchName, cwd) {
|
|
995
|
+
execCommand(`git branch ${branchName}`, { cwd });
|
|
996
|
+
}
|
|
905
997
|
|
|
906
998
|
// src/utils/formatter.ts
|
|
907
|
-
import
|
|
999
|
+
import chalk4 from "chalk";
|
|
908
1000
|
import { createInterface } from "readline";
|
|
909
1001
|
function printSuccess(message) {
|
|
910
|
-
console.log(
|
|
1002
|
+
console.log(chalk4.green(message));
|
|
911
1003
|
}
|
|
912
1004
|
function printError(message) {
|
|
913
|
-
console.error(
|
|
1005
|
+
console.error(chalk4.red(`\u2717 ${message}`));
|
|
914
1006
|
}
|
|
915
1007
|
function printWarning(message) {
|
|
916
|
-
console.log(
|
|
1008
|
+
console.log(chalk4.yellow(`\u26A0 ${message}`));
|
|
917
1009
|
}
|
|
918
1010
|
function printInfo(message) {
|
|
919
1011
|
console.log(message);
|
|
920
1012
|
}
|
|
921
1013
|
function printHint(message) {
|
|
922
|
-
console.log(
|
|
1014
|
+
console.log(chalk4.hex("#FF8C00")(message));
|
|
923
1015
|
}
|
|
924
1016
|
function printSeparator() {
|
|
925
1017
|
console.log(MESSAGES.SEPARATOR);
|
|
@@ -940,7 +1032,7 @@ function confirmAction(question) {
|
|
|
940
1032
|
});
|
|
941
1033
|
}
|
|
942
1034
|
function confirmDestructiveAction(dangerousCommand, description) {
|
|
943
|
-
printWarning(`\u5373\u5C06\u6267\u884C ${
|
|
1035
|
+
printWarning(`\u5373\u5C06\u6267\u884C ${chalk4.red.bold(dangerousCommand)}\uFF0C${description}`);
|
|
944
1036
|
return confirmAction("\u662F\u5426\u7EE7\u7EED\uFF1F");
|
|
945
1037
|
}
|
|
946
1038
|
function isWorktreeIdle(status) {
|
|
@@ -948,17 +1040,17 @@ function isWorktreeIdle(status) {
|
|
|
948
1040
|
}
|
|
949
1041
|
function formatWorktreeStatus(status) {
|
|
950
1042
|
const parts = [];
|
|
951
|
-
parts.push(
|
|
1043
|
+
parts.push(chalk4.yellow(`${status.commitCount} \u4E2A\u63D0\u4EA4`));
|
|
952
1044
|
if (status.insertions === 0 && status.deletions === 0) {
|
|
953
1045
|
parts.push("\u65E0\u53D8\u66F4");
|
|
954
1046
|
} else {
|
|
955
1047
|
const diffParts = [];
|
|
956
|
-
diffParts.push(
|
|
957
|
-
diffParts.push(
|
|
1048
|
+
diffParts.push(chalk4.green(`+${status.insertions}`));
|
|
1049
|
+
diffParts.push(chalk4.red(`-${status.deletions}`));
|
|
958
1050
|
parts.push(diffParts.join(" "));
|
|
959
1051
|
}
|
|
960
1052
|
if (status.hasDirtyFiles) {
|
|
961
|
-
parts.push(
|
|
1053
|
+
parts.push(chalk4.gray("(\u672A\u63D0\u4EA4\u4FEE\u6539)"));
|
|
962
1054
|
}
|
|
963
1055
|
return parts.join(" ");
|
|
964
1056
|
}
|
|
@@ -1076,8 +1168,8 @@ function validateClaudeCodeInstalled() {
|
|
|
1076
1168
|
}
|
|
1077
1169
|
|
|
1078
1170
|
// src/utils/worktree.ts
|
|
1079
|
-
import { join as
|
|
1080
|
-
import { existsSync as
|
|
1171
|
+
import { join as join4 } from "path";
|
|
1172
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
|
|
1081
1173
|
|
|
1082
1174
|
// src/utils/fs.ts
|
|
1083
1175
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmdirSync, statSync } from "fs";
|
|
@@ -1118,10 +1210,152 @@ function calculateDirSize(dirPath) {
|
|
|
1118
1210
|
return totalSize;
|
|
1119
1211
|
}
|
|
1120
1212
|
|
|
1213
|
+
// src/utils/validate-branch.ts
|
|
1214
|
+
import Enquirer from "enquirer";
|
|
1215
|
+
|
|
1216
|
+
// src/utils/project-config.ts
|
|
1217
|
+
import { existsSync as existsSync3, readFileSync, writeFileSync } from "fs";
|
|
1218
|
+
import { join as join3 } from "path";
|
|
1219
|
+
function getProjectConfigPath(projectName) {
|
|
1220
|
+
return join3(PROJECTS_CONFIG_DIR, projectName, "config.json");
|
|
1221
|
+
}
|
|
1222
|
+
function loadProjectConfig() {
|
|
1223
|
+
const projectName = getProjectName();
|
|
1224
|
+
const configPath = getProjectConfigPath(projectName);
|
|
1225
|
+
if (!existsSync3(configPath)) {
|
|
1226
|
+
return null;
|
|
1227
|
+
}
|
|
1228
|
+
try {
|
|
1229
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
1230
|
+
return JSON.parse(raw);
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
logger.warn(`\u9879\u76EE\u914D\u7F6E\u6587\u4EF6\u89E3\u6790\u5931\u8D25: ${error}`);
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
function saveProjectConfig(config2) {
|
|
1237
|
+
const projectName = getProjectName();
|
|
1238
|
+
const configPath = getProjectConfigPath(projectName);
|
|
1239
|
+
const projectDir = join3(PROJECTS_CONFIG_DIR, projectName);
|
|
1240
|
+
ensureDir(projectDir);
|
|
1241
|
+
writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf-8");
|
|
1242
|
+
logger.info(`\u9879\u76EE\u914D\u7F6E\u5DF2\u4FDD\u5B58: ${configPath}`);
|
|
1243
|
+
}
|
|
1244
|
+
function requireProjectConfig() {
|
|
1245
|
+
const config2 = loadProjectConfig();
|
|
1246
|
+
if (!config2) {
|
|
1247
|
+
throw new ClawtError(MESSAGES.PROJECT_NOT_INITIALIZED);
|
|
1248
|
+
}
|
|
1249
|
+
if (!config2.clawtMainWorkBranch) {
|
|
1250
|
+
throw new ClawtError(MESSAGES.PROJECT_CONFIG_MISSING_BRANCH);
|
|
1251
|
+
}
|
|
1252
|
+
return config2;
|
|
1253
|
+
}
|
|
1254
|
+
function getMainWorkBranch() {
|
|
1255
|
+
const config2 = requireProjectConfig();
|
|
1256
|
+
return config2.clawtMainWorkBranch;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// src/utils/validate-branch.ts
|
|
1260
|
+
function getValidateBranchName(branchName) {
|
|
1261
|
+
return `${VALIDATE_BRANCH_PREFIX}${branchName}`;
|
|
1262
|
+
}
|
|
1263
|
+
function createValidateBranch(branchName, cwd) {
|
|
1264
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
1265
|
+
if (checkBranchExists(validateBranchName, cwd)) {
|
|
1266
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA: ${validateBranchName}`);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
createBranch(validateBranchName, cwd);
|
|
1270
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u521B\u5EFA: ${validateBranchName}`);
|
|
1271
|
+
}
|
|
1272
|
+
function deleteValidateBranch(branchName, cwd) {
|
|
1273
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
1274
|
+
if (!checkBranchExists(validateBranchName, cwd)) {
|
|
1275
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u5220\u9664: ${validateBranchName}`);
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
deleteBranch(validateBranchName, cwd);
|
|
1279
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u5220\u9664: ${validateBranchName}`);
|
|
1280
|
+
}
|
|
1281
|
+
async function rebuildValidateBranch(branchName, cwd) {
|
|
1282
|
+
const mainBranch = getMainWorkBranch();
|
|
1283
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
1284
|
+
if (currentBranch === mainBranch) {
|
|
1285
|
+
} else if (currentBranch.startsWith(VALIDATE_BRANCH_PREFIX)) {
|
|
1286
|
+
gitResetHard(cwd);
|
|
1287
|
+
gitCleanForce(cwd);
|
|
1288
|
+
gitCheckout(mainBranch, cwd);
|
|
1289
|
+
} else {
|
|
1290
|
+
if (!isWorkingDirClean(cwd)) {
|
|
1291
|
+
await handleDirtyWorkingDir(cwd);
|
|
1292
|
+
}
|
|
1293
|
+
gitCheckout(mainBranch, cwd);
|
|
1294
|
+
}
|
|
1295
|
+
deleteValidateBranch(branchName, cwd);
|
|
1296
|
+
createValidateBranch(branchName, cwd);
|
|
1297
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u91CD\u5EFA: ${getValidateBranchName(branchName)}`);
|
|
1298
|
+
}
|
|
1299
|
+
async function handleDirtyWorkingDir(cwd) {
|
|
1300
|
+
printWarning("\u5F53\u524D\u5206\u652F\u6709\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF0C\u8BF7\u9009\u62E9\u5904\u7406\u65B9\u5F0F\uFF1A\n");
|
|
1301
|
+
const choice = await new Enquirer.Select({
|
|
1302
|
+
message: "\u9009\u62E9\u5904\u7406\u65B9\u5F0F",
|
|
1303
|
+
choices: [
|
|
1304
|
+
{
|
|
1305
|
+
name: "reset",
|
|
1306
|
+
message: "reset - \u4E22\u5F03\u6240\u6709\u66F4\u6539 (git reset --hard HEAD && git clean -fd)"
|
|
1307
|
+
},
|
|
1308
|
+
{
|
|
1309
|
+
name: "stash",
|
|
1310
|
+
message: "stash - \u6682\u5B58\u66F4\u6539 (git add . && git stash)"
|
|
1311
|
+
},
|
|
1312
|
+
{
|
|
1313
|
+
name: "exit",
|
|
1314
|
+
message: "exit - \u9000\u51FA\uFF0C\u624B\u52A8\u5904\u7406"
|
|
1315
|
+
}
|
|
1316
|
+
],
|
|
1317
|
+
initial: 0
|
|
1318
|
+
}).run();
|
|
1319
|
+
if (choice === "exit") {
|
|
1320
|
+
throw new ClawtError("\u7528\u6237\u9009\u62E9\u9000\u51FA\uFF0C\u8BF7\u624B\u52A8\u5904\u7406\u5DE5\u4F5C\u533A\u66F4\u6539\u540E\u91CD\u8BD5");
|
|
1321
|
+
}
|
|
1322
|
+
if (choice === "reset") {
|
|
1323
|
+
gitResetHard(cwd);
|
|
1324
|
+
gitCleanForce(cwd);
|
|
1325
|
+
} else if (choice === "stash") {
|
|
1326
|
+
gitAddAll(cwd);
|
|
1327
|
+
gitStashPush("clawt:auto-stash", cwd);
|
|
1328
|
+
}
|
|
1329
|
+
if (!isWorkingDirClean(cwd)) {
|
|
1330
|
+
throw new ClawtError("\u5DE5\u4F5C\u533A\u4ECD\u7136\u4E0D\u5E72\u51C0\uFF0C\u8BF7\u624B\u52A8\u5904\u7406");
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
async function ensureOnMainWorkBranch(cwd) {
|
|
1334
|
+
const mainBranch = getMainWorkBranch();
|
|
1335
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
1336
|
+
if (currentBranch === mainBranch) {
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
if (currentBranch.startsWith(VALIDATE_BRANCH_PREFIX)) {
|
|
1340
|
+
logger.info(`\u5F53\u524D\u5728\u9A8C\u8BC1\u5206\u652F ${currentBranch} \u4E0A\uFF0C\u81EA\u52A8\u5207\u56DE\u4E3B\u5DE5\u4F5C\u5206\u652F ${mainBranch}`);
|
|
1341
|
+
if (!isWorkingDirClean(cwd)) {
|
|
1342
|
+
gitResetHard(cwd);
|
|
1343
|
+
gitCleanForce(cwd);
|
|
1344
|
+
}
|
|
1345
|
+
gitCheckout(mainBranch, cwd);
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
logger.info(`\u5F53\u524D\u5728\u5206\u652F ${currentBranch} \u4E0A\uFF0C\u9700\u5207\u6362\u5230\u4E3B\u5DE5\u4F5C\u5206\u652F ${mainBranch}`);
|
|
1349
|
+
if (!isWorkingDirClean(cwd)) {
|
|
1350
|
+
await handleDirtyWorkingDir(cwd);
|
|
1351
|
+
}
|
|
1352
|
+
gitCheckout(mainBranch, cwd);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1121
1355
|
// src/utils/worktree.ts
|
|
1122
1356
|
function getProjectWorktreeDir() {
|
|
1123
1357
|
const projectName = getProjectName();
|
|
1124
|
-
return
|
|
1358
|
+
return join4(WORKTREES_DIR, projectName);
|
|
1125
1359
|
}
|
|
1126
1360
|
function createWorktrees(branchName, count) {
|
|
1127
1361
|
const sanitized = sanitizeBranchName(branchName);
|
|
@@ -1131,8 +1365,9 @@ function createWorktrees(branchName, count) {
|
|
|
1131
1365
|
ensureDir(projectDir);
|
|
1132
1366
|
const results = [];
|
|
1133
1367
|
for (const name of branchNames) {
|
|
1134
|
-
const worktreePath =
|
|
1368
|
+
const worktreePath = join4(projectDir, name);
|
|
1135
1369
|
createWorktree(name, worktreePath);
|
|
1370
|
+
createValidateBranch(name);
|
|
1136
1371
|
results.push({ path: worktreePath, branch: name });
|
|
1137
1372
|
logger.info(`worktree \u521B\u5EFA\u5B8C\u6210: ${worktreePath} (\u5206\u652F: ${name})`);
|
|
1138
1373
|
}
|
|
@@ -1144,8 +1379,9 @@ function createWorktreesByBranches(branchNames) {
|
|
|
1144
1379
|
ensureDir(projectDir);
|
|
1145
1380
|
const results = [];
|
|
1146
1381
|
for (const name of branchNames) {
|
|
1147
|
-
const worktreePath =
|
|
1382
|
+
const worktreePath = join4(projectDir, name);
|
|
1148
1383
|
createWorktree(name, worktreePath);
|
|
1384
|
+
createValidateBranch(name);
|
|
1149
1385
|
results.push({ path: worktreePath, branch: name });
|
|
1150
1386
|
logger.info(`worktree \u521B\u5EFA\u5B8C\u6210: ${worktreePath} (\u5206\u652F: ${name})`);
|
|
1151
1387
|
}
|
|
@@ -1153,7 +1389,7 @@ function createWorktreesByBranches(branchNames) {
|
|
|
1153
1389
|
}
|
|
1154
1390
|
function getProjectWorktrees() {
|
|
1155
1391
|
const projectDir = getProjectWorktreeDir();
|
|
1156
|
-
if (!
|
|
1392
|
+
if (!existsSync4(projectDir)) {
|
|
1157
1393
|
return [];
|
|
1158
1394
|
}
|
|
1159
1395
|
const worktreeListOutput = gitWorktreeList();
|
|
@@ -1166,7 +1402,7 @@ function getProjectWorktrees() {
|
|
|
1166
1402
|
if (!entry.isDirectory()) {
|
|
1167
1403
|
continue;
|
|
1168
1404
|
}
|
|
1169
|
-
const fullPath =
|
|
1405
|
+
const fullPath = join4(projectDir, entry.name);
|
|
1170
1406
|
if (registeredPaths.has(fullPath)) {
|
|
1171
1407
|
worktrees.push({
|
|
1172
1408
|
path: fullPath,
|
|
@@ -1181,6 +1417,7 @@ function cleanupWorktrees(worktrees) {
|
|
|
1181
1417
|
try {
|
|
1182
1418
|
removeWorktreeByPath(wt.path);
|
|
1183
1419
|
deleteBranch(wt.branch);
|
|
1420
|
+
deleteValidateBranch(wt.branch);
|
|
1184
1421
|
logger.info(`\u5DF2\u6E05\u7406 worktree \u548C\u5206\u652F: ${wt.branch}`);
|
|
1185
1422
|
} catch (error) {
|
|
1186
1423
|
logger.error(`\u6E05\u7406 worktree \u5931\u8D25: ${wt.path} - ${error}`);
|
|
@@ -1203,13 +1440,13 @@ function getWorktreeStatus(worktree) {
|
|
|
1203
1440
|
}
|
|
1204
1441
|
|
|
1205
1442
|
// src/utils/config.ts
|
|
1206
|
-
import { existsSync as
|
|
1443
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1207
1444
|
function loadConfig() {
|
|
1208
|
-
if (!
|
|
1445
|
+
if (!existsSync5(CONFIG_PATH)) {
|
|
1209
1446
|
return { ...DEFAULT_CONFIG };
|
|
1210
1447
|
}
|
|
1211
1448
|
try {
|
|
1212
|
-
const raw =
|
|
1449
|
+
const raw = readFileSync2(CONFIG_PATH, "utf-8");
|
|
1213
1450
|
return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
|
|
1214
1451
|
} catch {
|
|
1215
1452
|
logger.warn(MESSAGES.CONFIG_CORRUPTED);
|
|
@@ -1218,13 +1455,13 @@ function loadConfig() {
|
|
|
1218
1455
|
}
|
|
1219
1456
|
}
|
|
1220
1457
|
function writeConfig(config2) {
|
|
1221
|
-
|
|
1458
|
+
writeFileSync2(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
|
|
1222
1459
|
}
|
|
1223
1460
|
function writeDefaultConfig() {
|
|
1224
1461
|
writeConfig(DEFAULT_CONFIG);
|
|
1225
1462
|
}
|
|
1226
1463
|
function saveConfig(config2) {
|
|
1227
|
-
|
|
1464
|
+
writeFileSync2(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
|
|
1228
1465
|
}
|
|
1229
1466
|
function getConfigValue(key) {
|
|
1230
1467
|
const config2 = loadConfig();
|
|
@@ -1234,6 +1471,7 @@ function ensureClawtDirs() {
|
|
|
1234
1471
|
ensureDir(CLAWT_HOME);
|
|
1235
1472
|
ensureDir(LOGS_DIR);
|
|
1236
1473
|
ensureDir(WORKTREES_DIR);
|
|
1474
|
+
ensureDir(PROJECTS_CONFIG_DIR);
|
|
1237
1475
|
}
|
|
1238
1476
|
function parseConcurrency(optionValue, configValue) {
|
|
1239
1477
|
if (optionValue === void 0) {
|
|
@@ -1247,18 +1485,18 @@ function parseConcurrency(optionValue, configValue) {
|
|
|
1247
1485
|
}
|
|
1248
1486
|
|
|
1249
1487
|
// src/utils/prompt.ts
|
|
1250
|
-
import
|
|
1488
|
+
import Enquirer2 from "enquirer";
|
|
1251
1489
|
|
|
1252
1490
|
// src/utils/claude.ts
|
|
1253
1491
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1254
|
-
import { existsSync as
|
|
1255
|
-
import { join as
|
|
1492
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3 } from "fs";
|
|
1493
|
+
import { join as join5 } from "path";
|
|
1256
1494
|
|
|
1257
1495
|
// src/utils/terminal.ts
|
|
1258
1496
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1259
|
-
import { existsSync as
|
|
1497
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1260
1498
|
function isITerm2Installed() {
|
|
1261
|
-
return
|
|
1499
|
+
return existsSync6(ITERM2_APP_PATH);
|
|
1262
1500
|
}
|
|
1263
1501
|
function detectTerminalApp() {
|
|
1264
1502
|
const configured = getConfigValue("terminalApp");
|
|
@@ -1331,8 +1569,8 @@ function encodeClaudeProjectPath(absolutePath) {
|
|
|
1331
1569
|
}
|
|
1332
1570
|
function hasClaudeSessionHistory(worktreePath) {
|
|
1333
1571
|
const encodedName = encodeClaudeProjectPath(worktreePath);
|
|
1334
|
-
const projectDir =
|
|
1335
|
-
if (!
|
|
1572
|
+
const projectDir = join5(CLAUDE_PROJECTS_DIR, encodedName);
|
|
1573
|
+
if (!existsSync7(projectDir)) {
|
|
1336
1574
|
return false;
|
|
1337
1575
|
}
|
|
1338
1576
|
const entries = readdirSync3(projectDir);
|
|
@@ -1388,20 +1626,20 @@ function launchInteractiveClaudeInNewTerminal(worktree, hasPreviousSession) {
|
|
|
1388
1626
|
}
|
|
1389
1627
|
|
|
1390
1628
|
// src/utils/validate-snapshot.ts
|
|
1391
|
-
import { join as
|
|
1392
|
-
import { existsSync as
|
|
1629
|
+
import { join as join6 } from "path";
|
|
1630
|
+
import { existsSync as existsSync8, readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync, readdirSync as readdirSync4, rmdirSync as rmdirSync2, statSync as statSync2 } from "fs";
|
|
1393
1631
|
function getSnapshotPath(projectName, branchName) {
|
|
1394
|
-
return
|
|
1632
|
+
return join6(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.tree`);
|
|
1395
1633
|
}
|
|
1396
1634
|
function getSnapshotHeadPath(projectName, branchName) {
|
|
1397
|
-
return
|
|
1635
|
+
return join6(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.head`);
|
|
1398
1636
|
}
|
|
1399
1637
|
function hasSnapshot(projectName, branchName) {
|
|
1400
|
-
return
|
|
1638
|
+
return existsSync8(getSnapshotPath(projectName, branchName));
|
|
1401
1639
|
}
|
|
1402
1640
|
function getSnapshotModifiedTime(projectName, branchName) {
|
|
1403
1641
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1404
|
-
if (!
|
|
1642
|
+
if (!existsSync8(snapshotPath)) return null;
|
|
1405
1643
|
const stat = statSync2(snapshotPath);
|
|
1406
1644
|
return stat.mtime.toISOString();
|
|
1407
1645
|
}
|
|
@@ -1409,47 +1647,47 @@ function readSnapshot(projectName, branchName) {
|
|
|
1409
1647
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1410
1648
|
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
1411
1649
|
logger.debug(`\u8BFB\u53D6 validate \u5FEB\u7167: ${snapshotPath}`);
|
|
1412
|
-
const treeHash =
|
|
1413
|
-
const headCommitHash =
|
|
1650
|
+
const treeHash = existsSync8(snapshotPath) ? readFileSync3(snapshotPath, "utf-8").trim() : "";
|
|
1651
|
+
const headCommitHash = existsSync8(headPath) ? readFileSync3(headPath, "utf-8").trim() : "";
|
|
1414
1652
|
return { treeHash, headCommitHash };
|
|
1415
1653
|
}
|
|
1416
1654
|
function writeSnapshot(projectName, branchName, treeHash, headCommitHash) {
|
|
1417
1655
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1418
1656
|
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
1419
|
-
const snapshotDir =
|
|
1657
|
+
const snapshotDir = join6(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1420
1658
|
ensureDir(snapshotDir);
|
|
1421
|
-
|
|
1422
|
-
|
|
1659
|
+
writeFileSync3(snapshotPath, treeHash, "utf-8");
|
|
1660
|
+
writeFileSync3(headPath, headCommitHash, "utf-8");
|
|
1423
1661
|
logger.info(`\u5DF2\u4FDD\u5B58 validate \u5FEB\u7167: ${snapshotPath}, ${headPath}`);
|
|
1424
1662
|
}
|
|
1425
1663
|
function removeSnapshot(projectName, branchName) {
|
|
1426
1664
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1427
1665
|
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
1428
|
-
if (
|
|
1666
|
+
if (existsSync8(snapshotPath)) {
|
|
1429
1667
|
unlinkSync(snapshotPath);
|
|
1430
1668
|
logger.info(`\u5DF2\u5220\u9664 validate \u5FEB\u7167: ${snapshotPath}`);
|
|
1431
1669
|
}
|
|
1432
|
-
if (
|
|
1670
|
+
if (existsSync8(headPath)) {
|
|
1433
1671
|
unlinkSync(headPath);
|
|
1434
1672
|
logger.info(`\u5DF2\u5220\u9664 validate \u5FEB\u7167: ${headPath}`);
|
|
1435
1673
|
}
|
|
1436
1674
|
}
|
|
1437
1675
|
function getProjectSnapshotBranches(projectName) {
|
|
1438
|
-
const projectDir =
|
|
1439
|
-
if (!
|
|
1676
|
+
const projectDir = join6(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1677
|
+
if (!existsSync8(projectDir)) {
|
|
1440
1678
|
return [];
|
|
1441
1679
|
}
|
|
1442
1680
|
const files = readdirSync4(projectDir);
|
|
1443
1681
|
return files.filter((f) => f.endsWith(".tree")).map((f) => f.replace(/\.tree$/, ""));
|
|
1444
1682
|
}
|
|
1445
1683
|
function removeProjectSnapshots(projectName) {
|
|
1446
|
-
const projectDir =
|
|
1447
|
-
if (!
|
|
1684
|
+
const projectDir = join6(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1685
|
+
if (!existsSync8(projectDir)) {
|
|
1448
1686
|
return;
|
|
1449
1687
|
}
|
|
1450
1688
|
const files = readdirSync4(projectDir);
|
|
1451
1689
|
for (const file of files) {
|
|
1452
|
-
unlinkSync(
|
|
1690
|
+
unlinkSync(join6(projectDir, file));
|
|
1453
1691
|
}
|
|
1454
1692
|
try {
|
|
1455
1693
|
rmdirSync2(projectDir);
|
|
@@ -1459,7 +1697,7 @@ function removeProjectSnapshots(projectName) {
|
|
|
1459
1697
|
}
|
|
1460
1698
|
|
|
1461
1699
|
// src/utils/worktree-matcher.ts
|
|
1462
|
-
import
|
|
1700
|
+
import Enquirer3 from "enquirer";
|
|
1463
1701
|
import { statSync as statSync3 } from "fs";
|
|
1464
1702
|
function findExactMatch(worktrees, branchName) {
|
|
1465
1703
|
return worktrees.find((wt) => wt.branch === branchName);
|
|
@@ -1469,7 +1707,7 @@ function findFuzzyMatches(worktrees, keyword) {
|
|
|
1469
1707
|
return worktrees.filter((wt) => wt.branch.toLowerCase().includes(lowerKeyword));
|
|
1470
1708
|
}
|
|
1471
1709
|
async function promptSelectBranch(worktrees, message) {
|
|
1472
|
-
const selectedBranch = await new
|
|
1710
|
+
const selectedBranch = await new Enquirer3.Select({
|
|
1473
1711
|
message,
|
|
1474
1712
|
choices: worktrees.map((wt) => ({
|
|
1475
1713
|
name: wt.branch,
|
|
@@ -1487,7 +1725,7 @@ async function promptMultiSelectBranches(worktrees, message) {
|
|
|
1487
1725
|
{ name: SELECT_ALL_NAME, message: SELECT_ALL_LABEL },
|
|
1488
1726
|
...branchChoices
|
|
1489
1727
|
];
|
|
1490
|
-
const MultiSelect =
|
|
1728
|
+
const MultiSelect = Enquirer3.MultiSelect;
|
|
1491
1729
|
class MultiSelectWithSelectAll extends MultiSelect {
|
|
1492
1730
|
space() {
|
|
1493
1731
|
if (!this.focused) return;
|
|
@@ -1642,7 +1880,7 @@ async function promptGroupedMultiSelectBranches(worktrees, message) {
|
|
|
1642
1880
|
const groupMembershipMap = buildGroupMembershipMap(groups);
|
|
1643
1881
|
const groupSelectAllNames = new Set(groupMembershipMap.keys());
|
|
1644
1882
|
const allBranchNames = new Set(worktrees.map((wt) => wt.branch));
|
|
1645
|
-
const MultiSelect =
|
|
1883
|
+
const MultiSelect = Enquirer3.MultiSelect;
|
|
1646
1884
|
class MultiSelectWithGroupSelectAll extends MultiSelect {
|
|
1647
1885
|
space() {
|
|
1648
1886
|
if (!this.focused) return;
|
|
@@ -1700,7 +1938,7 @@ async function promptGroupedMultiSelectBranches(worktrees, message) {
|
|
|
1700
1938
|
}
|
|
1701
1939
|
|
|
1702
1940
|
// src/utils/progress-render.ts
|
|
1703
|
-
import
|
|
1941
|
+
import chalk5 from "chalk";
|
|
1704
1942
|
import stringWidth from "string-width";
|
|
1705
1943
|
var ANSI_RESET = "\x1B[0m";
|
|
1706
1944
|
function truncateToTerminalWidth(text, maxWidth) {
|
|
@@ -1737,23 +1975,23 @@ function renderTaskLine(task, total, maxPathWidth, spinnerChar) {
|
|
|
1737
1975
|
const pathStr = task.path.padEnd(maxPathWidth);
|
|
1738
1976
|
switch (task.status) {
|
|
1739
1977
|
case "pending": {
|
|
1740
|
-
return `${indexStr} ${pathStr} ${
|
|
1978
|
+
return `${indexStr} ${pathStr} ${chalk5.gray(TASK_STATUS_ICONS.PENDING)} ${chalk5.gray(TASK_STATUS_LABELS.PENDING)}`;
|
|
1741
1979
|
}
|
|
1742
1980
|
case "running": {
|
|
1743
1981
|
const elapsed = formatDuration(Date.now() - task.startedAt);
|
|
1744
|
-
const detail = task.activity ? ` ${
|
|
1745
|
-
return `${indexStr} ${pathStr} ${
|
|
1982
|
+
const detail = task.activity ? ` ${chalk5.dim(task.activity)}` : "";
|
|
1983
|
+
return `${indexStr} ${pathStr} ${chalk5.cyan(spinnerChar)} ${chalk5.cyan(TASK_STATUS_LABELS.RUNNING)} ${chalk5.gray(elapsed)}${detail}`;
|
|
1746
1984
|
}
|
|
1747
1985
|
case "done": {
|
|
1748
1986
|
const duration = task.durationMs != null ? formatDuration(task.durationMs) : "N/A";
|
|
1749
1987
|
const cost = task.costUsd != null ? `$${task.costUsd.toFixed(2)}` : "";
|
|
1750
|
-
const preview = task.resultPreview ? ` ${
|
|
1751
|
-
return `${indexStr} ${pathStr} ${
|
|
1988
|
+
const preview = task.resultPreview ? ` ${chalk5.dim(task.resultPreview)}` : "";
|
|
1989
|
+
return `${indexStr} ${pathStr} ${chalk5.green(TASK_STATUS_ICONS.DONE)} ${chalk5.green(TASK_STATUS_LABELS.DONE)} ${chalk5.gray(duration)} ${chalk5.yellow(cost)}${preview}`;
|
|
1752
1990
|
}
|
|
1753
1991
|
case "failed": {
|
|
1754
1992
|
const duration = task.durationMs != null ? formatDuration(task.durationMs) : "N/A";
|
|
1755
|
-
const preview = task.resultPreview ? ` ${
|
|
1756
|
-
return `${indexStr} ${pathStr} ${
|
|
1993
|
+
const preview = task.resultPreview ? ` ${chalk5.dim(task.resultPreview)}` : "";
|
|
1994
|
+
return `${indexStr} ${pathStr} ${chalk5.red(TASK_STATUS_ICONS.FAILED)} ${chalk5.red(TASK_STATUS_LABELS.FAILED)} ${chalk5.gray(duration)}${preview}`;
|
|
1757
1995
|
}
|
|
1758
1996
|
}
|
|
1759
1997
|
}
|
|
@@ -1763,10 +2001,10 @@ function renderSummaryLine(tasks, total) {
|
|
|
1763
2001
|
const failed = tasks.filter((t) => t.status === "failed").length;
|
|
1764
2002
|
const pending = tasks.filter((t) => t.status === "pending").length;
|
|
1765
2003
|
const parts = [];
|
|
1766
|
-
if (running > 0) parts.push(
|
|
1767
|
-
if (done > 0) parts.push(
|
|
1768
|
-
if (failed > 0) parts.push(
|
|
1769
|
-
if (pending > 0) parts.push(
|
|
2004
|
+
if (running > 0) parts.push(chalk5.cyan(`${running}/${total} ${TASK_STATUS_LABELS.RUNNING}`));
|
|
2005
|
+
if (done > 0) parts.push(chalk5.green(`${done}/${total} ${TASK_STATUS_LABELS.DONE}`));
|
|
2006
|
+
if (failed > 0) parts.push(chalk5.red(`${failed}/${total} ${TASK_STATUS_LABELS.FAILED}`));
|
|
2007
|
+
if (pending > 0) parts.push(chalk5.gray(`${pending}/${total} ${TASK_STATUS_LABELS.PENDING}`));
|
|
1770
2008
|
return `[${parts.join(", ")}]`;
|
|
1771
2009
|
}
|
|
1772
2010
|
|
|
@@ -1998,7 +2236,7 @@ var ProgressRenderer = class {
|
|
|
1998
2236
|
|
|
1999
2237
|
// src/utils/task-file.ts
|
|
2000
2238
|
import { resolve } from "path";
|
|
2001
|
-
import { existsSync as
|
|
2239
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
|
|
2002
2240
|
var TASK_BLOCK_REGEX = /<!-- CLAWT-TASKS:START -->([\s\S]*?)<!-- CLAWT-TASKS:END -->/g;
|
|
2003
2241
|
var BRANCH_LINE_REGEX = /^#\s*branch:\s*(.+)$/;
|
|
2004
2242
|
var EMPTY_TASKS_MESSAGE = "\u4EFB\u52A1\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A";
|
|
@@ -2044,10 +2282,10 @@ function parseTaskFile(content, options) {
|
|
|
2044
2282
|
}
|
|
2045
2283
|
function loadTaskFile(filePath, options) {
|
|
2046
2284
|
const absolutePath = resolve(filePath);
|
|
2047
|
-
if (!
|
|
2285
|
+
if (!existsSync9(absolutePath)) {
|
|
2048
2286
|
throw new ClawtError(MESSAGES.TASK_FILE_NOT_FOUND(absolutePath));
|
|
2049
2287
|
}
|
|
2050
|
-
const content =
|
|
2288
|
+
const content = readFileSync4(absolutePath, "utf-8");
|
|
2051
2289
|
const entries = parseTaskFile(content, options);
|
|
2052
2290
|
if (entries.length === 0) {
|
|
2053
2291
|
throw new ClawtError(MESSAGES.TASK_FILE_EMPTY);
|
|
@@ -2381,8 +2619,8 @@ async function executeBatchTasks(worktrees, tasks, concurrency) {
|
|
|
2381
2619
|
}
|
|
2382
2620
|
|
|
2383
2621
|
// src/utils/dry-run.ts
|
|
2384
|
-
import
|
|
2385
|
-
import { join as
|
|
2622
|
+
import chalk6 from "chalk";
|
|
2623
|
+
import { join as join7 } from "path";
|
|
2386
2624
|
var DRY_RUN_TASK_DESC_MAX_LENGTH = 80;
|
|
2387
2625
|
function truncateTaskDesc(task) {
|
|
2388
2626
|
const oneLine = task.replace(/\n/g, " ").trim();
|
|
@@ -2395,7 +2633,7 @@ function printDryRunPreview(branchNames, tasks, concurrency) {
|
|
|
2395
2633
|
const projectDir = getProjectWorktreeDir();
|
|
2396
2634
|
const isInteractive = tasks.length === 0;
|
|
2397
2635
|
printDoubleSeparator();
|
|
2398
|
-
printInfo(` ${
|
|
2636
|
+
printInfo(` ${chalk6.bold(MESSAGES.DRY_RUN_TITLE)}`);
|
|
2399
2637
|
printDoubleSeparator();
|
|
2400
2638
|
const summaryParts = [
|
|
2401
2639
|
MESSAGES.DRY_RUN_TASK_COUNT(branchNames.length),
|
|
@@ -2405,31 +2643,31 @@ function printDryRunPreview(branchNames, tasks, concurrency) {
|
|
|
2405
2643
|
if (isInteractive) {
|
|
2406
2644
|
summaryParts.push(MESSAGES.DRY_RUN_INTERACTIVE_MODE);
|
|
2407
2645
|
}
|
|
2408
|
-
printInfo(summaryParts.join(
|
|
2646
|
+
printInfo(summaryParts.join(chalk6.gray(" \u2502 ")));
|
|
2409
2647
|
printSeparator();
|
|
2410
2648
|
let hasConflict = false;
|
|
2411
2649
|
for (let i = 0; i < branchNames.length; i++) {
|
|
2412
2650
|
const branch = branchNames[i];
|
|
2413
|
-
const worktreePath =
|
|
2651
|
+
const worktreePath = join7(projectDir, branch);
|
|
2414
2652
|
const exists = checkBranchExists(branch);
|
|
2415
2653
|
if (exists) hasConflict = true;
|
|
2416
2654
|
const indexLabel = `[${i + 1}/${branchNames.length}]`;
|
|
2417
2655
|
if (exists) {
|
|
2418
|
-
printInfo(`${
|
|
2656
|
+
printInfo(`${chalk6.yellow("\u26A0")} ${indexLabel} ${chalk6.yellow(branch)} ${chalk6.gray("\u2014")} ${chalk6.yellow(MESSAGES.DRY_RUN_BRANCH_EXISTS_WARNING(branch))}`);
|
|
2419
2657
|
} else {
|
|
2420
|
-
printInfo(`${
|
|
2658
|
+
printInfo(`${chalk6.green("\u2713")} ${indexLabel} ${chalk6.cyan(branch)}`);
|
|
2421
2659
|
}
|
|
2422
|
-
printInfo(` ${
|
|
2660
|
+
printInfo(` ${chalk6.gray("\u8DEF\u5F84:")} ${worktreePath}`);
|
|
2423
2661
|
if (!isInteractive) {
|
|
2424
|
-
printInfo(` ${
|
|
2662
|
+
printInfo(` ${chalk6.gray("\u4EFB\u52A1:")} ${truncateTaskDesc(tasks[i])}`);
|
|
2425
2663
|
}
|
|
2426
2664
|
printInfo("");
|
|
2427
2665
|
}
|
|
2428
2666
|
printDoubleSeparator();
|
|
2429
2667
|
if (hasConflict) {
|
|
2430
|
-
printInfo(
|
|
2668
|
+
printInfo(chalk6.yellow(`\u26A0 ${MESSAGES.DRY_RUN_HAS_CONFLICT}`));
|
|
2431
2669
|
} else {
|
|
2432
|
-
printInfo(
|
|
2670
|
+
printInfo(chalk6.green(`\u2713 ${MESSAGES.DRY_RUN_READY}`));
|
|
2433
2671
|
}
|
|
2434
2672
|
}
|
|
2435
2673
|
|
|
@@ -2447,8 +2685,8 @@ function applyAliases(program2, aliases) {
|
|
|
2447
2685
|
}
|
|
2448
2686
|
|
|
2449
2687
|
// src/utils/config-strategy.ts
|
|
2450
|
-
import
|
|
2451
|
-
import
|
|
2688
|
+
import chalk7 from "chalk";
|
|
2689
|
+
import Enquirer4 from "enquirer";
|
|
2452
2690
|
function isValidConfigKey(key) {
|
|
2453
2691
|
return key in DEFAULT_CONFIG;
|
|
2454
2692
|
}
|
|
@@ -2491,16 +2729,16 @@ async function promptConfigValue(key, currentValue) {
|
|
|
2491
2729
|
}
|
|
2492
2730
|
function formatConfigValue(value) {
|
|
2493
2731
|
if (typeof value === "boolean") {
|
|
2494
|
-
return value ?
|
|
2732
|
+
return value ? chalk7.green("true") : chalk7.yellow("false");
|
|
2495
2733
|
}
|
|
2496
|
-
return
|
|
2734
|
+
return chalk7.cyan(String(value));
|
|
2497
2735
|
}
|
|
2498
2736
|
async function promptBooleanValue(key, currentValue) {
|
|
2499
2737
|
const choices = [
|
|
2500
2738
|
{ name: "true", message: "true" },
|
|
2501
2739
|
{ name: "false", message: "false" }
|
|
2502
2740
|
];
|
|
2503
|
-
const selected = await new
|
|
2741
|
+
const selected = await new Enquirer4.Select({
|
|
2504
2742
|
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
2505
2743
|
choices,
|
|
2506
2744
|
initial: currentValue ? 0 : 1
|
|
@@ -2508,7 +2746,7 @@ async function promptBooleanValue(key, currentValue) {
|
|
|
2508
2746
|
return selected === "true";
|
|
2509
2747
|
}
|
|
2510
2748
|
async function promptNumberValue(key, currentValue) {
|
|
2511
|
-
const input = await new
|
|
2749
|
+
const input = await new Enquirer4.Input({
|
|
2512
2750
|
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
2513
2751
|
initial: String(currentValue),
|
|
2514
2752
|
validate: (val) => {
|
|
@@ -2523,28 +2761,28 @@ async function promptEnumValue(key, currentValue, allowedValues) {
|
|
|
2523
2761
|
name: v,
|
|
2524
2762
|
message: v
|
|
2525
2763
|
}));
|
|
2526
|
-
return await new
|
|
2764
|
+
return await new Enquirer4.Select({
|
|
2527
2765
|
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
2528
2766
|
choices,
|
|
2529
2767
|
initial: allowedValues.indexOf(currentValue)
|
|
2530
2768
|
}).run();
|
|
2531
2769
|
}
|
|
2532
2770
|
async function promptStringValue(key, currentValue) {
|
|
2533
|
-
return await new
|
|
2771
|
+
return await new Enquirer4.Input({
|
|
2534
2772
|
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
2535
2773
|
initial: currentValue
|
|
2536
2774
|
}).run();
|
|
2537
2775
|
}
|
|
2538
2776
|
|
|
2539
2777
|
// src/utils/update-checker.ts
|
|
2540
|
-
import { readFileSync as
|
|
2778
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
2541
2779
|
import { execSync as execSync3 } from "child_process";
|
|
2542
2780
|
import { request } from "https";
|
|
2543
|
-
import
|
|
2781
|
+
import chalk8 from "chalk";
|
|
2544
2782
|
import stringWidth2 from "string-width";
|
|
2545
2783
|
function readUpdateCache() {
|
|
2546
2784
|
try {
|
|
2547
|
-
const raw =
|
|
2785
|
+
const raw = readFileSync5(UPDATE_CHECK_PATH, "utf-8");
|
|
2548
2786
|
return JSON.parse(raw);
|
|
2549
2787
|
} catch {
|
|
2550
2788
|
return null;
|
|
@@ -2552,7 +2790,7 @@ function readUpdateCache() {
|
|
|
2552
2790
|
}
|
|
2553
2791
|
function writeUpdateCache(cache) {
|
|
2554
2792
|
try {
|
|
2555
|
-
|
|
2793
|
+
writeFileSync4(UPDATE_CHECK_PATH, JSON.stringify(cache, null, 2), "utf-8");
|
|
2556
2794
|
} catch {
|
|
2557
2795
|
}
|
|
2558
2796
|
}
|
|
@@ -2615,12 +2853,12 @@ function detectPackageManager() {
|
|
|
2615
2853
|
}
|
|
2616
2854
|
function printUpdateNotification(currentVersion, latestVersion) {
|
|
2617
2855
|
const updateText = UPDATE_MESSAGES.UPDATE_AVAILABLE(
|
|
2618
|
-
|
|
2619
|
-
|
|
2856
|
+
chalk8.red(currentVersion),
|
|
2857
|
+
chalk8.green(latestVersion)
|
|
2620
2858
|
);
|
|
2621
2859
|
const pm = detectPackageManager();
|
|
2622
2860
|
const updateCommand = UPDATE_COMMANDS[pm] || UPDATE_COMMANDS.npm;
|
|
2623
|
-
const commandText = UPDATE_MESSAGES.UPDATE_HINT(
|
|
2861
|
+
const commandText = UPDATE_MESSAGES.UPDATE_HINT(chalk8.cyan(updateCommand));
|
|
2624
2862
|
const updateTextWidth = stringWidth2(updateText);
|
|
2625
2863
|
const commandTextWidth = stringWidth2(commandText);
|
|
2626
2864
|
const innerWidth = Math.max(updateTextWidth, commandTextWidth) + 4;
|
|
@@ -2667,10 +2905,642 @@ async function checkForUpdates(currentVersion) {
|
|
|
2667
2905
|
}
|
|
2668
2906
|
}
|
|
2669
2907
|
|
|
2908
|
+
// src/utils/json.ts
|
|
2909
|
+
function primitiveToString(value) {
|
|
2910
|
+
if (value === void 0) {
|
|
2911
|
+
return "undefined";
|
|
2912
|
+
}
|
|
2913
|
+
if (value === null) {
|
|
2914
|
+
return "null";
|
|
2915
|
+
}
|
|
2916
|
+
if (typeof value === "symbol") {
|
|
2917
|
+
return value.toString();
|
|
2918
|
+
}
|
|
2919
|
+
if (typeof value === "function") {
|
|
2920
|
+
return `[Function: ${value.name || "anonymous"}]`;
|
|
2921
|
+
}
|
|
2922
|
+
return String(value);
|
|
2923
|
+
}
|
|
2924
|
+
function safeStringify(value, indent = 2) {
|
|
2925
|
+
if (value === null || typeof value !== "object") {
|
|
2926
|
+
return primitiveToString(value);
|
|
2927
|
+
}
|
|
2928
|
+
try {
|
|
2929
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
2930
|
+
return JSON.stringify(
|
|
2931
|
+
value,
|
|
2932
|
+
(_key, val) => {
|
|
2933
|
+
if (typeof val === "bigint") {
|
|
2934
|
+
return val.toString();
|
|
2935
|
+
}
|
|
2936
|
+
if (typeof val === "undefined" || typeof val === "function" || typeof val === "symbol") {
|
|
2937
|
+
return primitiveToString(val);
|
|
2938
|
+
}
|
|
2939
|
+
if (typeof val === "object" && val !== null) {
|
|
2940
|
+
if (seen.has(val)) {
|
|
2941
|
+
return "[Circular]";
|
|
2942
|
+
}
|
|
2943
|
+
seen.add(val);
|
|
2944
|
+
}
|
|
2945
|
+
return val;
|
|
2946
|
+
},
|
|
2947
|
+
indent
|
|
2948
|
+
);
|
|
2949
|
+
} catch {
|
|
2950
|
+
try {
|
|
2951
|
+
return JSON.stringify(String(value), null, indent);
|
|
2952
|
+
} catch {
|
|
2953
|
+
return "[Unserializable]";
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
// src/utils/interactive-panel.ts
|
|
2959
|
+
import { createInterface as createInterface2 } from "readline";
|
|
2960
|
+
|
|
2961
|
+
// src/utils/interactive-panel-render.ts
|
|
2962
|
+
import chalk9 from "chalk";
|
|
2963
|
+
import stringWidth3 from "string-width";
|
|
2964
|
+
function buildSeparatorWithHint(cols, hint) {
|
|
2965
|
+
const maxWidth = Math.min(cols, 60);
|
|
2966
|
+
if (!hint) {
|
|
2967
|
+
return chalk9.gray("\u2500".repeat(maxWidth));
|
|
2968
|
+
}
|
|
2969
|
+
const hintWidth = stringWidth3(hint);
|
|
2970
|
+
const remaining = Math.max(maxWidth - 2 - hintWidth, 0);
|
|
2971
|
+
const leftLen = Math.floor(remaining / 2);
|
|
2972
|
+
const rightLen = remaining - leftLen;
|
|
2973
|
+
return `${chalk9.gray("\u2500".repeat(leftLen))} ${hint} ${chalk9.gray("\u2500".repeat(rightLen))}`;
|
|
2974
|
+
}
|
|
2975
|
+
function buildPanelFrame(statusResult, selectedIndex, scrollOffset, rows, cols, countdown) {
|
|
2976
|
+
const lines = [];
|
|
2977
|
+
lines.push(PANEL_TITLE(statusResult.main.projectName));
|
|
2978
|
+
lines.push(renderSnapshotSummary(statusResult.snapshots.total, statusResult.snapshots.orphaned));
|
|
2979
|
+
const visibleRows = calculateVisibleRows(rows);
|
|
2980
|
+
if (statusResult.worktrees.length === 0) {
|
|
2981
|
+
lines.push(buildSeparatorWithHint(cols, ""));
|
|
2982
|
+
lines.push(PANEL_NO_WORKTREES);
|
|
2983
|
+
lines.push(buildSeparatorWithHint(cols, ""));
|
|
2984
|
+
} else {
|
|
2985
|
+
const panelLines = buildGroupedWorktreeLines(statusResult.worktrees, selectedIndex);
|
|
2986
|
+
const hasOverflowUp = scrollOffset > 0;
|
|
2987
|
+
const hasOverflowDown = scrollOffset + visibleRows < panelLines.length;
|
|
2988
|
+
lines.push(buildSeparatorWithHint(cols, hasOverflowUp ? PANEL_OVERFLOW_UP_HINT : ""));
|
|
2989
|
+
const visibleLines = panelLines.slice(scrollOffset, scrollOffset + visibleRows);
|
|
2990
|
+
for (const pl of visibleLines) {
|
|
2991
|
+
lines.push(pl.text);
|
|
2992
|
+
}
|
|
2993
|
+
lines.push(buildSeparatorWithHint(cols, hasOverflowDown ? PANEL_OVERFLOW_DOWN_HINT : ""));
|
|
2994
|
+
}
|
|
2995
|
+
lines.push(renderFooter(countdown));
|
|
2996
|
+
return lines;
|
|
2997
|
+
}
|
|
2998
|
+
function buildDisplayOrder(worktrees) {
|
|
2999
|
+
const worktreeInfos = worktrees.map((wt) => ({ path: wt.path, branch: wt.branch }));
|
|
3000
|
+
const groups = groupWorktreesByDate(worktreeInfos);
|
|
3001
|
+
const branchIndexMap = /* @__PURE__ */ new Map();
|
|
3002
|
+
for (let i = 0; i < worktrees.length; i++) {
|
|
3003
|
+
branchIndexMap.set(worktrees[i].branch, i);
|
|
3004
|
+
}
|
|
3005
|
+
const displayOrder = [];
|
|
3006
|
+
for (const [, groupWorktrees] of groups) {
|
|
3007
|
+
for (const gwt of groupWorktrees) {
|
|
3008
|
+
displayOrder.push(branchIndexMap.get(gwt.branch));
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
return displayOrder;
|
|
3012
|
+
}
|
|
3013
|
+
function buildGroupedWorktreeLines(worktrees, selectedIndex) {
|
|
3014
|
+
const panelLines = [];
|
|
3015
|
+
const worktreeInfos = worktrees.map((wt) => ({ path: wt.path, branch: wt.branch }));
|
|
3016
|
+
const groups = groupWorktreesByDate(worktreeInfos);
|
|
3017
|
+
const branchIndexMap = /* @__PURE__ */ new Map();
|
|
3018
|
+
for (let i = 0; i < worktrees.length; i++) {
|
|
3019
|
+
branchIndexMap.set(worktrees[i].branch, i);
|
|
3020
|
+
}
|
|
3021
|
+
for (const [dateKey, groupWorktrees] of groups) {
|
|
3022
|
+
panelLines.push({
|
|
3023
|
+
type: "separator",
|
|
3024
|
+
text: renderDateSeparator(dateKey)
|
|
3025
|
+
});
|
|
3026
|
+
panelLines.push({
|
|
3027
|
+
type: "separator",
|
|
3028
|
+
text: ""
|
|
3029
|
+
});
|
|
3030
|
+
for (const gwt of groupWorktrees) {
|
|
3031
|
+
const wtIndex = branchIndexMap.get(gwt.branch);
|
|
3032
|
+
const wt = worktrees[wtIndex];
|
|
3033
|
+
const isSelected = wtIndex === selectedIndex;
|
|
3034
|
+
const blockLines = renderWorktreeBlock(wt, isSelected);
|
|
3035
|
+
for (const line of blockLines) {
|
|
3036
|
+
panelLines.push({
|
|
3037
|
+
type: "worktree-content",
|
|
3038
|
+
text: line,
|
|
3039
|
+
worktreeIndex: wtIndex
|
|
3040
|
+
});
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
return panelLines;
|
|
3045
|
+
}
|
|
3046
|
+
function renderDateSeparator(dateKey) {
|
|
3047
|
+
const leftPad = " ";
|
|
3048
|
+
if (dateKey === UNKNOWN_DATE_GROUP) {
|
|
3049
|
+
return `${leftPad}${chalk9.gray(PANEL_DATE_SEPARATOR_PREFIX)} ${chalk9.bold.hex("#FF8C00")("\u672A\u77E5\u65E5\u671F")} ${chalk9.gray(PANEL_DATE_SEPARATOR_PREFIX)}`;
|
|
3050
|
+
}
|
|
3051
|
+
const relativeDate = formatRelativeDate(dateKey);
|
|
3052
|
+
return `${leftPad}${chalk9.gray(PANEL_DATE_SEPARATOR_PREFIX)} ${chalk9.bold.hex("#FF8C00")(dateKey)}${chalk9.hex("#FF8C00")(`\uFF08${relativeDate}\uFF09`)} ${chalk9.gray(PANEL_DATE_SEPARATOR_PREFIX)}`;
|
|
3053
|
+
}
|
|
3054
|
+
function renderWorktreeBlock(wt, isSelected) {
|
|
3055
|
+
const lines = [];
|
|
3056
|
+
const indicator = isSelected ? chalk9.cyan(SELECTED_INDICATOR) : UNSELECTED_INDICATOR;
|
|
3057
|
+
const branchStyle = isSelected ? chalk9.bold.cyan : chalk9.bold;
|
|
3058
|
+
const statusLabel = formatChangeStatusLabel(wt.changeStatus);
|
|
3059
|
+
lines.push(`${indicator} ${branchStyle(wt.branch)} [${statusLabel}]`);
|
|
3060
|
+
const indent = " ";
|
|
3061
|
+
if (wt.insertions > 0 || wt.deletions > 0) {
|
|
3062
|
+
lines.push(`${indent}${chalk9.green(`+${wt.insertions}`)} ${chalk9.red(`-${wt.deletions}`)}`);
|
|
3063
|
+
}
|
|
3064
|
+
if (wt.commitsAhead > 0) {
|
|
3065
|
+
lines.push(`${indent}${chalk9.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`)}`);
|
|
3066
|
+
}
|
|
3067
|
+
if (wt.commitsBehind > 0) {
|
|
3068
|
+
lines.push(`${indent}${chalk9.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`)}`);
|
|
3069
|
+
} else {
|
|
3070
|
+
lines.push(`${indent}${chalk9.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65")}`);
|
|
3071
|
+
}
|
|
3072
|
+
if (wt.createdAt) {
|
|
3073
|
+
const relativeTime = formatRelativeTime(wt.createdAt);
|
|
3074
|
+
if (relativeTime) {
|
|
3075
|
+
lines.push(`${indent}${chalk9.gray(MESSAGES.STATUS_CREATED_AT(relativeTime))}`);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
if (wt.snapshotTime) {
|
|
3079
|
+
const relativeTime = formatRelativeTime(wt.snapshotTime);
|
|
3080
|
+
if (relativeTime) {
|
|
3081
|
+
lines.push(`${indent}${chalk9.green(MESSAGES.STATUS_LAST_VALIDATED(relativeTime))}`);
|
|
3082
|
+
}
|
|
3083
|
+
} else {
|
|
3084
|
+
lines.push(`${indent}${chalk9.red(MESSAGES.STATUS_NOT_VALIDATED)}`);
|
|
3085
|
+
}
|
|
3086
|
+
lines.push("");
|
|
3087
|
+
return lines;
|
|
3088
|
+
}
|
|
3089
|
+
function formatChangeStatusLabel(status) {
|
|
3090
|
+
switch (status) {
|
|
3091
|
+
case "committed":
|
|
3092
|
+
return chalk9.green(MESSAGES.STATUS_CHANGE_COMMITTED);
|
|
3093
|
+
case "uncommitted":
|
|
3094
|
+
return chalk9.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
|
|
3095
|
+
case "conflict":
|
|
3096
|
+
return chalk9.red(MESSAGES.STATUS_CHANGE_CONFLICT);
|
|
3097
|
+
case "clean":
|
|
3098
|
+
return chalk9.gray(MESSAGES.STATUS_CHANGE_CLEAN);
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
function renderSnapshotSummary(total, orphaned) {
|
|
3102
|
+
return PANEL_SNAPSHOT_SUMMARY(total, orphaned);
|
|
3103
|
+
}
|
|
3104
|
+
function renderFooter(countdown) {
|
|
3105
|
+
return `${PANEL_FOOTER_SHORTCUTS} ${PANEL_FOOTER_COUNTDOWN(countdown)}`;
|
|
3106
|
+
}
|
|
3107
|
+
function calculateVisibleRows(terminalRows) {
|
|
3108
|
+
const fixedRows = PANEL_FIXED_ROWS + 1;
|
|
3109
|
+
return Math.max(terminalRows - fixedRows, 3);
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
// src/utils/interactive-panel.ts
|
|
3113
|
+
var InteractivePanel = class {
|
|
3114
|
+
/** 当前状态数据 */
|
|
3115
|
+
statusResult;
|
|
3116
|
+
/** 当前选中的显示位置索引(对应 displayOrder 数组的下标) */
|
|
3117
|
+
selectedDisplayIndex;
|
|
3118
|
+
/** 显示顺序到原始索引的映射(按日期分组后的排列顺序) */
|
|
3119
|
+
displayOrder;
|
|
3120
|
+
/** 滚动偏移(基于行数) */
|
|
3121
|
+
scrollOffset;
|
|
3122
|
+
/** 数据刷新定时器引用 */
|
|
3123
|
+
refreshTimer;
|
|
3124
|
+
/** 倒计时定时器引用 */
|
|
3125
|
+
countdownTimer;
|
|
3126
|
+
/** 刷新倒计时剩余秒数 */
|
|
3127
|
+
refreshCountdown;
|
|
3128
|
+
/** 是否已停止 */
|
|
3129
|
+
stopped;
|
|
3130
|
+
/** 是否为 TTY 环境 */
|
|
3131
|
+
isTTY;
|
|
3132
|
+
/** resize 事件处理器引用 */
|
|
3133
|
+
resizeHandler;
|
|
3134
|
+
/** exit 兜底处理器 */
|
|
3135
|
+
exitHandler;
|
|
3136
|
+
/** stdin 数据处理器引用(用于清理) */
|
|
3137
|
+
stdinDataHandler;
|
|
3138
|
+
/** 操作锁(防止操作期间响应按键) */
|
|
3139
|
+
isOperating;
|
|
3140
|
+
/** Promise resolve 函数(stop 时调用以完成 start 返回的 Promise) */
|
|
3141
|
+
resolveStart;
|
|
3142
|
+
/** 数据收集函数引用 */
|
|
3143
|
+
collectStatusFn;
|
|
3144
|
+
/**
|
|
3145
|
+
* 创建交互式面板
|
|
3146
|
+
* @param {() => StatusResult} collectStatusFn - 数据收集函数
|
|
3147
|
+
*/
|
|
3148
|
+
constructor(collectStatusFn) {
|
|
3149
|
+
this.statusResult = null;
|
|
3150
|
+
this.selectedDisplayIndex = 0;
|
|
3151
|
+
this.displayOrder = [];
|
|
3152
|
+
this.scrollOffset = 0;
|
|
3153
|
+
this.refreshTimer = null;
|
|
3154
|
+
this.countdownTimer = null;
|
|
3155
|
+
this.refreshCountdown = PANEL_REFRESH_INTERVAL_MS / 1e3;
|
|
3156
|
+
this.stopped = false;
|
|
3157
|
+
this.isTTY = !!process.stdout.isTTY;
|
|
3158
|
+
this.resizeHandler = null;
|
|
3159
|
+
this.exitHandler = null;
|
|
3160
|
+
this.stdinDataHandler = null;
|
|
3161
|
+
this.isOperating = false;
|
|
3162
|
+
this.resolveStart = null;
|
|
3163
|
+
this.collectStatusFn = collectStatusFn;
|
|
3164
|
+
}
|
|
3165
|
+
/**
|
|
3166
|
+
* 启动交互式面板
|
|
3167
|
+
* 非 TTY 时打印提示并退出
|
|
3168
|
+
* @returns {Promise<void>} 面板关闭时 resolve
|
|
3169
|
+
*/
|
|
3170
|
+
start() {
|
|
3171
|
+
if (!this.isTTY) {
|
|
3172
|
+
console.log(PANEL_NOT_TTY);
|
|
3173
|
+
return Promise.resolve();
|
|
3174
|
+
}
|
|
3175
|
+
return new Promise((resolve3) => {
|
|
3176
|
+
this.resolveStart = resolve3;
|
|
3177
|
+
this.statusResult = this.collectStatusFn();
|
|
3178
|
+
this.displayOrder = buildDisplayOrder(this.statusResult.worktrees);
|
|
3179
|
+
this.initTerminal();
|
|
3180
|
+
this.startKeyboardListener();
|
|
3181
|
+
this.startAutoRefresh();
|
|
3182
|
+
this.render();
|
|
3183
|
+
});
|
|
3184
|
+
}
|
|
3185
|
+
/**
|
|
3186
|
+
* 停止面板,恢复终端状态
|
|
3187
|
+
*/
|
|
3188
|
+
stop() {
|
|
3189
|
+
if (this.stopped) return;
|
|
3190
|
+
this.stopped = true;
|
|
3191
|
+
this.clearTimers();
|
|
3192
|
+
this.stopKeyboardListener();
|
|
3193
|
+
this.restoreTerminal();
|
|
3194
|
+
if (this.resizeHandler) {
|
|
3195
|
+
process.stdout.removeListener("resize", this.resizeHandler);
|
|
3196
|
+
this.resizeHandler = null;
|
|
3197
|
+
}
|
|
3198
|
+
if (this.exitHandler) {
|
|
3199
|
+
process.removeListener("exit", this.exitHandler);
|
|
3200
|
+
this.exitHandler = null;
|
|
3201
|
+
}
|
|
3202
|
+
if (this.resolveStart) {
|
|
3203
|
+
this.resolveStart();
|
|
3204
|
+
this.resolveStart = null;
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
/**
|
|
3208
|
+
* 初始化终端:进入备选屏幕、隐藏光标、禁用行换行
|
|
3209
|
+
*/
|
|
3210
|
+
initTerminal() {
|
|
3211
|
+
process.stdout.write(ALT_SCREEN_ENTER);
|
|
3212
|
+
process.stdout.write(CURSOR_HIDE);
|
|
3213
|
+
process.stdout.write(LINE_WRAP_DISABLE);
|
|
3214
|
+
this.resizeHandler = () => {
|
|
3215
|
+
if (!this.stopped && !this.isOperating) {
|
|
3216
|
+
this.render();
|
|
3217
|
+
}
|
|
3218
|
+
};
|
|
3219
|
+
process.stdout.on("resize", this.resizeHandler);
|
|
3220
|
+
this.exitHandler = () => {
|
|
3221
|
+
process.stdout.write(LINE_WRAP_ENABLE);
|
|
3222
|
+
process.stdout.write(CURSOR_SHOW);
|
|
3223
|
+
process.stdout.write(ALT_SCREEN_LEAVE);
|
|
3224
|
+
};
|
|
3225
|
+
process.on("exit", this.exitHandler);
|
|
3226
|
+
}
|
|
3227
|
+
/**
|
|
3228
|
+
* 恢复终端:启用行换行、显示光标、退出备选屏幕
|
|
3229
|
+
*/
|
|
3230
|
+
restoreTerminal() {
|
|
3231
|
+
process.stdout.write(LINE_WRAP_ENABLE);
|
|
3232
|
+
process.stdout.write(CURSOR_SHOW);
|
|
3233
|
+
process.stdout.write(ALT_SCREEN_LEAVE);
|
|
3234
|
+
}
|
|
3235
|
+
/**
|
|
3236
|
+
* 启动键盘监听
|
|
3237
|
+
* 将 stdin 设为 raw 模式以捕获每个按键
|
|
3238
|
+
*/
|
|
3239
|
+
startKeyboardListener() {
|
|
3240
|
+
if (process.stdin.isTTY) {
|
|
3241
|
+
process.stdin.setRawMode(true);
|
|
3242
|
+
}
|
|
3243
|
+
process.stdin.resume();
|
|
3244
|
+
this.stdinDataHandler = (data) => {
|
|
3245
|
+
this.handleKeypress(data);
|
|
3246
|
+
};
|
|
3247
|
+
process.stdin.on("data", this.stdinDataHandler);
|
|
3248
|
+
}
|
|
3249
|
+
/**
|
|
3250
|
+
* 停止键盘监听,恢复 stdin 状态
|
|
3251
|
+
*/
|
|
3252
|
+
stopKeyboardListener() {
|
|
3253
|
+
if (this.stdinDataHandler) {
|
|
3254
|
+
process.stdin.removeListener("data", this.stdinDataHandler);
|
|
3255
|
+
this.stdinDataHandler = null;
|
|
3256
|
+
}
|
|
3257
|
+
if (process.stdin.isTTY) {
|
|
3258
|
+
process.stdin.setRawMode(false);
|
|
3259
|
+
}
|
|
3260
|
+
process.stdin.pause();
|
|
3261
|
+
}
|
|
3262
|
+
/**
|
|
3263
|
+
* 处理键盘输入
|
|
3264
|
+
* @param {Buffer} data - 按键数据
|
|
3265
|
+
*/
|
|
3266
|
+
handleKeypress(data) {
|
|
3267
|
+
if (this.isOperating) return;
|
|
3268
|
+
const str = data.toString();
|
|
3269
|
+
if (data[0] === KEY_CTRL_C) {
|
|
3270
|
+
this.stop();
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
if (str === KEY_ARROW_UP) {
|
|
3274
|
+
this.navigateUp();
|
|
3275
|
+
return;
|
|
3276
|
+
}
|
|
3277
|
+
if (str === KEY_ARROW_DOWN) {
|
|
3278
|
+
this.navigateDown();
|
|
3279
|
+
return;
|
|
3280
|
+
}
|
|
3281
|
+
const key = str.toLowerCase();
|
|
3282
|
+
if (key === PANEL_SHORTCUT_KEYS.QUIT) {
|
|
3283
|
+
this.stop();
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
if (key === PANEL_SHORTCUT_KEYS.REFRESH) {
|
|
3287
|
+
this.refreshData();
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
if (key === PANEL_SHORTCUT_KEYS.VALIDATE) {
|
|
3291
|
+
this.executeOperation(() => this.handleValidate());
|
|
3292
|
+
return;
|
|
3293
|
+
}
|
|
3294
|
+
if (key === PANEL_SHORTCUT_KEYS.MERGE) {
|
|
3295
|
+
this.executeOperation(() => this.handleMerge());
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
if (key === PANEL_SHORTCUT_KEYS.DELETE) {
|
|
3299
|
+
this.executeOperation(() => this.handleDelete());
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
if (key === PANEL_SHORTCUT_KEYS.RESUME) {
|
|
3303
|
+
this.executeOperation(() => this.handleResume());
|
|
3304
|
+
return;
|
|
3305
|
+
}
|
|
3306
|
+
if (key === PANEL_SHORTCUT_KEYS.SYNC) {
|
|
3307
|
+
this.executeOperation(() => this.handleSync());
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
/**
|
|
3312
|
+
* 向上导航,选中显示顺序中的上一个 worktree
|
|
3313
|
+
*/
|
|
3314
|
+
navigateUp() {
|
|
3315
|
+
if (!this.statusResult || this.displayOrder.length === 0) return;
|
|
3316
|
+
if (this.selectedDisplayIndex > 0) {
|
|
3317
|
+
this.selectedDisplayIndex--;
|
|
3318
|
+
this.adjustScrollForSelection();
|
|
3319
|
+
this.render();
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
/**
|
|
3323
|
+
* 向下导航,选中显示顺序中的下一个 worktree
|
|
3324
|
+
*/
|
|
3325
|
+
navigateDown() {
|
|
3326
|
+
if (!this.statusResult || this.displayOrder.length === 0) return;
|
|
3327
|
+
if (this.selectedDisplayIndex < this.displayOrder.length - 1) {
|
|
3328
|
+
this.selectedDisplayIndex++;
|
|
3329
|
+
this.adjustScrollForSelection();
|
|
3330
|
+
this.render();
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
/**
|
|
3334
|
+
* 获取当前选中的原始 worktree 索引
|
|
3335
|
+
* @returns {number} 原始 worktrees 数组中的索引
|
|
3336
|
+
*/
|
|
3337
|
+
getSelectedOriginalIndex() {
|
|
3338
|
+
return this.displayOrder[this.selectedDisplayIndex];
|
|
3339
|
+
}
|
|
3340
|
+
/**
|
|
3341
|
+
* 调整滚动偏移以确保选中项在可见区域内
|
|
3342
|
+
*/
|
|
3343
|
+
adjustScrollForSelection() {
|
|
3344
|
+
if (!this.statusResult || this.displayOrder.length === 0) return;
|
|
3345
|
+
const originalIndex = this.getSelectedOriginalIndex();
|
|
3346
|
+
const rows = process.stdout.rows || 24;
|
|
3347
|
+
const visibleRows = calculateVisibleRows(rows);
|
|
3348
|
+
const panelLines = buildGroupedWorktreeLines(this.statusResult.worktrees, originalIndex);
|
|
3349
|
+
let firstLine = -1;
|
|
3350
|
+
let lastLine = -1;
|
|
3351
|
+
for (let i = 0; i < panelLines.length; i++) {
|
|
3352
|
+
if (panelLines[i].worktreeIndex === originalIndex) {
|
|
3353
|
+
if (firstLine === -1) firstLine = i;
|
|
3354
|
+
lastLine = i;
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
if (firstLine === -1) return;
|
|
3358
|
+
let groupStart = firstLine;
|
|
3359
|
+
while (groupStart > 0 && panelLines[groupStart - 1].type === "separator") {
|
|
3360
|
+
groupStart--;
|
|
3361
|
+
}
|
|
3362
|
+
if (groupStart < this.scrollOffset) {
|
|
3363
|
+
this.scrollOffset = groupStart;
|
|
3364
|
+
}
|
|
3365
|
+
if (lastLine >= this.scrollOffset + visibleRows) {
|
|
3366
|
+
this.scrollOffset = lastLine - visibleRows + 1;
|
|
3367
|
+
}
|
|
3368
|
+
if (this.scrollOffset > groupStart) {
|
|
3369
|
+
this.scrollOffset = groupStart;
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
/**
|
|
3373
|
+
* 启动自动刷新:数据刷新定时器 + 倒计时定时器
|
|
3374
|
+
*/
|
|
3375
|
+
startAutoRefresh() {
|
|
3376
|
+
this.refreshCountdown = PANEL_REFRESH_INTERVAL_MS / 1e3;
|
|
3377
|
+
this.refreshTimer = setInterval(() => {
|
|
3378
|
+
this.refreshData();
|
|
3379
|
+
}, PANEL_REFRESH_INTERVAL_MS);
|
|
3380
|
+
this.countdownTimer = setInterval(() => {
|
|
3381
|
+
if (this.refreshCountdown > 0) {
|
|
3382
|
+
this.refreshCountdown--;
|
|
3383
|
+
}
|
|
3384
|
+
this.render();
|
|
3385
|
+
}, PANEL_COUNTDOWN_INTERVAL_MS);
|
|
3386
|
+
if (this.refreshTimer.unref) this.refreshTimer.unref();
|
|
3387
|
+
if (this.countdownTimer.unref) this.countdownTimer.unref();
|
|
3388
|
+
}
|
|
3389
|
+
/**
|
|
3390
|
+
* 清除所有定时器
|
|
3391
|
+
*/
|
|
3392
|
+
clearTimers() {
|
|
3393
|
+
if (this.refreshTimer) {
|
|
3394
|
+
clearInterval(this.refreshTimer);
|
|
3395
|
+
this.refreshTimer = null;
|
|
3396
|
+
}
|
|
3397
|
+
if (this.countdownTimer) {
|
|
3398
|
+
clearInterval(this.countdownTimer);
|
|
3399
|
+
this.countdownTimer = null;
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
/**
|
|
3403
|
+
* 刷新数据:记录当前选中分支 → 重新收集 → 恢复选中位置 → 重置倒计时 → 重绘
|
|
3404
|
+
*/
|
|
3405
|
+
refreshData() {
|
|
3406
|
+
if (this.stopped || this.isOperating) return;
|
|
3407
|
+
const originalIndex = this.displayOrder[this.selectedDisplayIndex];
|
|
3408
|
+
const previousBranch = this.statusResult?.worktrees[originalIndex]?.branch;
|
|
3409
|
+
this.statusResult = this.collectStatusFn();
|
|
3410
|
+
this.displayOrder = buildDisplayOrder(this.statusResult.worktrees);
|
|
3411
|
+
this.restoreSelection(previousBranch);
|
|
3412
|
+
this.refreshCountdown = PANEL_REFRESH_INTERVAL_MS / 1e3;
|
|
3413
|
+
this.render();
|
|
3414
|
+
}
|
|
3415
|
+
/**
|
|
3416
|
+
* 按分支名恢复选中位置(基于显示顺序)
|
|
3417
|
+
* @param {string | undefined} previousBranch - 之前选中的分支名
|
|
3418
|
+
*/
|
|
3419
|
+
restoreSelection(previousBranch) {
|
|
3420
|
+
if (!this.statusResult || !previousBranch || this.displayOrder.length === 0) {
|
|
3421
|
+
this.selectedDisplayIndex = 0;
|
|
3422
|
+
return;
|
|
3423
|
+
}
|
|
3424
|
+
const newDisplayIndex = this.displayOrder.findIndex(
|
|
3425
|
+
(origIdx) => this.statusResult.worktrees[origIdx]?.branch === previousBranch
|
|
3426
|
+
);
|
|
3427
|
+
if (newDisplayIndex >= 0) {
|
|
3428
|
+
this.selectedDisplayIndex = newDisplayIndex;
|
|
3429
|
+
} else {
|
|
3430
|
+
this.selectedDisplayIndex = Math.min(this.selectedDisplayIndex, Math.max(0, this.displayOrder.length - 1));
|
|
3431
|
+
}
|
|
3432
|
+
this.adjustScrollForSelection();
|
|
3433
|
+
}
|
|
3434
|
+
/**
|
|
3435
|
+
* 渲染一帧面板内容
|
|
3436
|
+
* 使用同步输出防止闪烁
|
|
3437
|
+
*/
|
|
3438
|
+
render() {
|
|
3439
|
+
if (this.stopped || this.isOperating || !this.statusResult) return;
|
|
3440
|
+
const cols = process.stdout.columns || DEFAULT_TERMINAL_COLUMNS;
|
|
3441
|
+
const rows = process.stdout.rows || 24;
|
|
3442
|
+
const frameLines = buildPanelFrame(
|
|
3443
|
+
this.statusResult,
|
|
3444
|
+
this.getSelectedOriginalIndex(),
|
|
3445
|
+
this.scrollOffset,
|
|
3446
|
+
rows,
|
|
3447
|
+
cols,
|
|
3448
|
+
this.refreshCountdown
|
|
3449
|
+
);
|
|
3450
|
+
process.stdout.write(SYNC_OUTPUT_START);
|
|
3451
|
+
process.stdout.write(CLEAR_SCREEN);
|
|
3452
|
+
process.stdout.write(CURSOR_HOME);
|
|
3453
|
+
for (let i = 0; i < frameLines.length; i++) {
|
|
3454
|
+
const suffix = i < frameLines.length - 1 ? "\n" : "";
|
|
3455
|
+
process.stdout.write(`${truncateToTerminalWidth(frameLines[i], cols)}${suffix}`);
|
|
3456
|
+
}
|
|
3457
|
+
process.stdout.write(SYNC_OUTPUT_END);
|
|
3458
|
+
}
|
|
3459
|
+
/**
|
|
3460
|
+
* 执行操作:暂停面板 → 恢复终端 → 执行命令 → 等待回车 → 恢复面板
|
|
3461
|
+
* @param {() => void} action - 要执行的操作
|
|
3462
|
+
*/
|
|
3463
|
+
async executeOperation(action) {
|
|
3464
|
+
if (!this.statusResult || this.displayOrder.length === 0) return;
|
|
3465
|
+
this.isOperating = true;
|
|
3466
|
+
this.clearTimers();
|
|
3467
|
+
this.restoreTerminal();
|
|
3468
|
+
this.stopKeyboardListener();
|
|
3469
|
+
action();
|
|
3470
|
+
console.log(PANEL_PRESS_ENTER_TO_RETURN);
|
|
3471
|
+
await this.waitForEnter();
|
|
3472
|
+
this.initTerminal();
|
|
3473
|
+
this.startKeyboardListener();
|
|
3474
|
+
this.isOperating = false;
|
|
3475
|
+
this.refreshData();
|
|
3476
|
+
this.startAutoRefresh();
|
|
3477
|
+
this.render();
|
|
3478
|
+
}
|
|
3479
|
+
/**
|
|
3480
|
+
* 获取当前选中的分支名
|
|
3481
|
+
* @returns {string} 当前选中的分支名
|
|
3482
|
+
*/
|
|
3483
|
+
getSelectedBranch() {
|
|
3484
|
+
const originalIndex = this.getSelectedOriginalIndex();
|
|
3485
|
+
return this.statusResult.worktrees[originalIndex].branch;
|
|
3486
|
+
}
|
|
3487
|
+
/**
|
|
3488
|
+
* 执行验证操作
|
|
3489
|
+
*/
|
|
3490
|
+
handleValidate() {
|
|
3491
|
+
const branch = this.getSelectedBranch();
|
|
3492
|
+
runCommandInherited(`clawt validate -b ${branch}`);
|
|
3493
|
+
}
|
|
3494
|
+
/**
|
|
3495
|
+
* 执行合并操作
|
|
3496
|
+
*/
|
|
3497
|
+
handleMerge() {
|
|
3498
|
+
const branch = this.getSelectedBranch();
|
|
3499
|
+
runCommandInherited(`clawt merge -b ${branch}`);
|
|
3500
|
+
}
|
|
3501
|
+
/**
|
|
3502
|
+
* 执行删除操作
|
|
3503
|
+
*/
|
|
3504
|
+
handleDelete() {
|
|
3505
|
+
const branch = this.getSelectedBranch();
|
|
3506
|
+
runCommandInherited(`clawt remove -b ${branch}`);
|
|
3507
|
+
}
|
|
3508
|
+
/**
|
|
3509
|
+
* 执行恢复操作
|
|
3510
|
+
*/
|
|
3511
|
+
handleResume() {
|
|
3512
|
+
const branch = this.getSelectedBranch();
|
|
3513
|
+
runCommandInherited(`clawt resume -b ${branch}`);
|
|
3514
|
+
}
|
|
3515
|
+
/**
|
|
3516
|
+
* 执行同步操作
|
|
3517
|
+
*/
|
|
3518
|
+
handleSync() {
|
|
3519
|
+
const branch = this.getSelectedBranch();
|
|
3520
|
+
runCommandInherited(`clawt sync -b ${branch}`);
|
|
3521
|
+
}
|
|
3522
|
+
/**
|
|
3523
|
+
* 等待用户按回车键
|
|
3524
|
+
* @returns {Promise<void>} 用户按回车时 resolve
|
|
3525
|
+
*/
|
|
3526
|
+
waitForEnter() {
|
|
3527
|
+
return new Promise((resolve3) => {
|
|
3528
|
+
const rl = createInterface2({
|
|
3529
|
+
input: process.stdin,
|
|
3530
|
+
output: process.stdout
|
|
3531
|
+
});
|
|
3532
|
+
rl.once("line", () => {
|
|
3533
|
+
rl.close();
|
|
3534
|
+
resolve3();
|
|
3535
|
+
});
|
|
3536
|
+
});
|
|
3537
|
+
}
|
|
3538
|
+
};
|
|
3539
|
+
|
|
2670
3540
|
// src/commands/list.ts
|
|
2671
|
-
import
|
|
3541
|
+
import chalk10 from "chalk";
|
|
2672
3542
|
function registerListCommand(program2) {
|
|
2673
|
-
program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
3543
|
+
program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree\uFF08\u652F\u6301 --json \u683C\u5F0F\u8F93\u51FA\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
2674
3544
|
handleList(options);
|
|
2675
3545
|
});
|
|
2676
3546
|
}
|
|
@@ -2705,12 +3575,12 @@ function printListAsText(projectName, worktrees) {
|
|
|
2705
3575
|
for (const wt of worktrees) {
|
|
2706
3576
|
const status = getWorktreeStatus(wt);
|
|
2707
3577
|
const isIdle = status ? isWorktreeIdle(status) : false;
|
|
2708
|
-
const pathDisplay = isIdle ?
|
|
3578
|
+
const pathDisplay = isIdle ? chalk10.hex("#FF8C00")(wt.path) : wt.path;
|
|
2709
3579
|
printInfo(` ${pathDisplay} [${wt.branch}]`);
|
|
2710
3580
|
if (status) {
|
|
2711
3581
|
printInfo(` ${formatWorktreeStatus(status)}`);
|
|
2712
3582
|
} else {
|
|
2713
|
-
printInfo(` ${
|
|
3583
|
+
printInfo(` ${chalk10.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
|
|
2714
3584
|
}
|
|
2715
3585
|
printInfo("");
|
|
2716
3586
|
}
|
|
@@ -2720,12 +3590,13 @@ function printListAsText(projectName, worktrees) {
|
|
|
2720
3590
|
|
|
2721
3591
|
// src/commands/create.ts
|
|
2722
3592
|
function registerCreateCommand(program2) {
|
|
2723
|
-
program2.command("create").description("\u6279\u91CF\u521B\u5EFA worktree \u53CA\u5BF9\u5E94\u5206\u652F").requiredOption("-b, --branch <branchName>", "\u5206\u652F\u540D").option("-n, --number <count>", "\u521B\u5EFA\u6570\u91CF", "1").action((options) => {
|
|
2724
|
-
handleCreate(options);
|
|
3593
|
+
program2.command("create").description("\u6279\u91CF\u521B\u5EFA worktree \u53CA\u5BF9\u5E94\u5206\u652F\uFF08\u542B\u9A8C\u8BC1\u5206\u652F\uFF09").requiredOption("-b, --branch <branchName>", "\u5206\u652F\u540D").option("-n, --number <count>", "\u521B\u5EFA\u6570\u91CF", "1").action(async (options) => {
|
|
3594
|
+
await handleCreate(options);
|
|
2725
3595
|
});
|
|
2726
3596
|
}
|
|
2727
|
-
function handleCreate(options) {
|
|
3597
|
+
async function handleCreate(options) {
|
|
2728
3598
|
validateMainWorktree();
|
|
3599
|
+
await ensureOnMainWorkBranch();
|
|
2729
3600
|
const count = Number(options.number);
|
|
2730
3601
|
if (!Number.isInteger(count) || count <= 0) {
|
|
2731
3602
|
throw new ClawtError(
|
|
@@ -2741,6 +3612,7 @@ function handleCreate(options) {
|
|
|
2741
3612
|
printInfo(`\u76EE\u5F55\u8DEF\u5F84${index + 1}\uFF1A`);
|
|
2742
3613
|
printInfo(` ${wt.path}`);
|
|
2743
3614
|
printInfo(` \u5206\u652F\u540D: ${wt.branch}`);
|
|
3615
|
+
printInfo(` \u9A8C\u8BC1\u5206\u652F: ${getValidateBranchName(wt.branch)}`);
|
|
2744
3616
|
printSeparator();
|
|
2745
3617
|
});
|
|
2746
3618
|
}
|
|
@@ -2753,7 +3625,7 @@ var REMOVE_RESOLVE_MESSAGES = {
|
|
|
2753
3625
|
noMatch: MESSAGES.REMOVE_NO_MATCH
|
|
2754
3626
|
};
|
|
2755
3627
|
function registerRemoveCommand(program2) {
|
|
2756
|
-
program2.command("remove").description("\u79FB\u9664 worktree\uFF08\u652F\u6301\
|
|
3628
|
+
program2.command("remove").description("\u79FB\u9664 worktree\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D/\u591A\u9009/\u5168\u90E8\uFF09").option("--all", "\u79FB\u9664\u5F53\u524D\u9879\u76EE\u4E0B\u6240\u6709 worktree").option("-b, --branch <branchName>", "\u6307\u5B9A\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").action(async (options) => {
|
|
2757
3629
|
await handleRemove(options);
|
|
2758
3630
|
});
|
|
2759
3631
|
}
|
|
@@ -2774,13 +3646,13 @@ async function handleRemove(options) {
|
|
|
2774
3646
|
}
|
|
2775
3647
|
printInfo("\u5373\u5C06\u79FB\u9664\u4EE5\u4E0B worktree \u53CA\u672C\u5730\u5206\u652F\uFF1A\n");
|
|
2776
3648
|
worktreesToRemove.forEach((wt, index) => {
|
|
2777
|
-
printInfo(` ${index + 1}. ${wt.path} \u2192 \u5206\u652F: ${wt.branch}`);
|
|
3649
|
+
printInfo(` ${index + 1}. ${wt.path} \u2192 \u5206\u652F: ${wt.branch} \u9A8C\u8BC1\u5206\u652F: ${getValidateBranchName(wt.branch)}`);
|
|
2778
3650
|
});
|
|
2779
3651
|
printInfo("");
|
|
2780
3652
|
const autoDelete = getConfigValue("autoDeleteBranch");
|
|
2781
3653
|
let shouldDeleteBranch = autoDelete;
|
|
2782
3654
|
if (!autoDelete) {
|
|
2783
|
-
shouldDeleteBranch = await confirmAction(
|
|
3655
|
+
shouldDeleteBranch = await confirmAction(MESSAGES.REMOVE_CONFIRM_DELETE_BRANCHES);
|
|
2784
3656
|
if (!shouldDeleteBranch) {
|
|
2785
3657
|
printHint(MESSAGES.REMOVE_BRANCHES_KEPT);
|
|
2786
3658
|
}
|
|
@@ -2788,10 +3660,12 @@ async function handleRemove(options) {
|
|
|
2788
3660
|
const failures = [];
|
|
2789
3661
|
for (const wt of worktreesToRemove) {
|
|
2790
3662
|
try {
|
|
3663
|
+
await ensureOnMainWorkBranch();
|
|
2791
3664
|
removeWorktreeByPath(wt.path);
|
|
2792
3665
|
if (shouldDeleteBranch) {
|
|
2793
3666
|
deleteBranch(wt.branch);
|
|
2794
3667
|
}
|
|
3668
|
+
deleteValidateBranch(wt.branch);
|
|
2795
3669
|
removeSnapshot(projectName, wt.branch);
|
|
2796
3670
|
printSuccess(MESSAGES.WORKTREE_REMOVED(wt.path));
|
|
2797
3671
|
} catch (error) {
|
|
@@ -2816,7 +3690,7 @@ async function handleRemove(options) {
|
|
|
2816
3690
|
|
|
2817
3691
|
// src/commands/run.ts
|
|
2818
3692
|
function registerRunCommand(program2) {
|
|
2819
|
-
program2.command("run").description("\u6279\u91CF\u521B\u5EFA worktree \
|
|
3693
|
+
program2.command("run").description("\u6279\u91CF\u521B\u5EFA worktree + \u542F\u52A8 Claude Code \u6267\u884C\u4EFB\u52A1\uFF08\u652F\u6301\u4EFB\u52A1\u6587\u4EF6\uFF09").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("--dry-run", "\u9884\u89C8\u6A21\u5F0F\uFF0C\u4EC5\u5C55\u793A\u4EFB\u52A1\u8BA1\u5212\u4E0D\u5B9E\u9645\u6267\u884C").action(async (options) => {
|
|
2820
3694
|
await handleRun(options);
|
|
2821
3695
|
});
|
|
2822
3696
|
}
|
|
@@ -2854,6 +3728,9 @@ function handleDryRunFromFile(options) {
|
|
|
2854
3728
|
}
|
|
2855
3729
|
async function handleRun(options) {
|
|
2856
3730
|
validateMainWorktree();
|
|
3731
|
+
if (!options.dryRun) {
|
|
3732
|
+
await ensureOnMainWorkBranch();
|
|
3733
|
+
}
|
|
2857
3734
|
if (options.file && options.tasks) {
|
|
2858
3735
|
throw new ClawtError(MESSAGES.FILE_AND_TASKS_CONFLICT);
|
|
2859
3736
|
}
|
|
@@ -2909,7 +3786,7 @@ var RESUME_RESOLVE_MESSAGES = {
|
|
|
2909
3786
|
noMatch: MESSAGES.RESUME_NO_MATCH
|
|
2910
3787
|
};
|
|
2911
3788
|
function registerResumeCommand(program2) {
|
|
2912
|
-
program2.command("resume").description("\u5728\u5DF2\u6709 worktree \u4E2D\u6062\u590D Claude Code \
|
|
3789
|
+
program2.command("resume").description("\u5728\u5DF2\u6709 worktree \u4E2D\u6062\u590D Claude Code \u4F1A\u8BDD\uFF08\u652F\u6301\u591A\u9009\u6279\u91CF\u6062\u590D\uFF09").option("-b, --branch <branchName>", "\u8981\u6062\u590D\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) => {
|
|
2913
3790
|
await handleResume(options);
|
|
2914
3791
|
});
|
|
2915
3792
|
}
|
|
@@ -2961,9 +3838,6 @@ async function handleBatchResume(worktrees) {
|
|
|
2961
3838
|
printSuccess(MESSAGES.RESUME_ALL_SUCCESS(worktrees.length));
|
|
2962
3839
|
}
|
|
2963
3840
|
|
|
2964
|
-
// src/commands/validate.ts
|
|
2965
|
-
import Enquirer4 from "enquirer";
|
|
2966
|
-
|
|
2967
3841
|
// src/commands/sync.ts
|
|
2968
3842
|
function registerSyncCommand(program2) {
|
|
2969
3843
|
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) => {
|
|
@@ -2993,9 +3867,9 @@ function mergeMainBranch(worktreePath, mainBranch) {
|
|
|
2993
3867
|
throw new ClawtError(`\u5408\u5E76 ${mainBranch} \u5931\u8D25`);
|
|
2994
3868
|
}
|
|
2995
3869
|
}
|
|
2996
|
-
function executeSyncForBranch(targetWorktreePath, branch) {
|
|
3870
|
+
async function executeSyncForBranch(targetWorktreePath, branch) {
|
|
2997
3871
|
const mainWorktreePath = getGitTopLevel();
|
|
2998
|
-
const mainBranch =
|
|
3872
|
+
const mainBranch = getMainWorkBranch();
|
|
2999
3873
|
if (!isWorkingDirClean(targetWorktreePath)) {
|
|
3000
3874
|
autoSaveChanges(targetWorktreePath, branch);
|
|
3001
3875
|
}
|
|
@@ -3011,15 +3885,20 @@ function executeSyncForBranch(targetWorktreePath, branch) {
|
|
|
3011
3885
|
logger.info(`\u5DF2\u6E05\u9664\u5206\u652F ${branch} \u7684 validate \u5FEB\u7167`);
|
|
3012
3886
|
}
|
|
3013
3887
|
printSuccess(MESSAGES.SYNC_SUCCESS(branch, mainBranch));
|
|
3888
|
+
await rebuildValidateBranch(branch, mainWorktreePath);
|
|
3889
|
+
const validateBranchName = getValidateBranchName(branch);
|
|
3890
|
+
printInfo(MESSAGES.SYNC_VALIDATE_BRANCH_REBUILT(validateBranchName));
|
|
3014
3891
|
return { success: true, hasConflict: false };
|
|
3015
3892
|
}
|
|
3016
3893
|
async function handleSync(options) {
|
|
3017
3894
|
validateMainWorktree();
|
|
3895
|
+
requireProjectConfig();
|
|
3896
|
+
await ensureOnMainWorkBranch();
|
|
3018
3897
|
logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
|
|
3019
3898
|
const worktrees = getProjectWorktrees();
|
|
3020
3899
|
const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
|
|
3021
3900
|
const { path: targetWorktreePath, branch } = worktree;
|
|
3022
|
-
executeSyncForBranch(targetWorktreePath, branch);
|
|
3901
|
+
await executeSyncForBranch(targetWorktreePath, branch);
|
|
3023
3902
|
}
|
|
3024
3903
|
|
|
3025
3904
|
// src/commands/validate.ts
|
|
@@ -3030,43 +3909,12 @@ var VALIDATE_RESOLVE_MESSAGES = {
|
|
|
3030
3909
|
noMatch: MESSAGES.VALIDATE_NO_MATCH
|
|
3031
3910
|
};
|
|
3032
3911
|
function registerValidateCommand(program2) {
|
|
3033
|
-
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) => {
|
|
3912
|
+
program2.command("validate").description("\u5728\u4E3B worktree \u9A8C\u8BC1\u67D0\u4E2A worktree \u5206\u652F\u7684\u53D8\u66F4\uFF08\u901A\u8FC7\u9A8C\u8BC1\u5206\u652F\uFF09").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) => {
|
|
3034
3913
|
await handleValidate(options);
|
|
3035
3914
|
});
|
|
3036
3915
|
}
|
|
3037
3916
|
async function handleDirtyMainWorktree(mainWorktreePath) {
|
|
3038
|
-
|
|
3039
|
-
const choice = await new Enquirer4.Select({
|
|
3040
|
-
message: "\u9009\u62E9\u5904\u7406\u65B9\u5F0F",
|
|
3041
|
-
choices: [
|
|
3042
|
-
{
|
|
3043
|
-
name: "reset",
|
|
3044
|
-
message: "reset (\u63A8\u8350) - \u4E22\u5F03\u6240\u6709\u66F4\u6539 (git reset --hard HEAD && git clean -fd)"
|
|
3045
|
-
},
|
|
3046
|
-
{
|
|
3047
|
-
name: "stash",
|
|
3048
|
-
message: "stash - \u6682\u5B58\u66F4\u6539 (git add . && git stash)"
|
|
3049
|
-
},
|
|
3050
|
-
{
|
|
3051
|
-
name: "exit",
|
|
3052
|
-
message: "exit - \u9000\u51FA\uFF0C\u624B\u52A8\u5904\u7406"
|
|
3053
|
-
}
|
|
3054
|
-
],
|
|
3055
|
-
initial: 0
|
|
3056
|
-
}).run();
|
|
3057
|
-
if (choice === "exit") {
|
|
3058
|
-
throw new ClawtError("\u7528\u6237\u9009\u62E9\u9000\u51FA");
|
|
3059
|
-
}
|
|
3060
|
-
if (choice === "reset") {
|
|
3061
|
-
gitResetHard(mainWorktreePath);
|
|
3062
|
-
gitCleanForce(mainWorktreePath);
|
|
3063
|
-
} else if (choice === "stash") {
|
|
3064
|
-
gitAddAll(mainWorktreePath);
|
|
3065
|
-
gitStashPush("clawt:auto-stash", mainWorktreePath);
|
|
3066
|
-
}
|
|
3067
|
-
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
3068
|
-
throw new ClawtError("\u5DE5\u4F5C\u533A\u4ECD\u7136\u4E0D\u5E72\u51C0\uFF0C\u8BF7\u624B\u52A8\u5904\u7406");
|
|
3069
|
-
}
|
|
3917
|
+
await handleDirtyWorkingDir(mainWorktreePath);
|
|
3070
3918
|
}
|
|
3071
3919
|
function migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted) {
|
|
3072
3920
|
let didTempCommit = false;
|
|
@@ -3109,7 +3957,7 @@ async function handlePatchApplyFailure(targetWorktreePath, branchName) {
|
|
|
3109
3957
|
return;
|
|
3110
3958
|
}
|
|
3111
3959
|
printInfo(MESSAGES.VALIDATE_AUTO_SYNC_START(branchName));
|
|
3112
|
-
const syncResult = executeSyncForBranch(targetWorktreePath, branchName);
|
|
3960
|
+
const syncResult = await executeSyncForBranch(targetWorktreePath, branchName);
|
|
3113
3961
|
}
|
|
3114
3962
|
function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName) {
|
|
3115
3963
|
gitAddAll(mainWorktreePath);
|
|
@@ -3121,6 +3969,7 @@ function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName) {
|
|
|
3121
3969
|
}
|
|
3122
3970
|
async function handleValidateClean(options) {
|
|
3123
3971
|
validateMainWorktree();
|
|
3972
|
+
requireProjectConfig();
|
|
3124
3973
|
const projectName = getProjectName();
|
|
3125
3974
|
const mainWorktreePath = getGitTopLevel();
|
|
3126
3975
|
const worktrees = getProjectWorktrees();
|
|
@@ -3141,17 +3990,24 @@ async function handleValidateClean(options) {
|
|
|
3141
3990
|
gitResetHard(mainWorktreePath);
|
|
3142
3991
|
gitCleanForce(mainWorktreePath);
|
|
3143
3992
|
}
|
|
3993
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
3144
3994
|
removeSnapshot(projectName, branchName);
|
|
3145
3995
|
printSuccess(MESSAGES.VALIDATE_CLEANED(branchName));
|
|
3146
3996
|
}
|
|
3147
3997
|
async function handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
|
|
3998
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
3999
|
+
if (!checkBranchExists(validateBranchName)) {
|
|
4000
|
+
throw new ClawtError(MESSAGES.VALIDATE_BRANCH_NOT_FOUND(validateBranchName, branchName));
|
|
4001
|
+
}
|
|
4002
|
+
gitCheckout(validateBranchName, mainWorktreePath);
|
|
3148
4003
|
const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
3149
4004
|
if (!result.success) {
|
|
4005
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
3150
4006
|
await handlePatchApplyFailure(targetWorktreePath, branchName);
|
|
3151
4007
|
return;
|
|
3152
4008
|
}
|
|
3153
4009
|
saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName);
|
|
3154
|
-
printSuccess(MESSAGES.
|
|
4010
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
3155
4011
|
}
|
|
3156
4012
|
async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
|
|
3157
4013
|
const { treeHash: oldTreeHash, headCommitHash: oldHeadCommitHash } = readSnapshot(projectName, branchName);
|
|
@@ -3159,8 +4015,17 @@ async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, p
|
|
|
3159
4015
|
gitResetHard(mainWorktreePath);
|
|
3160
4016
|
gitCleanForce(mainWorktreePath);
|
|
3161
4017
|
}
|
|
4018
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
4019
|
+
if (!checkBranchExists(validateBranchName)) {
|
|
4020
|
+
throw new ClawtError(MESSAGES.VALIDATE_BRANCH_NOT_FOUND(validateBranchName, branchName));
|
|
4021
|
+
}
|
|
4022
|
+
const currentBranch = getCurrentBranch(mainWorktreePath);
|
|
4023
|
+
if (currentBranch !== validateBranchName) {
|
|
4024
|
+
gitCheckout(validateBranchName, mainWorktreePath);
|
|
4025
|
+
}
|
|
3162
4026
|
const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
3163
4027
|
if (!result.success) {
|
|
4028
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
3164
4029
|
await handlePatchApplyFailure(targetWorktreePath, branchName);
|
|
3165
4030
|
return;
|
|
3166
4031
|
}
|
|
@@ -3175,7 +4040,7 @@ async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, p
|
|
|
3175
4040
|
} else if (oldChangePatch.length > 0) {
|
|
3176
4041
|
logger.warn("\u65E7\u53D8\u66F4 patch \u4E0E\u5F53\u524D HEAD \u51B2\u7A81\uFF0C\u964D\u7EA7\u4E3A\u5168\u91CF\u6A21\u5F0F");
|
|
3177
4042
|
printWarning(MESSAGES.INCREMENTAL_VALIDATE_FALLBACK);
|
|
3178
|
-
printSuccess(MESSAGES.
|
|
4043
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
3179
4044
|
return;
|
|
3180
4045
|
}
|
|
3181
4046
|
} else {
|
|
@@ -3184,7 +4049,7 @@ async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, p
|
|
|
3184
4049
|
} catch (error) {
|
|
3185
4050
|
logger.warn(`\u589E\u91CF read-tree \u5931\u8D25: ${error}`);
|
|
3186
4051
|
printWarning(MESSAGES.INCREMENTAL_VALIDATE_FALLBACK);
|
|
3187
|
-
printSuccess(MESSAGES.
|
|
4052
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
3188
4053
|
return;
|
|
3189
4054
|
}
|
|
3190
4055
|
printSuccess(MESSAGES.INCREMENTAL_VALIDATE_SUCCESS(branchName));
|
|
@@ -3248,6 +4113,7 @@ async function handleValidate(options) {
|
|
|
3248
4113
|
return;
|
|
3249
4114
|
}
|
|
3250
4115
|
validateMainWorktree();
|
|
4116
|
+
requireProjectConfig();
|
|
3251
4117
|
const projectName = getProjectName();
|
|
3252
4118
|
const mainWorktreePath = getGitTopLevel();
|
|
3253
4119
|
const worktrees = getProjectWorktrees();
|
|
@@ -3280,7 +4146,7 @@ async function handleValidate(options) {
|
|
|
3280
4146
|
|
|
3281
4147
|
// src/commands/merge.ts
|
|
3282
4148
|
function registerMergeCommand(program2) {
|
|
3283
|
-
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\uFF09").option("-m, --message <
|
|
4149
|
+
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) => {
|
|
3284
4150
|
await handleMerge(options);
|
|
3285
4151
|
});
|
|
3286
4152
|
}
|
|
@@ -3298,7 +4164,7 @@ async function handleSquashIfNeeded(targetWorktreePath, mainWorktreePath, branch
|
|
|
3298
4164
|
if (!shouldSquash) {
|
|
3299
4165
|
return false;
|
|
3300
4166
|
}
|
|
3301
|
-
const mainBranch =
|
|
4167
|
+
const mainBranch = getMainWorkBranch();
|
|
3302
4168
|
const mergeBase = gitMergeBase(mainBranch, branchName, mainWorktreePath);
|
|
3303
4169
|
logger.info(`squash: merge-base = ${mergeBase}, \u5206\u652F = ${branchName}`);
|
|
3304
4170
|
gitResetSoftTo(mergeBase, targetWorktreePath);
|
|
@@ -3325,6 +4191,7 @@ function cleanupWorktreeAndBranch(worktreePath, branchName) {
|
|
|
3325
4191
|
async function handleMerge(options) {
|
|
3326
4192
|
validateMainWorktree();
|
|
3327
4193
|
const mainWorktreePath = getGitTopLevel();
|
|
4194
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
3328
4195
|
logger.info(`merge \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}\uFF0C\u63D0\u4EA4\u4FE1\u606F: ${options.message ?? "(\u672A\u63D0\u4F9B)"}`);
|
|
3329
4196
|
const worktrees = getProjectWorktrees();
|
|
3330
4197
|
const worktree = await resolveTargetWorktree(worktrees, MERGE_RESOLVE_MESSAGES, options.branch);
|
|
@@ -3398,7 +4265,7 @@ async function handleMerge(options) {
|
|
|
3398
4265
|
}
|
|
3399
4266
|
|
|
3400
4267
|
// src/commands/config.ts
|
|
3401
|
-
import
|
|
4268
|
+
import chalk11 from "chalk";
|
|
3402
4269
|
import Enquirer5 from "enquirer";
|
|
3403
4270
|
function registerConfigCommand(program2) {
|
|
3404
4271
|
const configCmd = program2.command("config").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u5168\u5C40\u914D\u7F6E").action(async () => {
|
|
@@ -3459,7 +4326,7 @@ async function handleInteractiveConfigSet() {
|
|
|
3459
4326
|
const isObject = typeof DEFAULT_CONFIG[k] === "object";
|
|
3460
4327
|
return {
|
|
3461
4328
|
name: k,
|
|
3462
|
-
message: `${k}: ${isObject ?
|
|
4329
|
+
message: `${k}: ${isObject ? chalk11.dim(JSON.stringify(config2[k])) : formatConfigValue(config2[k])} ${chalk11.dim(`\u2014 ${CONFIG_DESCRIPTIONS[k]}`)}`,
|
|
3463
4330
|
...isObject && { disabled: CONFIG_ALIAS_DISABLED_HINT }
|
|
3464
4331
|
};
|
|
3465
4332
|
});
|
|
@@ -3492,6 +4359,7 @@ function registerResetCommand(program2) {
|
|
|
3492
4359
|
}
|
|
3493
4360
|
async function handleReset() {
|
|
3494
4361
|
validateMainWorktree();
|
|
4362
|
+
requireProjectConfig();
|
|
3495
4363
|
const mainWorktreePath = getGitTopLevel();
|
|
3496
4364
|
logger.info("reset \u547D\u4EE4\u6267\u884C");
|
|
3497
4365
|
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
@@ -3514,14 +4382,19 @@ async function handleReset() {
|
|
|
3514
4382
|
}
|
|
3515
4383
|
|
|
3516
4384
|
// src/commands/status.ts
|
|
3517
|
-
import
|
|
4385
|
+
import chalk12 from "chalk";
|
|
3518
4386
|
function registerStatusCommand(program2) {
|
|
3519
|
-
program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
3520
|
-
handleStatus(options);
|
|
4387
|
+
program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8\uFF08\u652F\u6301 --json \u683C\u5F0F\u8F93\u51FA\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").option("-i, --interactive", "\u4EA4\u4E92\u5F0F\u9762\u677F\u6A21\u5F0F").action(async (options) => {
|
|
4388
|
+
await handleStatus(options);
|
|
3521
4389
|
});
|
|
3522
4390
|
}
|
|
3523
|
-
function handleStatus(options) {
|
|
4391
|
+
async function handleStatus(options) {
|
|
3524
4392
|
validateMainWorktree();
|
|
4393
|
+
if (options.interactive) {
|
|
4394
|
+
const panel = new InteractivePanel(collectStatus);
|
|
4395
|
+
await panel.start();
|
|
4396
|
+
return;
|
|
4397
|
+
}
|
|
3525
4398
|
const statusResult = collectStatus();
|
|
3526
4399
|
logger.info(`status \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${statusResult.main.projectName}\uFF0C\u5171 ${statusResult.totalWorktrees} \u4E2A worktree`);
|
|
3527
4400
|
if (options.json) {
|
|
@@ -3627,7 +4500,7 @@ function printStatusAsJson(result) {
|
|
|
3627
4500
|
}
|
|
3628
4501
|
function printStatusAsText(result) {
|
|
3629
4502
|
printDoubleSeparator();
|
|
3630
|
-
printInfo(` ${
|
|
4503
|
+
printInfo(` ${chalk12.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
|
|
3631
4504
|
printDoubleSeparator();
|
|
3632
4505
|
printInfo("");
|
|
3633
4506
|
printMainSection(result.main);
|
|
@@ -3640,17 +4513,17 @@ function printStatusAsText(result) {
|
|
|
3640
4513
|
printDoubleSeparator();
|
|
3641
4514
|
}
|
|
3642
4515
|
function printMainSection(main2) {
|
|
3643
|
-
printInfo(` ${
|
|
3644
|
-
printInfo(` \u5206\u652F: ${
|
|
4516
|
+
printInfo(` ${chalk12.bold("\u25C6")} ${chalk12.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
|
|
4517
|
+
printInfo(` \u5206\u652F: ${chalk12.bold(main2.branch)}`);
|
|
3645
4518
|
if (main2.isClean) {
|
|
3646
|
-
printInfo(` \u72B6\u6001: ${
|
|
4519
|
+
printInfo(` \u72B6\u6001: ${chalk12.green("\u2713 \u5E72\u51C0")}`);
|
|
3647
4520
|
} else {
|
|
3648
|
-
printInfo(` \u72B6\u6001: ${
|
|
4521
|
+
printInfo(` \u72B6\u6001: ${chalk12.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
|
|
3649
4522
|
}
|
|
3650
4523
|
printInfo("");
|
|
3651
4524
|
}
|
|
3652
4525
|
function printWorktreesSection(worktrees, total) {
|
|
3653
|
-
printInfo(` ${
|
|
4526
|
+
printInfo(` ${chalk12.bold("\u25C6")} ${chalk12.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
|
|
3654
4527
|
printInfo("");
|
|
3655
4528
|
if (worktrees.length === 0) {
|
|
3656
4529
|
printInfo(` ${MESSAGES.STATUS_NO_WORKTREES}`);
|
|
@@ -3661,57 +4534,57 @@ function printWorktreesSection(worktrees, total) {
|
|
|
3661
4534
|
}
|
|
3662
4535
|
}
|
|
3663
4536
|
function printWorktreeItem(wt) {
|
|
3664
|
-
const statusLabel =
|
|
3665
|
-
printInfo(` ${
|
|
4537
|
+
const statusLabel = formatChangeStatusLabel2(wt.changeStatus);
|
|
4538
|
+
printInfo(` ${chalk12.bold("\u25CF")} ${chalk12.bold(wt.branch)} [${statusLabel}]`);
|
|
3666
4539
|
if (wt.insertions > 0 || wt.deletions > 0) {
|
|
3667
|
-
printInfo(` ${
|
|
4540
|
+
printInfo(` ${chalk12.green(`+${wt.insertions}`)} ${chalk12.red(`-${wt.deletions}`)}`);
|
|
3668
4541
|
}
|
|
3669
4542
|
if (wt.commitsAhead > 0) {
|
|
3670
|
-
printInfo(` ${
|
|
4543
|
+
printInfo(` ${chalk12.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`)}`);
|
|
3671
4544
|
}
|
|
3672
4545
|
if (wt.commitsBehind > 0) {
|
|
3673
|
-
printInfo(` ${
|
|
4546
|
+
printInfo(` ${chalk12.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`)}`);
|
|
3674
4547
|
} else {
|
|
3675
|
-
printInfo(` ${
|
|
4548
|
+
printInfo(` ${chalk12.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65")}`);
|
|
3676
4549
|
}
|
|
3677
4550
|
if (wt.createdAt) {
|
|
3678
4551
|
const relativeTime = formatRelativeTime(wt.createdAt);
|
|
3679
4552
|
if (relativeTime) {
|
|
3680
|
-
printInfo(` ${
|
|
4553
|
+
printInfo(` ${chalk12.gray(MESSAGES.STATUS_CREATED_AT(relativeTime))}`);
|
|
3681
4554
|
}
|
|
3682
4555
|
}
|
|
3683
4556
|
if (wt.snapshotTime) {
|
|
3684
4557
|
const relativeTime = formatRelativeTime(wt.snapshotTime);
|
|
3685
4558
|
if (relativeTime) {
|
|
3686
|
-
printInfo(` ${
|
|
4559
|
+
printInfo(` ${chalk12.green(MESSAGES.STATUS_LAST_VALIDATED(relativeTime))}`);
|
|
3687
4560
|
}
|
|
3688
4561
|
} else {
|
|
3689
|
-
printInfo(` ${
|
|
4562
|
+
printInfo(` ${chalk12.red(MESSAGES.STATUS_NOT_VALIDATED)}`);
|
|
3690
4563
|
}
|
|
3691
4564
|
printInfo("");
|
|
3692
4565
|
}
|
|
3693
|
-
function
|
|
4566
|
+
function formatChangeStatusLabel2(status) {
|
|
3694
4567
|
switch (status) {
|
|
3695
4568
|
case "committed":
|
|
3696
|
-
return
|
|
4569
|
+
return chalk12.green(MESSAGES.STATUS_CHANGE_COMMITTED);
|
|
3697
4570
|
case "uncommitted":
|
|
3698
|
-
return
|
|
4571
|
+
return chalk12.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
|
|
3699
4572
|
case "conflict":
|
|
3700
|
-
return
|
|
4573
|
+
return chalk12.red(MESSAGES.STATUS_CHANGE_CONFLICT);
|
|
3701
4574
|
case "clean":
|
|
3702
|
-
return
|
|
4575
|
+
return chalk12.gray(MESSAGES.STATUS_CHANGE_CLEAN);
|
|
3703
4576
|
}
|
|
3704
4577
|
}
|
|
3705
4578
|
function printSnapshotsSection(snapshots) {
|
|
3706
|
-
printInfo(` ${
|
|
4579
|
+
printInfo(` ${chalk12.bold("\u25C6")} ${chalk12.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.total} \u4E2A)`);
|
|
3707
4580
|
if (snapshots.orphaned > 0) {
|
|
3708
|
-
printInfo(` ${
|
|
4581
|
+
printInfo(` ${chalk12.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED(snapshots.orphaned))}`);
|
|
3709
4582
|
}
|
|
3710
4583
|
printInfo("");
|
|
3711
4584
|
}
|
|
3712
4585
|
|
|
3713
4586
|
// src/commands/alias.ts
|
|
3714
|
-
import
|
|
4587
|
+
import chalk13 from "chalk";
|
|
3715
4588
|
function getRegisteredCommandNames(program2) {
|
|
3716
4589
|
return program2.commands.map((cmd) => cmd.name());
|
|
3717
4590
|
}
|
|
@@ -3732,7 +4605,7 @@ ${MESSAGES.ALIAS_LIST_TITLE}
|
|
|
3732
4605
|
`);
|
|
3733
4606
|
printSeparator();
|
|
3734
4607
|
for (const [alias, command] of entries) {
|
|
3735
|
-
printInfo(` ${
|
|
4608
|
+
printInfo(` ${chalk13.bold(alias)} \u2192 ${chalk13.cyan(command)}`);
|
|
3736
4609
|
}
|
|
3737
4610
|
printInfo("");
|
|
3738
4611
|
printSeparator();
|
|
@@ -3764,7 +4637,7 @@ function handleAliasRemove(alias) {
|
|
|
3764
4637
|
printSuccess(MESSAGES.ALIAS_REMOVE_SUCCESS(alias));
|
|
3765
4638
|
}
|
|
3766
4639
|
function registerAliasCommand(program2) {
|
|
3767
|
-
const aliasCmd = program2.command("alias").description("\u7BA1\u7406\u547D\u4EE4\u522B\u540D").action(() => {
|
|
4640
|
+
const aliasCmd = program2.command("alias").description("\u7BA1\u7406\u547D\u4EE4\u522B\u540D\uFF08\u5217\u51FA / \u8BBE\u7F6E / \u79FB\u9664\uFF09").action(() => {
|
|
3768
4641
|
handleAliasList();
|
|
3769
4642
|
});
|
|
3770
4643
|
aliasCmd.command("list").description("\u5217\u51FA\u6240\u6709\u522B\u540D").action(() => {
|
|
@@ -3779,9 +4652,9 @@ function registerAliasCommand(program2) {
|
|
|
3779
4652
|
}
|
|
3780
4653
|
|
|
3781
4654
|
// src/commands/projects.ts
|
|
3782
|
-
import { existsSync as
|
|
3783
|
-
import { join as
|
|
3784
|
-
import
|
|
4655
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
4656
|
+
import { join as join8 } from "path";
|
|
4657
|
+
import chalk14 from "chalk";
|
|
3785
4658
|
function registerProjectsCommand(program2) {
|
|
3786
4659
|
program2.command("projects [name]").description("\u5C55\u793A\u6240\u6709\u9879\u76EE\u7684 worktree \u6982\u89C8\uFF0C\u6216\u67E5\u770B\u6307\u5B9A\u9879\u76EE\u7684 worktree \u8BE6\u60C5").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((name, options) => {
|
|
3787
4660
|
handleProjects({ name, json: options.json });
|
|
@@ -3804,8 +4677,8 @@ function handleProjectsOverview(json) {
|
|
|
3804
4677
|
printProjectsOverviewAsText(result);
|
|
3805
4678
|
}
|
|
3806
4679
|
function handleProjectDetail(name, json) {
|
|
3807
|
-
const projectDir =
|
|
3808
|
-
if (!
|
|
4680
|
+
const projectDir = join8(WORKTREES_DIR, name);
|
|
4681
|
+
if (!existsSync10(projectDir)) {
|
|
3809
4682
|
printError(MESSAGES.PROJECTS_NOT_FOUND(name));
|
|
3810
4683
|
process.exit(1);
|
|
3811
4684
|
}
|
|
@@ -3818,7 +4691,7 @@ function handleProjectDetail(name, json) {
|
|
|
3818
4691
|
printProjectDetailAsText(result);
|
|
3819
4692
|
}
|
|
3820
4693
|
function collectProjectsOverview() {
|
|
3821
|
-
if (!
|
|
4694
|
+
if (!existsSync10(WORKTREES_DIR)) {
|
|
3822
4695
|
return { projects: [], totalProjects: 0, totalDiskUsage: 0 };
|
|
3823
4696
|
}
|
|
3824
4697
|
const entries = readdirSync5(WORKTREES_DIR, { withFileTypes: true });
|
|
@@ -3827,7 +4700,7 @@ function collectProjectsOverview() {
|
|
|
3827
4700
|
if (!entry.isDirectory()) {
|
|
3828
4701
|
continue;
|
|
3829
4702
|
}
|
|
3830
|
-
const projectDir =
|
|
4703
|
+
const projectDir = join8(WORKTREES_DIR, entry.name);
|
|
3831
4704
|
const overview = collectSingleProjectOverview(entry.name, projectDir);
|
|
3832
4705
|
projects.push(overview);
|
|
3833
4706
|
}
|
|
@@ -3844,7 +4717,7 @@ function collectSingleProjectOverview(name, projectDir) {
|
|
|
3844
4717
|
const worktreeDirs = subEntries.filter((e) => e.isDirectory());
|
|
3845
4718
|
const worktreeCount = worktreeDirs.length;
|
|
3846
4719
|
const diskUsage = calculateDirSize(projectDir);
|
|
3847
|
-
const lastActiveTime = resolveProjectLastActiveTime(projectDir, worktreeDirs.map((e) =>
|
|
4720
|
+
const lastActiveTime = resolveProjectLastActiveTime(projectDir, worktreeDirs.map((e) => join8(projectDir, e.name)));
|
|
3848
4721
|
return {
|
|
3849
4722
|
name,
|
|
3850
4723
|
worktreeCount,
|
|
@@ -3859,7 +4732,7 @@ function collectProjectDetail(name, projectDir) {
|
|
|
3859
4732
|
if (!entry.isDirectory()) {
|
|
3860
4733
|
continue;
|
|
3861
4734
|
}
|
|
3862
|
-
const wtPath =
|
|
4735
|
+
const wtPath = join8(projectDir, entry.name);
|
|
3863
4736
|
const detail = collectSingleWorktreeDetail(entry.name, wtPath);
|
|
3864
4737
|
worktrees.push(detail);
|
|
3865
4738
|
}
|
|
@@ -3900,7 +4773,7 @@ function sortByLastActiveTimeDesc(projects) {
|
|
|
3900
4773
|
}
|
|
3901
4774
|
function printProjectsOverviewAsText(result) {
|
|
3902
4775
|
printDoubleSeparator();
|
|
3903
|
-
printInfo(` ${
|
|
4776
|
+
printInfo(` ${chalk14.bold.cyan(MESSAGES.PROJECTS_OVERVIEW_TITLE)}`);
|
|
3904
4777
|
printDoubleSeparator();
|
|
3905
4778
|
printInfo("");
|
|
3906
4779
|
if (result.projects.length === 0) {
|
|
@@ -3914,7 +4787,7 @@ function printProjectsOverviewAsText(result) {
|
|
|
3914
4787
|
}
|
|
3915
4788
|
printSeparator();
|
|
3916
4789
|
printInfo("");
|
|
3917
|
-
printInfo(` \u5171 ${
|
|
4790
|
+
printInfo(` \u5171 ${chalk14.bold(String(result.totalProjects))} \u4E2A\u9879\u76EE ${chalk14.gray(MESSAGES.PROJECTS_TOTAL_DISK_USAGE(formatDiskSize(result.totalDiskUsage)))}`);
|
|
3918
4791
|
printInfo("");
|
|
3919
4792
|
printDoubleSeparator();
|
|
3920
4793
|
}
|
|
@@ -3922,16 +4795,16 @@ function printProjectOverviewItem(project) {
|
|
|
3922
4795
|
const relativeTime = formatRelativeTime(project.lastActiveTime);
|
|
3923
4796
|
const activeLabel = relativeTime ? MESSAGES.PROJECTS_LAST_ACTIVE(relativeTime) : "";
|
|
3924
4797
|
const diskLabel = MESSAGES.PROJECTS_DISK_USAGE(formatDiskSize(project.diskUsage));
|
|
3925
|
-
printInfo(` ${
|
|
3926
|
-
printInfo(` ${MESSAGES.PROJECTS_WORKTREE_COUNT(project.worktreeCount)} ${
|
|
4798
|
+
printInfo(` ${chalk14.bold("\u25CF")} ${chalk14.bold(project.name)}`);
|
|
4799
|
+
printInfo(` ${MESSAGES.PROJECTS_WORKTREE_COUNT(project.worktreeCount)} ${chalk14.gray(activeLabel)} ${chalk14.gray(diskLabel)}`);
|
|
3927
4800
|
printInfo("");
|
|
3928
4801
|
}
|
|
3929
4802
|
function printProjectDetailAsText(result) {
|
|
3930
4803
|
printDoubleSeparator();
|
|
3931
|
-
printInfo(` ${
|
|
4804
|
+
printInfo(` ${chalk14.bold.cyan(MESSAGES.PROJECTS_DETAIL_TITLE(result.name))}`);
|
|
3932
4805
|
printDoubleSeparator();
|
|
3933
4806
|
printInfo("");
|
|
3934
|
-
printInfo(` ${
|
|
4807
|
+
printInfo(` ${chalk14.bold("\u25C6")} ${chalk14.bold(MESSAGES.PROJECTS_PATH(result.projectDir))}`);
|
|
3935
4808
|
printInfo(` ${MESSAGES.PROJECTS_TOTAL_DISK_USAGE(formatDiskSize(result.totalDiskUsage))}`);
|
|
3936
4809
|
printInfo("");
|
|
3937
4810
|
printSeparator();
|
|
@@ -3951,14 +4824,14 @@ function printWorktreeDetailItem(wt) {
|
|
|
3951
4824
|
const relativeTime = formatRelativeTime(wt.lastModifiedTime);
|
|
3952
4825
|
const modifiedLabel = relativeTime ? MESSAGES.PROJECTS_LAST_MODIFIED(relativeTime) : "";
|
|
3953
4826
|
const diskLabel = MESSAGES.PROJECTS_DISK_USAGE(formatDiskSize(wt.diskUsage));
|
|
3954
|
-
printInfo(` ${
|
|
4827
|
+
printInfo(` ${chalk14.bold("\u25CF")} ${chalk14.bold(wt.branch)}`);
|
|
3955
4828
|
printInfo(` ${wt.path}`);
|
|
3956
|
-
printInfo(` ${
|
|
4829
|
+
printInfo(` ${chalk14.gray(modifiedLabel)} ${chalk14.gray(diskLabel)}`);
|
|
3957
4830
|
printInfo("");
|
|
3958
4831
|
}
|
|
3959
4832
|
|
|
3960
4833
|
// src/commands/completion.ts
|
|
3961
|
-
import { readFileSync as
|
|
4834
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync12 } from "fs";
|
|
3962
4835
|
import { resolve as resolve2 } from "path";
|
|
3963
4836
|
import { homedir as homedir2 } from "os";
|
|
3964
4837
|
|
|
@@ -4009,14 +4882,14 @@ compdef _clawt_completion clawt
|
|
|
4009
4882
|
}
|
|
4010
4883
|
|
|
4011
4884
|
// src/utils/completion-engine.ts
|
|
4012
|
-
import { existsSync as
|
|
4013
|
-
import { join as
|
|
4885
|
+
import { existsSync as existsSync11, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
4886
|
+
import { join as join9, dirname, basename as basename2 } from "path";
|
|
4014
4887
|
function completeFilePath(partial) {
|
|
4015
4888
|
const cwd = process.cwd();
|
|
4016
4889
|
const hasDir = partial.includes("/");
|
|
4017
|
-
const searchDir = hasDir ?
|
|
4890
|
+
const searchDir = hasDir ? join9(cwd, dirname(partial)) : cwd;
|
|
4018
4891
|
const prefix = hasDir ? basename2(partial) : partial;
|
|
4019
|
-
if (!
|
|
4892
|
+
if (!existsSync11(searchDir)) {
|
|
4020
4893
|
return [];
|
|
4021
4894
|
}
|
|
4022
4895
|
const entries = readdirSync6(searchDir);
|
|
@@ -4025,7 +4898,7 @@ function completeFilePath(partial) {
|
|
|
4025
4898
|
for (const entry of entries) {
|
|
4026
4899
|
if (!entry.startsWith(prefix)) continue;
|
|
4027
4900
|
if (entry.startsWith(".")) continue;
|
|
4028
|
-
const fullPath =
|
|
4901
|
+
const fullPath = join9(searchDir, entry);
|
|
4029
4902
|
try {
|
|
4030
4903
|
const stat = statSync5(fullPath);
|
|
4031
4904
|
if (stat.isDirectory()) {
|
|
@@ -4114,15 +4987,15 @@ function generateCompletions(program2, args) {
|
|
|
4114
4987
|
|
|
4115
4988
|
// src/commands/completion.ts
|
|
4116
4989
|
function appendToFile(filePath, content) {
|
|
4117
|
-
if (
|
|
4118
|
-
const current =
|
|
4990
|
+
if (existsSync12(filePath)) {
|
|
4991
|
+
const current = readFileSync6(filePath, "utf-8");
|
|
4119
4992
|
if (current.includes("clawt completion")) {
|
|
4120
4993
|
printInfo(MESSAGES.COMPLETION_INSTALL_EXISTS + ": " + filePath);
|
|
4121
4994
|
return;
|
|
4122
4995
|
}
|
|
4123
4996
|
content = current + content;
|
|
4124
4997
|
}
|
|
4125
|
-
|
|
4998
|
+
writeFileSync5(filePath, content, "utf-8");
|
|
4126
4999
|
printSuccess(MESSAGES.COMPLETION_INSTALL_SUCCESS + ": " + filePath);
|
|
4127
5000
|
printInfo(MESSAGES.COMPLETION_INSTALL_RESTART(filePath));
|
|
4128
5001
|
}
|
|
@@ -4169,6 +5042,37 @@ function registerCompletionCommand(program2) {
|
|
|
4169
5042
|
});
|
|
4170
5043
|
}
|
|
4171
5044
|
|
|
5045
|
+
// src/commands/init.ts
|
|
5046
|
+
import { Command as Cmd } from "commander";
|
|
5047
|
+
function registerInitCommand(program2) {
|
|
5048
|
+
const initCmd = program2.command("init").description("\u521D\u59CB\u5316\u9879\u76EE\u7EA7\u914D\u7F6E\uFF0C\u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F").option("-b, --branch <branchName>", "\u6307\u5B9A\u4E3B\u5DE5\u4F5C\u5206\u652F\u540D\uFF08\u9ED8\u8BA4\u4F7F\u7528\u5F53\u524D\u5206\u652F\uFF09").action(async (options) => {
|
|
5049
|
+
await handleInit(options);
|
|
5050
|
+
});
|
|
5051
|
+
initCmd.addCommand(
|
|
5052
|
+
new Cmd("show").description("\u5C55\u793A\u5F53\u524D\u9879\u76EE\u7684 init \u914D\u7F6E").action(() => {
|
|
5053
|
+
handleInitShow();
|
|
5054
|
+
})
|
|
5055
|
+
);
|
|
5056
|
+
}
|
|
5057
|
+
function handleInitShow() {
|
|
5058
|
+
validateMainWorktree();
|
|
5059
|
+
const config2 = requireProjectConfig();
|
|
5060
|
+
const configJson = safeStringify(config2);
|
|
5061
|
+
printInfo(MESSAGES.INIT_SHOW(configJson));
|
|
5062
|
+
}
|
|
5063
|
+
async function handleInit(options) {
|
|
5064
|
+
validateMainWorktree();
|
|
5065
|
+
const existingConfig = loadProjectConfig();
|
|
5066
|
+
const branchName = options.branch || getCurrentBranch();
|
|
5067
|
+
logger.info(`init \u547D\u4EE4\u6267\u884C\uFF0C\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`);
|
|
5068
|
+
saveProjectConfig({ clawtMainWorkBranch: branchName });
|
|
5069
|
+
if (existingConfig) {
|
|
5070
|
+
printSuccess(MESSAGES.INIT_UPDATED(existingConfig.clawtMainWorkBranch, branchName));
|
|
5071
|
+
} else {
|
|
5072
|
+
printSuccess(MESSAGES.INIT_SUCCESS(branchName));
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
5075
|
+
|
|
4172
5076
|
// src/index.ts
|
|
4173
5077
|
var require2 = createRequire(import.meta.url);
|
|
4174
5078
|
var { version } = require2("../package.json");
|
|
@@ -4194,6 +5098,7 @@ registerStatusCommand(program);
|
|
|
4194
5098
|
registerAliasCommand(program);
|
|
4195
5099
|
registerProjectsCommand(program);
|
|
4196
5100
|
registerCompletionCommand(program);
|
|
5101
|
+
registerInitCommand(program);
|
|
4197
5102
|
var config = loadConfig();
|
|
4198
5103
|
applyAliases(program, config.aliases);
|
|
4199
5104
|
process.on("uncaughtException", (error) => {
|