clawt 2.20.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/index.js +395 -133
- package/dist/postinstall.js +35 -6
- package/docs/alias.md +108 -0
- package/docs/completion.md +55 -0
- package/docs/config-file.md +43 -0
- package/docs/config.md +91 -0
- package/docs/create.md +85 -0
- package/docs/init.md +65 -0
- package/docs/list.md +67 -0
- package/docs/log.md +67 -0
- package/docs/merge.md +137 -0
- package/docs/notification.md +94 -0
- package/docs/projects.md +135 -0
- package/docs/remove.md +79 -0
- package/docs/reset.md +35 -0
- package/docs/resume.md +99 -0
- package/docs/run.md +146 -0
- package/docs/spec.md +156 -1905
- package/docs/status.md +155 -0
- package/docs/sync.md +114 -0
- package/docs/update-check.md +95 -0
- package/docs/validate.md +368 -0
- package/package.json +1 -1
- package/src/commands/alias.ts +1 -1
- package/src/commands/create.ts +10 -5
- package/src/commands/init.ts +75 -0
- package/src/commands/list.ts +1 -1
- package/src/commands/merge.ts +11 -4
- package/src/commands/remove.ts +10 -3
- package/src/commands/reset.ts +3 -0
- package/src/commands/resume.ts +1 -1
- package/src/commands/run.ts +9 -3
- package/src/commands/status.ts +1 -1
- package/src/commands/sync.ts +18 -6
- package/src/commands/validate.ts +46 -52
- package/src/constants/branch.ts +3 -0
- package/src/constants/config.ts +1 -1
- package/src/constants/index.ts +2 -2
- package/src/constants/messages/completion.ts +1 -1
- package/src/constants/messages/create.ts +3 -0
- package/src/constants/messages/index.ts +2 -0
- package/src/constants/messages/init.ts +18 -0
- package/src/constants/messages/remove.ts +2 -0
- package/src/constants/messages/sync.ts +3 -0
- package/src/constants/messages/validate.ts +6 -0
- package/src/constants/paths.ts +3 -0
- package/src/index.ts +2 -0
- package/src/types/command.ts +7 -1
- package/src/types/index.ts +2 -1
- package/src/types/projectConfig.ts +5 -0
- package/src/utils/config.ts +2 -1
- package/src/utils/git.ts +18 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/json.ts +67 -0
- package/src/utils/project-config.ts +77 -0
- package/src/utils/validate-branch.ts +166 -0
- package/src/utils/worktree.ts +6 -2
- package/tests/unit/commands/create.test.ts +20 -16
- package/tests/unit/commands/init.test.ts +146 -0
- package/tests/unit/commands/merge.test.ts +7 -1
- package/tests/unit/commands/remove.test.ts +4 -0
- package/tests/unit/commands/reset.test.ts +2 -0
- package/tests/unit/commands/run.test.ts +2 -0
- package/tests/unit/commands/sync.test.ts +6 -0
- package/tests/unit/commands/validate.test.ts +13 -0
- package/tests/unit/utils/config.test.ts +2 -2
- package/tests/unit/utils/project-config.test.ts +136 -0
- package/tests/unit/utils/update-checker.test.ts +28 -7
- package/tests/unit/utils/validate-branch.test.ts +272 -0
- package/tests/unit/utils/worktree.test.ts +6 -0
package/dist/index.js
CHANGED
|
@@ -16,9 +16,11 @@ var WORKTREES_DIR = join(CLAWT_HOME, "worktrees");
|
|
|
16
16
|
var VALIDATE_SNAPSHOTS_DIR = join(CLAWT_HOME, "validate-snapshots");
|
|
17
17
|
var CLAUDE_PROJECTS_DIR = join(homedir(), ".claude", "projects");
|
|
18
18
|
var UPDATE_CHECK_PATH = join(CLAWT_HOME, "update-check.json");
|
|
19
|
+
var PROJECTS_CONFIG_DIR = join(CLAWT_HOME, "projects");
|
|
19
20
|
|
|
20
21
|
// src/constants/branch.ts
|
|
21
22
|
var INVALID_BRANCH_CHARS = /[\/\\.\s~:*?[\]^]+/g;
|
|
23
|
+
var VALIDATE_BRANCH_PREFIX = "clawt-validate-";
|
|
22
24
|
|
|
23
25
|
// src/constants/messages/common.ts
|
|
24
26
|
var COMMON_MESSAGES = {
|
|
@@ -121,7 +123,9 @@ var RUN_MESSAGES = {
|
|
|
121
123
|
// src/constants/messages/create.ts
|
|
122
124
|
var CREATE_MESSAGES = {
|
|
123
125
|
/** 创建数量参数无效 */
|
|
124
|
-
INVALID_COUNT: (value) => `\u65E0\u6548\u7684\u521B\u5EFA\u6570\u91CF: "${value}"\uFF0C\u8BF7\u8F93\u5165\u6B63\u6574\u6570
|
|
126
|
+
INVALID_COUNT: (value) => `\u65E0\u6548\u7684\u521B\u5EFA\u6570\u91CF: "${value}"\uFF0C\u8BF7\u8F93\u5165\u6B63\u6574\u6570`,
|
|
127
|
+
/** 当前不在主工作分支上的警告 */
|
|
128
|
+
CREATE_WARN_NOT_ON_MAIN_BRANCH: (mainBranch, currentBranch) => `\u5F53\u524D\u4E0D\u5728\u4E3B\u5DE5\u4F5C\u5206\u652F ${mainBranch} \u4E0A\uFF08\u5F53\u524D: ${currentBranch}\uFF09`
|
|
125
129
|
};
|
|
126
130
|
|
|
127
131
|
// src/constants/messages/merge.ts
|
|
@@ -218,7 +222,12 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
|
218
222
|
/** 自动 sync 开始提示 */
|
|
219
223
|
VALIDATE_AUTO_SYNC_START: (branch) => `\u6B63\u5728\u81EA\u52A8\u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch} ...`,
|
|
220
224
|
/** 用户拒绝自动 sync */
|
|
221
|
-
VALIDATE_AUTO_SYNC_DECLINED: (branch) => `\u8BF7\u624B\u52A8\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5
|
|
225
|
+
VALIDATE_AUTO_SYNC_DECLINED: (branch) => `\u8BF7\u624B\u52A8\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`,
|
|
226
|
+
/** 验证分支不存在 */
|
|
227
|
+
VALIDATE_BRANCH_NOT_FOUND: (validateBranch, branch) => `\u9A8C\u8BC1\u5206\u652F ${validateBranch} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C clawt create \u6216 clawt run \u521B\u5EFA\u5206\u652F ${branch}`,
|
|
228
|
+
/** validate 成功(含验证分支信息) */
|
|
229
|
+
VALIDATE_SUCCESS_WITH_BRANCH: (branch, validateBranch) => `\u2713 \u5DF2\u5207\u6362\u5230\u9A8C\u8BC1\u5206\u652F ${validateBranch} \u5E76\u5E94\u7528\u5206\u652F ${branch} \u7684\u53D8\u66F4
|
|
230
|
+
\u53EF\u4EE5\u5F00\u59CB\u9A8C\u8BC1\u4E86`
|
|
222
231
|
};
|
|
223
232
|
|
|
224
233
|
// src/constants/messages/sync.ts
|
|
@@ -243,7 +252,9 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
|
243
252
|
/** sync 交互选择提示 */
|
|
244
253
|
SYNC_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u540C\u6B65\u7684\u5206\u652F",
|
|
245
254
|
/** sync 模糊匹配到多个结果提示 */
|
|
246
|
-
SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A
|
|
255
|
+
SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
|
|
256
|
+
/** sync 后验证分支已重建提示 */
|
|
257
|
+
SYNC_VALIDATE_BRANCH_REBUILT: (validateBranch) => `\u9A8C\u8BC1\u5206\u652F ${validateBranch} \u5DF2\u91CD\u5EFA`
|
|
247
258
|
};
|
|
248
259
|
|
|
249
260
|
// src/constants/messages/resume.ts
|
|
@@ -284,7 +295,9 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
|
284
295
|
REMOVE_PARTIAL_FAILURE: (failures) => `\u4EE5\u4E0B worktree \u79FB\u9664\u5931\u8D25\uFF1A
|
|
285
296
|
${failures.map((f) => ` \u2717 ${f.path}: ${f.error}`).join("\n")}`,
|
|
286
297
|
/** 用户选择保留本地分支 */
|
|
287
|
-
REMOVE_BRANCHES_KEPT: "\u5DF2\u4FDD\u7559\u672C\u5730\u5206\u652F\uFF0C\u53EF\u7A0D\u540E\u4F7F\u7528 git branch -D <\u5206\u652F\u540D> \u624B\u52A8\u5220\u9664"
|
|
298
|
+
REMOVE_BRANCHES_KEPT: "\u5DF2\u4FDD\u7559\u672C\u5730\u5206\u652F\uFF0C\u53EF\u7A0D\u540E\u4F7F\u7528 git branch -D <\u5206\u652F\u540D> \u624B\u52A8\u5220\u9664",
|
|
299
|
+
/** 确认删除本地分支和验证分支 */
|
|
300
|
+
REMOVE_CONFIRM_DELETE_BRANCHES: "\u662F\u5426\u540C\u65F6\u5220\u9664\u5BF9\u5E94\u7684\u672C\u5730\u5206\u652F\u548C\u9A8C\u8BC1\u5206\u652F\uFF1F"
|
|
288
301
|
};
|
|
289
302
|
|
|
290
303
|
// src/constants/messages/reset.ts
|
|
@@ -402,7 +415,7 @@ var PROJECTS_MESSAGES = {
|
|
|
402
415
|
// src/constants/messages/completion.ts
|
|
403
416
|
var COMPLETION_MESSAGES = {
|
|
404
417
|
/** completion 命令的主描述 */
|
|
405
|
-
COMPLETION_COMMAND_DESC: "\
|
|
418
|
+
COMPLETION_COMMAND_DESC: "\u4E3A\u7EC8\u7AEF\u63D0\u4F9B shell \u81EA\u52A8\u8865\u5168\u529F\u80FD\uFF08bash/zsh\uFF09",
|
|
406
419
|
/** bash 子命令描述 */
|
|
407
420
|
COMPLETION_BASH_DESC: "\u8F93\u51FA bash \u81EA\u52A8\u8865\u5168\u811A\u672C",
|
|
408
421
|
/** zsh 子命令描述 */
|
|
@@ -434,6 +447,22 @@ var UPDATE_MESSAGES = {
|
|
|
434
447
|
UPDATE_HINT: (command) => `\u6267\u884C ${command} \u8FDB\u884C\u66F4\u65B0`
|
|
435
448
|
};
|
|
436
449
|
|
|
450
|
+
// src/constants/messages/init.ts
|
|
451
|
+
var INIT_MESSAGES = {
|
|
452
|
+
/** init 成功 */
|
|
453
|
+
INIT_SUCCESS: (branch) => `\u2713 \u9879\u76EE\u521D\u59CB\u5316\u6210\u529F\uFF0C\u4E3B\u5DE5\u4F5C\u5206\u652F\u8BBE\u7F6E\u4E3A: ${branch}`,
|
|
454
|
+
/** init 更新 */
|
|
455
|
+
INIT_UPDATED: (oldBranch, newBranch) => `\u2713 \u5DF2\u5C06\u4E3B\u5DE5\u4F5C\u5206\u652F\u4ECE ${oldBranch} \u66F4\u65B0\u4E3A ${newBranch}`,
|
|
456
|
+
/** init show 查看当前项目完整配置(JSON 格式) */
|
|
457
|
+
INIT_SHOW: (configJson) => `${configJson}`,
|
|
458
|
+
/** init 未初始化 */
|
|
459
|
+
INIT_NOT_INITIALIZED: "\u9879\u76EE\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F",
|
|
460
|
+
/** 项目未初始化(requireProjectConfig 使用) */
|
|
461
|
+
PROJECT_NOT_INITIALIZED: "\u9879\u76EE\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F",
|
|
462
|
+
/** 项目配置缺少 clawtMainWorkBranch 字段 */
|
|
463
|
+
PROJECT_CONFIG_MISSING_BRANCH: "\u9879\u76EE\u914D\u7F6E\u7F3A\u5C11\u4E3B\u5DE5\u4F5C\u5206\u652F\u4FE1\u606F\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C clawt init \u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F"
|
|
464
|
+
};
|
|
465
|
+
|
|
437
466
|
// src/constants/messages/index.ts
|
|
438
467
|
var MESSAGES = {
|
|
439
468
|
...COMMON_MESSAGES,
|
|
@@ -449,7 +478,8 @@ var MESSAGES = {
|
|
|
449
478
|
...STATUS_MESSAGES,
|
|
450
479
|
...ALIAS_MESSAGES,
|
|
451
480
|
...PROJECTS_MESSAGES,
|
|
452
|
-
...COMPLETION_MESSAGES
|
|
481
|
+
...COMPLETION_MESSAGES,
|
|
482
|
+
...INIT_MESSAGES
|
|
453
483
|
};
|
|
454
484
|
|
|
455
485
|
// src/constants/exitCodes.ts
|
|
@@ -902,6 +932,12 @@ function getBranchCreatedAt(branchName, cwd) {
|
|
|
902
932
|
return null;
|
|
903
933
|
}
|
|
904
934
|
}
|
|
935
|
+
function gitCheckout(branchName, cwd) {
|
|
936
|
+
execCommand(`git checkout ${branchName}`, { cwd });
|
|
937
|
+
}
|
|
938
|
+
function createBranch(branchName, cwd) {
|
|
939
|
+
execCommand(`git branch ${branchName}`, { cwd });
|
|
940
|
+
}
|
|
905
941
|
|
|
906
942
|
// src/utils/formatter.ts
|
|
907
943
|
import chalk3 from "chalk";
|
|
@@ -1076,8 +1112,8 @@ function validateClaudeCodeInstalled() {
|
|
|
1076
1112
|
}
|
|
1077
1113
|
|
|
1078
1114
|
// src/utils/worktree.ts
|
|
1079
|
-
import { join as
|
|
1080
|
-
import { existsSync as
|
|
1115
|
+
import { join as join4 } from "path";
|
|
1116
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
|
|
1081
1117
|
|
|
1082
1118
|
// src/utils/fs.ts
|
|
1083
1119
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmdirSync, statSync } from "fs";
|
|
@@ -1118,10 +1154,152 @@ function calculateDirSize(dirPath) {
|
|
|
1118
1154
|
return totalSize;
|
|
1119
1155
|
}
|
|
1120
1156
|
|
|
1157
|
+
// src/utils/validate-branch.ts
|
|
1158
|
+
import Enquirer from "enquirer";
|
|
1159
|
+
|
|
1160
|
+
// src/utils/project-config.ts
|
|
1161
|
+
import { existsSync as existsSync3, readFileSync, writeFileSync } from "fs";
|
|
1162
|
+
import { join as join3 } from "path";
|
|
1163
|
+
function getProjectConfigPath(projectName) {
|
|
1164
|
+
return join3(PROJECTS_CONFIG_DIR, projectName, "config.json");
|
|
1165
|
+
}
|
|
1166
|
+
function loadProjectConfig() {
|
|
1167
|
+
const projectName = getProjectName();
|
|
1168
|
+
const configPath = getProjectConfigPath(projectName);
|
|
1169
|
+
if (!existsSync3(configPath)) {
|
|
1170
|
+
return null;
|
|
1171
|
+
}
|
|
1172
|
+
try {
|
|
1173
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
1174
|
+
return JSON.parse(raw);
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
logger.warn(`\u9879\u76EE\u914D\u7F6E\u6587\u4EF6\u89E3\u6790\u5931\u8D25: ${error}`);
|
|
1177
|
+
return null;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
function saveProjectConfig(config2) {
|
|
1181
|
+
const projectName = getProjectName();
|
|
1182
|
+
const configPath = getProjectConfigPath(projectName);
|
|
1183
|
+
const projectDir = join3(PROJECTS_CONFIG_DIR, projectName);
|
|
1184
|
+
ensureDir(projectDir);
|
|
1185
|
+
writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf-8");
|
|
1186
|
+
logger.info(`\u9879\u76EE\u914D\u7F6E\u5DF2\u4FDD\u5B58: ${configPath}`);
|
|
1187
|
+
}
|
|
1188
|
+
function requireProjectConfig() {
|
|
1189
|
+
const config2 = loadProjectConfig();
|
|
1190
|
+
if (!config2) {
|
|
1191
|
+
throw new ClawtError(MESSAGES.PROJECT_NOT_INITIALIZED);
|
|
1192
|
+
}
|
|
1193
|
+
if (!config2.clawtMainWorkBranch) {
|
|
1194
|
+
throw new ClawtError(MESSAGES.PROJECT_CONFIG_MISSING_BRANCH);
|
|
1195
|
+
}
|
|
1196
|
+
return config2;
|
|
1197
|
+
}
|
|
1198
|
+
function getMainWorkBranch() {
|
|
1199
|
+
const config2 = requireProjectConfig();
|
|
1200
|
+
return config2.clawtMainWorkBranch;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/utils/validate-branch.ts
|
|
1204
|
+
function getValidateBranchName(branchName) {
|
|
1205
|
+
return `${VALIDATE_BRANCH_PREFIX}${branchName}`;
|
|
1206
|
+
}
|
|
1207
|
+
function createValidateBranch(branchName, cwd) {
|
|
1208
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
1209
|
+
if (checkBranchExists(validateBranchName, cwd)) {
|
|
1210
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA: ${validateBranchName}`);
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
createBranch(validateBranchName, cwd);
|
|
1214
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u521B\u5EFA: ${validateBranchName}`);
|
|
1215
|
+
}
|
|
1216
|
+
function deleteValidateBranch(branchName, cwd) {
|
|
1217
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
1218
|
+
if (!checkBranchExists(validateBranchName, cwd)) {
|
|
1219
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u5220\u9664: ${validateBranchName}`);
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
deleteBranch(validateBranchName, cwd);
|
|
1223
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u5220\u9664: ${validateBranchName}`);
|
|
1224
|
+
}
|
|
1225
|
+
async function rebuildValidateBranch(branchName, cwd) {
|
|
1226
|
+
const mainBranch = getMainWorkBranch();
|
|
1227
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
1228
|
+
if (currentBranch === mainBranch) {
|
|
1229
|
+
} else if (currentBranch.startsWith(VALIDATE_BRANCH_PREFIX)) {
|
|
1230
|
+
gitResetHard(cwd);
|
|
1231
|
+
gitCleanForce(cwd);
|
|
1232
|
+
gitCheckout(mainBranch, cwd);
|
|
1233
|
+
} else {
|
|
1234
|
+
if (!isWorkingDirClean(cwd)) {
|
|
1235
|
+
await handleDirtyWorkingDir(cwd);
|
|
1236
|
+
}
|
|
1237
|
+
gitCheckout(mainBranch, cwd);
|
|
1238
|
+
}
|
|
1239
|
+
deleteValidateBranch(branchName, cwd);
|
|
1240
|
+
createValidateBranch(branchName, cwd);
|
|
1241
|
+
logger.info(`\u9A8C\u8BC1\u5206\u652F\u5DF2\u91CD\u5EFA: ${getValidateBranchName(branchName)}`);
|
|
1242
|
+
}
|
|
1243
|
+
async function handleDirtyWorkingDir(cwd) {
|
|
1244
|
+
printWarning("\u5F53\u524D\u5206\u652F\u6709\u672A\u63D0\u4EA4\u7684\u66F4\u6539\uFF0C\u8BF7\u9009\u62E9\u5904\u7406\u65B9\u5F0F\uFF1A\n");
|
|
1245
|
+
const choice = await new Enquirer.Select({
|
|
1246
|
+
message: "\u9009\u62E9\u5904\u7406\u65B9\u5F0F",
|
|
1247
|
+
choices: [
|
|
1248
|
+
{
|
|
1249
|
+
name: "reset",
|
|
1250
|
+
message: "reset - \u4E22\u5F03\u6240\u6709\u66F4\u6539 (git reset --hard HEAD && git clean -fd)"
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
name: "stash",
|
|
1254
|
+
message: "stash - \u6682\u5B58\u66F4\u6539 (git add . && git stash)"
|
|
1255
|
+
},
|
|
1256
|
+
{
|
|
1257
|
+
name: "exit",
|
|
1258
|
+
message: "exit - \u9000\u51FA\uFF0C\u624B\u52A8\u5904\u7406"
|
|
1259
|
+
}
|
|
1260
|
+
],
|
|
1261
|
+
initial: 0
|
|
1262
|
+
}).run();
|
|
1263
|
+
if (choice === "exit") {
|
|
1264
|
+
throw new ClawtError("\u7528\u6237\u9009\u62E9\u9000\u51FA\uFF0C\u8BF7\u624B\u52A8\u5904\u7406\u5DE5\u4F5C\u533A\u66F4\u6539\u540E\u91CD\u8BD5");
|
|
1265
|
+
}
|
|
1266
|
+
if (choice === "reset") {
|
|
1267
|
+
gitResetHard(cwd);
|
|
1268
|
+
gitCleanForce(cwd);
|
|
1269
|
+
} else if (choice === "stash") {
|
|
1270
|
+
gitAddAll(cwd);
|
|
1271
|
+
gitStashPush("clawt:auto-stash", cwd);
|
|
1272
|
+
}
|
|
1273
|
+
if (!isWorkingDirClean(cwd)) {
|
|
1274
|
+
throw new ClawtError("\u5DE5\u4F5C\u533A\u4ECD\u7136\u4E0D\u5E72\u51C0\uFF0C\u8BF7\u624B\u52A8\u5904\u7406");
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
async function ensureOnMainWorkBranch(cwd) {
|
|
1278
|
+
const mainBranch = getMainWorkBranch();
|
|
1279
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
1280
|
+
if (currentBranch === mainBranch) {
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
if (currentBranch.startsWith(VALIDATE_BRANCH_PREFIX)) {
|
|
1284
|
+
logger.info(`\u5F53\u524D\u5728\u9A8C\u8BC1\u5206\u652F ${currentBranch} \u4E0A\uFF0C\u81EA\u52A8\u5207\u56DE\u4E3B\u5DE5\u4F5C\u5206\u652F ${mainBranch}`);
|
|
1285
|
+
if (!isWorkingDirClean(cwd)) {
|
|
1286
|
+
gitResetHard(cwd);
|
|
1287
|
+
gitCleanForce(cwd);
|
|
1288
|
+
}
|
|
1289
|
+
gitCheckout(mainBranch, cwd);
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
logger.info(`\u5F53\u524D\u5728\u5206\u652F ${currentBranch} \u4E0A\uFF0C\u9700\u5207\u6362\u5230\u4E3B\u5DE5\u4F5C\u5206\u652F ${mainBranch}`);
|
|
1293
|
+
if (!isWorkingDirClean(cwd)) {
|
|
1294
|
+
await handleDirtyWorkingDir(cwd);
|
|
1295
|
+
}
|
|
1296
|
+
gitCheckout(mainBranch, cwd);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1121
1299
|
// src/utils/worktree.ts
|
|
1122
1300
|
function getProjectWorktreeDir() {
|
|
1123
1301
|
const projectName = getProjectName();
|
|
1124
|
-
return
|
|
1302
|
+
return join4(WORKTREES_DIR, projectName);
|
|
1125
1303
|
}
|
|
1126
1304
|
function createWorktrees(branchName, count) {
|
|
1127
1305
|
const sanitized = sanitizeBranchName(branchName);
|
|
@@ -1131,8 +1309,9 @@ function createWorktrees(branchName, count) {
|
|
|
1131
1309
|
ensureDir(projectDir);
|
|
1132
1310
|
const results = [];
|
|
1133
1311
|
for (const name of branchNames) {
|
|
1134
|
-
const worktreePath =
|
|
1312
|
+
const worktreePath = join4(projectDir, name);
|
|
1135
1313
|
createWorktree(name, worktreePath);
|
|
1314
|
+
createValidateBranch(name);
|
|
1136
1315
|
results.push({ path: worktreePath, branch: name });
|
|
1137
1316
|
logger.info(`worktree \u521B\u5EFA\u5B8C\u6210: ${worktreePath} (\u5206\u652F: ${name})`);
|
|
1138
1317
|
}
|
|
@@ -1144,8 +1323,9 @@ function createWorktreesByBranches(branchNames) {
|
|
|
1144
1323
|
ensureDir(projectDir);
|
|
1145
1324
|
const results = [];
|
|
1146
1325
|
for (const name of branchNames) {
|
|
1147
|
-
const worktreePath =
|
|
1326
|
+
const worktreePath = join4(projectDir, name);
|
|
1148
1327
|
createWorktree(name, worktreePath);
|
|
1328
|
+
createValidateBranch(name);
|
|
1149
1329
|
results.push({ path: worktreePath, branch: name });
|
|
1150
1330
|
logger.info(`worktree \u521B\u5EFA\u5B8C\u6210: ${worktreePath} (\u5206\u652F: ${name})`);
|
|
1151
1331
|
}
|
|
@@ -1153,7 +1333,7 @@ function createWorktreesByBranches(branchNames) {
|
|
|
1153
1333
|
}
|
|
1154
1334
|
function getProjectWorktrees() {
|
|
1155
1335
|
const projectDir = getProjectWorktreeDir();
|
|
1156
|
-
if (!
|
|
1336
|
+
if (!existsSync4(projectDir)) {
|
|
1157
1337
|
return [];
|
|
1158
1338
|
}
|
|
1159
1339
|
const worktreeListOutput = gitWorktreeList();
|
|
@@ -1166,7 +1346,7 @@ function getProjectWorktrees() {
|
|
|
1166
1346
|
if (!entry.isDirectory()) {
|
|
1167
1347
|
continue;
|
|
1168
1348
|
}
|
|
1169
|
-
const fullPath =
|
|
1349
|
+
const fullPath = join4(projectDir, entry.name);
|
|
1170
1350
|
if (registeredPaths.has(fullPath)) {
|
|
1171
1351
|
worktrees.push({
|
|
1172
1352
|
path: fullPath,
|
|
@@ -1181,6 +1361,7 @@ function cleanupWorktrees(worktrees) {
|
|
|
1181
1361
|
try {
|
|
1182
1362
|
removeWorktreeByPath(wt.path);
|
|
1183
1363
|
deleteBranch(wt.branch);
|
|
1364
|
+
deleteValidateBranch(wt.branch);
|
|
1184
1365
|
logger.info(`\u5DF2\u6E05\u7406 worktree \u548C\u5206\u652F: ${wt.branch}`);
|
|
1185
1366
|
} catch (error) {
|
|
1186
1367
|
logger.error(`\u6E05\u7406 worktree \u5931\u8D25: ${wt.path} - ${error}`);
|
|
@@ -1203,13 +1384,13 @@ function getWorktreeStatus(worktree) {
|
|
|
1203
1384
|
}
|
|
1204
1385
|
|
|
1205
1386
|
// src/utils/config.ts
|
|
1206
|
-
import { existsSync as
|
|
1387
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1207
1388
|
function loadConfig() {
|
|
1208
|
-
if (!
|
|
1389
|
+
if (!existsSync5(CONFIG_PATH)) {
|
|
1209
1390
|
return { ...DEFAULT_CONFIG };
|
|
1210
1391
|
}
|
|
1211
1392
|
try {
|
|
1212
|
-
const raw =
|
|
1393
|
+
const raw = readFileSync2(CONFIG_PATH, "utf-8");
|
|
1213
1394
|
return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
|
|
1214
1395
|
} catch {
|
|
1215
1396
|
logger.warn(MESSAGES.CONFIG_CORRUPTED);
|
|
@@ -1218,13 +1399,13 @@ function loadConfig() {
|
|
|
1218
1399
|
}
|
|
1219
1400
|
}
|
|
1220
1401
|
function writeConfig(config2) {
|
|
1221
|
-
|
|
1402
|
+
writeFileSync2(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
|
|
1222
1403
|
}
|
|
1223
1404
|
function writeDefaultConfig() {
|
|
1224
1405
|
writeConfig(DEFAULT_CONFIG);
|
|
1225
1406
|
}
|
|
1226
1407
|
function saveConfig(config2) {
|
|
1227
|
-
|
|
1408
|
+
writeFileSync2(CONFIG_PATH, JSON.stringify(config2, null, 2), "utf-8");
|
|
1228
1409
|
}
|
|
1229
1410
|
function getConfigValue(key) {
|
|
1230
1411
|
const config2 = loadConfig();
|
|
@@ -1234,6 +1415,7 @@ function ensureClawtDirs() {
|
|
|
1234
1415
|
ensureDir(CLAWT_HOME);
|
|
1235
1416
|
ensureDir(LOGS_DIR);
|
|
1236
1417
|
ensureDir(WORKTREES_DIR);
|
|
1418
|
+
ensureDir(PROJECTS_CONFIG_DIR);
|
|
1237
1419
|
}
|
|
1238
1420
|
function parseConcurrency(optionValue, configValue) {
|
|
1239
1421
|
if (optionValue === void 0) {
|
|
@@ -1247,18 +1429,18 @@ function parseConcurrency(optionValue, configValue) {
|
|
|
1247
1429
|
}
|
|
1248
1430
|
|
|
1249
1431
|
// src/utils/prompt.ts
|
|
1250
|
-
import
|
|
1432
|
+
import Enquirer2 from "enquirer";
|
|
1251
1433
|
|
|
1252
1434
|
// src/utils/claude.ts
|
|
1253
1435
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1254
|
-
import { existsSync as
|
|
1255
|
-
import { join as
|
|
1436
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3 } from "fs";
|
|
1437
|
+
import { join as join5 } from "path";
|
|
1256
1438
|
|
|
1257
1439
|
// src/utils/terminal.ts
|
|
1258
1440
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1259
|
-
import { existsSync as
|
|
1441
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1260
1442
|
function isITerm2Installed() {
|
|
1261
|
-
return
|
|
1443
|
+
return existsSync6(ITERM2_APP_PATH);
|
|
1262
1444
|
}
|
|
1263
1445
|
function detectTerminalApp() {
|
|
1264
1446
|
const configured = getConfigValue("terminalApp");
|
|
@@ -1331,8 +1513,8 @@ function encodeClaudeProjectPath(absolutePath) {
|
|
|
1331
1513
|
}
|
|
1332
1514
|
function hasClaudeSessionHistory(worktreePath) {
|
|
1333
1515
|
const encodedName = encodeClaudeProjectPath(worktreePath);
|
|
1334
|
-
const projectDir =
|
|
1335
|
-
if (!
|
|
1516
|
+
const projectDir = join5(CLAUDE_PROJECTS_DIR, encodedName);
|
|
1517
|
+
if (!existsSync7(projectDir)) {
|
|
1336
1518
|
return false;
|
|
1337
1519
|
}
|
|
1338
1520
|
const entries = readdirSync3(projectDir);
|
|
@@ -1388,20 +1570,20 @@ function launchInteractiveClaudeInNewTerminal(worktree, hasPreviousSession) {
|
|
|
1388
1570
|
}
|
|
1389
1571
|
|
|
1390
1572
|
// src/utils/validate-snapshot.ts
|
|
1391
|
-
import { join as
|
|
1392
|
-
import { existsSync as
|
|
1573
|
+
import { join as join6 } from "path";
|
|
1574
|
+
import { existsSync as existsSync8, readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync, readdirSync as readdirSync4, rmdirSync as rmdirSync2, statSync as statSync2 } from "fs";
|
|
1393
1575
|
function getSnapshotPath(projectName, branchName) {
|
|
1394
|
-
return
|
|
1576
|
+
return join6(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.tree`);
|
|
1395
1577
|
}
|
|
1396
1578
|
function getSnapshotHeadPath(projectName, branchName) {
|
|
1397
|
-
return
|
|
1579
|
+
return join6(VALIDATE_SNAPSHOTS_DIR, projectName, `${branchName}.head`);
|
|
1398
1580
|
}
|
|
1399
1581
|
function hasSnapshot(projectName, branchName) {
|
|
1400
|
-
return
|
|
1582
|
+
return existsSync8(getSnapshotPath(projectName, branchName));
|
|
1401
1583
|
}
|
|
1402
1584
|
function getSnapshotModifiedTime(projectName, branchName) {
|
|
1403
1585
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1404
|
-
if (!
|
|
1586
|
+
if (!existsSync8(snapshotPath)) return null;
|
|
1405
1587
|
const stat = statSync2(snapshotPath);
|
|
1406
1588
|
return stat.mtime.toISOString();
|
|
1407
1589
|
}
|
|
@@ -1409,47 +1591,47 @@ function readSnapshot(projectName, branchName) {
|
|
|
1409
1591
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1410
1592
|
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
1411
1593
|
logger.debug(`\u8BFB\u53D6 validate \u5FEB\u7167: ${snapshotPath}`);
|
|
1412
|
-
const treeHash =
|
|
1413
|
-
const headCommitHash =
|
|
1594
|
+
const treeHash = existsSync8(snapshotPath) ? readFileSync3(snapshotPath, "utf-8").trim() : "";
|
|
1595
|
+
const headCommitHash = existsSync8(headPath) ? readFileSync3(headPath, "utf-8").trim() : "";
|
|
1414
1596
|
return { treeHash, headCommitHash };
|
|
1415
1597
|
}
|
|
1416
1598
|
function writeSnapshot(projectName, branchName, treeHash, headCommitHash) {
|
|
1417
1599
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1418
1600
|
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
1419
|
-
const snapshotDir =
|
|
1601
|
+
const snapshotDir = join6(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1420
1602
|
ensureDir(snapshotDir);
|
|
1421
|
-
|
|
1422
|
-
|
|
1603
|
+
writeFileSync3(snapshotPath, treeHash, "utf-8");
|
|
1604
|
+
writeFileSync3(headPath, headCommitHash, "utf-8");
|
|
1423
1605
|
logger.info(`\u5DF2\u4FDD\u5B58 validate \u5FEB\u7167: ${snapshotPath}, ${headPath}`);
|
|
1424
1606
|
}
|
|
1425
1607
|
function removeSnapshot(projectName, branchName) {
|
|
1426
1608
|
const snapshotPath = getSnapshotPath(projectName, branchName);
|
|
1427
1609
|
const headPath = getSnapshotHeadPath(projectName, branchName);
|
|
1428
|
-
if (
|
|
1610
|
+
if (existsSync8(snapshotPath)) {
|
|
1429
1611
|
unlinkSync(snapshotPath);
|
|
1430
1612
|
logger.info(`\u5DF2\u5220\u9664 validate \u5FEB\u7167: ${snapshotPath}`);
|
|
1431
1613
|
}
|
|
1432
|
-
if (
|
|
1614
|
+
if (existsSync8(headPath)) {
|
|
1433
1615
|
unlinkSync(headPath);
|
|
1434
1616
|
logger.info(`\u5DF2\u5220\u9664 validate \u5FEB\u7167: ${headPath}`);
|
|
1435
1617
|
}
|
|
1436
1618
|
}
|
|
1437
1619
|
function getProjectSnapshotBranches(projectName) {
|
|
1438
|
-
const projectDir =
|
|
1439
|
-
if (!
|
|
1620
|
+
const projectDir = join6(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1621
|
+
if (!existsSync8(projectDir)) {
|
|
1440
1622
|
return [];
|
|
1441
1623
|
}
|
|
1442
1624
|
const files = readdirSync4(projectDir);
|
|
1443
1625
|
return files.filter((f) => f.endsWith(".tree")).map((f) => f.replace(/\.tree$/, ""));
|
|
1444
1626
|
}
|
|
1445
1627
|
function removeProjectSnapshots(projectName) {
|
|
1446
|
-
const projectDir =
|
|
1447
|
-
if (!
|
|
1628
|
+
const projectDir = join6(VALIDATE_SNAPSHOTS_DIR, projectName);
|
|
1629
|
+
if (!existsSync8(projectDir)) {
|
|
1448
1630
|
return;
|
|
1449
1631
|
}
|
|
1450
1632
|
const files = readdirSync4(projectDir);
|
|
1451
1633
|
for (const file of files) {
|
|
1452
|
-
unlinkSync(
|
|
1634
|
+
unlinkSync(join6(projectDir, file));
|
|
1453
1635
|
}
|
|
1454
1636
|
try {
|
|
1455
1637
|
rmdirSync2(projectDir);
|
|
@@ -1459,7 +1641,7 @@ function removeProjectSnapshots(projectName) {
|
|
|
1459
1641
|
}
|
|
1460
1642
|
|
|
1461
1643
|
// src/utils/worktree-matcher.ts
|
|
1462
|
-
import
|
|
1644
|
+
import Enquirer3 from "enquirer";
|
|
1463
1645
|
import { statSync as statSync3 } from "fs";
|
|
1464
1646
|
function findExactMatch(worktrees, branchName) {
|
|
1465
1647
|
return worktrees.find((wt) => wt.branch === branchName);
|
|
@@ -1469,7 +1651,7 @@ function findFuzzyMatches(worktrees, keyword) {
|
|
|
1469
1651
|
return worktrees.filter((wt) => wt.branch.toLowerCase().includes(lowerKeyword));
|
|
1470
1652
|
}
|
|
1471
1653
|
async function promptSelectBranch(worktrees, message) {
|
|
1472
|
-
const selectedBranch = await new
|
|
1654
|
+
const selectedBranch = await new Enquirer3.Select({
|
|
1473
1655
|
message,
|
|
1474
1656
|
choices: worktrees.map((wt) => ({
|
|
1475
1657
|
name: wt.branch,
|
|
@@ -1487,7 +1669,7 @@ async function promptMultiSelectBranches(worktrees, message) {
|
|
|
1487
1669
|
{ name: SELECT_ALL_NAME, message: SELECT_ALL_LABEL },
|
|
1488
1670
|
...branchChoices
|
|
1489
1671
|
];
|
|
1490
|
-
const MultiSelect =
|
|
1672
|
+
const MultiSelect = Enquirer3.MultiSelect;
|
|
1491
1673
|
class MultiSelectWithSelectAll extends MultiSelect {
|
|
1492
1674
|
space() {
|
|
1493
1675
|
if (!this.focused) return;
|
|
@@ -1642,7 +1824,7 @@ async function promptGroupedMultiSelectBranches(worktrees, message) {
|
|
|
1642
1824
|
const groupMembershipMap = buildGroupMembershipMap(groups);
|
|
1643
1825
|
const groupSelectAllNames = new Set(groupMembershipMap.keys());
|
|
1644
1826
|
const allBranchNames = new Set(worktrees.map((wt) => wt.branch));
|
|
1645
|
-
const MultiSelect =
|
|
1827
|
+
const MultiSelect = Enquirer3.MultiSelect;
|
|
1646
1828
|
class MultiSelectWithGroupSelectAll extends MultiSelect {
|
|
1647
1829
|
space() {
|
|
1648
1830
|
if (!this.focused) return;
|
|
@@ -1998,7 +2180,7 @@ var ProgressRenderer = class {
|
|
|
1998
2180
|
|
|
1999
2181
|
// src/utils/task-file.ts
|
|
2000
2182
|
import { resolve } from "path";
|
|
2001
|
-
import { existsSync as
|
|
2183
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
|
|
2002
2184
|
var TASK_BLOCK_REGEX = /<!-- CLAWT-TASKS:START -->([\s\S]*?)<!-- CLAWT-TASKS:END -->/g;
|
|
2003
2185
|
var BRANCH_LINE_REGEX = /^#\s*branch:\s*(.+)$/;
|
|
2004
2186
|
var EMPTY_TASKS_MESSAGE = "\u4EFB\u52A1\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A";
|
|
@@ -2044,10 +2226,10 @@ function parseTaskFile(content, options) {
|
|
|
2044
2226
|
}
|
|
2045
2227
|
function loadTaskFile(filePath, options) {
|
|
2046
2228
|
const absolutePath = resolve(filePath);
|
|
2047
|
-
if (!
|
|
2229
|
+
if (!existsSync9(absolutePath)) {
|
|
2048
2230
|
throw new ClawtError(MESSAGES.TASK_FILE_NOT_FOUND(absolutePath));
|
|
2049
2231
|
}
|
|
2050
|
-
const content =
|
|
2232
|
+
const content = readFileSync4(absolutePath, "utf-8");
|
|
2051
2233
|
const entries = parseTaskFile(content, options);
|
|
2052
2234
|
if (entries.length === 0) {
|
|
2053
2235
|
throw new ClawtError(MESSAGES.TASK_FILE_EMPTY);
|
|
@@ -2382,7 +2564,7 @@ async function executeBatchTasks(worktrees, tasks, concurrency) {
|
|
|
2382
2564
|
|
|
2383
2565
|
// src/utils/dry-run.ts
|
|
2384
2566
|
import chalk5 from "chalk";
|
|
2385
|
-
import { join as
|
|
2567
|
+
import { join as join7 } from "path";
|
|
2386
2568
|
var DRY_RUN_TASK_DESC_MAX_LENGTH = 80;
|
|
2387
2569
|
function truncateTaskDesc(task) {
|
|
2388
2570
|
const oneLine = task.replace(/\n/g, " ").trim();
|
|
@@ -2410,7 +2592,7 @@ function printDryRunPreview(branchNames, tasks, concurrency) {
|
|
|
2410
2592
|
let hasConflict = false;
|
|
2411
2593
|
for (let i = 0; i < branchNames.length; i++) {
|
|
2412
2594
|
const branch = branchNames[i];
|
|
2413
|
-
const worktreePath =
|
|
2595
|
+
const worktreePath = join7(projectDir, branch);
|
|
2414
2596
|
const exists = checkBranchExists(branch);
|
|
2415
2597
|
if (exists) hasConflict = true;
|
|
2416
2598
|
const indexLabel = `[${i + 1}/${branchNames.length}]`;
|
|
@@ -2448,7 +2630,7 @@ function applyAliases(program2, aliases) {
|
|
|
2448
2630
|
|
|
2449
2631
|
// src/utils/config-strategy.ts
|
|
2450
2632
|
import chalk6 from "chalk";
|
|
2451
|
-
import
|
|
2633
|
+
import Enquirer4 from "enquirer";
|
|
2452
2634
|
function isValidConfigKey(key) {
|
|
2453
2635
|
return key in DEFAULT_CONFIG;
|
|
2454
2636
|
}
|
|
@@ -2500,7 +2682,7 @@ async function promptBooleanValue(key, currentValue) {
|
|
|
2500
2682
|
{ name: "true", message: "true" },
|
|
2501
2683
|
{ name: "false", message: "false" }
|
|
2502
2684
|
];
|
|
2503
|
-
const selected = await new
|
|
2685
|
+
const selected = await new Enquirer4.Select({
|
|
2504
2686
|
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
2505
2687
|
choices,
|
|
2506
2688
|
initial: currentValue ? 0 : 1
|
|
@@ -2508,7 +2690,7 @@ async function promptBooleanValue(key, currentValue) {
|
|
|
2508
2690
|
return selected === "true";
|
|
2509
2691
|
}
|
|
2510
2692
|
async function promptNumberValue(key, currentValue) {
|
|
2511
|
-
const input = await new
|
|
2693
|
+
const input = await new Enquirer4.Input({
|
|
2512
2694
|
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
2513
2695
|
initial: String(currentValue),
|
|
2514
2696
|
validate: (val) => {
|
|
@@ -2523,28 +2705,28 @@ async function promptEnumValue(key, currentValue, allowedValues) {
|
|
|
2523
2705
|
name: v,
|
|
2524
2706
|
message: v
|
|
2525
2707
|
}));
|
|
2526
|
-
return await new
|
|
2708
|
+
return await new Enquirer4.Select({
|
|
2527
2709
|
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
2528
2710
|
choices,
|
|
2529
2711
|
initial: allowedValues.indexOf(currentValue)
|
|
2530
2712
|
}).run();
|
|
2531
2713
|
}
|
|
2532
2714
|
async function promptStringValue(key, currentValue) {
|
|
2533
|
-
return await new
|
|
2715
|
+
return await new Enquirer4.Input({
|
|
2534
2716
|
message: MESSAGES.CONFIG_INPUT_PROMPT(key),
|
|
2535
2717
|
initial: currentValue
|
|
2536
2718
|
}).run();
|
|
2537
2719
|
}
|
|
2538
2720
|
|
|
2539
2721
|
// src/utils/update-checker.ts
|
|
2540
|
-
import { readFileSync as
|
|
2722
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
2541
2723
|
import { execSync as execSync3 } from "child_process";
|
|
2542
2724
|
import { request } from "https";
|
|
2543
2725
|
import chalk7 from "chalk";
|
|
2544
2726
|
import stringWidth2 from "string-width";
|
|
2545
2727
|
function readUpdateCache() {
|
|
2546
2728
|
try {
|
|
2547
|
-
const raw =
|
|
2729
|
+
const raw = readFileSync5(UPDATE_CHECK_PATH, "utf-8");
|
|
2548
2730
|
return JSON.parse(raw);
|
|
2549
2731
|
} catch {
|
|
2550
2732
|
return null;
|
|
@@ -2552,7 +2734,7 @@ function readUpdateCache() {
|
|
|
2552
2734
|
}
|
|
2553
2735
|
function writeUpdateCache(cache) {
|
|
2554
2736
|
try {
|
|
2555
|
-
|
|
2737
|
+
writeFileSync4(UPDATE_CHECK_PATH, JSON.stringify(cache, null, 2), "utf-8");
|
|
2556
2738
|
} catch {
|
|
2557
2739
|
}
|
|
2558
2740
|
}
|
|
@@ -2667,10 +2849,60 @@ async function checkForUpdates(currentVersion) {
|
|
|
2667
2849
|
}
|
|
2668
2850
|
}
|
|
2669
2851
|
|
|
2852
|
+
// src/utils/json.ts
|
|
2853
|
+
function primitiveToString(value) {
|
|
2854
|
+
if (value === void 0) {
|
|
2855
|
+
return "undefined";
|
|
2856
|
+
}
|
|
2857
|
+
if (value === null) {
|
|
2858
|
+
return "null";
|
|
2859
|
+
}
|
|
2860
|
+
if (typeof value === "symbol") {
|
|
2861
|
+
return value.toString();
|
|
2862
|
+
}
|
|
2863
|
+
if (typeof value === "function") {
|
|
2864
|
+
return `[Function: ${value.name || "anonymous"}]`;
|
|
2865
|
+
}
|
|
2866
|
+
return String(value);
|
|
2867
|
+
}
|
|
2868
|
+
function safeStringify(value, indent = 2) {
|
|
2869
|
+
if (value === null || typeof value !== "object") {
|
|
2870
|
+
return primitiveToString(value);
|
|
2871
|
+
}
|
|
2872
|
+
try {
|
|
2873
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
2874
|
+
return JSON.stringify(
|
|
2875
|
+
value,
|
|
2876
|
+
(_key, val) => {
|
|
2877
|
+
if (typeof val === "bigint") {
|
|
2878
|
+
return val.toString();
|
|
2879
|
+
}
|
|
2880
|
+
if (typeof val === "undefined" || typeof val === "function" || typeof val === "symbol") {
|
|
2881
|
+
return primitiveToString(val);
|
|
2882
|
+
}
|
|
2883
|
+
if (typeof val === "object" && val !== null) {
|
|
2884
|
+
if (seen.has(val)) {
|
|
2885
|
+
return "[Circular]";
|
|
2886
|
+
}
|
|
2887
|
+
seen.add(val);
|
|
2888
|
+
}
|
|
2889
|
+
return val;
|
|
2890
|
+
},
|
|
2891
|
+
indent
|
|
2892
|
+
);
|
|
2893
|
+
} catch {
|
|
2894
|
+
try {
|
|
2895
|
+
return JSON.stringify(String(value), null, indent);
|
|
2896
|
+
} catch {
|
|
2897
|
+
return "[Unserializable]";
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2670
2902
|
// src/commands/list.ts
|
|
2671
2903
|
import chalk8 from "chalk";
|
|
2672
2904
|
function registerListCommand(program2) {
|
|
2673
|
-
program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
2905
|
+
program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree\uFF08\u652F\u6301 --json \u683C\u5F0F\u8F93\u51FA\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
2674
2906
|
handleList(options);
|
|
2675
2907
|
});
|
|
2676
2908
|
}
|
|
@@ -2720,12 +2952,13 @@ function printListAsText(projectName, worktrees) {
|
|
|
2720
2952
|
|
|
2721
2953
|
// src/commands/create.ts
|
|
2722
2954
|
function registerCreateCommand(program2) {
|
|
2723
|
-
program2.command("create").description("\u6279\u91CF\u521B\u5EFA worktree \u53CA\u5BF9\u5E94\u5206\u652F").requiredOption("-b, --branch <branchName>", "\u5206\u652F\u540D").option("-n, --number <count>", "\u521B\u5EFA\u6570\u91CF", "1").action((options) => {
|
|
2724
|
-
handleCreate(options);
|
|
2955
|
+
program2.command("create").description("\u6279\u91CF\u521B\u5EFA worktree \u53CA\u5BF9\u5E94\u5206\u652F\uFF08\u542B\u9A8C\u8BC1\u5206\u652F\uFF09").requiredOption("-b, --branch <branchName>", "\u5206\u652F\u540D").option("-n, --number <count>", "\u521B\u5EFA\u6570\u91CF", "1").action(async (options) => {
|
|
2956
|
+
await handleCreate(options);
|
|
2725
2957
|
});
|
|
2726
2958
|
}
|
|
2727
|
-
function handleCreate(options) {
|
|
2959
|
+
async function handleCreate(options) {
|
|
2728
2960
|
validateMainWorktree();
|
|
2961
|
+
await ensureOnMainWorkBranch();
|
|
2729
2962
|
const count = Number(options.number);
|
|
2730
2963
|
if (!Number.isInteger(count) || count <= 0) {
|
|
2731
2964
|
throw new ClawtError(
|
|
@@ -2741,6 +2974,7 @@ function handleCreate(options) {
|
|
|
2741
2974
|
printInfo(`\u76EE\u5F55\u8DEF\u5F84${index + 1}\uFF1A`);
|
|
2742
2975
|
printInfo(` ${wt.path}`);
|
|
2743
2976
|
printInfo(` \u5206\u652F\u540D: ${wt.branch}`);
|
|
2977
|
+
printInfo(` \u9A8C\u8BC1\u5206\u652F: ${getValidateBranchName(wt.branch)}`);
|
|
2744
2978
|
printSeparator();
|
|
2745
2979
|
});
|
|
2746
2980
|
}
|
|
@@ -2753,7 +2987,7 @@ var REMOVE_RESOLVE_MESSAGES = {
|
|
|
2753
2987
|
noMatch: MESSAGES.REMOVE_NO_MATCH
|
|
2754
2988
|
};
|
|
2755
2989
|
function registerRemoveCommand(program2) {
|
|
2756
|
-
program2.command("remove").description("\u79FB\u9664 worktree\uFF08\u652F\u6301\
|
|
2990
|
+
program2.command("remove").description("\u79FB\u9664 worktree\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D/\u591A\u9009/\u5168\u90E8\uFF09").option("--all", "\u79FB\u9664\u5F53\u524D\u9879\u76EE\u4E0B\u6240\u6709 worktree").option("-b, --branch <branchName>", "\u6307\u5B9A\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").action(async (options) => {
|
|
2757
2991
|
await handleRemove(options);
|
|
2758
2992
|
});
|
|
2759
2993
|
}
|
|
@@ -2774,13 +3008,13 @@ async function handleRemove(options) {
|
|
|
2774
3008
|
}
|
|
2775
3009
|
printInfo("\u5373\u5C06\u79FB\u9664\u4EE5\u4E0B worktree \u53CA\u672C\u5730\u5206\u652F\uFF1A\n");
|
|
2776
3010
|
worktreesToRemove.forEach((wt, index) => {
|
|
2777
|
-
printInfo(` ${index + 1}. ${wt.path} \u2192 \u5206\u652F: ${wt.branch}`);
|
|
3011
|
+
printInfo(` ${index + 1}. ${wt.path} \u2192 \u5206\u652F: ${wt.branch} \u9A8C\u8BC1\u5206\u652F: ${getValidateBranchName(wt.branch)}`);
|
|
2778
3012
|
});
|
|
2779
3013
|
printInfo("");
|
|
2780
3014
|
const autoDelete = getConfigValue("autoDeleteBranch");
|
|
2781
3015
|
let shouldDeleteBranch = autoDelete;
|
|
2782
3016
|
if (!autoDelete) {
|
|
2783
|
-
shouldDeleteBranch = await confirmAction(
|
|
3017
|
+
shouldDeleteBranch = await confirmAction(MESSAGES.REMOVE_CONFIRM_DELETE_BRANCHES);
|
|
2784
3018
|
if (!shouldDeleteBranch) {
|
|
2785
3019
|
printHint(MESSAGES.REMOVE_BRANCHES_KEPT);
|
|
2786
3020
|
}
|
|
@@ -2788,10 +3022,12 @@ async function handleRemove(options) {
|
|
|
2788
3022
|
const failures = [];
|
|
2789
3023
|
for (const wt of worktreesToRemove) {
|
|
2790
3024
|
try {
|
|
3025
|
+
await ensureOnMainWorkBranch();
|
|
2791
3026
|
removeWorktreeByPath(wt.path);
|
|
2792
3027
|
if (shouldDeleteBranch) {
|
|
2793
3028
|
deleteBranch(wt.branch);
|
|
2794
3029
|
}
|
|
3030
|
+
deleteValidateBranch(wt.branch);
|
|
2795
3031
|
removeSnapshot(projectName, wt.branch);
|
|
2796
3032
|
printSuccess(MESSAGES.WORKTREE_REMOVED(wt.path));
|
|
2797
3033
|
} catch (error) {
|
|
@@ -2816,7 +3052,7 @@ async function handleRemove(options) {
|
|
|
2816
3052
|
|
|
2817
3053
|
// src/commands/run.ts
|
|
2818
3054
|
function registerRunCommand(program2) {
|
|
2819
|
-
program2.command("run").description("\u6279\u91CF\u521B\u5EFA worktree \
|
|
3055
|
+
program2.command("run").description("\u6279\u91CF\u521B\u5EFA worktree + \u542F\u52A8 Claude Code \u6267\u884C\u4EFB\u52A1\uFF08\u652F\u6301\u4EFB\u52A1\u6587\u4EF6\uFF09").option("-b, --branch <branchName>", "\u5206\u652F\u540D").option("--tasks <task...>", "\u4EFB\u52A1\u5217\u8868\uFF08\u53EF\u591A\u6B21\u6307\u5B9A\uFF09\uFF0C\u4E0D\u4F20\u5219\u5728 worktree \u4E2D\u6253\u5F00 Claude Code \u4EA4\u4E92\u5F0F\u754C\u9762").option("-c, --concurrency <n>", "\u6700\u5927\u5E76\u53D1\u6570\uFF0C0 \u8868\u793A\u4E0D\u9650\u5236").option("-f, --file <path>", "\u4ECE\u4EFB\u52A1\u6587\u4EF6\u8BFB\u53D6\u4EFB\u52A1\u5217\u8868\uFF08\u4E0E --tasks \u4E92\u65A5\uFF09").option("--dry-run", "\u9884\u89C8\u6A21\u5F0F\uFF0C\u4EC5\u5C55\u793A\u4EFB\u52A1\u8BA1\u5212\u4E0D\u5B9E\u9645\u6267\u884C").action(async (options) => {
|
|
2820
3056
|
await handleRun(options);
|
|
2821
3057
|
});
|
|
2822
3058
|
}
|
|
@@ -2854,6 +3090,9 @@ function handleDryRunFromFile(options) {
|
|
|
2854
3090
|
}
|
|
2855
3091
|
async function handleRun(options) {
|
|
2856
3092
|
validateMainWorktree();
|
|
3093
|
+
if (!options.dryRun) {
|
|
3094
|
+
await ensureOnMainWorkBranch();
|
|
3095
|
+
}
|
|
2857
3096
|
if (options.file && options.tasks) {
|
|
2858
3097
|
throw new ClawtError(MESSAGES.FILE_AND_TASKS_CONFLICT);
|
|
2859
3098
|
}
|
|
@@ -2909,7 +3148,7 @@ var RESUME_RESOLVE_MESSAGES = {
|
|
|
2909
3148
|
noMatch: MESSAGES.RESUME_NO_MATCH
|
|
2910
3149
|
};
|
|
2911
3150
|
function registerResumeCommand(program2) {
|
|
2912
|
-
program2.command("resume").description("\u5728\u5DF2\u6709 worktree \u4E2D\u6062\u590D Claude Code \
|
|
3151
|
+
program2.command("resume").description("\u5728\u5DF2\u6709 worktree \u4E2D\u6062\u590D Claude Code \u4F1A\u8BDD\uFF08\u652F\u6301\u591A\u9009\u6279\u91CF\u6062\u590D\uFF09").option("-b, --branch <branchName>", "\u8981\u6062\u590D\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").action(async (options) => {
|
|
2913
3152
|
await handleResume(options);
|
|
2914
3153
|
});
|
|
2915
3154
|
}
|
|
@@ -2961,9 +3200,6 @@ async function handleBatchResume(worktrees) {
|
|
|
2961
3200
|
printSuccess(MESSAGES.RESUME_ALL_SUCCESS(worktrees.length));
|
|
2962
3201
|
}
|
|
2963
3202
|
|
|
2964
|
-
// src/commands/validate.ts
|
|
2965
|
-
import Enquirer4 from "enquirer";
|
|
2966
|
-
|
|
2967
3203
|
// src/commands/sync.ts
|
|
2968
3204
|
function registerSyncCommand(program2) {
|
|
2969
3205
|
program2.command("sync").description("\u5C06\u4E3B\u5206\u652F\u6700\u65B0\u4EE3\u7801\u540C\u6B65\u5230\u76EE\u6807 worktree").option("-b, --branch <branchName>", "\u8981\u540C\u6B65\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").action(async (options) => {
|
|
@@ -2993,9 +3229,9 @@ function mergeMainBranch(worktreePath, mainBranch) {
|
|
|
2993
3229
|
throw new ClawtError(`\u5408\u5E76 ${mainBranch} \u5931\u8D25`);
|
|
2994
3230
|
}
|
|
2995
3231
|
}
|
|
2996
|
-
function executeSyncForBranch(targetWorktreePath, branch) {
|
|
3232
|
+
async function executeSyncForBranch(targetWorktreePath, branch) {
|
|
2997
3233
|
const mainWorktreePath = getGitTopLevel();
|
|
2998
|
-
const mainBranch =
|
|
3234
|
+
const mainBranch = getMainWorkBranch();
|
|
2999
3235
|
if (!isWorkingDirClean(targetWorktreePath)) {
|
|
3000
3236
|
autoSaveChanges(targetWorktreePath, branch);
|
|
3001
3237
|
}
|
|
@@ -3011,15 +3247,20 @@ function executeSyncForBranch(targetWorktreePath, branch) {
|
|
|
3011
3247
|
logger.info(`\u5DF2\u6E05\u9664\u5206\u652F ${branch} \u7684 validate \u5FEB\u7167`);
|
|
3012
3248
|
}
|
|
3013
3249
|
printSuccess(MESSAGES.SYNC_SUCCESS(branch, mainBranch));
|
|
3250
|
+
await rebuildValidateBranch(branch, mainWorktreePath);
|
|
3251
|
+
const validateBranchName = getValidateBranchName(branch);
|
|
3252
|
+
printInfo(MESSAGES.SYNC_VALIDATE_BRANCH_REBUILT(validateBranchName));
|
|
3014
3253
|
return { success: true, hasConflict: false };
|
|
3015
3254
|
}
|
|
3016
3255
|
async function handleSync(options) {
|
|
3017
3256
|
validateMainWorktree();
|
|
3257
|
+
requireProjectConfig();
|
|
3258
|
+
await ensureOnMainWorkBranch();
|
|
3018
3259
|
logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
|
|
3019
3260
|
const worktrees = getProjectWorktrees();
|
|
3020
3261
|
const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
|
|
3021
3262
|
const { path: targetWorktreePath, branch } = worktree;
|
|
3022
|
-
executeSyncForBranch(targetWorktreePath, branch);
|
|
3263
|
+
await executeSyncForBranch(targetWorktreePath, branch);
|
|
3023
3264
|
}
|
|
3024
3265
|
|
|
3025
3266
|
// src/commands/validate.ts
|
|
@@ -3030,43 +3271,12 @@ var VALIDATE_RESOLVE_MESSAGES = {
|
|
|
3030
3271
|
noMatch: MESSAGES.VALIDATE_NO_MATCH
|
|
3031
3272
|
};
|
|
3032
3273
|
function registerValidateCommand(program2) {
|
|
3033
|
-
program2.command("validate").description("\u5728\u4E3B worktree \u9A8C\u8BC1\u67D0\u4E2A worktree \u5206\u652F\u7684\u53D8\u66F4").option("-b, --branch <branchName>", "\u8981\u9A8C\u8BC1\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").option("--clean", "\u6E05\u7406 validate \u72B6\u6001\uFF08\u91CD\u7F6E\u4E3B worktree \u5E76\u5220\u9664\u5FEB\u7167\uFF09").option("-r, --run <command>", "validate \u6210\u529F\u540E\u5728\u4E3B worktree \u4E2D\u6267\u884C\u7684\u547D\u4EE4").action(async (options) => {
|
|
3274
|
+
program2.command("validate").description("\u5728\u4E3B worktree \u9A8C\u8BC1\u67D0\u4E2A worktree \u5206\u652F\u7684\u53D8\u66F4\uFF08\u901A\u8FC7\u9A8C\u8BC1\u5206\u652F\uFF09").option("-b, --branch <branchName>", "\u8981\u9A8C\u8BC1\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").option("--clean", "\u6E05\u7406 validate \u72B6\u6001\uFF08\u91CD\u7F6E\u4E3B worktree \u5E76\u5220\u9664\u5FEB\u7167\uFF09").option("-r, --run <command>", "validate \u6210\u529F\u540E\u5728\u4E3B worktree \u4E2D\u6267\u884C\u7684\u547D\u4EE4").action(async (options) => {
|
|
3034
3275
|
await handleValidate(options);
|
|
3035
3276
|
});
|
|
3036
3277
|
}
|
|
3037
3278
|
async function handleDirtyMainWorktree(mainWorktreePath) {
|
|
3038
|
-
|
|
3039
|
-
const choice = await new Enquirer4.Select({
|
|
3040
|
-
message: "\u9009\u62E9\u5904\u7406\u65B9\u5F0F",
|
|
3041
|
-
choices: [
|
|
3042
|
-
{
|
|
3043
|
-
name: "reset",
|
|
3044
|
-
message: "reset (\u63A8\u8350) - \u4E22\u5F03\u6240\u6709\u66F4\u6539 (git reset --hard HEAD && git clean -fd)"
|
|
3045
|
-
},
|
|
3046
|
-
{
|
|
3047
|
-
name: "stash",
|
|
3048
|
-
message: "stash - \u6682\u5B58\u66F4\u6539 (git add . && git stash)"
|
|
3049
|
-
},
|
|
3050
|
-
{
|
|
3051
|
-
name: "exit",
|
|
3052
|
-
message: "exit - \u9000\u51FA\uFF0C\u624B\u52A8\u5904\u7406"
|
|
3053
|
-
}
|
|
3054
|
-
],
|
|
3055
|
-
initial: 0
|
|
3056
|
-
}).run();
|
|
3057
|
-
if (choice === "exit") {
|
|
3058
|
-
throw new ClawtError("\u7528\u6237\u9009\u62E9\u9000\u51FA");
|
|
3059
|
-
}
|
|
3060
|
-
if (choice === "reset") {
|
|
3061
|
-
gitResetHard(mainWorktreePath);
|
|
3062
|
-
gitCleanForce(mainWorktreePath);
|
|
3063
|
-
} else if (choice === "stash") {
|
|
3064
|
-
gitAddAll(mainWorktreePath);
|
|
3065
|
-
gitStashPush("clawt:auto-stash", mainWorktreePath);
|
|
3066
|
-
}
|
|
3067
|
-
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
3068
|
-
throw new ClawtError("\u5DE5\u4F5C\u533A\u4ECD\u7136\u4E0D\u5E72\u51C0\uFF0C\u8BF7\u624B\u52A8\u5904\u7406");
|
|
3069
|
-
}
|
|
3279
|
+
await handleDirtyWorkingDir(mainWorktreePath);
|
|
3070
3280
|
}
|
|
3071
3281
|
function migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted) {
|
|
3072
3282
|
let didTempCommit = false;
|
|
@@ -3109,7 +3319,7 @@ async function handlePatchApplyFailure(targetWorktreePath, branchName) {
|
|
|
3109
3319
|
return;
|
|
3110
3320
|
}
|
|
3111
3321
|
printInfo(MESSAGES.VALIDATE_AUTO_SYNC_START(branchName));
|
|
3112
|
-
const syncResult = executeSyncForBranch(targetWorktreePath, branchName);
|
|
3322
|
+
const syncResult = await executeSyncForBranch(targetWorktreePath, branchName);
|
|
3113
3323
|
}
|
|
3114
3324
|
function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName) {
|
|
3115
3325
|
gitAddAll(mainWorktreePath);
|
|
@@ -3121,6 +3331,7 @@ function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName) {
|
|
|
3121
3331
|
}
|
|
3122
3332
|
async function handleValidateClean(options) {
|
|
3123
3333
|
validateMainWorktree();
|
|
3334
|
+
requireProjectConfig();
|
|
3124
3335
|
const projectName = getProjectName();
|
|
3125
3336
|
const mainWorktreePath = getGitTopLevel();
|
|
3126
3337
|
const worktrees = getProjectWorktrees();
|
|
@@ -3141,17 +3352,24 @@ async function handleValidateClean(options) {
|
|
|
3141
3352
|
gitResetHard(mainWorktreePath);
|
|
3142
3353
|
gitCleanForce(mainWorktreePath);
|
|
3143
3354
|
}
|
|
3355
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
3144
3356
|
removeSnapshot(projectName, branchName);
|
|
3145
3357
|
printSuccess(MESSAGES.VALIDATE_CLEANED(branchName));
|
|
3146
3358
|
}
|
|
3147
3359
|
async function handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
|
|
3360
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
3361
|
+
if (!checkBranchExists(validateBranchName)) {
|
|
3362
|
+
throw new ClawtError(MESSAGES.VALIDATE_BRANCH_NOT_FOUND(validateBranchName, branchName));
|
|
3363
|
+
}
|
|
3364
|
+
gitCheckout(validateBranchName, mainWorktreePath);
|
|
3148
3365
|
const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
3149
3366
|
if (!result.success) {
|
|
3367
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
3150
3368
|
await handlePatchApplyFailure(targetWorktreePath, branchName);
|
|
3151
3369
|
return;
|
|
3152
3370
|
}
|
|
3153
3371
|
saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName);
|
|
3154
|
-
printSuccess(MESSAGES.
|
|
3372
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
3155
3373
|
}
|
|
3156
3374
|
async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
|
|
3157
3375
|
const { treeHash: oldTreeHash, headCommitHash: oldHeadCommitHash } = readSnapshot(projectName, branchName);
|
|
@@ -3159,8 +3377,17 @@ async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, p
|
|
|
3159
3377
|
gitResetHard(mainWorktreePath);
|
|
3160
3378
|
gitCleanForce(mainWorktreePath);
|
|
3161
3379
|
}
|
|
3380
|
+
const validateBranchName = getValidateBranchName(branchName);
|
|
3381
|
+
if (!checkBranchExists(validateBranchName)) {
|
|
3382
|
+
throw new ClawtError(MESSAGES.VALIDATE_BRANCH_NOT_FOUND(validateBranchName, branchName));
|
|
3383
|
+
}
|
|
3384
|
+
const currentBranch = getCurrentBranch(mainWorktreePath);
|
|
3385
|
+
if (currentBranch !== validateBranchName) {
|
|
3386
|
+
gitCheckout(validateBranchName, mainWorktreePath);
|
|
3387
|
+
}
|
|
3162
3388
|
const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
|
|
3163
3389
|
if (!result.success) {
|
|
3390
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
3164
3391
|
await handlePatchApplyFailure(targetWorktreePath, branchName);
|
|
3165
3392
|
return;
|
|
3166
3393
|
}
|
|
@@ -3175,7 +3402,7 @@ async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, p
|
|
|
3175
3402
|
} else if (oldChangePatch.length > 0) {
|
|
3176
3403
|
logger.warn("\u65E7\u53D8\u66F4 patch \u4E0E\u5F53\u524D HEAD \u51B2\u7A81\uFF0C\u964D\u7EA7\u4E3A\u5168\u91CF\u6A21\u5F0F");
|
|
3177
3404
|
printWarning(MESSAGES.INCREMENTAL_VALIDATE_FALLBACK);
|
|
3178
|
-
printSuccess(MESSAGES.
|
|
3405
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
3179
3406
|
return;
|
|
3180
3407
|
}
|
|
3181
3408
|
} else {
|
|
@@ -3184,7 +3411,7 @@ async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, p
|
|
|
3184
3411
|
} catch (error) {
|
|
3185
3412
|
logger.warn(`\u589E\u91CF read-tree \u5931\u8D25: ${error}`);
|
|
3186
3413
|
printWarning(MESSAGES.INCREMENTAL_VALIDATE_FALLBACK);
|
|
3187
|
-
printSuccess(MESSAGES.
|
|
3414
|
+
printSuccess(MESSAGES.VALIDATE_SUCCESS_WITH_BRANCH(branchName, validateBranchName));
|
|
3188
3415
|
return;
|
|
3189
3416
|
}
|
|
3190
3417
|
printSuccess(MESSAGES.INCREMENTAL_VALIDATE_SUCCESS(branchName));
|
|
@@ -3248,6 +3475,7 @@ async function handleValidate(options) {
|
|
|
3248
3475
|
return;
|
|
3249
3476
|
}
|
|
3250
3477
|
validateMainWorktree();
|
|
3478
|
+
requireProjectConfig();
|
|
3251
3479
|
const projectName = getProjectName();
|
|
3252
3480
|
const mainWorktreePath = getGitTopLevel();
|
|
3253
3481
|
const worktrees = getProjectWorktrees();
|
|
@@ -3280,7 +3508,7 @@ async function handleValidate(options) {
|
|
|
3280
3508
|
|
|
3281
3509
|
// src/commands/merge.ts
|
|
3282
3510
|
function registerMergeCommand(program2) {
|
|
3283
|
-
program2.command("merge").description("\u5408\u5E76\u67D0\u4E2A\u5DF2\u9A8C\u8BC1\u7684 worktree \u5206\u652F\u5230\u4E3B worktree").option("-b, --branch <branchName>", "\u8981\u5408\u5E76\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").option("-m, --message <
|
|
3511
|
+
program2.command("merge").description("\u5408\u5E76\u67D0\u4E2A\u5DF2\u9A8C\u8BC1\u7684 worktree \u5206\u652F\u5230\u4E3B worktree").option("-b, --branch <branchName>", "\u8981\u5408\u5E76\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\u4F9B\u9009\u62E9\uFF09").option("-m, --message <commitMessage>", "\u63D0\u4EA4\u4FE1\u606F\uFF08\u76EE\u6807 worktree \u5DE5\u4F5C\u533A\u6709\u4FEE\u6539\u65F6\u5FC5\u586B\uFF09").action(async (options) => {
|
|
3284
3512
|
await handleMerge(options);
|
|
3285
3513
|
});
|
|
3286
3514
|
}
|
|
@@ -3298,7 +3526,7 @@ async function handleSquashIfNeeded(targetWorktreePath, mainWorktreePath, branch
|
|
|
3298
3526
|
if (!shouldSquash) {
|
|
3299
3527
|
return false;
|
|
3300
3528
|
}
|
|
3301
|
-
const mainBranch =
|
|
3529
|
+
const mainBranch = getMainWorkBranch();
|
|
3302
3530
|
const mergeBase = gitMergeBase(mainBranch, branchName, mainWorktreePath);
|
|
3303
3531
|
logger.info(`squash: merge-base = ${mergeBase}, \u5206\u652F = ${branchName}`);
|
|
3304
3532
|
gitResetSoftTo(mergeBase, targetWorktreePath);
|
|
@@ -3325,6 +3553,7 @@ function cleanupWorktreeAndBranch(worktreePath, branchName) {
|
|
|
3325
3553
|
async function handleMerge(options) {
|
|
3326
3554
|
validateMainWorktree();
|
|
3327
3555
|
const mainWorktreePath = getGitTopLevel();
|
|
3556
|
+
await ensureOnMainWorkBranch(mainWorktreePath);
|
|
3328
3557
|
logger.info(`merge \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}\uFF0C\u63D0\u4EA4\u4FE1\u606F: ${options.message ?? "(\u672A\u63D0\u4F9B)"}`);
|
|
3329
3558
|
const worktrees = getProjectWorktrees();
|
|
3330
3559
|
const worktree = await resolveTargetWorktree(worktrees, MERGE_RESOLVE_MESSAGES, options.branch);
|
|
@@ -3492,6 +3721,7 @@ function registerResetCommand(program2) {
|
|
|
3492
3721
|
}
|
|
3493
3722
|
async function handleReset() {
|
|
3494
3723
|
validateMainWorktree();
|
|
3724
|
+
requireProjectConfig();
|
|
3495
3725
|
const mainWorktreePath = getGitTopLevel();
|
|
3496
3726
|
logger.info("reset \u547D\u4EE4\u6267\u884C");
|
|
3497
3727
|
if (!isWorkingDirClean(mainWorktreePath)) {
|
|
@@ -3516,7 +3746,7 @@ async function handleReset() {
|
|
|
3516
3746
|
// src/commands/status.ts
|
|
3517
3747
|
import chalk10 from "chalk";
|
|
3518
3748
|
function registerStatusCommand(program2) {
|
|
3519
|
-
program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
3749
|
+
program2.command("status").description("\u663E\u793A\u9879\u76EE\u5168\u5C40\u72B6\u6001\u603B\u89C8\uFF08\u652F\u6301 --json \u683C\u5F0F\u8F93\u51FA\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
3520
3750
|
handleStatus(options);
|
|
3521
3751
|
});
|
|
3522
3752
|
}
|
|
@@ -3764,7 +3994,7 @@ function handleAliasRemove(alias) {
|
|
|
3764
3994
|
printSuccess(MESSAGES.ALIAS_REMOVE_SUCCESS(alias));
|
|
3765
3995
|
}
|
|
3766
3996
|
function registerAliasCommand(program2) {
|
|
3767
|
-
const aliasCmd = program2.command("alias").description("\u7BA1\u7406\u547D\u4EE4\u522B\u540D").action(() => {
|
|
3997
|
+
const aliasCmd = program2.command("alias").description("\u7BA1\u7406\u547D\u4EE4\u522B\u540D\uFF08\u5217\u51FA / \u8BBE\u7F6E / \u79FB\u9664\uFF09").action(() => {
|
|
3768
3998
|
handleAliasList();
|
|
3769
3999
|
});
|
|
3770
4000
|
aliasCmd.command("list").description("\u5217\u51FA\u6240\u6709\u522B\u540D").action(() => {
|
|
@@ -3779,8 +4009,8 @@ function registerAliasCommand(program2) {
|
|
|
3779
4009
|
}
|
|
3780
4010
|
|
|
3781
4011
|
// src/commands/projects.ts
|
|
3782
|
-
import { existsSync as
|
|
3783
|
-
import { join as
|
|
4012
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
|
|
4013
|
+
import { join as join8 } from "path";
|
|
3784
4014
|
import chalk12 from "chalk";
|
|
3785
4015
|
function registerProjectsCommand(program2) {
|
|
3786
4016
|
program2.command("projects [name]").description("\u5C55\u793A\u6240\u6709\u9879\u76EE\u7684 worktree \u6982\u89C8\uFF0C\u6216\u67E5\u770B\u6307\u5B9A\u9879\u76EE\u7684 worktree \u8BE6\u60C5").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((name, options) => {
|
|
@@ -3804,8 +4034,8 @@ function handleProjectsOverview(json) {
|
|
|
3804
4034
|
printProjectsOverviewAsText(result);
|
|
3805
4035
|
}
|
|
3806
4036
|
function handleProjectDetail(name, json) {
|
|
3807
|
-
const projectDir =
|
|
3808
|
-
if (!
|
|
4037
|
+
const projectDir = join8(WORKTREES_DIR, name);
|
|
4038
|
+
if (!existsSync10(projectDir)) {
|
|
3809
4039
|
printError(MESSAGES.PROJECTS_NOT_FOUND(name));
|
|
3810
4040
|
process.exit(1);
|
|
3811
4041
|
}
|
|
@@ -3818,7 +4048,7 @@ function handleProjectDetail(name, json) {
|
|
|
3818
4048
|
printProjectDetailAsText(result);
|
|
3819
4049
|
}
|
|
3820
4050
|
function collectProjectsOverview() {
|
|
3821
|
-
if (!
|
|
4051
|
+
if (!existsSync10(WORKTREES_DIR)) {
|
|
3822
4052
|
return { projects: [], totalProjects: 0, totalDiskUsage: 0 };
|
|
3823
4053
|
}
|
|
3824
4054
|
const entries = readdirSync5(WORKTREES_DIR, { withFileTypes: true });
|
|
@@ -3827,7 +4057,7 @@ function collectProjectsOverview() {
|
|
|
3827
4057
|
if (!entry.isDirectory()) {
|
|
3828
4058
|
continue;
|
|
3829
4059
|
}
|
|
3830
|
-
const projectDir =
|
|
4060
|
+
const projectDir = join8(WORKTREES_DIR, entry.name);
|
|
3831
4061
|
const overview = collectSingleProjectOverview(entry.name, projectDir);
|
|
3832
4062
|
projects.push(overview);
|
|
3833
4063
|
}
|
|
@@ -3844,7 +4074,7 @@ function collectSingleProjectOverview(name, projectDir) {
|
|
|
3844
4074
|
const worktreeDirs = subEntries.filter((e) => e.isDirectory());
|
|
3845
4075
|
const worktreeCount = worktreeDirs.length;
|
|
3846
4076
|
const diskUsage = calculateDirSize(projectDir);
|
|
3847
|
-
const lastActiveTime = resolveProjectLastActiveTime(projectDir, worktreeDirs.map((e) =>
|
|
4077
|
+
const lastActiveTime = resolveProjectLastActiveTime(projectDir, worktreeDirs.map((e) => join8(projectDir, e.name)));
|
|
3848
4078
|
return {
|
|
3849
4079
|
name,
|
|
3850
4080
|
worktreeCount,
|
|
@@ -3859,7 +4089,7 @@ function collectProjectDetail(name, projectDir) {
|
|
|
3859
4089
|
if (!entry.isDirectory()) {
|
|
3860
4090
|
continue;
|
|
3861
4091
|
}
|
|
3862
|
-
const wtPath =
|
|
4092
|
+
const wtPath = join8(projectDir, entry.name);
|
|
3863
4093
|
const detail = collectSingleWorktreeDetail(entry.name, wtPath);
|
|
3864
4094
|
worktrees.push(detail);
|
|
3865
4095
|
}
|
|
@@ -3958,7 +4188,7 @@ function printWorktreeDetailItem(wt) {
|
|
|
3958
4188
|
}
|
|
3959
4189
|
|
|
3960
4190
|
// src/commands/completion.ts
|
|
3961
|
-
import { readFileSync as
|
|
4191
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync12 } from "fs";
|
|
3962
4192
|
import { resolve as resolve2 } from "path";
|
|
3963
4193
|
import { homedir as homedir2 } from "os";
|
|
3964
4194
|
|
|
@@ -4009,14 +4239,14 @@ compdef _clawt_completion clawt
|
|
|
4009
4239
|
}
|
|
4010
4240
|
|
|
4011
4241
|
// src/utils/completion-engine.ts
|
|
4012
|
-
import { existsSync as
|
|
4013
|
-
import { join as
|
|
4242
|
+
import { existsSync as existsSync11, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
|
|
4243
|
+
import { join as join9, dirname, basename as basename2 } from "path";
|
|
4014
4244
|
function completeFilePath(partial) {
|
|
4015
4245
|
const cwd = process.cwd();
|
|
4016
4246
|
const hasDir = partial.includes("/");
|
|
4017
|
-
const searchDir = hasDir ?
|
|
4247
|
+
const searchDir = hasDir ? join9(cwd, dirname(partial)) : cwd;
|
|
4018
4248
|
const prefix = hasDir ? basename2(partial) : partial;
|
|
4019
|
-
if (!
|
|
4249
|
+
if (!existsSync11(searchDir)) {
|
|
4020
4250
|
return [];
|
|
4021
4251
|
}
|
|
4022
4252
|
const entries = readdirSync6(searchDir);
|
|
@@ -4025,7 +4255,7 @@ function completeFilePath(partial) {
|
|
|
4025
4255
|
for (const entry of entries) {
|
|
4026
4256
|
if (!entry.startsWith(prefix)) continue;
|
|
4027
4257
|
if (entry.startsWith(".")) continue;
|
|
4028
|
-
const fullPath =
|
|
4258
|
+
const fullPath = join9(searchDir, entry);
|
|
4029
4259
|
try {
|
|
4030
4260
|
const stat = statSync5(fullPath);
|
|
4031
4261
|
if (stat.isDirectory()) {
|
|
@@ -4114,15 +4344,15 @@ function generateCompletions(program2, args) {
|
|
|
4114
4344
|
|
|
4115
4345
|
// src/commands/completion.ts
|
|
4116
4346
|
function appendToFile(filePath, content) {
|
|
4117
|
-
if (
|
|
4118
|
-
const current =
|
|
4347
|
+
if (existsSync12(filePath)) {
|
|
4348
|
+
const current = readFileSync6(filePath, "utf-8");
|
|
4119
4349
|
if (current.includes("clawt completion")) {
|
|
4120
4350
|
printInfo(MESSAGES.COMPLETION_INSTALL_EXISTS + ": " + filePath);
|
|
4121
4351
|
return;
|
|
4122
4352
|
}
|
|
4123
4353
|
content = current + content;
|
|
4124
4354
|
}
|
|
4125
|
-
|
|
4355
|
+
writeFileSync5(filePath, content, "utf-8");
|
|
4126
4356
|
printSuccess(MESSAGES.COMPLETION_INSTALL_SUCCESS + ": " + filePath);
|
|
4127
4357
|
printInfo(MESSAGES.COMPLETION_INSTALL_RESTART(filePath));
|
|
4128
4358
|
}
|
|
@@ -4169,6 +4399,37 @@ function registerCompletionCommand(program2) {
|
|
|
4169
4399
|
});
|
|
4170
4400
|
}
|
|
4171
4401
|
|
|
4402
|
+
// src/commands/init.ts
|
|
4403
|
+
import { Command as Cmd } from "commander";
|
|
4404
|
+
function registerInitCommand(program2) {
|
|
4405
|
+
const initCmd = program2.command("init").description("\u521D\u59CB\u5316\u9879\u76EE\u7EA7\u914D\u7F6E\uFF0C\u8BBE\u7F6E\u4E3B\u5DE5\u4F5C\u5206\u652F").option("-b, --branch <branchName>", "\u6307\u5B9A\u4E3B\u5DE5\u4F5C\u5206\u652F\u540D\uFF08\u9ED8\u8BA4\u4F7F\u7528\u5F53\u524D\u5206\u652F\uFF09").action(async (options) => {
|
|
4406
|
+
await handleInit(options);
|
|
4407
|
+
});
|
|
4408
|
+
initCmd.addCommand(
|
|
4409
|
+
new Cmd("show").description("\u5C55\u793A\u5F53\u524D\u9879\u76EE\u7684 init \u914D\u7F6E").action(() => {
|
|
4410
|
+
handleInitShow();
|
|
4411
|
+
})
|
|
4412
|
+
);
|
|
4413
|
+
}
|
|
4414
|
+
function handleInitShow() {
|
|
4415
|
+
validateMainWorktree();
|
|
4416
|
+
const config2 = requireProjectConfig();
|
|
4417
|
+
const configJson = safeStringify(config2);
|
|
4418
|
+
printInfo(MESSAGES.INIT_SHOW(configJson));
|
|
4419
|
+
}
|
|
4420
|
+
async function handleInit(options) {
|
|
4421
|
+
validateMainWorktree();
|
|
4422
|
+
const existingConfig = loadProjectConfig();
|
|
4423
|
+
const branchName = options.branch || getCurrentBranch();
|
|
4424
|
+
logger.info(`init \u547D\u4EE4\u6267\u884C\uFF0C\u4E3B\u5DE5\u4F5C\u5206\u652F: ${branchName}`);
|
|
4425
|
+
saveProjectConfig({ clawtMainWorkBranch: branchName });
|
|
4426
|
+
if (existingConfig) {
|
|
4427
|
+
printSuccess(MESSAGES.INIT_UPDATED(existingConfig.clawtMainWorkBranch, branchName));
|
|
4428
|
+
} else {
|
|
4429
|
+
printSuccess(MESSAGES.INIT_SUCCESS(branchName));
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4172
4433
|
// src/index.ts
|
|
4173
4434
|
var require2 = createRequire(import.meta.url);
|
|
4174
4435
|
var { version } = require2("../package.json");
|
|
@@ -4194,6 +4455,7 @@ registerStatusCommand(program);
|
|
|
4194
4455
|
registerAliasCommand(program);
|
|
4195
4456
|
registerProjectsCommand(program);
|
|
4196
4457
|
registerCompletionCommand(program);
|
|
4458
|
+
registerInitCommand(program);
|
|
4197
4459
|
var config = loadConfig();
|
|
4198
4460
|
applyAliases(program, config.aliases);
|
|
4199
4461
|
process.on("uncaughtException", (error) => {
|