clawt 3.4.4 → 3.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +132 -115
- package/dist/postinstall.js +12 -0
- package/package.json +1 -1
- package/src/commands/cover-validate.ts +1 -1
- package/src/commands/create.ts +2 -10
- package/src/commands/home.ts +1 -1
- package/src/commands/init.ts +2 -2
- package/src/commands/list.ts +4 -4
- package/src/commands/merge.ts +2 -10
- package/src/commands/remove.ts +1 -1
- package/src/commands/reset.ts +1 -1
- package/src/commands/resume.ts +2 -3
- package/src/commands/run.ts +3 -11
- package/src/commands/status.ts +3 -16
- package/src/commands/sync.ts +2 -3
- package/src/commands/validate.ts +2 -2
- package/src/constants/index.ts +1 -0
- package/src/constants/interactive-panel.ts +2 -0
- package/src/constants/messages/interactive-panel.ts +1 -0
- package/src/constants/pre-checks.ts +30 -0
- package/src/utils/index.ts +3 -2
- package/src/utils/interactive-panel.ts +13 -0
- package/src/utils/project-config.ts +2 -24
- package/src/utils/validate-branch.ts +9 -4
- package/src/utils/validation.ts +43 -17
- package/src/utils/worktree-matcher.ts +15 -0
- package/tests/unit/commands/create.test.ts +11 -7
- package/tests/unit/commands/list.test.ts +18 -14
- package/tests/unit/commands/merge.test.ts +27 -23
- package/tests/unit/commands/resume.test.ts +14 -11
- package/tests/unit/commands/run.test.ts +29 -25
- package/tests/unit/commands/status.test.ts +58 -54
- package/tests/unit/commands/sync.test.ts +18 -14
- package/tests/unit/utils/validate-branch.test.ts +22 -1
package/dist/index.js
CHANGED
|
@@ -535,6 +535,8 @@ var PANEL_SHORTCUT_KEYS = {
|
|
|
535
535
|
RESUME: "r",
|
|
536
536
|
/** 同步 */
|
|
537
537
|
SYNC: "s",
|
|
538
|
+
/** 覆盖 */
|
|
539
|
+
COVER: "c",
|
|
538
540
|
/** 手动刷新 */
|
|
539
541
|
REFRESH: "f",
|
|
540
542
|
/** 退出 */
|
|
@@ -550,6 +552,7 @@ var SHORTCUT_LABELS = {
|
|
|
550
552
|
DELETE: "\u5220\u9664",
|
|
551
553
|
RESUME: "\u6062\u590D",
|
|
552
554
|
SYNC: "\u540C\u6B65",
|
|
555
|
+
COVER: "\u8986\u76D6",
|
|
553
556
|
REFRESH: "\u5237\u65B0",
|
|
554
557
|
QUIT: "\u9000\u51FA"
|
|
555
558
|
};
|
|
@@ -748,6 +751,32 @@ var GROUP_SEPARATOR_LABEL = (dateLabel, relativeTime) => `\u2550\u2550\u2550\u25
|
|
|
748
751
|
var UNKNOWN_DATE_GROUP = "\u672A\u77E5\u65E5\u671F";
|
|
749
752
|
var UNKNOWN_DATE_SEPARATOR_LABEL = `\u2550\u2550\u2550\u2550 ${chalk2.bold.hex("#FF8C00")("\u672A\u77E5\u65E5\u671F")} \u2550\u2550\u2550\u2550`;
|
|
750
753
|
|
|
754
|
+
// src/constants/pre-checks.ts
|
|
755
|
+
var PRE_CHECK_CREATE = {
|
|
756
|
+
requireMainWorktree: true,
|
|
757
|
+
requireHead: true,
|
|
758
|
+
ensureOnClawtMainWorkBranch: true,
|
|
759
|
+
requireCleanWorkingDir: true
|
|
760
|
+
};
|
|
761
|
+
var PRE_CHECK_RUN = { ...PRE_CHECK_CREATE };
|
|
762
|
+
var PRE_CHECK_DRY_RUN = { requireMainWorktree: true, requireHead: true };
|
|
763
|
+
var PRE_CHECK_MERGE = {
|
|
764
|
+
requireMainWorktree: true,
|
|
765
|
+
requireHead: true,
|
|
766
|
+
ensureOnClawtMainWorkBranch: true
|
|
767
|
+
};
|
|
768
|
+
var PRE_CHECK_SYNC = {
|
|
769
|
+
requireMainWorktree: true,
|
|
770
|
+
requireHead: true,
|
|
771
|
+
requireProjectConfig: true,
|
|
772
|
+
ensureOnClawtMainWorkBranch: true
|
|
773
|
+
};
|
|
774
|
+
var PRE_CHECK_RESUME = {
|
|
775
|
+
requireMainWorktree: true,
|
|
776
|
+
requireHead: true,
|
|
777
|
+
requireClaudeCode: true
|
|
778
|
+
};
|
|
779
|
+
|
|
751
780
|
// src/errors/index.ts
|
|
752
781
|
var ClawtError = class extends Error {
|
|
753
782
|
/** 退出码 */
|
|
@@ -1063,17 +1092,6 @@ function gitApplyCachedCheck(patchContent, cwd) {
|
|
|
1063
1092
|
return false;
|
|
1064
1093
|
}
|
|
1065
1094
|
}
|
|
1066
|
-
function getBranchCreatedAt(branchName, cwd) {
|
|
1067
|
-
try {
|
|
1068
|
-
const output = execCommand(`git reflog show ${branchName} --format=%cI`, { cwd });
|
|
1069
|
-
if (!output.trim()) return null;
|
|
1070
|
-
const lines = output.trim().split("\n");
|
|
1071
|
-
const lastLine = lines[lines.length - 1];
|
|
1072
|
-
return lastLine || null;
|
|
1073
|
-
} catch {
|
|
1074
|
-
return null;
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
1095
|
function gitCheckout(branchName, cwd) {
|
|
1078
1096
|
execCommand(`git checkout ${branchName}`, { cwd });
|
|
1079
1097
|
}
|
|
@@ -1321,76 +1339,11 @@ function guardMainWorkBranchExists(cwd) {
|
|
|
1321
1339
|
throw new ClawtError(MESSAGES.GUARD_BRANCH_NOT_EXISTS(mainBranch));
|
|
1322
1340
|
}
|
|
1323
1341
|
}
|
|
1324
|
-
async function guardMainWorkBranch(cwd) {
|
|
1325
|
-
guardMainWorkBranchExists(cwd);
|
|
1326
|
-
const config2 = requireProjectConfig();
|
|
1327
|
-
const mainBranch = config2.clawtMainWorkBranch;
|
|
1328
|
-
const currentBranch = getCurrentBranch(cwd);
|
|
1329
|
-
if (currentBranch !== mainBranch && !currentBranch.startsWith(VALIDATE_BRANCH_PREFIX)) {
|
|
1330
|
-
printWarning(MESSAGES.GUARD_BRANCH_MISMATCH(mainBranch, currentBranch));
|
|
1331
|
-
const confirmed = await confirmAction("\u662F\u5426\u7EE7\u7EED\u6267\u884C\uFF1F");
|
|
1332
|
-
if (!confirmed) {
|
|
1333
|
-
throw new ClawtError(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
1342
|
function getValidateRunCommand() {
|
|
1338
1343
|
const config2 = loadProjectConfig();
|
|
1339
1344
|
return config2?.validateRunCommand || void 0;
|
|
1340
1345
|
}
|
|
1341
1346
|
|
|
1342
|
-
// src/utils/validation.ts
|
|
1343
|
-
function validateMainWorktree() {
|
|
1344
|
-
try {
|
|
1345
|
-
const gitCommonDir = getGitCommonDir();
|
|
1346
|
-
if (gitCommonDir !== ".git") {
|
|
1347
|
-
throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
|
|
1348
|
-
}
|
|
1349
|
-
} catch (error) {
|
|
1350
|
-
if (error instanceof ClawtError) {
|
|
1351
|
-
throw error;
|
|
1352
|
-
}
|
|
1353
|
-
throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
function validateClaudeCodeInstalled() {
|
|
1357
|
-
try {
|
|
1358
|
-
execCommand("claude --version");
|
|
1359
|
-
} catch {
|
|
1360
|
-
throw new ClawtError(MESSAGES.CLAUDE_NOT_INSTALLED);
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
function validateHeadExists() {
|
|
1364
|
-
try {
|
|
1365
|
-
execCommand("git rev-parse --verify HEAD");
|
|
1366
|
-
} catch {
|
|
1367
|
-
throw new ClawtError(MESSAGES.HEAD_NOT_FOUND);
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
function validateWorkingDirClean() {
|
|
1371
|
-
if (!isWorkingDirClean()) {
|
|
1372
|
-
throw new ClawtError(MESSAGES.MAIN_WORKTREE_DIRTY);
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
function runPreChecks(options) {
|
|
1376
|
-
if (options.mainWorktree) {
|
|
1377
|
-
validateMainWorktree();
|
|
1378
|
-
}
|
|
1379
|
-
if (options.headExists) {
|
|
1380
|
-
validateHeadExists();
|
|
1381
|
-
}
|
|
1382
|
-
if (options.projectConfig) {
|
|
1383
|
-
requireProjectConfig();
|
|
1384
|
-
}
|
|
1385
|
-
if (options.branchExists) {
|
|
1386
|
-
guardMainWorkBranchExists();
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
// src/utils/worktree.ts
|
|
1391
|
-
import { join as join4 } from "path";
|
|
1392
|
-
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
|
|
1393
|
-
|
|
1394
1347
|
// src/utils/validate-branch.ts
|
|
1395
1348
|
import Enquirer from "enquirer";
|
|
1396
1349
|
function getValidateBranchName(branchName) {
|
|
@@ -1481,6 +1434,11 @@ async function ensureOnMainWorkBranch(cwd) {
|
|
|
1481
1434
|
gitCheckout(mainBranch, cwd);
|
|
1482
1435
|
return;
|
|
1483
1436
|
}
|
|
1437
|
+
printWarning(MESSAGES.GUARD_BRANCH_MISMATCH(mainBranch, currentBranch));
|
|
1438
|
+
const confirmed = await confirmAction("\u662F\u5426\u7EE7\u7EED\u6267\u884C\uFF1F");
|
|
1439
|
+
if (!confirmed) {
|
|
1440
|
+
throw new ClawtError(MESSAGES.DESTRUCTIVE_OP_CANCELLED);
|
|
1441
|
+
}
|
|
1484
1442
|
logger.info(`\u5F53\u524D\u5728\u5206\u652F ${currentBranch} \u4E0A\uFF0C\u9700\u5207\u6362\u5230\u4E3B\u5DE5\u4F5C\u5206\u652F ${mainBranch}`);
|
|
1485
1443
|
if (!isWorkingDirClean(cwd)) {
|
|
1486
1444
|
await handleDirtyWorkingDir(cwd);
|
|
@@ -1488,7 +1446,66 @@ async function ensureOnMainWorkBranch(cwd) {
|
|
|
1488
1446
|
gitCheckout(mainBranch, cwd);
|
|
1489
1447
|
}
|
|
1490
1448
|
|
|
1449
|
+
// src/utils/validation.ts
|
|
1450
|
+
function validateMainWorktree() {
|
|
1451
|
+
try {
|
|
1452
|
+
const gitCommonDir = getGitCommonDir();
|
|
1453
|
+
if (gitCommonDir !== ".git") {
|
|
1454
|
+
throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
|
|
1455
|
+
}
|
|
1456
|
+
} catch (error) {
|
|
1457
|
+
if (error instanceof ClawtError) {
|
|
1458
|
+
throw error;
|
|
1459
|
+
}
|
|
1460
|
+
throw new ClawtError(MESSAGES.NOT_MAIN_WORKTREE);
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
function validateClaudeCodeInstalled() {
|
|
1464
|
+
try {
|
|
1465
|
+
execCommand("claude --version");
|
|
1466
|
+
} catch {
|
|
1467
|
+
throw new ClawtError(MESSAGES.CLAUDE_NOT_INSTALLED);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
function validateHeadExists() {
|
|
1471
|
+
try {
|
|
1472
|
+
execCommand("git rev-parse --verify HEAD");
|
|
1473
|
+
} catch {
|
|
1474
|
+
throw new ClawtError(MESSAGES.HEAD_NOT_FOUND);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
function validateWorkingDirClean() {
|
|
1478
|
+
if (!isWorkingDirClean()) {
|
|
1479
|
+
throw new ClawtError(MESSAGES.MAIN_WORKTREE_DIRTY);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
async function runPreChecks(options) {
|
|
1483
|
+
if (options.requireMainWorktree) {
|
|
1484
|
+
validateMainWorktree();
|
|
1485
|
+
}
|
|
1486
|
+
if (options.requireHead) {
|
|
1487
|
+
validateHeadExists();
|
|
1488
|
+
}
|
|
1489
|
+
if (options.requireProjectConfig) {
|
|
1490
|
+
requireProjectConfig();
|
|
1491
|
+
}
|
|
1492
|
+
if (options.requireMainBranchExists) {
|
|
1493
|
+
guardMainWorkBranchExists();
|
|
1494
|
+
}
|
|
1495
|
+
if (options.ensureOnClawtMainWorkBranch) {
|
|
1496
|
+
await ensureOnMainWorkBranch();
|
|
1497
|
+
}
|
|
1498
|
+
if (options.requireCleanWorkingDir) {
|
|
1499
|
+
validateWorkingDirClean();
|
|
1500
|
+
}
|
|
1501
|
+
if (options.requireClaudeCode) {
|
|
1502
|
+
validateClaudeCodeInstalled();
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1491
1506
|
// src/utils/worktree.ts
|
|
1507
|
+
import { join as join4 } from "path";
|
|
1508
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
|
|
1492
1509
|
function getProjectWorktreeDir() {
|
|
1493
1510
|
const projectName = getProjectName();
|
|
1494
1511
|
return join4(WORKTREES_DIR, projectName);
|
|
@@ -1968,6 +1985,14 @@ function getWorktreeCreatedDate(dirPath) {
|
|
|
1968
1985
|
return null;
|
|
1969
1986
|
}
|
|
1970
1987
|
}
|
|
1988
|
+
function getWorktreeCreatedTime(dirPath) {
|
|
1989
|
+
try {
|
|
1990
|
+
const stat = statSync3(dirPath);
|
|
1991
|
+
return stat.birthtime.toISOString();
|
|
1992
|
+
} catch {
|
|
1993
|
+
return null;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1971
1996
|
function formatRelativeDate(dateStr) {
|
|
1972
1997
|
const today = formatLocalDate(/* @__PURE__ */ new Date());
|
|
1973
1998
|
const todayMs = new Date(today).getTime();
|
|
@@ -3585,6 +3610,10 @@ var InteractivePanel = class {
|
|
|
3585
3610
|
this.executeOperation(() => this.handleSync());
|
|
3586
3611
|
return;
|
|
3587
3612
|
}
|
|
3613
|
+
if (key === PANEL_SHORTCUT_KEYS.COVER) {
|
|
3614
|
+
this.executeOperation(() => this.handleCover());
|
|
3615
|
+
return;
|
|
3616
|
+
}
|
|
3588
3617
|
}
|
|
3589
3618
|
/**
|
|
3590
3619
|
* 向上导航,选中显示顺序中的上一个 worktree
|
|
@@ -3797,6 +3826,13 @@ var InteractivePanel = class {
|
|
|
3797
3826
|
const branch = this.getSelectedBranch();
|
|
3798
3827
|
runCommandInherited(`clawt sync -b ${branch}`);
|
|
3799
3828
|
}
|
|
3829
|
+
/**
|
|
3830
|
+
* 执行覆盖操作
|
|
3831
|
+
* cover 命令从主 worktree 当前所在的验证分支名自动推导目标分支
|
|
3832
|
+
*/
|
|
3833
|
+
handleCover() {
|
|
3834
|
+
runCommandInherited("clawt cover");
|
|
3835
|
+
}
|
|
3800
3836
|
/**
|
|
3801
3837
|
* 等待用户按回车键
|
|
3802
3838
|
* @returns {Promise<void>} 用户按回车时 resolve
|
|
@@ -3818,12 +3854,12 @@ var InteractivePanel = class {
|
|
|
3818
3854
|
// src/commands/list.ts
|
|
3819
3855
|
import chalk10 from "chalk";
|
|
3820
3856
|
function registerListCommand(program2) {
|
|
3821
|
-
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) => {
|
|
3822
|
-
handleList(options);
|
|
3857
|
+
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(async (options) => {
|
|
3858
|
+
await handleList(options);
|
|
3823
3859
|
});
|
|
3824
3860
|
}
|
|
3825
|
-
function handleList(options) {
|
|
3826
|
-
runPreChecks({
|
|
3861
|
+
async function handleList(options) {
|
|
3862
|
+
await runPreChecks({ requireMainWorktree: true });
|
|
3827
3863
|
const projectName = getProjectName();
|
|
3828
3864
|
const worktrees = getProjectWorktrees();
|
|
3829
3865
|
logger.info(`list \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}\uFF0C\u5171 ${worktrees.length} \u4E2A worktree`);
|
|
@@ -3873,10 +3909,7 @@ function registerCreateCommand(program2) {
|
|
|
3873
3909
|
});
|
|
3874
3910
|
}
|
|
3875
3911
|
async function handleCreate(options) {
|
|
3876
|
-
runPreChecks(
|
|
3877
|
-
await guardMainWorkBranch();
|
|
3878
|
-
await ensureOnMainWorkBranch();
|
|
3879
|
-
validateWorkingDirClean();
|
|
3912
|
+
await runPreChecks(PRE_CHECK_CREATE);
|
|
3880
3913
|
const count = Number(options.number);
|
|
3881
3914
|
if (!Number.isInteger(count) || count <= 0) {
|
|
3882
3915
|
throw new ClawtError(
|
|
@@ -3910,7 +3943,7 @@ function registerRemoveCommand(program2) {
|
|
|
3910
3943
|
});
|
|
3911
3944
|
}
|
|
3912
3945
|
async function handleRemove(options) {
|
|
3913
|
-
runPreChecks({
|
|
3946
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true });
|
|
3914
3947
|
const projectName = getProjectName();
|
|
3915
3948
|
logger.info(`remove \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}`);
|
|
3916
3949
|
const allWorktrees = getProjectWorktrees();
|
|
@@ -4016,12 +4049,8 @@ function handleDryRunFromFile(options) {
|
|
|
4016
4049
|
printDryRunPreview(branchNames, tasks, concurrency);
|
|
4017
4050
|
}
|
|
4018
4051
|
async function handleRun(options) {
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
await guardMainWorkBranch();
|
|
4022
|
-
await ensureOnMainWorkBranch();
|
|
4023
|
-
validateWorkingDirClean();
|
|
4024
|
-
}
|
|
4052
|
+
const preChecks = options.dryRun ? PRE_CHECK_DRY_RUN : PRE_CHECK_RUN;
|
|
4053
|
+
await runPreChecks(preChecks);
|
|
4025
4054
|
if (options.file && options.tasks) {
|
|
4026
4055
|
throw new ClawtError(MESSAGES.FILE_AND_TASKS_CONFLICT);
|
|
4027
4056
|
}
|
|
@@ -4082,8 +4111,7 @@ function registerResumeCommand(program2) {
|
|
|
4082
4111
|
});
|
|
4083
4112
|
}
|
|
4084
4113
|
async function handleResume(options) {
|
|
4085
|
-
runPreChecks(
|
|
4086
|
-
validateClaudeCodeInstalled();
|
|
4114
|
+
await runPreChecks(PRE_CHECK_RESUME);
|
|
4087
4115
|
logger.info(`resume \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F\u8FC7\u6EE4: ${options.branch ?? "(\u65E0)"}`);
|
|
4088
4116
|
const worktrees = getProjectWorktrees();
|
|
4089
4117
|
let targetWorktrees;
|
|
@@ -4183,8 +4211,7 @@ async function executeSyncForBranch(targetWorktreePath, branch) {
|
|
|
4183
4211
|
return { success: true, hasConflict: false };
|
|
4184
4212
|
}
|
|
4185
4213
|
async function handleSync(options) {
|
|
4186
|
-
runPreChecks(
|
|
4187
|
-
await guardMainWorkBranch();
|
|
4214
|
+
await runPreChecks(PRE_CHECK_SYNC);
|
|
4188
4215
|
logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
|
|
4189
4216
|
const worktrees = getProjectWorktrees();
|
|
4190
4217
|
const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
|
|
@@ -4217,7 +4244,7 @@ async function handlePatchApplyFailure(targetWorktreePath, branchName) {
|
|
|
4217
4244
|
const syncResult = await executeSyncForBranch(targetWorktreePath, branchName);
|
|
4218
4245
|
}
|
|
4219
4246
|
async function handleValidateClean(options) {
|
|
4220
|
-
runPreChecks({
|
|
4247
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true });
|
|
4221
4248
|
const projectName = getProjectName();
|
|
4222
4249
|
const mainWorktreePath = getGitTopLevel();
|
|
4223
4250
|
const worktrees = getProjectWorktrees();
|
|
@@ -4302,7 +4329,7 @@ async function handleValidate(options) {
|
|
|
4302
4329
|
await handleValidateClean(options);
|
|
4303
4330
|
return;
|
|
4304
4331
|
}
|
|
4305
|
-
runPreChecks({
|
|
4332
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true });
|
|
4306
4333
|
const projectName = getProjectName();
|
|
4307
4334
|
const mainWorktreePath = getGitTopLevel();
|
|
4308
4335
|
const worktrees = getProjectWorktrees();
|
|
@@ -4367,7 +4394,7 @@ function computeIncrementalPatch(snapshotTreeHash, mainWorktreePath) {
|
|
|
4367
4394
|
return { patch, currentTreeHash };
|
|
4368
4395
|
}
|
|
4369
4396
|
async function handleCoverValidate() {
|
|
4370
|
-
runPreChecks({
|
|
4397
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true });
|
|
4371
4398
|
const projectName = getProjectName();
|
|
4372
4399
|
const mainWorktreePath = getGitTopLevel();
|
|
4373
4400
|
const currentBranch = getCurrentBranch(mainWorktreePath);
|
|
@@ -4446,11 +4473,8 @@ function cleanupWorktreeAndBranch(worktreePath, branchName) {
|
|
|
4446
4473
|
printSuccess(MESSAGES.WORKTREE_CLEANED(branchName));
|
|
4447
4474
|
}
|
|
4448
4475
|
async function handleMerge(options) {
|
|
4449
|
-
runPreChecks(
|
|
4450
|
-
await guardMainWorkBranch();
|
|
4451
|
-
await guardMainWorkBranch();
|
|
4476
|
+
await runPreChecks(PRE_CHECK_MERGE);
|
|
4452
4477
|
const mainWorktreePath = getGitTopLevel();
|
|
4453
|
-
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
4454
4478
|
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)"}`);
|
|
4455
4479
|
const worktrees = getProjectWorktrees();
|
|
4456
4480
|
const worktree = await resolveTargetWorktree(worktrees, MERGE_RESOLVE_MESSAGES, options.branch);
|
|
@@ -4609,7 +4633,7 @@ function registerResetCommand(program2) {
|
|
|
4609
4633
|
});
|
|
4610
4634
|
}
|
|
4611
4635
|
async function handleReset() {
|
|
4612
|
-
runPreChecks({
|
|
4636
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true });
|
|
4613
4637
|
const mainWorktreePath = getGitTopLevel();
|
|
4614
4638
|
logger.info("reset \u547D\u4EE4\u6267\u884C");
|
|
4615
4639
|
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
@@ -4639,7 +4663,7 @@ function registerStatusCommand(program2) {
|
|
|
4639
4663
|
});
|
|
4640
4664
|
}
|
|
4641
4665
|
async function handleStatus(options) {
|
|
4642
|
-
runPreChecks({
|
|
4666
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true });
|
|
4643
4667
|
if (options.interactive) {
|
|
4644
4668
|
const panel = new InteractivePanel(collectStatus);
|
|
4645
4669
|
await panel.start();
|
|
@@ -4681,7 +4705,7 @@ function collectWorktreeDetailedStatus(worktree, projectName) {
|
|
|
4681
4705
|
const changeStatus = detectChangeStatus(worktree);
|
|
4682
4706
|
const { commitsAhead, commitsBehind } = countCommitDivergence(worktree.branch);
|
|
4683
4707
|
const { insertions, deletions } = countDiffStat(worktree.path);
|
|
4684
|
-
const createdAt =
|
|
4708
|
+
const createdAt = getWorktreeCreatedTime(worktree.path);
|
|
4685
4709
|
return {
|
|
4686
4710
|
path: worktree.path,
|
|
4687
4711
|
branch: worktree.branch,
|
|
@@ -4727,13 +4751,6 @@ function countDiffStat(worktreePath) {
|
|
|
4727
4751
|
return { insertions: 0, deletions: 0 };
|
|
4728
4752
|
}
|
|
4729
4753
|
}
|
|
4730
|
-
function resolveBranchCreatedAt(branchName) {
|
|
4731
|
-
try {
|
|
4732
|
-
return getBranchCreatedAt(branchName);
|
|
4733
|
-
} catch {
|
|
4734
|
-
return null;
|
|
4735
|
-
}
|
|
4736
|
-
}
|
|
4737
4754
|
function resolveSnapshotTime(projectName, branchName) {
|
|
4738
4755
|
try {
|
|
4739
4756
|
return getSnapshotModifiedTime(projectName, branchName);
|
|
@@ -5319,7 +5336,7 @@ function registerInitCommand(program2) {
|
|
|
5319
5336
|
);
|
|
5320
5337
|
}
|
|
5321
5338
|
async function handleInitShow() {
|
|
5322
|
-
runPreChecks({
|
|
5339
|
+
await runPreChecks({ requireMainWorktree: true, requireProjectConfig: true });
|
|
5323
5340
|
const config2 = requireProjectConfig();
|
|
5324
5341
|
logger.info("init show \u547D\u4EE4\u6267\u884C\uFF0C\u8FDB\u5165\u4EA4\u4E92\u5F0F\u9879\u76EE\u914D\u7F6E");
|
|
5325
5342
|
const { key, newValue } = await interactiveConfigEditor(
|
|
@@ -5332,7 +5349,7 @@ async function handleInitShow() {
|
|
|
5332
5349
|
printSuccess(MESSAGES.INIT_SET_SUCCESS(key, String(newValue)));
|
|
5333
5350
|
}
|
|
5334
5351
|
async function handleInit(options) {
|
|
5335
|
-
runPreChecks({
|
|
5352
|
+
await runPreChecks({ requireMainWorktree: true });
|
|
5336
5353
|
const existingConfig = loadProjectConfig();
|
|
5337
5354
|
if (!options.branch) {
|
|
5338
5355
|
validateHeadExists();
|
|
@@ -5354,7 +5371,7 @@ function registerHomeCommand(program2) {
|
|
|
5354
5371
|
});
|
|
5355
5372
|
}
|
|
5356
5373
|
async function handleHome() {
|
|
5357
|
-
runPreChecks({
|
|
5374
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true, requireMainBranchExists: true });
|
|
5358
5375
|
const mainBranch = getMainWorkBranch();
|
|
5359
5376
|
const currentBranch = getCurrentBranch();
|
|
5360
5377
|
if (currentBranch === mainBranch) {
|
package/dist/postinstall.js
CHANGED
|
@@ -505,6 +505,8 @@ var PANEL_SHORTCUT_KEYS = {
|
|
|
505
505
|
RESUME: "r",
|
|
506
506
|
/** 同步 */
|
|
507
507
|
SYNC: "s",
|
|
508
|
+
/** 覆盖 */
|
|
509
|
+
COVER: "c",
|
|
508
510
|
/** 手动刷新 */
|
|
509
511
|
REFRESH: "f",
|
|
510
512
|
/** 退出 */
|
|
@@ -518,6 +520,7 @@ var SHORTCUT_LABELS = {
|
|
|
518
520
|
DELETE: "\u5220\u9664",
|
|
519
521
|
RESUME: "\u6062\u590D",
|
|
520
522
|
SYNC: "\u540C\u6B65",
|
|
523
|
+
COVER: "\u8986\u76D6",
|
|
521
524
|
REFRESH: "\u5237\u65B0",
|
|
522
525
|
QUIT: "\u9000\u51FA"
|
|
523
526
|
};
|
|
@@ -639,6 +642,15 @@ var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
|
639
642
|
import chalk2 from "chalk";
|
|
640
643
|
var UNKNOWN_DATE_SEPARATOR_LABEL = `\u2550\u2550\u2550\u2550 ${chalk2.bold.hex("#FF8C00")("\u672A\u77E5\u65E5\u671F")} \u2550\u2550\u2550\u2550`;
|
|
641
644
|
|
|
645
|
+
// src/constants/pre-checks.ts
|
|
646
|
+
var PRE_CHECK_CREATE = {
|
|
647
|
+
requireMainWorktree: true,
|
|
648
|
+
requireHead: true,
|
|
649
|
+
ensureOnClawtMainWorkBranch: true,
|
|
650
|
+
requireCleanWorkingDir: true
|
|
651
|
+
};
|
|
652
|
+
var PRE_CHECK_RUN = { ...PRE_CHECK_CREATE };
|
|
653
|
+
|
|
642
654
|
// scripts/postinstall.ts
|
|
643
655
|
function ensureDirectory(dirPath) {
|
|
644
656
|
if (!existsSync(dirPath)) {
|
package/package.json
CHANGED
|
@@ -97,7 +97,7 @@ export function computeIncrementalPatch(snapshotTreeHash: string, mainWorktreePa
|
|
|
97
97
|
*/
|
|
98
98
|
async function handleCoverValidate(): Promise<void> {
|
|
99
99
|
// 步骤 1:前置校验
|
|
100
|
-
runPreChecks({
|
|
100
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true });
|
|
101
101
|
const projectName = getProjectName();
|
|
102
102
|
const mainWorktreePath = getGitTopLevel();
|
|
103
103
|
const currentBranch = getCurrentBranch(mainWorktreePath);
|
package/src/commands/create.ts
CHANGED
|
@@ -3,16 +3,14 @@ import { MESSAGES, EXIT_CODES } from '../constants/index.js';
|
|
|
3
3
|
import { ClawtError } from '../errors/index.js';
|
|
4
4
|
import { logger } from '../logger/index.js';
|
|
5
5
|
import type { CreateOptions } from '../types/index.js';
|
|
6
|
+
import { PRE_CHECK_CREATE } from '../constants/index.js';
|
|
6
7
|
import {
|
|
7
8
|
runPreChecks,
|
|
8
9
|
createWorktrees,
|
|
9
|
-
ensureOnMainWorkBranch,
|
|
10
|
-
validateWorkingDirClean,
|
|
11
10
|
getValidateBranchName,
|
|
12
11
|
printSuccess,
|
|
13
12
|
printInfo,
|
|
14
13
|
printSeparator,
|
|
15
|
-
guardMainWorkBranch,
|
|
16
14
|
} from '../utils/index.js';
|
|
17
15
|
|
|
18
16
|
/**
|
|
@@ -35,13 +33,7 @@ export function registerCreateCommand(program: Command): void {
|
|
|
35
33
|
* @param {CreateOptions} options - 命令选项
|
|
36
34
|
*/
|
|
37
35
|
async function handleCreate(options: CreateOptions): Promise<void> {
|
|
38
|
-
runPreChecks(
|
|
39
|
-
|
|
40
|
-
await guardMainWorkBranch();
|
|
41
|
-
|
|
42
|
-
await ensureOnMainWorkBranch();
|
|
43
|
-
|
|
44
|
-
validateWorkingDirClean();
|
|
36
|
+
await runPreChecks(PRE_CHECK_CREATE);
|
|
45
37
|
|
|
46
38
|
const count = Number(options.number);
|
|
47
39
|
|
package/src/commands/home.ts
CHANGED
|
@@ -27,7 +27,7 @@ export function registerHomeCommand(program: Command): void {
|
|
|
27
27
|
* 执行 home 命令:切换回主工作分支
|
|
28
28
|
*/
|
|
29
29
|
async function handleHome(): Promise<void> {
|
|
30
|
-
runPreChecks({
|
|
30
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true, requireMainBranchExists: true });
|
|
31
31
|
|
|
32
32
|
const mainBranch = getMainWorkBranch();
|
|
33
33
|
const currentBranch = getCurrentBranch();
|
package/src/commands/init.ts
CHANGED
|
@@ -41,7 +41,7 @@ export function registerInitCommand(program: Command): void {
|
|
|
41
41
|
* 处理 init show 子命令:交互式面板展示和修改项目配置
|
|
42
42
|
*/
|
|
43
43
|
async function handleInitShow(): Promise<void> {
|
|
44
|
-
runPreChecks({
|
|
44
|
+
await runPreChecks({ requireMainWorktree: true, requireProjectConfig: true });
|
|
45
45
|
const config = requireProjectConfig();
|
|
46
46
|
|
|
47
47
|
logger.info('init show 命令执行,进入交互式项目配置');
|
|
@@ -67,7 +67,7 @@ async function handleInitShow(): Promise<void> {
|
|
|
67
67
|
* @param {InitOptions} options - 命令选项
|
|
68
68
|
*/
|
|
69
69
|
async function handleInit(options: InitOptions): Promise<void> {
|
|
70
|
-
runPreChecks({
|
|
70
|
+
await runPreChecks({ requireMainWorktree: true });
|
|
71
71
|
|
|
72
72
|
const existingConfig = loadProjectConfig();
|
|
73
73
|
|
package/src/commands/list.ts
CHANGED
|
@@ -23,8 +23,8 @@ export function registerListCommand(program: Command): void {
|
|
|
23
23
|
.command('list')
|
|
24
24
|
.description('列出当前项目所有 worktree(支持 --json 格式输出)')
|
|
25
25
|
.option('--json', '以 JSON 格式输出')
|
|
26
|
-
.action((options: ListOptions) => {
|
|
27
|
-
handleList(options);
|
|
26
|
+
.action(async (options: ListOptions) => {
|
|
27
|
+
await handleList(options);
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -32,8 +32,8 @@ export function registerListCommand(program: Command): void {
|
|
|
32
32
|
* 执行 list 命令的核心逻辑
|
|
33
33
|
* @param {ListOptions} options - 命令选项
|
|
34
34
|
*/
|
|
35
|
-
function handleList(options: ListOptions): void {
|
|
36
|
-
runPreChecks({
|
|
35
|
+
async function handleList(options: ListOptions): Promise<void> {
|
|
36
|
+
await runPreChecks({ requireMainWorktree: true });
|
|
37
37
|
|
|
38
38
|
const projectName = getProjectName();
|
|
39
39
|
const worktrees = getProjectWorktrees();
|
package/src/commands/merge.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { logger } from '../logger/index.js';
|
|
|
3
3
|
import { ClawtError } from '../errors/index.js';
|
|
4
4
|
import { MESSAGES, AUTO_SAVE_COMMIT_MESSAGE } from '../constants/index.js';
|
|
5
5
|
import type { MergeOptions } from '../types/index.js';
|
|
6
|
+
import { PRE_CHECK_MERGE } from '../constants/index.js';
|
|
6
7
|
import {
|
|
7
8
|
runPreChecks,
|
|
8
9
|
getProjectName,
|
|
@@ -32,8 +33,6 @@ import {
|
|
|
32
33
|
gitCheckout,
|
|
33
34
|
resolveTargetWorktree,
|
|
34
35
|
getMainWorkBranch,
|
|
35
|
-
ensureOnMainWorkBranch,
|
|
36
|
-
guardMainWorkBranch,
|
|
37
36
|
} from '../utils/index.js';
|
|
38
37
|
import type { WorktreeResolveMessages } from '../utils/index.js';
|
|
39
38
|
|
|
@@ -136,17 +135,10 @@ function cleanupWorktreeAndBranch(worktreePath: string, branchName: string): voi
|
|
|
136
135
|
* @param {MergeOptions} options - 命令选项
|
|
137
136
|
*/
|
|
138
137
|
async function handleMerge(options: MergeOptions): Promise<void> {
|
|
139
|
-
runPreChecks(
|
|
140
|
-
|
|
141
|
-
await guardMainWorkBranch();
|
|
142
|
-
|
|
143
|
-
await guardMainWorkBranch();
|
|
138
|
+
await runPreChecks(PRE_CHECK_MERGE);
|
|
144
139
|
|
|
145
140
|
const mainWorktreePath = getGitTopLevel();
|
|
146
141
|
|
|
147
|
-
// 确保当前在主工作分支上
|
|
148
|
-
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
149
|
-
|
|
150
142
|
logger.info(`merge 命令执行,分支: ${options.branch ?? '(未指定)'},提交信息: ${options.message ?? '(未提供)'}`);
|
|
151
143
|
|
|
152
144
|
// 解析目标 worktree(精确匹配 / 模糊匹配 / 交互选择)
|
package/src/commands/remove.ts
CHANGED
|
@@ -55,7 +55,7 @@ export function registerRemoveCommand(program: Command): void {
|
|
|
55
55
|
* @param {RemoveOptions} options - 命令选项
|
|
56
56
|
*/
|
|
57
57
|
async function handleRemove(options: RemoveOptions): Promise<void> {
|
|
58
|
-
runPreChecks({
|
|
58
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true });
|
|
59
59
|
|
|
60
60
|
const projectName = getProjectName();
|
|
61
61
|
logger.info(`remove 命令执行,项目: ${projectName}`);
|
package/src/commands/reset.ts
CHANGED
|
@@ -30,7 +30,7 @@ export function registerResetCommand(program: Command): void {
|
|
|
30
30
|
* 执行 reset 命令:重置主 worktree 工作区和暂存区
|
|
31
31
|
*/
|
|
32
32
|
async function handleReset(): Promise<void> {
|
|
33
|
-
runPreChecks({
|
|
33
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true });
|
|
34
34
|
|
|
35
35
|
const mainWorktreePath = getGitTopLevel();
|
|
36
36
|
logger.info('reset 命令执行');
|
package/src/commands/resume.ts
CHANGED
|
@@ -3,9 +3,9 @@ import { logger } from '../logger/index.js';
|
|
|
3
3
|
import { MESSAGES } from '../constants/index.js';
|
|
4
4
|
import type { ResumeOptions } from '../types/index.js';
|
|
5
5
|
import type { WorktreeInfo } from '../types/index.js';
|
|
6
|
+
import { PRE_CHECK_RESUME } from '../constants/index.js';
|
|
6
7
|
import {
|
|
7
8
|
runPreChecks,
|
|
8
|
-
validateClaudeCodeInstalled,
|
|
9
9
|
getProjectWorktrees,
|
|
10
10
|
launchInteractiveClaude,
|
|
11
11
|
launchInteractiveClaudeInNewTerminal,
|
|
@@ -47,8 +47,7 @@ export function registerResumeCommand(program: Command): void {
|
|
|
47
47
|
* @param {ResumeOptions} options - 命令选项
|
|
48
48
|
*/
|
|
49
49
|
async function handleResume(options: ResumeOptions): Promise<void> {
|
|
50
|
-
runPreChecks(
|
|
51
|
-
validateClaudeCodeInstalled();
|
|
50
|
+
await runPreChecks(PRE_CHECK_RESUME);
|
|
52
51
|
|
|
53
52
|
logger.info(`resume 命令执行,分支过滤: ${options.branch ?? '(无)'}`);
|
|
54
53
|
const worktrees = getProjectWorktrees();
|