clawt 3.4.0 → 3.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -14
- package/dist/index.js +71 -5
- package/dist/postinstall.js +13 -2
- package/package.json +1 -1
- package/src/commands/create.ts +3 -0
- package/src/commands/home.ts +2 -0
- package/src/commands/merge.ts +3 -0
- package/src/commands/run.ts +2 -0
- package/src/commands/status.ts +22 -1
- package/src/commands/sync.ts +2 -0
- package/src/constants/config.ts +1 -1
- package/src/constants/interactive-panel.ts +2 -2
- package/src/constants/messages/common.ts +6 -0
- package/src/constants/messages/index.ts +2 -2
- package/src/constants/messages/interactive-panel.ts +27 -0
- package/src/constants/messages/status.ts +9 -0
- package/src/types/status.ts +4 -0
- package/src/utils/index.ts +1 -1
- package/src/utils/interactive-panel-render.ts +28 -2
- package/src/utils/project-config.ts +39 -2
- package/tests/unit/commands/cover-validate.test.ts +2 -0
- package/tests/unit/commands/create.test.ts +2 -0
- package/tests/unit/commands/init.test.ts +2 -0
- package/tests/unit/commands/merge.test.ts +2 -0
- package/tests/unit/commands/remove.test.ts +2 -0
- package/tests/unit/commands/reset.test.ts +2 -0
- package/tests/unit/commands/run.test.ts +2 -0
- package/tests/unit/commands/status.test.ts +6 -0
- package/tests/unit/commands/sync.test.ts +2 -0
- package/tests/unit/commands/validate.test.ts +2 -0
package/README.md
CHANGED
|
@@ -17,26 +17,36 @@ npm i -g clawt
|
|
|
17
17
|
## 快速开始
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
# 1. 在项目根目录(包含 .git
|
|
21
|
-
|
|
22
|
-
clawt run -b <branch-1>
|
|
23
|
-
clawt run -b <branch-2>
|
|
24
|
-
clawt run -b <branch-3>
|
|
20
|
+
# 1. 在项目根目录(包含 .git 的目录)下初始化,确认主工作分支
|
|
21
|
+
clawt init
|
|
25
22
|
|
|
26
|
-
#
|
|
27
|
-
clawt
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
clawt validate -b branch-1
|
|
31
|
-
|
|
32
|
-
# 5. 确认无误后合并到主分支
|
|
33
|
-
clawt merge -b branch-1 -m "feat: 实现xxx功能"
|
|
23
|
+
# 2. 并行执行任务,每个任务在独立的 worktree 中运行
|
|
24
|
+
clawt run -b feat-login
|
|
25
|
+
clawt run -b feat-search
|
|
26
|
+
clawt run -b fix-bug
|
|
34
27
|
|
|
28
|
+
# 3. 打开交互式面板,实时查看所有任务状态,一站式完成后续操作
|
|
29
|
+
clawt status -i
|
|
35
30
|
```
|
|
36
31
|
|
|
32
|
+
`clawt status -i` 提供实时刷新的 TUI 面板,用方向键选中 worktree 后可直接按快捷键操作:
|
|
33
|
+
|
|
34
|
+
| 快捷键 | 操作 | 等同命令 |
|
|
35
|
+
| ------ | ---- | -------- |
|
|
36
|
+
| `v` | 验证分支变更 | `clawt validate -b <branch>` |
|
|
37
|
+
| `m` | 合并到主分支 | `clawt merge -b <branch>` |
|
|
38
|
+
| `r` | 恢复 Claude Code 会话 | `clawt resume -b <branch>` |
|
|
39
|
+
| `s` | 同步主分支代码 | `clawt sync -b <branch>` |
|
|
40
|
+
| `d` | 删除 worktree | `clawt remove -b <branch>` |
|
|
41
|
+
| `q` | 退出面板 | — |
|
|
42
|
+
|
|
43
|
+
示例:
|
|
44
|
+

|
|
45
|
+
> 所有操作也可通过独立命令执行,详见下方「命令一览」。
|
|
46
|
+
|
|
37
47
|
## 命令一览
|
|
38
48
|
|
|
39
|
-
>
|
|
49
|
+
> 除 `config`、`alias`、`projects`、`completion` 外,其余命令需在**主 worktree 的仓库根目录**下执行。`-b` 参数支持模糊匹配。大部分操作命令(`run`、`create`、`validate`、`merge` 等)需要先执行 `clawt init`。
|
|
40
50
|
|
|
41
51
|
### `clawt init` — 初始化项目级配置
|
|
42
52
|
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,11 @@ var COMMON_MESSAGES = {
|
|
|
59
59
|
/** 分隔线 */
|
|
60
60
|
SEPARATOR: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
61
61
|
/** 粗分隔线 */
|
|
62
|
-
DOUBLE_SEPARATOR: "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"
|
|
62
|
+
DOUBLE_SEPARATOR: "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
63
|
+
/** 守卫检测:配置的主工作分支已不存在 */
|
|
64
|
+
GUARD_BRANCH_NOT_EXISTS: (branchName) => `\u914D\u7F6E\u7684\u4E3B\u5DE5\u4F5C\u5206\u652F ${branchName} \u5DF2\u4E0D\u5B58\u5728\uFF0C\u8BF7\u6267\u884C clawt init \u91CD\u65B0\u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F`,
|
|
65
|
+
/** 守卫检测:当前分支与配置的主工作分支不一致 */
|
|
66
|
+
GUARD_BRANCH_MISMATCH: (configuredBranch, currentBranch) => `\u5F53\u524D\u5206\u652F ${currentBranch} \u4E0E\u914D\u7F6E\u7684\u4E3B\u5DE5\u4F5C\u5206\u652F ${configuredBranch} \u4E0D\u4E00\u81F4\uFF0C\u5982\u9700\u66F4\u65B0\u8BF7\u6267\u884C clawt init`
|
|
63
67
|
};
|
|
64
68
|
|
|
65
69
|
// src/constants/messages/run.ts
|
|
@@ -371,7 +375,13 @@ var STATUS_MESSAGES = {
|
|
|
371
375
|
/** status 上次验证时间标签 */
|
|
372
376
|
STATUS_LAST_VALIDATED: (relativeTime) => `\u4E0A\u6B21\u9A8C\u8BC1: ${relativeTime}`,
|
|
373
377
|
/** status 未验证警示 */
|
|
374
|
-
STATUS_NOT_VALIDATED: "\u2717 \u672A\u9A8C\u8BC1"
|
|
378
|
+
STATUS_NOT_VALIDATED: "\u2717 \u672A\u9A8C\u8BC1",
|
|
379
|
+
/** status 配置的主工作分支(正常状态) */
|
|
380
|
+
STATUS_CONFIGURED_BRANCH: (branchName) => `\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`,
|
|
381
|
+
/** status 配置的主工作分支已不存在 */
|
|
382
|
+
STATUS_CONFIGURED_BRANCH_DELETED: (branchName) => `\u2717 \u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}\uFF08\u5DF2\u4E0D\u5B58\u5728\uFF0C\u8BF7\u6267\u884C clawt init \u91CD\u65B0\u8BBE\u7F6E\uFF09`,
|
|
383
|
+
/** status 当前分支与配置的主工作分支不一致 */
|
|
384
|
+
STATUS_CONFIGURED_BRANCH_MISMATCH: (branchName) => `\u26A0 \u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}\uFF08\u5F53\u524D\u5206\u652F\u4E0D\u4E00\u81F4\uFF0C\u5982\u9700\u66F4\u65B0\u8BF7\u6267\u884C clawt init\uFF09`
|
|
375
385
|
};
|
|
376
386
|
|
|
377
387
|
// src/constants/messages/alias.ts
|
|
@@ -529,7 +539,7 @@ var PANEL_SHORTCUT_KEYS = {
|
|
|
529
539
|
QUIT: "q"
|
|
530
540
|
};
|
|
531
541
|
var PANEL_DATE_SEPARATOR_PREFIX = "\u2550\u2550\u2550\u2550";
|
|
532
|
-
var PANEL_FIXED_ROWS =
|
|
542
|
+
var PANEL_FIXED_ROWS = 5;
|
|
533
543
|
|
|
534
544
|
// src/constants/messages/interactive-panel.ts
|
|
535
545
|
var SHORTCUT_LABELS = {
|
|
@@ -556,6 +566,10 @@ var PANEL_NO_WORKTREES = "(\u65E0\u6D3B\u8DC3 worktree)";
|
|
|
556
566
|
var PANEL_PRESS_ENTER_TO_RETURN = chalk.gray("\n\u6309 Enter \u8FD4\u56DE\u9762\u677F...");
|
|
557
567
|
var PANEL_NOT_TTY = "\u4EA4\u4E92\u5F0F\u9762\u677F\u9700\u8981 TTY \u7EC8\u7AEF\u73AF\u5883\uFF0C\u8BF7\u76F4\u63A5\u5728\u7EC8\u7AEF\u4E2D\u8FD0\u884C clawt status -i";
|
|
558
568
|
var PANEL_TITLE = (projectName) => chalk.bold.cyan(`\u9879\u76EE\u72B6\u6001\u603B\u89C8: ${projectName}`);
|
|
569
|
+
var PANEL_CONFIGURED_BRANCH = (branchName) => chalk.gray(`\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`);
|
|
570
|
+
var PANEL_CONFIGURED_BRANCH_DELETED = (branchName) => chalk.red(`\u2717 \u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}\uFF08\u5DF2\u4E0D\u5B58\u5728\uFF09`);
|
|
571
|
+
var PANEL_CONFIGURED_BRANCH_MISMATCH = (branchName) => chalk.yellow(`\u26A0 \u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}\uFF08\u4E0D\u4E00\u81F4\uFF09`);
|
|
572
|
+
var PANEL_NOT_INITIALIZED = chalk.gray("\u672A\u521D\u59CB\u5316\uFF08\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F\uFF09");
|
|
559
573
|
|
|
560
574
|
// src/constants/messages/index.ts
|
|
561
575
|
var MESSAGES = {
|
|
@@ -593,7 +607,7 @@ var VALID_TERMINAL_APPS = ["auto", "iterm2", "terminal"];
|
|
|
593
607
|
var ITERM2_APP_PATH = "/Applications/iTerm.app";
|
|
594
608
|
|
|
595
609
|
// src/constants/config.ts
|
|
596
|
-
var APPEND_SYSTEM_PROMPT = "
|
|
610
|
+
var APPEND_SYSTEM_PROMPT = "Currently, you are in the git worktree directory. There are no dependencies. To save disk space and improve the speed of task completion, installing dependencies is prohibited.";
|
|
597
611
|
var CONFIG_DEFINITIONS = {
|
|
598
612
|
autoDeleteBranch: {
|
|
599
613
|
defaultValue: false,
|
|
@@ -1325,6 +1339,26 @@ function getMainWorkBranch() {
|
|
|
1325
1339
|
const config2 = requireProjectConfig();
|
|
1326
1340
|
return config2.clawtMainWorkBranch;
|
|
1327
1341
|
}
|
|
1342
|
+
function guardMainWorkBranchExists(cwd) {
|
|
1343
|
+
const config2 = requireProjectConfig();
|
|
1344
|
+
const mainBranch = config2.clawtMainWorkBranch;
|
|
1345
|
+
if (!checkBranchExists(mainBranch, cwd)) {
|
|
1346
|
+
throw new ClawtError(MESSAGES.GUARD_BRANCH_NOT_EXISTS(mainBranch));
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
async function guardMainWorkBranch(cwd) {
|
|
1350
|
+
guardMainWorkBranchExists(cwd);
|
|
1351
|
+
const config2 = requireProjectConfig();
|
|
1352
|
+
const mainBranch = config2.clawtMainWorkBranch;
|
|
1353
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
1354
|
+
if (currentBranch !== mainBranch && !currentBranch.startsWith(VALIDATE_BRANCH_PREFIX)) {
|
|
1355
|
+
printWarning(MESSAGES.GUARD_BRANCH_MISMATCH(mainBranch, currentBranch));
|
|
1356
|
+
const confirmed = await confirmAction("\u662F\u5426\u7EE7\u7EED\u6267\u884C\uFF1F");
|
|
1357
|
+
if (!confirmed) {
|
|
1358
|
+
throw new ClawtError(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1328
1362
|
function getValidateRunCommand() {
|
|
1329
1363
|
const config2 = loadProjectConfig();
|
|
1330
1364
|
return config2?.validateRunCommand || void 0;
|
|
@@ -3178,6 +3212,7 @@ function buildSeparatorWithHint(cols, hint) {
|
|
|
3178
3212
|
function buildPanelFrame(statusResult, selectedIndex, scrollOffset, rows, cols, countdown) {
|
|
3179
3213
|
const lines = [];
|
|
3180
3214
|
lines.push(PANEL_TITLE(statusResult.main.projectName));
|
|
3215
|
+
lines.push(renderConfiguredBranchLine(statusResult.main));
|
|
3181
3216
|
lines.push(renderSnapshotSummary(statusResult.snapshots.total, statusResult.snapshots.orphaned));
|
|
3182
3217
|
const visibleRows = calculateVisibleRows(rows);
|
|
3183
3218
|
if (statusResult.worktrees.length === 0) {
|
|
@@ -3301,6 +3336,18 @@ function formatChangeStatusLabel(status) {
|
|
|
3301
3336
|
return chalk9.gray(MESSAGES.STATUS_CHANGE_CLEAN);
|
|
3302
3337
|
}
|
|
3303
3338
|
}
|
|
3339
|
+
function renderConfiguredBranchLine(main2) {
|
|
3340
|
+
if (main2.configuredMainBranch === null) {
|
|
3341
|
+
return PANEL_NOT_INITIALIZED;
|
|
3342
|
+
}
|
|
3343
|
+
if (main2.configuredBranchExists === false) {
|
|
3344
|
+
return PANEL_CONFIGURED_BRANCH_DELETED(main2.configuredMainBranch);
|
|
3345
|
+
}
|
|
3346
|
+
if (main2.branch !== main2.configuredMainBranch && !main2.branch.startsWith(VALIDATE_BRANCH_PREFIX)) {
|
|
3347
|
+
return PANEL_CONFIGURED_BRANCH_MISMATCH(main2.configuredMainBranch);
|
|
3348
|
+
}
|
|
3349
|
+
return PANEL_CONFIGURED_BRANCH(main2.configuredMainBranch);
|
|
3350
|
+
}
|
|
3304
3351
|
function renderSnapshotSummary(total, orphaned) {
|
|
3305
3352
|
return PANEL_SNAPSHOT_SUMMARY(total, orphaned);
|
|
3306
3353
|
}
|
|
@@ -3799,6 +3846,7 @@ function registerCreateCommand(program2) {
|
|
|
3799
3846
|
}
|
|
3800
3847
|
async function handleCreate(options) {
|
|
3801
3848
|
validateMainWorktree();
|
|
3849
|
+
await guardMainWorkBranch();
|
|
3802
3850
|
await ensureOnMainWorkBranch();
|
|
3803
3851
|
const count = Number(options.number);
|
|
3804
3852
|
if (!Number.isInteger(count) || count <= 0) {
|
|
@@ -3942,6 +3990,7 @@ function handleDryRunFromFile(options) {
|
|
|
3942
3990
|
async function handleRun(options) {
|
|
3943
3991
|
validateMainWorktree();
|
|
3944
3992
|
if (!options.dryRun) {
|
|
3993
|
+
await guardMainWorkBranch();
|
|
3945
3994
|
await ensureOnMainWorkBranch();
|
|
3946
3995
|
}
|
|
3947
3996
|
if (options.file && options.tasks) {
|
|
@@ -4107,6 +4156,7 @@ async function executeSyncForBranch(targetWorktreePath, branch) {
|
|
|
4107
4156
|
async function handleSync(options) {
|
|
4108
4157
|
validateMainWorktree();
|
|
4109
4158
|
requireProjectConfig();
|
|
4159
|
+
await guardMainWorkBranch();
|
|
4110
4160
|
logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
|
|
4111
4161
|
const worktrees = getProjectWorktrees();
|
|
4112
4162
|
const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
|
|
@@ -4372,6 +4422,7 @@ function cleanupWorktreeAndBranch(worktreePath, branchName) {
|
|
|
4372
4422
|
}
|
|
4373
4423
|
async function handleMerge(options) {
|
|
4374
4424
|
validateMainWorktree();
|
|
4425
|
+
await guardMainWorkBranch();
|
|
4375
4426
|
const mainWorktreePath = getGitTopLevel();
|
|
4376
4427
|
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
4377
4428
|
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)"}`);
|
|
@@ -4581,10 +4632,15 @@ function collectStatus() {
|
|
|
4581
4632
|
const projectName = getProjectName();
|
|
4582
4633
|
const currentBranch = getCurrentBranch();
|
|
4583
4634
|
const isClean = isWorkingDirClean();
|
|
4635
|
+
const projectConfig = loadProjectConfig();
|
|
4636
|
+
const configuredMainBranch = projectConfig?.clawtMainWorkBranch || null;
|
|
4637
|
+
const configuredBranchExists = configuredMainBranch ? checkBranchExists(configuredMainBranch) : null;
|
|
4584
4638
|
const main2 = {
|
|
4585
4639
|
branch: currentBranch,
|
|
4586
4640
|
isClean,
|
|
4587
|
-
projectName
|
|
4641
|
+
projectName,
|
|
4642
|
+
configuredMainBranch,
|
|
4643
|
+
configuredBranchExists
|
|
4588
4644
|
};
|
|
4589
4645
|
const worktrees = getProjectWorktrees();
|
|
4590
4646
|
const worktreeStatuses = worktrees.map((wt) => collectWorktreeDetailedStatus(wt, projectName));
|
|
@@ -4694,6 +4750,15 @@ function printMainSection(main2) {
|
|
|
4694
4750
|
} else {
|
|
4695
4751
|
printInfo(` \u72B6\u6001: ${chalk11.yellow("\u2717 \u6709\u672A\u63D0\u4EA4\u4FEE\u6539")}`);
|
|
4696
4752
|
}
|
|
4753
|
+
if (main2.configuredMainBranch !== null) {
|
|
4754
|
+
if (main2.configuredBranchExists === false) {
|
|
4755
|
+
printInfo(` ${chalk11.red(MESSAGES.STATUS_CONFIGURED_BRANCH_DELETED(main2.configuredMainBranch))}`);
|
|
4756
|
+
} else if (main2.branch !== main2.configuredMainBranch && !main2.branch.startsWith(VALIDATE_BRANCH_PREFIX)) {
|
|
4757
|
+
printInfo(` ${chalk11.yellow(MESSAGES.STATUS_CONFIGURED_BRANCH_MISMATCH(main2.configuredMainBranch))}`);
|
|
4758
|
+
} else {
|
|
4759
|
+
printInfo(` ${chalk11.gray(MESSAGES.STATUS_CONFIGURED_BRANCH(main2.configuredMainBranch))}`);
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4697
4762
|
printInfo("");
|
|
4698
4763
|
}
|
|
4699
4764
|
function printWorktreesSection(worktrees, total) {
|
|
@@ -5263,6 +5328,7 @@ function registerHomeCommand(program2) {
|
|
|
5263
5328
|
async function handleHome() {
|
|
5264
5329
|
validateMainWorktree();
|
|
5265
5330
|
requireProjectConfig();
|
|
5331
|
+
guardMainWorkBranchExists();
|
|
5266
5332
|
const mainBranch = getMainWorkBranch();
|
|
5267
5333
|
const currentBranch = getCurrentBranch();
|
|
5268
5334
|
if (currentBranch === mainBranch) {
|
package/dist/postinstall.js
CHANGED
|
@@ -50,7 +50,11 @@ var COMMON_MESSAGES = {
|
|
|
50
50
|
/** 分隔线 */
|
|
51
51
|
SEPARATOR: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
52
52
|
/** 粗分隔线 */
|
|
53
|
-
DOUBLE_SEPARATOR: "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"
|
|
53
|
+
DOUBLE_SEPARATOR: "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
54
|
+
/** 守卫检测:配置的主工作分支已不存在 */
|
|
55
|
+
GUARD_BRANCH_NOT_EXISTS: (branchName) => `\u914D\u7F6E\u7684\u4E3B\u5DE5\u4F5C\u5206\u652F ${branchName} \u5DF2\u4E0D\u5B58\u5728\uFF0C\u8BF7\u6267\u884C clawt init \u91CD\u65B0\u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F`,
|
|
56
|
+
/** 守卫检测:当前分支与配置的主工作分支不一致 */
|
|
57
|
+
GUARD_BRANCH_MISMATCH: (configuredBranch, currentBranch) => `\u5F53\u524D\u5206\u652F ${currentBranch} \u4E0E\u914D\u7F6E\u7684\u4E3B\u5DE5\u4F5C\u5206\u652F ${configuredBranch} \u4E0D\u4E00\u81F4\uFF0C\u5982\u9700\u66F4\u65B0\u8BF7\u6267\u884C clawt init`
|
|
54
58
|
};
|
|
55
59
|
|
|
56
60
|
// src/constants/messages/run.ts
|
|
@@ -361,7 +365,13 @@ var STATUS_MESSAGES = {
|
|
|
361
365
|
/** status 上次验证时间标签 */
|
|
362
366
|
STATUS_LAST_VALIDATED: (relativeTime) => `\u4E0A\u6B21\u9A8C\u8BC1: ${relativeTime}`,
|
|
363
367
|
/** status 未验证警示 */
|
|
364
|
-
STATUS_NOT_VALIDATED: "\u2717 \u672A\u9A8C\u8BC1"
|
|
368
|
+
STATUS_NOT_VALIDATED: "\u2717 \u672A\u9A8C\u8BC1",
|
|
369
|
+
/** status 配置的主工作分支(正常状态) */
|
|
370
|
+
STATUS_CONFIGURED_BRANCH: (branchName) => `\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`,
|
|
371
|
+
/** status 配置的主工作分支已不存在 */
|
|
372
|
+
STATUS_CONFIGURED_BRANCH_DELETED: (branchName) => `\u2717 \u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}\uFF08\u5DF2\u4E0D\u5B58\u5728\uFF0C\u8BF7\u6267\u884C clawt init \u91CD\u65B0\u8BBE\u7F6E\uFF09`,
|
|
373
|
+
/** status 当前分支与配置的主工作分支不一致 */
|
|
374
|
+
STATUS_CONFIGURED_BRANCH_MISMATCH: (branchName) => `\u26A0 \u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}\uFF08\u5F53\u524D\u5206\u652F\u4E0D\u4E00\u81F4\uFF0C\u5982\u9700\u66F4\u65B0\u8BF7\u6267\u884C clawt init\uFF09`
|
|
365
375
|
};
|
|
366
376
|
|
|
367
377
|
// src/constants/messages/alias.ts
|
|
@@ -513,6 +523,7 @@ var PANEL_FOOTER_SHORTCUTS = Object.entries(SHORTCUT_LABELS).map(([key, label])
|
|
|
513
523
|
var PANEL_OVERFLOW_DOWN_HINT = chalk.gray("\u2193 \u66F4\u591A worktree...");
|
|
514
524
|
var PANEL_OVERFLOW_UP_HINT = chalk.gray("\u2191 \u66F4\u591A worktree...");
|
|
515
525
|
var PANEL_PRESS_ENTER_TO_RETURN = chalk.gray("\n\u6309 Enter \u8FD4\u56DE\u9762\u677F...");
|
|
526
|
+
var PANEL_NOT_INITIALIZED = chalk.gray("\u672A\u521D\u59CB\u5316\uFF08\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F\uFF09");
|
|
516
527
|
|
|
517
528
|
// src/constants/messages/index.ts
|
|
518
529
|
var MESSAGES = {
|
package/package.json
CHANGED
package/src/commands/create.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
printSuccess,
|
|
12
12
|
printInfo,
|
|
13
13
|
printSeparator,
|
|
14
|
+
guardMainWorkBranch,
|
|
14
15
|
} from '../utils/index.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -35,6 +36,8 @@ export function registerCreateCommand(program: Command): void {
|
|
|
35
36
|
async function handleCreate(options: CreateOptions): Promise<void> {
|
|
36
37
|
validateMainWorktree();
|
|
37
38
|
|
|
39
|
+
await guardMainWorkBranch();
|
|
40
|
+
|
|
38
41
|
await ensureOnMainWorkBranch();
|
|
39
42
|
|
|
40
43
|
const count = Number(options.number);
|
package/src/commands/home.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getMainWorkBranch,
|
|
9
9
|
printSuccess,
|
|
10
10
|
printInfo,
|
|
11
|
+
guardMainWorkBranchExists,
|
|
11
12
|
} from '../utils/index.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -29,6 +30,7 @@ export function registerHomeCommand(program: Command): void {
|
|
|
29
30
|
async function handleHome(): Promise<void> {
|
|
30
31
|
validateMainWorktree();
|
|
31
32
|
requireProjectConfig();
|
|
33
|
+
guardMainWorkBranchExists();
|
|
32
34
|
|
|
33
35
|
const mainBranch = getMainWorkBranch();
|
|
34
36
|
const currentBranch = getCurrentBranch();
|
package/src/commands/merge.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
resolveTargetWorktree,
|
|
34
34
|
getMainWorkBranch,
|
|
35
35
|
ensureOnMainWorkBranch,
|
|
36
|
+
guardMainWorkBranch,
|
|
36
37
|
} from '../utils/index.js';
|
|
37
38
|
import type { WorktreeResolveMessages } from '../utils/index.js';
|
|
38
39
|
|
|
@@ -137,6 +138,8 @@ function cleanupWorktreeAndBranch(worktreePath: string, branchName: string): voi
|
|
|
137
138
|
async function handleMerge(options: MergeOptions): Promise<void> {
|
|
138
139
|
validateMainWorktree();
|
|
139
140
|
|
|
141
|
+
await guardMainWorkBranch();
|
|
142
|
+
|
|
140
143
|
const mainWorktreePath = getGitTopLevel();
|
|
141
144
|
|
|
142
145
|
// 确保当前在主工作分支上
|
package/src/commands/run.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
executeBatchTasks,
|
|
21
21
|
printDryRunPreview,
|
|
22
22
|
ensureOnMainWorkBranch,
|
|
23
|
+
guardMainWorkBranch,
|
|
23
24
|
} from '../utils/index.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -123,6 +124,7 @@ async function handleRun(options: RunOptions): Promise<void> {
|
|
|
123
124
|
|
|
124
125
|
// dry-run 模式跳过项目配置前置校验
|
|
125
126
|
if (!options.dryRun) {
|
|
127
|
+
await guardMainWorkBranch();
|
|
126
128
|
await ensureOnMainWorkBranch();
|
|
127
129
|
}
|
|
128
130
|
|
package/src/commands/status.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import { MESSAGES } from '../constants/index.js';
|
|
3
|
+
import { MESSAGES, VALIDATE_BRANCH_PREFIX } from '../constants/index.js';
|
|
4
4
|
import { logger } from '../logger/index.js';
|
|
5
5
|
import type { StatusOptions, WorktreeDetailedStatus, MainWorktreeStatus, SnapshotSummary, StatusResult, WorktreeInfo } from '../types/index.js';
|
|
6
6
|
import {
|
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
printDoubleSeparator,
|
|
23
23
|
printSeparator,
|
|
24
24
|
InteractivePanel,
|
|
25
|
+
loadProjectConfig,
|
|
26
|
+
checkBranchExists,
|
|
25
27
|
} from '../utils/index.js';
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -74,11 +76,18 @@ export function collectStatus(): StatusResult {
|
|
|
74
76
|
const currentBranch = getCurrentBranch();
|
|
75
77
|
const isClean = isWorkingDirClean();
|
|
76
78
|
|
|
79
|
+
// 配置分支信息收集
|
|
80
|
+
const projectConfig = loadProjectConfig();
|
|
81
|
+
const configuredMainBranch = projectConfig?.clawtMainWorkBranch || null;
|
|
82
|
+
const configuredBranchExists = configuredMainBranch ? checkBranchExists(configuredMainBranch) : null;
|
|
83
|
+
|
|
77
84
|
// 主 worktree 状态
|
|
78
85
|
const main: MainWorktreeStatus = {
|
|
79
86
|
branch: currentBranch,
|
|
80
87
|
isClean,
|
|
81
88
|
projectName,
|
|
89
|
+
configuredMainBranch,
|
|
90
|
+
configuredBranchExists,
|
|
82
91
|
};
|
|
83
92
|
|
|
84
93
|
// 各 worktree 详细状态
|
|
@@ -265,6 +274,18 @@ function printMainSection(main: MainWorktreeStatus): void {
|
|
|
265
274
|
} else {
|
|
266
275
|
printInfo(` 状态: ${chalk.yellow('✗ 有未提交修改')}`);
|
|
267
276
|
}
|
|
277
|
+
|
|
278
|
+
// 配置分支信息展示
|
|
279
|
+
if (main.configuredMainBranch !== null) {
|
|
280
|
+
if (main.configuredBranchExists === false) {
|
|
281
|
+
printInfo(` ${chalk.red(MESSAGES.STATUS_CONFIGURED_BRANCH_DELETED(main.configuredMainBranch))}`);
|
|
282
|
+
} else if (main.branch !== main.configuredMainBranch && !main.branch.startsWith(VALIDATE_BRANCH_PREFIX)) {
|
|
283
|
+
printInfo(` ${chalk.yellow(MESSAGES.STATUS_CONFIGURED_BRANCH_MISMATCH(main.configuredMainBranch))}`);
|
|
284
|
+
} else {
|
|
285
|
+
printInfo(` ${chalk.gray(MESSAGES.STATUS_CONFIGURED_BRANCH(main.configuredMainBranch))}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
268
289
|
printInfo('');
|
|
269
290
|
}
|
|
270
291
|
|
package/src/commands/sync.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
getMainWorkBranch,
|
|
21
21
|
rebuildValidateBranch,
|
|
22
22
|
getValidateBranchName,
|
|
23
|
+
guardMainWorkBranch,
|
|
23
24
|
} from '../utils/index.js';
|
|
24
25
|
import type { WorktreeResolveMessages } from '../utils/index.js';
|
|
25
26
|
|
|
@@ -129,6 +130,7 @@ export async function executeSyncForBranch(targetWorktreePath: string, branch: s
|
|
|
129
130
|
async function handleSync(options: SyncOptions): Promise<void> {
|
|
130
131
|
validateMainWorktree();
|
|
131
132
|
requireProjectConfig();
|
|
133
|
+
await guardMainWorkBranch();
|
|
132
134
|
|
|
133
135
|
logger.info(`sync 命令执行,分支: ${options.branch ?? '(未指定)'}`);
|
|
134
136
|
|
package/src/constants/config.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { VALID_TERMINAL_APPS } from './terminal.js';
|
|
|
3
3
|
|
|
4
4
|
/** Claude Code 系统约束提示 */
|
|
5
5
|
export const APPEND_SYSTEM_PROMPT =
|
|
6
|
-
'
|
|
6
|
+
'Currently, you are in the git worktree directory. There are no dependencies. To save disk space and improve the speed of task completion, installing dependencies is prohibited.';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* 配置项完整定义(单一数据源)
|
|
@@ -40,5 +40,5 @@ export const PANEL_SHORTCUT_KEYS = {
|
|
|
40
40
|
/** 日期分隔线前缀 */
|
|
41
41
|
export const PANEL_DATE_SEPARATOR_PREFIX = '════';
|
|
42
42
|
|
|
43
|
-
/**
|
|
44
|
-
export const PANEL_FIXED_ROWS =
|
|
43
|
+
/** 固定占用行数(配置分支信息 + 快照摘要 + 顶部分隔线 + 底部分隔线 + 底栏) */
|
|
44
|
+
export const PANEL_FIXED_ROWS = 5;
|
|
@@ -38,4 +38,10 @@ export const COMMON_MESSAGES = {
|
|
|
38
38
|
SEPARATOR: '────────────────────────────────────────',
|
|
39
39
|
/** 粗分隔线 */
|
|
40
40
|
DOUBLE_SEPARATOR: '════════════════════════════════════════',
|
|
41
|
+
/** 守卫检测:配置的主工作分支已不存在 */
|
|
42
|
+
GUARD_BRANCH_NOT_EXISTS: (branchName: string) =>
|
|
43
|
+
`配置的主工作分支 ${branchName} 已不存在,请执行 clawt init 重新设置主工作分支`,
|
|
44
|
+
/** 守卫检测:当前分支与配置的主工作分支不一致 */
|
|
45
|
+
GUARD_BRANCH_MISMATCH: (configuredBranch: string, currentBranch: string) =>
|
|
46
|
+
`当前分支 ${currentBranch} 与配置的主工作分支 ${configuredBranch} 不一致,如需更新请执行 clawt init`,
|
|
41
47
|
} as const;
|
|
@@ -16,11 +16,11 @@ import { UPDATE_MESSAGES, UPDATE_COMMANDS } from './update.js';
|
|
|
16
16
|
import { INIT_MESSAGES } from './init.js';
|
|
17
17
|
import { COVER_VALIDATE_MESSAGES } from './cover-validate.js';
|
|
18
18
|
import { HOME_MESSAGES } from './home.js';
|
|
19
|
-
import { PANEL_FOOTER_SHORTCUTS, PANEL_FOOTER_COUNTDOWN, PANEL_OVERFLOW_DOWN_HINT, PANEL_OVERFLOW_UP_HINT, PANEL_SNAPSHOT_SUMMARY, PANEL_NO_WORKTREES as PANEL_NO_WORKTREES_MSG, PANEL_PRESS_ENTER_TO_RETURN, PANEL_NOT_TTY, PANEL_TITLE } from './interactive-panel.js';
|
|
19
|
+
import { PANEL_FOOTER_SHORTCUTS, PANEL_FOOTER_COUNTDOWN, PANEL_OVERFLOW_DOWN_HINT, PANEL_OVERFLOW_UP_HINT, PANEL_SNAPSHOT_SUMMARY, PANEL_NO_WORKTREES as PANEL_NO_WORKTREES_MSG, PANEL_PRESS_ENTER_TO_RETURN, PANEL_NOT_TTY, PANEL_TITLE, PANEL_CONFIGURED_BRANCH, PANEL_CONFIGURED_BRANCH_DELETED, PANEL_CONFIGURED_BRANCH_MISMATCH, PANEL_NOT_INITIALIZED } from './interactive-panel.js';
|
|
20
20
|
|
|
21
21
|
export { CONFIG_ALIAS_DISABLED_HINT };
|
|
22
22
|
export { UPDATE_MESSAGES, UPDATE_COMMANDS };
|
|
23
|
-
export { PANEL_FOOTER_SHORTCUTS, PANEL_FOOTER_COUNTDOWN, PANEL_OVERFLOW_DOWN_HINT, PANEL_OVERFLOW_UP_HINT, PANEL_SNAPSHOT_SUMMARY, PANEL_NO_WORKTREES_MSG, PANEL_PRESS_ENTER_TO_RETURN, PANEL_NOT_TTY, PANEL_TITLE };
|
|
23
|
+
export { PANEL_FOOTER_SHORTCUTS, PANEL_FOOTER_COUNTDOWN, PANEL_OVERFLOW_DOWN_HINT, PANEL_OVERFLOW_UP_HINT, PANEL_SNAPSHOT_SUMMARY, PANEL_NO_WORKTREES_MSG, PANEL_PRESS_ENTER_TO_RETURN, PANEL_NOT_TTY, PANEL_TITLE, PANEL_CONFIGURED_BRANCH, PANEL_CONFIGURED_BRANCH_DELETED, PANEL_CONFIGURED_BRANCH_MISMATCH, PANEL_NOT_INITIALIZED };
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* 提示消息模板
|
|
@@ -59,3 +59,30 @@ export const PANEL_NOT_TTY = '交互式面板需要 TTY 终端环境,请直接
|
|
|
59
59
|
* @returns {string} 格式化的标题
|
|
60
60
|
*/
|
|
61
61
|
export const PANEL_TITLE = (projectName: string): string => chalk.bold.cyan(`项目状态总览: ${projectName}`);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 面板配置分支信息(正常)
|
|
65
|
+
* @param {string} branchName - 分支名
|
|
66
|
+
* @returns {string} 格式化的分支信息
|
|
67
|
+
*/
|
|
68
|
+
export const PANEL_CONFIGURED_BRANCH = (branchName: string): string =>
|
|
69
|
+
chalk.gray(`主工作分支: ${branchName}`);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 面板配置分支信息(分支已删除)
|
|
73
|
+
* @param {string} branchName - 分支名
|
|
74
|
+
* @returns {string} 格式化的分支信息
|
|
75
|
+
*/
|
|
76
|
+
export const PANEL_CONFIGURED_BRANCH_DELETED = (branchName: string): string =>
|
|
77
|
+
chalk.red(`✗ 主工作分支: ${branchName}(已不存在)`);
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 面板配置分支信息(分支不一致)
|
|
81
|
+
* @param {string} branchName - 分支名
|
|
82
|
+
* @returns {string} 格式化的分支信息
|
|
83
|
+
*/
|
|
84
|
+
export const PANEL_CONFIGURED_BRANCH_MISMATCH = (branchName: string): string =>
|
|
85
|
+
chalk.yellow(`⚠ 主工作分支: ${branchName}(不一致)`);
|
|
86
|
+
|
|
87
|
+
/** 面板配置分支信息(未初始化) */
|
|
88
|
+
export const PANEL_NOT_INITIALIZED = chalk.gray('未初始化(执行 clawt init 设置主工作分支)');
|
|
@@ -30,4 +30,13 @@ export const STATUS_MESSAGES = {
|
|
|
30
30
|
STATUS_LAST_VALIDATED: (relativeTime: string) => `上次验证: ${relativeTime}`,
|
|
31
31
|
/** status 未验证警示 */
|
|
32
32
|
STATUS_NOT_VALIDATED: '✗ 未验证',
|
|
33
|
+
/** status 配置的主工作分支(正常状态) */
|
|
34
|
+
STATUS_CONFIGURED_BRANCH: (branchName: string) =>
|
|
35
|
+
`主工作分支: ${branchName}`,
|
|
36
|
+
/** status 配置的主工作分支已不存在 */
|
|
37
|
+
STATUS_CONFIGURED_BRANCH_DELETED: (branchName: string) =>
|
|
38
|
+
`✗ 主工作分支: ${branchName}(已不存在,请执行 clawt init 重新设置)`,
|
|
39
|
+
/** status 当前分支与配置的主工作分支不一致 */
|
|
40
|
+
STATUS_CONFIGURED_BRANCH_MISMATCH: (branchName: string) =>
|
|
41
|
+
`⚠ 主工作分支: ${branchName}(当前分支不一致,如需更新请执行 clawt init)`,
|
|
33
42
|
} as const;
|
package/src/types/status.ts
CHANGED
|
@@ -28,6 +28,10 @@ export interface MainWorktreeStatus {
|
|
|
28
28
|
isClean: boolean;
|
|
29
29
|
/** 项目名 */
|
|
30
30
|
projectName: string;
|
|
31
|
+
/** 配置的主工作分支名(项目未初始化时为 null) */
|
|
32
|
+
configuredMainBranch: string | null;
|
|
33
|
+
/** 配置的主工作分支是否存在(项目未初始化时为 null) */
|
|
34
|
+
configuredBranchExists: boolean | null;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
/** validate 快照信息 */
|
package/src/utils/index.ts
CHANGED
|
@@ -70,7 +70,7 @@ export { truncateTaskDesc, printDryRunPreview } from './dry-run.js';
|
|
|
70
70
|
export { applyAliases } from './alias.js';
|
|
71
71
|
export { isValidConfigKey, getValidConfigKeys, parseConfigValue, promptConfigValue, formatConfigValue, interactiveConfigEditor } from './config-strategy.js';
|
|
72
72
|
export { checkForUpdates } from './update-checker.js';
|
|
73
|
-
export { getProjectConfigPath, loadProjectConfig, saveProjectConfig, requireProjectConfig, getMainWorkBranch, getValidateRunCommand } from './project-config.js';
|
|
73
|
+
export { getProjectConfigPath, loadProjectConfig, saveProjectConfig, requireProjectConfig, getMainWorkBranch, guardMainWorkBranchExists, guardMainWorkBranch, getValidateRunCommand } from './project-config.js';
|
|
74
74
|
export { getValidateBranchName, createValidateBranch, deleteValidateBranch, rebuildValidateBranch, ensureOnMainWorkBranch, handleDirtyWorkingDir } from './validate-branch.js';
|
|
75
75
|
export { safeStringify } from './json.js';
|
|
76
76
|
export { executeRunCommand } from './validate-runner.js';
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
PANEL_DATE_SEPARATOR_PREFIX,
|
|
8
8
|
PANEL_FIXED_ROWS,
|
|
9
9
|
UNKNOWN_DATE_GROUP,
|
|
10
|
+
VALIDATE_BRANCH_PREFIX,
|
|
10
11
|
} from '../constants/index.js';
|
|
11
12
|
import {
|
|
12
13
|
PANEL_FOOTER_SHORTCUTS,
|
|
@@ -16,8 +17,12 @@ import {
|
|
|
16
17
|
PANEL_SNAPSHOT_SUMMARY,
|
|
17
18
|
PANEL_NO_WORKTREES_MSG,
|
|
18
19
|
PANEL_TITLE,
|
|
20
|
+
PANEL_CONFIGURED_BRANCH,
|
|
21
|
+
PANEL_CONFIGURED_BRANCH_DELETED,
|
|
22
|
+
PANEL_CONFIGURED_BRANCH_MISMATCH,
|
|
23
|
+
PANEL_NOT_INITIALIZED,
|
|
19
24
|
} from '../constants/messages/index.js';
|
|
20
|
-
import type { StatusResult, WorktreeDetailedStatus } from '../types/index.js';
|
|
25
|
+
import type { StatusResult, WorktreeDetailedStatus, MainWorktreeStatus } from '../types/index.js';
|
|
21
26
|
import { formatRelativeTime, groupWorktreesByDate, formatRelativeDate } from './index.js';
|
|
22
27
|
|
|
23
28
|
/** 面板行类型 */
|
|
@@ -73,6 +78,9 @@ export function buildPanelFrame(
|
|
|
73
78
|
// 标题行
|
|
74
79
|
lines.push(PANEL_TITLE(statusResult.main.projectName));
|
|
75
80
|
|
|
81
|
+
// 配置分支信息行
|
|
82
|
+
lines.push(renderConfiguredBranchLine(statusResult.main));
|
|
83
|
+
|
|
76
84
|
// 快照摘要行
|
|
77
85
|
lines.push(renderSnapshotSummary(statusResult.snapshots.total, statusResult.snapshots.orphaned));
|
|
78
86
|
|
|
@@ -283,6 +291,24 @@ function formatChangeStatusLabel(status: WorktreeDetailedStatus['changeStatus'])
|
|
|
283
291
|
}
|
|
284
292
|
}
|
|
285
293
|
|
|
294
|
+
/**
|
|
295
|
+
* 渲染配置的主工作分支信息行(面板模式)
|
|
296
|
+
* @param {MainWorktreeStatus} main - 主 worktree 状态
|
|
297
|
+
* @returns {string} 格式化的配置分支信息
|
|
298
|
+
*/
|
|
299
|
+
function renderConfiguredBranchLine(main: MainWorktreeStatus): string {
|
|
300
|
+
if (main.configuredMainBranch === null) {
|
|
301
|
+
return PANEL_NOT_INITIALIZED;
|
|
302
|
+
}
|
|
303
|
+
if (main.configuredBranchExists === false) {
|
|
304
|
+
return PANEL_CONFIGURED_BRANCH_DELETED(main.configuredMainBranch);
|
|
305
|
+
}
|
|
306
|
+
if (main.branch !== main.configuredMainBranch && !main.branch.startsWith(VALIDATE_BRANCH_PREFIX)) {
|
|
307
|
+
return PANEL_CONFIGURED_BRANCH_MISMATCH(main.configuredMainBranch);
|
|
308
|
+
}
|
|
309
|
+
return PANEL_CONFIGURED_BRANCH(main.configuredMainBranch);
|
|
310
|
+
}
|
|
311
|
+
|
|
286
312
|
/**
|
|
287
313
|
* 渲染快照摘要行
|
|
288
314
|
* @param {number} total - 快照总数
|
|
@@ -309,7 +335,7 @@ export function renderFooter(countdown: number): string {
|
|
|
309
335
|
* @returns {number} 滚动区域总行数
|
|
310
336
|
*/
|
|
311
337
|
export function calculateVisibleRows(terminalRows: number): number {
|
|
312
|
-
// 固定行:标题(1) + 快照摘要 + 顶部分隔线 + 底部分隔线 +
|
|
338
|
+
// 固定行:标题(1) + 配置分支信息 + 快照摘要 + 顶部分隔线 + 底部分隔线 + 底栏(后五项 = PANEL_FIXED_ROWS)
|
|
313
339
|
const fixedRows = PANEL_FIXED_ROWS + 1;
|
|
314
340
|
return Math.max(terminalRows - fixedRows, 3);
|
|
315
341
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { PROJECTS_CONFIG_DIR, MESSAGES } from '../constants/index.js';
|
|
3
|
+
import { PROJECTS_CONFIG_DIR, MESSAGES, VALIDATE_BRANCH_PREFIX } from '../constants/index.js';
|
|
4
4
|
import { ClawtError } from '../errors/index.js';
|
|
5
5
|
import { logger } from '../logger/index.js';
|
|
6
|
-
import { getProjectName } from './git.js';
|
|
6
|
+
import { getProjectName, checkBranchExists, getCurrentBranch } from './git.js';
|
|
7
7
|
import { ensureDir } from './fs.js';
|
|
8
|
+
import { printWarning, confirmAction } from './formatter.js';
|
|
8
9
|
import type { ProjectConfig } from '../types/index.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -76,6 +77,42 @@ export function getMainWorkBranch(): string {
|
|
|
76
77
|
return config.clawtMainWorkBranch;
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
/**
|
|
81
|
+
* 守卫检测:仅验证配置中的主工作分支是否存在
|
|
82
|
+
* 分支不存在 → 抛出 ClawtError(致命错误)
|
|
83
|
+
* 适用于不需要分支一致性检测的场景(如 home 命令)
|
|
84
|
+
* @param {string} [cwd] - 工作目录
|
|
85
|
+
*/
|
|
86
|
+
export function guardMainWorkBranchExists(cwd?: string): void {
|
|
87
|
+
const config = requireProjectConfig();
|
|
88
|
+
const mainBranch = config.clawtMainWorkBranch;
|
|
89
|
+
|
|
90
|
+
if (!checkBranchExists(mainBranch, cwd)) {
|
|
91
|
+
throw new ClawtError(MESSAGES.GUARD_BRANCH_NOT_EXISTS(mainBranch));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 守卫检测:验证配置中的主工作分支是否有效
|
|
97
|
+
* 分支不存在 → 抛出 ClawtError(致命错误)
|
|
98
|
+
* 当前分支与配置分支不一致且非验证分支 → 警告并交互确认是否继续
|
|
99
|
+
* @param {string} [cwd] - 工作目录
|
|
100
|
+
*/
|
|
101
|
+
export async function guardMainWorkBranch(cwd?: string): Promise<void> {
|
|
102
|
+
guardMainWorkBranchExists(cwd);
|
|
103
|
+
|
|
104
|
+
const config = requireProjectConfig();
|
|
105
|
+
const mainBranch = config.clawtMainWorkBranch;
|
|
106
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
107
|
+
if (currentBranch !== mainBranch && !currentBranch.startsWith(VALIDATE_BRANCH_PREFIX)) {
|
|
108
|
+
printWarning(MESSAGES.GUARD_BRANCH_MISMATCH(mainBranch, currentBranch));
|
|
109
|
+
const confirmed = await confirmAction('是否继续执行?');
|
|
110
|
+
if (!confirmed) {
|
|
111
|
+
throw new ClawtError(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
79
116
|
/**
|
|
80
117
|
* 从项目配置中获取 validate 成功后自动执行的命令
|
|
81
118
|
* @returns {string | undefined} 配置的命令字符串,未配置时返回 undefined
|
|
@@ -48,6 +48,8 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
48
48
|
printInfo: vi.fn(),
|
|
49
49
|
isWorkingDirClean: vi.fn().mockReturnValue(false),
|
|
50
50
|
confirmAction: vi.fn().mockResolvedValue(true),
|
|
51
|
+
guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
|
|
52
|
+
guardMainWorkBranchExists: vi.fn(),
|
|
51
53
|
}));
|
|
52
54
|
|
|
53
55
|
import { registerCoverValidateCommand, extractTargetBranchName, findTargetWorktreePath, computeIncrementalPatch } from '../../../src/commands/cover-validate.js';
|
|
@@ -33,6 +33,8 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
33
33
|
printSuccess: vi.fn(),
|
|
34
34
|
printInfo: vi.fn(),
|
|
35
35
|
printSeparator: vi.fn(),
|
|
36
|
+
guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
|
|
37
|
+
guardMainWorkBranchExists: vi.fn(),
|
|
36
38
|
}));
|
|
37
39
|
|
|
38
40
|
import { registerCreateCommand } from '../../../src/commands/create.js';
|
|
@@ -31,6 +31,8 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
31
31
|
printInfo: vi.fn(),
|
|
32
32
|
safeStringify: vi.fn((value: unknown, indent: number = 2) => JSON.stringify(value, null, indent)),
|
|
33
33
|
interactiveConfigEditor: vi.fn(),
|
|
34
|
+
guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
|
|
35
|
+
guardMainWorkBranchExists: vi.fn(),
|
|
34
36
|
}));
|
|
35
37
|
|
|
36
38
|
import { registerInitCommand } from '../../../src/commands/init.js';
|
|
@@ -71,6 +71,8 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
71
71
|
requireProjectConfig: vi.fn().mockReturnValue({ clawtMainWorkBranch: 'main' }),
|
|
72
72
|
getMainWorkBranch: vi.fn().mockReturnValue('main'),
|
|
73
73
|
ensureOnMainWorkBranch: vi.fn(),
|
|
74
|
+
guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
|
|
75
|
+
guardMainWorkBranchExists: vi.fn(),
|
|
74
76
|
}));
|
|
75
77
|
|
|
76
78
|
import { registerMergeCommand } from '../../../src/commands/merge.js';
|
|
@@ -54,6 +54,8 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
54
54
|
getValidateBranchName: vi.fn((name: string) => `clawt-validate-${name}`),
|
|
55
55
|
deleteValidateBranch: vi.fn(),
|
|
56
56
|
getCurrentBranch: vi.fn(),
|
|
57
|
+
guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
|
|
58
|
+
guardMainWorkBranchExists: vi.fn(),
|
|
57
59
|
}));
|
|
58
60
|
|
|
59
61
|
import { registerRemoveCommand } from '../../../src/commands/remove.js';
|
|
@@ -25,6 +25,8 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
25
25
|
printInfo: vi.fn(),
|
|
26
26
|
requireProjectConfig: vi.fn().mockReturnValue({ clawtMainWorkBranch: 'main' }),
|
|
27
27
|
switchBackIfOnValidateBranch: vi.fn(),
|
|
28
|
+
guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
|
|
29
|
+
guardMainWorkBranchExists: vi.fn(),
|
|
28
30
|
}));
|
|
29
31
|
|
|
30
32
|
import { registerResetCommand } from '../../../src/commands/reset.js';
|
|
@@ -73,6 +73,8 @@ vi.mock('../../../src/utils/index.js', async (importOriginal) => {
|
|
|
73
73
|
printDryRunPreview: vi.fn(),
|
|
74
74
|
requireProjectConfig: vi.fn().mockReturnValue({ clawtMainWorkBranch: 'main' }),
|
|
75
75
|
ensureOnMainWorkBranch: vi.fn().mockResolvedValue(undefined),
|
|
76
|
+
guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
|
|
77
|
+
guardMainWorkBranchExists: vi.fn(),
|
|
76
78
|
};
|
|
77
79
|
});
|
|
78
80
|
|
|
@@ -22,7 +22,11 @@ vi.mock('../../../src/constants/index.js', () => ({
|
|
|
22
22
|
STATUS_NO_DIVERGED_COMMITS: '尚无分叉提交',
|
|
23
23
|
STATUS_LAST_VALIDATED: (relativeTime: string) => `上次验证: ${relativeTime}`,
|
|
24
24
|
STATUS_NOT_VALIDATED: '✗ 未验证',
|
|
25
|
+
STATUS_CONFIGURED_BRANCH: (branchName: string) => `主工作分支: ${branchName}`,
|
|
26
|
+
STATUS_CONFIGURED_BRANCH_DELETED: (branchName: string) => `✗ 主工作分支: ${branchName}(已不存在)`,
|
|
27
|
+
STATUS_CONFIGURED_BRANCH_MISMATCH: (branchName: string) => `⚠ 主工作分支: ${branchName}(当前分支不一致)`,
|
|
25
28
|
},
|
|
29
|
+
VALIDATE_BRANCH_PREFIX: 'clawt-validate-',
|
|
26
30
|
}));
|
|
27
31
|
|
|
28
32
|
vi.mock('../../../src/utils/index.js', () => ({
|
|
@@ -43,6 +47,8 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
43
47
|
printInfo: vi.fn(),
|
|
44
48
|
printDoubleSeparator: vi.fn(),
|
|
45
49
|
printSeparator: vi.fn(),
|
|
50
|
+
loadProjectConfig: vi.fn().mockReturnValue(null),
|
|
51
|
+
checkBranchExists: vi.fn().mockReturnValue(true),
|
|
46
52
|
}));
|
|
47
53
|
|
|
48
54
|
import { registerStatusCommand } from '../../../src/commands/status.js';
|
|
@@ -48,6 +48,8 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
48
48
|
getMainWorkBranch: vi.fn().mockReturnValue('main'),
|
|
49
49
|
rebuildValidateBranch: vi.fn(),
|
|
50
50
|
getValidateBranchName: vi.fn((name: string) => `clawt-validate-${name}`),
|
|
51
|
+
guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
|
|
52
|
+
guardMainWorkBranchExists: vi.fn(),
|
|
51
53
|
}));
|
|
52
54
|
|
|
53
55
|
import { registerSyncCommand } from '../../../src/commands/sync.js';
|
|
@@ -92,6 +92,8 @@ vi.mock('../../../src/utils/index.js', () => ({
|
|
|
92
92
|
switchToValidateBranch: vi.fn((name: string) => `clawt-validate-${name}`),
|
|
93
93
|
// validate-runner.ts 抽离的函数
|
|
94
94
|
executeRunCommand: vi.fn(),
|
|
95
|
+
guardMainWorkBranch: vi.fn().mockResolvedValue(undefined),
|
|
96
|
+
guardMainWorkBranchExists: vi.fn(),
|
|
95
97
|
}));
|
|
96
98
|
|
|
97
99
|
import { registerValidateCommand } from '../../../src/commands/validate.js';
|