clawt 2.7.3 → 2.8.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 +11 -0
- package/README.md +14 -1
- package/dist/index.js +78 -23
- package/docs/spec.md +42 -0
- package/package.json +1 -1
- package/src/commands/merge.ts +17 -2
- package/src/constants/index.ts +1 -0
- package/src/constants/logger.ts +5 -0
- package/src/constants/messages.ts +6 -1
- package/src/index.ts +10 -2
- package/src/logger/index.ts +50 -1
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
|
|
17
17
|
### README.md
|
|
18
18
|
- 面向用户的使用文档
|
|
19
|
+
- 全局选项在 `## 全局选项` 章节(位于 `## 使用前提` 和 `## 命令` 之间)
|
|
19
20
|
- 每个命令一个 `###` 小节,含命令格式、参数表格、简要说明、示例
|
|
20
21
|
- 配置文件说明在 `## 配置文件` 章节
|
|
22
|
+
- 日志说明在 `## 日志` 章节(文档末尾)
|
|
21
23
|
- 更新模式:更新命令说明段落,配置项表格
|
|
22
24
|
|
|
23
25
|
## 关键约定
|
|
@@ -90,3 +92,12 @@ Notes:
|
|
|
90
92
|
- `removeSnapshot()` 同时清理 `.tree` 和 `.head` 文件
|
|
91
93
|
- merge 成功后自动清理对应快照;merge 时主 worktree 脏 + 存在快照会输出警告提示
|
|
92
94
|
- docs/spec.md 中 validate 章节(5.4)按 `--clean 模式`、`首次 validate`、`增量 validate` 三段描述
|
|
95
|
+
|
|
96
|
+
## 全局选项
|
|
97
|
+
|
|
98
|
+
- `--debug` 全局选项在 `src/index.ts` 通过 Commander.js `.option()` + `preAction` 钩子实现
|
|
99
|
+
- `enableConsoleTransport()` 在 `src/logger/index.ts`,幂等地向 winston 添加 Console transport
|
|
100
|
+
- 调试相关常量在 `src/constants/logger.ts`:`DEBUG_LOG_PREFIX`、`DEBUG_TIMESTAMP_FORMAT`
|
|
101
|
+
- docs/spec.md 中 `--debug` 说明位于 `5.9 日志系统` 章节下的 `#### --debug 控制台调试输出` 子章节
|
|
102
|
+
- docs/spec.md 中 `4. 命令总览` 的命令表格后有 `**全局选项:**` 表格
|
|
103
|
+
- README.md 中 `## 全局选项` 章节(在 `## 使用前提` 和 `## 命令` 之间)
|
package/README.md
CHANGED
|
@@ -18,6 +18,19 @@ npm i -g clawt
|
|
|
18
18
|
|
|
19
19
|
所有命令**必须在主 worktree 的仓库根目录**下执行(即包含 `.git` 目录的原始仓库)。在子 worktree 或子目录中执行会被拒绝。
|
|
20
20
|
|
|
21
|
+
## 全局选项
|
|
22
|
+
|
|
23
|
+
| 选项 | 说明 |
|
|
24
|
+
| ---- | ---- |
|
|
25
|
+
| `--debug` | 输出详细调试信息到终端,实时显示带颜色和时间戳的日志 |
|
|
26
|
+
|
|
27
|
+
`--debug` 可与任意子命令组合使用:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
clawt run -b feature-login --debug
|
|
31
|
+
clawt validate -b scheme --debug
|
|
32
|
+
```
|
|
33
|
+
|
|
21
34
|
## 命令
|
|
22
35
|
|
|
23
36
|
### `clawt create` — 批量创建 worktree
|
|
@@ -313,4 +326,4 @@ feature/a.b → feature-a-b
|
|
|
313
326
|
|
|
314
327
|
## 日志
|
|
315
328
|
|
|
316
|
-
日志保存在 `~/.clawt/logs/` 目录,按日期滚动,保留 30
|
|
329
|
+
日志保存在 `~/.clawt/logs/` 目录,按日期滚动,保留 30 天。使用 `--debug` 全局选项可在终端实时查看调试日志。
|
package/dist/index.js
CHANGED
|
@@ -55,7 +55,7 @@ var MESSAGES = {
|
|
|
55
55
|
/** merge 成功(无提交信息,目标 worktree 已提交过) */
|
|
56
56
|
MERGE_SUCCESS_NO_MESSAGE: (branch, pushed) => `\u2713 \u5206\u652F ${branch} \u5DF2\u6210\u529F\u5408\u5E76\u5230\u5F53\u524D\u5206\u652F${pushed ? "\n \u5DF2\u63A8\u9001\u5230\u8FDC\u7A0B\u4ED3\u5E93" : ""}`,
|
|
57
57
|
/** merge 冲突 */
|
|
58
|
-
MERGE_CONFLICT: "\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u5904\u7406",
|
|
58
|
+
MERGE_CONFLICT: "\u5408\u5E76\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u624B\u52A8\u5904\u7406\uFF1A\n \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git merge --continue",
|
|
59
59
|
/** merge 后清理 worktree 和分支成功 */
|
|
60
60
|
WORKTREE_CLEANED: (branch) => `\u2713 \u5DF2\u6E05\u7406 worktree \u548C\u5206\u652F: ${branch}`,
|
|
61
61
|
/** 请提供提交信息 */
|
|
@@ -166,7 +166,11 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
|
|
|
166
166
|
/** sync 交互选择提示 */
|
|
167
167
|
SYNC_SELECT_BRANCH: "\u8BF7\u9009\u62E9\u8981\u540C\u6B65\u7684\u5206\u652F",
|
|
168
168
|
/** sync 模糊匹配到多个结果提示 */
|
|
169
|
-
SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A
|
|
169
|
+
SYNC_MULTIPLE_MATCHES: (name) => `"${name}" \u5339\u914D\u5230\u591A\u4E2A\u5206\u652F\uFF0C\u8BF7\u9009\u62E9\uFF1A`,
|
|
170
|
+
/** merge 后 pull 冲突 */
|
|
171
|
+
PULL_CONFLICT: "\u81EA\u52A8 pull \u65F6\u53D1\u751F\u51B2\u7A81\uFF0Cmerge \u5DF2\u5B8C\u6210\u4F46\u8FDC\u7A0B\u540C\u6B65\u5931\u8D25\n \u8BF7\u624B\u52A8\u89E3\u51B3\u51B2\u7A81\uFF1A\n \u89E3\u51B3\u51B2\u7A81\u540E\u6267\u884C git add . && git commit\n \u7136\u540E\u6267\u884C git push \u63A8\u9001\u5230\u8FDC\u7A0B",
|
|
172
|
+
/** push 失败 */
|
|
173
|
+
PUSH_FAILED: "\u81EA\u52A8 push \u5931\u8D25\uFF0Cmerge \u548C pull \u5DF2\u5B8C\u6210\n \u8BF7\u624B\u52A8\u6267\u884C git push"
|
|
170
174
|
};
|
|
171
175
|
|
|
172
176
|
// src/constants/exitCodes.ts
|
|
@@ -217,6 +221,9 @@ var CONFIG_DESCRIPTIONS = deriveConfigDescriptions(CONFIG_DEFINITIONS);
|
|
|
217
221
|
// src/constants/git.ts
|
|
218
222
|
var AUTO_SAVE_COMMIT_MESSAGE = "chore: auto-save before sync";
|
|
219
223
|
|
|
224
|
+
// src/constants/logger.ts
|
|
225
|
+
var DEBUG_TIMESTAMP_FORMAT = "HH:mm:ss.SSS";
|
|
226
|
+
|
|
220
227
|
// src/errors/index.ts
|
|
221
228
|
var ClawtError = class extends Error {
|
|
222
229
|
/** 退出码 */
|
|
@@ -235,6 +242,7 @@ var ClawtError = class extends Error {
|
|
|
235
242
|
// src/logger/index.ts
|
|
236
243
|
import winston from "winston";
|
|
237
244
|
import DailyRotateFile from "winston-daily-rotate-file";
|
|
245
|
+
import chalk from "chalk";
|
|
238
246
|
import { existsSync, mkdirSync } from "fs";
|
|
239
247
|
if (!existsSync(LOGS_DIR)) {
|
|
240
248
|
mkdirSync(LOGS_DIR, { recursive: true });
|
|
@@ -258,6 +266,35 @@ var logger = winston.createLogger({
|
|
|
258
266
|
),
|
|
259
267
|
transports: [dailyRotateTransport]
|
|
260
268
|
});
|
|
269
|
+
var LEVEL_COLORS = {
|
|
270
|
+
error: chalk.red,
|
|
271
|
+
warn: chalk.yellow,
|
|
272
|
+
info: chalk.cyan,
|
|
273
|
+
debug: chalk.gray
|
|
274
|
+
};
|
|
275
|
+
function colorizeLevel(level) {
|
|
276
|
+
const colorFn = LEVEL_COLORS[level] || chalk.white;
|
|
277
|
+
return colorFn(level.toUpperCase().padEnd(5));
|
|
278
|
+
}
|
|
279
|
+
function enableConsoleTransport() {
|
|
280
|
+
const hasConsole = logger.transports.some(
|
|
281
|
+
(t) => t instanceof winston.transports.Console
|
|
282
|
+
);
|
|
283
|
+
if (hasConsole) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const consoleFormat = winston.format.printf(({ level, message, timestamp }) => {
|
|
287
|
+
return `${chalk.gray(timestamp)} ${colorizeLevel(level)} ${message}`;
|
|
288
|
+
});
|
|
289
|
+
const consoleTransport = new winston.transports.Console({
|
|
290
|
+
level: "debug",
|
|
291
|
+
format: winston.format.combine(
|
|
292
|
+
winston.format.timestamp({ format: DEBUG_TIMESTAMP_FORMAT }),
|
|
293
|
+
consoleFormat
|
|
294
|
+
)
|
|
295
|
+
});
|
|
296
|
+
logger.add(consoleTransport);
|
|
297
|
+
}
|
|
261
298
|
|
|
262
299
|
// src/utils/shell.ts
|
|
263
300
|
import { execSync, execFileSync, spawn } from "child_process";
|
|
@@ -463,16 +500,16 @@ function gitApplyCachedCheck(patchContent, cwd) {
|
|
|
463
500
|
}
|
|
464
501
|
|
|
465
502
|
// src/utils/formatter.ts
|
|
466
|
-
import
|
|
503
|
+
import chalk2 from "chalk";
|
|
467
504
|
import { createInterface } from "readline";
|
|
468
505
|
function printSuccess(message) {
|
|
469
|
-
console.log(
|
|
506
|
+
console.log(chalk2.green(message));
|
|
470
507
|
}
|
|
471
508
|
function printError(message) {
|
|
472
|
-
console.error(
|
|
509
|
+
console.error(chalk2.red(`\u2717 ${message}`));
|
|
473
510
|
}
|
|
474
511
|
function printWarning(message) {
|
|
475
|
-
console.log(
|
|
512
|
+
console.log(chalk2.yellow(`\u26A0 ${message}`));
|
|
476
513
|
}
|
|
477
514
|
function printInfo(message) {
|
|
478
515
|
console.log(message);
|
|
@@ -496,7 +533,7 @@ function confirmAction(question) {
|
|
|
496
533
|
});
|
|
497
534
|
}
|
|
498
535
|
function confirmDestructiveAction(dangerousCommand, description) {
|
|
499
|
-
printWarning(`\u5373\u5C06\u6267\u884C ${
|
|
536
|
+
printWarning(`\u5373\u5C06\u6267\u884C ${chalk2.red.bold(dangerousCommand)}\uFF0C${description}`);
|
|
500
537
|
return confirmAction("\u662F\u5426\u7EE7\u7EED\uFF1F");
|
|
501
538
|
}
|
|
502
539
|
function isWorktreeIdle(status) {
|
|
@@ -504,17 +541,17 @@ function isWorktreeIdle(status) {
|
|
|
504
541
|
}
|
|
505
542
|
function formatWorktreeStatus(status) {
|
|
506
543
|
const parts = [];
|
|
507
|
-
parts.push(
|
|
544
|
+
parts.push(chalk2.yellow(`${status.commitCount} \u4E2A\u63D0\u4EA4`));
|
|
508
545
|
if (status.insertions === 0 && status.deletions === 0) {
|
|
509
546
|
parts.push("\u65E0\u53D8\u66F4");
|
|
510
547
|
} else {
|
|
511
548
|
const diffParts = [];
|
|
512
|
-
diffParts.push(
|
|
513
|
-
diffParts.push(
|
|
549
|
+
diffParts.push(chalk2.green(`+${status.insertions}`));
|
|
550
|
+
diffParts.push(chalk2.red(`-${status.deletions}`));
|
|
514
551
|
parts.push(diffParts.join(" "));
|
|
515
552
|
}
|
|
516
553
|
if (status.hasDirtyFiles) {
|
|
517
|
-
parts.push(
|
|
554
|
+
parts.push(chalk2.gray("(\u672A\u63D0\u4EA4\u4FEE\u6539)"));
|
|
518
555
|
}
|
|
519
556
|
return parts.join(" ");
|
|
520
557
|
}
|
|
@@ -822,7 +859,7 @@ async function resolveTargetWorktree(worktrees, messages, branchName) {
|
|
|
822
859
|
}
|
|
823
860
|
|
|
824
861
|
// src/commands/list.ts
|
|
825
|
-
import
|
|
862
|
+
import chalk3 from "chalk";
|
|
826
863
|
function registerListCommand(program2) {
|
|
827
864
|
program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
|
|
828
865
|
handleList(options);
|
|
@@ -859,12 +896,12 @@ function printListAsText(projectName, worktrees) {
|
|
|
859
896
|
for (const wt of worktrees) {
|
|
860
897
|
const status = getWorktreeStatus(wt);
|
|
861
898
|
const isIdle = status ? isWorktreeIdle(status) : false;
|
|
862
|
-
const pathDisplay = isIdle ?
|
|
899
|
+
const pathDisplay = isIdle ? chalk3.hex("#FF8C00")(wt.path) : wt.path;
|
|
863
900
|
printInfo(` ${pathDisplay} [${wt.branch}]`);
|
|
864
901
|
if (status) {
|
|
865
902
|
printInfo(` ${formatWorktreeStatus(status)}`);
|
|
866
903
|
} else {
|
|
867
|
-
printInfo(` ${
|
|
904
|
+
printInfo(` ${chalk3.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
|
|
868
905
|
}
|
|
869
906
|
printInfo("");
|
|
870
907
|
}
|
|
@@ -1430,8 +1467,21 @@ async function handleMerge(options) {
|
|
|
1430
1467
|
}
|
|
1431
1468
|
const autoPullPush = getConfigValue("autoPullPush");
|
|
1432
1469
|
if (autoPullPush) {
|
|
1433
|
-
|
|
1434
|
-
|
|
1470
|
+
try {
|
|
1471
|
+
gitPull(mainWorktreePath);
|
|
1472
|
+
} catch {
|
|
1473
|
+
if (hasMergeConflict(mainWorktreePath)) {
|
|
1474
|
+
printWarning(MESSAGES.PULL_CONFLICT);
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
throw new ClawtError(MESSAGES.PULL_CONFLICT);
|
|
1478
|
+
}
|
|
1479
|
+
try {
|
|
1480
|
+
gitPush(mainWorktreePath);
|
|
1481
|
+
} catch {
|
|
1482
|
+
printWarning(MESSAGES.PUSH_FAILED);
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1435
1485
|
} else {
|
|
1436
1486
|
printInfo("\u5DF2\u8DF3\u8FC7\u81EA\u52A8 pull/push\uFF0C\u8BF7\u624B\u52A8\u6267\u884C git pull && git push");
|
|
1437
1487
|
}
|
|
@@ -1450,7 +1500,7 @@ async function handleMerge(options) {
|
|
|
1450
1500
|
}
|
|
1451
1501
|
|
|
1452
1502
|
// src/commands/config.ts
|
|
1453
|
-
import
|
|
1503
|
+
import chalk4 from "chalk";
|
|
1454
1504
|
function registerConfigCommand(program2) {
|
|
1455
1505
|
const configCmd = program2.command("config").description("\u67E5\u770B\u548C\u7BA1\u7406\u5168\u5C40\u914D\u7F6E").action(() => {
|
|
1456
1506
|
handleConfig();
|
|
@@ -1463,7 +1513,7 @@ function handleConfig() {
|
|
|
1463
1513
|
const config = loadConfig();
|
|
1464
1514
|
logger.info("config \u547D\u4EE4\u6267\u884C\uFF0C\u5C55\u793A\u5168\u5C40\u914D\u7F6E");
|
|
1465
1515
|
printInfo(`
|
|
1466
|
-
${
|
|
1516
|
+
${chalk4.dim("\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84:")} ${CONFIG_PATH}
|
|
1467
1517
|
`);
|
|
1468
1518
|
printSeparator();
|
|
1469
1519
|
const keys = Object.keys(DEFAULT_CONFIG);
|
|
@@ -1473,8 +1523,8 @@ ${chalk3.dim("\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84:")} ${CONFIG_PATH}
|
|
|
1473
1523
|
const description = CONFIG_DESCRIPTIONS[key];
|
|
1474
1524
|
const formattedValue = formatConfigValue(value);
|
|
1475
1525
|
if (i === 0) printInfo("");
|
|
1476
|
-
printInfo(` ${
|
|
1477
|
-
printInfo(` ${
|
|
1526
|
+
printInfo(` ${chalk4.bold(key)}: ${formattedValue}`);
|
|
1527
|
+
printInfo(` ${chalk4.dim(description)}`);
|
|
1478
1528
|
printInfo("");
|
|
1479
1529
|
}
|
|
1480
1530
|
printSeparator();
|
|
@@ -1494,9 +1544,9 @@ async function handleConfigReset() {
|
|
|
1494
1544
|
}
|
|
1495
1545
|
function formatConfigValue(value) {
|
|
1496
1546
|
if (typeof value === "boolean") {
|
|
1497
|
-
return value ?
|
|
1547
|
+
return value ? chalk4.green("true") : chalk4.yellow("false");
|
|
1498
1548
|
}
|
|
1499
|
-
return
|
|
1549
|
+
return chalk4.cyan(String(value));
|
|
1500
1550
|
}
|
|
1501
1551
|
|
|
1502
1552
|
// src/commands/sync.ts
|
|
@@ -1587,7 +1637,12 @@ var require2 = createRequire(import.meta.url);
|
|
|
1587
1637
|
var { version } = require2("../package.json");
|
|
1588
1638
|
ensureClawtDirs();
|
|
1589
1639
|
var program = new Command();
|
|
1590
|
-
program.name("clawt").description("\u672C\u5730\u5E76\u884C\u6267\u884C\u591A\u4E2AClaude Code Agent\u4EFB\u52A1\uFF0C\u878D\u5408 Git Worktree \u4E0E Claude Code CLI \u7684\u547D\u4EE4\u884C\u5DE5\u5177").version(version);
|
|
1640
|
+
program.name("clawt").description("\u672C\u5730\u5E76\u884C\u6267\u884C\u591A\u4E2AClaude Code Agent\u4EFB\u52A1\uFF0C\u878D\u5408 Git Worktree \u4E0E Claude Code CLI \u7684\u547D\u4EE4\u884C\u5DE5\u5177").version(version).option("--debug", "\u8F93\u51FA\u8BE6\u7EC6\u8C03\u8BD5\u4FE1\u606F\u5230\u7EC8\u7AEF");
|
|
1641
|
+
program.hook("preAction", (thisCommand) => {
|
|
1642
|
+
if (thisCommand.opts().debug) {
|
|
1643
|
+
enableConsoleTransport();
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1591
1646
|
registerListCommand(program);
|
|
1592
1647
|
registerCreateCommand(program);
|
|
1593
1648
|
registerRemoveCommand(program);
|
package/docs/spec.md
CHANGED
|
@@ -173,6 +173,12 @@ git show-ref --verify refs/heads/<branchName> 2>/dev/null
|
|
|
173
173
|
| `clawt sync` | 将主分支最新代码同步到目标 worktree | 5.12 |
|
|
174
174
|
| `clawt reset` | 重置主 worktree 工作区和暂存区 | 5.13 |
|
|
175
175
|
|
|
176
|
+
**全局选项:**
|
|
177
|
+
|
|
178
|
+
| 选项 | 说明 |
|
|
179
|
+
| --------- | ---------------------------------------- |
|
|
180
|
+
| `--debug` | 输出详细调试信息到终端(启用 Console transport) |
|
|
181
|
+
|
|
176
182
|
所有命令执行前,都必须先执行**主 worktree 校验**(见 [2.1](#21-主-worktree-的定义与定位规则))。
|
|
177
183
|
|
|
178
184
|
---
|
|
@@ -879,6 +885,42 @@ clawt list [--json]
|
|
|
879
885
|
- 日志文件保留 30 天
|
|
880
886
|
- 单个日志文件最大 10MB
|
|
881
887
|
|
|
888
|
+
#### `--debug` 控制台调试输出
|
|
889
|
+
|
|
890
|
+
通过全局选项 `--debug` 可将调试日志实时输出到终端,方便排查问题。
|
|
891
|
+
|
|
892
|
+
**实现机制:**
|
|
893
|
+
|
|
894
|
+
- 在 Commander.js 的 `preAction` 钩子中检测 `--debug` 选项,按需调用 `enableConsoleTransport()` 函数
|
|
895
|
+
- `enableConsoleTransport()` 动态向 winston 实例添加 `Console` transport(level 为 `debug`),该函数幂等,多次调用不会重复添加 transport
|
|
896
|
+
- 相关常量定义在 `src/constants/logger.ts`:
|
|
897
|
+
- `DEBUG_LOG_PREFIX`:控制台调试输出的日志前缀标识
|
|
898
|
+
- `DEBUG_TIMESTAMP_FORMAT`:时间戳格式(`HH:mm:ss.SSS`,精简,不含日期)
|
|
899
|
+
|
|
900
|
+
**控制台日志格式:**
|
|
901
|
+
|
|
902
|
+
```
|
|
903
|
+
HH:mm:ss.SSS LEVEL 消息内容
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
**日志级别颜色映射:**
|
|
907
|
+
|
|
908
|
+
| 级别 | 颜色 |
|
|
909
|
+
| ------- | ------ |
|
|
910
|
+
| `error` | 红色 |
|
|
911
|
+
| `warn` | 黄色 |
|
|
912
|
+
| `info` | 青色 |
|
|
913
|
+
| `debug` | 灰色 |
|
|
914
|
+
|
|
915
|
+
**使用示例:**
|
|
916
|
+
|
|
917
|
+
```bash
|
|
918
|
+
clawt run -b feature-login --debug
|
|
919
|
+
clawt validate -b feature-scheme --debug
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
> **注意:** `--debug` 选项不影响文件日志(file transport),文件日志始终按原有策略写入。控制台输出仅在传入 `--debug` 时启用。
|
|
923
|
+
|
|
882
924
|
---
|
|
883
925
|
|
|
884
926
|
### 5.10 查看和管理全局配置
|
package/package.json
CHANGED
package/src/commands/merge.ts
CHANGED
|
@@ -196,8 +196,23 @@ async function handleMerge(options: MergeOptions): Promise<void> {
|
|
|
196
196
|
// 步骤 7:根据配置决定是否自动 pull 和 push
|
|
197
197
|
const autoPullPush = getConfigValue('autoPullPush');
|
|
198
198
|
if (autoPullPush) {
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
// pull 阶段:检测冲突并给出针对性引导
|
|
200
|
+
try {
|
|
201
|
+
gitPull(mainWorktreePath);
|
|
202
|
+
} catch {
|
|
203
|
+
if (hasMergeConflict(mainWorktreePath)) {
|
|
204
|
+
printWarning(MESSAGES.PULL_CONFLICT);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
throw new ClawtError(MESSAGES.PULL_CONFLICT);
|
|
208
|
+
}
|
|
209
|
+
// push 阶段
|
|
210
|
+
try {
|
|
211
|
+
gitPush(mainWorktreePath);
|
|
212
|
+
} catch {
|
|
213
|
+
printWarning(MESSAGES.PUSH_FAILED);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
201
216
|
} else {
|
|
202
217
|
printInfo('已跳过自动 pull/push,请手动执行 git pull && git push');
|
|
203
218
|
}
|
package/src/constants/index.ts
CHANGED
|
@@ -5,3 +5,4 @@ export { EXIT_CODES } from './exitCodes.js';
|
|
|
5
5
|
export { ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, PASTE_THRESHOLD_MS } from './terminal.js';
|
|
6
6
|
export { DEFAULT_CONFIG, CONFIG_DESCRIPTIONS, APPEND_SYSTEM_PROMPT } from './config.js';
|
|
7
7
|
export { AUTO_SAVE_COMMIT_MESSAGE } from './git.js';
|
|
8
|
+
export { DEBUG_LOG_PREFIX, DEBUG_TIMESTAMP_FORMAT } from './logger.js';
|
|
@@ -39,7 +39,7 @@ export const MESSAGES = {
|
|
|
39
39
|
MERGE_SUCCESS_NO_MESSAGE: (branch: string, pushed: boolean) =>
|
|
40
40
|
`✓ 分支 ${branch} 已成功合并到当前分支${pushed ? '\n 已推送到远程仓库' : ''}`,
|
|
41
41
|
/** merge 冲突 */
|
|
42
|
-
MERGE_CONFLICT: '
|
|
42
|
+
MERGE_CONFLICT: '合并存在冲突,请手动处理:\n 解决冲突后执行 git add . && git merge --continue',
|
|
43
43
|
/** merge 后清理 worktree 和分支成功 */
|
|
44
44
|
WORKTREE_CLEANED: (branch: string) => `✓ 已清理 worktree 和分支: ${branch}`,
|
|
45
45
|
/** 请提供提交信息 */
|
|
@@ -148,4 +148,9 @@ export const MESSAGES = {
|
|
|
148
148
|
SYNC_SELECT_BRANCH: '请选择要同步的分支',
|
|
149
149
|
/** sync 模糊匹配到多个结果提示 */
|
|
150
150
|
SYNC_MULTIPLE_MATCHES: (name: string) => `"${name}" 匹配到多个分支,请选择:`,
|
|
151
|
+
/** merge 后 pull 冲突 */
|
|
152
|
+
PULL_CONFLICT:
|
|
153
|
+
'自动 pull 时发生冲突,merge 已完成但远程同步失败\n 请手动解决冲突:\n 解决冲突后执行 git add . && git commit\n 然后执行 git push 推送到远程',
|
|
154
|
+
/** push 失败 */
|
|
155
|
+
PUSH_FAILED: '自动 push 失败,merge 和 pull 已完成\n 请手动执行 git push',
|
|
151
156
|
} as const;
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { ClawtError } from './errors/index.js';
|
|
4
|
-
import { logger } from './logger/index.js';
|
|
4
|
+
import { logger, enableConsoleTransport } from './logger/index.js';
|
|
5
5
|
import { EXIT_CODES } from './constants/index.js';
|
|
6
6
|
import { printError, ensureClawtDirs } from './utils/index.js';
|
|
7
7
|
import { registerListCommand } from './commands/list.js';
|
|
@@ -27,7 +27,15 @@ const program = new Command();
|
|
|
27
27
|
program
|
|
28
28
|
.name('clawt')
|
|
29
29
|
.description('本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具')
|
|
30
|
-
.version(version)
|
|
30
|
+
.version(version)
|
|
31
|
+
.option('--debug', '输出详细调试信息到终端');
|
|
32
|
+
|
|
33
|
+
// 在子命令 action 执行前检查 --debug 选项,按需启用控制台日志
|
|
34
|
+
program.hook('preAction', (thisCommand) => {
|
|
35
|
+
if (thisCommand.opts().debug) {
|
|
36
|
+
enableConsoleTransport();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
31
39
|
|
|
32
40
|
// 注册所有命令
|
|
33
41
|
registerListCommand(program);
|
package/src/logger/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import winston from 'winston';
|
|
2
2
|
import DailyRotateFile from 'winston-daily-rotate-file';
|
|
3
|
-
import
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { LOGS_DIR, DEBUG_TIMESTAMP_FORMAT } from '../constants/index.js';
|
|
4
5
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
5
6
|
|
|
6
7
|
// 确保日志目录存在
|
|
@@ -32,3 +33,51 @@ export const logger = winston.createLogger({
|
|
|
32
33
|
),
|
|
33
34
|
transports: [dailyRotateTransport],
|
|
34
35
|
});
|
|
36
|
+
|
|
37
|
+
/** 日志级别颜色映射 */
|
|
38
|
+
const LEVEL_COLORS: Record<string, (text: string) => string> = {
|
|
39
|
+
error: chalk.red,
|
|
40
|
+
warn: chalk.yellow,
|
|
41
|
+
info: chalk.cyan,
|
|
42
|
+
debug: chalk.gray,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 为日志级别添加对应颜色
|
|
47
|
+
* @param {string} level - 日志级别
|
|
48
|
+
* @returns {string} 带颜色的日志级别字符串
|
|
49
|
+
*/
|
|
50
|
+
function colorizeLevel(level: string): string {
|
|
51
|
+
const colorFn = LEVEL_COLORS[level] || chalk.white;
|
|
52
|
+
return colorFn(level.toUpperCase().padEnd(5));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 启用控制台日志输出(--debug 模式)
|
|
57
|
+
* 动态向 winston 实例添加 Console transport,输出带颜色和时间戳的调试信息
|
|
58
|
+
* 该函数幂等,多次调用不会重复添加 transport
|
|
59
|
+
*/
|
|
60
|
+
export function enableConsoleTransport(): void {
|
|
61
|
+
// 幂等保护:检查是否已添加过 Console transport
|
|
62
|
+
const hasConsole = logger.transports.some(
|
|
63
|
+
(t) => t instanceof winston.transports.Console,
|
|
64
|
+
);
|
|
65
|
+
if (hasConsole) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** --debug 模式的控制台日志格式:精简时间戳 + 带颜色级别 + 消息 */
|
|
70
|
+
const consoleFormat = winston.format.printf(({ level, message, timestamp }) => {
|
|
71
|
+
return `${chalk.gray(timestamp)} ${colorizeLevel(level)} ${message}`;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const consoleTransport = new winston.transports.Console({
|
|
75
|
+
level: 'debug',
|
|
76
|
+
format: winston.format.combine(
|
|
77
|
+
winston.format.timestamp({ format: DEBUG_TIMESTAMP_FORMAT }),
|
|
78
|
+
consoleFormat,
|
|
79
|
+
),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
logger.add(consoleTransport);
|
|
83
|
+
}
|