clawt 1.0.5 → 1.1.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/.claude/agent-memory/docs-sync-updater/MEMORY.md +14 -2
- package/CLAUDE.md +2 -2
- package/README.md +11 -1
- package/dist/index.js +126 -9
- package/dist/postinstall.js +27 -6
- package/docs/spec.md +42 -1
- package/package.json +1 -1
- package/src/commands/config.ts +56 -0
- package/src/commands/list.ts +14 -1
- package/src/constants/config.ts +48 -8
- package/src/constants/index.ts +1 -1
- package/src/constants/messages.ts +2 -0
- package/src/index.ts +2 -0
- package/src/types/config.ts +13 -0
- package/src/types/index.ts +2 -2
- package/src/types/worktree.ts +12 -0
- package/src/utils/formatter.ts +30 -0
- package/src/utils/git.ts +55 -0
- package/src/utils/index.ts +4 -2
- package/src/utils/worktree.ts +21 -2
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
### docs/spec.md
|
|
6
6
|
- 完整的软件规格说明,包含 7 大章节
|
|
7
|
-
- 命令流程在 `5. 需求场景详细设计` 下,每个命令一个子章节(5.1-5.
|
|
7
|
+
- 命令流程在 `5. 需求场景详细设计` 下,每个命令一个子章节(5.1-5.10)
|
|
8
8
|
- run 命令对应 `5.2 批量创建 Worktree + 执行 Claude Code 任务`,流程按步骤编号描述
|
|
9
9
|
- merge 命令对应 `5.6 合并验证过的分支`,流程按步骤编号描述
|
|
10
|
+
- config 命令对应 `5.10 查看全局配置`,只读展示配置
|
|
10
11
|
- 配置项说明在 `5.7 默认配置文件` 章节的表格中
|
|
11
12
|
- 更新模式:新增步骤时追加编号,配置项影响范围变化时更新说明列
|
|
12
13
|
|
|
@@ -34,15 +35,26 @@
|
|
|
34
35
|
## 配置项同步检查点
|
|
35
36
|
|
|
36
37
|
配置项变更时需在以下 5 处保持一致:
|
|
37
|
-
1. `src/constants/config.ts` —
|
|
38
|
+
1. `src/constants/config.ts` — CONFIG_DEFINITIONS 对象(单一数据源,包含 defaultValue + description)
|
|
38
39
|
2. `src/types/config.ts` — ClawtConfig 接口
|
|
39
40
|
3. `docs/spec.md` — 5.7 默认配置文件章节(JSON 示例 + 配置项表格)
|
|
40
41
|
4. `CLAUDE.md` — 关键约定段落中的配置描述
|
|
41
42
|
5. `README.md` — 配置文件章节(JSON 示例 + 配置项表格)
|
|
42
43
|
|
|
44
|
+
## 配置架构
|
|
45
|
+
|
|
46
|
+
- `CONFIG_DEFINITIONS` 是配置项的单一数据源,定义在 `src/constants/config.ts`
|
|
47
|
+
- `DEFAULT_CONFIG` 和 `CONFIG_DESCRIPTIONS` 均从 `CONFIG_DEFINITIONS` 派生
|
|
48
|
+
- 新增配置项时只需在 `CONFIG_DEFINITIONS` 中维护,派生值自动同步
|
|
49
|
+
- 类型:`ConfigItemDefinition<T>` 和 `ConfigDefinitions` 定义在 `src/types/config.ts`
|
|
50
|
+
|
|
43
51
|
## run 命令双模式
|
|
44
52
|
|
|
45
53
|
run 命令有两种模式(自 claudeCodeCommand 特性后):
|
|
46
54
|
- 不传 `--tasks`:交互式界面模式(单 worktree + `launchInteractiveClaude` + spawnSync)
|
|
47
55
|
- 传 `--tasks`:并行任务模式(多 worktree + `executeClaudeTask` + spawnProcess)
|
|
48
56
|
- CLAUDE.md 中的核心流程按模式分段描述
|
|
57
|
+
|
|
58
|
+
## 命令清单(7 个)
|
|
59
|
+
|
|
60
|
+
`create`、`run`、`list`、`remove`、`validate`、`merge`、`config`
|
package/CLAUDE.md
CHANGED
|
@@ -24,7 +24,7 @@ npm i -g . # 本地全局安装进行测试
|
|
|
24
24
|
|
|
25
25
|
每个命令为独立文件 `src/commands/<name>.ts`,导出 `registerXxxCommand(program)` 函数,在 `src/index.ts` 中统一注册到 Commander。命令内部逻辑封装在对应的 `handleXxx` 函数中。
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
七个命令:`create`、`run`、`list`、`remove`、`validate`、`merge`、`config`。
|
|
28
28
|
|
|
29
29
|
### 核心流程(run 命令)
|
|
30
30
|
|
|
@@ -65,7 +65,7 @@ run 命令有两种模式:
|
|
|
65
65
|
|
|
66
66
|
- 所有命令执行前都会调用 `validateMainWorktree()` 确保在主 worktree 根目录(`git rev-parse --git-common-dir === ".git"`)
|
|
67
67
|
- Worktree 统一存放在 `~/.clawt/worktrees/<projectName>/` 下
|
|
68
|
-
- 全局配置文件 `~/.clawt/config.json`,postinstall 时自动创建/合并,包含 `autoDeleteBranch
|
|
68
|
+
- 全局配置文件 `~/.clawt/config.json`,postinstall 时自动创建/合并,包含 `autoDeleteBranch`(是否自动删除分支)、`claudeCodeCommand`(Claude Code CLI 启动指令)、`autoPullPush`(merge 后是否自动 pull/push)三个配置项。配置项以 `CONFIG_DEFINITIONS` 为单一数据源,`DEFAULT_CONFIG` 和 `CONFIG_DESCRIPTIONS` 均从中派生
|
|
69
69
|
- shell 命令执行有同步(`execCommand` → `execSync`)和异步(`spawnProcess` → `spawn`)两种方式
|
|
70
70
|
- 项目为纯 ESM(`"type": "module"`),模块导入需带 `.js` 后缀
|
|
71
71
|
- 分支名特殊字符会被 `sanitizeBranchName()` 自动清理
|
package/README.md
CHANGED
|
@@ -139,6 +139,14 @@ clawt list
|
|
|
139
139
|
|
|
140
140
|
列出当前项目在 `~/.clawt/worktrees/` 下的所有 worktree 及对应分支。
|
|
141
141
|
|
|
142
|
+
### `clawt config` — 查看全局配置
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
clawt config
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
读取并展示全局配置文件 `~/.clawt/config.json` 中的所有配置项,包括每项的当前值和描述说明。编辑配置需直接修改配置文件。
|
|
149
|
+
|
|
142
150
|
## 配置文件
|
|
143
151
|
|
|
144
152
|
安装后会自动在 `~/.clawt/config.json` 生成全局配置文件:
|
|
@@ -146,7 +154,8 @@ clawt list
|
|
|
146
154
|
```json
|
|
147
155
|
{
|
|
148
156
|
"autoDeleteBranch": false,
|
|
149
|
-
"claudeCodeCommand": "claude"
|
|
157
|
+
"claudeCodeCommand": "claude",
|
|
158
|
+
"autoPullPush": false
|
|
150
159
|
}
|
|
151
160
|
```
|
|
152
161
|
|
|
@@ -154,6 +163,7 @@ clawt list
|
|
|
154
163
|
| ------ | ---- | ------ | ---- |
|
|
155
164
|
| `autoDeleteBranch` | `boolean` | `false` | 移除 worktree 时自动删除对应本地分支;merge 成功后自动清理 worktree 和分支;run 中断后自动清理本次创建的 worktree 和分支 |
|
|
156
165
|
| `claudeCodeCommand` | `string` | `"claude"` | Claude Code CLI 启动指令,用于 `clawt run` 不传 `--tasks` 时在 worktree 中打开交互式界面 |
|
|
166
|
+
| `autoPullPush` | `boolean` | `false` | merge 成功后是否自动执行 git pull 和 git push |
|
|
157
167
|
|
|
158
168
|
## 分支名规则
|
|
159
169
|
|
package/dist/index.js
CHANGED
|
@@ -79,7 +79,9 @@ var MESSAGES = {
|
|
|
79
79
|
/** 粗分隔线 */
|
|
80
80
|
DOUBLE_SEPARATOR: "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
|
|
81
81
|
/** 创建数量参数无效 */
|
|
82
|
-
INVALID_COUNT: (value) => `\u65E0\u6548\u7684\u521B\u5EFA\u6570\u91CF: "${value}"\uFF0C\u8BF7\u8F93\u5165\u6B63\u6574\u6570
|
|
82
|
+
INVALID_COUNT: (value) => `\u65E0\u6548\u7684\u521B\u5EFA\u6570\u91CF: "${value}"\uFF0C\u8BF7\u8F93\u5165\u6B63\u6574\u6570`,
|
|
83
|
+
/** worktree 状态获取失败 */
|
|
84
|
+
WORKTREE_STATUS_UNAVAILABLE: "(\u72B6\u6001\u4E0D\u53EF\u7528)"
|
|
83
85
|
};
|
|
84
86
|
|
|
85
87
|
// src/constants/exitCodes.ts
|
|
@@ -94,13 +96,34 @@ var EXIT_CODES = {
|
|
|
94
96
|
|
|
95
97
|
// src/constants/config.ts
|
|
96
98
|
var APPEND_SYSTEM_PROMPT = "After the code execution is completed, it is prohibited to build the project for verification.";
|
|
97
|
-
var
|
|
98
|
-
autoDeleteBranch:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
var CONFIG_DEFINITIONS = {
|
|
100
|
+
autoDeleteBranch: {
|
|
101
|
+
defaultValue: false,
|
|
102
|
+
description: "\u79FB\u9664 worktree \u65F6\u662F\u5426\u81EA\u52A8\u5220\u9664\u5BF9\u5E94\u672C\u5730\u5206\u652F"
|
|
103
|
+
},
|
|
104
|
+
claudeCodeCommand: {
|
|
105
|
+
defaultValue: "claude",
|
|
106
|
+
description: "Claude Code CLI \u542F\u52A8\u6307\u4EE4"
|
|
107
|
+
},
|
|
108
|
+
autoPullPush: {
|
|
109
|
+
defaultValue: false,
|
|
110
|
+
description: "merge \u6210\u529F\u540E\u662F\u5426\u81EA\u52A8\u6267\u884C git pull \u548C git push"
|
|
111
|
+
}
|
|
103
112
|
};
|
|
113
|
+
function deriveDefaultConfig(definitions) {
|
|
114
|
+
const entries = Object.entries(definitions).map(
|
|
115
|
+
([key, def]) => [key, def.defaultValue]
|
|
116
|
+
);
|
|
117
|
+
return Object.fromEntries(entries);
|
|
118
|
+
}
|
|
119
|
+
function deriveConfigDescriptions(definitions) {
|
|
120
|
+
const entries = Object.entries(definitions).map(
|
|
121
|
+
([key, def]) => [key, def.description]
|
|
122
|
+
);
|
|
123
|
+
return Object.fromEntries(entries);
|
|
124
|
+
}
|
|
125
|
+
var DEFAULT_CONFIG = deriveDefaultConfig(CONFIG_DEFINITIONS);
|
|
126
|
+
var CONFIG_DESCRIPTIONS = deriveConfigDescriptions(CONFIG_DEFINITIONS);
|
|
104
127
|
|
|
105
128
|
// src/errors/index.ts
|
|
106
129
|
var ClawtError = class extends Error {
|
|
@@ -266,6 +289,33 @@ function hasLocalCommits(branchName, cwd) {
|
|
|
266
289
|
return false;
|
|
267
290
|
}
|
|
268
291
|
}
|
|
292
|
+
function getCommitCountAhead(branchName, cwd) {
|
|
293
|
+
const output = execCommand(`git rev-list --count HEAD..${branchName}`, { cwd });
|
|
294
|
+
return parseInt(output, 10) || 0;
|
|
295
|
+
}
|
|
296
|
+
function parseShortStat(output) {
|
|
297
|
+
let insertions = 0;
|
|
298
|
+
let deletions = 0;
|
|
299
|
+
const insertMatch = output.match(/(\d+)\s+insertion/);
|
|
300
|
+
if (insertMatch) {
|
|
301
|
+
insertions = parseInt(insertMatch[1], 10);
|
|
302
|
+
}
|
|
303
|
+
const deleteMatch = output.match(/(\d+)\s+deletion/);
|
|
304
|
+
if (deleteMatch) {
|
|
305
|
+
deletions = parseInt(deleteMatch[1], 10);
|
|
306
|
+
}
|
|
307
|
+
return { insertions, deletions };
|
|
308
|
+
}
|
|
309
|
+
function getDiffStat(branchName, worktreePath, cwd) {
|
|
310
|
+
const committedOutput = execCommand(`git diff --shortstat HEAD...${branchName}`, { cwd });
|
|
311
|
+
const committed = parseShortStat(committedOutput);
|
|
312
|
+
const uncommittedOutput = execCommand("git diff --shortstat HEAD", { cwd: worktreePath });
|
|
313
|
+
const uncommitted = parseShortStat(uncommittedOutput);
|
|
314
|
+
return {
|
|
315
|
+
insertions: committed.insertions + uncommitted.insertions,
|
|
316
|
+
deletions: committed.deletions + uncommitted.deletions
|
|
317
|
+
};
|
|
318
|
+
}
|
|
269
319
|
|
|
270
320
|
// src/utils/formatter.ts
|
|
271
321
|
import chalk from "chalk";
|
|
@@ -300,6 +350,22 @@ function confirmAction(question) {
|
|
|
300
350
|
});
|
|
301
351
|
});
|
|
302
352
|
}
|
|
353
|
+
function formatWorktreeStatus(status) {
|
|
354
|
+
const parts = [];
|
|
355
|
+
parts.push(chalk.yellow(`${status.commitCount} \u4E2A\u63D0\u4EA4`));
|
|
356
|
+
if (status.insertions === 0 && status.deletions === 0) {
|
|
357
|
+
parts.push("\u65E0\u53D8\u66F4");
|
|
358
|
+
} else {
|
|
359
|
+
const diffParts = [];
|
|
360
|
+
diffParts.push(chalk.green(`+${status.insertions}`));
|
|
361
|
+
diffParts.push(chalk.red(`-${status.deletions}`));
|
|
362
|
+
parts.push(diffParts.join(" "));
|
|
363
|
+
}
|
|
364
|
+
if (status.hasDirtyFiles) {
|
|
365
|
+
parts.push(chalk.gray("(\u672A\u63D0\u4EA4\u4FEE\u6539)"));
|
|
366
|
+
}
|
|
367
|
+
return parts.join(" ");
|
|
368
|
+
}
|
|
303
369
|
|
|
304
370
|
// src/utils/branch.ts
|
|
305
371
|
function sanitizeBranchName(branchName) {
|
|
@@ -428,6 +494,17 @@ function cleanupWorktrees(worktrees) {
|
|
|
428
494
|
const projectDir = getProjectWorktreeDir();
|
|
429
495
|
removeEmptyDir(projectDir);
|
|
430
496
|
}
|
|
497
|
+
function getWorktreeStatus(worktree) {
|
|
498
|
+
try {
|
|
499
|
+
const commitCount = getCommitCountAhead(worktree.branch);
|
|
500
|
+
const { insertions, deletions } = getDiffStat(worktree.branch, worktree.path);
|
|
501
|
+
const hasDirtyFiles = !isWorkingDirClean(worktree.path);
|
|
502
|
+
return { commitCount, insertions, deletions, hasDirtyFiles };
|
|
503
|
+
} catch (error) {
|
|
504
|
+
logger.error(`\u83B7\u53D6 worktree \u72B6\u6001\u5931\u8D25: ${worktree.path} - ${error}`);
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
431
508
|
|
|
432
509
|
// src/utils/config.ts
|
|
433
510
|
import { existsSync as existsSync4, readFileSync, writeFileSync } from "fs";
|
|
@@ -461,6 +538,7 @@ function ensureClawtDirs() {
|
|
|
461
538
|
import Enquirer from "enquirer";
|
|
462
539
|
|
|
463
540
|
// src/commands/list.ts
|
|
541
|
+
import chalk2 from "chalk";
|
|
464
542
|
function registerListCommand(program2) {
|
|
465
543
|
program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").action(() => {
|
|
466
544
|
handleList();
|
|
@@ -478,9 +556,15 @@ function handleList() {
|
|
|
478
556
|
} else {
|
|
479
557
|
for (const wt of worktrees) {
|
|
480
558
|
printInfo(` ${wt.path} [${wt.branch}]`);
|
|
559
|
+
const status = getWorktreeStatus(wt);
|
|
560
|
+
if (status) {
|
|
561
|
+
printInfo(` ${formatWorktreeStatus(status)}`);
|
|
562
|
+
} else {
|
|
563
|
+
printInfo(` ${chalk2.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
|
|
564
|
+
}
|
|
565
|
+
printInfo("");
|
|
481
566
|
}
|
|
482
|
-
printInfo(`
|
|
483
|
-
\u5171 ${worktrees.length} \u4E2A worktree`);
|
|
567
|
+
printInfo(`\u5171 ${worktrees.length} \u4E2A worktree`);
|
|
484
568
|
}
|
|
485
569
|
}
|
|
486
570
|
|
|
@@ -918,6 +1002,38 @@ async function handleMerge(options) {
|
|
|
918
1002
|
}
|
|
919
1003
|
}
|
|
920
1004
|
|
|
1005
|
+
// src/commands/config.ts
|
|
1006
|
+
import chalk3 from "chalk";
|
|
1007
|
+
function registerConfigCommand(program2) {
|
|
1008
|
+
program2.command("config").description("\u67E5\u770B\u5168\u5C40\u914D\u7F6E").action(() => {
|
|
1009
|
+
handleConfig();
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
function handleConfig() {
|
|
1013
|
+
const config = loadConfig();
|
|
1014
|
+
logger.info("config \u547D\u4EE4\u6267\u884C\uFF0C\u5C55\u793A\u5168\u5C40\u914D\u7F6E");
|
|
1015
|
+
printInfo(`
|
|
1016
|
+
${chalk3.dim("\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84:")} ${CONFIG_PATH}
|
|
1017
|
+
`);
|
|
1018
|
+
printSeparator();
|
|
1019
|
+
const keys = Object.keys(DEFAULT_CONFIG);
|
|
1020
|
+
for (const key of keys) {
|
|
1021
|
+
const value = config[key];
|
|
1022
|
+
const description = CONFIG_DESCRIPTIONS[key];
|
|
1023
|
+
const formattedValue = formatConfigValue(value);
|
|
1024
|
+
printInfo(` ${chalk3.bold(key)}: ${formattedValue}`);
|
|
1025
|
+
printInfo(` ${chalk3.dim(description)}
|
|
1026
|
+
`);
|
|
1027
|
+
}
|
|
1028
|
+
printSeparator();
|
|
1029
|
+
}
|
|
1030
|
+
function formatConfigValue(value) {
|
|
1031
|
+
if (typeof value === "boolean") {
|
|
1032
|
+
return value ? chalk3.green("true") : chalk3.yellow("false");
|
|
1033
|
+
}
|
|
1034
|
+
return chalk3.cyan(String(value));
|
|
1035
|
+
}
|
|
1036
|
+
|
|
921
1037
|
// src/index.ts
|
|
922
1038
|
var require2 = createRequire(import.meta.url);
|
|
923
1039
|
var { version } = require2("../package.json");
|
|
@@ -930,6 +1046,7 @@ registerRemoveCommand(program);
|
|
|
930
1046
|
registerRunCommand(program);
|
|
931
1047
|
registerValidateCommand(program);
|
|
932
1048
|
registerMergeCommand(program);
|
|
1049
|
+
registerConfigCommand(program);
|
|
933
1050
|
process.on("uncaughtException", (error) => {
|
|
934
1051
|
if (error instanceof ClawtError) {
|
|
935
1052
|
printError(error.message);
|
package/dist/postinstall.js
CHANGED
|
@@ -12,13 +12,34 @@ var LOGS_DIR = join(CLAWT_HOME, "logs");
|
|
|
12
12
|
var WORKTREES_DIR = join(CLAWT_HOME, "worktrees");
|
|
13
13
|
|
|
14
14
|
// src/constants/config.ts
|
|
15
|
-
var
|
|
16
|
-
autoDeleteBranch:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
var CONFIG_DEFINITIONS = {
|
|
16
|
+
autoDeleteBranch: {
|
|
17
|
+
defaultValue: false,
|
|
18
|
+
description: "\u79FB\u9664 worktree \u65F6\u662F\u5426\u81EA\u52A8\u5220\u9664\u5BF9\u5E94\u672C\u5730\u5206\u652F"
|
|
19
|
+
},
|
|
20
|
+
claudeCodeCommand: {
|
|
21
|
+
defaultValue: "claude",
|
|
22
|
+
description: "Claude Code CLI \u542F\u52A8\u6307\u4EE4"
|
|
23
|
+
},
|
|
24
|
+
autoPullPush: {
|
|
25
|
+
defaultValue: false,
|
|
26
|
+
description: "merge \u6210\u529F\u540E\u662F\u5426\u81EA\u52A8\u6267\u884C git pull \u548C git push"
|
|
27
|
+
}
|
|
21
28
|
};
|
|
29
|
+
function deriveDefaultConfig(definitions) {
|
|
30
|
+
const entries = Object.entries(definitions).map(
|
|
31
|
+
([key, def]) => [key, def.defaultValue]
|
|
32
|
+
);
|
|
33
|
+
return Object.fromEntries(entries);
|
|
34
|
+
}
|
|
35
|
+
function deriveConfigDescriptions(definitions) {
|
|
36
|
+
const entries = Object.entries(definitions).map(
|
|
37
|
+
([key, def]) => [key, def.description]
|
|
38
|
+
);
|
|
39
|
+
return Object.fromEntries(entries);
|
|
40
|
+
}
|
|
41
|
+
var DEFAULT_CONFIG = deriveDefaultConfig(CONFIG_DEFINITIONS);
|
|
42
|
+
var CONFIG_DESCRIPTIONS = deriveConfigDescriptions(CONFIG_DEFINITIONS);
|
|
22
43
|
|
|
23
44
|
// scripts/postinstall.ts
|
|
24
45
|
var CLAWT_HOME2 = join2(homedir2(), ".clawt");
|
package/docs/spec.md
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
- [5.7 默认配置文件](#57-默认配置文件)
|
|
22
22
|
- [5.8 获取当前项目所有 Worktree](#58-获取当前项目所有-worktree)
|
|
23
23
|
- [5.9 日志系统](#59-日志系统)
|
|
24
|
+
- [5.10 查看全局配置](#510-查看全局配置)
|
|
24
25
|
- [6. 错误处理规范](#6-错误处理规范)
|
|
25
26
|
- [7. 非功能性需求](#7-非功能性需求)
|
|
26
27
|
|
|
@@ -157,6 +158,7 @@ git show-ref --verify refs/heads/<branchName> 2>/dev/null
|
|
|
157
158
|
| `clawt merge` | 合并某个已验证的 worktree 分支到主 worktree | 5.6 |
|
|
158
159
|
| `clawt remove` | 移除 worktree(支持单个/批量/全部) | 5.5 |
|
|
159
160
|
| `clawt list` | 列出当前项目所有 worktree | 5.8 |
|
|
161
|
+
| `clawt config` | 查看全局配置 | 5.10 |
|
|
160
162
|
|
|
161
163
|
所有命令执行前,都必须先执行**主 worktree 校验**(见 [2.1](#21-主-worktree-的定义与定位规则))。
|
|
162
164
|
|
|
@@ -580,7 +582,8 @@ clawt merge -b <branchName> [-m <commitMessage>]
|
|
|
580
582
|
```json
|
|
581
583
|
{
|
|
582
584
|
"autoDeleteBranch": false,
|
|
583
|
-
"claudeCodeCommand": "claude"
|
|
585
|
+
"claudeCodeCommand": "claude",
|
|
586
|
+
"autoPullPush": false
|
|
584
587
|
}
|
|
585
588
|
```
|
|
586
589
|
|
|
@@ -590,6 +593,7 @@ clawt merge -b <branchName> [-m <commitMessage>]
|
|
|
590
593
|
| ------------------ | --------- | --------- | -------------------------------------------------- |
|
|
591
594
|
| `autoDeleteBranch` | `boolean` | `false` | 移除 worktree 时是否自动删除对应本地分支(无需每次确认);merge 成功后是否自动清理 worktree 和分支;run 任务被中断(Ctrl+C)后是否自动清理本次创建的 worktree 和分支 |
|
|
592
595
|
| `claudeCodeCommand` | `string` | `"claude"` | Claude Code CLI 启动指令,用于 `clawt run` 不传 `--tasks` 时在 worktree 中打开交互式界面 |
|
|
596
|
+
| `autoPullPush` | `boolean` | `false` | merge 成功后是否自动执行 git pull 和 git push |
|
|
593
597
|
|
|
594
598
|
---
|
|
595
599
|
|
|
@@ -665,6 +669,43 @@ clawt list
|
|
|
665
669
|
|
|
666
670
|
---
|
|
667
671
|
|
|
672
|
+
### 5.10 查看全局配置
|
|
673
|
+
|
|
674
|
+
**命令:**
|
|
675
|
+
|
|
676
|
+
```bash
|
|
677
|
+
clawt config
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
**运行流程:**
|
|
681
|
+
|
|
682
|
+
1. 读取全局配置文件 `~/.clawt/config.json`
|
|
683
|
+
2. 遍历所有配置项(以 `CONFIG_DEFINITIONS` 为单一数据源),逐项展示:
|
|
684
|
+
- 配置项名称(粗体)
|
|
685
|
+
- 当前值(布尔值绿色/黄色,字符串青色)
|
|
686
|
+
- 配置项描述(灰色)
|
|
687
|
+
3. 输出配置文件路径,提示用户可直接编辑
|
|
688
|
+
|
|
689
|
+
**输出格式:**
|
|
690
|
+
|
|
691
|
+
```
|
|
692
|
+
配置文件路径: ~/.clawt/config.json
|
|
693
|
+
────────────────────────────────────────
|
|
694
|
+
autoDeleteBranch: false
|
|
695
|
+
移除 worktree 时是否自动删除对应本地分支
|
|
696
|
+
|
|
697
|
+
claudeCodeCommand: claude
|
|
698
|
+
Claude Code CLI 启动指令
|
|
699
|
+
|
|
700
|
+
autoPullPush: false
|
|
701
|
+
merge 成功后是否自动执行 git pull 和 git push
|
|
702
|
+
|
|
703
|
+
────────────────────────────────────────
|
|
704
|
+
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
---
|
|
708
|
+
|
|
668
709
|
## 6. 错误处理规范
|
|
669
710
|
|
|
670
711
|
### 6.1 通用错误处理
|
package/package.json
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { CONFIG_PATH, DEFAULT_CONFIG, CONFIG_DESCRIPTIONS } from '../constants/index.js';
|
|
4
|
+
import { logger } from '../logger/index.js';
|
|
5
|
+
import { loadConfig, printInfo, printSeparator } from '../utils/index.js';
|
|
6
|
+
import type { ClawtConfig } from '../types/index.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 注册 config 命令:查看全局配置
|
|
10
|
+
* @param {Command} program - Commander 实例
|
|
11
|
+
*/
|
|
12
|
+
export function registerConfigCommand(program: Command): void {
|
|
13
|
+
program
|
|
14
|
+
.command('config')
|
|
15
|
+
.description('查看全局配置')
|
|
16
|
+
.action(() => {
|
|
17
|
+
handleConfig();
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 执行 config 命令的核心逻辑,读取并展示配置列表
|
|
23
|
+
*/
|
|
24
|
+
function handleConfig(): void {
|
|
25
|
+
const config = loadConfig();
|
|
26
|
+
|
|
27
|
+
logger.info('config 命令执行,展示全局配置');
|
|
28
|
+
|
|
29
|
+
printInfo(`\n${chalk.dim('配置文件路径:')} ${CONFIG_PATH}\n`);
|
|
30
|
+
printSeparator();
|
|
31
|
+
|
|
32
|
+
const keys = Object.keys(DEFAULT_CONFIG) as Array<keyof ClawtConfig>;
|
|
33
|
+
|
|
34
|
+
for (const key of keys) {
|
|
35
|
+
const value = config[key];
|
|
36
|
+
const description = CONFIG_DESCRIPTIONS[key];
|
|
37
|
+
const formattedValue = formatConfigValue(value);
|
|
38
|
+
|
|
39
|
+
printInfo(` ${chalk.bold(key)}: ${formattedValue}`);
|
|
40
|
+
printInfo(` ${chalk.dim(description)}\n`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
printSeparator();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 格式化配置值的显示样式
|
|
48
|
+
* @param {ClawtConfig[keyof ClawtConfig]} value - 配置值
|
|
49
|
+
* @returns {string} 格式化后的字符串
|
|
50
|
+
*/
|
|
51
|
+
function formatConfigValue(value: ClawtConfig[keyof ClawtConfig]): string {
|
|
52
|
+
if (typeof value === 'boolean') {
|
|
53
|
+
return value ? chalk.green('true') : chalk.yellow('false');
|
|
54
|
+
}
|
|
55
|
+
return chalk.cyan(String(value));
|
|
56
|
+
}
|
package/src/commands/list.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
2
3
|
import { MESSAGES } from '../constants/index.js';
|
|
3
4
|
import { logger } from '../logger/index.js';
|
|
4
5
|
import {
|
|
5
6
|
validateMainWorktree,
|
|
6
7
|
getProjectName,
|
|
7
8
|
getProjectWorktrees,
|
|
9
|
+
getWorktreeStatus,
|
|
10
|
+
formatWorktreeStatus,
|
|
8
11
|
printInfo,
|
|
9
12
|
} from '../utils/index.js';
|
|
10
13
|
|
|
@@ -39,7 +42,17 @@ function handleList(): void {
|
|
|
39
42
|
} else {
|
|
40
43
|
for (const wt of worktrees) {
|
|
41
44
|
printInfo(` ${wt.path} [${wt.branch}]`);
|
|
45
|
+
|
|
46
|
+
// 获取并展示 worktree 变更状态
|
|
47
|
+
const status = getWorktreeStatus(wt);
|
|
48
|
+
if (status) {
|
|
49
|
+
printInfo(` ${formatWorktreeStatus(status)}`);
|
|
50
|
+
} else {
|
|
51
|
+
printInfo(` ${chalk.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
printInfo('');
|
|
42
55
|
}
|
|
43
|
-
printInfo(
|
|
56
|
+
printInfo(`共 ${worktrees.length} 个 worktree`);
|
|
44
57
|
}
|
|
45
58
|
}
|
package/src/constants/config.ts
CHANGED
|
@@ -1,14 +1,54 @@
|
|
|
1
|
-
import type { ClawtConfig } from '../types/index.js';
|
|
1
|
+
import type { ClawtConfig, ConfigDefinitions } from '../types/index.js';
|
|
2
2
|
|
|
3
3
|
/** Claude Code 系统约束提示,禁止代码执行完成后构建项目验证 */
|
|
4
4
|
export const APPEND_SYSTEM_PROMPT =
|
|
5
5
|
'After the code execution is completed, it is prohibited to build the project for verification.';
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
/**
|
|
8
|
+
* 配置项完整定义(单一数据源)
|
|
9
|
+
* 新增配置项时只需在此处维护,DEFAULT_CONFIG 和 CONFIG_DESCRIPTIONS 会自动同步
|
|
10
|
+
*/
|
|
11
|
+
export const CONFIG_DEFINITIONS: ConfigDefinitions = {
|
|
12
|
+
autoDeleteBranch: {
|
|
13
|
+
defaultValue: false,
|
|
14
|
+
description: '移除 worktree 时是否自动删除对应本地分支',
|
|
15
|
+
},
|
|
16
|
+
claudeCodeCommand: {
|
|
17
|
+
defaultValue: 'claude',
|
|
18
|
+
description: 'Claude Code CLI 启动指令',
|
|
19
|
+
},
|
|
20
|
+
autoPullPush: {
|
|
21
|
+
defaultValue: false,
|
|
22
|
+
description: 'merge 成功后是否自动执行 git pull 和 git push',
|
|
23
|
+
},
|
|
14
24
|
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 从 CONFIG_DEFINITIONS 派生默认配置
|
|
28
|
+
* @param {ConfigDefinitions} definitions - 配置项完整定义
|
|
29
|
+
* @returns {ClawtConfig} 默认配置对象
|
|
30
|
+
*/
|
|
31
|
+
function deriveDefaultConfig(definitions: ConfigDefinitions): ClawtConfig {
|
|
32
|
+
const entries = Object.entries(definitions).map(
|
|
33
|
+
([key, def]) => [key, def.defaultValue],
|
|
34
|
+
);
|
|
35
|
+
return Object.fromEntries(entries) as ClawtConfig;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 从 CONFIG_DEFINITIONS 派生配置项描述映射
|
|
40
|
+
* @param {ConfigDefinitions} definitions - 配置项完整定义
|
|
41
|
+
* @returns {Record<keyof ClawtConfig, string>} 配置项描述映射
|
|
42
|
+
*/
|
|
43
|
+
function deriveConfigDescriptions(definitions: ConfigDefinitions): Record<keyof ClawtConfig, string> {
|
|
44
|
+
const entries = Object.entries(definitions).map(
|
|
45
|
+
([key, def]) => [key, def.description],
|
|
46
|
+
);
|
|
47
|
+
return Object.fromEntries(entries) as Record<keyof ClawtConfig, string>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 默认配置 */
|
|
51
|
+
export const DEFAULT_CONFIG: ClawtConfig = deriveDefaultConfig(CONFIG_DEFINITIONS);
|
|
52
|
+
|
|
53
|
+
/** 配置项描述映射 */
|
|
54
|
+
export const CONFIG_DESCRIPTIONS: Record<keyof ClawtConfig, string> = deriveConfigDescriptions(CONFIG_DEFINITIONS);
|
package/src/constants/index.ts
CHANGED
|
@@ -3,4 +3,4 @@ export { INVALID_BRANCH_CHARS } from './branch.js';
|
|
|
3
3
|
export { MESSAGES } from './messages.js';
|
|
4
4
|
export { EXIT_CODES } from './exitCodes.js';
|
|
5
5
|
export { ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, PASTE_THRESHOLD_MS } from './terminal.js';
|
|
6
|
-
export { DEFAULT_CONFIG, APPEND_SYSTEM_PROMPT } from './config.js';
|
|
6
|
+
export { DEFAULT_CONFIG, CONFIG_DESCRIPTIONS, APPEND_SYSTEM_PROMPT } from './config.js';
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { registerRemoveCommand } from './commands/remove.js';
|
|
|
10
10
|
import { registerRunCommand } from './commands/run.js';
|
|
11
11
|
import { registerValidateCommand } from './commands/validate.js';
|
|
12
12
|
import { registerMergeCommand } from './commands/merge.js';
|
|
13
|
+
import { registerConfigCommand } from './commands/config.js';
|
|
13
14
|
|
|
14
15
|
// 从 package.json 读取版本号,避免硬编码
|
|
15
16
|
const require = createRequire(import.meta.url);
|
|
@@ -32,6 +33,7 @@ registerRemoveCommand(program);
|
|
|
32
33
|
registerRunCommand(program);
|
|
33
34
|
registerValidateCommand(program);
|
|
34
35
|
registerMergeCommand(program);
|
|
36
|
+
registerConfigCommand(program);
|
|
35
37
|
|
|
36
38
|
// 全局未捕获异常处理
|
|
37
39
|
process.on('uncaughtException', (error) => {
|
package/src/types/config.ts
CHANGED
|
@@ -7,3 +7,16 @@ export interface ClawtConfig {
|
|
|
7
7
|
/** merge 成功后是否自动执行 git pull 和 git push */
|
|
8
8
|
autoPullPush: boolean;
|
|
9
9
|
}
|
|
10
|
+
|
|
11
|
+
/** 单个配置项的完整定义(默认值 + 描述) */
|
|
12
|
+
export interface ConfigItemDefinition<T> {
|
|
13
|
+
/** 默认值 */
|
|
14
|
+
defaultValue: T;
|
|
15
|
+
/** 配置项描述,用于 config 命令展示 */
|
|
16
|
+
description: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 所有配置项的完整定义映射 */
|
|
20
|
+
export type ConfigDefinitions = {
|
|
21
|
+
[K in keyof ClawtConfig]: ConfigItemDefinition<ClawtConfig[K]>;
|
|
22
|
+
};
|
package/src/types/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { ClawtConfig } from './config.js';
|
|
1
|
+
export type { ClawtConfig, ConfigItemDefinition, ConfigDefinitions } from './config.js';
|
|
2
2
|
export type { CreateOptions, RunOptions, ValidateOptions, MergeOptions, RemoveOptions } from './command.js';
|
|
3
|
-
export type { WorktreeInfo } from './worktree.js';
|
|
3
|
+
export type { WorktreeInfo, WorktreeStatus } from './worktree.js';
|
|
4
4
|
export type { ClaudeCodeResult } from './claudeCode.js';
|
|
5
5
|
export type { TaskResult, TaskSummary } from './taskResult.js';
|
package/src/types/worktree.ts
CHANGED
|
@@ -5,3 +5,15 @@ export interface WorktreeInfo {
|
|
|
5
5
|
/** 分支名 */
|
|
6
6
|
branch: string;
|
|
7
7
|
}
|
|
8
|
+
|
|
9
|
+
/** worktree 变更统计信息 */
|
|
10
|
+
export interface WorktreeStatus {
|
|
11
|
+
/** 相对于主分支的新增提交数 */
|
|
12
|
+
commitCount: number;
|
|
13
|
+
/** 新增行数 */
|
|
14
|
+
insertions: number;
|
|
15
|
+
/** 删除行数 */
|
|
16
|
+
deletions: number;
|
|
17
|
+
/** 工作区是否有未提交修改 */
|
|
18
|
+
hasDirtyFiles: boolean;
|
|
19
|
+
}
|
package/src/utils/formatter.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { MESSAGES } from '../constants/index.js';
|
|
3
3
|
import { createInterface } from 'node:readline';
|
|
4
|
+
import type { WorktreeStatus } from '../types/index.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* 输出成功信息
|
|
@@ -65,3 +66,32 @@ export function confirmAction(question: string): Promise<boolean> {
|
|
|
65
66
|
});
|
|
66
67
|
});
|
|
67
68
|
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 将 WorktreeStatus 格式化为带颜色的字符串
|
|
72
|
+
* @param {WorktreeStatus} status - worktree 变更统计信息
|
|
73
|
+
* @returns {string} 格式化后的状态字符串
|
|
74
|
+
*/
|
|
75
|
+
export function formatWorktreeStatus(status: WorktreeStatus): string {
|
|
76
|
+
const parts: string[] = [];
|
|
77
|
+
|
|
78
|
+
// 提交数(黄色)
|
|
79
|
+
parts.push(chalk.yellow(`${status.commitCount} 个提交`));
|
|
80
|
+
|
|
81
|
+
// 变更统计
|
|
82
|
+
if (status.insertions === 0 && status.deletions === 0) {
|
|
83
|
+
parts.push('无变更');
|
|
84
|
+
} else {
|
|
85
|
+
const diffParts: string[] = [];
|
|
86
|
+
diffParts.push(chalk.green(`+${status.insertions}`));
|
|
87
|
+
diffParts.push(chalk.red(`-${status.deletions}`));
|
|
88
|
+
parts.push(diffParts.join(' '));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 未提交修改提示(灰色)
|
|
92
|
+
if (status.hasDirtyFiles) {
|
|
93
|
+
parts.push(chalk.gray('(未提交修改)'));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return parts.join(' ');
|
|
97
|
+
}
|
package/src/utils/git.ts
CHANGED
|
@@ -241,3 +241,58 @@ export function hasLocalCommits(branchName: string, cwd?: string): boolean {
|
|
|
241
241
|
return false;
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 获取目标分支相对于当前分支的新增提交数
|
|
247
|
+
* @param {string} branchName - 目标分支名
|
|
248
|
+
* @param {string} [cwd] - 工作目录
|
|
249
|
+
* @returns {number} 新增提交数
|
|
250
|
+
*/
|
|
251
|
+
export function getCommitCountAhead(branchName: string, cwd?: string): number {
|
|
252
|
+
const output = execCommand(`git rev-list --count HEAD..${branchName}`, { cwd });
|
|
253
|
+
return parseInt(output, 10) || 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 解析 git diff --shortstat 输出,提取新增行数和删除行数
|
|
258
|
+
* @param {string} output - shortstat 输出字符串
|
|
259
|
+
* @returns {{ insertions: number; deletions: number }} 新增和删除行数
|
|
260
|
+
*/
|
|
261
|
+
function parseShortStat(output: string): { insertions: number; deletions: number } {
|
|
262
|
+
let insertions = 0;
|
|
263
|
+
let deletions = 0;
|
|
264
|
+
|
|
265
|
+
const insertMatch = output.match(/(\d+)\s+insertion/);
|
|
266
|
+
if (insertMatch) {
|
|
267
|
+
insertions = parseInt(insertMatch[1], 10);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const deleteMatch = output.match(/(\d+)\s+deletion/);
|
|
271
|
+
if (deleteMatch) {
|
|
272
|
+
deletions = parseInt(deleteMatch[1], 10);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return { insertions, deletions };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 获取目标分支的变更统计(已提交 + 未提交)
|
|
280
|
+
* @param {string} branchName - 目标分支名
|
|
281
|
+
* @param {string} worktreePath - worktree 目录路径
|
|
282
|
+
* @param {string} [cwd] - 执行 git diff HEAD...branch 的工作目录
|
|
283
|
+
* @returns {{ insertions: number; deletions: number }} 聚合后的新增和删除行数
|
|
284
|
+
*/
|
|
285
|
+
export function getDiffStat(branchName: string, worktreePath: string, cwd?: string): { insertions: number; deletions: number } {
|
|
286
|
+
// 已提交的变更(当前分支与目标分支的差异)
|
|
287
|
+
const committedOutput = execCommand(`git diff --shortstat HEAD...${branchName}`, { cwd });
|
|
288
|
+
const committed = parseShortStat(committedOutput);
|
|
289
|
+
|
|
290
|
+
// 未提交的变更(在 worktree 内执行)
|
|
291
|
+
const uncommittedOutput = execCommand('git diff --shortstat HEAD', { cwd: worktreePath });
|
|
292
|
+
const uncommitted = parseShortStat(uncommittedOutput);
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
insertions: committed.insertions + uncommitted.insertions,
|
|
296
|
+
deletions: committed.deletions + uncommitted.deletions,
|
|
297
|
+
};
|
|
298
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -25,11 +25,13 @@ export {
|
|
|
25
25
|
gitWorktreeList,
|
|
26
26
|
gitWorktreePrune,
|
|
27
27
|
hasLocalCommits,
|
|
28
|
+
getCommitCountAhead,
|
|
29
|
+
getDiffStat,
|
|
28
30
|
} from './git.js';
|
|
29
31
|
export { sanitizeBranchName, generateBranchNames, validateBranchesNotExist } from './branch.js';
|
|
30
32
|
export { validateMainWorktree, validateGitInstalled, validateClaudeCodeInstalled } from './validation.js';
|
|
31
|
-
export { createWorktrees, getProjectWorktrees, getProjectWorktreeDir, cleanupWorktrees } from './worktree.js';
|
|
33
|
+
export { createWorktrees, getProjectWorktrees, getProjectWorktreeDir, cleanupWorktrees, getWorktreeStatus } from './worktree.js';
|
|
32
34
|
export { loadConfig, getConfigValue, ensureClawtDirs } from './config.js';
|
|
33
|
-
export { printSuccess, printError, printWarning, printInfo, printSeparator, printDoubleSeparator, confirmAction } from './formatter.js';
|
|
35
|
+
export { printSuccess, printError, printWarning, printInfo, printSeparator, printDoubleSeparator, confirmAction, formatWorktreeStatus } from './formatter.js';
|
|
34
36
|
export { ensureDir, removeEmptyDir } from './fs.js';
|
|
35
37
|
export { multilineInput } from './prompt.js';
|
package/src/utils/worktree.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { join } from 'node:path';
|
|
|
2
2
|
import { existsSync, readdirSync } from 'node:fs';
|
|
3
3
|
import { WORKTREES_DIR } from '../constants/index.js';
|
|
4
4
|
import { logger } from '../logger/index.js';
|
|
5
|
-
import { createWorktree as gitCreateWorktree, getProjectName, gitWorktreeList, removeWorktreeByPath, deleteBranch, gitWorktreePrune } from './git.js';
|
|
5
|
+
import { createWorktree as gitCreateWorktree, getProjectName, gitWorktreeList, removeWorktreeByPath, deleteBranch, gitWorktreePrune, getCommitCountAhead, getDiffStat, isWorkingDirClean } from './git.js';
|
|
6
6
|
import { sanitizeBranchName, generateBranchNames, validateBranchesNotExist } from './branch.js';
|
|
7
7
|
import { ensureDir, removeEmptyDir } from './fs.js';
|
|
8
|
-
import type { WorktreeInfo } from '../types/index.js';
|
|
8
|
+
import type { WorktreeInfo, WorktreeStatus } from '../types/index.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* 获取当前项目的 worktree 存放目录
|
|
@@ -105,3 +105,22 @@ export function cleanupWorktrees(worktrees: WorktreeInfo[]): void {
|
|
|
105
105
|
const projectDir = getProjectWorktreeDir();
|
|
106
106
|
removeEmptyDir(projectDir);
|
|
107
107
|
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 获取 worktree 的变更统计信息
|
|
111
|
+
* 聚合提交数、变更行数、未提交修改状态
|
|
112
|
+
* @param {WorktreeInfo} worktree - worktree 信息
|
|
113
|
+
* @returns {WorktreeStatus | null} 变更统计信息,获取失败时返回 null
|
|
114
|
+
*/
|
|
115
|
+
export function getWorktreeStatus(worktree: WorktreeInfo): WorktreeStatus | null {
|
|
116
|
+
try {
|
|
117
|
+
const commitCount = getCommitCountAhead(worktree.branch);
|
|
118
|
+
const { insertions, deletions } = getDiffStat(worktree.branch, worktree.path);
|
|
119
|
+
const hasDirtyFiles = !isWorkingDirClean(worktree.path);
|
|
120
|
+
|
|
121
|
+
return { commitCount, insertions, deletions, hasDirtyFiles };
|
|
122
|
+
} catch (error) {
|
|
123
|
+
logger.error(`获取 worktree 状态失败: ${worktree.path} - ${error}`);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|