clawt 2.19.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/index.js +626 -219
- package/dist/postinstall.js +39 -6
- 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 +156 -1879
- package/docs/status.md +155 -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 +9 -3
- package/src/commands/run.ts +9 -3
- package/src/commands/status.ts +1 -1
- 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 +3 -3
- package/src/constants/messages/completion.ts +1 -1
- package/src/constants/messages/create.ts +3 -0
- package/src/constants/messages/index.ts +2 -0
- package/src/constants/messages/init.ts +18 -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/constants/prompt.ts +28 -0
- package/src/index.ts +2 -0
- package/src/types/command.ts +7 -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 +6 -1
- 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 +268 -1
- 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/resume.test.ts +29 -8
- 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-matcher.test.ts +142 -1
- 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,22 @@ 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
|
+
|
|
437
466
|
// src/constants/messages/index.ts
|
|
438
467
|
var MESSAGES = {
|
|
439
468
|
...COMMON_MESSAGES,
|
|
@@ -449,7 +478,8 @@ var MESSAGES = {
|
|
|
449
478
|
...STATUS_MESSAGES,
|
|
450
479
|
...ALIAS_MESSAGES,
|
|
451
480
|
...PROJECTS_MESSAGES,
|
|
452
|
-
...COMPLETION_MESSAGES
|
|
481
|
+
...COMPLETION_MESSAGES,
|
|
482
|
+
...INIT_MESSAGES
|
|
453
483
|
};
|
|
454
484
|
|
|
455
485
|
// src/constants/exitCodes.ts
|
|
@@ -567,8 +597,14 @@ var CLEAR_SCREEN = "\x1B[2J";
|
|
|
567
597
|
var CURSOR_HOME = "\x1B[H";
|
|
568
598
|
|
|
569
599
|
// src/constants/prompt.ts
|
|
600
|
+
import chalk from "chalk";
|
|
570
601
|
var SELECT_ALL_NAME = "__select_all__";
|
|
571
602
|
var SELECT_ALL_LABEL = "[select-all]";
|
|
603
|
+
var GROUP_SELECT_ALL_PREFIX = "__group_select_all_";
|
|
604
|
+
var GROUP_SELECT_ALL_LABEL = (dateLabel) => `[select-all: ${dateLabel}]`;
|
|
605
|
+
var GROUP_SEPARATOR_LABEL = (dateLabel, relativeTime) => `\u2550\u2550\u2550\u2550 ${chalk.bold.hex("#FF8C00")(dateLabel)}\uFF08${chalk.hex("#FF8C00")(relativeTime)}\uFF09 \u2550\u2550\u2550\u2550`;
|
|
606
|
+
var UNKNOWN_DATE_GROUP = "\u672A\u77E5\u65E5\u671F";
|
|
607
|
+
var UNKNOWN_DATE_SEPARATOR_LABEL = `\u2550\u2550\u2550\u2550 ${chalk.bold.hex("#FF8C00")("\u672A\u77E5\u65E5\u671F")} \u2550\u2550\u2550\u2550`;
|
|
572
608
|
|
|
573
609
|
// src/errors/index.ts
|
|
574
610
|
var ClawtError = class extends Error {
|
|
@@ -588,7 +624,7 @@ var ClawtError = class extends Error {
|
|
|
588
624
|
// src/logger/index.ts
|
|
589
625
|
import winston from "winston";
|
|
590
626
|
import DailyRotateFile from "winston-daily-rotate-file";
|
|
591
|
-
import
|
|
627
|
+
import chalk2 from "chalk";
|
|
592
628
|
import { existsSync, mkdirSync } from "fs";
|
|
593
629
|
if (!existsSync(LOGS_DIR)) {
|
|
594
630
|
mkdirSync(LOGS_DIR, { recursive: true });
|
|
@@ -613,13 +649,13 @@ var logger = winston.createLogger({
|
|
|
613
649
|
transports: [dailyRotateTransport]
|
|
614
650
|
});
|
|
615
651
|
var LEVEL_COLORS = {
|
|
616
|
-
error:
|
|
617
|
-
warn:
|
|
618
|
-
info:
|
|
619
|
-
debug:
|
|
652
|
+
error: chalk2.red,
|
|
653
|
+
warn: chalk2.yellow,
|
|
654
|
+
info: chalk2.cyan,
|
|
655
|
+
debug: chalk2.gray
|
|
620
656
|
};
|
|
621
657
|
function colorizeLevel(level) {
|
|
622
|
-
const colorFn = LEVEL_COLORS[level] ||
|
|
658
|
+
const colorFn = LEVEL_COLORS[level] || chalk2.white;
|
|
623
659
|
return colorFn(level.toUpperCase().padEnd(5));
|
|
624
660
|
}
|
|
625
661
|
function enableConsoleTransport() {
|
|
@@ -630,7 +666,7 @@ function enableConsoleTransport() {
|
|
|
630
666
|
return;
|
|
631
667
|
}
|
|
632
668
|
const consoleFormat = winston.format.printf(({ level, message, timestamp }) => {
|
|
633
|
-
return `${
|
|
669
|
+
return `${chalk2.gray(timestamp)} ${colorizeLevel(level)} ${message}`;
|
|
634
670
|
});
|
|
635
671
|
const consoleTransport = new winston.transports.Console({
|
|
636
672
|
level: "debug",
|
|
@@ -896,24 +932,30 @@ function getBranchCreatedAt(branchName, cwd) {
|
|
|
896
932
|
return null;
|
|
897
933
|
}
|
|
898
934
|
}
|
|
935
|
+
function gitCheckout(branchName, cwd) {
|
|
936
|
+
execCommand(`git checkout ${branchName}`, { cwd });
|
|
937
|
+
}
|
|
938
|
+
function createBranch(branchName, cwd) {
|
|
939
|
+
execCommand(`git branch ${branchName}`, { cwd });
|
|
940
|
+
}
|
|
899
941
|
|
|
900
942
|
// src/utils/formatter.ts
|
|
901
|
-
import
|
|
943
|
+
import chalk3 from "chalk";
|
|
902
944
|
import { createInterface } from "readline";
|
|
903
945
|
function printSuccess(message) {
|
|
904
|
-
console.log(
|
|
946
|
+
console.log(chalk3.green(message));
|
|
905
947
|
}
|
|
906
948
|
function printError(message) {
|
|
907
|
-
console.error(
|
|
949
|
+
console.error(chalk3.red(`\u2717 ${message}`));
|
|
908
950
|
}
|
|
909
951
|
function printWarning(message) {
|
|
910
|
-
console.log(
|
|
952
|
+
console.log(chalk3.yellow(`\u26A0 ${message}`));
|
|
911
953
|
}
|
|
912
954
|
function printInfo(message) {
|
|
913
955
|
console.log(message);
|
|
914
956
|
}
|
|
915
957
|
function printHint(message) {
|
|
916
|
-
console.log(
|
|
958
|
+
console.log(chalk3.hex("#FF8C00")(message));
|
|
917
959
|
}
|
|
918
960
|
function printSeparator() {
|
|
919
961
|
console.log(MESSAGES.SEPARATOR);
|
|
@@ -934,7 +976,7 @@ function confirmAction(question) {
|
|
|
934
976
|
});
|
|
935
977
|
}
|
|
936
978
|
function confirmDestructiveAction(dangerousCommand, description) {
|
|
937
|
-
printWarning(`\u5373\u5C06\u6267\u884C ${
|
|
979
|
+
printWarning(`\u5373\u5C06\u6267\u884C ${chalk3.red.bold(dangerousCommand)}\uFF0C${description}`);
|
|
938
980
|
return confirmAction("\u662F\u5426\u7EE7\u7EED\uFF1F");
|
|
939
981
|
}
|
|
940
982
|
function isWorktreeIdle(status) {
|
|
@@ -942,17 +984,17 @@ function isWorktreeIdle(status) {
|
|
|
942
984
|
}
|
|
943
985
|
function formatWorktreeStatus(status) {
|
|
944
986
|
const parts = [];
|
|
945
|
-
parts.push(
|
|
987
|
+
parts.push(chalk3.yellow(`${status.commitCount} \u4E2A\u63D0\u4EA4`));
|
|
946
988
|
if (status.insertions === 0 && status.deletions === 0) {
|
|
947
989
|
parts.push("\u65E0\u53D8\u66F4");
|
|
948
990
|
} else {
|
|
949
991
|
const diffParts = [];
|
|
950
|
-
diffParts.push(
|
|
951
|
-
diffParts.push(
|
|
992
|
+
diffParts.push(chalk3.green(`+${status.insertions}`));
|
|
993
|
+
diffParts.push(chalk3.red(`-${status.deletions}`));
|
|
952
994
|
parts.push(diffParts.join(" "));
|
|
953
995
|
}
|
|
954
996
|
if (status.hasDirtyFiles) {
|
|
955
|
-
parts.push(
|
|
997
|
+
parts.push(chalk3.gray("(\u672A\u63D0\u4EA4\u4FEE\u6539)"));
|
|
956
998
|
}
|
|
957
999
|
return parts.join(" ");
|
|
958
1000
|
}
|
|
@@ -1070,8 +1112,8 @@ function validateClaudeCodeInstalled() {
|
|
|
1070
1112
|
}
|
|
1071
1113
|
|
|
1072
1114
|
// src/utils/worktree.ts
|
|
1073
|
-
import { join as
|
|
1074
|
-
import { existsSync as
|
|
1115
|
+
import { join as join4 } from "path";
|
|
1116
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
|
|
1075
1117
|
|
|
1076
1118
|
// src/utils/fs.ts
|
|
1077
1119
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmdirSync, statSync } from "fs";
|
|
@@ -1112,10 +1154,152 @@ function calculateDirSize(dirPath) {
|
|
|
1112
1154
|
return totalSize;
|
|
1113
1155
|
}
|
|
1114
1156
|
|
|
1157
|
+
// src/utils/validate-branch.ts
|
|
1158
|
+
import Enquirer from "enquirer";
|
|
1159
|
+
|
|
1160
|
+
// src/utils/project-config.ts
|
|
1161
|
+
import { existsSync as existsSync3, readFileSync, writeFileSync } from "fs";
|
|
1162
|
+
import { join as join3 } from "path";
|
|
1163
|
+
function getProjectConfigPath(projectName) {
|
|
1164
|
+
return join3(PROJECTS_CONFIG_DIR, projectName, "config.json");
|
|
1165
|
+
}
|
|
1166
|
+
function loadProjectConfig() {
|
|
1167
|
+
const projectName = getProjectName();
|
|
1168
|
+
const configPath = getProjectConfigPath(projectName);
|
|
1169
|
+
if (!existsSync3(configPath)) {
|
|
1170
|
+
return null;
|
|
1171
|
+
}
|
|
1172
|
+
try {
|
|
1173
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
1174
|
+
return JSON.parse(raw);
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
logger.warn(`\u9879\u76EE\u914D\u7F6E\u6587\u4EF6\u89E3\u6790\u5931\u8D25: ${error}`);
|
|
1177
|
+
return null;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
function saveProjectConfig(config2) {
|
|
1181
|
+
const projectName = getProjectName();
|
|
1182
|
+
const configPath = getProjectConfigPath(projectName);
|
|
1183
|
+
const projectDir = join3(PROJECTS_CONFIG_DIR, projectName);
|
|
1184
|
+
ensureDir(projectDir);
|
|
1185
|
+
writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf-8");
|
|
1186
|
+
logger.info(`\u9879\u76EE\u914D\u7F6E\u5DF2\u4FDD\u5B58: ${configPath}`);
|
|
1187
|
+
}
|
|
1188
|
+
function requireProjectConfig() {
|
|
1189
|
+
const config2 = loadProjectConfig();
|
|
1190
|
+
if (!config2) {
|
|
1191
|
+
throw new ClawtError(MESSAGES.PROJECT_NOT_INITIALIZED);
|
|
1192
|
+
}
|
|
1193
|
+
if (!config2.clawtMainWorkBranch) {
|
|
1194
|
+
throw new ClawtError(MESSAGES.PROJECT_CONFIG_MISSING_BRANCH);
|
|
1195
|
+
}
|
|
1196
|
+
return config2;
|
|
1197
|
+
}
|
|
1198
|
+
function getMainWorkBranch() {
|
|
1199
|
+
const config2 = requireProjectConfig();
|
|
1200
|
+
return config2.clawtMainWorkBranch;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/utils/validate-branch.ts
|
|
1204
|
+
function getValidateBranchName(branchName) {
|
|
1205
|
+
return `${VALIDATE_BRANCH_PREFIX}${branchName}`;
|
|
1206
|
+
}
|
|
1207
|
+
function createValidateBranch(branchName, cwd) {
|
|
1208
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
1209
|
+
if (checkBranchExists(validateBranchName, cwd)) {
|
|
1210
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA: ${validateBranchName}`);
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
createBranch(validateBranchName, cwd);
|
|
1214
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u521B\u5EFA: ${validateBranchName}`);
|
|
1215
|
+
}
|
|
1216
|
+
function deleteValidateBranch(branchName, cwd) {
|
|
1217
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
1218
|
+
if (!checkBranchExists(validateBranchName, cwd)) {
|
|
1219
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u5220\u9664: ${validateBranchName}`);
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
deleteBranch(validateBranchName, cwd);
|
|
1223
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u5220\u9664: ${validateBranchName}`);
|
|
1224
|
+
}
|
|
1225
|
+
async function rebuildValidateBranch(branchName, cwd) {
|
|
1226
|
+
const mainBranch = getMainWorkBranch();
|
|
1227
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
1228
|
+
if (currentBranch === mainBranch) {
|
|
1229
|
+
} else if (currentBranch.startsWith(VALIDATE_BRANCH_PREFIX)) {
|
|
1230
|
+
gitResetHard(cwd);
|
|
1231
|
+
gitCleanForce(cwd);
|
|
1232
|
+
gitCheckout(mainBranch, cwd);
|
|
1233
|
+
} else {
|
|
1234
|
+
if (!isWorkingDirClean(cwd)) {
|
|
1235
|
+
await handleDirtyWorkingDir(cwd);
|
|
1236
|
+
}
|
|
1237
|
+
gitCheckout(mainBranch, cwd);
|
|
1238
|
+
}
|
|
1239
|
+
deleteValidateBranch(branchName, cwd);
|
|
1240
|
+
createValidateBranch(branchName, cwd);
|
|
1241
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u91CD\u5EFA: ${getValidateBranchName(branchName)}`);
|
|
1242
|
+
}
|
|
1243
|
+
async function handleDirtyWorkingDir(cwd) {
|
|
1244
|
+
printWarning("\u5F53\u524D\u5206\u652F\u6709\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF0C\u8BF7\u9009\u62E9\u5904\u7406\u65B9\u5F0F\uFF1A\n");
|
|
1245
|
+
const choice = await new Enquirer.Select({
|
|
1246
|
+
message: "\u9009\u62E9\u5904\u7406\u65B9\u5F0F",
|
|
1247
|
+
choices: [
|
|
1248
|
+
{
|
|
1249
|
+
name: "reset",
|
|
1250
|
+
message: "reset - \u4E22\u5F03\u6240\u6709\u66F4\u6539 (git reset --hard HEAD && git clean -fd)"
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
name: "stash",
|
|
1254
|
+
message: "stash - \u6682\u5B58\u66F4\u6539 (git add . && git stash)"
|
|
1255
|
+
},
|
|
1256
|
+
{
|
|
1257
|
+
name: "exit",
|
|
1258
|
+
message: "exit - \u9000\u51FA\uFF0C\u624B\u52A8\u5904\u7406"
|
|
1259
|
+
}
|
|
1260
|
+
],
|
|
1261
|
+
initial: 0
|
|
1262
|
+
}).run();
|
|
1263
|
+
if (choice === "exit") {
|
|
1264
|
+
throw new ClawtError("\u7528\u6237\u9009\u62E9\u9000\u51FA\uFF0C\u8BF7\u624B\u52A8\u5904\u7406\u5DE5\u4F5C\u533A\u66F4\u6539\u540E\u91CD\u8BD5");
|
|
1265
|
+
}
|
|
1266
|
+
if (choice === "reset") {
|
|
1267
|
+
gitResetHard(cwd);
|
|
1268
|
+
gitCleanForce(cwd);
|
|
1269
|
+
} else if (choice === "stash") {
|
|
1270
|
+
gitAddAll(cwd);
|
|
1271
|
+
gitStashPush("clawt:auto-stash", cwd);
|
|
1272
|
+
}
|
|
1273
|
+
if (!isWorkingDirClean(cwd)) {
|
|
1274
|
+
throw new ClawtError("\u5DE5\u4F5C\u533A\u4ECD\u7136\u4E0D\u5E72\u51C0\uFF0C\u8BF7\u624B\u52A8\u5904\u7406");
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
async function ensureOnMainWorkBranch(cwd) {
|
|
1278
|
+
const mainBranch = getMainWorkBranch();
|
|
1279
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
1280
|
+
if (currentBranch === mainBranch) {
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
if (currentBranch.startsWith(VALIDATE_BRANCH_PREFIX)) {
|
|
1284
|
+
logger.info(`\u5F53\u524D\u5728\u9A8C\u8BC1\u5206\u652F ${currentBranch} \u4E0A\uFF0C\u81EA\u52A8\u5207\u56DE\u4E3B\u5DE5\u4F5C\u5206\u652F ${mainBranch}`);
|
|
1285
|
+
if (!isWorkingDirClean(cwd)) {
|
|
1286
|
+
gitResetHard(cwd);
|
|
1287
|
+
gitCleanForce(cwd);
|
|
1288
|
+
}
|
|
1289
|
+
gitCheckout(mainBranch, cwd);
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
logger.info(`\u5F53\u524D\u5728\u5206\u652F ${currentBranch} \u4E0A\uFF0C\u9700\u5207\u6362\u5230\u4E3B\u5DE5\u4F5C\u5206\u652F ${mainBranch}`);
|
|
1293
|
+
if (!isWorkingDirClean(cwd)) {
|
|
1294
|
+
await handleDirtyWorkingDir(cwd);
|
|
1295
|
+
}
|
|
1296
|
+
gitCheckout(mainBranch, cwd);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1115
1299
|
// src/utils/worktree.ts
|
|
1116
1300
|
function getProjectWorktreeDir() {
|
|
1117
1301
|
const projectName = getProjectName();
|
|
1118
|
-
return
|
|
1302
|
+
return join4(WORKTREES_DIR, projectName);
|
|
1119
1303
|
}
|
|
1120
1304
|
function createWorktrees(branchName, count) {
|
|
1121
1305
|
const sanitized = sanitizeBranchName(branchName);
|
|
@@ -1125,8 +1309,9 @@ function createWorktrees(branchName, count) {
|
|
|
1125
1309
|
ensureDir(projectDir);
|
|
1126
1310
|
const results = [];
|
|
1127
1311
|
for (const name of branchNames) {
|
|
1128
|
-
const worktreePath =
|
|
1312
|
+
const worktreePath = join4(projectDir, name);
|
|
1129
1313
|
createWorktree(name, worktreePath);
|
|
1314
|
+
createValidateBranch(name);
|
|
1130
1315
|
results.push({ path: worktreePath, branch: name });
|
|
1131
1316
|
logger.info(`worktree \u521B\u5EFA\u5B8C\u6210: ${worktreePath} (\u5206\u652F: ${name})`);
|
|
1132
1317
|
}
|
|
@@ -1138,8 +1323,9 @@ function createWorktreesByBranches(branchNames) {
|
|
|
1138
1323
|
ensureDir(projectDir);
|
|
1139
1324
|
const results = [];
|
|
1140
1325
|
for (const name of branchNames) {
|
|
1141
|
-
const worktreePath =
|
|
1326
|
+
const worktreePath = join4(projectDir, name);
|
|
1142
1327
|
createWorktree(name, worktreePath);
|
|
1328
|
+
createValidateBranch(name);
|
|
1143
1329
|
results.push({ path: worktreePath, branch: name });
|
|
1144
1330
|
logger.info(`worktree \u521B\u5EFA\u5B8C\u6210: ${worktreePath} (\u5206\u652F: ${name})`);
|
|
1145
1331
|
}
|
|
@@ -1147,7 +1333,7 @@ function createWorktreesByBranches(branchNames) {
|
|
|
1147
1333
|
}
|
|
1148
1334
|
function getProjectWorktrees() {
|
|
1149
1335
|
const projectDir = getProjectWorktreeDir();
|
|
1150
|
-
if (!
|
|
1336
|
+
if (!existsSync4(projectDir)) {
|
|
1151
1337
|
return [];
|
|
1152
1338
|
}
|
|
1153
1339
|
const worktreeListOutput = gitWorktreeList();
|
|
@@ -1160,7 +1346,7 @@ function getProjectWorktrees() {
|
|
|
1160
1346
|
if (!entry.isDirectory()) {
|
|
1161
1347
|
continue;
|
|
1162
1348
|
}
|
|
1163
|
-
const fullPath =
|
|
1349
|
+
const fullPath = join4(projectDir, entry.name);
|
|
1164
1350
|
if (registeredPaths.has(fullPath)) {
|
|
1165
1351
|
worktrees.push({
|
|
1166
1352
|
path: fullPath,
|
|
@@ -1175,6 +1361,7 @@ function cleanupWorktrees(worktrees) {
|
|
|
1175
1361
|
try {
|
|
1176
1362
|
removeWorktreeByPath(wt.path);
|
|
1177
1363
|
deleteBranch(wt.branch);
|
|
1364
|
+
deleteValidateBranch(wt.branch);
|
|
1178
1365
|
logger.info(`\u5DF2\u6E05\u7406 worktree \u548C\u5206\u652F: ${wt.branch}`);
|
|
1179
1366
|
} catch (error) {
|
|
1180
1367
|
logger.error(`\u6E05\u7406 worktree \u5931\u8D25: ${wt.path} - ${error}`);
|
|
@@ -1197,13 +1384,13 @@ function getWorktreeStatus(worktree) {
|
|
|
1197
1384
|
}
|
|
1198
1385
|
|
|
1199
1386
|
// src/utils/config.ts
|
|
1200
|
-
import { existsSync as
|
|
1387
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1201
1388
|
function loadConfig() {
|
|
1202
|
-
if (!
|
|
1389
|
+
if (!existsSync5(CONFIG_PATH)) {
|
|
1203
1390
|
return { ...DEFAULT_CONFIG };
|
|
1204
1391
|
}
|
|
1205
1392
|
try {
|
|
1206
|
-
const raw =
|
|
1393
|
+
const raw = readFileSync2(CONFIG_PATH, "utf-8");
|
|
1207
1394
|
return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
|
|
1208
1395
|
} catch {
|
|
1209
1396
|
logger.warn(MESSAGES.CONFIG_CORRUPTED);
|
|
@@ -1212,13 +1399,13 @@ function loadConfig() {
|
|
|
1212
1399
|
}
|
|
1213
1400
|
}
|
|
1214
1401
|
function writeConfig(config2) {
|
|
1215
|
-
|
|
1402
|
+
writeFileSync2(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
|
|
1216
1403
|
}
|
|
1217
1404
|
function writeDefaultConfig() {
|
|
1218
1405
|
writeConfig(DEFAULT_CONFIG);
|
|
1219
1406
|
}
|
|
1220
1407
|
function saveConfig(config2) {
|
|
1221
|
-
|
|
1408
|
+
writeFileSync2(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
|
|
1222
1409
|
}
|
|
1223
1410
|
function getConfigValue(key) {
|
|
1224
1411
|
const config2 = loadConfig();
|
|
@@ -1228,6 +1415,7 @@ function ensureClawtDirs() {
|
|
|
1228
1415
|
ensureDir(CLAWT_HOME);
|
|
1229
1416
|
ensureDir(LOGS_DIR);
|
|
1230
1417
|
ensureDir(WORKTREES_DIR);
|
|
1418
|
+
ensureDir(PROJECTS_CONFIG_DIR);
|
|
1231
1419
|
}
|
|
1232
1420
|
function parseConcurrency(optionValue, configValue) {
|
|
1233
1421
|
if (optionValue === void 0) {
|
|
@@ -1241,18 +1429,18 @@ function parseConcurrency(optionValue, configValue) {
|
|
|
1241
1429
|
}
|
|
1242
1430
|
|
|
1243
1431
|
// src/utils/prompt.ts
|
|
1244
|
-
import
|
|
1432
|
+
import Enquirer2 from "enquirer";
|
|
1245
1433
|
|
|
1246
1434
|
// src/utils/claude.ts
|
|
1247
1435
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1248
|
-
import { existsSync as
|
|
1249
|
-
import { join as
|
|
1436
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3 } from "fs";
|
|
1437
|
+
import { join as join5 } from "path";
|
|
1250
1438
|
|
|
1251
1439
|
// src/utils/terminal.ts
|
|
1252
1440
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1253
|
-
import { existsSync as
|
|
1441
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1254
1442
|
function isITerm2Installed() {
|
|
1255
|
-
return
|
|
1443
|
+
return existsSync6(ITERM2_APP_PATH);
|
|
1256
1444
|
}
|
|
1257
1445
|
function detectTerminalApp() {
|
|
1258
1446
|
const configured = getConfigValue("terminalApp");
|
|
@@ -1325,8 +1513,8 @@ function encodeClaudeProjectPath(absolutePath) {
|
|
|
1325
1513
|
}
|
|
1326
1514
|
function hasClaudeSessionHistory(worktreePath) {
|
|
1327
1515
|
const encodedName = encodeClaudeProjectPath(worktreePath);
|
|
1328
|
-
const projectDir =
|
|
1329
|
-
if (!
|
|
1516
|
+
const projectDir = join5(CLAUDE_PROJECTS_DIR, encodedName);
|
|
1517
|
+
if (!existsSync7(projectDir)) {
|
|
1330
1518
|
return false;
|
|
1331
1519
|
}
|
|
1332
1520
|
const entries = readdirSync3(projectDir);
|
|
@@ -1382,20 +1570,20 @@ function launchInteractiveClaudeInNewTerminal(worktree, hasPreviousSession) {
|
|
|
1382
1570
|
}
|
|
1383
1571
|
|
|
1384
1572
|
// src/utils/validate-snapshot.ts
|
|
1385
|
-
import { join as
|
|
1386
|
-
import { existsSync as
|
|
1573
|
+
import { join as join6 } from "path";
|
|
1574
|
+
import { existsSync as existsSync8, readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync, readdirSync as readdirSync4, rmdirSync as rmdirSync2, statSync as statSync2 } from "fs";
|
|
1387
1575
|
function getSnapshotPath(projectName, branchName) {
|
|
1388
|
-
return
|
|
1576
|
+
return join6(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.tree`);
|
|
1389
1577
|
}
|
|
1390
1578
|
function getSnapshotHeadPath(projectName, branchName) {
|
|
1391
|
-
return
|
|
1579
|
+
return join6(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.head`);
|
|
1392
1580
|
}
|
|
1393
1581
|
function hasSnapshot(projectName, branchName) {
|
|
1394
|
-
return
|
|
1582
|
+
return existsSync8(getSnapshotPath(projectName, branchName));
|
|
1395
1583
|
}
|
|
1396
1584
|
function getSnapshotModifiedTime(projectName, branchName) {
|
|
1397
1585
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1398
|
-
if (!
|
|
1586
|
+
if (!existsSync8(snapshotPath)) return null;
|
|
1399
1587
|
const stat = statSync2(snapshotPath);
|
|
1400
1588
|
return stat.mtime.toISOString();
|
|
1401
1589
|
}
|
|
@@ -1403,47 +1591,47 @@ function readSnapshot(projectName, branchName) {
|
|
|
1403
1591
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1404
1592
|
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
1405
1593
|
logger.debug(`\u8BFB\u53D6 validate \u5FEB\u7167: ${snapshotPath}`);
|
|
1406
|
-
const treeHash =
|
|
1407
|
-
const headCommitHash =
|
|
1594
|
+
const treeHash = existsSync8(snapshotPath) ? readFileSync3(snapshotPath, "utf-8").trim() : "";
|
|
1595
|
+
const headCommitHash = existsSync8(headPath) ? readFileSync3(headPath, "utf-8").trim() : "";
|
|
1408
1596
|
return { treeHash, headCommitHash };
|
|
1409
1597
|
}
|
|
1410
1598
|
function writeSnapshot(projectName, branchName, treeHash, headCommitHash) {
|
|
1411
1599
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1412
1600
|
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
1413
|
-
const snapshotDir =
|
|
1601
|
+
const snapshotDir = join6(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1414
1602
|
ensureDir(snapshotDir);
|
|
1415
|
-
|
|
1416
|
-
|
|
1603
|
+
writeFileSync3(snapshotPath, treeHash, "utf-8");
|
|
1604
|
+
writeFileSync3(headPath, headCommitHash, "utf-8");
|
|
1417
1605
|
logger.info(`\u5DF2\u4FDD\u5B58 validate \u5FEB\u7167: ${snapshotPath}, ${headPath}`);
|
|
1418
1606
|
}
|
|
1419
1607
|
function removeSnapshot(projectName, branchName) {
|
|
1420
1608
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1421
1609
|
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
1422
|
-
if (
|
|
1610
|
+
if (existsSync8(snapshotPath)) {
|
|
1423
1611
|
unlinkSync(snapshotPath);
|
|
1424
1612
|
logger.info(`\u5DF2\u5220\u9664 validate \u5FEB\u7167: ${snapshotPath}`);
|
|
1425
1613
|
}
|
|
1426
|
-
if (
|
|
1614
|
+
if (existsSync8(headPath)) {
|
|
1427
1615
|
unlinkSync(headPath);
|
|
1428
1616
|
logger.info(`\u5DF2\u5220\u9664 validate \u5FEB\u7167: ${headPath}`);
|
|
1429
1617
|
}
|
|
1430
1618
|
}
|
|
1431
1619
|
function getProjectSnapshotBranches(projectName) {
|
|
1432
|
-
const projectDir =
|
|
1433
|
-
if (!
|
|
1620
|
+
const projectDir = join6(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1621
|
+
if (!existsSync8(projectDir)) {
|
|
1434
1622
|
return [];
|
|
1435
1623
|
}
|
|
1436
1624
|
const files = readdirSync4(projectDir);
|
|
1437
1625
|
return files.filter((f) => f.endsWith(".tree")).map((f) => f.replace(/\.tree$/, ""));
|
|
1438
1626
|
}
|
|
1439
1627
|
function removeProjectSnapshots(projectName) {
|
|
1440
|
-
const projectDir =
|
|
1441
|
-
if (!
|
|
1628
|
+
const projectDir = join6(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1629
|
+
if (!existsSync8(projectDir)) {
|
|
1442
1630
|
return;
|
|
1443
1631
|
}
|
|
1444
1632
|
const files = readdirSync4(projectDir);
|
|
1445
1633
|
for (const file of files) {
|
|
1446
|
-
unlinkSync(
|
|
1634
|
+
unlinkSync(join6(projectDir, file));
|
|
1447
1635
|
}
|
|
1448
1636
|
try {
|
|
1449
1637
|
rmdirSync2(projectDir);
|
|
@@ -1453,7 +1641,8 @@ function removeProjectSnapshots(projectName) {
|
|
|
1453
1641
|
}
|
|
1454
1642
|
|
|
1455
1643
|
// src/utils/worktree-matcher.ts
|
|
1456
|
-
import
|
|
1644
|
+
import Enquirer3 from "enquirer";
|
|
1645
|
+
import { statSync as statSync3 } from "fs";
|
|
1457
1646
|
function findExactMatch(worktrees, branchName) {
|
|
1458
1647
|
return worktrees.find((wt) => wt.branch === branchName);
|
|
1459
1648
|
}
|
|
@@ -1462,7 +1651,7 @@ function findFuzzyMatches(worktrees, keyword) {
|
|
|
1462
1651
|
return worktrees.filter((wt) => wt.branch.toLowerCase().includes(lowerKeyword));
|
|
1463
1652
|
}
|
|
1464
1653
|
async function promptSelectBranch(worktrees, message) {
|
|
1465
|
-
const selectedBranch = await new
|
|
1654
|
+
const selectedBranch = await new Enquirer3.Select({
|
|
1466
1655
|
message,
|
|
1467
1656
|
choices: worktrees.map((wt) => ({
|
|
1468
1657
|
name: wt.branch,
|
|
@@ -1480,7 +1669,7 @@ async function promptMultiSelectBranches(worktrees, message) {
|
|
|
1480
1669
|
{ name: SELECT_ALL_NAME, message: SELECT_ALL_LABEL },
|
|
1481
1670
|
...branchChoices
|
|
1482
1671
|
];
|
|
1483
|
-
const MultiSelect =
|
|
1672
|
+
const MultiSelect = Enquirer3.MultiSelect;
|
|
1484
1673
|
class MultiSelectWithSelectAll extends MultiSelect {
|
|
1485
1674
|
space() {
|
|
1486
1675
|
if (!this.focused) return;
|
|
@@ -1558,9 +1747,142 @@ async function resolveTargetWorktree(worktrees, messages, branchName) {
|
|
|
1558
1747
|
const allBranches = worktrees.map((wt) => wt.branch);
|
|
1559
1748
|
throw new ClawtError(messages.noMatch(branchName, allBranches));
|
|
1560
1749
|
}
|
|
1750
|
+
function formatLocalDate(date) {
|
|
1751
|
+
const year = date.getFullYear();
|
|
1752
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
1753
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
1754
|
+
return `${year}-${month}-${day}`;
|
|
1755
|
+
}
|
|
1756
|
+
function getWorktreeCreatedDate(dirPath) {
|
|
1757
|
+
try {
|
|
1758
|
+
const stat = statSync3(dirPath);
|
|
1759
|
+
return formatLocalDate(stat.birthtime);
|
|
1760
|
+
} catch {
|
|
1761
|
+
return null;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
function formatRelativeDate(dateStr) {
|
|
1765
|
+
const today = formatLocalDate(/* @__PURE__ */ new Date());
|
|
1766
|
+
const todayMs = new Date(today).getTime();
|
|
1767
|
+
const targetMs = new Date(dateStr).getTime();
|
|
1768
|
+
const diffDays = Math.round((todayMs - targetMs) / (1e3 * 60 * 60 * 24));
|
|
1769
|
+
if (diffDays === 0) return "\u4ECA\u5929";
|
|
1770
|
+
if (diffDays === 1) return "\u6628\u5929";
|
|
1771
|
+
if (diffDays < 30) return `${diffDays} \u5929\u524D`;
|
|
1772
|
+
if (diffDays < 365) {
|
|
1773
|
+
const months = Math.floor(diffDays / 30);
|
|
1774
|
+
return `${months} \u4E2A\u6708\u524D`;
|
|
1775
|
+
}
|
|
1776
|
+
const years = Math.floor(diffDays / 365);
|
|
1777
|
+
return `${years} \u5E74\u524D`;
|
|
1778
|
+
}
|
|
1779
|
+
function groupWorktreesByDate(worktrees) {
|
|
1780
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1781
|
+
for (const wt of worktrees) {
|
|
1782
|
+
const dateKey = getWorktreeCreatedDate(wt.path) ?? UNKNOWN_DATE_GROUP;
|
|
1783
|
+
if (!groups.has(dateKey)) {
|
|
1784
|
+
groups.set(dateKey, []);
|
|
1785
|
+
}
|
|
1786
|
+
groups.get(dateKey).push(wt);
|
|
1787
|
+
}
|
|
1788
|
+
const sortedEntries = [...groups.entries()].sort((a, b) => {
|
|
1789
|
+
if (a[0] === UNKNOWN_DATE_GROUP) return 1;
|
|
1790
|
+
if (b[0] === UNKNOWN_DATE_GROUP) return -1;
|
|
1791
|
+
return b[0].localeCompare(a[0]);
|
|
1792
|
+
});
|
|
1793
|
+
return new Map(sortedEntries);
|
|
1794
|
+
}
|
|
1795
|
+
function buildGroupedChoices(groups) {
|
|
1796
|
+
const choices = [];
|
|
1797
|
+
choices.push({ name: SELECT_ALL_NAME, message: SELECT_ALL_LABEL });
|
|
1798
|
+
for (const [dateKey, worktreeList] of groups) {
|
|
1799
|
+
if (dateKey === UNKNOWN_DATE_GROUP) {
|
|
1800
|
+
choices.push({ role: "separator", message: UNKNOWN_DATE_SEPARATOR_LABEL });
|
|
1801
|
+
} else {
|
|
1802
|
+
const relativeTime = formatRelativeDate(dateKey);
|
|
1803
|
+
choices.push({ role: "separator", message: GROUP_SEPARATOR_LABEL(dateKey, relativeTime) });
|
|
1804
|
+
}
|
|
1805
|
+
const groupSelectAllName = `${GROUP_SELECT_ALL_PREFIX}${dateKey}`;
|
|
1806
|
+
choices.push({ name: groupSelectAllName, message: GROUP_SELECT_ALL_LABEL(dateKey) });
|
|
1807
|
+
for (const wt of worktreeList) {
|
|
1808
|
+
choices.push({ name: wt.branch, message: wt.branch });
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
return choices;
|
|
1812
|
+
}
|
|
1813
|
+
function buildGroupMembershipMap(groups) {
|
|
1814
|
+
const map = /* @__PURE__ */ new Map();
|
|
1815
|
+
for (const [dateKey, worktreeList] of groups) {
|
|
1816
|
+
const groupSelectAllName = `${GROUP_SELECT_ALL_PREFIX}${dateKey}`;
|
|
1817
|
+
map.set(groupSelectAllName, worktreeList.map((wt) => wt.branch));
|
|
1818
|
+
}
|
|
1819
|
+
return map;
|
|
1820
|
+
}
|
|
1821
|
+
async function promptGroupedMultiSelectBranches(worktrees, message) {
|
|
1822
|
+
const groups = groupWorktreesByDate(worktrees);
|
|
1823
|
+
const choices = buildGroupedChoices(groups);
|
|
1824
|
+
const groupMembershipMap = buildGroupMembershipMap(groups);
|
|
1825
|
+
const groupSelectAllNames = new Set(groupMembershipMap.keys());
|
|
1826
|
+
const allBranchNames = new Set(worktrees.map((wt) => wt.branch));
|
|
1827
|
+
const MultiSelect = Enquirer3.MultiSelect;
|
|
1828
|
+
class MultiSelectWithGroupSelectAll extends MultiSelect {
|
|
1829
|
+
space() {
|
|
1830
|
+
if (!this.focused) return;
|
|
1831
|
+
const focusedName = this.focused.name;
|
|
1832
|
+
if (focusedName === SELECT_ALL_NAME) {
|
|
1833
|
+
const willEnable = !this.focused.enabled;
|
|
1834
|
+
for (const ch of this.choices) {
|
|
1835
|
+
ch.enabled = willEnable;
|
|
1836
|
+
}
|
|
1837
|
+
return this.render();
|
|
1838
|
+
}
|
|
1839
|
+
if (groupSelectAllNames.has(focusedName)) {
|
|
1840
|
+
const willEnable = !this.focused.enabled;
|
|
1841
|
+
const memberNames = groupMembershipMap.get(focusedName);
|
|
1842
|
+
this.focused.enabled = willEnable;
|
|
1843
|
+
for (const ch of this.choices) {
|
|
1844
|
+
if (memberNames.includes(ch.name)) {
|
|
1845
|
+
ch.enabled = willEnable;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
syncGlobalSelectAll(this.choices);
|
|
1849
|
+
return this.render();
|
|
1850
|
+
}
|
|
1851
|
+
this.toggle(this.focused);
|
|
1852
|
+
syncGroupSelectAll(this.choices, focusedName);
|
|
1853
|
+
syncGlobalSelectAll(this.choices);
|
|
1854
|
+
return this.render();
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
function syncGlobalSelectAll(choiceList) {
|
|
1858
|
+
const selectAllChoice = choiceList.find((ch) => ch.name === SELECT_ALL_NAME);
|
|
1859
|
+
if (!selectAllChoice) return;
|
|
1860
|
+
const branchItems = choiceList.filter((ch) => allBranchNames.has(ch.name));
|
|
1861
|
+
selectAllChoice.enabled = branchItems.length > 0 && branchItems.every((ch) => ch.enabled);
|
|
1862
|
+
}
|
|
1863
|
+
function syncGroupSelectAll(choiceList, branchName) {
|
|
1864
|
+
for (const [groupName, memberNames] of groupMembershipMap) {
|
|
1865
|
+
if (!memberNames.includes(branchName)) continue;
|
|
1866
|
+
const groupChoice = choiceList.find((ch) => ch.name === groupName);
|
|
1867
|
+
if (!groupChoice) continue;
|
|
1868
|
+
const memberChoices = choiceList.filter((ch) => memberNames.includes(ch.name));
|
|
1869
|
+
groupChoice.enabled = memberChoices.length > 0 && memberChoices.every((ch) => ch.enabled);
|
|
1870
|
+
break;
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
const selectedBranches = await new MultiSelectWithGroupSelectAll({
|
|
1874
|
+
message,
|
|
1875
|
+
choices,
|
|
1876
|
+
// 使用空心圆/实心圆作为选中指示符
|
|
1877
|
+
symbols: {
|
|
1878
|
+
indicator: { on: "\u25CF", off: "\u25CB" }
|
|
1879
|
+
}
|
|
1880
|
+
}).run();
|
|
1881
|
+
return worktrees.filter((wt) => selectedBranches.includes(wt.branch));
|
|
1882
|
+
}
|
|
1561
1883
|
|
|
1562
1884
|
// src/utils/progress-render.ts
|
|
1563
|
-
import
|
|
1885
|
+
import chalk4 from "chalk";
|
|
1564
1886
|
import stringWidth from "string-width";
|
|
1565
1887
|
var ANSI_RESET = "\x1B[0m";
|
|
1566
1888
|
function truncateToTerminalWidth(text, maxWidth) {
|
|
@@ -1597,23 +1919,23 @@ function renderTaskLine(task, total, maxPathWidth, spinnerChar) {
|
|
|
1597
1919
|
const pathStr = task.path.padEnd(maxPathWidth);
|
|
1598
1920
|
switch (task.status) {
|
|
1599
1921
|
case "pending": {
|
|
1600
|
-
return `${indexStr} ${pathStr} ${
|
|
1922
|
+
return `${indexStr} ${pathStr} ${chalk4.gray(TASK_STATUS_ICONS.PENDING)} ${chalk4.gray(TASK_STATUS_LABELS.PENDING)}`;
|
|
1601
1923
|
}
|
|
1602
1924
|
case "running": {
|
|
1603
1925
|
const elapsed = formatDuration(Date.now() - task.startedAt);
|
|
1604
|
-
const detail = task.activity ? ` ${
|
|
1605
|
-
return `${indexStr} ${pathStr} ${
|
|
1926
|
+
const detail = task.activity ? ` ${chalk4.dim(task.activity)}` : "";
|
|
1927
|
+
return `${indexStr} ${pathStr} ${chalk4.cyan(spinnerChar)} ${chalk4.cyan(TASK_STATUS_LABELS.RUNNING)} ${chalk4.gray(elapsed)}${detail}`;
|
|
1606
1928
|
}
|
|
1607
1929
|
case "done": {
|
|
1608
1930
|
const duration = task.durationMs != null ? formatDuration(task.durationMs) : "N/A";
|
|
1609
1931
|
const cost = task.costUsd != null ? `$${task.costUsd.toFixed(2)}` : "";
|
|
1610
|
-
const preview = task.resultPreview ? ` ${
|
|
1611
|
-
return `${indexStr} ${pathStr} ${
|
|
1932
|
+
const preview = task.resultPreview ? ` ${chalk4.dim(task.resultPreview)}` : "";
|
|
1933
|
+
return `${indexStr} ${pathStr} ${chalk4.green(TASK_STATUS_ICONS.DONE)} ${chalk4.green(TASK_STATUS_LABELS.DONE)} ${chalk4.gray(duration)} ${chalk4.yellow(cost)}${preview}`;
|
|
1612
1934
|
}
|
|
1613
1935
|
case "failed": {
|
|
1614
1936
|
const duration = task.durationMs != null ? formatDuration(task.durationMs) : "N/A";
|
|
1615
|
-
const preview = task.resultPreview ? ` ${
|
|
1616
|
-
return `${indexStr} ${pathStr} ${
|
|
1937
|
+
const preview = task.resultPreview ? ` ${chalk4.dim(task.resultPreview)}` : "";
|
|
1938
|
+
return `${indexStr} ${pathStr} ${chalk4.red(TASK_STATUS_ICONS.FAILED)} ${chalk4.red(TASK_STATUS_LABELS.FAILED)} ${chalk4.gray(duration)}${preview}`;
|
|
1617
1939
|
}
|
|
1618
1940
|
}
|
|
1619
1941
|
}
|
|
@@ -1623,10 +1945,10 @@ function renderSummaryLine(tasks, total) {
|
|
|
1623
1945
|
const failed = tasks.filter((t) => t.status === "failed").length;
|
|
1624
1946
|
const pending = tasks.filter((t) => t.status === "pending").length;
|
|
1625
1947
|
const parts = [];
|
|
1626
|
-
if (running > 0) parts.push(
|
|
1627
|
-
if (done > 0) parts.push(
|
|
1628
|
-
if (failed > 0) parts.push(
|
|
1629
|
-
if (pending > 0) parts.push(
|
|
1948
|
+
if (running > 0) parts.push(chalk4.cyan(`${running}/${total} ${TASK_STATUS_LABELS.RUNNING}`));
|
|
1949
|
+
if (done > 0) parts.push(chalk4.green(`${done}/${total} ${TASK_STATUS_LABELS.DONE}`));
|
|
1950
|
+
if (failed > 0) parts.push(chalk4.red(`${failed}/${total} ${TASK_STATUS_LABELS.FAILED}`));
|
|
1951
|
+
if (pending > 0) parts.push(chalk4.gray(`${pending}/${total} ${TASK_STATUS_LABELS.PENDING}`));
|
|
1630
1952
|
return `[${parts.join(", ")}]`;
|
|
1631
1953
|
}
|
|
1632
1954
|
|
|
@@ -1858,7 +2180,7 @@ var ProgressRenderer = class {
|
|
|
1858
2180
|
|
|
1859
2181
|
// src/utils/task-file.ts
|
|
1860
2182
|
import { resolve } from "path";
|
|
1861
|
-
import { existsSync as
|
|
2183
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
|
|
1862
2184
|
var TASK_BLOCK_REGEX = /<!-- CLAWT-TASKS:START -->([\s\S]*?)<!-- CLAWT-TASKS:END -->/g;
|
|
1863
2185
|
var BRANCH_LINE_REGEX = /^#\s*branch:\s*(.+)$/;
|
|
1864
2186
|
var EMPTY_TASKS_MESSAGE = "\u4EFB\u52A1\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A";
|
|
@@ -1904,10 +2226,10 @@ function parseTaskFile(content, options) {
|
|
|
1904
2226
|
}
|
|
1905
2227
|
function loadTaskFile(filePath, options) {
|
|
1906
2228
|
const absolutePath = resolve(filePath);
|
|
1907
|
-
if (!
|
|
2229
|
+
if (!existsSync9(absolutePath)) {
|
|
1908
2230
|
throw new ClawtError(MESSAGES.TASK_FILE_NOT_FOUND(absolutePath));
|
|
1909
2231
|
}
|
|
1910
|
-
const content =
|
|
2232
|
+
const content = readFileSync4(absolutePath, "utf-8");
|
|
1911
2233
|
const entries = parseTaskFile(content, options);
|
|
1912
2234
|
if (entries.length === 0) {
|
|
1913
2235
|
throw new ClawtError(MESSAGES.TASK_FILE_EMPTY);
|
|
@@ -2241,8 +2563,8 @@ async function executeBatchTasks(worktrees, tasks, concurrency) {
|
|
|
2241
2563
|
}
|
|
2242
2564
|
|
|
2243
2565
|
// src/utils/dry-run.ts
|
|
2244
|
-
import
|
|
2245
|
-
import { join as
|
|
2566
|
+
import chalk5 from "chalk";
|
|
2567
|
+
import { join as join7 } from "path";
|
|
2246
2568
|
var DRY_RUN_TASK_DESC_MAX_LENGTH = 80;
|
|
2247
2569
|
function truncateTaskDesc(task) {
|
|
2248
2570
|
const oneLine = task.replace(/\n/g, " ").trim();
|
|
@@ -2255,7 +2577,7 @@ function printDryRunPreview(branchNames, tasks, concurrency) {
|
|
|
2255
2577
|
const projectDir = getProjectWorktreeDir();
|
|
2256
2578
|
const isInteractive = tasks.length === 0;
|
|
2257
2579
|
printDoubleSeparator();
|
|
2258
|
-
printInfo(` ${
|
|
2580
|
+
printInfo(` ${chalk5.bold(MESSAGES.DRY_RUN_TITLE)}`);
|
|
2259
2581
|
printDoubleSeparator();
|
|
2260
2582
|
const summaryParts = [
|
|
2261
2583
|
MESSAGES.DRY_RUN_TASK_COUNT(branchNames.length),
|
|
@@ -2265,31 +2587,31 @@ function printDryRunPreview(branchNames, tasks, concurrency) {
|
|
|
2265
2587
|
if (isInteractive) {
|
|
2266
2588
|
summaryParts.push(MESSAGES.DRY_RUN_INTERACTIVE_MODE);
|
|
2267
2589
|
}
|
|
2268
|
-
printInfo(summaryParts.join(
|
|
2590
|
+
printInfo(summaryParts.join(chalk5.gray(" \u2502 ")));
|
|
2269
2591
|
printSeparator();
|
|
2270
2592
|
let hasConflict = false;
|
|
2271
2593
|
for (let i = 0; i < branchNames.length; i++) {
|
|
2272
2594
|
const branch = branchNames[i];
|
|
2273
|
-
const worktreePath =
|
|
2595
|
+
const worktreePath = join7(projectDir, branch);
|
|
2274
2596
|
const exists = checkBranchExists(branch);
|
|
2275
2597
|
if (exists) hasConflict = true;
|
|
2276
2598
|
const indexLabel = `[${i + 1}/${branchNames.length}]`;
|
|
2277
2599
|
if (exists) {
|
|
2278
|
-
printInfo(`${
|
|
2600
|
+
printInfo(`${chalk5.yellow("\u26A0")} ${indexLabel} ${chalk5.yellow(branch)} ${chalk5.gray("\u2014")} ${chalk5.yellow(MESSAGES.DRY_RUN_BRANCH_EXISTS_WARNING(branch))}`);
|
|
2279
2601
|
} else {
|
|
2280
|
-
printInfo(`${
|
|
2602
|
+
printInfo(`${chalk5.green("\u2713")} ${indexLabel} ${chalk5.cyan(branch)}`);
|
|
2281
2603
|
}
|
|
2282
|
-
printInfo(` ${
|
|
2604
|
+
printInfo(` ${chalk5.gray("\u8DEF\u5F84:")} ${worktreePath}`);
|
|
2283
2605
|
if (!isInteractive) {
|
|
2284
|
-
printInfo(` ${
|
|
2606
|
+
printInfo(` ${chalk5.gray("\u4EFB\u52A1:")} ${truncateTaskDesc(tasks[i])}`);
|
|
2285
2607
|
}
|
|
2286
2608
|
printInfo("");
|
|
2287
2609
|
}
|
|
2288
2610
|
printDoubleSeparator();
|
|
2289
2611
|
if (hasConflict) {
|
|
2290
|
-
printInfo(
|
|
2612
|
+
printInfo(chalk5.yellow(`\u26A0 ${MESSAGES.DRY_RUN_HAS_CONFLICT}`));
|
|
2291
2613
|
} else {
|
|
2292
|
-
printInfo(
|
|
2614
|
+
printInfo(chalk5.green(`\u2713 ${MESSAGES.DRY_RUN_READY}`));
|
|
2293
2615
|
}
|
|
2294
2616
|
}
|
|
2295
2617
|
|
|
@@ -2307,8 +2629,8 @@ function applyAliases(program2, aliases) {
|
|
|
2307
2629
|
}
|
|
2308
2630
|
|
|
2309
2631
|
// src/utils/config-strategy.ts
|
|
2310
|
-
import
|
|
2311
|
-
import
|
|
2632
|
+
import chalk6 from "chalk";
|
|
2633
|
+
import Enquirer4 from "enquirer";
|
|
2312
2634
|
function isValidConfigKey(key) {
|
|
2313
2635
|
return key in DEFAULT_CONFIG;
|
|
2314
2636
|
}
|
|
@@ -2351,16 +2673,16 @@ async function promptConfigValue(key, currentValue) {
|
|
|
2351
2673
|
}
|
|
2352
2674
|
function formatConfigValue(value) {
|
|
2353
2675
|
if (typeof value === "boolean") {
|
|
2354
|
-
return value ?
|
|
2676
|
+
return value ? chalk6.green("true") : chalk6.yellow("false");
|
|
2355
2677
|
}
|
|
2356
|
-
return
|
|
2678
|
+
return chalk6.cyan(String(value));
|
|
2357
2679
|
}
|
|
2358
2680
|
async function promptBooleanValue(key, currentValue) {
|
|
2359
2681
|
const choices = [
|
|
2360
2682
|
{ name: "true", message: "true" },
|
|
2361
2683
|
{ name: "false", message: "false" }
|
|
2362
2684
|
];
|
|
2363
|
-
const selected = await new
|
|
2685
|
+
const selected = await new Enquirer4.Select({
|
|
2364
2686
|
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
2365
2687
|
choices,
|
|
2366
2688
|
initial: currentValue ? 0 : 1
|
|
@@ -2368,7 +2690,7 @@ async function promptBooleanValue(key, currentValue) {
|
|
|
2368
2690
|
return selected === "true";
|
|
2369
2691
|
}
|
|
2370
2692
|
async function promptNumberValue(key, currentValue) {
|
|
2371
|
-
const input = await new
|
|
2693
|
+
const input = await new Enquirer4.Input({
|
|
2372
2694
|
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
2373
2695
|
initial: String(currentValue),
|
|
2374
2696
|
validate: (val) => {
|
|
@@ -2383,28 +2705,28 @@ async function promptEnumValue(key, currentValue, allowedValues) {
|
|
|
2383
2705
|
name: v,
|
|
2384
2706
|
message: v
|
|
2385
2707
|
}));
|
|
2386
|
-
return await new
|
|
2708
|
+
return await new Enquirer4.Select({
|
|
2387
2709
|
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
2388
2710
|
choices,
|
|
2389
2711
|
initial: allowedValues.indexOf(currentValue)
|
|
2390
2712
|
}).run();
|
|
2391
2713
|
}
|
|
2392
2714
|
async function promptStringValue(key, currentValue) {
|
|
2393
|
-
return await new
|
|
2715
|
+
return await new Enquirer4.Input({
|
|
2394
2716
|
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
2395
2717
|
initial: currentValue
|
|
2396
2718
|
}).run();
|
|
2397
2719
|
}
|
|
2398
2720
|
|
|
2399
2721
|
// src/utils/update-checker.ts
|
|
2400
|
-
import { readFileSync as
|
|
2722
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
2401
2723
|
import { execSync as execSync3 } from "child_process";
|
|
2402
2724
|
import { request } from "https";
|
|
2403
|
-
import
|
|
2725
|
+
import chalk7 from "chalk";
|
|
2404
2726
|
import stringWidth2 from "string-width";
|
|
2405
2727
|
function readUpdateCache() {
|
|
2406
2728
|
try {
|
|
2407
|
-
const raw =
|
|
2729
|
+
const raw = readFileSync5(UPDATE_CHECK_PATH, "utf-8");
|
|
2408
2730
|
return JSON.parse(raw);
|
|
2409
2731
|
} catch {
|
|
2410
2732
|
return null;
|
|
@@ -2412,7 +2734,7 @@ function readUpdateCache() {
|
|
|
2412
2734
|
}
|
|
2413
2735
|
function writeUpdateCache(cache) {
|
|
2414
2736
|
try {
|
|
2415
|
-
|
|
2737
|
+
writeFileSync4(UPDATE_CHECK_PATH, JSON.stringify(cache, null, 2), "utf-8");
|
|
2416
2738
|
} catch {
|
|
2417
2739
|
}
|
|
2418
2740
|
}
|
|
@@ -2475,12 +2797,12 @@ function detectPackageManager() {
|
|
|
2475
2797
|
}
|
|
2476
2798
|
function printUpdateNotification(currentVersion, latestVersion) {
|
|
2477
2799
|
const updateText = UPDATE_MESSAGES.UPDATE_AVAILABLE(
|
|
2478
|
-
|
|
2479
|
-
|
|
2800
|
+
chalk7.red(currentVersion),
|
|
2801
|
+
chalk7.green(latestVersion)
|
|
2480
2802
|
);
|
|
2481
2803
|
const pm = detectPackageManager();
|
|
2482
2804
|
const updateCommand = UPDATE_COMMANDS[pm] || UPDATE_COMMANDS.npm;
|
|
2483
|
-
const commandText = UPDATE_MESSAGES.UPDATE_HINT(
|
|
2805
|
+
const commandText = UPDATE_MESSAGES.UPDATE_HINT(chalk7.cyan(updateCommand));
|
|
2484
2806
|
const updateTextWidth = stringWidth2(updateText);
|
|
2485
2807
|
const commandTextWidth = stringWidth2(commandText);
|
|
2486
2808
|
const innerWidth = Math.max(updateTextWidth, commandTextWidth) + 4;
|
|
@@ -2527,10 +2849,60 @@ async function checkForUpdates(currentVersion) {
|
|
|
2527
2849
|
}
|
|
2528
2850
|
}
|
|
2529
2851
|
|
|
2852
|
+
// src/utils/json.ts
|
|
2853
|
+
function primitiveToString(value) {
|
|
2854
|
+
if (value === void 0) {
|
|
2855
|
+
return "undefined";
|
|
2856
|
+
}
|
|
2857
|
+
if (value === null) {
|
|
2858
|
+
return "null";
|
|
2859
|
+
}
|
|
2860
|
+
if (typeof value === "symbol") {
|
|
2861
|
+
return value.toString();
|
|
2862
|
+
}
|
|
2863
|
+
if (typeof value === "function") {
|
|
2864
|
+
return `[Function: ${value.name || "anonymous"}]`;
|
|
2865
|
+
}
|
|
2866
|
+
return String(value);
|
|
2867
|
+
}
|
|
2868
|
+
function safeStringify(value, indent = 2) {
|
|
2869
|
+
if (value === null || typeof value !== "object") {
|
|
2870
|
+
return primitiveToString(value);
|
|
2871
|
+
}
|
|
2872
|
+
try {
|
|
2873
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
2874
|
+
return JSON.stringify(
|
|
2875
|
+
value,
|
|
2876
|
+
(_key, val) => {
|
|
2877
|
+
if (typeof val === "bigint") {
|
|
2878
|
+
return val.toString();
|
|
2879
|
+
}
|
|
2880
|
+
if (typeof val === "undefined" || typeof val === "function" || typeof val === "symbol") {
|
|
2881
|
+
return primitiveToString(val);
|
|
2882
|
+
}
|
|
2883
|
+
if (typeof val === "object" && val !== null) {
|
|
2884
|
+
if (seen.has(val)) {
|
|
2885
|
+
return "[Circular]";
|
|
2886
|
+
}
|
|
2887
|
+
seen.add(val);
|
|
2888
|
+
}
|
|
2889
|
+
return val;
|
|
2890
|
+
},
|
|
2891
|
+
indent
|
|
2892
|
+
);
|
|
2893
|
+
} catch {
|
|
2894
|
+
try {
|
|
2895
|
+
return JSON.stringify(String(value), null, indent);
|
|
2896
|
+
} catch {
|
|
2897
|
+
return "[Unserializable]";
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2530
2902
|
// src/commands/list.ts
|
|
2531
|
-
import
|
|
2903
|
+
import chalk8 from "chalk";
|
|
2532
2904
|
function registerListCommand(program2) {
|
|
2533
|
-
program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
2905
|
+
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) => {
|
|
2534
2906
|
handleList(options);
|
|
2535
2907
|
});
|
|
2536
2908
|
}
|
|
@@ -2565,12 +2937,12 @@ function printListAsText(projectName, worktrees) {
|
|
|
2565
2937
|
for (const wt of worktrees) {
|
|
2566
2938
|
const status = getWorktreeStatus(wt);
|
|
2567
2939
|
const isIdle = status ? isWorktreeIdle(status) : false;
|
|
2568
|
-
const pathDisplay = isIdle ?
|
|
2940
|
+
const pathDisplay = isIdle ? chalk8.hex("#FF8C00")(wt.path) : wt.path;
|
|
2569
2941
|
printInfo(` ${pathDisplay} [${wt.branch}]`);
|
|
2570
2942
|
if (status) {
|
|
2571
2943
|
printInfo(` ${formatWorktreeStatus(status)}`);
|
|
2572
2944
|
} else {
|
|
2573
|
-
printInfo(` ${
|
|
2945
|
+
printInfo(` ${chalk8.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
|
|
2574
2946
|
}
|
|
2575
2947
|
printInfo("");
|
|
2576
2948
|
}
|
|
@@ -2580,12 +2952,13 @@ function printListAsText(projectName, worktrees) {
|
|
|
2580
2952
|
|
|
2581
2953
|
// src/commands/create.ts
|
|
2582
2954
|
function registerCreateCommand(program2) {
|
|
2583
|
-
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) => {
|
|
2584
|
-
handleCreate(options);
|
|
2955
|
+
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) => {
|
|
2956
|
+
await handleCreate(options);
|
|
2585
2957
|
});
|
|
2586
2958
|
}
|
|
2587
|
-
function handleCreate(options) {
|
|
2959
|
+
async function handleCreate(options) {
|
|
2588
2960
|
validateMainWorktree();
|
|
2961
|
+
await ensureOnMainWorkBranch();
|
|
2589
2962
|
const count = Number(options.number);
|
|
2590
2963
|
if (!Number.isInteger(count) || count <= 0) {
|
|
2591
2964
|
throw new ClawtError(
|
|
@@ -2601,6 +2974,7 @@ function handleCreate(options) {
|
|
|
2601
2974
|
printInfo(`\u76EE\u5F55\u8DEF\u5F84${index + 1}\uFF1A`);
|
|
2602
2975
|
printInfo(` ${wt.path}`);
|
|
2603
2976
|
printInfo(` \u5206\u652F\u540D: ${wt.branch}`);
|
|
2977
|
+
printInfo(` \u9A8C\u8BC1\u5206\u652F: ${getValidateBranchName(wt.branch)}`);
|
|
2604
2978
|
printSeparator();
|
|
2605
2979
|
});
|
|
2606
2980
|
}
|
|
@@ -2613,7 +2987,7 @@ var REMOVE_RESOLVE_MESSAGES = {
|
|
|
2613
2987
|
noMatch: MESSAGES.REMOVE_NO_MATCH
|
|
2614
2988
|
};
|
|
2615
2989
|
function registerRemoveCommand(program2) {
|
|
2616
|
-
program2.command("remove").description("\u79FB\u9664 worktree\uFF08\u652F\u6301\
|
|
2990
|
+
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) => {
|
|
2617
2991
|
await handleRemove(options);
|
|
2618
2992
|
});
|
|
2619
2993
|
}
|
|
@@ -2634,13 +3008,13 @@ async function handleRemove(options) {
|
|
|
2634
3008
|
}
|
|
2635
3009
|
printInfo("\u5373\u5C06\u79FB\u9664\u4EE5\u4E0B worktree \u53CA\u672C\u5730\u5206\u652F\uFF1A\n");
|
|
2636
3010
|
worktreesToRemove.forEach((wt, index) => {
|
|
2637
|
-
printInfo(` ${index + 1}. ${wt.path} \u2192 \u5206\u652F: ${wt.branch}`);
|
|
3011
|
+
printInfo(` ${index + 1}. ${wt.path} \u2192 \u5206\u652F: ${wt.branch} \u9A8C\u8BC1\u5206\u652F: ${getValidateBranchName(wt.branch)}`);
|
|
2638
3012
|
});
|
|
2639
3013
|
printInfo("");
|
|
2640
3014
|
const autoDelete = getConfigValue("autoDeleteBranch");
|
|
2641
3015
|
let shouldDeleteBranch = autoDelete;
|
|
2642
3016
|
if (!autoDelete) {
|
|
2643
|
-
shouldDeleteBranch = await confirmAction(
|
|
3017
|
+
shouldDeleteBranch = await confirmAction(MESSAGES.REMOVE_CONFIRM_DELETE_BRANCHES);
|
|
2644
3018
|
if (!shouldDeleteBranch) {
|
|
2645
3019
|
printHint(MESSAGES.REMOVE_BRANCHES_KEPT);
|
|
2646
3020
|
}
|
|
@@ -2648,10 +3022,12 @@ async function handleRemove(options) {
|
|
|
2648
3022
|
const failures = [];
|
|
2649
3023
|
for (const wt of worktreesToRemove) {
|
|
2650
3024
|
try {
|
|
3025
|
+
await ensureOnMainWorkBranch();
|
|
2651
3026
|
removeWorktreeByPath(wt.path);
|
|
2652
3027
|
if (shouldDeleteBranch) {
|
|
2653
3028
|
deleteBranch(wt.branch);
|
|
2654
3029
|
}
|
|
3030
|
+
deleteValidateBranch(wt.branch);
|
|
2655
3031
|
removeSnapshot(projectName, wt.branch);
|
|
2656
3032
|
printSuccess(MESSAGES.WORKTREE_REMOVED(wt.path));
|
|
2657
3033
|
} catch (error) {
|
|
@@ -2676,7 +3052,7 @@ async function handleRemove(options) {
|
|
|
2676
3052
|
|
|
2677
3053
|
// src/commands/run.ts
|
|
2678
3054
|
function registerRunCommand(program2) {
|
|
2679
|
-
program2.command("run").description("\u6279\u91CF\u521B\u5EFA worktree \
|
|
3055
|
+
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) => {
|
|
2680
3056
|
await handleRun(options);
|
|
2681
3057
|
});
|
|
2682
3058
|
}
|
|
@@ -2714,6 +3090,9 @@ function handleDryRunFromFile(options) {
|
|
|
2714
3090
|
}
|
|
2715
3091
|
async function handleRun(options) {
|
|
2716
3092
|
validateMainWorktree();
|
|
3093
|
+
if (!options.dryRun) {
|
|
3094
|
+
await ensureOnMainWorkBranch();
|
|
3095
|
+
}
|
|
2717
3096
|
if (options.file && options.tasks) {
|
|
2718
3097
|
throw new ClawtError(MESSAGES.FILE_AND_TASKS_CONFLICT);
|
|
2719
3098
|
}
|
|
@@ -2769,7 +3148,7 @@ var RESUME_RESOLVE_MESSAGES = {
|
|
|
2769
3148
|
noMatch: MESSAGES.RESUME_NO_MATCH
|
|
2770
3149
|
};
|
|
2771
3150
|
function registerResumeCommand(program2) {
|
|
2772
|
-
program2.command("resume").description("\u5728\u5DF2\u6709 worktree \u4E2D\u6062\u590D Claude Code \
|
|
3151
|
+
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) => {
|
|
2773
3152
|
await handleResume(options);
|
|
2774
3153
|
});
|
|
2775
3154
|
}
|
|
@@ -2778,7 +3157,12 @@ async function handleResume(options) {
|
|
|
2778
3157
|
validateClaudeCodeInstalled();
|
|
2779
3158
|
logger.info(`resume \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F\u8FC7\u6EE4: ${options.branch ?? "(\u65E0)"}`);
|
|
2780
3159
|
const worktrees = getProjectWorktrees();
|
|
2781
|
-
|
|
3160
|
+
let targetWorktrees;
|
|
3161
|
+
if (!options.branch && worktrees.length > 1) {
|
|
3162
|
+
targetWorktrees = await promptGroupedMultiSelectBranches(worktrees, RESUME_RESOLVE_MESSAGES.selectBranch);
|
|
3163
|
+
} else {
|
|
3164
|
+
targetWorktrees = await resolveTargetWorktrees(worktrees, RESUME_RESOLVE_MESSAGES, options.branch);
|
|
3165
|
+
}
|
|
2782
3166
|
if (targetWorktrees.length === 0) {
|
|
2783
3167
|
return;
|
|
2784
3168
|
}
|
|
@@ -2816,9 +3200,6 @@ async function handleBatchResume(worktrees) {
|
|
|
2816
3200
|
printSuccess(MESSAGES.RESUME_ALL_SUCCESS(worktrees.length));
|
|
2817
3201
|
}
|
|
2818
3202
|
|
|
2819
|
-
// src/commands/validate.ts
|
|
2820
|
-
import Enquirer4 from "enquirer";
|
|
2821
|
-
|
|
2822
3203
|
// src/commands/sync.ts
|
|
2823
3204
|
function registerSyncCommand(program2) {
|
|
2824
3205
|
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) => {
|
|
@@ -2848,9 +3229,9 @@ function mergeMainBranch(worktreePath, mainBranch) {
|
|
|
2848
3229
|
throw new ClawtError(`\u5408\u5E76 ${mainBranch} \u5931\u8D25`);
|
|
2849
3230
|
}
|
|
2850
3231
|
}
|
|
2851
|
-
function executeSyncForBranch(targetWorktreePath, branch) {
|
|
3232
|
+
async function executeSyncForBranch(targetWorktreePath, branch) {
|
|
2852
3233
|
const mainWorktreePath = getGitTopLevel();
|
|
2853
|
-
const mainBranch =
|
|
3234
|
+
const mainBranch = getMainWorkBranch();
|
|
2854
3235
|
if (!isWorkingDirClean(targetWorktreePath)) {
|
|
2855
3236
|
autoSaveChanges(targetWorktreePath, branch);
|
|
2856
3237
|
}
|
|
@@ -2866,15 +3247,20 @@ function executeSyncForBranch(targetWorktreePath, branch) {
|
|
|
2866
3247
|
logger.info(`\u5DF2\u6E05\u9664\u5206\u652F ${branch} \u7684 validate \u5FEB\u7167`);
|
|
2867
3248
|
}
|
|
2868
3249
|
printSuccess(MESSAGES.SYNC_SUCCESS(branch, mainBranch));
|
|
3250
|
+
await rebuildValidateBranch(branch, mainWorktreePath);
|
|
3251
|
+
const validateBranchName = getValidateBranchName(branch);
|
|
3252
|
+
printInfo(MESSAGES.SYNC_VALIDATE_BRANCH_REBUILT(validateBranchName));
|
|
2869
3253
|
return { success: true, hasConflict: false };
|
|
2870
3254
|
}
|
|
2871
3255
|
async function handleSync(options) {
|
|
2872
3256
|
validateMainWorktree();
|
|
3257
|
+
requireProjectConfig();
|
|
3258
|
+
await ensureOnMainWorkBranch();
|
|
2873
3259
|
logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
|
|
2874
3260
|
const worktrees = getProjectWorktrees();
|
|
2875
3261
|
const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
|
|
2876
3262
|
const { path: targetWorktreePath, branch } = worktree;
|
|
2877
|
-
executeSyncForBranch(targetWorktreePath, branch);
|
|
3263
|
+
await executeSyncForBranch(targetWorktreePath, branch);
|
|
2878
3264
|
}
|
|
2879
3265
|
|
|
2880
3266
|
// src/commands/validate.ts
|
|
@@ -2885,43 +3271,12 @@ var VALIDATE_RESOLVE_MESSAGES = {
|
|
|
2885
3271
|
noMatch: MESSAGES.VALIDATE_NO_MATCH
|
|
2886
3272
|
};
|
|
2887
3273
|
function registerValidateCommand(program2) {
|
|
2888
|
-
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) => {
|
|
3274
|
+
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) => {
|
|
2889
3275
|
await handleValidate(options);
|
|
2890
3276
|
});
|
|
2891
3277
|
}
|
|
2892
3278
|
async function handleDirtyMainWorktree(mainWorktreePath) {
|
|
2893
|
-
|
|
2894
|
-
const choice = await new Enquirer4.Select({
|
|
2895
|
-
message: "\u9009\u62E9\u5904\u7406\u65B9\u5F0F",
|
|
2896
|
-
choices: [
|
|
2897
|
-
{
|
|
2898
|
-
name: "reset",
|
|
2899
|
-
message: "reset (\u63A8\u8350) - \u4E22\u5F03\u6240\u6709\u66F4\u6539 (git reset --hard HEAD && git clean -fd)"
|
|
2900
|
-
},
|
|
2901
|
-
{
|
|
2902
|
-
name: "stash",
|
|
2903
|
-
message: "stash - \u6682\u5B58\u66F4\u6539 (git add . && git stash)"
|
|
2904
|
-
},
|
|
2905
|
-
{
|
|
2906
|
-
name: "exit",
|
|
2907
|
-
message: "exit - \u9000\u51FA\uFF0C\u624B\u52A8\u5904\u7406"
|
|
2908
|
-
}
|
|
2909
|
-
],
|
|
2910
|
-
initial: 0
|
|
2911
|
-
}).run();
|
|
2912
|
-
if (choice === "exit") {
|
|
2913
|
-
throw new ClawtError("\u7528\u6237\u9009\u62E9\u9000\u51FA");
|
|
2914
|
-
}
|
|
2915
|
-
if (choice === "reset") {
|
|
2916
|
-
gitResetHard(mainWorktreePath);
|
|
2917
|
-
gitCleanForce(mainWorktreePath);
|
|
2918
|
-
} else if (choice === "stash") {
|
|
2919
|
-
gitAddAll(mainWorktreePath);
|
|
2920
|
-
gitStashPush("clawt:auto-stash", mainWorktreePath);
|
|
2921
|
-
}
|
|
2922
|
-
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
2923
|
-
throw new ClawtError("\u5DE5\u4F5C\u533A\u4ECD\u7136\u4E0D\u5E72\u51C0\uFF0C\u8BF7\u624B\u52A8\u5904\u7406");
|
|
2924
|
-
}
|
|
3279
|
+
await handleDirtyWorkingDir(mainWorktreePath);
|
|
2925
3280
|
}
|
|
2926
3281
|
function migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted) {
|
|
2927
3282
|
let didTempCommit = false;
|
|
@@ -2964,7 +3319,7 @@ async function handlePatchApplyFailure(targetWorktreePath, branchName) {
|
|
|
2964
3319
|
return;
|
|
2965
3320
|
}
|
|
2966
3321
|
printInfo(MESSAGES.VALIDATE_AUTO_SYNC_START(branchName));
|
|
2967
|
-
const syncResult = executeSyncForBranch(targetWorktreePath, branchName);
|
|
3322
|
+
const syncResult = await executeSyncForBranch(targetWorktreePath, branchName);
|
|
2968
3323
|
}
|
|
2969
3324
|
function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName) {
|
|
2970
3325
|
gitAddAll(mainWorktreePath);
|
|
@@ -2976,6 +3331,7 @@ function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName) {
|
|
|
2976
3331
|
}
|
|
2977
3332
|
async function handleValidateClean(options) {
|
|
2978
3333
|
validateMainWorktree();
|
|
3334
|
+
requireProjectConfig();
|
|
2979
3335
|
const projectName = getProjectName();
|
|
2980
3336
|
const mainWorktreePath = getGitTopLevel();
|
|
2981
3337
|
const worktrees = getProjectWorktrees();
|
|
@@ -2996,17 +3352,24 @@ async function handleValidateClean(options) {
|
|
|
2996
3352
|
gitResetHard(mainWorktreePath);
|
|
2997
3353
|
gitCleanForce(mainWorktreePath);
|
|
2998
3354
|
}
|
|
3355
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
2999
3356
|
removeSnapshot(projectName, branchName);
|
|
3000
3357
|
printSuccess(MESSAGES.VALIDATE_CLEANED(branchName));
|
|
3001
3358
|
}
|
|
3002
3359
|
async function handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
|
|
3360
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
3361
|
+
if (!checkBranchExists(validateBranchName)) {
|
|
3362
|
+
throw new ClawtError(MESSAGES.VALIDATE_BRANCH_NOT_FOUND(validateBranchName, branchName));
|
|
3363
|
+
}
|
|
3364
|
+
gitCheckout(validateBranchName, mainWorktreePath);
|
|
3003
3365
|
const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
3004
3366
|
if (!result.success) {
|
|
3367
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
3005
3368
|
await handlePatchApplyFailure(targetWorktreePath, branchName);
|
|
3006
3369
|
return;
|
|
3007
3370
|
}
|
|
3008
3371
|
saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName);
|
|
3009
|
-
printSuccess(MESSAGES.
|
|
3372
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
3010
3373
|
}
|
|
3011
3374
|
async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
|
|
3012
3375
|
const { treeHash: oldTreeHash, headCommitHash: oldHeadCommitHash } = readSnapshot(projectName, branchName);
|
|
@@ -3014,8 +3377,17 @@ async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, p
|
|
|
3014
3377
|
gitResetHard(mainWorktreePath);
|
|
3015
3378
|
gitCleanForce(mainWorktreePath);
|
|
3016
3379
|
}
|
|
3380
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
3381
|
+
if (!checkBranchExists(validateBranchName)) {
|
|
3382
|
+
throw new ClawtError(MESSAGES.VALIDATE_BRANCH_NOT_FOUND(validateBranchName, branchName));
|
|
3383
|
+
}
|
|
3384
|
+
const currentBranch = getCurrentBranch(mainWorktreePath);
|
|
3385
|
+
if (currentBranch !== validateBranchName) {
|
|
3386
|
+
gitCheckout(validateBranchName, mainWorktreePath);
|
|
3387
|
+
}
|
|
3017
3388
|
const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
3018
3389
|
if (!result.success) {
|
|
3390
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
3019
3391
|
await handlePatchApplyFailure(targetWorktreePath, branchName);
|
|
3020
3392
|
return;
|
|
3021
3393
|
}
|
|
@@ -3030,7 +3402,7 @@ async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, p
|
|
|
3030
3402
|
} else if (oldChangePatch.length > 0) {
|
|
3031
3403
|
logger.warn("\u65E7\u53D8\u66F4 patch \u4E0E\u5F53\u524D HEAD \u51B2\u7A81\uFF0C\u964D\u7EA7\u4E3A\u5168\u91CF\u6A21\u5F0F");
|
|
3032
3404
|
printWarning(MESSAGES.INCREMENTAL_VALIDATE_FALLBACK);
|
|
3033
|
-
printSuccess(MESSAGES.
|
|
3405
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
3034
3406
|
return;
|
|
3035
3407
|
}
|
|
3036
3408
|
} else {
|
|
@@ -3039,7 +3411,7 @@ async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, p
|
|
|
3039
3411
|
} catch (error) {
|
|
3040
3412
|
logger.warn(`\u589E\u91CF read-tree \u5931\u8D25: ${error}`);
|
|
3041
3413
|
printWarning(MESSAGES.INCREMENTAL_VALIDATE_FALLBACK);
|
|
3042
|
-
printSuccess(MESSAGES.
|
|
3414
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
3043
3415
|
return;
|
|
3044
3416
|
}
|
|
3045
3417
|
printSuccess(MESSAGES.INCREMENTAL_VALIDATE_SUCCESS(branchName));
|
|
@@ -3103,6 +3475,7 @@ async function handleValidate(options) {
|
|
|
3103
3475
|
return;
|
|
3104
3476
|
}
|
|
3105
3477
|
validateMainWorktree();
|
|
3478
|
+
requireProjectConfig();
|
|
3106
3479
|
const projectName = getProjectName();
|
|
3107
3480
|
const mainWorktreePath = getGitTopLevel();
|
|
3108
3481
|
const worktrees = getProjectWorktrees();
|
|
@@ -3135,7 +3508,7 @@ async function handleValidate(options) {
|
|
|
3135
3508
|
|
|
3136
3509
|
// src/commands/merge.ts
|
|
3137
3510
|
function registerMergeCommand(program2) {
|
|
3138
|
-
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 <
|
|
3511
|
+
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) => {
|
|
3139
3512
|
await handleMerge(options);
|
|
3140
3513
|
});
|
|
3141
3514
|
}
|
|
@@ -3153,7 +3526,7 @@ async function handleSquashIfNeeded(targetWorktreePath, mainWorktreePath, branch
|
|
|
3153
3526
|
if (!shouldSquash) {
|
|
3154
3527
|
return false;
|
|
3155
3528
|
}
|
|
3156
|
-
const mainBranch =
|
|
3529
|
+
const mainBranch = getMainWorkBranch();
|
|
3157
3530
|
const mergeBase = gitMergeBase(mainBranch, branchName, mainWorktreePath);
|
|
3158
3531
|
logger.info(`squash: merge-base = ${mergeBase}, \u5206\u652F = ${branchName}`);
|
|
3159
3532
|
gitResetSoftTo(mergeBase, targetWorktreePath);
|
|
@@ -3180,6 +3553,7 @@ function cleanupWorktreeAndBranch(worktreePath, branchName) {
|
|
|
3180
3553
|
async function handleMerge(options) {
|
|
3181
3554
|
validateMainWorktree();
|
|
3182
3555
|
const mainWorktreePath = getGitTopLevel();
|
|
3556
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
3183
3557
|
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)"}`);
|
|
3184
3558
|
const worktrees = getProjectWorktrees();
|
|
3185
3559
|
const worktree = await resolveTargetWorktree(worktrees, MERGE_RESOLVE_MESSAGES, options.branch);
|
|
@@ -3253,7 +3627,7 @@ async function handleMerge(options) {
|
|
|
3253
3627
|
}
|
|
3254
3628
|
|
|
3255
3629
|
// src/commands/config.ts
|
|
3256
|
-
import
|
|
3630
|
+
import chalk9 from "chalk";
|
|
3257
3631
|
import Enquirer5 from "enquirer";
|
|
3258
3632
|
function registerConfigCommand(program2) {
|
|
3259
3633
|
const configCmd = program2.command("config").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u5168\u5C40\u914D\u7F6E").action(async () => {
|
|
@@ -3314,7 +3688,7 @@ async function handleInteractiveConfigSet() {
|
|
|
3314
3688
|
const isObject = typeof DEFAULT_CONFIG[k] === "object";
|
|
3315
3689
|
return {
|
|
3316
3690
|
name: k,
|
|
3317
|
-
message: `${k}: ${isObject ?
|
|
3691
|
+
message: `${k}: ${isObject ? chalk9.dim(JSON.stringify(config2[k])) : formatConfigValue(config2[k])} ${chalk9.dim(`\u2014 ${CONFIG_DESCRIPTIONS[k]}`)}`,
|
|
3318
3692
|
...isObject && { disabled: CONFIG_ALIAS_DISABLED_HINT }
|
|
3319
3693
|
};
|
|
3320
3694
|
});
|
|
@@ -3347,6 +3721,7 @@ function registerResetCommand(program2) {
|
|
|
3347
3721
|
}
|
|
3348
3722
|
async function handleReset() {
|
|
3349
3723
|
validateMainWorktree();
|
|
3724
|
+
requireProjectConfig();
|
|
3350
3725
|
const mainWorktreePath = getGitTopLevel();
|
|
3351
3726
|
logger.info("reset \u547D\u4EE4\u6267\u884C");
|
|
3352
3727
|
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
@@ -3369,9 +3744,9 @@ async function handleReset() {
|
|
|
3369
3744
|
}
|
|
3370
3745
|
|
|
3371
3746
|
// src/commands/status.ts
|
|
3372
|
-
import
|
|
3747
|
+
import chalk10 from "chalk";
|
|
3373
3748
|
function registerStatusCommand(program2) {
|
|
3374
|
-
program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
3749
|
+
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").action((options) => {
|
|
3375
3750
|
handleStatus(options);
|
|
3376
3751
|
});
|
|
3377
3752
|
}
|
|
@@ -3482,7 +3857,7 @@ function printStatusAsJson(result) {
|
|
|
3482
3857
|
}
|
|
3483
3858
|
function printStatusAsText(result) {
|
|
3484
3859
|
printDoubleSeparator();
|
|
3485
|
-
printInfo(` ${
|
|
3860
|
+
printInfo(` ${chalk10.bold.cyan(MESSAGES.STATUS_TITLE(result.main.projectName))}`);
|
|
3486
3861
|
printDoubleSeparator();
|
|
3487
3862
|
printInfo("");
|
|
3488
3863
|
printMainSection(result.main);
|
|
@@ -3495,17 +3870,17 @@ function printStatusAsText(result) {
|
|
|
3495
3870
|
printDoubleSeparator();
|
|
3496
3871
|
}
|
|
3497
3872
|
function printMainSection(main2) {
|
|
3498
|
-
printInfo(` ${
|
|
3499
|
-
printInfo(` \u5206\u652F: ${
|
|
3873
|
+
printInfo(` ${chalk10.bold("\u25C6")} ${chalk10.bold(MESSAGES.STATUS_MAIN_SECTION)}`);
|
|
3874
|
+
printInfo(` \u5206\u652F: ${chalk10.bold(main2.branch)}`);
|
|
3500
3875
|
if (main2.isClean) {
|
|
3501
|
-
printInfo(` \u72B6\u6001: ${
|
|
3876
|
+
printInfo(` \u72B6\u6001: ${chalk10.green("\u2713 \u5E72\u51C0")}`);
|
|
3502
3877
|
} else {
|
|
3503
|
-
printInfo(` \u72B6\u6001: ${
|
|
3878
|
+
printInfo(` \u72B6\u6001: ${chalk10.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
|
|
3504
3879
|
}
|
|
3505
3880
|
printInfo("");
|
|
3506
3881
|
}
|
|
3507
3882
|
function printWorktreesSection(worktrees, total) {
|
|
3508
|
-
printInfo(` ${
|
|
3883
|
+
printInfo(` ${chalk10.bold("\u25C6")} ${chalk10.bold(MESSAGES.STATUS_WORKTREES_SECTION)} (${total} \u4E2A)`);
|
|
3509
3884
|
printInfo("");
|
|
3510
3885
|
if (worktrees.length === 0) {
|
|
3511
3886
|
printInfo(` ${MESSAGES.STATUS_NO_WORKTREES}`);
|
|
@@ -3517,56 +3892,56 @@ function printWorktreesSection(worktrees, total) {
|
|
|
3517
3892
|
}
|
|
3518
3893
|
function printWorktreeItem(wt) {
|
|
3519
3894
|
const statusLabel = formatChangeStatusLabel(wt.changeStatus);
|
|
3520
|
-
printInfo(` ${
|
|
3895
|
+
printInfo(` ${chalk10.bold("\u25CF")} ${chalk10.bold(wt.branch)} [${statusLabel}]`);
|
|
3521
3896
|
if (wt.insertions > 0 || wt.deletions > 0) {
|
|
3522
|
-
printInfo(` ${
|
|
3897
|
+
printInfo(` ${chalk10.green(`+${wt.insertions}`)} ${chalk10.red(`-${wt.deletions}`)}`);
|
|
3523
3898
|
}
|
|
3524
3899
|
if (wt.commitsAhead > 0) {
|
|
3525
|
-
printInfo(` ${
|
|
3900
|
+
printInfo(` ${chalk10.yellow(`${wt.commitsAhead} \u4E2A\u672C\u5730\u63D0\u4EA4`)}`);
|
|
3526
3901
|
}
|
|
3527
3902
|
if (wt.commitsBehind > 0) {
|
|
3528
|
-
printInfo(` ${
|
|
3903
|
+
printInfo(` ${chalk10.yellow(`\u843D\u540E\u4E3B\u5206\u652F ${wt.commitsBehind} \u4E2A\u63D0\u4EA4`)}`);
|
|
3529
3904
|
} else {
|
|
3530
|
-
printInfo(` ${
|
|
3905
|
+
printInfo(` ${chalk10.green("\u4E0E\u4E3B\u5206\u652F\u540C\u6B65")}`);
|
|
3531
3906
|
}
|
|
3532
3907
|
if (wt.createdAt) {
|
|
3533
3908
|
const relativeTime = formatRelativeTime(wt.createdAt);
|
|
3534
3909
|
if (relativeTime) {
|
|
3535
|
-
printInfo(` ${
|
|
3910
|
+
printInfo(` ${chalk10.gray(MESSAGES.STATUS_CREATED_AT(relativeTime))}`);
|
|
3536
3911
|
}
|
|
3537
3912
|
}
|
|
3538
3913
|
if (wt.snapshotTime) {
|
|
3539
3914
|
const relativeTime = formatRelativeTime(wt.snapshotTime);
|
|
3540
3915
|
if (relativeTime) {
|
|
3541
|
-
printInfo(` ${
|
|
3916
|
+
printInfo(` ${chalk10.green(MESSAGES.STATUS_LAST_VALIDATED(relativeTime))}`);
|
|
3542
3917
|
}
|
|
3543
3918
|
} else {
|
|
3544
|
-
printInfo(` ${
|
|
3919
|
+
printInfo(` ${chalk10.red(MESSAGES.STATUS_NOT_VALIDATED)}`);
|
|
3545
3920
|
}
|
|
3546
3921
|
printInfo("");
|
|
3547
3922
|
}
|
|
3548
3923
|
function formatChangeStatusLabel(status) {
|
|
3549
3924
|
switch (status) {
|
|
3550
3925
|
case "committed":
|
|
3551
|
-
return
|
|
3926
|
+
return chalk10.green(MESSAGES.STATUS_CHANGE_COMMITTED);
|
|
3552
3927
|
case "uncommitted":
|
|
3553
|
-
return
|
|
3928
|
+
return chalk10.yellow(MESSAGES.STATUS_CHANGE_UNCOMMITTED);
|
|
3554
3929
|
case "conflict":
|
|
3555
|
-
return
|
|
3930
|
+
return chalk10.red(MESSAGES.STATUS_CHANGE_CONFLICT);
|
|
3556
3931
|
case "clean":
|
|
3557
|
-
return
|
|
3932
|
+
return chalk10.gray(MESSAGES.STATUS_CHANGE_CLEAN);
|
|
3558
3933
|
}
|
|
3559
3934
|
}
|
|
3560
3935
|
function printSnapshotsSection(snapshots) {
|
|
3561
|
-
printInfo(` ${
|
|
3936
|
+
printInfo(` ${chalk10.bold("\u25C6")} ${chalk10.bold(MESSAGES.STATUS_SNAPSHOTS_SECTION)} (${snapshots.total} \u4E2A)`);
|
|
3562
3937
|
if (snapshots.orphaned > 0) {
|
|
3563
|
-
printInfo(` ${
|
|
3938
|
+
printInfo(` ${chalk10.yellow(MESSAGES.STATUS_SNAPSHOT_ORPHANED(snapshots.orphaned))}`);
|
|
3564
3939
|
}
|
|
3565
3940
|
printInfo("");
|
|
3566
3941
|
}
|
|
3567
3942
|
|
|
3568
3943
|
// src/commands/alias.ts
|
|
3569
|
-
import
|
|
3944
|
+
import chalk11 from "chalk";
|
|
3570
3945
|
function getRegisteredCommandNames(program2) {
|
|
3571
3946
|
return program2.commands.map((cmd) => cmd.name());
|
|
3572
3947
|
}
|
|
@@ -3587,7 +3962,7 @@ ${MESSAGES.ALIAS_LIST_TITLE}
|
|
|
3587
3962
|
`);
|
|
3588
3963
|
printSeparator();
|
|
3589
3964
|
for (const [alias, command] of entries) {
|
|
3590
|
-
printInfo(` ${
|
|
3965
|
+
printInfo(` ${chalk11.bold(alias)} \u2192 ${chalk11.cyan(command)}`);
|
|
3591
3966
|
}
|
|
3592
3967
|
printInfo("");
|
|
3593
3968
|
printSeparator();
|
|
@@ -3619,7 +3994,7 @@ function handleAliasRemove(alias) {
|
|
|
3619
3994
|
printSuccess(MESSAGES.ALIAS_REMOVE_SUCCESS(alias));
|
|
3620
3995
|
}
|
|
3621
3996
|
function registerAliasCommand(program2) {
|
|
3622
|
-
const aliasCmd = program2.command("alias").description("\u7BA1\u7406\u547D\u4EE4\u522B\u540D").action(() => {
|
|
3997
|
+
const aliasCmd = program2.command("alias").description("\u7BA1\u7406\u547D\u4EE4\u522B\u540D\uFF08\u5217\u51FA / \u8BBE\u7F6E / \u79FB\u9664\uFF09").action(() => {
|
|
3623
3998
|
handleAliasList();
|
|
3624
3999
|
});
|
|
3625
4000
|
aliasCmd.command("list").description("\u5217\u51FA\u6240\u6709\u522B\u540D").action(() => {
|
|
@@ -3634,9 +4009,9 @@ function registerAliasCommand(program2) {
|
|
|
3634
4009
|
}
|
|
3635
4010
|
|
|
3636
4011
|
// src/commands/projects.ts
|
|
3637
|
-
import { existsSync as
|
|
3638
|
-
import { join as
|
|
3639
|
-
import
|
|
4012
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
4013
|
+
import { join as join8 } from "path";
|
|
4014
|
+
import chalk12 from "chalk";
|
|
3640
4015
|
function registerProjectsCommand(program2) {
|
|
3641
4016
|
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) => {
|
|
3642
4017
|
handleProjects({ name, json: options.json });
|
|
@@ -3659,8 +4034,8 @@ function handleProjectsOverview(json) {
|
|
|
3659
4034
|
printProjectsOverviewAsText(result);
|
|
3660
4035
|
}
|
|
3661
4036
|
function handleProjectDetail(name, json) {
|
|
3662
|
-
const projectDir =
|
|
3663
|
-
if (!
|
|
4037
|
+
const projectDir = join8(WORKTREES_DIR, name);
|
|
4038
|
+
if (!existsSync10(projectDir)) {
|
|
3664
4039
|
printError(MESSAGES.PROJECTS_NOT_FOUND(name));
|
|
3665
4040
|
process.exit(1);
|
|
3666
4041
|
}
|
|
@@ -3673,7 +4048,7 @@ function handleProjectDetail(name, json) {
|
|
|
3673
4048
|
printProjectDetailAsText(result);
|
|
3674
4049
|
}
|
|
3675
4050
|
function collectProjectsOverview() {
|
|
3676
|
-
if (!
|
|
4051
|
+
if (!existsSync10(WORKTREES_DIR)) {
|
|
3677
4052
|
return { projects: [], totalProjects: 0, totalDiskUsage: 0 };
|
|
3678
4053
|
}
|
|
3679
4054
|
const entries = readdirSync5(WORKTREES_DIR, { withFileTypes: true });
|
|
@@ -3682,7 +4057,7 @@ function collectProjectsOverview() {
|
|
|
3682
4057
|
if (!entry.isDirectory()) {
|
|
3683
4058
|
continue;
|
|
3684
4059
|
}
|
|
3685
|
-
const projectDir =
|
|
4060
|
+
const projectDir = join8(WORKTREES_DIR, entry.name);
|
|
3686
4061
|
const overview = collectSingleProjectOverview(entry.name, projectDir);
|
|
3687
4062
|
projects.push(overview);
|
|
3688
4063
|
}
|
|
@@ -3699,7 +4074,7 @@ function collectSingleProjectOverview(name, projectDir) {
|
|
|
3699
4074
|
const worktreeDirs = subEntries.filter((e) => e.isDirectory());
|
|
3700
4075
|
const worktreeCount = worktreeDirs.length;
|
|
3701
4076
|
const diskUsage = calculateDirSize(projectDir);
|
|
3702
|
-
const lastActiveTime = resolveProjectLastActiveTime(projectDir, worktreeDirs.map((e) =>
|
|
4077
|
+
const lastActiveTime = resolveProjectLastActiveTime(projectDir, worktreeDirs.map((e) => join8(projectDir, e.name)));
|
|
3703
4078
|
return {
|
|
3704
4079
|
name,
|
|
3705
4080
|
worktreeCount,
|
|
@@ -3714,7 +4089,7 @@ function collectProjectDetail(name, projectDir) {
|
|
|
3714
4089
|
if (!entry.isDirectory()) {
|
|
3715
4090
|
continue;
|
|
3716
4091
|
}
|
|
3717
|
-
const wtPath =
|
|
4092
|
+
const wtPath = join8(projectDir, entry.name);
|
|
3718
4093
|
const detail = collectSingleWorktreeDetail(entry.name, wtPath);
|
|
3719
4094
|
worktrees.push(detail);
|
|
3720
4095
|
}
|
|
@@ -3728,7 +4103,7 @@ function collectProjectDetail(name, projectDir) {
|
|
|
3728
4103
|
};
|
|
3729
4104
|
}
|
|
3730
4105
|
function collectSingleWorktreeDetail(branch, wtPath) {
|
|
3731
|
-
const stat =
|
|
4106
|
+
const stat = statSync4(wtPath);
|
|
3732
4107
|
const diskUsage = calculateDirSize(wtPath);
|
|
3733
4108
|
return {
|
|
3734
4109
|
branch,
|
|
@@ -3738,10 +4113,10 @@ function collectSingleWorktreeDetail(branch, wtPath) {
|
|
|
3738
4113
|
};
|
|
3739
4114
|
}
|
|
3740
4115
|
function resolveProjectLastActiveTime(projectDir, worktreePaths) {
|
|
3741
|
-
let latestTime =
|
|
4116
|
+
let latestTime = statSync4(projectDir).mtime;
|
|
3742
4117
|
for (const wtPath of worktreePaths) {
|
|
3743
4118
|
try {
|
|
3744
|
-
const wtStat =
|
|
4119
|
+
const wtStat = statSync4(wtPath);
|
|
3745
4120
|
if (wtStat.mtime > latestTime) {
|
|
3746
4121
|
latestTime = wtStat.mtime;
|
|
3747
4122
|
}
|
|
@@ -3755,7 +4130,7 @@ function sortByLastActiveTimeDesc(projects) {
|
|
|
3755
4130
|
}
|
|
3756
4131
|
function printProjectsOverviewAsText(result) {
|
|
3757
4132
|
printDoubleSeparator();
|
|
3758
|
-
printInfo(` ${
|
|
4133
|
+
printInfo(` ${chalk12.bold.cyan(MESSAGES.PROJECTS_OVERVIEW_TITLE)}`);
|
|
3759
4134
|
printDoubleSeparator();
|
|
3760
4135
|
printInfo("");
|
|
3761
4136
|
if (result.projects.length === 0) {
|
|
@@ -3769,7 +4144,7 @@ function printProjectsOverviewAsText(result) {
|
|
|
3769
4144
|
}
|
|
3770
4145
|
printSeparator();
|
|
3771
4146
|
printInfo("");
|
|
3772
|
-
printInfo(` \u5171 ${
|
|
4147
|
+
printInfo(` \u5171 ${chalk12.bold(String(result.totalProjects))} \u4E2A\u9879\u76EE ${chalk12.gray(MESSAGES.PROJECTS_TOTAL_DISK_USAGE(formatDiskSize(result.totalDiskUsage)))}`);
|
|
3773
4148
|
printInfo("");
|
|
3774
4149
|
printDoubleSeparator();
|
|
3775
4150
|
}
|
|
@@ -3777,16 +4152,16 @@ function printProjectOverviewItem(project) {
|
|
|
3777
4152
|
const relativeTime = formatRelativeTime(project.lastActiveTime);
|
|
3778
4153
|
const activeLabel = relativeTime ? MESSAGES.PROJECTS_LAST_ACTIVE(relativeTime) : "";
|
|
3779
4154
|
const diskLabel = MESSAGES.PROJECTS_DISK_USAGE(formatDiskSize(project.diskUsage));
|
|
3780
|
-
printInfo(` ${
|
|
3781
|
-
printInfo(` ${MESSAGES.PROJECTS_WORKTREE_COUNT(project.worktreeCount)} ${
|
|
4155
|
+
printInfo(` ${chalk12.bold("\u25CF")} ${chalk12.bold(project.name)}`);
|
|
4156
|
+
printInfo(` ${MESSAGES.PROJECTS_WORKTREE_COUNT(project.worktreeCount)} ${chalk12.gray(activeLabel)} ${chalk12.gray(diskLabel)}`);
|
|
3782
4157
|
printInfo("");
|
|
3783
4158
|
}
|
|
3784
4159
|
function printProjectDetailAsText(result) {
|
|
3785
4160
|
printDoubleSeparator();
|
|
3786
|
-
printInfo(` ${
|
|
4161
|
+
printInfo(` ${chalk12.bold.cyan(MESSAGES.PROJECTS_DETAIL_TITLE(result.name))}`);
|
|
3787
4162
|
printDoubleSeparator();
|
|
3788
4163
|
printInfo("");
|
|
3789
|
-
printInfo(` ${
|
|
4164
|
+
printInfo(` ${chalk12.bold("\u25C6")} ${chalk12.bold(MESSAGES.PROJECTS_PATH(result.projectDir))}`);
|
|
3790
4165
|
printInfo(` ${MESSAGES.PROJECTS_TOTAL_DISK_USAGE(formatDiskSize(result.totalDiskUsage))}`);
|
|
3791
4166
|
printInfo("");
|
|
3792
4167
|
printSeparator();
|
|
@@ -3806,14 +4181,14 @@ function printWorktreeDetailItem(wt) {
|
|
|
3806
4181
|
const relativeTime = formatRelativeTime(wt.lastModifiedTime);
|
|
3807
4182
|
const modifiedLabel = relativeTime ? MESSAGES.PROJECTS_LAST_MODIFIED(relativeTime) : "";
|
|
3808
4183
|
const diskLabel = MESSAGES.PROJECTS_DISK_USAGE(formatDiskSize(wt.diskUsage));
|
|
3809
|
-
printInfo(` ${
|
|
4184
|
+
printInfo(` ${chalk12.bold("\u25CF")} ${chalk12.bold(wt.branch)}`);
|
|
3810
4185
|
printInfo(` ${wt.path}`);
|
|
3811
|
-
printInfo(` ${
|
|
4186
|
+
printInfo(` ${chalk12.gray(modifiedLabel)} ${chalk12.gray(diskLabel)}`);
|
|
3812
4187
|
printInfo("");
|
|
3813
4188
|
}
|
|
3814
4189
|
|
|
3815
4190
|
// src/commands/completion.ts
|
|
3816
|
-
import { readFileSync as
|
|
4191
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync12 } from "fs";
|
|
3817
4192
|
import { resolve as resolve2 } from "path";
|
|
3818
4193
|
import { homedir as homedir2 } from "os";
|
|
3819
4194
|
|
|
@@ -3864,14 +4239,14 @@ compdef _clawt_completion clawt
|
|
|
3864
4239
|
}
|
|
3865
4240
|
|
|
3866
4241
|
// src/utils/completion-engine.ts
|
|
3867
|
-
import { existsSync as
|
|
3868
|
-
import { join as
|
|
4242
|
+
import { existsSync as existsSync11, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
4243
|
+
import { join as join9, dirname, basename as basename2 } from "path";
|
|
3869
4244
|
function completeFilePath(partial) {
|
|
3870
4245
|
const cwd = process.cwd();
|
|
3871
4246
|
const hasDir = partial.includes("/");
|
|
3872
|
-
const searchDir = hasDir ?
|
|
4247
|
+
const searchDir = hasDir ? join9(cwd, dirname(partial)) : cwd;
|
|
3873
4248
|
const prefix = hasDir ? basename2(partial) : partial;
|
|
3874
|
-
if (!
|
|
4249
|
+
if (!existsSync11(searchDir)) {
|
|
3875
4250
|
return [];
|
|
3876
4251
|
}
|
|
3877
4252
|
const entries = readdirSync6(searchDir);
|
|
@@ -3880,9 +4255,9 @@ function completeFilePath(partial) {
|
|
|
3880
4255
|
for (const entry of entries) {
|
|
3881
4256
|
if (!entry.startsWith(prefix)) continue;
|
|
3882
4257
|
if (entry.startsWith(".")) continue;
|
|
3883
|
-
const fullPath =
|
|
4258
|
+
const fullPath = join9(searchDir, entry);
|
|
3884
4259
|
try {
|
|
3885
|
-
const stat =
|
|
4260
|
+
const stat = statSync5(fullPath);
|
|
3886
4261
|
if (stat.isDirectory()) {
|
|
3887
4262
|
results.push(dirPrefix + entry + "/");
|
|
3888
4263
|
} else if (stat.isFile()) {
|
|
@@ -3969,15 +4344,15 @@ function generateCompletions(program2, args) {
|
|
|
3969
4344
|
|
|
3970
4345
|
// src/commands/completion.ts
|
|
3971
4346
|
function appendToFile(filePath, content) {
|
|
3972
|
-
if (
|
|
3973
|
-
const current =
|
|
4347
|
+
if (existsSync12(filePath)) {
|
|
4348
|
+
const current = readFileSync6(filePath, "utf-8");
|
|
3974
4349
|
if (current.includes("clawt completion")) {
|
|
3975
4350
|
printInfo(MESSAGES.COMPLETION_INSTALL_EXISTS + ": " + filePath);
|
|
3976
4351
|
return;
|
|
3977
4352
|
}
|
|
3978
4353
|
content = current + content;
|
|
3979
4354
|
}
|
|
3980
|
-
|
|
4355
|
+
writeFileSync5(filePath, content, "utf-8");
|
|
3981
4356
|
printSuccess(MESSAGES.COMPLETION_INSTALL_SUCCESS + ": " + filePath);
|
|
3982
4357
|
printInfo(MESSAGES.COMPLETION_INSTALL_RESTART(filePath));
|
|
3983
4358
|
}
|
|
@@ -4024,6 +4399,37 @@ function registerCompletionCommand(program2) {
|
|
|
4024
4399
|
});
|
|
4025
4400
|
}
|
|
4026
4401
|
|
|
4402
|
+
// src/commands/init.ts
|
|
4403
|
+
import { Command as Cmd } from "commander";
|
|
4404
|
+
function registerInitCommand(program2) {
|
|
4405
|
+
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) => {
|
|
4406
|
+
await handleInit(options);
|
|
4407
|
+
});
|
|
4408
|
+
initCmd.addCommand(
|
|
4409
|
+
new Cmd("show").description("\u5C55\u793A\u5F53\u524D\u9879\u76EE\u7684 init \u914D\u7F6E").action(() => {
|
|
4410
|
+
handleInitShow();
|
|
4411
|
+
})
|
|
4412
|
+
);
|
|
4413
|
+
}
|
|
4414
|
+
function handleInitShow() {
|
|
4415
|
+
validateMainWorktree();
|
|
4416
|
+
const config2 = requireProjectConfig();
|
|
4417
|
+
const configJson = safeStringify(config2);
|
|
4418
|
+
printInfo(MESSAGES.INIT_SHOW(configJson));
|
|
4419
|
+
}
|
|
4420
|
+
async function handleInit(options) {
|
|
4421
|
+
validateMainWorktree();
|
|
4422
|
+
const existingConfig = loadProjectConfig();
|
|
4423
|
+
const branchName = options.branch || getCurrentBranch();
|
|
4424
|
+
logger.info(`init \u547D\u4EE4\u6267\u884C\uFF0C\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`);
|
|
4425
|
+
saveProjectConfig({ clawtMainWorkBranch: branchName });
|
|
4426
|
+
if (existingConfig) {
|
|
4427
|
+
printSuccess(MESSAGES.INIT_UPDATED(existingConfig.clawtMainWorkBranch, branchName));
|
|
4428
|
+
} else {
|
|
4429
|
+
printSuccess(MESSAGES.INIT_SUCCESS(branchName));
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4027
4433
|
// src/index.ts
|
|
4028
4434
|
var require2 = createRequire(import.meta.url);
|
|
4029
4435
|
var { version } = require2("../package.json");
|
|
@@ -4049,6 +4455,7 @@ registerStatusCommand(program);
|
|
|
4049
4455
|
registerAliasCommand(program);
|
|
4050
4456
|
registerProjectsCommand(program);
|
|
4051
4457
|
registerCompletionCommand(program);
|
|
4458
|
+
registerInitCommand(program);
|
|
4052
4459
|
var config = loadConfig();
|
|
4053
4460
|
applyAliases(program, config.aliases);
|
|
4054
4461
|
process.on("uncaughtException", (error) => {
|