clawt 3.4.3 → 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 -108
- 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 -7
- 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 -9
- 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 +4 -3
- 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 +55 -18
- 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,71 +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 runPreChecks(options) {
|
|
1371
|
-
if (options.mainWorktree) {
|
|
1372
|
-
validateMainWorktree();
|
|
1373
|
-
}
|
|
1374
|
-
if (options.headExists) {
|
|
1375
|
-
validateHeadExists();
|
|
1376
|
-
}
|
|
1377
|
-
if (options.projectConfig) {
|
|
1378
|
-
requireProjectConfig();
|
|
1379
|
-
}
|
|
1380
|
-
if (options.branchExists) {
|
|
1381
|
-
guardMainWorkBranchExists();
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
// src/utils/worktree.ts
|
|
1386
|
-
import { join as join4 } from "path";
|
|
1387
|
-
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
|
|
1388
|
-
|
|
1389
1347
|
// src/utils/validate-branch.ts
|
|
1390
1348
|
import Enquirer from "enquirer";
|
|
1391
1349
|
function getValidateBranchName(branchName) {
|
|
@@ -1476,6 +1434,11 @@ async function ensureOnMainWorkBranch(cwd) {
|
|
|
1476
1434
|
gitCheckout(mainBranch, cwd);
|
|
1477
1435
|
return;
|
|
1478
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
|
+
}
|
|
1479
1442
|
logger.info(`\u5F53\u524D\u5728\u5206\u652F ${currentBranch} \u4E0A\uFF0C\u9700\u5207\u6362\u5230\u4E3B\u5DE5\u4F5C\u5206\u652F ${mainBranch}`);
|
|
1480
1443
|
if (!isWorkingDirClean(cwd)) {
|
|
1481
1444
|
await handleDirtyWorkingDir(cwd);
|
|
@@ -1483,7 +1446,66 @@ async function ensureOnMainWorkBranch(cwd) {
|
|
|
1483
1446
|
gitCheckout(mainBranch, cwd);
|
|
1484
1447
|
}
|
|
1485
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
|
+
|
|
1486
1506
|
// src/utils/worktree.ts
|
|
1507
|
+
import { join as join4 } from "path";
|
|
1508
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
|
|
1487
1509
|
function getProjectWorktreeDir() {
|
|
1488
1510
|
const projectName = getProjectName();
|
|
1489
1511
|
return join4(WORKTREES_DIR, projectName);
|
|
@@ -1963,6 +1985,14 @@ function getWorktreeCreatedDate(dirPath) {
|
|
|
1963
1985
|
return null;
|
|
1964
1986
|
}
|
|
1965
1987
|
}
|
|
1988
|
+
function getWorktreeCreatedTime(dirPath) {
|
|
1989
|
+
try {
|
|
1990
|
+
const stat = statSync3(dirPath);
|
|
1991
|
+
return stat.birthtime.toISOString();
|
|
1992
|
+
} catch {
|
|
1993
|
+
return null;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1966
1996
|
function formatRelativeDate(dateStr) {
|
|
1967
1997
|
const today = formatLocalDate(/* @__PURE__ */ new Date());
|
|
1968
1998
|
const todayMs = new Date(today).getTime();
|
|
@@ -3580,6 +3610,10 @@ var InteractivePanel = class {
|
|
|
3580
3610
|
this.executeOperation(() => this.handleSync());
|
|
3581
3611
|
return;
|
|
3582
3612
|
}
|
|
3613
|
+
if (key === PANEL_SHORTCUT_KEYS.COVER) {
|
|
3614
|
+
this.executeOperation(() => this.handleCover());
|
|
3615
|
+
return;
|
|
3616
|
+
}
|
|
3583
3617
|
}
|
|
3584
3618
|
/**
|
|
3585
3619
|
* 向上导航,选中显示顺序中的上一个 worktree
|
|
@@ -3792,6 +3826,13 @@ var InteractivePanel = class {
|
|
|
3792
3826
|
const branch = this.getSelectedBranch();
|
|
3793
3827
|
runCommandInherited(`clawt sync -b ${branch}`);
|
|
3794
3828
|
}
|
|
3829
|
+
/**
|
|
3830
|
+
* 执行覆盖操作
|
|
3831
|
+
* cover 命令从主 worktree 当前所在的验证分支名自动推导目标分支
|
|
3832
|
+
*/
|
|
3833
|
+
handleCover() {
|
|
3834
|
+
runCommandInherited("clawt cover");
|
|
3835
|
+
}
|
|
3795
3836
|
/**
|
|
3796
3837
|
* 等待用户按回车键
|
|
3797
3838
|
* @returns {Promise<void>} 用户按回车时 resolve
|
|
@@ -3813,12 +3854,12 @@ var InteractivePanel = class {
|
|
|
3813
3854
|
// src/commands/list.ts
|
|
3814
3855
|
import chalk10 from "chalk";
|
|
3815
3856
|
function registerListCommand(program2) {
|
|
3816
|
-
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) => {
|
|
3817
|
-
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);
|
|
3818
3859
|
});
|
|
3819
3860
|
}
|
|
3820
|
-
function handleList(options) {
|
|
3821
|
-
runPreChecks({
|
|
3861
|
+
async function handleList(options) {
|
|
3862
|
+
await runPreChecks({ requireMainWorktree: true });
|
|
3822
3863
|
const projectName = getProjectName();
|
|
3823
3864
|
const worktrees = getProjectWorktrees();
|
|
3824
3865
|
logger.info(`list \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}\uFF0C\u5171 ${worktrees.length} \u4E2A worktree`);
|
|
@@ -3868,9 +3909,7 @@ function registerCreateCommand(program2) {
|
|
|
3868
3909
|
});
|
|
3869
3910
|
}
|
|
3870
3911
|
async function handleCreate(options) {
|
|
3871
|
-
runPreChecks(
|
|
3872
|
-
await guardMainWorkBranch();
|
|
3873
|
-
await ensureOnMainWorkBranch();
|
|
3912
|
+
await runPreChecks(PRE_CHECK_CREATE);
|
|
3874
3913
|
const count = Number(options.number);
|
|
3875
3914
|
if (!Number.isInteger(count) || count <= 0) {
|
|
3876
3915
|
throw new ClawtError(
|
|
@@ -3904,7 +3943,7 @@ function registerRemoveCommand(program2) {
|
|
|
3904
3943
|
});
|
|
3905
3944
|
}
|
|
3906
3945
|
async function handleRemove(options) {
|
|
3907
|
-
runPreChecks({
|
|
3946
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true });
|
|
3908
3947
|
const projectName = getProjectName();
|
|
3909
3948
|
logger.info(`remove \u547D\u4EE4\u6267\u884C\uFF0C\u9879\u76EE: ${projectName}`);
|
|
3910
3949
|
const allWorktrees = getProjectWorktrees();
|
|
@@ -4010,11 +4049,8 @@ function handleDryRunFromFile(options) {
|
|
|
4010
4049
|
printDryRunPreview(branchNames, tasks, concurrency);
|
|
4011
4050
|
}
|
|
4012
4051
|
async function handleRun(options) {
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
await guardMainWorkBranch();
|
|
4016
|
-
await ensureOnMainWorkBranch();
|
|
4017
|
-
}
|
|
4052
|
+
const preChecks = options.dryRun ? PRE_CHECK_DRY_RUN : PRE_CHECK_RUN;
|
|
4053
|
+
await runPreChecks(preChecks);
|
|
4018
4054
|
if (options.file && options.tasks) {
|
|
4019
4055
|
throw new ClawtError(MESSAGES.FILE_AND_TASKS_CONFLICT);
|
|
4020
4056
|
}
|
|
@@ -4075,8 +4111,7 @@ function registerResumeCommand(program2) {
|
|
|
4075
4111
|
});
|
|
4076
4112
|
}
|
|
4077
4113
|
async function handleResume(options) {
|
|
4078
|
-
runPreChecks(
|
|
4079
|
-
validateClaudeCodeInstalled();
|
|
4114
|
+
await runPreChecks(PRE_CHECK_RESUME);
|
|
4080
4115
|
logger.info(`resume \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F\u8FC7\u6EE4: ${options.branch ?? "(\u65E0)"}`);
|
|
4081
4116
|
const worktrees = getProjectWorktrees();
|
|
4082
4117
|
let targetWorktrees;
|
|
@@ -4176,8 +4211,7 @@ async function executeSyncForBranch(targetWorktreePath, branch) {
|
|
|
4176
4211
|
return { success: true, hasConflict: false };
|
|
4177
4212
|
}
|
|
4178
4213
|
async function handleSync(options) {
|
|
4179
|
-
runPreChecks(
|
|
4180
|
-
await guardMainWorkBranch();
|
|
4214
|
+
await runPreChecks(PRE_CHECK_SYNC);
|
|
4181
4215
|
logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
|
|
4182
4216
|
const worktrees = getProjectWorktrees();
|
|
4183
4217
|
const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
|
|
@@ -4210,7 +4244,7 @@ async function handlePatchApplyFailure(targetWorktreePath, branchName) {
|
|
|
4210
4244
|
const syncResult = await executeSyncForBranch(targetWorktreePath, branchName);
|
|
4211
4245
|
}
|
|
4212
4246
|
async function handleValidateClean(options) {
|
|
4213
|
-
runPreChecks({
|
|
4247
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true });
|
|
4214
4248
|
const projectName = getProjectName();
|
|
4215
4249
|
const mainWorktreePath = getGitTopLevel();
|
|
4216
4250
|
const worktrees = getProjectWorktrees();
|
|
@@ -4295,7 +4329,7 @@ async function handleValidate(options) {
|
|
|
4295
4329
|
await handleValidateClean(options);
|
|
4296
4330
|
return;
|
|
4297
4331
|
}
|
|
4298
|
-
runPreChecks({
|
|
4332
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true });
|
|
4299
4333
|
const projectName = getProjectName();
|
|
4300
4334
|
const mainWorktreePath = getGitTopLevel();
|
|
4301
4335
|
const worktrees = getProjectWorktrees();
|
|
@@ -4360,7 +4394,7 @@ function computeIncrementalPatch(snapshotTreeHash, mainWorktreePath) {
|
|
|
4360
4394
|
return { patch, currentTreeHash };
|
|
4361
4395
|
}
|
|
4362
4396
|
async function handleCoverValidate() {
|
|
4363
|
-
runPreChecks({
|
|
4397
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true });
|
|
4364
4398
|
const projectName = getProjectName();
|
|
4365
4399
|
const mainWorktreePath = getGitTopLevel();
|
|
4366
4400
|
const currentBranch = getCurrentBranch(mainWorktreePath);
|
|
@@ -4439,11 +4473,8 @@ function cleanupWorktreeAndBranch(worktreePath, branchName) {
|
|
|
4439
4473
|
printSuccess(MESSAGES.WORKTREE_CLEANED(branchName));
|
|
4440
4474
|
}
|
|
4441
4475
|
async function handleMerge(options) {
|
|
4442
|
-
runPreChecks(
|
|
4443
|
-
await guardMainWorkBranch();
|
|
4444
|
-
await guardMainWorkBranch();
|
|
4476
|
+
await runPreChecks(PRE_CHECK_MERGE);
|
|
4445
4477
|
const mainWorktreePath = getGitTopLevel();
|
|
4446
|
-
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
4447
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)"}`);
|
|
4448
4479
|
const worktrees = getProjectWorktrees();
|
|
4449
4480
|
const worktree = await resolveTargetWorktree(worktrees, MERGE_RESOLVE_MESSAGES, options.branch);
|
|
@@ -4602,7 +4633,7 @@ function registerResetCommand(program2) {
|
|
|
4602
4633
|
});
|
|
4603
4634
|
}
|
|
4604
4635
|
async function handleReset() {
|
|
4605
|
-
runPreChecks({
|
|
4636
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true });
|
|
4606
4637
|
const mainWorktreePath = getGitTopLevel();
|
|
4607
4638
|
logger.info("reset \u547D\u4EE4\u6267\u884C");
|
|
4608
4639
|
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
@@ -4632,7 +4663,7 @@ function registerStatusCommand(program2) {
|
|
|
4632
4663
|
});
|
|
4633
4664
|
}
|
|
4634
4665
|
async function handleStatus(options) {
|
|
4635
|
-
runPreChecks({
|
|
4666
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true });
|
|
4636
4667
|
if (options.interactive) {
|
|
4637
4668
|
const panel = new InteractivePanel(collectStatus);
|
|
4638
4669
|
await panel.start();
|
|
@@ -4674,7 +4705,7 @@ function collectWorktreeDetailedStatus(worktree, projectName) {
|
|
|
4674
4705
|
const changeStatus = detectChangeStatus(worktree);
|
|
4675
4706
|
const { commitsAhead, commitsBehind } = countCommitDivergence(worktree.branch);
|
|
4676
4707
|
const { insertions, deletions } = countDiffStat(worktree.path);
|
|
4677
|
-
const createdAt =
|
|
4708
|
+
const createdAt = getWorktreeCreatedTime(worktree.path);
|
|
4678
4709
|
return {
|
|
4679
4710
|
path: worktree.path,
|
|
4680
4711
|
branch: worktree.branch,
|
|
@@ -4720,13 +4751,6 @@ function countDiffStat(worktreePath) {
|
|
|
4720
4751
|
return { insertions: 0, deletions: 0 };
|
|
4721
4752
|
}
|
|
4722
4753
|
}
|
|
4723
|
-
function resolveBranchCreatedAt(branchName) {
|
|
4724
|
-
try {
|
|
4725
|
-
return getBranchCreatedAt(branchName);
|
|
4726
|
-
} catch {
|
|
4727
|
-
return null;
|
|
4728
|
-
}
|
|
4729
|
-
}
|
|
4730
4754
|
function resolveSnapshotTime(projectName, branchName) {
|
|
4731
4755
|
try {
|
|
4732
4756
|
return getSnapshotModifiedTime(projectName, branchName);
|
|
@@ -5312,7 +5336,7 @@ function registerInitCommand(program2) {
|
|
|
5312
5336
|
);
|
|
5313
5337
|
}
|
|
5314
5338
|
async function handleInitShow() {
|
|
5315
|
-
runPreChecks({
|
|
5339
|
+
await runPreChecks({ requireMainWorktree: true, requireProjectConfig: true });
|
|
5316
5340
|
const config2 = requireProjectConfig();
|
|
5317
5341
|
logger.info("init show \u547D\u4EE4\u6267\u884C\uFF0C\u8FDB\u5165\u4EA4\u4E92\u5F0F\u9879\u76EE\u914D\u7F6E");
|
|
5318
5342
|
const { key, newValue } = await interactiveConfigEditor(
|
|
@@ -5325,7 +5349,7 @@ async function handleInitShow() {
|
|
|
5325
5349
|
printSuccess(MESSAGES.INIT_SET_SUCCESS(key, String(newValue)));
|
|
5326
5350
|
}
|
|
5327
5351
|
async function handleInit(options) {
|
|
5328
|
-
runPreChecks({
|
|
5352
|
+
await runPreChecks({ requireMainWorktree: true });
|
|
5329
5353
|
const existingConfig = loadProjectConfig();
|
|
5330
5354
|
if (!options.branch) {
|
|
5331
5355
|
validateHeadExists();
|
|
@@ -5347,7 +5371,7 @@ function registerHomeCommand(program2) {
|
|
|
5347
5371
|
});
|
|
5348
5372
|
}
|
|
5349
5373
|
async function handleHome() {
|
|
5350
|
-
runPreChecks({
|
|
5374
|
+
await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true, requireMainBranchExists: true });
|
|
5351
5375
|
const mainBranch = getMainWorkBranch();
|
|
5352
5376
|
const currentBranch = getCurrentBranch();
|
|
5353
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,15 +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
10
|
getValidateBranchName,
|
|
11
11
|
printSuccess,
|
|
12
12
|
printInfo,
|
|
13
13
|
printSeparator,
|
|
14
|
-
guardMainWorkBranch,
|
|
15
14
|
} from '../utils/index.js';
|
|
16
15
|
|
|
17
16
|
/**
|
|
@@ -34,11 +33,7 @@ export function registerCreateCommand(program: Command): void {
|
|
|
34
33
|
* @param {CreateOptions} options - 命令选项
|
|
35
34
|
*/
|
|
36
35
|
async function handleCreate(options: CreateOptions): Promise<void> {
|
|
37
|
-
runPreChecks(
|
|
38
|
-
|
|
39
|
-
await guardMainWorkBranch();
|
|
40
|
-
|
|
41
|
-
await ensureOnMainWorkBranch();
|
|
36
|
+
await runPreChecks(PRE_CHECK_CREATE);
|
|
42
37
|
|
|
43
38
|
const count = Number(options.number);
|
|
44
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();
|
package/src/commands/run.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { logger } from '../logger/index.js';
|
|
|
3
3
|
import { ClawtError } from '../errors/index.js';
|
|
4
4
|
import { MESSAGES } from '../constants/index.js';
|
|
5
5
|
import type { RunOptions, WorktreeInfo } from '../types/index.js';
|
|
6
|
+
import { PRE_CHECK_DRY_RUN, PRE_CHECK_RUN } from '../constants/index.js';
|
|
6
7
|
import {
|
|
7
8
|
runPreChecks,
|
|
8
9
|
validateClaudeCodeInstalled,
|
|
@@ -19,8 +20,6 @@ import {
|
|
|
19
20
|
parseTasksFromOptions,
|
|
20
21
|
executeBatchTasks,
|
|
21
22
|
printDryRunPreview,
|
|
22
|
-
ensureOnMainWorkBranch,
|
|
23
|
-
guardMainWorkBranch,
|
|
24
23
|
} from '../utils/index.js';
|
|
25
24
|
|
|
26
25
|
/**
|
|
@@ -120,13 +119,8 @@ function handleDryRunFromFile(options: RunOptions): void {
|
|
|
120
119
|
* @param {RunOptions} options - 命令选项
|
|
121
120
|
*/
|
|
122
121
|
async function handleRun(options: RunOptions): Promise<void> {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// dry-run 模式跳过项目配置前置校验
|
|
126
|
-
if (!options.dryRun) {
|
|
127
|
-
await guardMainWorkBranch();
|
|
128
|
-
await ensureOnMainWorkBranch();
|
|
129
|
-
}
|
|
122
|
+
const preChecks = options.dryRun ? PRE_CHECK_DRY_RUN : PRE_CHECK_RUN;
|
|
123
|
+
await runPreChecks(preChecks);
|
|
130
124
|
|
|
131
125
|
// 互斥校验:--file 和 --tasks 不能同时使用
|
|
132
126
|
if (options.file && options.tasks) {
|